diff --git a/src/Project.zig b/src/Project.zig index ce09226..017c12c 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -1054,9 +1054,9 @@ fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: var documentation_kind: []const u8 = ""; var sortText: []const u8 = ""; var insertTextFormat: usize = 0; - var textEdit_newText: []const u8 = ""; - var textEdit_insert: ?Range = null; - var textEdit_replace: ?Range = null; + var textEdit: TextEdit = .{}; + var additionalTextEdits: [32]TextEdit = undefined; + var additionalTextEdits_len: usize = 0; var iter = item; var len = cbor.decodeMapHeader(&iter) catch return; @@ -1098,30 +1098,22 @@ fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: } else if (std.mem.eql(u8, field_name, "insertTextFormat")) { if (!(try cbor.matchValue(&iter, cbor.extract(&insertTextFormat)))) return invalid_field("insertTextFormat"); } else if (std.mem.eql(u8, field_name, "textEdit")) { - // var textEdit: []const u8 = ""; // { "newText": "wait_expired(${1:timeout_ns: isize})", "insert": Range, "replace": Range }, - var len_ = cbor.decodeMapHeader(&iter) catch return; + textEdit = try read_textEdit(&iter); + } else if (std.mem.eql(u8, field_name, "additionalTextEdits")) { + var len_ = cbor.decodeArrayHeader(&iter) catch return; + additionalTextEdits_len = len_; + var idx: usize = 0; while (len_ > 0) : (len_ -= 1) { - if (!(try cbor.matchString(&iter, &field_name))) return invalid_field("textEdit"); - if (std.mem.eql(u8, field_name, "newText")) { - if (!(try cbor.matchValue(&iter, cbor.extract(&textEdit_newText)))) return invalid_field("textEdit.newText"); - } else if (std.mem.eql(u8, field_name, "insert")) { - var range_: []const u8 = undefined; - if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&range_)))) return invalid_field("textEdit.insert"); - textEdit_insert = try read_range(range_); - } else if (std.mem.eql(u8, field_name, "replace") or std.mem.eql(u8, field_name, "range")) { - var range_: []const u8 = undefined; - if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&range_)))) return invalid_field("textEdit.replace"); - textEdit_replace = try read_range(range_); - } else { - try cbor.skipValue(&iter); - } + additionalTextEdits[idx] = try read_textEdit(&iter); + idx += 1; } + try cbor.skipValue(&iter); } else { try cbor.skipValue(&iter); } } - const insert = textEdit_insert orelse Range{ .start = .{ .line = 0, .character = 0 }, .end = .{ .line = 0, .character = 0 } }; - const replace = textEdit_replace orelse Range{ .start = .{ .line = 0, .character = 0 }, .end = .{ .line = 0, .character = 0 } }; + const insert = textEdit.insert orelse Range{ .start = .{ .line = 0, .character = 0 }, .end = .{ .line = 0, .character = 0 } }; + const replace = textEdit.replace orelse Range{ .start = .{ .line = 0, .character = 0 }, .end = .{ .line = 0, .character = 0 } }; return to.send(.{ "cmd", "add_completion", .{ file_path, @@ -1137,7 +1129,7 @@ fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: documentation_kind, sortText, insertTextFormat, - textEdit_newText, + textEdit.newText, insert.start.line, insert.start.character, insert.end.line, @@ -1146,10 +1138,42 @@ fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: replace.start.character, replace.end.line, replace.end.character, + additionalTextEdits[0..additionalTextEdits_len], }, }) catch error.ClientFailed; } +fn read_textEdit(iter: *[]const u8) !TextEdit { + var field_name: []const u8 = undefined; + var newText: []const u8 = ""; + var insert: ?Range = null; + var replace: ?Range = null; + var len_ = cbor.decodeMapHeader(iter) catch return invalid_field("textEdit"); + while (len_ > 0) : (len_ -= 1) { + if (!(try cbor.matchString(iter, &field_name))) return invalid_field("textEdit"); + if (std.mem.eql(u8, field_name, "newText")) { + if (!(try cbor.matchValue(iter, cbor.extract(&newText)))) return invalid_field("textEdit.newText"); + } else if (std.mem.eql(u8, field_name, "insert")) { + var range_: []const u8 = undefined; + if (!(try cbor.matchValue(iter, cbor.extract_cbor(&range_)))) return invalid_field("textEdit.insert"); + insert = try read_range(range_); + } else if (std.mem.eql(u8, field_name, "replace") or std.mem.eql(u8, field_name, "range")) { + var range_: []const u8 = undefined; + if (!(try cbor.matchValue(iter, cbor.extract_cbor(&range_)))) return invalid_field("textEdit.replace"); + replace = try read_range(range_); + } else { + try cbor.skipValue(iter); + } + } + return .{ .newText = newText, .insert = insert, .replace = replace }; +} + +const TextEdit = struct { + newText: []const u8 = &.{}, + insert: ?Range = null, + replace: ?Range = null, +}; + const Rename = struct { uri: []const u8, new_text: []const u8, diff --git a/src/tui/mode/overlay/completion_palette.zig b/src/tui/mode/overlay/completion_palette.zig index 1145c6e..780f118 100644 --- a/src/tui/mode/overlay/completion_palette.zig +++ b/src/tui/mode/overlay/completion_palette.zig @@ -40,7 +40,7 @@ pub fn load_entries(palette: *Type) !usize { var max_label_len: usize = 0; for (palette.entries.items) |*item| { - const label_, const sort_text, _, const maybe_replace = get_values(item.cbor); + const label_, const sort_text, _, const maybe_replace, _ = get_values(item.cbor); if (get_replace_selection(maybe_replace)) |replace| { if (palette.value.replace == null) palette.value.replace = replace; } @@ -91,7 +91,7 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget. if (!(cbor.matchValue(&iter, cbor.extract_cbor(&item_cbor)) catch false)) return false; if (!(cbor.matchValue(&iter, cbor.extract_cbor(&matches_cbor)) catch false)) return false; - const label_, _, const kind, _ = get_values(item_cbor); + const label_, _, const kind, _, _ = get_values(item_cbor); const icon_: []const u8 = kind_icon(@enumFromInt(kind)); const color: u24 = 0x0; const indicator: []const u8 = &.{}; @@ -99,11 +99,12 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget. return tui.render_file_item(&button.plane, label_, icon_, color, indicator, matches_cbor, button.active, selected, button.hover, theme); } -fn get_values(item_cbor: []const u8) struct { []const u8, []const u8, u8, Buffer.Selection } { +fn get_values(item_cbor: []const u8) struct { []const u8, []const u8, u8, Buffer.Selection, []const u8 } { var label_: []const u8 = ""; var sort_text: []const u8 = ""; var kind: u8 = 0; var replace: Buffer.Selection = .{}; + var additionalTextEdits: []const u8 = &.{}; _ = cbor.match(item_cbor, .{ cbor.any, // file_path cbor.any, // row @@ -127,10 +128,15 @@ fn get_values(item_cbor: []const u8) struct { []const u8, []const u8, u8, Buffer 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_, sort_text, kind, replace }; + return .{ label_, sort_text, kind, replace, additionalTextEdits }; } +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 (tui.get_active_editor()) |edt| edt.selection_pos_to_width(replace) @@ -141,14 +147,14 @@ fn get_replace_selection(replace: Buffer.Selection) ?Buffer.Selection { } fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { - const label_, _, _, _ = get_values(button.opts.label); + const label_, _, _, _, _ = get_values(button.opts.label); tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e); tp.self_pid().send(.{ "cmd", "insert_chars", .{label_} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e); } pub fn updated(palette: *Type, button_: ?*Type.ButtonState) !void { const button = button_ orelse return cancel(palette); - _, _, _, const replace = get_values(button.opts.label); + _, _, _, const replace, _ = get_values(button.opts.label); const editor = tui.get_active_editor() orelse return error.NotFound; editor.get_primary().selection = get_replace_selection(replace); }