feat: add Editor.set_hover_content info_box function

This commit is contained in:
CJ van den Berg 2026-02-05 21:42:12 +01:00
parent ebe59eb0f7
commit 4a1b206d2b
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9

View file

@ -28,6 +28,7 @@ const WidgetList = @import("WidgetList.zig");
const tui = @import("tui.zig"); const tui = @import("tui.zig");
const IndentMode = @import("config").IndentMode; const IndentMode = @import("config").IndentMode;
const WhitespaceMode = @import("config").WhitespaceMode; const WhitespaceMode = @import("config").WhitespaceMode;
const info_view = @import("info_view.zig");
pub const Cursor = Buffer.Cursor; pub const Cursor = Buffer.Cursor;
pub const View = Buffer.View; pub const View = Buffer.View;
@ -414,6 +415,7 @@ pub const Editor = struct {
diag_warnings: usize = 0, diag_warnings: usize = 0,
diag_info: usize = 0, diag_info: usize = 0,
diag_hints: usize = 0, diag_hints: usize = 0,
info_box: ?Widget = null,
completions: CompletionState = .empty, completions: CompletionState = .empty,
completions_request: ?CompletionState = .done, completions_request: ?CompletionState = .done,
@ -643,6 +645,7 @@ pub const Editor = struct {
var meta: std.Io.Writer.Allocating = .init(self.allocator); var meta: std.Io.Writer.Allocating = .init(self.allocator);
defer meta.deinit(); defer meta.deinit();
if (self.buffer) |_| self.write_state(&meta.writer) catch {}; if (self.buffer) |_| self.write_state(&meta.writer) catch {};
if (self.info_box) |*w| w.deinit(self.allocator);
for (self.diagnostics.items) |*d| d.deinit(self.allocator); for (self.diagnostics.items) |*d| d.deinit(self.allocator);
self.diagnostics.deinit(self.allocator); self.diagnostics.deinit(self.allocator);
self.completions.deinit(self.allocator); self.completions.deinit(self.allocator);
@ -1294,6 +1297,7 @@ pub const Editor = struct {
self.render_blame(theme, hl_row, ctx_.cell_map) catch {}; self.render_blame(theme, hl_row, ctx_.cell_map) catch {};
self.render_column_highlights() catch {}; self.render_column_highlights() catch {};
self.render_cursors(theme, ctx_.cell_map, focused) catch {}; self.render_cursors(theme, ctx_.cell_map, focused) catch {};
if (self.info_box) |w| _ = w.render(theme);
} }
fn render_cursors(self: *Self, theme: *const Widget.Theme, cell_map: CellMap, focused: bool) !void { fn render_cursors(self: *Self, theme: *const Widget.Theme, cell_map: CellMap, focused: bool) !void {
@ -1919,8 +1923,10 @@ pub const Editor = struct {
self.last.cursels = self.cursels.items.len; self.last.cursels = self.cursels.items.len;
} }
if (lines != self.last.lines or !primary.cursor.eql(self.last.primary.cursor)) if (lines != self.last.lines or !primary.cursor.eql(self.last.primary.cursor)) {
self.clear_info_box();
try self.send_editor_pos(lines, &primary.cursor); try self.send_editor_pos(lines, &primary.cursor);
}
if (primary.selection) |primary_selection_| { if (primary.selection) |primary_selection_| {
var primary_selection = primary_selection_; var primary_selection = primary_selection_;
@ -1934,8 +1940,10 @@ pub const Editor = struct {
} else if (self.last.primary.selection) |_| } else if (self.last.primary.selection) |_|
try self.send_editor_selection_removed(); try self.send_editor_selection_removed();
if (lines != self.last.lines or !self.view.eql(self.last.view)) if (lines != self.last.lines or !self.view.eql(self.last.view)) {
self.clear_info_box();
try self.send_editor_view(lines, self.view); try self.send_editor_view(lines, self.view);
}
self.last.view = self.view; self.last.view = self.view;
self.last.lines = lines; self.last.lines = lines;
@ -2044,6 +2052,56 @@ pub const Editor = struct {
_ = try self.handlers.msg(.{ "E", "eol_mode", eol_mode, utf8_sanitized, indent_mode }); _ = try self.handlers.msg(.{ "E", "eol_mode", eol_mode, utf8_sanitized, indent_mode });
} }
fn clear_info_box(self: *Self) void {
if (self.info_box) |*w| {
w.deinit(self.allocator);
self.info_box = null;
self.clear_matches();
}
}
const info_box_widget_type: Widget.Type = .info_box;
fn show_info_box(self: *Self) !*info_view {
const w = self.info_box orelse blk: {
self.info_box = try info_view.create_widget_type(self.allocator, self.plane, info_box_widget_type);
break :blk self.info_box.?;
};
const info_ = if (w.get(@typeName(info_view))) |w_| w_.dynamic_cast(info_view) orelse null else null;
const info = info_ orelse @panic("Editor.show_info_box");
return info;
}
fn place_info_box(self: *Self, cursor: Cursor) void {
const w = self.info_box orelse return;
const info = self.show_info_box() catch return;
const padding = tui.get_widget_style(info_box_widget_type).padding;
const dim = info.content_size();
const pos = self.screen_cursor(&cursor) orelse {
self.clear_info_box();
return;
};
const y, const x = self.plane.rel_yx_to_abs(@intCast(pos.row), @intCast(pos.col));
w.resize(.{
.h = dim.rows + padding.top + padding.bottom,
.w = dim.cols + padding.left + padding.right + 3,
.y = @intCast(y + 1),
.x = @intCast(x + 1),
});
}
pub fn set_hover_content(self: *Self, range: Match, content: []const u8) !void {
self.add_hover_highlight(range);
if (content.len == 0) {
self.clear_info_box();
return;
}
const info = try self.show_info_box();
info.clear();
try info.append_content(content);
self.place_info_box(range.begin);
}
fn clamp_abs_offset(self: *Self, abs: bool, offset: usize) void { fn clamp_abs_offset(self: *Self, abs: bool, offset: usize) void {
var dest: View = self.view; var dest: View = self.view;
dest.clamp_offset(&self.get_primary().cursor, abs, offset); dest.clamp_offset(&self.get_primary().cursor, abs, offset);
@ -4464,6 +4522,7 @@ pub const Editor = struct {
pub const move_buffer_end_meta: Meta = .{ .description = "Move cursor to end of file" }; pub const move_buffer_end_meta: Meta = .{ .description = "Move cursor to end of file" };
pub fn cancel(self: *Self, _: Context) Result { pub fn cancel(self: *Self, _: Context) Result {
self.clear_info_box();
self.cancel_all_tabstops(); self.cancel_all_tabstops();
self.cancel_all_selections(); self.cancel_all_selections();
self.cancel_all_matches(); self.cancel_all_matches();