@@ -37,14 +37,14 @@ public class QueryCursor implements AutoCloseable{
3737 private final Arena arena ;
3838
3939 private final Query query ;
40- private final Node cursorRootNode ;
40+ private final Tree tree ;
4141
4242
4343 QueryCursor (Query query , Node cursorRootNode , @ Nullable QueryCursorOptions options ){
4444 arena = Arena .ofConfined ();
4545 cursor = ts_query_cursor_new ().reinterpret (arena , TreeSitter ::ts_query_cursor_delete );
4646 this .query = query ;
47- this .cursorRootNode = cursorRootNode ;
47+ this .tree = cursorRootNode . getTree () ;
4848 if (options != null ){
4949 applyOptions (options );
5050 }
@@ -79,7 +79,6 @@ private void applyOptions(QueryCursorOptions options){
7979 if (options .getTimeoutMicros () >= 0 ) {
8080 ts_query_cursor_set_timeout_micros (cursor , options .getTimeoutMicros ());
8181 }
82-
8382 }
8483
8584
@@ -117,30 +116,61 @@ public void removeMatch(@Unsigned int matchId){
117116 ts_query_cursor_remove_match (cursor , matchId );
118117 }
119118
119+ /**
120+ * Stream the matches produced by the query. The stream can not be consumed after the cursor is closed. The native
121+ * nodes backing the matches are bound to the lifetime of the cursor.
122+ * @return a stream of matches
123+ */
120124 public Stream <QueryMatch > stream () {
121125 return stream (null );
122126 }
123127
128+ /**
129+ * Like {@link #stream()} but allows for custom predicates to be applied to the matches.
130+ * @param predicate a function to handle custom predicates.
131+ * @return a stream of matches
132+ */
124133 public Stream <QueryMatch > stream (@ Nullable BiPredicate <QueryPredicate , QueryMatch > predicate ) {
125134 return stream (arena , predicate );
126135 }
127136
137+ /**
138+ * Like {@link #stream(BiPredicate)} but allows for a custom allocator to be used for allocating the native nodes.
139+ * @param allocator allocator to use for allocating the native nodes backing the matches
140+ * @param predicate a function to handle custom predicates.
141+ * @return a stream of matches
142+ */
128143 public Stream <QueryMatch > stream (SegmentAllocator allocator , @ Nullable BiPredicate <QueryPredicate , QueryMatch > predicate ) {
129144 return StreamSupport .stream (new MatchesIterator (this , allocator , predicate ), false );
130145 }
131146
132147
148+ /**
149+ * Get the next match produced by the query. The native nodes backing the match are bound to the lifetime of the cursor.
150+ * @return the next match, if available
151+ */
133152 public Optional <QueryMatch > nextMatch () {
134153 return nextMatch (null );
135154 }
136155
156+ /**
157+ * Like {@link #nextMatch()} but allows for custom predicates to be applied to the matches.
158+ * @param predicate a function to handle custom predicates.
159+ * @return the next match, if available
160+ */
137161 public Optional <QueryMatch > nextMatch (@ Nullable BiPredicate <QueryPredicate , QueryMatch > predicate ) {
138162 return nextMatch (arena , predicate );
139163 }
140164
165+ /**
166+ * Like {@link #nextMatch(BiPredicate)} but allows for a custom allocator to be used for allocating the native nodes.
167+ * @param allocator allocator to use for allocating the native nodes backing the matches
168+ * @param predicate a function to handle custom predicates.
169+ * @return the next match, if available
170+ */
141171 public Optional <QueryMatch > nextMatch (SegmentAllocator allocator , @ Nullable BiPredicate <QueryPredicate , QueryMatch > predicate ) {
142172
143- var hasNoText = cursorRootNode . getTree () .getText () == null ;
173+ var hasNoText = tree .getText () == null ;
144174 MemorySegment match = arena .allocate (TSQueryMatch .layout ());
145175 while (ts_query_cursor_next_match (cursor , match )) {
146176 var count = Short .toUnsignedInt (TSQueryMatch .capture_count (match ));
@@ -150,11 +180,13 @@ public Optional<QueryMatch> nextMatch(SegmentAllocator allocator, @Nullable BiPr
150180 var capture = TSQueryCapture .asSlice (matchCaptures , i );
151181 var name = query .getCaptureNames ().get (TSQueryCapture .index (capture ));
152182 var node = TSNode .allocate (allocator ).copyFrom (TSQueryCapture .node (capture ));
153- captureList .add (new QueryCapture (name , new Node (node , this . cursorRootNode . getTree () )));
183+ captureList .add (new QueryCapture (name , new Node (node , tree )));
154184 }
155185 var patternIndex = TSQueryMatch .pattern_index (match );
156186 var matchId = TSQueryMatch .id (match );
157- var result = new QueryMatch (matchId , patternIndex , captureList );
187+ // we copy all the data. So we can directly remove the match from the cursor to free memory.
188+ ts_query_cursor_remove_match (cursor , matchId );
189+ var result = new QueryMatch (patternIndex , captureList );
158190 if (hasNoText || matches (predicate , result )) {
159191 return Optional .of (result );
160192 }
@@ -190,6 +222,11 @@ public MatchesIterator(QueryCursor cursor, SegmentAllocator allocator, @Nullable
190222 }
191223 @ Override
192224 public boolean tryAdvance (Consumer <? super QueryMatch > action ) {
225+
226+ if (!cursor .arena .scope ().isAlive ()){
227+ throw new IllegalStateException ("Cursor is closed. Cannot produce more matches." );
228+ }
229+
193230 Optional <QueryMatch > queryMatch = cursor .nextMatch (allocator , predicate );
194231 queryMatch .ifPresent (action );
195232 return queryMatch .isPresent ();
0 commit comments