From 5caab3629781fff069affcb6063efaac7ed98c58 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 25 Aug 2025 21:02:53 +0200 Subject: [PATCH 1/4] feat: add basic POC support for kitty multi cursor protocol --- build.zig.zon | 4 ++-- src/renderer/vaxis/renderer.zig | 13 +++++++++++++ src/tui/editor.zig | 15 ++++++++++++--- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 64a5fe7..6ae9a7b 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -30,8 +30,8 @@ .hash = "fuzzig-0.1.1-AAAAALNIAQBmbHr-MPalGuR393Vem2pTQXI7_LXeNJgX", }, .vaxis = .{ - .url = "git+https://github.com/neurocyte/libvaxis?ref=main#beb82aa0a9d77f63462f8ca8250c2b9eecf057a2", - .hash = "vaxis-0.5.1-BWNV_FMWCQDGtkYFKz_85wSkBRO1kynUZaPO1-RCSrQM", + .url = "git+https://github.com/neurocyte/libvaxis?ref=main#b3c4a114dad73dcec94ffef2b0d3373a078c263c", + .hash = "vaxis-0.5.1-BWNV_JkcCQDV7RK7e_lzY4EyrQY5XfBod_C0nQi_q615", }, .zeit = .{ .url = "https://github.com/rockorager/zeit/archive/8fd203f85f597f16e0a525c1f1ca1e0bffded809.tar.gz", diff --git a/src/renderer/vaxis/renderer.zig b/src/renderer/vaxis/renderer.zig index e9e9e02..c065b85 100644 --- a/src/renderer/vaxis/renderer.zig +++ b/src/renderer/vaxis/renderer.zig @@ -382,6 +382,11 @@ pub fn process_renderer_event(self: *Self, msg: []const u8) Error!void { self.vx.caps.rgb = true; }, .cap_color_scheme_updates => {}, + + .cap_multi_cursor => { + self.logger.print("multi cursor capability detected", .{}); + self.vx.caps.multi_cursor = true; + }, } } @@ -535,6 +540,14 @@ pub fn cursor_disable(self: *Self) void { self.vx.screen.cursor_vis = false; } +pub fn clear_all_multi_cursors(self: *Self) !void { + try self.tty.anyWriter().print("\x1b[>0;4 q", .{}); +} + +pub fn show_multi_cursor_yx(self: *Self, y: c_int, x: c_int) !void { + try self.tty.anyWriter().print("\x1b[>-1;2:{d}:{d} q", .{ y + 1, x + 1 }); +} + fn sync_mod_state(self: *Self, keypress: u32, modifiers: vaxis.Key.Modifiers) !void { if (modifiers.ctrl and !self.mods.ctrl and !(keypress == input.key.left_control or keypress == input.key.right_control)) try self.send_sync_key(input.event.press, input.key.left_control, "", modifiers); diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 06c0480..e64168b 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1179,6 +1179,8 @@ pub const Editor = struct { const style = tui.get_selection_style(); const frame = tracy.initZone(@src(), .{ .name = "editor render cursors" }); defer frame.deinit(); + if (tui.config().enable_terminal_cursor and tui.rdr().vx.caps.multi_cursor) + tui.rdr().clear_all_multi_cursors() catch {}; for (self.cursels.items[0 .. self.cursels.items.len - 1]) |*cursel_| if (cursel_.*) |*cursel| { const cursor = try self.get_rendered_cursor(style, cursel); try self.render_cursor_secondary(&cursor, theme, cell_map); @@ -1207,7 +1209,9 @@ pub const Editor = struct { set_cell_map_cursor(cell_map, pos.row, pos.col); const y, const x = self.plane.rel_yx_to_abs(@intCast(pos.row), @intCast(pos.col)); const configured_shape = tui.get_cursor_shape(); - const cursor_shape = if (self.cursels.items.len > 1) switch (configured_shape) { + const cursor_shape = if (tui.rdr().vx.caps.multi_cursor) + configured_shape + else if (self.cursels.items.len > 1) switch (configured_shape) { .beam => .block, .beam_blink => .block_blink, .underline => .block, @@ -1224,8 +1228,13 @@ pub const Editor = struct { fn render_cursor_secondary(self: *Self, cursor: *const Cursor, theme: *const Widget.Theme, cell_map: CellMap) !void { if (self.screen_cursor(cursor)) |pos| { set_cell_map_cursor(cell_map, pos.row, pos.col); - self.plane.cursor_move_yx(@intCast(pos.row), @intCast(pos.col)) catch return; - self.render_cursor_cell(theme.editor_cursor_secondary); + if (tui.config().enable_terminal_cursor and tui.rdr().vx.caps.multi_cursor) { + const y, const x = self.plane.rel_yx_to_abs(@intCast(pos.row), @intCast(pos.col)); + tui.rdr().show_multi_cursor_yx(y, x) catch return; + } else { + self.plane.cursor_move_yx(@intCast(pos.row), @intCast(pos.col)) catch return; + self.render_cursor_cell(theme.editor_cursor_secondary); + } } } From dd5bdfdf8a529f324e4d2620882bf773568a9c91 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 25 Aug 2025 21:13:33 +0200 Subject: [PATCH 2/4] fix(win32): add missing multi cursor function stubs --- src/renderer/win32/renderer.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/renderer/win32/renderer.zig b/src/renderer/win32/renderer.zig index a4d8ed8..17fb191 100644 --- a/src/renderer/win32/renderer.zig +++ b/src/renderer/win32/renderer.zig @@ -476,3 +476,14 @@ pub fn cursor_disable(self: *Self) void { _ = self; //@panic("todo"); } +pub fn clear_all_multi_cursors(self: *Self) !void { + _ = self; + //@panic("todo"); +} +pub fn show_multi_cursor_yx(self: *Self, y: c_int, x: c_int) !void { + _ = self; + _ = y; + _ = x; + //@panic("todo"); +} + From 6ac8ecd2f069badbbe68638352b8f8a7984f0715 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 25 Aug 2025 21:16:12 +0200 Subject: [PATCH 3/4] fix: run zig fmt --- src/renderer/win32/renderer.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/win32/renderer.zig b/src/renderer/win32/renderer.zig index 17fb191..701a631 100644 --- a/src/renderer/win32/renderer.zig +++ b/src/renderer/win32/renderer.zig @@ -486,4 +486,3 @@ pub fn show_multi_cursor_yx(self: *Self, y: c_int, x: c_int) !void { _ = x; //@panic("todo"); } - From 76ef0aff3aba97acf454c3838b7668aa4e954de5 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 25 Aug 2025 21:32:58 +0200 Subject: [PATCH 4/4] fix: minor off-by-one bug in View.is_visible --- src/buffer/View.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/buffer/View.zig b/src/buffer/View.zig index b3909ca..09e8def 100644 --- a/src/buffer/View.zig +++ b/src/buffer/View.zig @@ -67,8 +67,9 @@ inline fn is_at_bottom(self: *const Self, root: Buffer.Root) bool { } pub inline fn is_visible(self: *const Self, cursor: *const Cursor) bool { + if (self.rows == 0) return false; const row_min = self.row; - const row_max = row_min + self.rows; + const row_max = row_min + self.rows - 1; const col_min = self.col; const col_max = col_min + self.cols; return row_min <= cursor.row and cursor.row <= row_max and