diff --git a/src/tui/keyhints.zig b/src/tui/keyhints.zig new file mode 100644 index 0000000..e65b674 --- /dev/null +++ b/src/tui/keyhints.zig @@ -0,0 +1,116 @@ +const std = @import("std"); +const keybind = @import("keybind"); +const command = @import("command"); +const Plane = @import("renderer").Plane; + +const tui = @import("tui.zig"); +const Widget = @import("Widget.zig"); + +const widget_type: Widget.Type = .hint_window; + +pub fn render_current_key_event_sequence(allocator: std.mem.Allocator, theme: *const Widget.Theme) void { + const mode = tui.input_mode() orelse return; + const bindings = mode.current_key_event_sequence_bindings(allocator) catch return; + defer allocator.free(bindings); + return render(bindings, theme); +} + +pub fn render(bindings: []const keybind.Binding, theme: *const Widget.Theme) void { + // return if something is already rendering to the top layer + if (tui.have_top_layer()) return; + if (bindings.len == 0) return; + + var key_events_buf: [256]u8 = undefined; + const key_events = blk: { + var writer = std.Io.Writer.fixed(&key_events_buf); + writer.print("{f}", .{keybind.current_key_event_sequence_fmt()}) catch {}; + break :blk writer.buffered(); + }; + + const max_prefix_len = get_max_prefix_len(bindings) - key_events.len; + const max_description_len = get_max_description_len(bindings); + const max_len = max_prefix_len + max_description_len + 2 + 2; + const widget_style = tui.get_widget_style(widget_type); + + const scr = tui.screen(); + var box: Widget.Box = .{ + .h = bindings.len, + .w = max_len, + .x = scr.w -| max_len -| 2 -| widget_style.padding.left -| widget_style.padding.right, + .y = scr.h -| bindings.len -| 1 -| widget_style.padding.top -| widget_style.padding.bottom, + }; + const deco_box = box.from_client_box(widget_style.padding); + + // reset position for top layer offset + box.x -= deco_box.x; + box.y -= deco_box.y; + + const top_layer_ = tui.top_layer(deco_box.to_layer()) orelse return; + widget_style.render_decoration(deco_box, widget_type, top_layer_, theme); + + // var plane = top_layer_; + var plane = try Plane.init(&(Widget.Box{}).opts(@typeName(@This())), top_layer_.*); + defer plane.deinit(); + plane.resize_simple(@intCast(box.h), @intCast(box.w)) catch return; + plane.move_yx(@intCast(box.y), @intCast(box.x)) catch return; + + const style_base = theme.editor_widget; + const style_label = theme.editor_widget; + const style_hint = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_label; + plane.set_base_style(style_base); + plane.erase(); + plane.home(); + plane.set_style(style_label); + plane.fill(" "); + plane.home(); + plane.set_style(style_hint); + + for (bindings, 0..) |binding, y| { + var keybind_buf: [256]u8 = undefined; + const keybind_txt = blk: { + var writer = std.Io.Writer.fixed(&keybind_buf); + writer.print("{f}", .{keybind.key_event_sequence_fmt(binding.key_events)}) catch break :blk ""; + break :blk writer.buffered(); + }; + plane.cursor_move_yx(@intCast(y), 0) catch break; + _ = plane.print("{s}", .{keybind_txt[key_events.len..]}) catch {}; + } + + plane.set_style(style_label); + + for (bindings, 0..) |binding, y| { + const padding = max_prefix_len + 2; + + const description = blk: { + const id = binding.commands[0].command_id orelse + command.get_id(binding.commands[0].command) orelse + break :blk binding.commands[0].command; + break :blk command.get_description(id) orelse break :blk "[n/a]"; + }; + + plane.cursor_move_yx(@intCast(y), @intCast(padding)) catch break; + _ = plane.print("{s}", .{if (description.len > 0) description else binding.commands[0].command}) catch {}; + } +} + +fn get_max_prefix_len(bindings: anytype) usize { + var max: usize = 0; + for (bindings) |binding| { + var keybind_buf: [256]u8 = undefined; + var writer = std.Io.Writer.fixed(&keybind_buf); + writer.print("{f}", .{keybind.key_event_sequence_fmt(binding.key_events)}) catch continue; + max = @max(max, writer.buffered().len); + } + return max; +} + +fn get_max_description_len(bindings: anytype) usize { + var max: usize = 0; + for (bindings) |binding| { + const id = binding.commands[0].command_id orelse command.get_id(binding.commands[0].command) orelse continue; + const description = command.get_description(id) orelse continue; + const text = if (description.len > 0) description else binding.commands[0].command; + max = @max(max, text.len); + } + return max; +} diff --git a/src/tui/tui.zig b/src/tui/tui.zig index a2b0a38..3bfeab2 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -559,7 +559,11 @@ fn render(self: *Self) void { const frame = tracy.initZone(@src(), .{ .name = "tui render" }); defer frame.deinit(); self.rdr_.stdplane().erase(); - break :ret if (self.mainview_) |mv| mv.render(self.current_theme()) else false; + const continue_mainview = if (self.mainview_) |mv| mv.render(self.current_theme()) else false; + + @import("keyhints.zig").render_current_key_event_sequence(self.allocator, self.current_theme()); + + break :ret continue_mainview; }; if (self.top_layer_) |top_layer_| {