summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/helix-keys.md2
-rw-r--r--src/edit.zig12
-rw-r--r--test/hxcases/cases.jsonl3
-rw-r--r--test/hxcases/goldens.jsonl3
4 files changed, 16 insertions, 4 deletions
diff --git a/docs/helix-keys.md b/docs/helix-keys.md
index 84ee5124..0b0da2ae 100644
--- a/docs/helix-keys.md
+++ b/docs/helix-keys.md
@@ -283,7 +283,7 @@ keeps the matches it found so far.
| --- | --- | --- | --- |
| `mm` | goto matching bracket | dumb text scan with nesting for `()[]{}<>`; ON a bracket only (no TS "nearest pair"); works on terminals via the motion surface | helix-verified (phase 5) |
| `mi<pair>` / `ma<pair>` | select inside / around textobject | pairs `( ) [ ] { } < >` nesting-aware multi-line; quotes `' " `` ` `` ` **line-scoped** (plain-text strings don't span lines); `w`/`W` word run (+trailing ws around, leading if none); `p` blank-line block (+trailing blanks around). Empty inside (`()`) = no-op. Selections work on terminals; `Pane.pending2` holds the i/a/s/r/d sub-key | helix-verified (phase 5) |
-| `ms<ch>` | surround selection (or cursor char) with the `<ch>` pair; wrap incl. pair becomes the selection | either bracket names its pair; any other ASCII char wraps with itself; file panes only | helix-verified (phase 5) |
+| `ms<ch>` | surround selection (or cursor char) with the `<ch>` pair; wrap incl. pair becomes the selection, keeping the direction it had | either bracket names its pair; any other ASCII char wraps with itself; file panes only | helix-verified (phase 5) |
| `mr<from><to>` | replace enclosing `<from>` pair chars with `<to>`'s | `Pane.pending_ch` holds `<from>` while `<to>` pends; file panes only | helix-verified (phase 5) |
| `md<ch>` | delete the enclosing `<ch>` pair chars | file panes only | helix-verified (phase 5) |
diff --git a/src/edit.zig b/src/edit.zig
index 3cbbc28c..56d537bf 100644
--- a/src/edit.zig
+++ b/src/edit.zig
@@ -1512,16 +1512,22 @@ pub fn surroundAdd(p: *Pardes, t: *Text, ch: u21) void {
t.select = false;
const eb = editTextEol(p, t, selRows(t)) orelse return;
const r = selRange(t, eb.text, eb.row0);
+ // the wrapped range keeps the direction it had (helix with_direction)
+ 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 close_col = modal.nextGrapheme(modal.lineSlice(eb.text, r.b.row), r.b.col);
var new = modal.insertAt(p.gpa, eb.text, .{ .row = r.b.row, .col = close_col }, &[1]u8{pr.c}) catch return;
setEditText(p, t, new);
new = modal.insertAt(p.gpa, new, .{ .row = r.a.row, .col = r.a.col }, &[1]u8{pr.o}) catch return;
setEditText(p, t, new);
+ const open_row = @as(i32, @intCast(r.a.row)) + eb.row0;
+ const open_col: i32 = @intCast(r.a.col);
+ const close_row = @as(i32, @intCast(r.b.row)) + eb.row0;
+ const close_at: i32 = @intCast(close_col + @as(usize, if (r.a.row == r.b.row) 1 else 0));
t.msel.active = false;
- t.vsel = .{ .active = true, .row = @as(i32, @intCast(r.a.row)) + eb.row0, .col = @intCast(r.a.col), .explicit = false };
- t.cur_row = @as(i32, @intCast(r.b.row)) + eb.row0;
- t.cur_col = @intCast(close_col + @as(usize, if (r.a.row == r.b.row) 1 else 0));
+ t.vsel = .{ .active = true, .row = if (back) close_row else open_row, .col = if (back) close_at else open_col, .explicit = false };
+ t.cur_row = if (back) open_row else close_row;
+ t.cur_col = if (back) open_col else close_at;
t.cur_pinned = true;
t.sticky_col = -1;
t.show();
diff --git a/test/hxcases/cases.jsonl b/test/hxcases/cases.jsonl
index b0a3aad4..d7f76912 100644
--- a/test/hxcases/cases.jsonl
+++ b/test/hxcases/cases.jsonl
@@ -316,6 +316,9 @@
{"name": "ms-sel", "pane": "file", "text": "hello\n", "keys": "vllms("}
{"name": "ms-bare", "pane": "file", "text": "ab\n", "keys": "ms("}
{"name": "ms-quote-self", "pane": "file", "text": "ab cd\n", "keys": "wems'"}
+{"name": "ms-backward", "pane": "file", "text": "ab cd\n", "keys": "wbms("}
+{"name": "ms-backward-lines", "pane": "file", "text": "ab\ncd\nef\n", "keys": "jjlvkkms["}
+{"name": "msel-ms-backward", "pane": "file", "text": "id 1,x 2\n", "keys": "%s\\d<ret>dhbms`"}
{"name": "md-basic", "pane": "file", "text": "a(bcd)e\n", "keys": "3lmd("}
{"name": "md-quote", "pane": "file", "text": "a 'bc' d\n", "keys": "3lmd'"}
{"name": "mr-basic", "pane": "file", "text": "a(bcd)e\n", "keys": "3lmr(["}
diff --git a/test/hxcases/goldens.jsonl b/test/hxcases/goldens.jsonl
index eee7294b..cab7393b 100644
--- a/test/hxcases/goldens.jsonl
+++ b/test/hxcases/goldens.jsonl
@@ -316,6 +316,9 @@
{"name":"ms-sel","text":"(hel)lo\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":0}}
{"name":"ms-bare","text":"(a)b\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
{"name":"ms-quote-self","text":"ab' cd'\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":2}}
+{"name":"ms-backward","text":"(ab )cd\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":4}}
+{"name":"ms-backward-lines","text":"a[b\ncd\nef]\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":2,"col":2}}
+{"name":"msel-ms-backward","text":"`id `,`x `\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":4},"sels":[{"cursor":{"row":0,"col":0},"anchor":{"row":0,"col":4}},{"cursor":{"row":0,"col":6},"anchor":{"row":0,"col":9}}],"primary":0}
{"name":"md-basic","text":"abcde\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
{"name":"md-quote","text":"a bc d\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
{"name":"mr-basic","text":"a[bcd]e\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}