From 0abd35b1f4d172bc860c49a588321ba8787555cc Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 5 Jan 2026 12:11:11 +0100 Subject: [PATCH 1/6] refactor: remove spammy trigger debug log messages --- src/tui/lsp_info.zig | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tui/lsp_info.zig b/src/tui/lsp_info.zig index 6659804..d50691a 100644 --- a/src/tui/lsp_info.zig +++ b/src/tui/lsp_info.zig @@ -37,11 +37,10 @@ pub fn add_from_event(self: *@This(), cbor_buf: []const u8) error{ InvalidTrigge .{ cbor.extract(&lsp_arg0), cbor.more }, cbor.extract_cbor(&trigger_characters), }) catch return)) return; - const value = try self.add(lsp_arg0, &trigger_characters); - std.log.debug("{s} triggers: {any}", .{ lsp_arg0, value.trigger_characters.items }); + try self.add(lsp_arg0, &trigger_characters); } -pub fn add(self: *@This(), lsp_arg0: []const u8, iter: *[]const u8) error{ InvalidTriggersArray, OutOfMemory }!*Info { +fn add(self: *@This(), lsp_arg0: []const u8, iter: *[]const u8) error{ InvalidTriggersArray, OutOfMemory }!void { const key = try self.allocator.dupe(u8, lsp_arg0); errdefer self.allocator.free(key); @@ -61,7 +60,6 @@ pub fn add(self: *@This(), lsp_arg0: []const u8, iter: *[]const u8) error{ Inval if (!(cbor.matchValue(iter, cbor.extract(&char)) catch return error.InvalidTriggersArray)) return error.InvalidTriggersArray; (try value.trigger_characters.addOne(self.allocator)).* = try self.allocator.dupe(u8, char); } - return value; } pub fn write_state(self: *@This(), writer: *std.Io.Writer) error{WriteFailed}!void { @@ -81,8 +79,7 @@ pub fn extract_state(self: *@This(), iter: *[]const u8) error{ InvalidTriggersAr var len = cbor.decodeArrayHeader(iter) catch return; while (len > 0) : (len -= 1) { if (cbor.matchValue(iter, .{ cbor.extract(&lsp_arg0), cbor.extract_cbor(&trigger_characters) }) catch false) { - const value = try self.add(lsp_arg0, &trigger_characters); - std.log.debug("restored {s} triggers: {any}", .{ lsp_arg0, value.trigger_characters.items }); + try self.add(lsp_arg0, &trigger_characters); } else { cbor.skipValue(iter) catch return error.InvalidTriggersArray; } From 63275963b5e8ec66799c68a388f253638e87b212 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 6 Jan 2026 18:31:44 +0100 Subject: [PATCH 2/6] refactor: add more split keybindings --- src/keybind/builtin/flow.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index ed65972..a33b479 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -16,6 +16,10 @@ ["ctrl+2", "focus_split", 1], ["ctrl+3", "focus_split", 2], ["ctrl+4", "focus_split", 3], + ["ctrl+5", "focus_split", 4], + ["ctrl+6", "focus_split", 5], + ["ctrl+7", "focus_split", 6], + ["ctrl+8", "focus_split", 7], ["ctrl+j", "toggle_panel"], ["ctrl+q", "quit"], ["ctrl+w", "close_split"], From f7f227dd826ae5bb652b0ed368241146a1208509 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 6 Jan 2026 18:33:42 +0100 Subject: [PATCH 3/6] refactor: broadcast line numbering mode and style changes to all splits --- src/tui/editor_gutter.zig | 30 ++++++++++++++++++++---------- src/tui/mainview.zig | 14 ++++---------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/tui/editor_gutter.zig b/src/tui/editor_gutter.zig index 50224a1..9890635 100644 --- a/src/tui/editor_gutter.zig +++ b/src/tui/editor_gutter.zig @@ -28,8 +28,8 @@ lines: u32 = 0, view_rows: u32 = 1, view_top: u32 = 1, line: usize = 0, -mode: ?LineNumberMode = null, -render_style: DigitStyle, +line_number_mode: ?LineNumberMode = null, +line_number_style: DigitStyle, highlight: bool, symbols: bool, width: usize = 4, @@ -49,8 +49,8 @@ pub fn create(allocator: Allocator, parent: Widget, event_source: Widget, editor .allocator = allocator, .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent.plane.*), .parent = parent, - .mode = tui.config().gutter_line_numbers_mode, - .render_style = tui.config().gutter_line_numbers_style, + .line_number_mode = tui.config().gutter_line_numbers_mode, + .line_number_style = tui.config().gutter_line_numbers_style, .highlight = tui.config().highlight_current_line_gutter, .symbols = tui.config().gutter_symbols, .editor = editor, @@ -94,6 +94,8 @@ pub fn handle_event(self: *Self, _: tp.pid_ref, m: tp.message) tp.result { pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { var y: i32 = undefined; var ypx: i32 = undefined; + var line_number_mode: ?LineNumberMode = null; + var line_number_style: DigitStyle = undefined; if (try m.match(.{ "B", input.event.press, @intFromEnum(input.mouse.BUTTON1), tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) return self.primary_click(y); @@ -107,14 +109,22 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { return self.mouse_click_button4(); if (try m.match(.{ "B", input.event.press, @intFromEnum(input.mouse.BUTTON5), tp.more })) return self.mouse_click_button5(); + if (try m.match(.{ "line_number_mode", tp.extract(&line_number_mode) })) { + self.line_number_mode = line_number_mode; + return false; + } + if (try m.match(.{ "line_number_style", tp.extract(&line_number_style) })) { + self.line_number_style = line_number_style; + return false; + } return false; } fn update_width(self: *Self) void { - if (self.mode == .none) return; + if (self.line_number_mode == .none) return; const width = int_width(self.lines); - self.width = if (self.mode == .relative and width > 4) 4 else @max(width, 2); + self.width = if (self.line_number_mode == .relative and width > 4) 4 else @max(width, 2); self.width += if (self.symbols) 3 else 1; } @@ -123,11 +133,11 @@ pub fn layout(self: *Self) Widget.Layout { } inline fn get_width(self: *Self) usize { - return if (self.mode != .none) self.width else if (self.symbols) 3 else 1; + return if (self.line_number_mode != .none) self.width else if (self.symbols) 3 else 1; } fn get_numbering_mode(self: *const Self) LineNumberMode { - return self.mode orelse switch (if (tui.input_mode()) |mode| mode.line_numbers else .absolute) { + return self.line_number_mode orelse switch (if (tui.input_mode()) |mode| mode.line_numbers else .absolute) { .relative => .relative, .inherit => if (tui.input_mode_outer()) |mode| from_mode_enum(mode.line_numbers) else .absolute, .absolute => .absolute, @@ -190,7 +200,7 @@ pub fn render_linear(self: *Self, theme: *const Widget.Theme) void { self.plane.off_styles(styles.bold); } try self.plane.cursor_move_yx(@intCast(pos), 0); - try self.print_digits(linenum, self.render_style); + try self.print_digits(linenum, self.line_number_style); if (self.highlight and linenum == self.line + 1) self.render_line_highlight(pos, theme); self.render_diff_symbols(&diff_symbols, pos, linenum, theme); @@ -216,7 +226,7 @@ pub fn render_relative(self: *Self, theme: *const Widget.Theme) void { if (val > 999999) _ = self.plane.print_aligned_right(@intCast(pos), "==> ", .{}) catch {} else - self.print_digits(val, self.render_style) catch {}; + self.print_digits(val, self.line_number_style) catch {}; if (self.highlight and linenum == 0) self.render_line_highlight(pos, theme); diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index a68e0cd..af1ff90 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -946,7 +946,7 @@ const cmds = struct { } pub const focus_split_meta: Meta = .{ .description = "Focus split view", .arguments = &.{.integer} }; - pub fn gutter_mode_next(self: *Self, _: Ctx) Result { + pub fn gutter_mode_next(_: *Self, _: Ctx) Result { const config = tui.config_mut(); const mode: ?@import("config").LineNumberMode = if (config.gutter_line_numbers_mode) |mode| switch (mode) { .absolute => .relative, @@ -956,14 +956,11 @@ const cmds = struct { config.gutter_line_numbers_mode = mode; try tui.save_config(); - if (self.widgets.get("editor_gutter")) |gutter_widget| { - const gutter = gutter_widget.dynamic_cast(@import("editor_gutter.zig")) orelse return; - gutter.mode = mode; - } + try tp.self_pid().send(.{ "line_number_mode", mode }); } pub const gutter_mode_next_meta: Meta = .{ .description = "Next gutter mode" }; - pub fn gutter_style_next(self: *Self, _: Ctx) Result { + pub fn gutter_style_next(_: *Self, _: Ctx) Result { const config = tui.config_mut(); config.gutter_line_numbers_style = switch (config.gutter_line_numbers_style) { .ascii => .digital, @@ -972,10 +969,7 @@ const cmds = struct { .superscript => .ascii, }; try tui.save_config(); - if (self.widgets.get("editor_gutter")) |gutter_widget| { - const gutter = gutter_widget.dynamic_cast(@import("editor_gutter.zig")) orelse return; - gutter.render_style = config.gutter_line_numbers_style; - } + try tp.self_pid().send(.{ "line_number_style", config.gutter_line_numbers_style }); } pub const gutter_style_next_meta: Meta = .{ .description = "Next line number style" }; From 902fc0ab75f9d4f2542f8e0387379aa3a22b2ae4 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 6 Jan 2026 19:17:25 +0100 Subject: [PATCH 4/6] refactor: pass gutter scroll events directly to related editor --- src/tui/editor_gutter.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tui/editor_gutter.zig b/src/tui/editor_gutter.zig index 9890635..0ca4f27 100644 --- a/src/tui/editor_gutter.zig +++ b/src/tui/editor_gutter.zig @@ -333,13 +333,13 @@ fn middle_click(_: *Self) error{Exit}!bool { return true; } -fn mouse_click_button4(_: *Self) error{Exit}!bool { - try command.executeName("scroll_up_pageup", .{}); +fn mouse_click_button4(self: *Self) error{Exit}!bool { + self.editor.scroll_up_pageup(.{}) catch {}; return true; } -fn mouse_click_button5(_: *Self) error{Exit}!bool { - try command.executeName("scroll_down_pagedown", .{}); +fn mouse_click_button5(self: *Self) error{Exit}!bool { + self.editor.scroll_down_pagedown(.{}) catch {}; return true; } From c1200ac5bde5a78b12aa504f91a9291ac559705d Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 6 Jan 2026 19:22:24 +0100 Subject: [PATCH 5/6] refactor: make Widget.get method const --- src/tui/Widget.zig | 10 +++++----- src/tui/WidgetList.zig | 2 +- src/tui/mainview.zig | 6 +++--- src/tui/status/tabs.zig | 2 +- src/tui/tui.zig | 5 ++--- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/tui/Widget.zig b/src/tui/Widget.zig index 3e209ce..89a0aef 100644 --- a/src/tui/Widget.zig +++ b/src/tui/Widget.zig @@ -51,7 +51,7 @@ pub const VTable = struct { layout: *const fn (ctx: *anyopaque) Layout, subscribe: *const fn (ctx: *anyopaque, h: EventHandler) error{NotSupported}!void, unsubscribe: *const fn (ctx: *anyopaque, h: EventHandler) error{NotSupported}!void, - get: *const fn (ctx: *anyopaque, name_: []const u8) ?*Self, + get: *const fn (ctx: *const anyopaque, name_: []const u8) ?*const Self, walk: *const fn (ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn, self_widget: *Self) bool, focus: *const fn (ctx: *anyopaque) void, unfocus: *const fn (ctx: *anyopaque) void, @@ -134,8 +134,8 @@ pub fn to(pimpl: anytype) Self { } }.unsubscribe, .get = struct { - pub fn get(ctx: *anyopaque, name_: []const u8) ?*Self { - return if (comptime @hasDecl(child, "get")) child.get(@as(*child, @ptrCast(@alignCast(ctx))), name_) else null; + pub fn get(ctx: *const anyopaque, name_: []const u8) ?*const Self { + return if (comptime @hasDecl(child, "get")) child.get(@as(*const child, @ptrCast(@alignCast(ctx))), name_) else null; } }.get, .walk = struct { @@ -222,7 +222,7 @@ pub fn unsubscribe(self: Self, h: EventHandler) !void { return self.vtable.unsubscribe(self.ptr, h); } -pub fn get(self: *Self, name_: []const u8) ?*Self { +pub fn get(self: *const Self, name_: []const u8) ?*const Self { var buf: [256]u8 = undefined; return if (std.mem.eql(u8, self.plane.name(&buf), name_)) self @@ -297,7 +297,7 @@ pub fn empty(allocator: Allocator, parent: Plane, layout_: Layout) !Self { } }.unsubscribe, .get = struct { - pub fn get(_: *anyopaque, _: []const u8) ?*Self { + pub fn get(_: *const anyopaque, _: []const u8) ?*const Self { return null; } }.get, diff --git a/src/tui/WidgetList.zig b/src/tui/WidgetList.zig index ba62123..91c38aa 100644 --- a/src/tui/WidgetList.zig +++ b/src/tui/WidgetList.zig @@ -115,7 +115,7 @@ pub fn addP(self: *Self, w_: Widget) !*Widget { return &w.widget; } -pub fn get(self: *Self, name_: []const u8) ?*Widget { +pub fn get(self: *const Self, name_: []const u8) ?*const Widget { for (self.widgets.items) |*w| if (w.widget.get(name_)) |p| return p; diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index af1ff90..9e16fb8 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -1634,9 +1634,9 @@ fn add_and_activate_view(self: *Self, widget: Widget) !void { if (self.views.get_at(self.active_view)) |view| view.focus(); } -pub fn find_view_for_widget(self: *Self, w_: *Widget) ?usize { +pub fn find_view_for_widget(self: *Self, w_: *const Widget) ?usize { const Ctx = struct { - w: *Widget, + w: *const Widget, fn find(ctx_: *anyopaque, w: *Widget) bool { const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_))); return ctx.w == w; @@ -1648,7 +1648,7 @@ pub fn find_view_for_widget(self: *Self, w_: *Widget) ?usize { return null; } -pub fn focus_view_by_widget(self: *Self, w: *Widget) tui.FocusAction { +pub fn focus_view_by_widget(self: *Self, w: *const Widget) tui.FocusAction { const n = self.find_view_for_widget(w) orelse return .notfound; if (n >= self.views.widgets.items.len) return .notfound; if (n == self.active_view) return .same; diff --git a/src/tui/status/tabs.zig b/src/tui/status/tabs.zig index f17fcff..5a77307 100644 --- a/src/tui/status/tabs.zig +++ b/src/tui/status/tabs.zig @@ -217,7 +217,7 @@ pub const TabBar = struct { self.plane = self.widget_list.plane; } - pub fn get(self: *Self, name: []const u8) ?*Widget { + pub fn get(self: *const Self, name: []const u8) ?*const Widget { return self.widget_list_widget.get(name); } diff --git a/src/tui/tui.zig b/src/tui/tui.zig index c09bc00..c58df9b 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -788,9 +788,8 @@ fn is_live_widget_ptr(self: *Self, w_: *Widget) bool { pub const FocusAction = enum { same, changed, notfound }; -pub fn set_focus_by_widget(w: *Widget) FocusAction { - const self = current(); - const mv = self.mainview_ orelse return .notfound; +pub fn set_focus_by_widget(w: *const Widget) FocusAction { + const mv = mainview() orelse return .notfound; return mv.focus_view_by_widget(w); } From 9b2edba3b495594a115dbeeab7a0b6e969803223 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 6 Jan 2026 19:22:58 +0100 Subject: [PATCH 6/6] fix: focus editor on gutter click events --- src/tui/editor_gutter.zig | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/tui/editor_gutter.zig b/src/tui/editor_gutter.zig index 0ca4f27..ae30356 100644 --- a/src/tui/editor_gutter.zig +++ b/src/tui/editor_gutter.zig @@ -34,6 +34,7 @@ highlight: bool, symbols: bool, width: usize = 4, editor: *ed.Editor, +editor_widget: ?*const Widget = null, diff_: diff.AsyncDiffer, diff_symbols: std.ArrayList(Symbol), @@ -305,7 +306,17 @@ fn render_diagnostic(self: *Self, diag: *const ed.Diagnostic, theme: *const Widg _ = self.plane.putc(&cell) catch {}; } -fn primary_click(self: *const Self, y_: i32) error{Exit}!bool { +fn focus_editor(self: *Self) void { + const editor_widget = self.editor_widget orelse blk: { + const editor_widget = self.parent.get("editor") orelse return; + self.editor_widget = editor_widget; + break :blk editor_widget; + }; + _ = tui.set_focus_by_widget(editor_widget); +} + +fn primary_click(self: *Self, y_: i32) error{Exit}!bool { + self.focus_editor(); const y = self.editor.plane.abs_y_to_rel(y_); var line = self.view_top + 1; line += @intCast(y); @@ -317,7 +328,8 @@ fn primary_click(self: *const Self, y_: i32) error{Exit}!bool { return true; } -fn primary_drag(self: *const Self, y_: i32) error{Exit}!bool { +fn primary_drag(self: *Self, y_: i32) error{Exit}!bool { + self.focus_editor(); const y = self.editor.plane.abs_y_to_rel(y_); try command.executeName("drag_to", command.fmt(.{ y + 1, 0 })); return true;