diff --git a/src/Project.zig b/src/Project.zig index 982cea6..2aaede9 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -1012,6 +1012,7 @@ fn send_completion_items(to: tp.pid_ref, file_path: []const u8, row: usize, col: if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&item)))) return error.InvalidMessageField; try send_completion_item(to, file_path, row, col, item, if (len > 1) true else is_incomplete); } + return to.send(.{ "cmd", "add_completion_done", .{ file_path, row, col } }) catch error.ClientFailed; } fn invalid_field(field: []const u8) error{InvalidMessage} { diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 0535ce4..6cce2b3 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -838,11 +838,8 @@ const cmds = struct { tp.more, })) return error.InvalidAddDiagnosticArgument; file_path = project_manager.normalize_file_path(file_path); - if (self.get_active_editor()) |editor| { - if (std.mem.eql(u8, file_path, editor.file_path orelse "")) - try editor.add_completion(row, col, is_incomplete, ctx.args); - try tui.open_overlay(@import("mode/overlay/completion_palette.zig").Type); - } + if (self.get_active_editor()) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse "")) + try editor.add_completion(row, col, is_incomplete, ctx.args); } pub const add_completion_meta: Meta = .{ .arguments = &.{ @@ -871,6 +868,28 @@ const cmds = struct { }, }; + pub fn add_completion_done(self: *Self, ctx: Ctx) Result { + var file_path: []const u8 = undefined; + var row: usize = undefined; + var col: usize = undefined; + + if (!try ctx.args.match(.{ + tp.extract(&file_path), + tp.extract(&row), + tp.extract(&col), + })) return error.InvalidAddDiagnosticArgument; + file_path = project_manager.normalize_file_path(file_path); + if (self.get_active_editor()) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse "")) + try tui.open_overlay(@import("mode/overlay/completion_palette.zig").Type); + } + pub const add_completion_done_meta: Meta = .{ + .arguments = &.{ + .string, // file_path + .integer, // row + .integer, // col + }, + }; + pub fn rename_symbol_item(self: *Self, ctx: Ctx) Result { const editor = self.get_active_editor() orelse return; // because the incoming message is an array of Renames, we manuallly diff --git a/src/tui/mode/overlay/completion_palette.zig b/src/tui/mode/overlay/completion_palette.zig index ad22951..ce7afde 100644 --- a/src/tui/mode/overlay/completion_palette.zig +++ b/src/tui/mode/overlay/completion_palette.zig @@ -3,9 +3,11 @@ const cbor = @import("cbor"); const tp = @import("thespian"); const root = @import("root"); const command = @import("command"); +const Buffer = @import("Buffer"); const tui = @import("../../tui.zig"); pub const Type = @import("palette.zig").Create(@This()); +const ed = @import("../../editor.zig"); const module_name = @typeName(@This()); const Widget = @import("../../Widget.zig"); @@ -20,8 +22,15 @@ pub const Entry = struct { cbor: []const u8, }; +pub const ValueType = struct { + start: ed.CurSel = .{}, + replace: ?Buffer.Selection = null, +}; +pub const defaultValue: ValueType = .{}; + pub fn load_entries(palette: *Type) !usize { const editor = tui.get_active_editor() orelse return error.NotFound; + palette.value.start = editor.get_primary().*; var iter: []const u8 = editor.completions.items; while (iter.len > 0) { var cbor_item: []const u8 = undefined; @@ -31,7 +40,9 @@ pub fn load_entries(palette: *Type) !usize { var max_label_len: usize = 0; for (palette.entries.items) |*item| { - const label_, const sort_text, _ = get_values(item.cbor); + const label_, const sort_text, _, const replace = get_values(item.cbor); + if (palette.value.replace == null) + palette.value.replace = replace; item.label = label_; item.sort_text = sort_text; max_label_len = @max(max_label_len, item.label.len); @@ -49,6 +60,14 @@ pub fn load_entries(palette: *Type) !usize { return if (max_label_len > label.len + 3) 0 else label.len + 3 - max_label_len; } +pub fn initial_query(palette: *Type, allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 { + return if (palette.value.replace) |replace| blk: { + const editor = tui.get_active_editor() orelse break :blk allocator.dupe(u8, ""); + const sel: Buffer.Selection = .{ .begin = replace.begin, .end = palette.value.start.cursor }; + break :blk editor.get_selection(sel, allocator) catch break :blk allocator.dupe(u8, ""); + } else allocator.dupe(u8, ""); +} + pub fn clear_entries(palette: *Type) void { palette.entries.clearRetainingCapacity(); } @@ -71,7 +90,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 = &.{}; @@ -79,10 +98,11 @@ 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 } { +fn get_values(item_cbor: []const u8) struct { []const u8, []const u8, u8, Buffer.Selection } { var label_: []const u8 = ""; var sort_text: []const u8 = ""; var kind: u8 = 0; + var replace: Buffer.Selection = .{}; _ = cbor.match(item_cbor, .{ cbor.any, // file_path cbor.any, // row @@ -102,20 +122,32 @@ fn get_values(item_cbor: []const u8) struct { []const u8, []const u8, u8 } { cbor.any, // insert.begin.col cbor.any, // insert.end.row cbor.any, // insert.end.col - cbor.any, // replace.begin.row - cbor.any, // replace.begin.col - cbor.any, // replace.end.row - cbor.any, // replace.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 }) catch false; - return .{ label_, sort_text, kind }; + return .{ label_, sort_text, kind, replace }; } 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 editor = tui.get_active_editor() orelse return error.NotFound; + editor.get_primary().selection = if (replace.empty()) null else replace; +} + +pub fn cancel(palette: *Type) !void { + const editor = tui.get_active_editor() orelse return; + editor.get_primary().selection = palette.value.start.selection; +} + const CompletionItemKind = enum(u8) { None = 0, Text = 1, diff --git a/src/tui/mode/overlay/palette.zig b/src/tui/mode/overlay/palette.zig index d6ad651..28b2b5b 100644 --- a/src/tui/mode/overlay/palette.zig +++ b/src/tui/mode/overlay/palette.zig @@ -40,8 +40,11 @@ pub fn Create(options: type) type { view_pos: usize = 0, total_items: usize = 0, + value: ValueType = if (@hasDecl(options, "defaultValue")) options.defaultValue else {}, + const Entry = options.Entry; const Self = @This(); + const ValueType = if (@hasDecl(options, "ValueType")) options.ValueType else void; pub const MenuState = Menu.State(*Self); pub const ButtonState = Button.State(*Menu.State(*Self)); @@ -88,6 +91,12 @@ pub fn Create(options: type) type { if (@hasDecl(options, "restore_state")) options.restore_state(self) catch {}; try self.commands.init(self); + if (@hasDecl(options, "initial_query")) blk: { + const initial_query = options.initial_query(self, self.allocator) catch break :blk; + defer self.allocator.free(initial_query); + try self.inputbox.text.appendSlice(self.allocator, initial_query); + self.inputbox.cursor = tui.egc_chunk_width(self.inputbox.text.items, 0, 8); + } try self.start_query(0); try mv.floating_views.add(self.modal.widget()); try mv.floating_views.add(self.menu.container_widget); diff --git a/src/tui/tui.zig b/src/tui/tui.zig index e0d934f..c584058 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -189,7 +189,7 @@ fn init(allocator: Allocator) InitError!*Self { } self.mainview_ = try MainView.create(allocator); resize(); - self.set_terminal_style(); + self.set_terminal_style(self.current_theme()); try save_config(); try self.init_input_namespace(); if (tp.env.get().is("restore-session")) { @@ -773,7 +773,7 @@ fn set_theme_by_name(self: *Self, name: []const u8, action: enum { none, store } self.light_parsed_theme = parsed_theme; }, } - self.set_terminal_style(); + self.set_terminal_style(&theme_); self.logger.print("theme: {s}", .{theme_.description}); switch (action) { .none => {}, @@ -790,12 +790,14 @@ fn set_theme_by_name(self: *Self, name: []const u8, action: enum { none, store } fn force_color_scheme(self: *Self, color_scheme: @TypeOf(self.color_scheme)) void { self.color_scheme = color_scheme; self.color_scheme_locked = true; + self.set_terminal_style(self.current_theme()); self.logger.print("color scheme: {s} ({s})", .{ @tagName(self.color_scheme), self.current_theme().name }); } fn set_color_scheme(self: *Self, color_scheme: @TypeOf(self.color_scheme)) void { if (self.color_scheme_locked) return; self.color_scheme = color_scheme; + self.set_terminal_style(self.current_theme()); self.logger.print("color scheme: {s} ({s})", .{ @tagName(self.color_scheme), self.current_theme().name }); } @@ -1548,12 +1550,12 @@ pub const fallbacks: []const FallBack = &[_]FallBack{ .{ .ts = "text.title", .tm = "entity.name.section" }, }; -fn set_terminal_style(self: *Self) void { +fn set_terminal_style(self: *Self, theme_: *const Widget.Theme) void { if (build_options.gui or self.config_.enable_terminal_color_scheme) { - self.rdr_.set_terminal_style(self.current_theme().editor); - self.rdr_.set_terminal_cursor_color(self.current_theme().editor_cursor.bg.?); + self.rdr_.set_terminal_style(theme_.editor); + self.rdr_.set_terminal_cursor_color(theme_.editor_cursor.bg.?); if (self.rdr_.vx.caps.multi_cursor) - self.rdr_.set_terminal_secondary_cursor_color(self.current_theme().editor_cursor_secondary.bg orelse self.current_theme().editor_cursor.bg.?); + self.rdr_.set_terminal_secondary_cursor_color(theme_.editor_cursor_secondary.bg orelse theme_.editor_cursor.bg.?); } }