From 59de78723ed5965ace0875a5f0cc09cbb9e5b196 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 3 Feb 2026 19:26:41 +0100 Subject: [PATCH 1/2] refactor: import Selection directly into completion_dropdown --- src/tui/mode/overlay/completion_dropdown.zig | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/tui/mode/overlay/completion_dropdown.zig b/src/tui/mode/overlay/completion_dropdown.zig index ee2a1b6..4a8e25d 100644 --- a/src/tui/mode/overlay/completion_dropdown.zig +++ b/src/tui/mode/overlay/completion_dropdown.zig @@ -4,6 +4,7 @@ const tp = @import("thespian"); const root = @import("soft_root").root; const command = @import("command"); const Buffer = @import("Buffer"); +const Selection = Buffer.Selection; const builtin = @import("builtin"); const CompletionItemKind = @import("lsp_types").CompletionItemKind; @@ -33,7 +34,7 @@ pub const ValueType = struct { editor: *ed.Editor = undefined, cursor: ed.Cursor = .{}, view: ed.View = .{}, - query: ?Buffer.Selection = null, + query: ?Selection = null, last_query: ?[]const u8 = null, commands: command.Collection(cmds) = undefined, data: []const u8 = &.{}, @@ -118,7 +119,7 @@ pub fn initial_query(self: *Type, allocator: std.mem.Allocator) error{ Stop, Out fn get_query_text_nostore(self: *Type, cursor: ed.Cursor, allocator: std.mem.Allocator) error{ Stop, OutOfMemory }![]const u8 { return if (self.value.query) |query| blk: { - const sel: Buffer.Selection = .{ .begin = query.begin, .end = cursor }; + const sel: Selection = .{ .begin = query.begin, .end = cursor }; break :blk try self.value.editor.get_selection(sel, allocator); } else allocator.dupe(u8, ""); } @@ -219,8 +220,8 @@ pub const Values = struct { label: []const u8, sort_text: []const u8, kind: CompletionItemKind, - insert: ?Buffer.Selection, - replace: ?Buffer.Selection, + insert: ?Selection, + replace: ?Selection, additionalTextEdits: []const u8, label_detail: []const u8, label_description: []const u8, @@ -282,8 +283,8 @@ pub fn get_values(item_cbor: []const u8) Values { }; } -fn get_range(range_cbor: []const u8) ?Buffer.Selection { - var range: Buffer.Selection = .{}; +fn get_range(range_cbor: []const u8) ?Selection { + var range: Selection = .{}; return if (cbor.match(range_cbor, tp.null_) catch false) null else if (cbor.match(range_cbor, .{ @@ -301,11 +302,11 @@ const TextEdit = struct { newText: []const u8 = &.{}, insert: ?Range = null, rep const Range = struct { start: Position, end: Position }; const Position = struct { line: usize, character: usize }; -pub fn get_query_selection(editor: *ed.Editor, values: Values) ?Buffer.Selection { +pub fn get_query_selection(editor: *ed.Editor, values: Values) ?Selection { return get_replacement_selection(editor, values.insert, values.replace, null); } -fn get_replacement_selection(editor: *ed.Editor, insert_: ?Buffer.Selection, replace_: ?Buffer.Selection, query: ?Buffer.Selection) Buffer.Selection { +fn get_replacement_selection(editor: *ed.Editor, insert_: ?Selection, replace_: ?Selection, query: ?Selection) Selection { const pos = switch (tui.config().completion_insert_mode) { .replace => replace_ orelse insert_, .insert => insert_ orelse replace_, @@ -334,7 +335,7 @@ fn get_replacement_selection(editor: *ed.Editor, insert_: ?Buffer.Selection, rep }; } -fn get_insert_selection(editor: *ed.Editor, values: Values, query: ?Buffer.Selection) Buffer.Selection { +fn get_insert_selection(editor: *ed.Editor, values: Values, query: ?Selection) Selection { return get_replacement_selection(editor, values.insert, values.replace, query); } From 75fa4408fac5c845bebd5cd9b338d187d63653a3 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 3 Feb 2026 19:37:51 +0100 Subject: [PATCH 2/2] fix: guess completion insertion range by back scanning for trigger chars or word breaks closes #484 --- src/tui/editor.zig | 26 ++++++++++++++++++++ src/tui/mode/overlay/completion_dropdown.zig | 15 ++--------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 4c1a659..7cea194 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -4995,6 +4995,32 @@ pub const Editor = struct { _ = self.pop_tabstop(); } + fn is_trigger_left(root: Buffer.Root, cursor: *const Cursor, metrics: Buffer.Metrics, triggers: []const TriggerSymbol) bool { + if (cursor.col == 0) return true; + var next = cursor.*; + next.move_left(root, metrics) catch return true; + const egc_left, _, _ = next.egc_at(root, metrics) catch return true; + switch (egc_left[0]) { + ' ', '\t' => return true, + else => |c| for (triggers) |t| if (c == t.char) return true, + } + return false; + } + + pub fn guest_completion_range(self: *Self) Selection { + var cursel = self.get_primary().*; + var sel = Selection.from_cursor(&cursel.cursor); + if (cursel.cursor.col == 0) return sel; + const root = self.buf_root() catch return sel; + + while (!is_trigger_left(root, &sel.begin, self.metrics, self.get_event_triggers(.insert).items)) + move_cursor_left(root, &sel.begin, self.metrics) catch return sel; + + if (tui.config().completion_insert_mode == .replace) + move_cursor_word_right(root, &sel.end, self.metrics) catch return sel; + return sel; + } + fn replicate_selection(self: *Self, sel: Selection) void { if (sel.begin.row != sel.end.row) return; const primary = self.get_primary(); diff --git a/src/tui/mode/overlay/completion_dropdown.zig b/src/tui/mode/overlay/completion_dropdown.zig index 4a8e25d..4bb279d 100644 --- a/src/tui/mode/overlay/completion_dropdown.zig +++ b/src/tui/mode/overlay/completion_dropdown.zig @@ -314,19 +314,8 @@ fn get_replacement_selection(editor: *ed.Editor, insert_: ?Selection, replace_: var sel = if (pos) |p| p.from_pos(editor.buf_root() catch return ed.Selection.from_cursor(&editor.get_primary().cursor), editor.metrics) - else blk: { - if (query) |sel| break :blk sel; - var cursel = editor.get_primary().*; - var sel = ed.Selection.from_cursor(&cursel.cursor); - if (cursel.cursor.col == 0) break :blk sel; - const root_ = editor.buf_root() catch break :blk sel; - ed.Editor.move_cursor_word_left(root_, &sel.begin, editor.metrics) catch break :blk sel; - if (tui.config().completion_insert_mode == .replace) { - sel.end = sel.begin; - ed.Editor.move_cursor_word_right(root_, &sel.end, editor.metrics) catch break :blk sel; - } - break :blk sel; - }; + else + query orelse editor.guest_completion_range(); sel.normalize(); const cursor = editor.get_primary().cursor; return switch (tui.config().completion_insert_mode) {