diff options
| -rw-r--r-- | src/edit.zig | 72 | ||||
| -rw-r--r-- | test/perf-baseline-gui-Debug.json | 2 | ||||
| -rw-r--r-- | test/perf-baseline-tty-Debug.json | 2 | ||||
| -rw-r--r-- | test/perf-baseline-tty-ReleaseFast.json | 2 |
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}]} |
