Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-syntax/src/text_object.rs')
-rw-r--r--helix-syntax/src/text_object.rs93
1 files changed, 93 insertions, 0 deletions
diff --git a/helix-syntax/src/text_object.rs b/helix-syntax/src/text_object.rs
new file mode 100644
index 00000000..09cb8a10
--- /dev/null
+++ b/helix-syntax/src/text_object.rs
@@ -0,0 +1,93 @@
+// TODO: rework using query iter
+
+use std::iter;
+
+use ropey::RopeSlice;
+
+use crate::tree_sitter::{InactiveQueryCursor, Query, RopeTsInput, SyntaxTreeNode};
+use crate::TREE_SITTER_MATCH_LIMIT;
+
+#[derive(Debug)]
+pub enum CapturedNode<'a> {
+ Single(SyntaxTreeNode<'a>),
+ /// Guaranteed to be not empty
+ Grouped(Vec<SyntaxTreeNode<'a>>),
+}
+
+impl<'a> CapturedNode<'a> {
+ pub fn start_byte(&self) -> usize {
+ match self {
+ Self::Single(n) => n.start_byte(),
+ Self::Grouped(ns) => ns[0].start_byte(),
+ }
+ }
+
+ pub fn end_byte(&self) -> usize {
+ match self {
+ Self::Single(n) => n.end_byte(),
+ Self::Grouped(ns) => ns.last().unwrap().end_byte(),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct TextObjectQuery {
+ pub query: Query,
+}
+
+impl TextObjectQuery {
+ /// Run the query on the given node and return sub nodes which match given
+ /// capture ("function.inside", "class.around", etc).
+ ///
+ /// Captures may contain multiple nodes by using quantifiers (+, *, etc),
+ /// and support for this is partial and could use improvement.
+ ///
+ /// ```query
+ /// (comment)+ @capture
+ ///
+ /// ; OR
+ /// (
+ /// (comment)*
+ /// .
+ /// (function)
+ /// ) @capture
+ /// ```
+ pub fn capture_nodes<'a>(
+ &'a self,
+ capture_name: &str,
+ node: SyntaxTreeNode<'a>,
+ slice: RopeSlice<'a>,
+ cursor: InactiveQueryCursor,
+ ) -> Option<impl Iterator<Item = CapturedNode<'a>>> {
+ self.capture_nodes_any(&[capture_name], node, slice, cursor)
+ }
+
+ /// Find the first capture that exists out of all given `capture_names`
+ /// and return sub nodes that match this capture.
+ pub fn capture_nodes_any<'a>(
+ &'a self,
+ capture_names: &[&str],
+ node: SyntaxTreeNode<'a>,
+ slice: RopeSlice<'a>,
+ mut cursor: InactiveQueryCursor,
+ ) -> Option<impl Iterator<Item = CapturedNode<'a>>> {
+ let capture = capture_names
+ .iter()
+ .find_map(|cap| self.query.get_capture(cap))?;
+
+ cursor.set_match_limit(TREE_SITTER_MATCH_LIMIT);
+ let mut cursor = cursor.execute_query(&self.query, &node, RopeTsInput::new(slice));
+ let capture_node = iter::from_fn(move || {
+ let (mat, _) = cursor.next_matched_node()?;
+ Some(mat.nodes_for_capture(capture).cloned().collect())
+ })
+ .filter_map(move |nodes: Vec<_>| {
+ if nodes.len() > 1 {
+ Some(CapturedNode::Grouped(nodes))
+ } else {
+ nodes.into_iter().map(CapturedNode::Single).next()
+ }
+ });
+ Some(capture_node)
+ }
+}