From 59921d8e1073aebff7854b014d3bff4f2f4a5598 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 17 Sep 2025 10:04:27 +0200 Subject: [PATCH] feat: restore cursor column when cancelling goto mini mode This commit refactors the numeric_input mini mode to make the input value type generic. This allows the goto mini mode to store the origin column along with the row. Also, this will allow more complex numeric_input modes, for example a goto mini mode that supports column and row. --- src/tui/mode/mini/goto.zig | 43 +++++++++++++++++++-- src/tui/mode/mini/numeric_input.zig | 59 +++++++++++++++++++---------- 2 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/tui/mode/mini/goto.zig b/src/tui/mode/mini/goto.zig index 6607dc6..e8bc12a 100644 --- a/src/tui/mode/mini/goto.zig +++ b/src/tui/mode/mini/goto.zig @@ -1,3 +1,4 @@ +const fmt = @import("std").fmt; const command = @import("command"); const tui = @import("../../tui.zig"); @@ -5,13 +6,43 @@ const tui = @import("../../tui.zig"); pub const Type = @import("numeric_input.zig").Create(@This()); pub const create = Type.create; +pub const ValueType = @import("../../editor.zig").Cursor; + pub fn name(_: *Type) []const u8 { return "#goto"; } -pub fn start(_: *Type) usize { - const editor = tui.get_active_editor() orelse return 1; - return editor.get_primary().cursor.row + 1; +pub fn start(_: *Type) ValueType { + const editor = tui.get_active_editor() orelse return .{}; + return editor.get_primary().cursor; +} + +pub fn process_digit(self: *Type, digit: u8) void { + switch (digit) { + 0 => { + if (self.input) |*x| x.row = x.row * 10; + }, + 1...9 => { + if (self.input) |*x| { + x.row = x.row * 10 + digit; + } else { + self.input = .{ .row = digit }; + } + }, + else => unreachable, + } +} + +pub fn delete(self: *Type, input: *ValueType) void { + const newval = if (input.row < 10) 0 else input.row / 10; + if (newval == 0) self.input = null else input.row = newval; +} + +pub fn format_value(_: *Type, input: ?ValueType, buf: []u8) []const u8 { + return if (input) |value| + (fmt.bufPrint(buf, "{d}", .{value.row}) catch "") + else + ""; } pub const preview = goto; @@ -19,5 +50,9 @@ pub const apply = goto; pub const cancel = goto; fn goto(self: *Type, _: command.Context) void { - command.executeName("goto_line", command.fmt(.{self.input orelse self.start})) catch {}; + if (self.input) |input| { + command.executeName("goto_line", command.fmt(.{input.row})) catch {}; + } else { + command.executeName("goto_line_and_column", command.fmt(.{ self.start.row, self.start.col })) catch {}; + } } diff --git a/src/tui/mode/mini/numeric_input.zig b/src/tui/mode/mini/numeric_input.zig index 5b6be0f..ad32849 100644 --- a/src/tui/mode/mini/numeric_input.zig +++ b/src/tui/mode/mini/numeric_input.zig @@ -18,10 +18,12 @@ pub fn Create(options: type) type { const Commands = command.Collection(cmds); + const ValueType = if (@hasDecl(options, "ValueType")) options.ValueType else usize; + allocator: Allocator, buf: [30]u8 = undefined, - input: ?usize = null, - start: usize, + input: ?ValueType = null, + start: ValueType, ctx: command.Context, commands: Commands = undefined, @@ -31,7 +33,7 @@ pub fn Create(options: type) type { self.* = .{ .allocator = allocator, .ctx = .{ .args = try ctx.args.clone(allocator) }, - .start = 0, + .start = if (@hasDecl(options, "ValueType")) ValueType{} else 0, }; self.start = options.start(self); try self.commands.init(self); @@ -55,27 +57,42 @@ pub fn Create(options: type) type { 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 - ""; + if (@hasDecl(options, "format_value")) { + mini_mode.text = options.format_value(self, self.input, &self.buf); + } else { + 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 => {}, + const process_digit_ = if (@hasDecl(options, "process_digit")) options.process_digit else process_digit; + if (@hasDecl(options, "Separator")) { + switch (char) { + '0'...'9' => process_digit_(@intCast(char - '0')), + options.Separator => options.process_separator(self), + else => {}, + } + } else { + switch (char) { + '0'...'9' => process_digit_(self, @intCast(char - '0')), + else => {}, + } } } + fn process_digit(self: *Self, digit: u8) void { + self.input = switch (digit) { + 0 => if (self.input) |value| value * 10 else 0, + 1...9 => if (self.input) |x| x * 10 + digit else digit, + else => unreachable, + }; + } + fn insert_bytes(self: *Self, bytes: []const u8) void { for (bytes) |c| self.insert_char(c); } @@ -101,9 +118,13 @@ pub fn Create(options: type) type { 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; + if (self.input) |*input| { + if (@hasDecl(options, "delete")) { + options.delete(self, input); + } else { + const newval = if (input.* < 10) 0 else input.* / 10; + self.input = if (newval == 0) null else newval; + } self.update_mini_mode_text(); options.preview(self, self.ctx); }