Skip to content

Add ability to match in next and previous char pairs (2) #11695

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions book/src/generated/static-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,28 @@
| `surround_add` | Surround add | normal: `` ms ``, select: `` ms `` |
| `surround_replace` | Surround replace | normal: `` mr ``, select: `` mr `` |
| `surround_delete` | Surround delete | normal: `` md ``, select: `` md `` |
| `select_textobject_around` | Select around object | normal: `` ma ``, select: `` ma `` |
| `select_textobject_inner` | Select inside object | normal: `` mi ``, select: `` mi `` |
| `select_textobject_inside_type` | Select inside type definition (tree-sitter) | normal: `` mit ``, select: `` mit `` |
| `select_textobject_around_type` | Select around type definition (tree-sitter) | normal: `` mat ``, select: `` mat `` |
| `select_textobject_inside_function` | Select inside function (tree-sitter) | normal: `` mif ``, select: `` mif `` |
| `select_textobject_around_function` | Select around function (tree-sitter) | normal: `` maf ``, select: `` maf `` |
| `select_textobject_inside_parameter` | Select inside argument/parameter (tree-sitter) | normal: `` mia ``, select: `` mia `` |
| `select_textobject_around_parameter` | Select around argument/parameter (tree-sitter) | normal: `` maa ``, select: `` maa `` |
| `select_textobject_inside_comment` | Select inside comment (tree-sitter) | normal: `` mic ``, select: `` mic `` |
| `select_textobject_around_comment` | Select around comment (tree-sitter) | normal: `` mac ``, select: `` mac `` |
| `select_textobject_inside_test` | Select inside test (tree-sitter) | normal: `` miT ``, select: `` miT `` |
| `select_textobject_around_test` | Select around test (tree-sitter) | normal: `` maT ``, select: `` maT `` |
| `select_textobject_inside_entry` | Select inside data structure entry (tree-sitter) | normal: `` mie ``, select: `` mie `` |
| `select_textobject_around_entry` | Select around data structure entry (tree-sitter) | normal: `` mae ``, select: `` mae `` |
| `select_textobject_inside_paragraph` | Select inside paragraph | normal: `` mip ``, select: `` mip `` |
| `select_textobject_around_paragraph` | Select around paragraph | normal: `` map ``, select: `` map `` |
| `select_textobject_inside_closest_surrounding_pair` | Select inside closest surrounding pair (tree-sitter) | normal: `` mim ``, select: `` mim `` |
| `select_textobject_around_closest_surrounding_pair` | Select around closest surrounding pair (tree-sitter) | normal: `` mam ``, select: `` mam `` |
| `select_textobject_inside_word` | Select inside word | normal: `` miw ``, select: `` miw `` |
| `select_textobject_around_word` | Select around word | normal: `` maw ``, select: `` maw `` |
| `select_textobject_inside_WORD` | Select inside WORD | normal: `` miW ``, select: `` miW `` |
| `select_textobject_around_WORD` | Select around WORD | normal: `` maW ``, select: `` maW `` |
| `select_textobject_inside_change` | Select inside VCS change | normal: `` mig ``, select: `` mig `` |
| `select_textobject_around_change` | Select around VCS change | normal: `` mag ``, select: `` mag `` |
| `goto_next_function` | Goto next function | normal: `` ]f ``, select: `` ]f `` |
| `goto_prev_function` | Goto previous function | normal: `` [f ``, select: `` [f `` |
| `goto_next_class` | Goto next type definition | normal: `` ]t ``, select: `` ]t `` |
Expand Down
82 changes: 76 additions & 6 deletions helix-core/src/surround.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,24 +154,42 @@ fn find_nth_closest_pairs_plain(
Err(Error::PairNotFound)
}

pub enum FindType {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like each of these enum variants are storing the count, right? Let's separate the enum from the count and pass that as a separate parameter even if it means adding #[allow(clippy::too_many_arguments)] for some functions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, done

Surround,
Next,
Prev,
}

/// 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)
/// the first pair found and keep looking)
pub fn find_nth_pairs_pos(
text: RopeSlice,
ch: char,
range: Range,
find_type: FindType,
n: usize,
) -> Result<(usize, usize)> {
if text.len_chars() < 2 {
return Err(Error::PairNotFound);
}
if range.to() >= text.len_chars() {
if range.to() > text.len_chars() {
return Err(Error::RangeExceedsText);
}

let (open, close) = get_pair(ch);
let pos = range.cursor(text);
let (pos, n) = match find_type {
FindType::Surround => (pos, n),
FindType::Next => match search::find_nth_next(text, open, pos, n) {
Some(next_pos) => (next_pos + 1, 1),
None => return Err(Error::PairNotFound),
},
FindType::Prev => match search::find_nth_prev(text, close, pos, n) {
Some(next_pos) => (next_pos - 1, 1),
None => return Err(Error::PairNotFound),
},
};

let (open, close) = if open == close {
if Some(open) == text.get_char(pos) {
Expand Down Expand Up @@ -298,7 +316,7 @@ pub fn get_surround_pos(
for &range in selection {
let (open_pos, close_pos) = {
let range_raw = match ch {
Some(ch) => find_nth_pairs_pos(text, ch, range, skip)?,
Some(ch) => find_nth_pairs_pos(text, ch, range, FindType::Surround, skip)?,
None => find_nth_closest_pairs_pos(syntax, text, range, skip)?,
};
let range = Range::new(range_raw.0, range_raw.1);
Expand Down Expand Up @@ -392,8 +410,14 @@ mod test {

assert_eq!(2, expectations.len());
assert_eq!(
find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 1)
.expect("find should succeed"),
find_nth_pairs_pos(
doc.slice(..),
'\'',
selection.primary(),
FindType::Surround,
1
)
.expect("find should succeed"),
(expectations[0], expectations[1])
)
}
Expand All @@ -409,7 +433,47 @@ mod test {

assert_eq!(2, expectations.len());
assert_eq!(
find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 2)
find_nth_pairs_pos(
doc.slice(..),
'\'',
selection.primary(),
FindType::Surround,
2
)
.expect("find should succeed"),
(expectations[0], expectations[1])
)
}

#[test]
fn test_find_inside_third_next_quote() {
#[rustfmt::skip]
let (doc, selection, expectations) =
rope_with_selections_and_expectations(
"some 'nested 'quoted' text' on this 'line'\n'and this one'",
" ^ _ _ \n "
);

assert_eq!(2, expectations.len());
assert_eq!(
find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), FindType::Next, 3)
.expect("find should succeed"),
(expectations[0], expectations[1])
)
}

#[test]
fn test_find_inside_prev_quote() {
#[rustfmt::skip]
let (doc, selection, expectations) =
rope_with_selections_and_expectations(
"some 'nested 'quoted' text' on this 'line'\n'and this one'",
" _ _ ^ \n "
);

assert_eq!(2, expectations.len());
assert_eq!(
find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), FindType::Prev, 1)
.expect("find should succeed"),
(expectations[0], expectations[1])
)
Expand All @@ -425,7 +489,13 @@ mod test {
);

assert_eq!(
find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 1),
find_nth_pairs_pos(
doc.slice(..),
'\'',
selection.primary(),
FindType::Surround,
1
),
Err(Error::CursorOnAmbiguousPair)
)
}
Expand Down
47 changes: 38 additions & 9 deletions helix-core/src/textobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::chars::{categorize_char, char_is_whitespace, CharCategory};
use crate::graphemes::{next_grapheme_boundary, prev_grapheme_boundary};
use crate::line_ending::rope_is_line_ending;
use crate::movement::Direction;
use crate::surround::FindType;
use crate::syntax::LanguageConfiguration;
use crate::Range;
use crate::{surround, Syntax};
Expand Down Expand Up @@ -204,9 +205,16 @@ pub fn textobject_pair_surround(
range: Range,
textobject: TextObject,
ch: char,
find_type: FindType,
count: usize,
) -> Range {
textobject_pair_surround_impl(syntax, slice, range, textobject, Some(ch), count)
textobject_pair_surround_impl(
syntax,
slice,
range,
textobject,
FindVariant::Char((ch, find_type, count)),
)
}

pub fn textobject_pair_surround_closest(
Expand All @@ -216,20 +224,34 @@ pub fn textobject_pair_surround_closest(
textobject: TextObject,
count: usize,
) -> Range {
textobject_pair_surround_impl(syntax, slice, range, textobject, None, count)
textobject_pair_surround_impl(
syntax,
slice,
range,
textobject,
FindVariant::Closest(count),
)
}

enum FindVariant {
Char((char, FindType, usize)),
Closest(usize),
}

fn textobject_pair_surround_impl(
syntax: Option<&Syntax>,
slice: RopeSlice,
range: Range,
textobject: TextObject,
ch: Option<char>,
count: usize,
find_variant: FindVariant,
) -> Range {
let pair_pos = match ch {
Some(ch) => surround::find_nth_pairs_pos(slice, ch, range, count),
None => surround::find_nth_closest_pairs_pos(syntax, slice, range, count),
let pair_pos = match find_variant {
FindVariant::Char((ch, find_type, count)) => {
surround::find_nth_pairs_pos(slice, ch, range, find_type, count)
}
FindVariant::Closest(count) => {
surround::find_nth_closest_pairs_pos(syntax, slice, range, count)
}
};
pair_pos
.map(|(anchor, head)| match textobject {
Expand Down Expand Up @@ -576,8 +598,15 @@ mod test {
let slice = doc.slice(..);
for &case in scenario {
let (pos, objtype, expected_range, ch, count) = case;
let result =
textobject_pair_surround(None, slice, Range::point(pos), objtype, ch, count);
let result = textobject_pair_surround(
None,
slice,
Range::point(pos),
objtype,
ch,
FindType::Surround,
count,
);
assert_eq!(
result,
expected_range.into(),
Expand Down
Loading