refactor: major re-work of completion to edit via buffer instead of query

This means completion no longer changes the buffer in anyway until a
completion menu entry is actually selected. This simplifies (or eliminates)
many edge cases and greatly improves multi-cursor support.
This commit is contained in:
CJ van den Berg 2026-01-30 11:20:48 +01:00
parent 211648b2c9
commit 518af3ab45
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
6 changed files with 107 additions and 244 deletions

View file

@ -13,6 +13,10 @@ const ed = @import("../../editor.zig");
const module_name = @typeName(@This());
const Widget = @import("../../Widget.zig");
const Values = @import("completion_dropdown.zig").Values;
const get_values = @import("completion_dropdown.zig").get_values;
const get_replace_selection = @import("completion_dropdown.zig").get_replace_selection;
pub const label = "Select completion";
pub const name = "completion";
pub const description = "completions";
@ -53,7 +57,7 @@ pub fn load_entries(palette: *Type) !usize {
if (existing.contains(dup_text)) continue;
try existing.put(palette.allocator, dup_text, {});
if (palette.value.replace == null) if (get_replace_selection(values.replace)) |replace| {
if (palette.value.replace == null) if (get_replace_selection(editor, values)) |replace| {
palette.value.replace = replace;
};
const item = (try palette.entries.addOne(palette.allocator));
@ -135,89 +139,6 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.T
);
}
const Values = struct {
label: []const u8,
sort_text: []const u8,
kind: CompletionItemKind,
replace: Buffer.Selection,
additionalTextEdits: []const u8,
label_detail: []const u8,
label_description: []const u8,
detail: []const u8,
documentation: []const u8,
insertText: []const u8,
insertTextFormat: usize,
textEdit_newText: []const u8,
};
fn get_values(item_cbor: []const u8) Values {
var label_: []const u8 = "";
var label_detail: []const u8 = "";
var label_description: []const u8 = "";
var detail: []const u8 = "";
var documentation: []const u8 = "";
var sort_text: []const u8 = "";
var kind: u8 = 0;
var insertText: []const u8 = "";
var insertTextFormat: usize = 0;
var textEdit_newText: []const u8 = "";
var replace: Buffer.Selection = .{};
var additionalTextEdits: []const u8 = &.{};
_ = cbor.match(item_cbor, .{
cbor.any, // file_path
cbor.any, // row
cbor.any, // col
cbor.any, // is_incomplete
cbor.extract(&label_), // label
cbor.extract(&label_detail), // label_detail
cbor.extract(&label_description), // label_description
cbor.extract(&kind), // kind
cbor.extract(&detail), // detail
cbor.extract(&documentation), // documentation
cbor.any, // documentation_kind
cbor.extract(&sort_text), // sortText
cbor.extract(&insertText), // insertText
cbor.extract(&insertTextFormat), // insertTextFormat
cbor.extract(&textEdit_newText), // textEdit_newText
cbor.any, // insert.begin.row
cbor.any, // insert.begin.col
cbor.any, // insert.end.row
cbor.any, // insert.end.col
cbor.extract(&replace.begin.row), // replace.begin.row
cbor.extract(&replace.begin.col), // replace.begin.col
cbor.extract(&replace.end.row), // replace.end.row
cbor.extract(&replace.end.col), // replace.end.col
cbor.extract_cbor(&additionalTextEdits),
}) catch false;
return .{
.label = label_,
.sort_text = sort_text,
.kind = @enumFromInt(kind),
.replace = replace,
.additionalTextEdits = additionalTextEdits,
.label_detail = label_detail,
.label_description = label_description,
.detail = detail,
.documentation = documentation,
.insertTextFormat = insertTextFormat,
.insertText = insertText,
.textEdit_newText = textEdit_newText,
};
}
const TextEdit = struct { newText: []const u8 = &.{}, insert: ?Range = null, replace: ?Range = null };
const Range = struct { start: Position, end: Position };
const Position = struct { line: usize, character: usize };
fn get_replace_selection(replace: Buffer.Selection) ?Buffer.Selection {
return if (replace.empty())
null
else if (tui.get_active_editor()) |edt|
edt.get_completion_replacement_selection(replace)
else
replace;
}
pub fn complete(palette: *Type, _: ?*Type.ButtonType) !void {
palette.menu.activate_selected();
}
@ -241,7 +162,7 @@ pub fn updated(palette: *Type, button_: ?*Type.ButtonType) !void {
const button = button_ orelse return cancel(palette);
const values = get_values(button.opts.label);
const editor = tui.get_active_editor() orelse return error.NotFound;
editor.get_primary().selection = get_replace_selection(values.replace);
editor.get_primary().selection = get_replace_selection(editor, values);
const mv = tui.mainview() orelse return;
try mv.set_info_content(values.label, .replace);