@@ -244,16 +244,55 @@ pub struct TextObjectQuery {
244244 pub query: Query,
245245}
246246
247+pub enum CapturedNode<'a> {
248+ Single(Node<'a>),
249+ Grouped(Vec<Node<'a>>),
250+}
251+
252+impl<'a> CapturedNode<'a> {
253+ pub fn start_byte(&self) -> usize {
254+ match self {
255+ Self::Single(n) => n.start_byte(),
256+ Self::Grouped(ns) => ns[0].start_byte(),
257+ }
258+ }
259+
260+ pub fn end_byte(&self) -> usize {
261+ match self {
262+ Self::Single(n) => n.end_byte(),
263+ Self::Grouped(ns) => ns.last().unwrap().end_byte(),
264+ }
265+ }
266+
267+ pub fn byte_range(&self) -> std::ops::Range<usize> {
268+ self.start_byte()..self.end_byte()
269+ }
270+}
271+
247272impl TextObjectQuery {
248273 /// Run the query on the given node and return sub nodes which match given
249274 /// capture ("function.inside", "class.around", etc).
275+ ///
276+ /// Captures may contain multiple nodes by using quantifiers (+, *, etc),
277+ /// and support for this is partial and could use improvement.
278+ ///
279+ /// ```query
280+ /// ;; supported:
281+ /// (comment)+ @capture
282+ ///
283+ /// ;; unsupported:
284+ /// (
285+ /// (comment)+
286+ /// (function)
287+ /// ) @capture
288+ /// ```
250289 pub fn capture_nodes<'a>(
251290 &'a self,
252291 capture_name: &str,
253292 node: Node<'a>,
254293 slice: RopeSlice<'a>,
255294 cursor: &'a mut QueryCursor,
256− ) -> Option<impl Iterator<Item = Node<'a>>> {
295+ ) -> Option<impl Iterator<Item = CapturedNode<'a>>> {
257296 self.capture_nodes_any(&[capture_name], node, slice, cursor)
258297 }
259298
@@ -265,17 +304,28 @@ impl TextObjectQuery {
265304 node: Node<'a>,
266305 slice: RopeSlice<'a>,
267306 cursor: &'a mut QueryCursor,
268− ) -> Option<impl Iterator<Item = Node<'a>>> {
307+ ) -> Option<impl Iterator<Item = CapturedNode<'a>>> {
269308 let capture_idx = capture_names
270309 .iter()
271310 .find_map(|cap| self.query.capture_index_for_name(cap))?;
272− let captures = cursor.captures(&self.query, node, RopeProvider(slice));
311+ let captures = cursor.matches(&self.query, node, RopeProvider(slice));
273312
274− captures
275− .filter_map(move |(mat, idx)| {
276− (mat.captures[idx].index == capture_idx).then(|| mat.captures[idx].node)
277− })
278− .into()
313+ let nodes = captures.flat_map(move |mat| {
314+ let captures = mat.captures.iter().filter(move |c| c.index == capture_idx);
315+ let nodes = captures.map(|c| c.node);
316+ let pattern_idx = mat.pattern_index;
317+ let quantifier = self.query.capture_quantifiers(pattern_idx)[capture_idx as usize];
318+
319+ let iter: Box<dyn Iterator<Item = CapturedNode>> = match quantifier {
320+ CaptureQuantifier::OneOrMore | CaptureQuantifier::ZeroOrMore => {
321+ Box::new(std::iter::once(CapturedNode::Grouped(nodes.collect())))
322+ }
323+ _ => Box::new(nodes.map(CapturedNode::Single)),
324+ };
325+
326+ iter
327+ });
328+ Some(nodes)
279329 }
280330}
281331
@@ -1075,8 +1125,8 @@ pub(crate) fn generate_edits(
10751125use std::sync::atomic::{AtomicUsize, Ordering};
10761126use std::{iter, mem, ops, str, usize};
10771127use tree_sitter::{
1078− Language as Grammar, Node, Parser, Point, Query, QueryCaptures, QueryCursor, QueryError,
1079− QueryMatch, Range, TextProvider, Tree,
1128+ CaptureQuantifier, Language as Grammar, Node, Parser, Point, Query, QueryCaptures, QueryCursor,
1129+ QueryError, QueryMatch, Range, TextProvider, Tree,
10801130};
10811131
10821132const CANCELLATION_CHECK_INTERVAL: usize = 100;
@@ -1928,6 +1978,50 @@ mod test {
19281978 use super::*;
19291979 use crate::{Rope, Transaction};
19301980
1981+ #[test]
1982+ fn test_textobject_queries() {
1983+ let query_str = r#"
1984+ (line_comment)+ @quantified_nodes
1985+ ((line_comment)+) @quantified_nodes_grouped
1986+ ((line_comment) (line_comment)) @multiple_nodes_grouped
1987+ "#;
1988+ let source = Rope::from_str(
1989+ r#"
1990+/// a comment on
1991+/// mutiple lines
1992+ "#,
1993+ );
1994+
1995+ let loader = Loader::new(Configuration { language: vec![] });
1996+ let language = get_language(&crate::RUNTIME_DIR, "Rust").unwrap();
1997+
1998+ let query = Query::new(language, query_str).unwrap();
1999+ let textobject = TextObjectQuery { query };
2000+ let mut cursor = QueryCursor::new();
2001+
2002+ let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2003+ let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2004+
2005+ let root = syntax.tree().root_node();
2006+ let mut test = |capture, range| {
2007+ let matches: Vec<_> = textobject
2008+ .capture_nodes(capture, root, source.slice(..), &mut cursor)
2009+ .unwrap()
2010+ .collect();
2011+
2012+ assert_eq!(
2013+ matches[0].byte_range(),
2014+ range,
2015+ "@{capture} expected {range:?}"
2016+ )
2017+ };
2018+
2019+ test("quantified_nodes", 1..35);
2020+ // NOTE: Enable after implementing proper node group capturing
2021+ // test("quantified_nodes_grouped", 1..35);
2022+ // test("multiple_nodes_grouped", 1..35);
2023+ }
2024+
19312025 #[test]
19322026 fn test_parser() {
19332027 let highlight_names: Vec<String> = [