Fix select motions and mobile line aliases

This commit is contained in:
slhx agent
2026-06-21 20:15:10 +02:00
parent b83431aefc
commit 9f4c1539e4
4 changed files with 89 additions and 34 deletions
+67 -26
View File
@@ -1055,6 +1055,8 @@ pub const Client = struct {
return;
}
if (std.mem.eql(u8, text, "s")) {
const snap = try self.session.snapshot();
try self.session.selectRange(snap.cursor_byte, snap.cursor_byte);
self.mode = .select;
self.message = "select";
self.pending_count = 0;
@@ -1070,8 +1072,8 @@ pub const Client = struct {
if (std.mem.eql(u8, text, "p")) return self.pasteRegister();
if (std.mem.eql(u8, text, "u")) return self.undoEdit();
if (std.mem.eql(u8, text, "U")) return self.redoEdit();
if (std.mem.eql(u8, text, "0")) return self.applyProtocol("command move_line_start");
if (std.mem.eql(u8, text, "$")) return self.applyProtocol("command move_line_end");
if (std.mem.eql(u8, text, "H") or std.mem.eql(u8, text, "0") or std.mem.eql(u8, text, "^")) return self.applyProtocol("command move_line_start");
if (std.mem.eql(u8, text, "L") or std.mem.eql(u8, text, "$")) return self.applyProtocol("command move_line_end");
if (std.mem.eql(u8, text, "%")) return self.jumpToMatch(null);
if (std.mem.eql(u8, text, "m")) return self.openPrefix(.match);
if (std.mem.eql(u8, text, "g")) return self.openPrefix(.go);
@@ -1110,7 +1112,7 @@ pub const Client = struct {
.key => |key| switch (key) {
.space => try self.insertText(" "),
.enter => try self.insertNewlineWithIndent(),
.tab => try self.insertText(try self.smartTabText()),
.tab => try self.insertText(" "),
.escape => {
self.mode = .normal;
self.message = "normal";
@@ -1136,7 +1138,16 @@ pub const Client = struct {
self.message = "normal";
return;
}
if (std.mem.eql(u8, text, "%")) return self.jumpToMatch(null);
if (std.mem.eql(u8, text, "%")) return self.extendSelectionWithJump(null);
if (std.mem.eql(u8, text, "H") or std.mem.eql(u8, text, "0") or std.mem.eql(u8, text, "^")) return self.extendSelectionWithProtocol("command move_line_start");
if (std.mem.eql(u8, text, "L") or std.mem.eql(u8, text, "$")) return self.extendSelectionWithProtocol("command move_line_end");
if (std.mem.eql(u8, text, "j")) return self.repeatSelectionProtocol("command move_down", self.takeRepeat());
if (std.mem.eql(u8, text, "k")) return self.repeatSelectionProtocol("command move_up", self.takeRepeat());
if (std.mem.eql(u8, text, "h")) return self.repeatSelectionProtocol("command move_left", self.takeRepeat());
if (std.mem.eql(u8, text, "l")) return self.repeatSelectionProtocol("command move_right", self.takeRepeat());
if (std.mem.eql(u8, text, "w")) return self.repeatSelectionProtocol("command move_word_forward", self.takeRepeat());
if (std.mem.eql(u8, text, "b")) return self.repeatSelectionProtocol("command move_word_back", self.takeRepeat());
if (std.mem.eql(u8, text, "e")) return self.repeatSelectionProtocol("command move_word_end", self.takeRepeat());
if (std.mem.eql(u8, text, "m")) return self.openPrefix(.match);
if (std.mem.eql(u8, text, "g")) return self.openPrefix(.go);
if (text.len == 1) {
@@ -1153,12 +1164,12 @@ pub const Client = struct {
self.mode = .normal;
self.message = "normal";
},
.arrow_left => try self.applyProtocol("command move_left"),
.arrow_right => try self.applyProtocol("command move_right"),
.arrow_up => try self.applyProtocol("command move_up"),
.arrow_down => try self.applyProtocol("command move_down"),
.home => try self.applyProtocol("command move_line_start"),
.end => try self.applyProtocol("command move_line_end"),
.arrow_left => try self.extendSelectionWithProtocol("command move_left"),
.arrow_right => try self.extendSelectionWithProtocol("command move_right"),
.arrow_up => try self.extendSelectionWithProtocol("command move_up"),
.arrow_down => try self.extendSelectionWithProtocol("command move_down"),
.home => try self.extendSelectionWithProtocol("command move_line_start"),
.end => try self.extendSelectionWithProtocol("command move_line_end"),
else => {},
},
.unknown => self.unknownPrefixOrInput("select"),
@@ -1868,6 +1879,13 @@ pub const Client = struct {
}
return;
}
if (self.effectiveMode() == .select and text.len == 1) {
if (self.objectRangeForKey(text[0])) |range| {
try self.session.selectRange(range.start, range.end);
self.message = "select";
return;
} else |_| {}
}
self.unknownPrefixOrInput("normal");
}
@@ -2157,20 +2175,6 @@ pub const Client = struct {
try self.insertText(text.items);
}
fn smartTabText(self: *Client) ![]const u8 {
const snap = try self.session.snapshot();
const line_start = currentLineStart(snap.bytes, snap.cursor_byte);
if (line_start < snap.bytes.len and snap.bytes[line_start] == '\t') return "\t";
var i: usize = 0;
while (i < snap.bytes.len) {
if (snap.bytes[i] == '\t') return "\t";
while (i < snap.bytes.len and snap.bytes[i] != '\n') : (i += 1) {}
if (i < snap.bytes.len) i += 1;
while (i < snap.bytes.len and snap.bytes[i] == ' ') : (i += 1) {}
}
return " ";
}
fn currentLineStart(bytes: []const u8, cursor_byte: usize) usize {
var start = @min(cursor_byte, bytes.len);
while (start > 0 and bytes[start - 1] != '\n') start -= 1;
@@ -2202,6 +2206,29 @@ pub const Client = struct {
while (i < repeat) : (i += 1) try self.applyProtocol(line);
}
fn extendSelectionWithProtocol(self: *Client, line: []const u8) !void {
const before = try self.session.snapshot();
const anchor = if (before.selection) |selection| selection.anchor else before.cursor_byte;
try self.applyProtocol(line);
const after = try self.session.snapshot();
try self.session.selectRange(@min(anchor, after.cursor_byte), @max(anchor, after.cursor_byte));
self.message = "select";
}
fn repeatSelectionProtocol(self: *Client, line: []const u8, repeat: usize) !void {
var i: usize = 0;
while (i < repeat) : (i += 1) try self.extendSelectionWithProtocol(line);
}
fn extendSelectionWithJump(self: *Client, delimiter: ?u8) !void {
const before = try self.session.snapshot();
const anchor = if (before.selection) |selection| selection.anchor else before.cursor_byte;
try self.jumpToMatch(delimiter);
const after = try self.session.snapshot();
try self.session.selectRange(@min(anchor, after.cursor_byte), @max(anchor, after.cursor_byte));
self.message = "select";
}
fn repeatMutatingProtocol(self: *Client, line: []const u8, repeat: usize) !void {
var i: usize = 0;
while (i < repeat) : (i += 1) try self.applyMutatingProtocol(line);
@@ -2417,9 +2444,13 @@ fn appendEditorLine(
try out.append(allocator, '\n');
return;
}
const horizontal_scroll = if (is_cursor_line and cursor_col >= content_width)
cursor_col - content_width + 1
else
0;
if (is_cursor_line) try out.appendSlice(allocator, ansi_current_line);
try out.appendSlice(allocator, ansi_text);
try appendEditorCells(allocator, out, line, line_start_byte, content_width, is_cursor_line, cursor_col, cursor_byte, selection);
try appendEditorCells(allocator, out, line, line_start_byte, content_width, is_cursor_line, cursor_col, cursor_byte, selection, horizontal_scroll);
try out.appendSlice(allocator, ansi_reset);
try out.append(allocator, '\n');
}
@@ -2458,11 +2489,19 @@ fn appendEditorCells(
cursor_col: usize,
cursor_byte: usize,
selection: ?session_mod.Selection,
horizontal_scroll: usize,
) !void {
var i: usize = 0;
var source_col: usize = 0;
var visual_col: usize = 0;
var drew_cursor = false;
while (i < bytes.len and source_col < horizontal_scroll) {
const len = std.unicode.utf8ByteSequenceLength(bytes[i]) catch 1;
const end = @min(bytes.len, i + len);
const width = @max(@as(usize, 1), session_mod.cellWidth(bytes[i..end]));
source_col += width;
i = end;
}
while (i < bytes.len and visual_col < max_cells) {
const len = std.unicode.utf8ByteSequenceLength(bytes[i]) catch 1;
const end = @min(bytes.len, i + len);
@@ -4178,12 +4217,14 @@ test "regular: select mode object grammar selects word line indent parameter and
try client.handleInput("j");
try client.handleInput("w");
try client.handleInput("s");
try client.handleInput("m");
try client.handleInput("w");
var snap = try client.session.snapshot();
try std.testing.expectEqualStrings("alpha", snap.bytes[snap.selection.?.anchor..snap.selection.?.cursor]);
try client.handleInput("n");
try client.handleInput("s");
try client.handleInput("m");
try client.handleInput("l");
snap = try client.session.snapshot();
try std.testing.expectEqualStrings(" alpha(beta, gamma);\n", snap.bytes[snap.selection.?.anchor..snap.selection.?.cursor]);
@@ -5100,5 +5141,5 @@ test "regular: insert Enter copies current line indent and Tab follows tab-inden
try tabs.handleTraceLine("key tab");
try tabs.handleInput("x");
snap = try tabs.session.snapshot();
try std.testing.expectEqualStrings("\titem\tx", snap.bytes);
try std.testing.expectEqualStrings("\titem x", snap.bytes);
}