235 lines
6.7 KiB
Go
235 lines
6.7 KiB
Go
//go:build treesitter && cgo
|
|
|
|
package builtin
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
sitter "github.com/tree-sitter/go-tree-sitter"
|
|
tree_sitter_javascript "github.com/tree-sitter/tree-sitter-javascript/bindings/go"
|
|
tree_sitter_python "github.com/tree-sitter/tree-sitter-python/bindings/go"
|
|
tree_sitter_rust "github.com/tree-sitter/tree-sitter-rust/bindings/go"
|
|
tree_sitter_typescript "github.com/tree-sitter/tree-sitter-typescript/bindings/go"
|
|
)
|
|
|
|
type codeIndexTreeSitterSpec struct {
|
|
language func() unsafe.Pointer
|
|
query string
|
|
}
|
|
|
|
func (c codeIndex) parseTreeSitter(path string) ([]codeSymbol, bool, error) {
|
|
spec, ok := codeIndexTreeSitterSpecForExt(filepath.Ext(path))
|
|
if !ok {
|
|
return nil, false, nil
|
|
}
|
|
source, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, true, err
|
|
}
|
|
language := sitter.NewLanguage(spec.language())
|
|
parser := sitter.NewParser()
|
|
defer parser.Close()
|
|
if err := parser.SetLanguage(language); err != nil {
|
|
return nil, true, err
|
|
}
|
|
tree := parser.Parse(source, nil)
|
|
if tree == nil {
|
|
return nil, true, fmt.Errorf("tree-sitter parse returned nil")
|
|
}
|
|
defer tree.Close()
|
|
query, qerr := sitter.NewQuery(language, spec.query)
|
|
if qerr != nil {
|
|
return nil, true, qerr
|
|
}
|
|
defer query.Close()
|
|
|
|
cursor := sitter.NewQueryCursor()
|
|
defer cursor.Close()
|
|
cursor.SetTimeoutMicros(50_000)
|
|
|
|
captureNames := query.CaptureNames()
|
|
matches := cursor.Matches(query, tree.RootNode(), source)
|
|
var out []codeSymbol
|
|
for match := matches.Next(); match != nil; match = matches.Next() {
|
|
var nameNode *sitter.Node
|
|
var symbolNode *sitter.Node
|
|
var captureKind string
|
|
for _, capture := range match.Captures {
|
|
captureName := captureNames[capture.Index]
|
|
kind, role, ok := splitCodeIndexTreeSitterCapture(captureName)
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch role {
|
|
case "name":
|
|
captureKind = kind
|
|
node := capture.Node
|
|
nameNode = &node
|
|
case "symbol":
|
|
if captureKind == "" {
|
|
captureKind = kind
|
|
}
|
|
node := capture.Node
|
|
symbolNode = &node
|
|
}
|
|
}
|
|
if nameNode == nil || captureKind == "" {
|
|
continue
|
|
}
|
|
if symbolNode == nil {
|
|
symbolNode = nameNode
|
|
}
|
|
name := strings.TrimSpace(nameNode.Utf8Text(source))
|
|
if name == "" {
|
|
continue
|
|
}
|
|
kind := normalizeCodeIndexKind(captureKind)
|
|
parent := treeSitterParentName(kind, symbolNode, source)
|
|
out = append(out, codeSymbol{
|
|
Name: name,
|
|
Kind: kind,
|
|
File: c.displayPath(path),
|
|
Line: int(symbolNode.StartPosition().Row) + 1,
|
|
Parent: parent,
|
|
Signature: treeSitterLine(source, int(symbolNode.StartByte())),
|
|
})
|
|
}
|
|
if cursor.DidExceedMatchLimit() {
|
|
return out, true, fmt.Errorf("tree-sitter query exceeded match limit")
|
|
}
|
|
return out, true, nil
|
|
}
|
|
|
|
func codeIndexTreeSitterEnabled() bool {
|
|
return true
|
|
}
|
|
|
|
func codeIndexTreeSitterSpecForExt(ext string) (codeIndexTreeSitterSpec, bool) {
|
|
switch ext {
|
|
case ".js", ".jsx":
|
|
return codeIndexTreeSitterSpec{language: tree_sitter_javascript.Language, query: treeSitterJavaScriptQuery}, true
|
|
case ".ts":
|
|
return codeIndexTreeSitterSpec{language: tree_sitter_typescript.LanguageTypescript, query: treeSitterTypeScriptQuery}, true
|
|
case ".tsx":
|
|
return codeIndexTreeSitterSpec{language: tree_sitter_typescript.LanguageTSX, query: treeSitterTypeScriptQuery}, true
|
|
case ".py":
|
|
return codeIndexTreeSitterSpec{language: tree_sitter_python.Language, query: treeSitterPythonQuery}, true
|
|
case ".rs":
|
|
return codeIndexTreeSitterSpec{language: tree_sitter_rust.Language, query: treeSitterRustQuery}, true
|
|
default:
|
|
return codeIndexTreeSitterSpec{}, false
|
|
}
|
|
}
|
|
|
|
func splitCodeIndexTreeSitterCapture(name string) (kind, role string, ok bool) {
|
|
before, after, found := strings.Cut(name, ".")
|
|
if !found || before == "" || after == "" {
|
|
return "", "", false
|
|
}
|
|
if after != "name" && after != "symbol" {
|
|
return "", "", false
|
|
}
|
|
return before, after, true
|
|
}
|
|
|
|
func treeSitterParentName(kind string, node *sitter.Node, source []byte) string {
|
|
if kind != "method" {
|
|
return ""
|
|
}
|
|
for parent := node.Parent(); parent != nil; parent = parent.Parent() {
|
|
switch parent.Kind() {
|
|
case "abstract_class_declaration", "class_declaration", "class":
|
|
if name := parent.ChildByFieldName("name"); name != nil {
|
|
return strings.TrimSpace(name.Utf8Text(source))
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func treeSitterLine(source []byte, start int) string {
|
|
if start < 0 {
|
|
start = 0
|
|
}
|
|
if start > len(source) {
|
|
start = len(source)
|
|
}
|
|
lineStart := start
|
|
for lineStart > 0 && source[lineStart-1] != '\n' && source[lineStart-1] != '\r' {
|
|
lineStart--
|
|
}
|
|
lineEnd := start
|
|
for lineEnd < len(source) && source[lineEnd] != '\n' && source[lineEnd] != '\r' {
|
|
lineEnd++
|
|
}
|
|
return strings.TrimSpace(string(source[lineStart:lineEnd]))
|
|
}
|
|
|
|
const treeSitterJavaScriptQuery = `
|
|
(function_declaration
|
|
name: (identifier) @func.name) @func.symbol
|
|
(generator_function_declaration
|
|
name: (identifier) @func.name) @func.symbol
|
|
(class_declaration
|
|
name: (identifier) @class.name) @class.symbol
|
|
(method_definition
|
|
name: [(property_identifier) (private_property_identifier)] @method.name) @method.symbol
|
|
(lexical_declaration
|
|
(variable_declarator
|
|
name: (identifier) @func.name
|
|
value: [(arrow_function) (function_expression)])) @func.symbol
|
|
(variable_declaration
|
|
(variable_declarator
|
|
name: (identifier) @func.name
|
|
value: [(arrow_function) (function_expression)])) @func.symbol
|
|
`
|
|
|
|
const treeSitterTypeScriptQuery = `
|
|
(function_declaration
|
|
name: (identifier) @func.name) @func.symbol
|
|
(generator_function_declaration
|
|
name: (identifier) @func.name) @func.symbol
|
|
(class_declaration
|
|
name: (type_identifier) @class.name) @class.symbol
|
|
(abstract_class_declaration
|
|
name: (type_identifier) @class.name) @class.symbol
|
|
(method_definition
|
|
name: [(property_identifier) (private_property_identifier)] @method.name) @method.symbol
|
|
(interface_declaration
|
|
name: (type_identifier) @interface.name) @interface.symbol
|
|
(type_alias_declaration
|
|
name: (type_identifier) @type.name) @type.symbol
|
|
(enum_declaration
|
|
name: (identifier) @enum.name) @enum.symbol
|
|
(lexical_declaration
|
|
(variable_declarator
|
|
name: (identifier) @func.name
|
|
value: [(arrow_function) (function_expression)])) @func.symbol
|
|
(variable_declaration
|
|
(variable_declarator
|
|
name: (identifier) @func.name
|
|
value: [(arrow_function) (function_expression)])) @func.symbol
|
|
`
|
|
|
|
const treeSitterPythonQuery = `
|
|
(function_definition
|
|
name: (identifier) @func.name) @func.symbol
|
|
(class_definition
|
|
name: (identifier) @class.name) @class.symbol
|
|
`
|
|
|
|
const treeSitterRustQuery = `
|
|
(function_item
|
|
name: (identifier) @fn.name) @fn.symbol
|
|
(struct_item
|
|
name: (type_identifier) @struct.name) @struct.symbol
|
|
(enum_item
|
|
name: (type_identifier) @enum.name) @enum.symbol
|
|
(trait_item
|
|
name: (type_identifier) @trait.name) @trait.symbol
|
|
`
|