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.rs240
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(),