Last active
October 24, 2025 13:29
-
-
Save Melkor333/3fa4738238fde02187b303f41eb33b36 to your computer and use it in GitHub Desktop.
Example Golang Syntax Highlighter with Tree-Sitter using Terminal escapes for coloring
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "context" | |
| _ "embed" | |
| "fmt" | |
| tree_sitter "github.com/tree-sitter/go-tree-sitter" | |
| tree_sitter_javascript "github.com/tree-sitter/tree-sitter-javascript/bindings/go" | |
| highlight "go.gopad.dev/go-tree-sitter-highlight" | |
| "log" | |
| "strings" | |
| ) | |
| // We need at least 2 additional `.scm` files: | |
| // https://github.com/nvim-treesitter/nvim-treesitter/blob/42fc28ba918343ebfd5565147a42a26580579482/queries/ecma/highlights.scm | |
| // | |
| //go:embed highlights.scm | |
| var highlights []byte | |
| // https://github.com/nvim-treesitter/nvim-treesitter/blob/42fc28ba918343ebfd5565147a42a26580579482/queries/ecma/locals.scm | |
| // | |
| //go:embed locals.scm | |
| var locals []byte | |
| // No injections for now! | |
| // Would be: https://github.com/nvim-treesitter/nvim-treesitter/blob/42fc28ba918343ebfd5565147a42a26580579482/queries/ecma/injections.scm | |
| // But this means we'd maintain a language stack, additional languages, etc. | |
| var injections = []byte{} | |
| func main() { | |
| // The code we want to highlight | |
| code := []byte(` | |
| const foo = 1 + 2; | |
| const bar = 2 + "hello" | |
| $( document ).ready(function() { | |
| console.log( "ready!" ); | |
| });`) | |
| // The types we want to highlight | |
| // Order is relevant | |
| captureNames := []string{ | |
| "variable", | |
| "function", | |
| "string", | |
| "keyword", | |
| "comment", | |
| "constant", | |
| "number", | |
| } | |
| // the colors for each type | |
| // better approach would be reading a theme: | |
| // https://tree-sitter.github.io/tree-sitter/cli/init-config.html#theme | |
| colormap := map[string]string{ | |
| "black": "\033[0m", | |
| "constant": "\033[0;31m", | |
| "type": "\033[0;33m", | |
| "constructor": "\033[0;36m", | |
| "type.builtin": "\033[0;32m", | |
| "label": "\033[0;35m", | |
| "variable.member": "\033[0;37m", | |
| "variable": "\033[0;38m", | |
| } | |
| // set up the language and parser | |
| language := tree_sitter.NewLanguage(tree_sitter_javascript.Language()) | |
| parser := tree_sitter.NewParser() | |
| defer parser.Close() | |
| parser.SetLanguage(language) | |
| // Parse and create the query | |
| tree := parser.Parse(code, nil) | |
| defer tree.Close() | |
| q, _ := tree_sitter.NewQuery(language, string(highlights)) | |
| qc := tree_sitter.NewQueryCursor() | |
| defer q.Close() | |
| defer qc.Close() | |
| // Get a list of all captures | |
| // More efficient would be to make the colormap based on int -> color instead of string -> color | |
| existingCaptures := q.CaptureNames() | |
| // Set up the highlighter and its config | |
| cfg, err := highlight.NewConfiguration(language, "go", highlights, injections, locals) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| // What objects we care about | |
| cfg.Configure(captureNames) | |
| // Create the highlighter and | |
| highlighter := highlight.New() | |
| events := highlighter.Highlight(context.Background(), *cfg, code, func(name string) *highlight.Configuration { | |
| return nil | |
| }) | |
| // The final string containing all highlights | |
| var s strings.Builder | |
| // Current highlighting type | |
| var t string | |
| for event, err := range events { | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| switch e := event.(type) { | |
| //case highlight.EventLayerStart: | |
| // Only required if language injections exists | |
| // In which case we need to add the new language to the stack | |
| // and make sure to use the same language down below | |
| //case highlight.EventLayerEnd: | |
| // Only required if language injections exists | |
| // In which case we need to remove the language from the stack | |
| case highlight.EventCaptureStart: | |
| log.Printf("New highlight capture %v, %v", e.Highlight, existingCaptures[e.Highlight]) | |
| t = existingCaptures[e.Highlight] | |
| case highlight.EventCaptureEnd: | |
| //log.Printf("Capture end") | |
| case highlight.EventSource: | |
| log.Printf("Highlight range %d-%d", e.StartByte, e.EndByte) | |
| if t != "" { | |
| s.WriteString(colormap[t]) | |
| } | |
| s.Write(code[e.StartByte:e.EndByte]) | |
| if t != "" { | |
| s.WriteString(colormap["black"]) | |
| } | |
| t = "" | |
| } | |
| } | |
| fmt.Println(s.String()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment