Skip to content

Expose text on QueryMatches and QueryCaptures#54

Open
lollipopman wants to merge 1 commit into
tree-sitter:masterfrom
lollipopman:expose-query-match-text
Open

Expose text on QueryMatches and QueryCaptures#54
lollipopman wants to merge 1 commit into
tree-sitter:masterfrom
lollipopman:expose-query-match-text

Conversation

@lollipopman

Copy link
Copy Markdown
Contributor

Prior to this change if you edited the tree while interating over matches or captures, subsequent matches would fail, since the source text used by Next() was not updated to match the edit.

After this change the source text is exported, so it can be updated after editing and before Next() is called.

Prior to this change if you edited the tree while interating
over matches or captures, subsequent matches would fail, since
the source text used by Next() was not updated to match the edit.

After this change the source text is exported, so it can be updated
after editing and before Next() is called.
@lollipopman

lollipopman commented Apr 24, 2026

Copy link
Copy Markdown
Contributor Author

Example usage:

package main

import (
	"fmt"
	"slices"
	"strconv"

	sitter "github.com/tree-sitter/go-tree-sitter"
	sitterGo "github.com/tree-sitter/tree-sitter-go/bindings/go"
)

func editNode(
	tree *sitter.Tree,
	modRange sitter.Range,
	modStartPoint sitter.Point,
	modEndPoint sitter.Point,
	modSrc []byte,
	newContent string,
) []byte {
	modSrc = slices.Concat(
		modSrc[0:modRange.StartByte],
		[]byte(newContent),
		modSrc[modRange.EndByte:],
	)
	edit := &sitter.InputEdit{
		StartByte:      modRange.StartByte,
		OldEndByte:     modRange.EndByte,
		NewEndByte:     modRange.StartByte + uint(len([]byte(newContent))),
		StartPosition:  modRange.StartPoint,
		OldEndPosition: modRange.EndPoint,
		NewEndPosition: sitter.Point{
			Row:    modEndPoint.Row,
			Column: modStartPoint.Column + uint(len(newContent)),
		},
	}
	tree.Edit(edit)
	return modSrc
}

func main() {
	src := []byte(`package main

import (
	"fmt"
)

func main() {
	fmt.Println(1)
	fmt.Println(2)
	fmt.Println(3)
	fmt.Println(4)
	fmt.Println(5)
}
`)
	parser := sitter.NewParser()
	defer parser.Close()
	lang := sitter.NewLanguage(sitterGo.Language())
	parser.SetLanguage(lang)
	tree := parser.Parse(src, nil)
	defer tree.Close()
	querySrc := `
          (call_expression 
            function: (selector_expression 
              operand: (identifier)
              field: (field_identifier) @field
			  (#eq? @field "Println")

            )
            arguments: (argument_list
              (int_literal) @int
		    )
		  )
	`
	query, queryErr := sitter.NewQuery(lang, querySrc)
	if queryErr != nil {
		panic(fmt.Errorf("Unable to build query: %w", queryErr))
	}
	defer query.Close()
	qc := sitter.NewQueryCursor()
	defer qc.Close()
	var (
		modStartPoint sitter.Point
		modEndPoint   sitter.Point
		modRange      sitter.Range
	)
	matches := qc.Matches(query, tree.RootNode(), src)
	for match := matches.Next(); match != nil; match = matches.Next() {
		for _, capture := range match.Captures {
			captureName := query.CaptureNames()[capture.Index]
			if captureName == "int" {
				modRange = capture.Node.Range()
				modStartPoint = capture.Node.StartPosition()
				modEndPoint = capture.Node.EndPosition()
			} else {
				continue
			}
			parsedInt, err := strconv.Atoi(capture.Node.Utf8Text(src))
			if err != nil {
				panic(err)
			}
			newInt := parsedInt + 100
			src = editNode(
				tree,
				modRange,
				modStartPoint,
				modEndPoint,
				src,
				strconv.Itoa(newInt),
			)
			// Update query text based on edit
			matches.Text = src
		}
	}
	fmt.Println(string(src))
}

@lollipopman

Copy link
Copy Markdown
Contributor Author

Output with updating matches.Text:

package main

import (
	"fmt"
)

func main() {
	fmt.Println(101)
	fmt.Println(102)
	fmt.Println(103)
	fmt.Println(104)
	fmt.Println(105)
}

and without:

package main

import (
	"fmt"
)

func main() {
	fmt.Println(101)
	fmt.Println(2)
	fmt.Println(3)
	fmt.Println(4)
	fmt.Println(5)
}

@lollipopman

Copy link
Copy Markdown
Contributor Author

@amaanq would love to know if this approach is reasonable, or if there is another way you would prefer?

@lollipopman

Copy link
Copy Markdown
Contributor Author

@amaanq any chance you could take a look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant