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.
This commit is contained in:
CJ van den Berg 2025-09-17 10:04:27 +02:00
parent 9bdc3e0a0a
commit 59921d8e10
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
2 changed files with 79 additions and 23 deletions

View file

@ -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 {};
}
}

View file

@ -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);
}