diff options
| -rw-r--r-- | docs/helix-keys.md | 2 | ||||
| -rw-r--r-- | src/edit.zig | 29 | ||||
| -rw-r--r-- | test/hxcases/cases.jsonl | 2 | ||||
| -rw-r--r-- | test/hxcases/goldens.jsonl | 2 |
4 files changed, 26 insertions, 9 deletions
diff --git a/docs/helix-keys.md b/docs/helix-keys.md index 0b0da2ae..816010a6 100644 --- a/docs/helix-keys.md +++ b/docs/helix-keys.md @@ -182,7 +182,7 @@ found. Pure text math in `modal.zig`: `findChar`, `matchBracket`, | Key | Behavior | Notes | Status | | --- | --- | --- | --- | | `r<ch>` | replace selection/char with `<ch>`, newlines kept | terminals: replaces the typed-run byte under the cursor if any, else no-op — one code path for both kinds, since `normalReplaceChar` goes through `Pardes.editText`/`setEditText`, which route to a file's content or to `term_pane`'s typed-run overlay | helix-verified (phase 5) | -| `R` | replace selection (or cursor char) with the yank register; pasted text becomes the selection, head on its last char | uses the INTERNAL yank (`p.yank`), no clipboard round trip — `SPC R` is the system-clipboard twin; empty register = no-op; terminals: no-op | helix-verified (phase 5) | +| `R` | replace selection (or cursor char) with the yank register; pasted text becomes the selection in the direction the replaced one had (head on its last char, or its first when backward) | uses the INTERNAL yank (`p.yank`), no clipboard round trip — `SPC R` is the system-clipboard twin; empty register = no-op; terminals: no-op | helix-verified (phase 5) | | `~` | switch case of selection/char, selection kept | terminals: run byte only | helix-verified (phase 5) | | `` ` `` | selection to lowercase | terminals: run byte only | helix-verified (phase 5) | | ``Alt-` `` | selection to uppercase | terminals: run byte only | helix-verified (phase 5) | diff --git a/src/edit.zig b/src/edit.zig index 56d537bf..fb8681d8 100644 --- a/src/edit.zig +++ b/src/edit.zig @@ -1125,27 +1125,40 @@ pub fn normalReplaceYank(p: *Pardes, t: *Text) void { replaceWithText(p, t, p.yank orelse return); } -/// The pasted text becomes the selection, head on its last char (which for -/// a trailing-newline `y` is the '\n' cell of the last full line). +/// The pasted text becomes the selection, in the direction the replaced one +/// had: a forward head lands on its last char (which for a trailing-newline +/// `y` is the '\n' cell of the last full line), a backward one on its first. fn replaceWithText(p: *Pardes, t: *Text, y: []const u8) void { if (y.len == 0) return; t.select = false; const eb = editTextEol(p, t, selRows(t)) orelse return; const r = selRange(t, eb.text, eb.row0); + const back = t.vsel.active and (t.cur_row < t.vsel.row or (t.cur_row == t.vsel.row and t.cur_col < t.vsel.col)); pushUndo(p, t); const new = modal.replaceRange(p.gpa, eb.text, r.a, r.b, y) catch return; setEditText(p, t, new); t.msel.active = false; - t.vsel = .{ .active = modal.nextGrapheme(y, 0) < y.len, .row = @as(i32, @intCast(r.a.row)) + eb.row0, .col = @intCast(r.a.col), .explicit = false }; const end = modal.advanceBy(r.a, y); + var last_row: i32 = undefined; + var last_col: i32 = undefined; if (end.col > 0) { - t.cur_row = @as(i32, @intCast(end.row)) + eb.row0; - t.cur_col = @intCast(modal.prevGrapheme(modal.lineSlice(new, end.row), end.col)); + last_row = @as(i32, @intCast(end.row)) + eb.row0; + last_col = @intCast(modal.prevGrapheme(modal.lineSlice(new, end.row), end.col)); } else { - // the yank ended in '\n': the cursor lands ON that newline - t.cur_row = @as(i32, @intCast(end.row -| 1)) + eb.row0; - t.cur_col = @intCast(modal.lineSlice(new, end.row -| 1).len); + // the yank ended in '\n': its last cell is that newline + last_row = @as(i32, @intCast(end.row -| 1)) + eb.row0; + last_col = @intCast(modal.lineSlice(new, end.row -| 1).len); } + const first_row = @as(i32, @intCast(r.a.row)) + eb.row0; + const first_col: i32 = @intCast(r.a.col); + t.vsel = .{ + .active = modal.nextGrapheme(y, 0) < y.len, + .row = if (back) last_row else first_row, + .col = if (back) last_col else first_col, + .explicit = false, + }; + t.cur_row = if (back) first_row else last_row; + t.cur_col = if (back) first_col else last_col; t.cur_pinned = true; t.sticky_col = -1; t.show(); diff --git a/test/hxcases/cases.jsonl b/test/hxcases/cases.jsonl index d7f76912..6e1b8188 100644 --- a/test/hxcases/cases.jsonl +++ b/test/hxcases/cases.jsonl @@ -287,6 +287,8 @@ {"name": "r-cancel-esc", "pane": "file", "text": "abc\n", "keys": "r<esc>l"} {"name": "R-inline", "pane": "file", "text": "alpha beta\n", "keys": "wyggR"} {"name": "R-linewise", "pane": "file", "text": "one\ntwo\n", "keys": "xyjR"} +{"name": "R-backward", "pane": "file", "text": "alpha beta\n", "keys": "wyebR"} +{"name": "R-backward-linewise", "pane": "file", "text": "one\ntwo three\n", "keys": "xyjxbR"} {"name": "tilde-sel-keeps", "pane": "file", "text": "abc\n", "keys": "vll~"} {"name": "tilde-bare", "pane": "file", "text": "aBc\n", "keys": "~"} {"name": "backtick-lower", "pane": "file", "text": "ABC\n", "keys": "vll`"} diff --git a/test/hxcases/goldens.jsonl b/test/hxcases/goldens.jsonl index cab7393b..8ac110e6 100644 --- a/test/hxcases/goldens.jsonl +++ b/test/hxcases/goldens.jsonl @@ -287,6 +287,8 @@ {"name":"r-cancel-esc","text":"abc\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}} {"name":"R-inline","text":"alpha lpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}} {"name":"R-linewise","text":"one\ntwoone\n","mode":"normal","cursor":{"row":1,"col":6},"anchor":{"row":1,"col":3}} +{"name":"R-backward","text":"alpha alpha \n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":11}} +{"name":"R-backward-linewise","text":"one\ntwo one\n\n","mode":"normal","cursor":{"row":1,"col":4},"anchor":{"row":1,"col":7}} {"name":"tilde-sel-keeps","text":"ABC\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}} {"name":"tilde-bare","text":"ABc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}} {"name":"backtick-lower","text":"abc\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}} |
