summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/edit.zig72
-rw-r--r--test/perf-baseline-gui-Debug.json2
-rw-r--r--test/perf-baseline-tty-Debug.json2
-rw-r--r--test/perf-baseline-tty-ReleaseFast.json2
4 files changed, 59 insertions, 19 deletions
diff --git a/src/edit.zig b/src/edit.zig
index a194ef48..183d6325 100644
--- a/src/edit.zig
+++ b/src/edit.zig
@@ -487,6 +487,19 @@ fn editTextEol(p: *Pardes, t: *Text, b: Bounds) ?panes.EditText {
/// install a rewritten editable text (frees the old one)
pub fn setEditText(p: *Pardes, t: *Text, new: []u8) void {
+ setEditTextEnd(p, t, new, null);
+}
+
+/// Where text `s` put in at `c` ends: `g.`'s place after typing it.
+fn insertedEnd(c: modal.Cursor, s: []const u8) modal.Cursor {
+ const nl = std.mem.lastIndexOfScalar(u8, s, '\n') orelse return .{ .row = c.row, .col = c.col + s.len };
+ return .{ .row = c.row + std.mem.count(u8, s, "\n"), .col = s.len - nl - 1 };
+}
+
+/// `setEditText` with where the edit ended in `new` (in the edit text's
+/// rows) when the caller knows it, as typing does: then nothing scans the
+/// whole text to find it. Null, it is found by comparing the texts.
+pub fn setEditTextEnd(p: *Pardes, t: *Text, new: []u8, known_end: ?modal.Cursor) void {
if (t.what == .tag) {
// A tag is edited as it is shown, but only what follows its
// prefix is its own: an edit that changed the prefix is refused.
@@ -512,10 +525,14 @@ pub fn setEditText(p: *Pardes, t: *Text, new: []u8) void {
const pane = t.pane() orelse return retireEditText(p, new);
// what changed ended where the texts agree again from their ends
const old: []const u8, const row0: i32 = if (pane.file) |f| .{ f.content, 0 } else if (pane.ovl) |o| .{ o.text, o.row } else .{ "", 0 };
- const front = std.mem.indexOfDiff(u8, old, new) orelse @min(old.len, new.len);
- var back: usize = 0;
- while (back < @min(old.len, new.len) - front and old[old.len - 1 - back] == new[new.len - 1 - back]) back += 1;
- const end = modal.positionAt(new, new.len - back);
+ const end = known_end orelse found: {
+ // ponytail: O(file), for the edits that do not say where they ended
+ // (paste, a pipe, s): typing passes its end and never comes here.
+ const front = std.mem.indexOfDiff(u8, old, new) orelse @min(old.len, new.len);
+ var back: usize = 0;
+ while (back < @min(old.len, new.len) - front and old[old.len - 1 - back] == new[new.len - 1 - back]) back += 1;
+ break :found modal.positionAt(new, new.len - back);
+ };
t.last_edit = .{ .row = @as(i32, @intCast(end.row)) + row0, .col = @intCast(end.col) };
if (pane.file) |*f| return panes.File.setContent(p, f, new);
panes.Terminal.setEditText(p, pane, new);
@@ -813,7 +830,7 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void {
const c = modal.Cursor{ .row = @intCast(@max(0, t.cur_row - eb.row0)), .col = @intCast(@max(0, t.cur_col)) };
if (key.text.len > 0 and !key.ctrl and !key.alt) {
const new = modal.insertAt(p.gpa, text, c, key.text) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, insertedEnd(c, key.text));
for (key.text) |ch| {
if (ch == '\n') {
t.cur_row += 1;
@@ -837,7 +854,7 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void {
const from = @min(wr.anchor, wr.head);
const fc = modal.positionAt(e2.text, from);
const new = modal.deleteSpan(p.gpa, e2.text, fc, c2) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, fc);
t.cur_row = @as(i32, @intCast(fc.row)) + e2.row0;
t.cur_col = @intCast(fc.col);
t.cur_pinned = true;
@@ -855,13 +872,13 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void {
const to = @max(wr.anchor, wr.head);
if (to <= g) return;
const new = modal.deleteSpan(p.gpa, e2.text, c2, modal.positionAt(e2.text, to)) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, c2);
t.cur_pinned = true;
return;
}
if (hit(key, config.kill_to_line_start)) {
const new = modal.deleteSpan(p.gpa, text, .{ .row = c.row, .col = 0 }, c) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, .{ .row = c.row, .col = 0 });
t.cur_col = 0;
t.cur_pinned = true;
t.show();
@@ -870,7 +887,7 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void {
if (hit(key, config.kill_to_line_end)) {
const llen = modal.lineSlice(text, c.row).len;
const new = modal.deleteSpan(p.gpa, text, c, .{ .row = c.row, .col = llen }) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, c);
t.cur_pinned = true;
return;
}
@@ -883,7 +900,7 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void {
// cursor go and the new line takes the indent
if (std.mem.indexOfNone(u8, before, " \t") == null) {
const new = modal.insertAt(p.gpa, text, .{ .row = c.row, .col = 0 }, "\n") catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, .{ .row = c.row + 1, .col = 0 });
t.cur_row += 1;
t.cur_pinned = true;
t.show();
@@ -900,7 +917,7 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void {
const cut = modal.deleteSpan(p.gpa, text, .{ .row = c.row, .col = kept }, c) catch return;
defer p.gpa.free(cut);
const new = modal.insertAt(p.gpa, cut, .{ .row = c.row, .col = kept }, block) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, .{ .row = c.row + 1, .col = indent });
t.cur_row += 1;
t.cur_col = @intCast(indent);
t.cur_pinned = true;
@@ -922,7 +939,7 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void {
while (drop > 0 and prev > 0 and line[prev - 1] == ' ') : (drop -= 1) prev -= 1;
}
const new = modal.deleteSpan(p.gpa, text, .{ .row = c.row, .col = prev }, c) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, .{ .row = c.row, .col = prev });
t.cur_col = @intCast(prev);
t.cur_pinned = true;
t.show();
@@ -936,7 +953,7 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void {
if (row == 0) return;
const prevlen = modal.lineSlice(e2.text, row - 1).len;
const new = modal.deleteSpan(p.gpa, e2.text, .{ .row = row - 1, .col = prevlen }, .{ .row = row, .col = 0 }) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, .{ .row = row - 1, .col = prevlen });
t.cur_row -= 1;
t.cur_col = @intCast(prevlen);
t.cur_pinned = true;
@@ -948,13 +965,13 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void {
const line = modal.lineSlice(text, c.row);
if (c.col < line.len) {
const new = modal.deleteChar(p.gpa, text, c) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, c);
} else {
const e2 = editText(p, t, t.cur_row, t.cur_row + 1, t.cur_col) orelse return;
const row: usize = @intCast(@max(0, t.cur_row - e2.row0));
if (row + 1 >= modal.lineCount(e2.text)) return;
const new = modal.deleteSpan(p.gpa, e2.text, .{ .row = row, .col = c.col }, .{ .row = row + 1, .col = 0 }) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, .{ .row = row, .col = c.col });
}
t.cur_pinned = true;
},
@@ -993,7 +1010,7 @@ pub fn insertTab(p: *Pardes, t: *Text) void {
};
const pad = modal.INDENT_W - (c.col % modal.INDENT_W);
const new = modal.insertAt(p.gpa, eb.text, c, " "[0..pad]) catch return;
- setEditText(p, t, new);
+ setEditTextEnd(p, t, new, .{ .row = c.row, .col = c.col + pad });
t.cur_col += @intCast(pad);
t.cur_pinned = true;
t.show();
@@ -2365,3 +2382,26 @@ test "double-click selects as acme's does" {
const e = doubleClickRange(lines, 7);
try std.testing.expectEqualStrings("two\n", lines[e[0]..e[1]]);
}
+
+test "a typed edit's known end is where the texts' comparison would find it" {
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 20, .tty_only = true });
+ defer p.deinit();
+ const pane = try p.setTestFile("one\ntwo three\nfour\n");
+ const t = &pane.body;
+ const cases = [_]struct { at: modal.Cursor, s: []const u8 }{
+ .{ .at = .{ .row = 1, .col = 3 }, .s = "xy" },
+ .{ .at = .{ .row = 0, .col = 0 }, .s = "a\nbc" },
+ .{ .at = .{ .row = 2, .col = 4 }, .s = "\n" },
+ };
+ for (cases) |c| {
+ const base = try p.gpa.dupe(u8, pane.file.?.content);
+ defer p.gpa.free(base);
+ const scanned = try modal.insertAt(p.gpa, base, c.at, c.s);
+ setEditTextEnd(p, t, scanned, null);
+ const by_scan = t.last_edit.?;
+ const known = try modal.insertAt(p.gpa, base, c.at, c.s);
+ setEditTextEnd(p, t, known, insertedEnd(c.at, c.s));
+ try std.testing.expectEqual(by_scan.row, t.last_edit.?.row);
+ try std.testing.expectEqual(by_scan.col, t.last_edit.?.col);
+ }
+}
diff --git a/test/perf-baseline-gui-Debug.json b/test/perf-baseline-gui-Debug.json
index fd64ebf9..6bfe2c90 100644
--- a/test/perf-baseline-gui-Debug.json
+++ b/test/perf-baseline-gui-Debug.json
@@ -1 +1 @@
-{"harness":"67f85bf9692f1778","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"gui","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1768,"med_us":2025,"p90_us":2122,"max_us":2125},{"op":"render","fixture":"medium","min_us":72,"med_us":75,"p90_us":76,"max_us":83},{"op":"key-down","fixture":"medium","min_us":73,"med_us":76,"p90_us":80,"max_us":84},{"op":"key-right","fixture":"medium","min_us":74,"med_us":77,"p90_us":81,"max_us":84},{"op":"page-down","fixture":"medium","min_us":73,"med_us":74,"p90_us":77,"max_us":79},{"op":"wheel","fixture":"medium","min_us":71,"med_us":75,"p90_us":81,"max_us":81},{"op":"edit-char","fixture":"medium","min_us":2413,"med_us":2674,"p90_us":2943,"max_us":3111}]}
+{"harness":"67f85bf9692f1778","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"gui","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1887,"med_us":2147,"p90_us":2277,"max_us":2326},{"op":"render","fixture":"medium","min_us":73,"med_us":76,"p90_us":84,"max_us":96},{"op":"key-down","fixture":"medium","min_us":73,"med_us":81,"p90_us":84,"max_us":86},{"op":"key-right","fixture":"medium","min_us":73,"med_us":77,"p90_us":91,"max_us":173},{"op":"page-down","fixture":"medium","min_us":75,"med_us":78,"p90_us":80,"max_us":84},{"op":"wheel","fixture":"medium","min_us":72,"med_us":76,"p90_us":79,"max_us":83},{"op":"edit-char","fixture":"medium","min_us":1269,"med_us":1437,"p90_us":1680,"max_us":1697}]}
diff --git a/test/perf-baseline-tty-Debug.json b/test/perf-baseline-tty-Debug.json
index 682ea028..6ffd034c 100644
--- a/test/perf-baseline-tty-Debug.json
+++ b/test/perf-baseline-tty-Debug.json
@@ -1 +1 @@
-{"harness":"67f85bf9692f1778","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1944,"med_us":2130,"p90_us":2232,"max_us":2394},{"op":"render","fixture":"medium","min_us":71,"med_us":75,"p90_us":76,"max_us":91},{"op":"key-down","fixture":"medium","min_us":72,"med_us":79,"p90_us":93,"max_us":102},{"op":"key-right","fixture":"medium","min_us":73,"med_us":76,"p90_us":78,"max_us":81},{"op":"page-down","fixture":"medium","min_us":72,"med_us":73,"p90_us":76,"max_us":76},{"op":"wheel","fixture":"medium","min_us":71,"med_us":85,"p90_us":96,"max_us":102},{"op":"edit-char","fixture":"medium","min_us":2612,"med_us":3112,"p90_us":3238,"max_us":3618}]}
+{"harness":"67f85bf9692f1778","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1907,"med_us":2037,"p90_us":2283,"max_us":2371},{"op":"render","fixture":"medium","min_us":72,"med_us":80,"p90_us":87,"max_us":87},{"op":"key-down","fixture":"medium","min_us":73,"med_us":78,"p90_us":82,"max_us":88},{"op":"key-right","fixture":"medium","min_us":72,"med_us":75,"p90_us":87,"max_us":87},{"op":"page-down","fixture":"medium","min_us":73,"med_us":81,"p90_us":93,"max_us":94},{"op":"wheel","fixture":"medium","min_us":73,"med_us":75,"p90_us":84,"max_us":88},{"op":"edit-char","fixture":"medium","min_us":1333,"med_us":1712,"p90_us":1956,"max_us":2636}]}
diff --git a/test/perf-baseline-tty-ReleaseFast.json b/test/perf-baseline-tty-ReleaseFast.json
index 4fd96d92..5d68e6b2 100644
--- a/test/perf-baseline-tty-ReleaseFast.json
+++ b/test/perf-baseline-tty-ReleaseFast.json
@@ -1 +1 @@
-{"harness":"67f85bf9692f1778","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"ReleaseFast","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseFast","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1971,"med_us":2259,"p90_us":2308,"max_us":2339},{"op":"render","fixture":"medium","min_us":71,"med_us":75,"p90_us":78,"max_us":79},{"op":"key-down","fixture":"medium","min_us":73,"med_us":77,"p90_us":79,"max_us":95},{"op":"key-right","fixture":"medium","min_us":72,"med_us":77,"p90_us":80,"max_us":88},{"op":"page-down","fixture":"medium","min_us":72,"med_us":76,"p90_us":81,"max_us":83},{"op":"wheel","fixture":"medium","min_us":71,"med_us":75,"p90_us":80,"max_us":91},{"op":"edit-char","fixture":"medium","min_us":2662,"med_us":2999,"p90_us":3375,"max_us":3622}]}
+{"harness":"67f85bf9692f1778","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"ReleaseFast","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseFast","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1771,"med_us":2022,"p90_us":2188,"max_us":2228},{"op":"render","fixture":"medium","min_us":75,"med_us":76,"p90_us":76,"max_us":86},{"op":"key-down","fixture":"medium","min_us":73,"med_us":79,"p90_us":80,"max_us":80},{"op":"key-right","fixture":"medium","min_us":72,"med_us":78,"p90_us":81,"max_us":82},{"op":"page-down","fixture":"medium","min_us":72,"med_us":76,"p90_us":80,"max_us":84},{"op":"wheel","fixture":"medium","min_us":75,"med_us":76,"p90_us":78,"max_us":79},{"op":"edit-char","fixture":"medium","min_us":1106,"med_us":1436,"p90_us":1692,"max_us":1721}]}