Add m textobject to select closest surround pair

This commit is contained in:
Gokul Soumya 2022-04-03 21:31:43 +05:30 committed by Blaž Hrastnik
parent c22873c33f
commit de15d70171
4 changed files with 66 additions and 9 deletions

View File

@ -66,6 +66,7 @@ ## Textobjects
| `w` | Word | | `w` | Word |
| `W` | WORD | | `W` | WORD |
| `(`, `[`, `'`, etc | Specified surround pairs | | `(`, `[`, `'`, etc | Specified surround pairs |
| `m` | Closest surround pair |
| `f` | Function | | `f` | Function |
| `c` | Class | | `c` | Class |
| `a` | Argument/parameter | | `a` | Argument/parameter |

View File

@ -52,6 +52,45 @@ pub fn get_pair(ch: char) -> (char, char) {
.unwrap_or((ch, ch)) .unwrap_or((ch, ch))
} }
pub fn find_nth_closest_pairs_pos(
text: RopeSlice,
range: Range,
n: usize,
) -> Result<(usize, usize)> {
let is_open_pair = |ch| PAIRS.iter().any(|(open, _)| *open == ch);
let is_close_pair = |ch| PAIRS.iter().any(|(_, close)| *close == ch);
let mut stack = Vec::with_capacity(2);
let pos = range.cursor(text);
for ch in text.chars_at(pos) {
if is_open_pair(ch) {
// Track open pairs encountered so that we can step over
// the correspoding close pairs that will come up further
// down the loop. We want to find a lone close pair whose
// open pair is before the cursor position.
stack.push(ch);
continue;
} else if is_close_pair(ch) {
let (open, _) = get_pair(ch);
if stack.last() == Some(&open) {
stack.pop();
continue;
} else {
// In the ideal case the stack would be empty here and the
// current character would be the close pair that we are
// looking for. It could also be the case that the pairs
// are unbalanced and we encounter a close pair that doesn't
// close the last seen open pair. In either case use this
// char as the auto-detected closest pair.
return find_nth_pairs_pos(text, ch, range, n);
}
}
}
Err(Error::PairNotFound)
}
/// Find the position of surround pairs of `ch` which can be either a closing /// Find the position of surround pairs of `ch` which can be either a closing
/// or opening pair. `n` will skip n - 1 pairs (eg. n=2 will discard (only) /// or opening pair. `n` will skip n - 1 pairs (eg. n=2 will discard (only)
/// the first pair found and keep looking) /// the first pair found and keep looking)

View File

@ -205,7 +205,31 @@ pub fn textobject_surround(
ch: char, ch: char,
count: usize, count: usize,
) -> Range { ) -> Range {
surround::find_nth_pairs_pos(slice, ch, range, count) textobject_surround_impl(slice, range, textobject, Some(ch), count)
}
pub fn textobject_surround_closest(
slice: RopeSlice,
range: Range,
textobject: TextObject,
count: usize,
) -> Range {
textobject_surround_impl(slice, range, textobject, None, count)
}
fn textobject_surround_impl(
slice: RopeSlice,
range: Range,
textobject: TextObject,
ch: Option<char>,
count: usize,
) -> Range {
let pair_pos = match ch {
Some(ch) => surround::find_nth_pairs_pos(slice, ch, range, count),
// Automatically find the closest surround pairs
None => surround::find_nth_closest_pairs_pos(slice, range, count),
};
pair_pos
.map(|(anchor, head)| match textobject { .map(|(anchor, head)| match textobject {
TextObject::Inside => Range::new(next_grapheme_boundary(slice, anchor), head), TextObject::Inside => Range::new(next_grapheme_boundary(slice, anchor), head),
TextObject::Around => Range::new(anchor, next_grapheme_boundary(slice, head)), TextObject::Around => Range::new(anchor, next_grapheme_boundary(slice, head)),

View File

@ -4040,14 +4040,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
'a' => textobject_treesitter("parameter", range), 'a' => textobject_treesitter("parameter", range),
'o' => textobject_treesitter("comment", range), 'o' => textobject_treesitter("comment", range),
'p' => textobject::textobject_paragraph(text, range, objtype, count), 'p' => textobject::textobject_paragraph(text, range, objtype, count),
'm' => { 'm' => textobject::textobject_surround_closest(text, range, objtype, count),
let ch = text.char(range.cursor(text));
if !ch.is_ascii_alphanumeric() {
textobject::textobject_surround(text, range, objtype, ch, count)
} else {
range
}
}
// TODO: cancel new ranges if inconsistent surround matches across lines // TODO: cancel new ranges if inconsistent surround matches across lines
ch if !ch.is_ascii_alphanumeric() => { ch if !ch.is_ascii_alphanumeric() => {
textobject::textobject_surround(text, range, objtype, ch, count) textobject::textobject_surround(text, range, objtype, ch, count)