diff --git a/src/main/java/io/github/treesitter/jtreesitter/Query.java b/src/main/java/io/github/treesitter/jtreesitter/Query.java index 690b751..0489fce 100644 --- a/src/main/java/io/github/treesitter/jtreesitter/Query.java +++ b/src/main/java/io/github/treesitter/jtreesitter/Query.java @@ -5,6 +5,7 @@ import io.github.treesitter.jtreesitter.internal.*; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; import java.util.*; import java.util.function.BiPredicate; import java.util.function.Consumer; @@ -480,10 +481,12 @@ public Map> getPatternAssertions(@Unsigned int index, b /** * Iterate over all the matches in the order that they were found. * + * @implNote The lifetime of the matches is bound to that of the query. + * * @param node The node that the query will run on. */ public Stream findMatches(Node node) { - return findMatches(node, null); + return findMatches(node, arena, null); } /** @@ -500,14 +503,26 @@ public Stream findMatches(Node node) { * }); * } * + * @implNote The lifetime of the matches is bound to that of the query. + * * @param node The node that the query will run on. * @param predicate A function that handles custom predicates. */ public Stream findMatches(Node node, @Nullable BiPredicate predicate) { + return findMatches(node, arena, predicate); + } + + /** + * Iterate over all the matches in the order that they were found, using the given allocator. + * + * @see #findMatches(Node, BiPredicate) + */ + public Stream findMatches( + Node node, SegmentAllocator allocator, @Nullable BiPredicate predicate) { try (var alloc = Arena.ofConfined()) { ts_query_cursor_exec(cursor, query, node.copy(alloc)); } - return StreamSupport.stream(new MatchesIterator(node.getTree(), predicate), false); + return StreamSupport.stream(new MatchesIterator(node.getTree(), allocator, predicate), false); } @Override @@ -537,11 +552,14 @@ private void checkIndex(@Unsigned int index) throws IndexOutOfBoundsException { private final class MatchesIterator extends Spliterators.AbstractSpliterator { private final @Nullable BiPredicate predicate; private final Tree tree; + private final SegmentAllocator allocator; - public MatchesIterator(Tree tree, @Nullable BiPredicate predicate) { + public MatchesIterator( + Tree tree, SegmentAllocator allocator, @Nullable BiPredicate predicate) { super(Long.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.NONNULL); this.predicate = predicate; this.tree = tree; + this.allocator = allocator; } @Override @@ -555,7 +573,7 @@ public boolean tryAdvance(Consumer action) { for (int i = 0; i < count; ++i) { var capture = TSQueryCapture.asSlice(matchCaptures, i); var name = captureNames.get(TSQueryCapture.index(capture)); - var node = TSNode.allocate(arena).copyFrom(TSQueryCapture.node(capture)); + var node = TSNode.allocate(allocator).copyFrom(TSQueryCapture.node(capture)); captureList.add(new QueryCapture(name, new Node(node, tree))); } var patternIndex = TSQueryMatch.pattern_index(match); diff --git a/src/main/java/io/github/treesitter/jtreesitter/TreeCursor.java b/src/main/java/io/github/treesitter/jtreesitter/TreeCursor.java index 18be2b3..3ba8013 100644 --- a/src/main/java/io/github/treesitter/jtreesitter/TreeCursor.java +++ b/src/main/java/io/github/treesitter/jtreesitter/TreeCursor.java @@ -4,6 +4,7 @@ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; import java.util.OptionalInt; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -36,7 +37,11 @@ private TreeCursor(TreeCursor cursor) { node = cursor.node; } - /** Get the current node of the cursor. */ + /** + * Get the current node of the cursor. + * + * @implNote The node will become invalid once the cursor is closed. + */ public Node getCurrentNode() { if (this.node == null) { var node = ts_tree_cursor_current_node(arena, self); @@ -45,6 +50,16 @@ public Node getCurrentNode() { return this.node; } + /** + * Get the current node of the cursor using the given allocator. + * + * @since 0.25.0 + */ + public Node getCurrentNode(SegmentAllocator allocator) { + var node = ts_tree_cursor_current_node(allocator, self); + return new Node(node, tree); + } + /** * Get the depth of the cursor's current node relative to * the original node that the cursor was constructed with. diff --git a/src/test/java/io/github/treesitter/jtreesitter/TreeCursorTest.java b/src/test/java/io/github/treesitter/jtreesitter/TreeCursorTest.java index 899f8d1..ade2d10 100644 --- a/src/test/java/io/github/treesitter/jtreesitter/TreeCursorTest.java +++ b/src/test/java/io/github/treesitter/jtreesitter/TreeCursorTest.java @@ -3,7 +3,13 @@ import static org.junit.jupiter.api.Assertions.*; import io.github.treesitter.jtreesitter.languages.TreeSitterJava; -import org.junit.jupiter.api.*; +import java.lang.foreign.Arena; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; class TreeCursorTest { private static Tree tree; @@ -37,6 +43,15 @@ void getCurrentNode() { var node = cursor.getCurrentNode(); assertEquals(tree.getRootNode(), node); assertSame(node, cursor.getCurrentNode()); + + try (var arena = Arena.ofConfined()) { + try (var copy = cursor.clone()) { + node = copy.getCurrentNode(arena); + assertEquals(node, tree.getRootNode()); + } + // can still access node after cursor was closed + assertEquals(node, tree.getRootNode()); + } } @Test