From 1632061144884aeb3804bc61a091fbe2df035324 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 11 Aug 2025 14:07:11 +0200 Subject: [PATCH 1/2] refactor: goto minimode into a reusable numeric input minimode --- src/keybind/builtin/flow.json | 4 +- src/tui/mode/mini/goto.zig | 140 ++------------------------- src/tui/mode/mini/numeric_input.zig | 145 ++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 134 deletions(-) create mode 100644 src/tui/mode/mini/numeric_input.zig diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 339972b..314f676 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -328,7 +328,7 @@ ["right_control", "palette_menu_activate_quick"] ] }, - "mini/goto": { + "mini/numeric": { "press": [ ["ctrl+q", "quit"], ["ctrl+v", "system_paste"], @@ -338,7 +338,7 @@ ["ctrl+l", "scroll_view_center_cycle"], ["ctrl+space", "mini_mode_cancel"], ["escape", "mini_mode_cancel"], - ["enter", "exit_mini_mode"], + ["enter", "mini_mode_select"], ["backspace", "mini_mode_delete_backwards"] ] }, diff --git a/src/tui/mode/mini/goto.zig b/src/tui/mode/mini/goto.zig index 94d5c6d..13abeec 100644 --- a/src/tui/mode/mini/goto.zig +++ b/src/tui/mode/mini/goto.zig @@ -1,140 +1,16 @@ -const tp = @import("thespian"); - -const key = @import("renderer").input.key; -const mod = @import("renderer").input.modifier; -const event_type = @import("renderer").input.event_type; -const keybind = @import("keybind"); const command = @import("command"); -const EventHandler = @import("EventHandler"); -const tui = @import("../../tui.zig"); +pub const Type = @import("numeric_input.zig").Create(@This()); +pub const create = Type.create; -const Allocator = @import("std").mem.Allocator; -const fmt = @import("std").fmt; - -const Self = @This(); -const name = "#goto"; - -const Commands = command.Collection(cmds); - -allocator: Allocator, -buf: [30]u8 = undefined, -input: ?usize = null, -start: usize, -commands: Commands = undefined, - -pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } { - const editor = tui.get_active_editor() orelse return error.NotFound; - const self = try allocator.create(Self); - errdefer allocator.destroy(self); - self.* = .{ - .allocator = allocator, - .start = editor.get_primary().cursor.row + 1, - }; - try self.commands.init(self); - var mode = try keybind.mode("mini/goto", allocator, .{ - .insert_command = "mini_mode_insert_bytes", - }); - mode.event_handler = EventHandler.to_owned(self); - return .{ mode, .{ .name = name } }; +pub fn name(_: *Type) []const u8 { + return "#goto"; } -pub fn deinit(self: *Self) void { - self.commands.deinit(); - self.allocator.destroy(self); -} +pub const preview = goto; +pub const apply = goto; +pub const cancel = goto; -pub fn receive(self: *Self, _: tp.pid_ref, _: tp.message) error{Exit}!bool { - self.update_mini_mode_text(); - return false; -} - -fn update_mini_mode_text(self: *Self) void { - if (tui.mini_mode()) |mini_mode| { - mini_mode.text = if (self.input) |linenum| - (fmt.bufPrint(&self.buf, "{d}", .{linenum}) catch "") - else - ""; - mini_mode.cursor = tui.egc_chunk_width(mini_mode.text, 0, 1); - } -} - -fn goto(self: *Self) void { +fn goto(self: *Type) void { command.executeName("goto_line", command.fmt(.{self.input orelse self.start})) catch {}; } - -fn insert_char(self: *Self, char: u8) void { - switch (char) { - '0' => { - if (self.input) |linenum| self.input = linenum * 10; - }, - '1'...'9' => { - const digit: usize = @intCast(char - '0'); - self.input = if (self.input) |x| x * 10 + digit else digit; - }, - else => {}, - } -} - -fn insert_bytes(self: *Self, bytes: []const u8) void { - for (bytes) |c| self.insert_char(c); -} - -const cmds = struct { - pub const Target = Self; - const Ctx = command.Context; - const Meta = command.Metadata; - const Result = command.Result; - - pub fn mini_mode_reset(self: *Self, _: Ctx) Result { - self.input = null; - self.update_mini_mode_text(); - } - pub const mini_mode_reset_meta: Meta = .{ .description = "Clear input" }; - - pub fn mini_mode_cancel(self: *Self, _: Ctx) Result { - self.input = null; - self.update_mini_mode_text(); - self.goto(); - command.executeName("exit_mini_mode", .{}) catch {}; - } - pub const mini_mode_cancel_meta: Meta = .{ .description = "Cancel input" }; - - pub fn mini_mode_delete_backwards(self: *Self, _: Ctx) Result { - if (self.input) |linenum| { - const newval = if (linenum < 10) 0 else linenum / 10; - self.input = if (newval == 0) null else newval; - self.update_mini_mode_text(); - self.goto(); - } - } - pub const mini_mode_delete_backwards_meta: Meta = .{ .description = "Delete backwards" }; - - pub fn mini_mode_insert_code_point(self: *Self, ctx: Ctx) Result { - var keypress: usize = 0; - if (!try ctx.args.match(.{tp.extract(&keypress)})) - return error.InvalidGotoInsertCodePointArgument; - switch (keypress) { - '0'...'9' => self.insert_char(@intCast(keypress)), - else => {}, - } - self.update_mini_mode_text(); - self.goto(); - } - pub const mini_mode_insert_code_point_meta: Meta = .{ .arguments = &.{.integer} }; - - pub fn mini_mode_insert_bytes(self: *Self, ctx: Ctx) Result { - var bytes: []const u8 = undefined; - if (!try ctx.args.match(.{tp.extract(&bytes)})) - return error.InvalidGotoInsertBytesArgument; - self.insert_bytes(bytes); - self.update_mini_mode_text(); - self.goto(); - } - pub const mini_mode_insert_bytes_meta: Meta = .{ .arguments = &.{.string} }; - - pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result { - return mini_mode_insert_bytes(self, ctx); - } - pub const mini_mode_paste_meta: Meta = .{ .arguments = &.{.string} }; -}; diff --git a/src/tui/mode/mini/numeric_input.zig b/src/tui/mode/mini/numeric_input.zig new file mode 100644 index 0000000..2781a45 --- /dev/null +++ b/src/tui/mode/mini/numeric_input.zig @@ -0,0 +1,145 @@ +const tp = @import("thespian"); + +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; +const keybind = @import("keybind"); +const command = @import("command"); +const EventHandler = @import("EventHandler"); + +const tui = @import("../../tui.zig"); + +const Allocator = @import("std").mem.Allocator; +const fmt = @import("std").fmt; + +pub fn Create(options: type) type { + return struct { + const Self = @This(); + + const Commands = command.Collection(cmds); + + allocator: Allocator, + buf: [30]u8 = undefined, + input: ?usize = null, + start: usize, + commands: Commands = undefined, + + pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } { + const editor = tui.get_active_editor() orelse return error.NotFound; + const self = try allocator.create(Self); + errdefer allocator.destroy(self); + self.* = .{ + .allocator = allocator, + .start = editor.get_primary().cursor.row + 1, + }; + try self.commands.init(self); + var mode = try keybind.mode("mini/numeric", allocator, .{ + .insert_command = "mini_mode_insert_bytes", + }); + mode.event_handler = EventHandler.to_owned(self); + return .{ mode, .{ .name = options.name(self) } }; + } + + pub fn deinit(self: *Self) void { + self.commands.deinit(); + self.allocator.destroy(self); + } + + pub fn receive(self: *Self, _: tp.pid_ref, _: tp.message) error{Exit}!bool { + self.update_mini_mode_text(); + return false; + } + + fn update_mini_mode_text(self: *Self) void { + if (tui.mini_mode()) |mini_mode| { + mini_mode.text = if (self.input) |linenum| + (fmt.bufPrint(&self.buf, "{d}", .{linenum}) catch "") + else + ""; + mini_mode.cursor = tui.egc_chunk_width(mini_mode.text, 0, 1); + } + } + + fn insert_char(self: *Self, char: u8) void { + switch (char) { + '0' => { + if (self.input) |linenum| self.input = linenum * 10; + }, + '1'...'9' => { + const digit: usize = @intCast(char - '0'); + self.input = if (self.input) |x| x * 10 + digit else digit; + }, + else => {}, + } + } + + fn insert_bytes(self: *Self, bytes: []const u8) void { + for (bytes) |c| self.insert_char(c); + } + + const cmds = struct { + pub const Target = Self; + const Ctx = command.Context; + const Meta = command.Metadata; + const Result = command.Result; + + pub fn mini_mode_reset(self: *Self, _: Ctx) Result { + self.input = null; + self.update_mini_mode_text(); + } + pub const mini_mode_reset_meta: Meta = .{ .description = "Clear input" }; + + pub fn mini_mode_cancel(self: *Self, _: Ctx) Result { + self.input = null; + self.update_mini_mode_text(); + options.cancel(self); + command.executeName("exit_mini_mode", .{}) catch {}; + } + pub const mini_mode_cancel_meta: Meta = .{ .description = "Cancel input" }; + + pub fn mini_mode_delete_backwards(self: *Self, _: Ctx) Result { + if (self.input) |linenum| { + const newval = if (linenum < 10) 0 else linenum / 10; + self.input = if (newval == 0) null else newval; + self.update_mini_mode_text(); + options.preview(self); + } + } + pub const mini_mode_delete_backwards_meta: Meta = .{ .description = "Delete backwards" }; + + pub fn mini_mode_insert_code_point(self: *Self, ctx: Ctx) Result { + var keypress: usize = 0; + if (!try ctx.args.match(.{tp.extract(&keypress)})) + return error.InvalidGotoInsertCodePointArgument; + switch (keypress) { + '0'...'9' => self.insert_char(@intCast(keypress)), + else => {}, + } + self.update_mini_mode_text(); + options.preview(self); + } + pub const mini_mode_insert_code_point_meta: Meta = .{ .arguments = &.{.integer} }; + + pub fn mini_mode_insert_bytes(self: *Self, ctx: Ctx) Result { + var bytes: []const u8 = undefined; + if (!try ctx.args.match(.{tp.extract(&bytes)})) + return error.InvalidGotoInsertBytesArgument; + self.insert_bytes(bytes); + self.update_mini_mode_text(); + options.preview(self); + } + pub const mini_mode_insert_bytes_meta: Meta = .{ .arguments = &.{.string} }; + + pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result { + return mini_mode_insert_bytes(self, ctx); + } + pub const mini_mode_paste_meta: Meta = .{ .arguments = &.{.string} }; + + pub fn mini_mode_select(self: *Self, _: Ctx) Result { + options.apply(self); + command.executeName("exit_mini_mode", .{}) catch {}; + } + pub const mini_mode_select_meta: Meta = .{ .description = "Select" }; + }; + }; +} From 3e0e75c9c8f5efcfb43a4fa4797e3533b6da947a Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 11 Aug 2025 14:29:23 +0200 Subject: [PATCH 2/2] feat: add interactive and non-interactive commands to set the current buffer's tab_width --- src/tui/editor.zig | 18 ++++++++++++++++++ src/tui/mode/mini/tab_width.zig | 16 ++++++++++++++++ src/tui/tui.zig | 5 +++++ 3 files changed, 39 insertions(+) create mode 100644 src/tui/mode/mini/tab_width.zig diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 701626d..b9fce38 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -432,6 +432,7 @@ pub const Editor = struct { tp.extract_cbor(&cursels_cbor), })) return error.RestoreStateMatch; + self.refresh_tab_width(); if (op == .open_file) try self.open(file_path); self.clipboard = if (clipboard.len > 0) try self.allocator.dupe(u8, clipboard) else null; @@ -702,6 +703,23 @@ pub const Editor = struct { return; } + fn refresh_tab_width(self: *Self) void { + self.metrics = self.plane.metrics(self.tab_width); + switch (self.indent_mode) { + .spaces, .auto => {}, + .tabs => self.indent_size = self.tab_width, + } + } + + pub fn set_tab_width(self: *Self, ctx: Context) Result { + var tab_width: usize = 0; + if (!try ctx.args.match(.{tp.extract(&tab_width)})) + return error.InvalidSetTabWidthArgument; + self.tab_width = tab_width; + self.refresh_tab_width(); + } + pub const set_tab_width_meta: Meta = .{ .arguments = &.{.integer} }; + fn close(self: *Self) !void { var meta = std.ArrayListUnmanaged(u8).empty; defer meta.deinit(self.allocator); diff --git a/src/tui/mode/mini/tab_width.zig b/src/tui/mode/mini/tab_width.zig new file mode 100644 index 0000000..a2ca836 --- /dev/null +++ b/src/tui/mode/mini/tab_width.zig @@ -0,0 +1,16 @@ +const command = @import("command"); + +pub const Type = @import("numeric_input.zig").Create(@This()); +pub const create = Type.create; + +pub fn name(_: *Type) []const u8 { + return " tab size"; +} + +pub const preview = goto; +pub const apply = goto; +pub const cancel = goto; + +fn goto(self: *Type) void { + command.executeName("set_tab_width", command.fmt(.{self.input orelse self.start})) catch {}; +} diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 3a3e7c8..f762102 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -744,6 +744,11 @@ const cmds = struct { } pub const force_terminate_meta: Meta = .{ .description = "Force quit without saving" }; + pub fn tab_width(self: *Self, ctx: Ctx) Result { + return enter_mini_mode(self, @import("mode/mini/tab_width.zig"), ctx); + } + pub const tab_width_meta: Meta = .{ .description = "Set tab width" }; + pub fn set_theme(self: *Self, ctx: Ctx) Result { var name: []const u8 = undefined; if (!try ctx.args.match(.{tp.extract(&name)}))