Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/textobject.rs')
| -rw-r--r-- | helix-core/src/textobject.rs | 240 |
1 files changed, 15 insertions, 225 deletions
diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index 008228f4..5a55a6f1 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -1,14 +1,14 @@ use std::fmt::Display; use ropey::RopeSlice; +use tree_sitter::{Node, QueryCursor}; 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::graphemes::next_grapheme_boundary; use crate::movement::Direction; -use crate::syntax; +use crate::surround; +use crate::syntax::LanguageConfiguration; use crate::Range; -use crate::{surround, Syntax}; fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction, long: bool) -> usize { use CharCategory::{Eol, Whitespace}; @@ -111,141 +111,17 @@ pub fn textobject_word( } } -pub fn textobject_paragraph( - slice: RopeSlice, - range: Range, - textobject: TextObject, - count: usize, -) -> Range { - let mut line = range.cursor_line(slice); - let prev_line_empty = rope_is_line_ending(slice.line(line.saturating_sub(1))); - let curr_line_empty = rope_is_line_ending(slice.line(line)); - let next_line_empty = rope_is_line_ending(slice.line(line.saturating_sub(1))); - let last_char = - prev_grapheme_boundary(slice, slice.line_to_char(line + 1)) == range.cursor(slice); - let prev_empty_to_line = prev_line_empty && !curr_line_empty; - let curr_empty_to_line = curr_line_empty && !next_line_empty; - - // skip character before paragraph boundary - let mut line_back = line; // line but backwards - if prev_empty_to_line || curr_empty_to_line { - line_back += 1; - } - // do not include current paragraph on paragraph end (include next) - if !(curr_empty_to_line && last_char) { - let mut lines = slice.lines_at(line_back); - lines.reverse(); - let mut lines = lines.map(rope_is_line_ending).peekable(); - while lines.next_if(|&e| e).is_some() { - line_back -= 1; - } - while lines.next_if(|&e| !e).is_some() { - line_back -= 1; - } - } - - // skip character after paragraph boundary - if curr_empty_to_line && last_char { - line += 1; - } - let mut lines = slice.lines_at(line).map(rope_is_line_ending).peekable(); - let mut count_done = 0; // count how many non-whitespace paragraphs done - for _ in 0..count { - let mut done = false; - while lines.next_if(|&e| !e).is_some() { - line += 1; - done = true; - } - while lines.next_if(|&e| e).is_some() { - line += 1; - } - count_done += done as usize; - } - - // search one paragraph backwards for last paragraph - // makes `map` at the end of the paragraph with trailing newlines useful - let last_paragraph = count_done != count && lines.peek().is_none(); - if last_paragraph { - let mut lines = slice.lines_at(line_back); - lines.reverse(); - let mut lines = lines.map(rope_is_line_ending).peekable(); - while lines.next_if(|&e| e).is_some() { - line_back -= 1; - } - while lines.next_if(|&e| !e).is_some() { - line_back -= 1; - } - } - - // handle last whitespaces part separately depending on textobject - match textobject { - TextObject::Around => {} - TextObject::Inside => { - // remove last whitespace paragraph - let mut lines = slice.lines_at(line); - lines.reverse(); - let mut lines = lines.map(rope_is_line_ending).peekable(); - while lines.next_if(|&e| e).is_some() { - line -= 1; - } - } - TextObject::Movement => unreachable!(), - } - - let anchor = slice.line_to_char(line_back); - let head = slice.line_to_char(line); - Range::new(anchor, head) -} - -pub fn textobject_pair_surround( - syntax: Option<&Syntax>, +pub fn textobject_surround( slice: RopeSlice, range: Range, textobject: TextObject, ch: char, count: usize, ) -> Range { - textobject_pair_surround_impl(syntax, slice, range, textobject, Some(ch), count) -} - -pub fn textobject_pair_surround_closest( - syntax: Option<&Syntax>, - slice: RopeSlice, - range: Range, - textobject: TextObject, - count: usize, -) -> Range { - textobject_pair_surround_impl(syntax, slice, range, textobject, None, count) -} - -fn textobject_pair_surround_impl( - syntax: Option<&Syntax>, - 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), - None => surround::find_nth_closest_pairs_pos(syntax, slice, range, count), - }; - pair_pos + surround::find_nth_pairs_pos(slice, ch, range, count) .map(|(anchor, head)| match textobject { - TextObject::Inside => { - if anchor < head { - Range::new(next_grapheme_boundary(slice, anchor), head) - } else { - Range::new(anchor, next_grapheme_boundary(slice, head)) - } - } - TextObject::Around => { - if anchor < head { - Range::new(anchor, next_grapheme_boundary(slice, head)) - } else { - 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::Movement => unreachable!(), }) .unwrap_or(range) @@ -259,18 +135,18 @@ pub fn textobject_treesitter( range: Range, textobject: TextObject, object_name: &str, - syntax: &Syntax, - loader: &syntax::Loader, + slice_tree: Node, + lang_config: &LanguageConfiguration, _count: usize, ) -> Range { - let root = syntax.tree().root_node(); - let textobject_query = loader.textobject_query(syntax.root_language()); let get_range = move || -> Option<Range> { let byte_pos = slice.char_to_byte(range.cursor(slice)); let capture_name = format!("{}.{}", object_name, textobject); // eg. function.inner - let node = textobject_query? - .capture_nodes(&capture_name, &root, slice)? + let mut cursor = QueryCursor::new(); + let node = lang_config + .textobject_query()? + .capture_nodes(&capture_name, slice_tree, slice, &mut cursor)? .filter(|node| node.byte_range().contains(&byte_pos)) .min_by_key(|node| node.byte_range().len())?; @@ -413,91 +289,6 @@ mod test { } #[test] - fn test_textobject_paragraph_inside_single() { - let tests = [ - ("#[|]#", "#[|]#"), - ("firs#[t|]#\n\nparagraph\n\n", "#[first\n|]#\nparagraph\n\n"), - ( - "second\n\npa#[r|]#agraph\n\n", - "second\n\n#[paragraph\n|]#\n", - ), - ("#[f|]#irst char\n\n", "#[first char\n|]#\n"), - ("last char\n#[\n|]#", "#[last char\n|]#\n"), - ( - "empty to line\n#[\n|]#paragraph boundary\n\n", - "empty to line\n\n#[paragraph boundary\n|]#\n", - ), - ( - "line to empty\n\n#[p|]#aragraph boundary\n\n", - "line to empty\n\n#[paragraph boundary\n|]#\n", - ), - ]; - - for (before, expected) in tests { - let (s, selection) = crate::test::print(before); - let text = Rope::from(s.as_str()); - let selection = selection - .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Inside, 1)); - let actual = crate::test::plain(s.as_ref(), &selection); - assert_eq!(actual, expected, "\nbefore: `{:?}`", before); - } - } - - #[test] - fn test_textobject_paragraph_inside_double() { - let tests = [ - ( - "last two\n\n#[p|]#aragraph\n\nwithout whitespaces\n\n", - "last two\n\n#[paragraph\n\nwithout whitespaces\n|]#\n", - ), - ( - "last two\n#[\n|]#paragraph\n\nwithout whitespaces\n\n", - "last two\n\n#[paragraph\n\nwithout whitespaces\n|]#\n", - ), - ]; - - for (before, expected) in tests { - let (s, selection) = crate::test::print(before); - let text = Rope::from(s.as_str()); - let selection = selection - .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Inside, 2)); - let actual = crate::test::plain(s.as_ref(), &selection); - assert_eq!(actual, expected, "\nbefore: `{:?}`", before); - } - } - - #[test] - fn test_textobject_paragraph_around_single() { - let tests = [ - ("#[|]#", "#[|]#"), - ("firs#[t|]#\n\nparagraph\n\n", "#[first\n\n|]#paragraph\n\n"), - ( - "second\n\npa#[r|]#agraph\n\n", - "second\n\n#[paragraph\n\n|]#", - ), - ("#[f|]#irst char\n\n", "#[first char\n\n|]#"), - ("last char\n#[\n|]#", "#[last char\n\n|]#"), - ( - "empty to line\n#[\n|]#paragraph boundary\n\n", - "empty to line\n\n#[paragraph boundary\n\n|]#", - ), - ( - "line to empty\n\n#[p|]#aragraph boundary\n\n", - "line to empty\n\n#[paragraph boundary\n\n|]#", - ), - ]; - - for (before, expected) in tests { - let (s, selection) = crate::test::print(before); - let text = Rope::from(s.as_str()); - let selection = selection - .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Around, 1)); - let actual = crate::test::plain(s.as_ref(), &selection); - assert_eq!(actual, expected, "\nbefore: `{:?}`", before); - } - } - - #[test] fn test_textobject_surround() { // (text, [(cursor position, textobject, final range, surround char, count), ...]) let tests = &[ @@ -575,8 +366,7 @@ 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_surround(slice, Range::point(pos), objtype, ch, count); assert_eq!( result, expected_range.into(), |