on master help sign in
Add `m` textobject to select closest surround pair
commitde15d7017186cb735cdd31d12510f61a1707d5fb authorGokul Soumya <gokulps15@gmail.com> dateSun 3 Apr 2022, 21:31 (5 years ago) parentc22873c
4 files changed+66−9
book/src/usage.md+1 helix-core/src/surround.rs+39 helix-core/src/textobject.rs+25−1 helix-term/src/commands.rs+1−8
book/src/usage.md+1
@@ -66,6 +66,7 @@ Currently supported: `word`, `surround`, `function`, `class`, `parameter`.
6666| `w` | Word |
6767| `W` | WORD |
6868| `(`, `[`, `'`, etc | Specified surround pairs |
69+| `m` | Closest surround pair |
6970| `f` | Function |
7071| `c` | Class |
7172| `a` | Argument/parameter |
helix-core/src/surround.rs+39
@@ -52,6 +52,45 @@ pub fn get_pair(ch: char) -> (char, char) {
5252 .unwrap_or((ch, ch))
5353}
5454
55+pub fn find_nth_closest_pairs_pos(
56+ text: RopeSlice,
57+ range: Range,
58+ n: usize,
59+) -> Result<(usize, usize)> {
60+ let is_open_pair = |ch| PAIRS.iter().any(|(open, _)| *open == ch);
61+ let is_close_pair = |ch| PAIRS.iter().any(|(_, close)| *close == ch);
62+
63+ let mut stack = Vec::with_capacity(2);
64+ let pos = range.cursor(text);
65+
66+ for ch in text.chars_at(pos) {
67+ if is_open_pair(ch) {
68+ // Track open pairs encountered so that we can step over
69+ // the correspoding close pairs that will come up further
70+ // down the loop. We want to find a lone close pair whose
71+ // open pair is before the cursor position.
72+ stack.push(ch);
73+ continue;
74+ } else if is_close_pair(ch) {
75+ let (open, _) = get_pair(ch);
76+ if stack.last() == Some(&open) {
77+ stack.pop();
78+ continue;
79+ } else {
80+ // In the ideal case the stack would be empty here and the
81+ // current character would be the close pair that we are
82+ // looking for. It could also be the case that the pairs
83+ // are unbalanced and we encounter a close pair that doesn't
84+ // close the last seen open pair. In either case use this
85+ // char as the auto-detected closest pair.
86+ return find_nth_pairs_pos(text, ch, range, n);
87+ }
88+ }
89+ }
90+
91+ Err(Error::PairNotFound)
92+}
93+
5594/// Find the position of surround pairs of `ch` which can be either a closing
5695/// or opening pair. `n` will skip n - 1 pairs (eg. n=2 will discard (only)
5796/// the first pair found and keep looking)
helix-core/src/textobject.rs+25−1
@@ -205,7 +205,31 @@ pub fn textobject_surround(
205205 ch: char,
206206 count: usize,
207207) -> Range {
208− surround::find_nth_pairs_pos(slice, ch, range, count)
208+ textobject_surround_impl(slice, range, textobject, Some(ch), count)
209+}
210+
211+pub fn textobject_surround_closest(
212+ slice: RopeSlice,
213+ range: Range,
214+ textobject: TextObject,
215+ count: usize,
216+) -> Range {
217+ textobject_surround_impl(slice, range, textobject, None, count)
218+}
219+
220+fn textobject_surround_impl(
221+ slice: RopeSlice,
222+ range: Range,
223+ textobject: TextObject,
224+ ch: Option<char>,
225+ count: usize,
226+) -> Range {
227+ let pair_pos = match ch {
228+ Some(ch) => surround::find_nth_pairs_pos(slice, ch, range, count),
229+ // Automatically find the closest surround pairs
230+ None => surround::find_nth_closest_pairs_pos(slice, range, count),
231+ };
232+ pair_pos
209233 .map(|(anchor, head)| match textobject {
210234 TextObject::Inside => Range::new(next_grapheme_boundary(slice, anchor), head),
211235 TextObject::Around => Range::new(anchor, next_grapheme_boundary(slice, head)),
helix-term/src/commands.rs+1−8
@@ -4040,14 +4040,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
40404040 'a' => textobject_treesitter("parameter", range),
40414041 'o' => textobject_treesitter("comment", range),
40424042 'p' => textobject::textobject_paragraph(text, range, objtype, count),
4043− 'm' => {
4044− let ch = text.char(range.cursor(text));
4045− if !ch.is_ascii_alphanumeric() {
4046− textobject::textobject_surround(text, range, objtype, ch, count)
4047− } else {
4048− range
4049− }
4050− }
4043+ 'm' => textobject::textobject_surround_closest(text, range, objtype, count),
40514044 // TODO: cancel new ranges if inconsistent surround matches across lines
40524045 ch if !ch.is_ascii_alphanumeric() => {
40534046 textobject::textobject_surround(text, range, objtype, ch, count)