mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 09:26:19 +04:00
Add m
textobject to select closest surround pair
This commit is contained in:
parent
c22873c33f
commit
de15d70171
@ -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 |
|
||||||
|
@ -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)
|
||||||
|
@ -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)),
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user