diff options
Diffstat (limited to 'helix-core/src/textobject.rs')
-rw-r--r-- | helix-core/src/textobject.rs | 51 |
1 files changed, 51 insertions, 0 deletions
diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index b965f6df..975ed115 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -1,9 +1,13 @@ +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; use crate::movement::Direction; use crate::surround; +use crate::syntax::LanguageConfiguration; use crate::Range; fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction) -> usize { @@ -51,6 +55,15 @@ pub enum TextObject { Inside, } +impl Display for TextObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Around => "around", + Self::Inside => "inside", + }) + } +} + // count doesn't do anything yet pub fn textobject_word( slice: RopeSlice, @@ -108,6 +121,44 @@ pub fn textobject_surround( .unwrap_or(range) } +/// Transform the given range to select text objects based on tree-sitter. +/// `object_name` is a query capture base name like "function", "class", etc. +/// `slice_tree` is the tree-sitter node corresponding to given text slice. +pub fn textobject_treesitter( + slice: RopeSlice, + range: Range, + textobject: TextObject, + object_name: &str, + slice_tree: Node, + lang_config: &LanguageConfiguration, + _count: usize, +) -> Range { + 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 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())?; + + let len = slice.len_bytes(); + let start_byte = node.start_byte(); + let end_byte = node.end_byte(); + if start_byte >= len || end_byte >= len { + return None; + } + + let start_char = slice.byte_to_char(start_byte); + let end_char = slice.byte_to_char(end_byte); + + Some(Range::new(start_char, end_char)) + }; + get_range().unwrap_or(range) +} + #[cfg(test)] mod test { use super::TextObject::*; |