registry.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. package highlight
  2. /*
  3. #cgo LDFLAGS: -ldl
  4. #include <dlfcn.h>
  5. #include <stdlib.h>
  6. // Helper to resolve the C function pointer for tree_sitter_<lang>()
  7. void* load_symbol(void* handle, const char* symbol) {
  8. return dlsym(handle, symbol);
  9. }
  10. */
  11. import "C"
  12. import (
  13. "fmt"
  14. "sync"
  15. "unsafe"
  16. "strings"
  17. "unicode"
  18. "path/filepath"
  19. sitter "github.com/smacker/go-tree-sitter"
  20. )
  21. type LanguageSpec struct {
  22. Name string
  23. Language *sitter.Language
  24. Query *sitter.Query
  25. }
  26. type Registry struct {
  27. mu sync.RWMutex
  28. languages map[string]*LanguageSpec
  29. }
  30. var GlobalRegistry = &Registry{
  31. languages: make(map[string]*LanguageSpec),
  32. }
  33. func CleanScmQuery(scm string) string {
  34. cleaned := strings.Map(func(r rune) rune {
  35. if r == '\uFEFF' { // Remove UTF-8 Byte Order Mark (BOM)
  36. return -1
  37. }
  38. if unicode.IsSpace(r) && r != '\n' && r != '\r' && r != '\t' {
  39. return ' '
  40. }
  41. return r
  42. }, scm)
  43. return strings.TrimSpace(cleaned)
  44. }
  45. func RegisterTreeSitterLang(langName, parserPath, queryScm string) error {
  46. absPath, err := filepath.Abs(parserPath)
  47. if err == nil {
  48. parserPath = absPath
  49. }
  50. cPath := C.CString(parserPath)
  51. defer C.free(unsafe.Pointer(cPath))
  52. handle := C.dlopen(cPath, C.RTLD_NOW)
  53. if handle == nil {
  54. errStr := C.GoString(C.dlerror())
  55. return fmt.Errorf("failed to load parser library at %s: %s", parserPath, errStr)
  56. }
  57. symbolName := C.CString(fmt.Sprintf("tree_sitter_%s", langName))
  58. defer C.free(unsafe.Pointer(symbolName))
  59. sym := C.load_symbol(handle, symbolName)
  60. if sym == nil {
  61. return fmt.Errorf("symbol 'tree_sitter_%s' not found in %s", langName, parserPath)
  62. }
  63. ptr := unsafe.Pointer(sym)
  64. sitterLang := sitter.NewLanguage(ptr)
  65. if sitterLang == nil {
  66. return fmt.Errorf("invalid TSLanguage pointer returned for %s", langName)
  67. }
  68. cleanScm := CleanScmQuery(queryScm)
  69. query, err := sitter.NewQuery([]byte(cleanScm), sitterLang)
  70. if err != nil {
  71. if qErr, ok := err.(*sitter.QueryError); ok {
  72. return fmt.Errorf("query compile error at byte offset %d (type: %v)", qErr.Offset, qErr.Type)
  73. }
  74. return fmt.Errorf("failed to compile highlight query for %s: %w", langName, err)
  75. }
  76. GlobalRegistry.mu.Lock()
  77. GlobalRegistry.languages[langName] = &LanguageSpec{
  78. Name: langName,
  79. Language: sitterLang,
  80. Query: query,
  81. }
  82. GlobalRegistry.mu.Unlock()
  83. return nil
  84. }
  85. func (r *Registry) GetLanguage(langName string) (*LanguageSpec, bool) {
  86. r.mu.RLock()
  87. defer r.mu.RUnlock()
  88. spec, ok := r.languages[langName]
  89. return spec, ok
  90. }