summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/Buffer.zig79
-rw-r--r--src/Selection.zig367
-rw-r--r--src/main.janet20
-rw-r--r--src/main.zig13
4 files changed, 314 insertions, 165 deletions
diff --git a/src/Buffer.zig b/src/Buffer.zig
index faee5c4..b1aacaf 100644
--- a/src/Buffer.zig
+++ b/src/Buffer.zig
@@ -27,7 +27,8 @@ const initial_gap_size = std.atomic.cache_line;
 
 const Buffer = @This();
 /// File content with gap, owned by Janet runtime.
-data: []u8,
+data: []const u8,
+file: []const u8,
 gap_position: u32 = 0,
 gap_size: u32 = initial_gap_size,
 parser: *Parser,
@@ -53,10 +54,11 @@ fn read(payload: ?*anyopaque, byte_index: u32, _: Point,
     }
 }
 
-pub fn open(text: []u8) !Buffer {
+pub fn open(text: []const u8, file: []const u8) !Buffer {
     const data = gap: {
+        const unsafe_ptr: *anyopaque = @constCast(@ptrCast(text.ptr));
         const n: i32 = @intCast(text.len);
-        const buffer = janet.pointerBufferUnsafe(@ptrCast(text.ptr), n, n);
+        const buffer = janet.pointerBufferUnsafe(unsafe_ptr, n, n);
         const capacity: usize = @intCast(n + initial_gap_size);
         if (janet.realloc(buffer.*.data, capacity)) |data| {
             janet.gcpressure(initial_gap_size);
@@ -78,6 +80,7 @@ pub fn open(text: []u8) !Buffer {
     try parser.setLanguage(language);
     var result = Buffer{
         .data = data,
+        .file = file,
         .parser = parser,
         .tree = undefined,
         .selection = undefined,
@@ -86,10 +89,8 @@ pub fn open(text: []u8) !Buffer {
         .payload = &result,
         .read = Buffer.read,
     }, null).?;
-    result.selection = .{
-        .head = .{ .node = result.tree.rootNode() },
-        .tail = .{ .node = result.tree.rootNode() },
-    };
+    const span0 = Selection.Unit{ .span = .{ .tree = result.tree } };
+    result.selection = .{ .head = span0, .tail = span0 };
     return result;
 }
 
@@ -99,11 +100,37 @@ pub fn close(buffer: Buffer) void {
     buffer.parser.destroy();
 }
 
+pub fn length(buffer: Buffer) u32 {
+    return @intCast(buffer.data.len - buffer.gap_size);
+}
+
+pub fn graphemeAt(buffer: Buffer, n: u32) Grapheme {
+    const text = if (n < buffer.gap_position)
+        buffer.data[n..buffer.gap_position]
+    else
+        buffer.data[buffer.gap_size..][n..];
+    var iterator = graphemes.iterator(text);
+    return .{ .offset = n, .len = iterator.peek().?.len };
+}
+
+pub fn graphemeBefore(buffer: Buffer, n: u32) ?Grapheme {
+    if (n == 0)
+        return null;
+    const text = if (n <= buffer.gap_position)
+        buffer.data[0..n]
+    else
+        buffer.data[buffer.gap_size..][buffer.gap_position..n];
+    var iterator = graphemes.reverseIterator(text);
+    const len = iterator.peek().?.len;
+    return .{ .offset = n - len, .len = len };
+}
+
 fn skipLines(text: []const u8, from: u32, n: u32) union(enum) {
     done: u32,
     left: u32,
 } {
-    assert(n > 0);
+    if (n == 0)
+        return .{ .done = from };
     var lines: u32 = 0;
     for (text[from..], 1..) |c, i|
         if (c == '\n') {
@@ -138,20 +165,28 @@ pub fn iterate(buffer: Buffer) struct {
         } else return null;
     }
 } {
-    const root_node = buffer.tree.rootNode();
     const point = Point{ .row = buffer.scroll_row, .column = 0 };
-    const node = root_node.descendantForPointRange(point, point).?;
-    const start_point = node.startPoint();
-    assert(start_point.row < point.row or eql(start_point, point));
-
-    const start_byte = node.startByte();
-    const start = if (eql(start_point, point))
-        start_byte
-    else if (start_byte >= buffer.gap_position)
-        skipLines(buffer.data[buffer.gap_size..], start_byte,
-                  point.row - start_point.row).done
-    else switch (skipLines(buffer.data[0..buffer.gap_position], start_byte,
-                           point.row - start_point.row)) {
+    const root_node = buffer.tree.rootNode();
+    const root_start = root_node.startPoint();
+    const start = if (root_start.row < point.row
+                      or eql(root_start, point)) skip: {
+        const node = root_node.descendantForPointRange(point, point).?;
+        const start_point = node.startPoint();
+        const start_byte = node.startByte();
+        if (eql(start_point, point))
+            break :skip start_byte;
+        assert(start_point.row < point.row);
+        break :skip if (start_byte >= buffer.gap_position)
+            skipLines(buffer.data[buffer.gap_size..],
+                      start_byte, point.row - start_point.row).done
+        else switch (skipLines(buffer.data[0..buffer.gap_position],
+                               start_byte, point.row - start_point.row)) {
+            .done => |offset| offset,
+            .left => |left| skipLines(buffer.data[buffer.gap_size..],
+                                      buffer.gap_position, left).done,
+        };
+    } else switch (skipLines(buffer.data[0..buffer.gap_position], 0,
+                             point.row)) {
         .done => |offset| offset,
         .left => |left| skipLines(buffer.data[buffer.gap_size..],
                                   buffer.gap_position, left).done,
@@ -172,7 +207,7 @@ pub fn iterate(buffer: Buffer) struct {
         .graphemes_iterator = graphemes.iterator(buffer.data[offset..]),
         .data = undefined,
         .offset = start,
-        .before_gap = true,
+        .before_gap = false,
         .gap_start = undefined,
         .gap_end = undefined,
     };
diff --git a/src/Selection.zig b/src/Selection.zig
index 352af7a..5e01d32 100644
--- a/src/Selection.zig
+++ b/src/Selection.zig
@@ -2,6 +2,7 @@
 // SPDX-FileCopyrightText: 2025 Nguyễn Gia Phong
 // SPDX-License-Identifier: GPL-3.0-or-later
 
+const assert = std.debug.assert;
 const eql = std.meta.eql;
 const order = std.math.order;
 const std = @import("std");
@@ -13,6 +14,7 @@ const tree_sitter = @import("tree-sitter");
 const Grapheme = vaxis.Graphemes.Grapheme;
 const vaxis = @import("vaxis");
 
+const Buffer = @import("Buffer.zig");
 const graphemes = &@import("root").graphemes;
 
 const Selection = @This();
@@ -37,25 +39,79 @@ pub fn inBody(selection: Selection, grapheme: Grapheme) bool {
             and grapheme.offset + grapheme.len <= selection.endByte());
 }
 
-fn graphemeAt(text: []const u8, index: u32) Grapheme {
-    var iterator = graphemes.iterator(text[index..]);
-    return .{ .offset = index, .len = iterator.peek().?.len };
-}
-
-fn graphemeBefore(text: []const u8, index: u32) Grapheme {
-    var iterator = graphemes.reverseIterator(text[0..index]);
-    return iterator.peek().?;
-}
-
 // TODO: nesting
-const Span = struct {
-    start: u32,
-    end: u32,
+pub const Span = struct {
+    start: u32 = 0,
+    end: u32 = 0,
     tree: *const Tree,
 
-    fn parent(unit: Span) Node {
-        const root_node = unit.tree.rootNode();
-        return root_node.descendantForByteRange(unit.start, unit.end).?;
+    pub fn root(text_len: u32, tree: *const Tree) Span {
+        return .{ .start = 0, .end = text_len, .tree = tree };
+    }
+
+    fn parent(span: Span, text_len: u32) Unit {
+        const root_span = Span.root(text_len, span.tree);
+        if (eql(root_span, span))
+            return .{ .span = span };
+        const root_node = span.tree.rootNode();
+        const root_node_start = root_node.startByte();
+        const root_node_end = root_node.endByte();
+        if (root_node_start == span.start and root_node_end == span.end)
+            return .{ .span = root_span };
+        if (root_node_start > span.start) {
+            const first_span = Span{
+                .start = 0,
+                .end = root_node_start,
+                .tree = span.tree,
+            };
+            return .{
+                .span = if (eql(first_span, span)) root_span else first_span
+            };
+        }
+        if (root_node_end < span.end) {
+            const last_span = Span{
+                .start = root_node_start,
+                .end = text_len,
+                .tree = span.tree,
+            };
+            return .{
+                .span = if (eql(last_span, span)) root_span else last_span
+            };
+        }
+        const parent_node = root_node.descendantForByteRange(span.start,
+                                                             span.end).?;
+        const parent_node_start = parent_node.startByte();
+        assert(parent_node_start <= span.start);
+        const parent_node_end = parent_node.endByte();
+        assert(parent_node_end >= span.end);
+        const parent_span = grow: {
+            if (parent_node.firstChildForByte(span.end)) |next_node| {
+                break :grow if (next_node.prevSibling()) |prev_node| Span{
+                    .start = prev_node.endByte(),
+                    .end = next_node.startByte(),
+                    .tree = span.tree,
+                } else Span{
+                    .start = parent_node_start,
+                    .end = next_node.startByte(),
+                    .tree = span.tree,
+                };
+            } else {
+                const siblings = parent_node.childCount();
+                if (siblings == 0)
+                    return .{ .node = parent_node };
+                const prev_node = parent_node.child(siblings - 1).?;
+                break :grow Span{
+                    .start = prev_node.endByte(),
+                    .end = parent_node_end,
+                    .tree = span.tree,
+                };
+            }
+        };
+        return if (eql(parent_span, span)) .{
+            .node = parent_node
+        } else .{
+            .span = parent_span
+        };
     }
 };
 
@@ -71,51 +127,26 @@ const GraphemeUnit = struct {
         return unit.slice.offset + unit.slice.len;
     }
 
-    fn parent(unit: GraphemeUnit) Span {
-        const start = unit.startByte();
-        const end = unit.endByte();
-        const root_node = unit.tree.rootNode();
-        const parent_node = root_node.descendantForByteRange(start, end).?;
-        if (parent_node.firstChildForByte(end)) |next_node| {
-            return if (next_node.prevSibling()) |prev_node| .{
-                .start = prev_node.endByte(),
-                .end = next_node.startByte(),
-                .tree = unit.tree,
-            } else .{
-                .start = parent_node.startByte(),
-                .end = next_node.startByte(),
-                .tree = unit.tree,
-            };
-        } else {
-            const siblings = parent_node.childCount();
-            if (siblings == 0)
-                return .{
-                    .start = parent_node.startByte(),
-                    .end = parent_node.endByte(),
-                    .tree = unit.tree,
-                };
-            const prev_node = parent_node.child(siblings - 1).?;
-            return .{
-                .start = prev_node.endByte(),
-                .end = parent_node.endByte(),
-                .tree = unit.tree,
-            };
-        }
+    fn parent(unit: GraphemeUnit, text_len: u32) Unit {
+        return Span.parent(.{
+            .start = unit.startByte(),
+            .end = unit.endByte(),
+            .tree = unit.tree,
+        }, text_len);
     }
 
-    fn at(unit: GraphemeUnit, text: []const u8, index: u32) GraphemeUnit {
+    fn at(unit: GraphemeUnit, buffer: Buffer, index: u32) GraphemeUnit {
         return .{
-            .slice = graphemeAt(text, index),
+            .slice = buffer.graphemeAt(index),
             .tree = unit.tree,
         };
     }
 
-    fn before(unit: GraphemeUnit,
-              text: []const u8, index: u32) GraphemeUnit {
-        return .{
-            .slice = graphemeBefore(text, index),
+    fn before(unit: GraphemeUnit, buffer: Buffer, index: u32) ?GraphemeUnit {
+        return if (buffer.graphemeBefore(index)) |grapheme| .{
+            .slice = grapheme,
             .tree = unit.tree,
-        };
+        } else null;
     }
 };
 
@@ -141,103 +172,175 @@ pub const Unit = union(enum) {
         };
     }
 
-    pub fn up(unit: Unit, text: []const u8) Unit {
-        switch (unit) {
-            .node => |node| return if (node.parent()) |parent_node|
-                if (parent_node.eql(node))
-                    .up(.{ .node = parent_node }, text)
-                else
-                    .{ .node = parent_node }
-            else unit,
-            .span => |span| {
-                const parent_node = span.parent();
-                return if (parent_node.startByte() == span.start
-                               and parent_node.endByte() == span.end)
-                    .up(.{ .node = parent_node }, text)
-                else
-                    .{ .node = parent_node };
-            },
-            .grapheme => |grapheme| {
-                const parent_span = grapheme.parent();
-                return if (parent_span.start == grapheme.startByte()
-                               and parent_span.end == grapheme.endByte())
-                    .up(.{ .span = parent_span }, text)
-                else
-                    .{ .span = parent_span };
-            },
-        }
+    fn tree(unit: Unit) *const Tree {
+        return switch (unit) {
+            .node => |node| node.tree,
+            .span => |span| span.tree,
+            .grapheme => |grapheme| grapheme.tree,
+        };
+    }
+
+    fn equal(unit: Unit, other: Unit) bool {
+        return (unit.startByte() == other.startByte()
+                and unit.endByte() == other.endByte());
+    }
+
+    pub fn up(unit: Unit, buffer: Buffer) Unit {
+        const parent = switch (unit) {
+            .node => |node| if (node.parent()) |parent| Unit{
+                .node = parent
+            } else Span.parent(.{
+                .start = node.startByte(),
+                .end = node.endByte(),
+                .tree = node.tree,
+            }, buffer.length()),
+            .span => |span| span.parent(buffer.length()),
+            .grapheme => |grapheme| grapheme.parent(buffer.length()),
+        };
+        return if (!parent.equal(unit) or parent.equal(.{
+            .span = .root(buffer.length(), unit.tree())
+        })) parent else parent.up(buffer);
     }
 
-    pub fn right(unit: Unit, text: []const u8) Unit {
+    pub fn right(unit: Unit, buffer: Buffer) Unit {
         switch (unit) {
-            .node => |node| if (node.nextSibling()) |sibling| {
+            .node => |node| {
                 const prev = node.endByte();
-                const next = sibling.startByte();
-                return switch (order(prev, next)) {
-                    .lt => .{
-                        .span = .{
-                            .start = prev,
-                            .end = next,
-                            .tree = node.tree,
-                        }
-                    },
-                    .eq => .{ .node = sibling },
-                    .gt => unreachable,
-                };
+                const root_node = node.tree.rootNode();
+                if (root_node.eql(node)) {
+                    const span = Span{
+                        .start = prev,
+                        .end = buffer.length(),
+                        .tree = node.tree,
+                    };
+                    if (span.start < span.end)
+                        return .{ .span = span };
+                } else if (node.nextSibling()) |sibling| {
+                    const next = sibling.startByte();
+                    return switch (order(prev, next)) {
+                        .lt => .{
+                            .span = .{
+                                .start = prev,
+                                .end = next,
+                                .tree = node.tree,
+                            }
+                        },
+                        .eq => .{ .node = sibling },
+                        .gt => unreachable,
+                    };
+                }
             },
-            .span => |span| {
-                const parent_node = unit.up(text).node;
-                if (parent_node.firstChildForByte(span.end)) |next_node|
-                    return .{ .node = next_node };
+            .span => |span| switch (unit.up(buffer)) {
+                .node => |parent_node| {
+                    if (parent_node.firstChildForByte(span.end)) |next_node|
+                        return .{ .node = next_node };
+                },
+                .span => |parent_span| {
+                    const root_node = span.tree.rootNode();
+                    if (root_node.startByte() == span.end)
+                        return .{ .node = root_node };
+                    const next_span = Span{
+                        .start = span.end,
+                        .end = parent_span.end,
+                        .tree = span.tree,
+                    };
+                    if (next_span.start < next_span.end)
+                        return .{ .span = next_span };
+                },
+                .grapheme => unreachable,
             },
             .grapheme => |grapheme| {
-                const start = grapheme.endByte();
-                const next_grapheme = grapheme.at(text, start);
-                if (eql(next_grapheme.parent(), grapheme.parent()))
+                const next_grapheme = grapheme.at(buffer, grapheme.endByte());
+                const len = buffer.length();
+                if (next_grapheme.parent(len).equal(grapheme.parent(len)))
                     return .{ .grapheme = next_grapheme };
+                return .right(.{
+                    .span = .{
+                        .start = grapheme.startByte(),
+                        .end = grapheme.endByte(),
+                        .tree = grapheme.tree,
+                    }
+                }, buffer);
             },
         }
-        return .up(unit, text);
+        return unit.up(buffer);
     }
 
-    pub fn left(unit: Unit, text: []const u8) Unit {
+    pub fn left(unit: Unit, buffer: Buffer) Unit {
         switch (unit) {
-            .node => |node| if (node.prevSibling()) |sibling| {
-                const prev = sibling.endByte();
+            .node => |node| {
                 const next = node.startByte();
-                return switch (order(prev, next)) {
-                    .lt => .{
-                        .span = .{
-                            .start = prev,
-                            .end = next,
-                            .tree = node.tree,
-                        }
-                    },
-                    .eq => .{ .node = sibling },
-                    .gt => unreachable,
-                };
-            },
-            .span => |span| {
-                const parent_node = unit.up(text).node;
-                if (parent_node.firstChildForByte(span.end)) |next_node| {
-                    if (next_node.prevSibling()) |prev_node|
-                        return .{ .node = prev_node };
-                } else {
-                    const siblings = parent_node.childCount();
-                    return .{ .node =  parent_node.child(siblings - 1).? };
+                const root_node = node.tree.rootNode();
+                if (root_node.eql(node)) {
+                    const span = Span{
+                        .start = 0,
+                        .end = next,
+                        .tree = node.tree,
+                    };
+                    if (span.start < span.end)
+                        return .{ .span = span };
+                } else if (node.prevSibling()) |sibling| {
+                    const prev = sibling.endByte();
+                    return switch (order(prev, next)) {
+                        .lt => .{
+                            .span = .{
+                                .start = prev,
+                                .end = next,
+                                .tree = node.tree,
+                            }
+                        },
+                        .eq => .{ .node = sibling },
+                        .gt => unreachable,
+                    };
                 }
             },
+            .span => |span| switch (unit.up(buffer)) {
+                .node => |parent_node| {
+                    if (parent_node.firstChildForByte(span.end)) |next_node| {
+                        if (next_node.prevSibling()) |prev_node|
+                            return .{ .node = prev_node };
+                    } else {
+                        const siblings = parent_node.childCount();
+                        if (siblings > 0)
+                            return .{
+                                .node =  parent_node.child(siblings - 1).?
+                            };
+                    }
+                },
+                .span => |parent_span| {
+                    const root_node = span.tree.rootNode();
+                    if (root_node.endByte() == span.start)
+                        return .{ .node = root_node };
+                    const prev_span = Span{
+                        .start = span.start,
+                        .end = parent_span.start,
+                        .tree = span.tree,
+                    };
+                    if (prev_span.start < prev_span.end)
+                        return .{ .span = prev_span };
+                },
+                .grapheme => unreachable,
+            },
             .grapheme => |grapheme| {
-                const end = grapheme.slice.offset;
-                const prev_grapheme = grapheme.before(text, end);
-                if (eql(prev_grapheme.parent(), grapheme.parent()))
-                    return .{ .grapheme = prev_grapheme };
+                const start = grapheme.startByte();
+                if (grapheme.before(buffer, start)) |prev_grapheme| {
+                    const len = buffer.length();
+                    if (prev_grapheme.parent(len).equal(grapheme.parent(len)))
+                        return .{ .grapheme = prev_grapheme };
+                }
+                return .left(.{
+                    .span = .{
+                        .start = start,
+                        .end = grapheme.endByte(),
+                        .tree = grapheme.tree,
+                    }
+                }, buffer);
             },
         }
-        return .up(unit, text);
+        return unit.up(buffer);
     }
 
-    pub fn down(unit: Unit, text: []const u8) Unit {
+    pub fn down(unit: Unit, buffer: Buffer) Unit {
         switch (unit) {
             .node => |node| {
                 const start = node.startByte();
@@ -245,17 +348,17 @@ pub const Unit = union(enum) {
                 // TODO: history
                 return if (node.child(0)) |child|
                     if (child.startByte() == start and child.endByte() == end)
-                        .down(.{ .node = child }, text)
+                        .down(.{ .node = child }, buffer)
                     else
                         .{ .node = child }
                 else .down(.{
                     .span = .{ .start = start, .end = end, .tree = node.tree }
-                }, text);
+                }, buffer);
             },
             .span => |span| return .{
                 .grapheme = .{
                     // TODO: history
-                    .slice = graphemeAt(text, span.start),
+                    .slice = buffer.graphemeAt(span.start),
                     .tree = span.tree,
                 }
             },
diff --git a/src/main.janet b/src/main.janet
index 4034dc9..9a6f42b 100644
--- a/src/main.janet
+++ b/src/main.janet
@@ -4,24 +4,22 @@
 
 (defn kay/select/move
   [buf direction]
-  (let [data (buf :data)
-        selection (buf :selection)]
+  (let [selection (buf :selection)]
     (struct ;(kvs buf)
             :selection (match selection
                          {:head head :tail tail}
                          (struct ;(kvs selection)
-                                 :head (direction head data)
-                                 :tail (direction tail data))))))
+                                 :head (direction head buf)
+                                 :tail (direction tail buf))))))
 
 (defn kay/select/head
   [buf direction]
-  (let [data (buf :data)
-        selection (buf :selection)]
+  (let [selection (buf :selection)]
     (struct ;(kvs buf)
             :selection (match selection
                          {:head head :tail tail}
                          (struct ;(kvs selection)
-                                 :head (direction head data)
+                                 :head (direction head buf)
                                  :tail tail)))))
 
 (defn kay/select/flip
@@ -40,7 +38,7 @@
       (:matches key (chr "c") {:ctrl true}) [[:quit] buf]
       (:matches key (chr "y") {}) (do (:yank kay/env buf)
                                       [[] buf])
-      (:matches key (chr "i") {}) (do (:paste kay/env)
+      (:matches key (chr "i") {}) (do (:request-paste kay/env)
                                       [[] buf])
       (:matches key (chr "p") {}) [[] (struct ;(kvs buf)
                                               :scroll-row (max 0 (dec (buf :scroll-row))))]
@@ -69,6 +67,8 @@
     (run @[(:next-event kay/env)]
          buf)))
 
-(with [buf (kay/open (with [f (file/open (string kay/path))]
-                       (buffer (:read f :all))))]
+(with [buf (kay/open (if-with [f (file/open (string kay/path))]
+                       (buffer (:read f :all))
+                       @"\n")
+                     kay/path)]
   (run @[] buf))
diff --git a/src/main.zig b/src/main.zig
index d26cefa..2a4c39d 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -14,16 +14,18 @@ const ns_per_s = std.time.ns_per_s;
 const smp_allocator = std.heap.smp_allocator;
 const std = @import("std");
 
+const CursorShape = vaxis.Cell.CursorShape;
 const Graphemes = vaxis.Graphemes;
 const Key = vaxis.Key;
 const Loop = vaxis.Loop;
 const Style = vaxis.Cell.Style;
 const Tty = vaxis.Tty;
 const Unicode = vaxis.Unicode;
+const Vaxis = vaxis.Vaxis;
 const Window = vaxis.Window;
 const Winsize = vaxis.Winsize;
 const gwidth = vaxis.gwidth.gwidth;
-const Vaxis = vaxis.Vaxis;
+const ctlseqs = vaxis.ctlseqs;
 const vaxis = @import("vaxis");
 pub const panic = vaxis.panic_handler;
 
@@ -61,10 +63,16 @@ const Environment = struct {
     pub fn render(self: Environment, buffer: Buffer) !void {
         const window = self.vaxis.window();
         window.clear();
+        const sel_start = buffer.selection.startByte();
+        window.screen.cursor_vis = sel_start == buffer.selection.endByte();
         var col: u16 = 0;
         var row: u16 = 0;
         var graphemes_iter = buffer.iterate();
         while (graphemes_iter.next()) |grapheme| {
+            if (window.screen.cursor_vis and grapheme.offset == sel_start) {
+                window.setCursorShape(.beam);
+                window.showCursor(col, row);
+            }
             const in_head = buffer.selection.inHead(grapheme);
             const in_body = buffer.selection.inBody(grapheme);
             const style = Style {
@@ -135,6 +143,9 @@ pub fn main() !void {
 
     var tty = try Tty.init();
     defer tty.deinit();
+    defer tty.anyWriter().print(ctlseqs.cursor_shape, .{
+        @intFromEnum(CursorShape.block), // FIXME: make configurable
+    }) catch {};
     var vx = try vaxis.init(allocator, .{
         .system_clipboard_allocator = allocator,
     });