Compare commits

..

No commits in common. "6034e44364ee7d1095d17b36f64233fdd626de70" and "32fe3f1eddd6ad158f7d7f73fef5798cc7a9d9e1" have entirely different histories.

45 changed files with 275 additions and 8494 deletions

View File

@ -295,7 +295,7 @@ Buffers are in-memory representations of files. A buffer exists for each open fi
### Buffer State
- [ ] Track cursor position per buffer
- [x] Track undo history per buffer
- [ ] Track undo history per buffer
- [ ] Track marks per buffer
- [ ] Remember scroll position when switching
- [ ] Alternate buffer (`#`) tracking

View File

@ -20,7 +20,7 @@
- [ ] Configuration file support (~/.gimrc or similar)
- [ ] Persistent undo history
- [ ] Line wrapping display option
- [ ] Handle large files gracefully (>10k lines) [Pretty sure this is fine]
- [ ] Handle large files gracefully (>10k lines)
## Nice to Have (Polish)
- [ ] Incremental search (search as you type)

View File

@ -41,7 +41,7 @@
export GOOS=linux
export GOARCH=amd64
export CGO_CFLAGS=-Wno-error=cpp;
export CGO_ENABLED=1
export CGO_ENABLED=0
# Exec zsh to replace the current shell process with zsh.
# This ensures your prompt and zsh configurations load correctly.

18
go.mod
View File

@ -3,24 +3,10 @@ module git.gophernest.net/azpect/TextEditor
go 1.25.5
require (
github.com/alecthomas/chroma/v2 v2.23.1
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/x/exp/teatest v0.0.0-20260209132835-6b065b8ba62c
github.com/tree-sitter/go-tree-sitter v0.25.0
github.com/tree-sitter/tree-sitter-bash v0.25.1
github.com/tree-sitter/tree-sitter-c v0.24.1
github.com/tree-sitter/tree-sitter-c-sharp v0.23.1
github.com/tree-sitter/tree-sitter-cpp v0.23.4
github.com/tree-sitter/tree-sitter-css v0.25.0
github.com/tree-sitter/tree-sitter-go v0.25.0
github.com/tree-sitter/tree-sitter-html v0.23.2
github.com/tree-sitter/tree-sitter-java v0.23.5
github.com/tree-sitter/tree-sitter-javascript v0.25.0
github.com/tree-sitter/tree-sitter-json v0.24.8
github.com/tree-sitter/tree-sitter-python v0.25.0
github.com/tree-sitter/tree-sitter-ruby v0.23.1
github.com/tree-sitter/tree-sitter-rust v0.24.2
github.com/tree-sitter/tree-sitter-typescript v0.23.2
)
require (
@ -34,11 +20,11 @@ require (
github.com/clipperhouse/displaywidth v0.9.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-pointer v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect

54
go.sum
View File

@ -1,3 +1,9 @@
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
@ -24,18 +30,18 @@ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfa
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
@ -44,46 +50,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tree-sitter/go-tree-sitter v0.25.0 h1:sx6kcg8raRFCvc9BnXglke6axya12krCJF5xJ2sftRU=
github.com/tree-sitter/go-tree-sitter v0.25.0/go.mod h1:r77ig7BikoZhHrrsjAnv8RqGti5rtSyvDHPzgTPsUuU=
github.com/tree-sitter/tree-sitter-bash v0.25.1 h1:ZD3MK4oDB5lAsFztqbdcyYEd24pxDtx3g9UOWA062rE=
github.com/tree-sitter/tree-sitter-bash v0.25.1/go.mod h1:AksQ6zE+sP9hnp7mKTMT7Q+CwpthV7VGQLXvweVXz9U=
github.com/tree-sitter/tree-sitter-c v0.24.1 h1:GV9DjvIV6uYe3W/JBKMFwE4hJcRxzRDq63llxNFHOkY=
github.com/tree-sitter/tree-sitter-c v0.24.1/go.mod h1:/SpJlv2BuiCgFA5xvtgukFGi51WxctByPUGDxPl60fc=
github.com/tree-sitter/tree-sitter-c-sharp v0.23.1 h1:ddG6osP34sMieVNN6lu5ZG/3N8Wn+67+43BmipqidyM=
github.com/tree-sitter/tree-sitter-c-sharp v0.23.1/go.mod h1:H7/aFm5vR1A8Yn5VIOfLWPdlKuJsMgZ5eDmaJdv8bY0=
github.com/tree-sitter/tree-sitter-cpp v0.23.4 h1:LaWZsiqQKvR65yHgKmnaqA+uz6tlDJTJFCyFIeZU/8w=
github.com/tree-sitter/tree-sitter-cpp v0.23.4/go.mod h1:doqNW64BriC7WBCQ1klf0KmJpdEvfxyXtoEybnBo6v8=
github.com/tree-sitter/tree-sitter-css v0.25.0 h1:S5NbzhdZ5LE5V474wmdg+7NthmLjIg5v4wbyewMpziw=
github.com/tree-sitter/tree-sitter-css v0.25.0/go.mod h1:0Z46XCb3L16nVOVw0Lhb43pzloUG/4T6E/pAOE62fEw=
github.com/tree-sitter/tree-sitter-embedded-template v0.23.2 h1:nFkkH6Sbe56EXLmZBqHHcamTpmz3TId97I16EnGy4rg=
github.com/tree-sitter/tree-sitter-embedded-template v0.23.2/go.mod h1:HNPOhN0qF3hWluYLdxWs5WbzP/iE4aaRVPMsdxuzIaQ=
github.com/tree-sitter/tree-sitter-go v0.25.0 h1:cEB0Q3LHgZtS+ECHx9wcP7AwzoOddJFQCVmytX42cVU=
github.com/tree-sitter/tree-sitter-go v0.25.0/go.mod h1:Jrx8QqYN0v7npv1fJRH1AznddllYiCMUChtVjxPK040=
github.com/tree-sitter/tree-sitter-html v0.23.2 h1:1UYDV+Yd05GGRhVnTcbP58GkKLSHHZwVaN+lBZV11Lc=
github.com/tree-sitter/tree-sitter-html v0.23.2/go.mod h1:gpUv/dG3Xl/eebqgeYeFMt+JLOY9cgFinb/Nw08a9og=
github.com/tree-sitter/tree-sitter-java v0.23.5 h1:J9YeMGMwXYlKSP3K4Us8CitC6hjtMjqpeOf2GGo6tig=
github.com/tree-sitter/tree-sitter-java v0.23.5/go.mod h1:NRKlI8+EznxA7t1Yt3xtraPk1Wzqh3GAIC46wxvc320=
github.com/tree-sitter/tree-sitter-javascript v0.25.0 h1:ZkWETb66/w8cc13yhfnNuHOLDQWl3BnKlH6f9AdR88c=
github.com/tree-sitter/tree-sitter-javascript v0.25.0/go.mod h1:lmGD1EJdCA+v0S1u2fFgepMg/opzSg/4pgFym2FPGAs=
github.com/tree-sitter/tree-sitter-json v0.24.8 h1:tV5rMkihgtiOe14a9LHfDY5kzTl5GNUYe6carZBn0fQ=
github.com/tree-sitter/tree-sitter-json v0.24.8/go.mod h1:F351KK0KGvCaYbZ5zxwx/gWWvZhIDl0eMtn+1r+gQbo=
github.com/tree-sitter/tree-sitter-php v0.23.11 h1:iHewsLNDmznh8kgGyfWfujsZxIz1YGbSd2ZTEM0ZiP8=
github.com/tree-sitter/tree-sitter-php v0.23.11/go.mod h1:T/kbfi+UcCywQfUNAJnGTN/fMSUjnwPXA8k4yoIks74=
github.com/tree-sitter/tree-sitter-python v0.25.0 h1:O6XD9v8U1LOcRc3cNj9nM7XufrtEBezE6VrpRrHZDf0=
github.com/tree-sitter/tree-sitter-python v0.25.0/go.mod h1:cpdthSy/Yoa28aJFBscFHlGiU+cnSiSh1kuDVtI8YeM=
github.com/tree-sitter/tree-sitter-ruby v0.23.1 h1:T/NKHUA+iVbHM440hFx+lzVOzS4dV6z8Qw8ai+72bYo=
github.com/tree-sitter/tree-sitter-ruby v0.23.1/go.mod h1:kUS4kCCQloFcdX6sdpr8p6r2rogbM6ZjTox5ZOQy8cA=
github.com/tree-sitter/tree-sitter-rust v0.24.2 h1:NL4nF67ib21RMzzfvkmXlVwe45vvhW10DVyO+D0z/W0=
github.com/tree-sitter/tree-sitter-rust v0.24.2/go.mod h1:hfeGWic9BAfgTrc7Xf6FaOAguCFJRo3RBbs7QJ6D7MI=
github.com/tree-sitter/tree-sitter-typescript v0.23.2 h1:/Odvphn18PniVixb9e97X0DbNVsU6Qocv9mfkyzdXwU=
github.com/tree-sitter/tree-sitter-typescript v0.23.2/go.mod h1:zjzMXT/Ulffel2xfOcAkQQkiAkmgnbtPGlFQw/5X4xA=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
@ -94,5 +62,3 @@ golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -13,6 +13,7 @@ import (
"git.gophernest.net/azpect/TextEditor/internal/action"
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/style"
"github.com/alecthomas/chroma/v2/styles"
tea "github.com/charmbracelet/bubbletea"
)
@ -882,31 +883,29 @@ func parseSetOption(m action.Model, opt string) error {
// Colorscheme Commands
// --------------------------------------------------
// TODO: Implement this using the new colorschemes
func cmdColorscheme(m action.Model, args []string, force bool) tea.Cmd {
_ = force
// No args, just print the current scheme
if len(args) == 0 {
s := m.Styles().ChromaStyle
if s == nil {
return nil
}
m.SetCommandOutput(&core.CommandOutput{
Lines: []string{"default"},
Lines: []string{s.Name},
Inline: true,
IsError: false,
})
return nil
}
// Theme switching is disabled while migrating away from Chroma.
name := strings.TrimSpace(strings.Join(args, " "))
if name == "" {
m.SetCommandOutput(&core.CommandOutput{
Lines: []string{"colorscheme not found: "},
Inline: true,
IsError: true,
})
return nil
}
if name != "" && strings.ToLower(name) != "default" {
// Args given, set the scheme
name := strings.Join(args, " ")
chromaStyle := styles.Registry[name]
if chromaStyle == nil {
m.SetCommandOutput(&core.CommandOutput{
Lines: []string{fmt.Sprintf("colorscheme not found: %s", name)},
Inline: true,
@ -915,14 +914,14 @@ func cmdColorscheme(m action.Model, args []string, force bool) tea.Cmd {
return nil
}
m.SetStyles(style.DefaultStyles())
m.SetStyles(style.ChromaStyles(chromaStyle))
return nil
}
func cmdListColorschemes(m action.Model, args []string, force bool) tea.Cmd {
_, _ = args, force
colors := []string{"default"}
colors := styles.Names()
m.SetMode(core.CommandOutputMode)
m.SetCommandOutput(&core.CommandOutput{

View File

@ -10,6 +10,7 @@ import (
"git.gophernest.net/azpect/TextEditor/internal/action"
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/style"
cStyles "github.com/alecthomas/chroma/v2/styles"
tea "github.com/charmbracelet/bubbletea"
)
@ -5514,24 +5515,22 @@ func TestCmdColorscheme(t *testing.T) {
t.Run("valid name updates styles on model", func(t *testing.T) {
m := action.NewMockModel()
m.SetStyles(style.DefaultStyles())
before := m.StylesVal.BackgroundStyle.Render(" ")
cmdColorscheme(m, []string{"default"}, false)
cmdColorscheme(m, []string{"onedark"}, false)
after := m.StylesVal.BackgroundStyle.Render(" ")
if after != before {
t.Error("expected default styles to remain stable after applying default")
name := m.Styles().ChromaStyle.Name
if name != "onedark" {
t.Error("expected styles to change after setting a valid colorscheme")
}
})
t.Run("same valid name applied twice produces same styles", func(t *testing.T) {
m := action.NewMockModel()
cmdColorscheme(m, []string{"default"}, false)
cmdColorscheme(m, []string{"monokai"}, false)
first := m.StylesVal.BackgroundStyle.Render(" ")
cmdColorscheme(m, []string{"default"}, false)
cmdColorscheme(m, []string{"monokai"}, false)
second := m.StylesVal.BackgroundStyle.Render(" ")
if first != second {
@ -5542,7 +5541,7 @@ func TestCmdColorscheme(t *testing.T) {
t.Run("valid name sets no error output", func(t *testing.T) {
m := action.NewMockModel()
cmdColorscheme(m, []string{"default"}, false)
cmdColorscheme(m, []string{"monokai"}, false)
if m.CommandOutputVal != nil && m.CommandOutputVal.IsError {
t.Error("expected no error output for a valid colorscheme name")
@ -5552,7 +5551,7 @@ func TestCmdColorscheme(t *testing.T) {
t.Run("valid name returns nil tea.Cmd", func(t *testing.T) {
m := action.NewMockModel()
cmd := cmdColorscheme(m, []string{"default"}, false)
cmd := cmdColorscheme(m, []string{"monokai"}, false)
if cmd != nil {
t.Error("expected nil tea.Cmd for colorscheme command")
@ -5652,7 +5651,7 @@ func TestCmdColorscheme(t *testing.T) {
t.Run("extra args beyond name do not panic", func(t *testing.T) {
m := action.NewMockModel()
cmdColorscheme(m, []string{"default", "extra", "args"}, false)
cmdColorscheme(m, []string{"monokai", "extra", "args"}, false)
})
// --------------------------------------------------
@ -5662,9 +5661,10 @@ func TestCmdColorscheme(t *testing.T) {
t.Run("force flag with valid name still sets styles", func(t *testing.T) {
m := action.NewMockModel()
cmdColorscheme(m, []string{"default"}, true)
cmdColorscheme(m, []string{"monokai"}, true)
if m.CommandOutputVal != nil && m.CommandOutputVal.IsError {
name := m.Styles().ChromaStyle.Name
if name != "monokai" {
t.Error("expected styles to change with force=true and valid name")
}
})
@ -5742,7 +5742,7 @@ func TestCmdListColorschemes(t *testing.T) {
cmdListColorschemes(m, []string{}, false)
lines := m.CommandOutputVal.Lines
known := []string{"default"}
known := []string{"monokai", "github-dark", "dracula"}
for _, name := range known {
found := false
for _, l := range lines {
@ -5762,7 +5762,7 @@ func TestCmdListColorschemes(t *testing.T) {
cmdListColorschemes(m, []string{}, false)
expected := []string{"default"}
expected := cStyles.Names()
if len(m.CommandOutputVal.Lines) != len(expected) {
t.Errorf("expected %d colorschemes, got %d", len(expected), len(m.CommandOutputVal.Lines))
}
@ -5785,7 +5785,7 @@ func TestCmdListColorschemes(t *testing.T) {
m2 := action.NewMockModel()
cmdListColorschemes(m1, []string{}, false)
cmdListColorschemes(m2, []string{"default", "extra"}, true)
cmdListColorschemes(m2, []string{"monokai", "extra"}, true)
if len(m1.CommandOutputVal.Lines) != len(m2.CommandOutputVal.Lines) {
t.Error("expected args and force to have no effect on list output")

View File

@ -1,48 +1,9 @@
package core
import "strings"
type BufferOptions struct {
// tabstop expandtab
}
type BufferChangeKind int
const (
BufferChangeSetLine BufferChangeKind = iota
BufferChangeInsertLine
BufferChangeDeleteLine
BufferChangeSetLines
)
type BufferChange struct {
Kind BufferChangeKind
StartLine int
EndLine int
Edit *BufferEdit
}
// TextPoint is a byte-oriented row/column point.
//
// Column is counted in bytes (Tree-sitter compatible), not runes.
type TextPoint struct {
Row uint
Column uint
}
// BufferEdit represents a text edit in byte offsets and points.
//
// These fields map directly to Tree-sitter incremental edit inputs.
type BufferEdit struct {
StartByte uint
OldEndByte uint
NewEndByte uint
StartPoint TextPoint
OldEndPoint TextPoint
NewEndPoint TextPoint
}
type BufferType int
const (
@ -69,9 +30,6 @@ type Buffer struct {
// Options BufferOptions
UndoStack *UndoStack
// Optional change callback used by higher layers (editor/syntax) to react to edits.
OnChange func(change BufferChange)
}
// ==================================================
@ -90,33 +48,20 @@ func (b *Buffer) Line(idx int) string {
// Buffer.SetLine: Set the content of the line at an index. Does nothing if the
// index is out of bounds. This function sets the modified flag.
func (b *Buffer) SetLine(idx int, content string) {
oldSource := b.sourceString()
changed := false
if idx >= 0 && idx < len(b.Lines) {
// Record set line in undo stack
if b.UndoStack != nil {
b.UndoStack.RecordSetLine(idx, b.Lines[idx].String(), content)
}
b.Lines[idx].Set(content)
changed = true
}
b.Modified = true
if changed {
newSource := b.sourceString()
edit, ok := computeBufferEdit(oldSource, newSource)
change := BufferChange{Kind: BufferChangeSetLine, StartLine: idx, EndLine: idx}
if ok {
change.Edit = &edit
}
b.notifyChange(change)
}
}
// Buffer.InsertLine: Insert a line with content at an index. The index is clamped
// to valid bounds (0 to len(Lines)). The new line is inserted before the line at
// the given index. This function sets the modified flag.
func (b *Buffer) InsertLine(idx int, content string) {
oldSource := b.sourceString()
if idx < 0 {
idx = 0
}
@ -132,39 +77,19 @@ func (b *Buffer) InsertLine(idx int, content string) {
newLine := NewGapBuffer(content)
b.Lines = append(b.Lines[:idx], append([]*GapBuffer{newLine}, b.Lines[idx:]...)...)
b.Modified = true
newSource := b.sourceString()
edit, ok := computeBufferEdit(oldSource, newSource)
change := BufferChange{Kind: BufferChangeInsertLine, StartLine: idx, EndLine: len(b.Lines) - 1}
if ok {
change.Edit = &edit
}
b.notifyChange(change)
}
// Buffer.DeleteLine: Delete a line at an index. Does nothing if the index is out
// of bounds. This function sets the modified flag.
func (b *Buffer) DeleteLine(idx int) {
oldSource := b.sourceString()
changed := false
if idx >= 0 && idx < len(b.Lines) {
// Record delete line in undo stack
if b.UndoStack != nil {
b.UndoStack.RecordDeleteLine(idx, b.Lines[idx].String())
}
b.Lines = append(b.Lines[:idx], b.Lines[idx+1:]...)
changed = true
}
b.Modified = true
if changed {
newSource := b.sourceString()
edit, ok := computeBufferEdit(oldSource, newSource)
change := BufferChange{Kind: BufferChangeDeleteLine, StartLine: idx, EndLine: len(b.Lines) - 1}
if ok {
change.Edit = &edit
}
b.notifyChange(change)
}
}
// Buffer.LineCount: Get the number of lines in the buffer.
@ -180,8 +105,6 @@ func (b *Buffer) Undo(w *Window) bool {
return false
}
oldSource := b.sourceString()
block := b.UndoStack.Undo()
if block == nil {
return false
@ -221,16 +144,6 @@ func (b *Buffer) Undo(w *Window) bool {
w.SetCursorLine(block.OldCursor.Line)
w.SetCursorCol(block.OldCursor.Col)
newSource := b.sourceString()
if edit, ok := computeBufferEdit(oldSource, newSource); ok {
b.notifyChange(BufferChange{
Kind: BufferChangeSetLines,
StartLine: 0,
EndLine: max(0, len(b.Lines)-1),
Edit: &edit,
})
}
return true
}
@ -239,8 +152,6 @@ func (b *Buffer) Redo(w *Window) bool {
return false
}
oldSource := b.sourceString()
block := b.UndoStack.Redo()
if block == nil {
return false
@ -278,16 +189,6 @@ func (b *Buffer) Redo(w *Window) bool {
w.SetCursorLine(block.NewCursor.Line)
w.SetCursorCol(block.NewCursor.Col)
newSource := b.sourceString()
if edit, ok := computeBufferEdit(oldSource, newSource); ok {
b.notifyChange(BufferChange{
Kind: BufferChangeSetLines,
StartLine: 0,
EndLine: max(0, len(b.Lines)-1),
Edit: &edit,
})
}
return true
}
@ -310,93 +211,10 @@ func (b *Buffer) SetFiletype(filetype string) {
// Buffer.SetLines: Replace all lines in the buffer with the provided lines.
// This is useful when loading a file or resetting buffer content.
func (b *Buffer) SetLines(lines []string) {
oldSource := b.sourceString()
b.Lines = make([]*GapBuffer, len(lines))
for i, line := range lines {
b.Lines[i] = NewGapBuffer(line)
}
newSource := b.sourceString()
edit, ok := computeBufferEdit(oldSource, newSource)
change := BufferChange{Kind: BufferChangeSetLines, StartLine: 0, EndLine: len(lines) - 1}
if ok {
change.Edit = &edit
}
b.notifyChange(change)
}
func (b *Buffer) notifyChange(change BufferChange) {
if b.OnChange != nil {
b.OnChange(change)
}
}
func (b *Buffer) sourceString() string {
if len(b.Lines) == 0 {
return ""
}
parts := make([]string, len(b.Lines))
for i := range b.Lines {
parts[i] = b.Lines[i].String()
}
return strings.Join(parts, "\n")
}
func computeBufferEdit(oldSource, newSource string) (BufferEdit, bool) {
if oldSource == newSource {
return BufferEdit{}, false
}
oldBytes := []byte(oldSource)
newBytes := []byte(newSource)
prefix := 0
maxPrefix := min(len(oldBytes), len(newBytes))
for prefix < maxPrefix && oldBytes[prefix] == newBytes[prefix] {
prefix++
}
oldEnd := len(oldBytes)
newEnd := len(newBytes)
for oldEnd > prefix && newEnd > prefix && oldBytes[oldEnd-1] == newBytes[newEnd-1] {
oldEnd--
newEnd--
}
edit := BufferEdit{
StartByte: uint(prefix),
OldEndByte: uint(oldEnd),
NewEndByte: uint(newEnd),
StartPoint: byteOffsetToPoint(oldBytes, prefix),
OldEndPoint: byteOffsetToPoint(oldBytes, oldEnd),
NewEndPoint: byteOffsetToPoint(newBytes, newEnd),
}
return edit, true
}
func byteOffsetToPoint(src []byte, offset int) TextPoint {
if offset < 0 {
offset = 0
}
if offset > len(src) {
offset = len(src)
}
var row uint
var col uint
for i := 0; i < offset; i++ {
if src[i] == '\n' {
row++
col = 0
} else {
col++
}
}
return TextPoint{Row: row, Column: col}
}
// Buffer.SetModified: Set the modified flag for this buffer. A modified buffer

View File

@ -1,62 +0,0 @@
package core
import "testing"
func FuzzComputeBufferEditInvariants(f *testing.F) {
f.Add("abc\ndef", "abc\nxyz")
f.Add("", "x")
f.Add("same", "same")
f.Add("hello", "")
f.Fuzz(func(t *testing.T, oldSource, newSource string) {
edit, ok := computeBufferEdit(oldSource, newSource)
if oldSource == newSource {
if ok {
t.Fatalf("expected no edit when strings are equal")
}
return
}
if !ok {
t.Fatalf("expected edit for differing strings")
}
oldBytes := []byte(oldSource)
newBytes := []byte(newSource)
start := int(edit.StartByte)
oldEnd := int(edit.OldEndByte)
newEnd := int(edit.NewEndByte)
if start < 0 || start > len(oldBytes) || start > len(newBytes) {
t.Fatalf("invalid start byte: %d", start)
}
if oldEnd < start || oldEnd > len(oldBytes) {
t.Fatalf("invalid old end byte: %d", oldEnd)
}
if newEnd < start || newEnd > len(newBytes) {
t.Fatalf("invalid new end byte: %d", newEnd)
}
if string(oldBytes[:start]) != string(newBytes[:start]) {
t.Fatalf("prefix before edit start must match")
}
if string(oldBytes[oldEnd:]) != string(newBytes[newEnd:]) {
t.Fatalf("suffix after edit end must match")
}
sp := byteOffsetToPoint(oldBytes, start)
op := byteOffsetToPoint(oldBytes, oldEnd)
np := byteOffsetToPoint(newBytes, newEnd)
if sp != edit.StartPoint {
t.Fatalf("start point mismatch: got %+v want %+v", edit.StartPoint, sp)
}
if op != edit.OldEndPoint {
t.Fatalf("old end point mismatch: got %+v want %+v", edit.OldEndPoint, op)
}
if np != edit.NewEndPoint {
t.Fatalf("new end point mismatch: got %+v want %+v", edit.NewEndPoint, np)
}
})
}

View File

@ -1,103 +0,0 @@
package core
import "testing"
func TestComputeBufferEditReplaceLine(t *testing.T) {
oldSource := "abc\ndef"
newSource := "abc\nxyz"
edit, ok := computeBufferEdit(oldSource, newSource)
if !ok {
t.Fatalf("expected edit to be detected")
}
if edit.StartPoint.Row != 1 || edit.StartPoint.Column != 0 {
t.Fatalf("unexpected start point: %+v", edit.StartPoint)
}
if edit.OldEndPoint.Row != 1 || edit.OldEndPoint.Column != 3 {
t.Fatalf("unexpected old end point: %+v", edit.OldEndPoint)
}
if edit.NewEndPoint.Row != 1 || edit.NewEndPoint.Column != 3 {
t.Fatalf("unexpected new end point: %+v", edit.NewEndPoint)
}
}
func TestComputeBufferEditInsertAtEnd(t *testing.T) {
oldSource := "a\nb"
newSource := "a\nbb"
edit, ok := computeBufferEdit(oldSource, newSource)
if !ok {
t.Fatalf("expected edit to be detected")
}
if edit.StartByte != 3 || edit.OldEndByte != 3 || edit.NewEndByte != 4 {
t.Fatalf("unexpected byte offsets: %+v", edit)
}
if edit.StartPoint.Row != 1 || edit.StartPoint.Column != 1 {
t.Fatalf("unexpected start point: %+v", edit.StartPoint)
}
}
func TestByteOffsetToPoint(t *testing.T) {
src := []byte("ab\ncd\nef")
p := byteOffsetToPoint(src, 0)
if p.Row != 0 || p.Column != 0 {
t.Fatalf("offset 0 mismatch: %+v", p)
}
p = byteOffsetToPoint(src, 4) // right after 'c'
if p.Row != 1 || p.Column != 1 {
t.Fatalf("offset 4 mismatch: %+v", p)
}
p = byteOffsetToPoint(src, len(src))
if p.Row != 2 || p.Column != 2 {
t.Fatalf("end offset mismatch: %+v", p)
}
}
func TestUndoRedoEmitBufferChange(t *testing.T) {
b := NewBufferBuilder().WithFiletype("go").WithLines([]string{"one", "two"}).Build()
buf := &b
win := NewWindowBuilder().WithBuffer(buf).Build()
w := &win
buf.UndoStack.BeginBlock(Position{Line: 0, Col: 0})
buf.SetLine(0, "ONE")
buf.UndoStack.EndBlock(Position{Line: 0, Col: 3})
changes := []BufferChange{}
buf.OnChange = func(change BufferChange) {
changes = append(changes, change)
}
if !buf.Undo(w) {
t.Fatalf("expected undo to succeed")
}
if len(changes) != 1 {
t.Fatalf("expected one change notification on undo, got %d", len(changes))
}
if changes[0].Edit == nil {
t.Fatalf("expected undo change to include edit metadata")
}
if got := buf.Line(0); got != "one" {
t.Fatalf("undo did not restore content, got %q", got)
}
changes = nil
if !buf.Redo(w) {
t.Fatalf("expected redo to succeed")
}
if len(changes) != 1 {
t.Fatalf("expected one change notification on redo, got %d", len(changes))
}
if changes[0].Edit == nil {
t.Fatalf("expected redo change to include edit metadata")
}
if got := buf.Line(0); got != "ONE" {
t.Fatalf("redo did not reapply content, got %q", got)
}
}

View File

@ -8,7 +8,6 @@ import (
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/input"
"git.gophernest.net/azpect/TextEditor/internal/style"
"git.gophernest.net/azpect/TextEditor/internal/syntax"
tea "github.com/charmbracelet/bubbletea"
)
@ -52,7 +51,6 @@ type Model struct {
// Visual styles
styles style.Styles
syntax syntax.Engine
// Dot operator state
lastChangeKeys []string
@ -89,7 +87,6 @@ func (m *Model) Buffers() []*core.Buffer {
func (m *Model) SetBuffers(bufs []*core.Buffer) {
m.buffers = bufs
m.bindBufferSyntaxHooks(bufs)
}
func (m *Model) ActiveBuffer() *core.Buffer {
@ -351,42 +348,6 @@ func (m *Model) SetStyles(s style.Styles) {
m.styles = s
}
func (m *Model) Syntax() syntax.Engine {
return m.syntax
}
func (m *Model) SetSyntax(s syntax.Engine) {
m.syntax = s
m.bindBufferSyntaxHooks(m.buffers)
}
func (m *Model) bindBufferSyntaxHooks(bufs []*core.Buffer) {
if m.syntax == nil {
return
}
for _, buf := range bufs {
if buf == nil {
continue
}
b := buf
b.OnChange = func(change core.BufferChange) {
if change.Edit != nil {
m.syntax.ApplyEdit(b, change.Edit)
return
}
switch change.Kind {
case core.BufferChangeSetLine:
m.syntax.InvalidateLines(b, change.StartLine, change.EndLine)
default:
m.syntax.InvalidateBuffer(b)
}
}
}
}
// ==================================================
// Registers
// ==================================================

View File

@ -4,7 +4,7 @@ import (
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/input"
"git.gophernest.net/azpect/TextEditor/internal/style"
"git.gophernest.net/azpect/TextEditor/internal/syntax"
"github.com/alecthomas/chroma/v2/styles"
)
type ModelBuilder struct {
@ -13,7 +13,7 @@ type ModelBuilder struct {
// NewModelBuilder: Builds and returns a new model, using the default color scheme (kanagawa-wave).
func NewModelBuilder() *ModelBuilder {
editorStyles := style.DefaultStyles()
chromaStyle := styles.Get("kanagawa-wave")
return &ModelBuilder{
model: Model{
@ -32,8 +32,7 @@ func NewModelBuilder() *ModelBuilder {
commandOutput: nil,
settings: core.NewDefaultSettings(),
registers: core.DefaultRegisters(),
styles: editorStyles,
syntax: syntax.NewTreeSitterEngine(editorStyles),
styles: style.ChromaStyles(chromaStyle),
},
}
}
@ -134,7 +133,5 @@ func (mb *ModelBuilder) WithStyles(styles style.Styles) *ModelBuilder {
// ModelBuilder.Build: Build and return the configured Model instance.
func (mb *ModelBuilder) Build() *Model {
m := &mb.model
m.bindBufferSyntaxHooks(m.buffers)
return m
return &mb.model
}

View File

@ -7,7 +7,6 @@ import (
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/style"
"git.gophernest.net/azpect/TextEditor/internal/syntax"
"github.com/charmbracelet/lipgloss"
)
@ -29,7 +28,7 @@ func (m Model) View() string {
options.GutterSize = max(options.GutterSize, maxLineLen+2)
// Draw window
view := viewWindow(win, styles, options, m.Mode(), m.Syntax())
view := viewWindow(win, styles, options, m.Mode())
// Command bar is seperate
cmdBar := drawCommandBar(m)
@ -47,24 +46,21 @@ func (m Model) View() string {
// viewWindow: Renders a single window's content including line numbers and buffer text.
// Each window has its own line numbers, gutter, and viewport dimensions.
func viewWindow(w *core.Window, styles style.Styles, options core.WinOptions, mode core.Mode, sx syntax.Engine) string {
func viewWindow(w *core.Window, styles style.Styles, options core.WinOptions, mode core.Mode) string {
buf := w.Buffer
var view strings.Builder
if sx != nil {
sx.PrepareBuffer(buf)
}
// Compute window size (y)
start := w.ScrollY
end := w.ScrollY + w.ViewportHeight()
// Chroma stuff
lexer := style.GetLexer(buf)
// Draw buffer lines
for lineNum := start; lineNum < end; lineNum++ {
if lineNum < buf.LineCount() {
styleMap := make([]lipgloss.Style, len([]rune(buf.Line(lineNum))))
if sx != nil {
styleMap = sx.LineStyleMap(buf, lineNum)
}
styleMap := styles.MakeStyleMap(lexer, buf.Line(lineNum))
line := drawLine(w, styles, options, mode, buf.Line(lineNum), lineNum, styleMap)
view.WriteString(line)
} else {

View File

@ -1,52 +0,0 @@
package style
import (
"strings"
"github.com/charmbracelet/lipgloss"
)
func CaptureStyle(base lipgloss.Style, capture string) lipgloss.Style {
full := strings.ToLower(strings.TrimSpace(capture))
baseName := strings.Split(full, ".")[0]
switch full {
case "keyword", "keyword.type", "keyword.function", "keyword.coroutine", "keyword.repeat", "keyword.import", "keyword.conditional":
return base.Foreground(lipgloss.Color("#c678dd"))
case "function", "function.call", "function.method", "function.method.call":
return base.Foreground(lipgloss.Color("#61afef"))
case "function.builtin", "constructor", "keyword.return":
return base.Foreground(lipgloss.Color("#ff5f5f"))
case "type", "type.builtin", "type.definition":
return base.Foreground(lipgloss.Color("#e5c07b"))
case "string", "string.escape":
return base.Foreground(lipgloss.Color("#98c379"))
case "number", "number.float", "boolean", "constant", "constant.builtin":
return base.Foreground(lipgloss.Color("#56b6c2"))
case "operator", "punctuation.delimiter", "punctuation.bracket":
return base.Foreground(lipgloss.Color("#d19a66"))
case "comment", "comment.documentation":
return base.Foreground(lipgloss.Color("#7f848e"))
case "variable.parameter":
return base.Foreground(lipgloss.Color("#dcdfe4")).Italic(true)
case "module", "label", "property", "variable.member", "variable":
return base.Foreground(lipgloss.Color("#dcdfe4"))
}
switch baseName {
case "keyword":
return base.Foreground(lipgloss.Color("#c678dd"))
case "function":
return base.Foreground(lipgloss.Color("#61afef"))
case "type":
return base.Foreground(lipgloss.Color("#e5c07b"))
case "string":
return base.Foreground(lipgloss.Color("#98c379"))
case "number", "boolean", "constant":
return base.Foreground(lipgloss.Color("#56b6c2"))
case "comment":
return base.Foreground(lipgloss.Color("#7f848e"))
default:
return base
}
}

223
internal/style/style.go Normal file → Executable file
View File

@ -1,7 +1,11 @@
package style
import (
"strings"
"git.gophernest.net/azpect/TextEditor/internal/core"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/charmbracelet/lipgloss"
)
@ -19,7 +23,7 @@ type Styles struct {
// Visual mode
VisualHighlight lipgloss.Style
VisualAnchor lipgloss.Style
VisualAnchor lipgloss.Style // debugging
// Status bar
StatusBar lipgloss.Style
@ -30,72 +34,132 @@ type Styles struct {
CommandOutputBorder lipgloss.Style
CommandContinueMessage lipgloss.Style
// General styles
LineStyle lipgloss.Style
BackgroundStyle lipgloss.Style
// General Styles
LineStyle lipgloss.Style // This is a simple background with no text coloring
BackgroundStyle lipgloss.Style // This is just the background
// Chroma data
ChromaStyle *chroma.Style
}
// DefaultStyles: Returns the default editor color scheme.
func DefaultStyles() Styles {
bg := lipgloss.Color("#1f2335")
fg := lipgloss.Color("#dcd7ba")
return Styles{
CursorNormal: lipgloss.NewStyle().Reverse(true),
CursorInsert: lipgloss.NewStyle().Underline(true),
CursorCommand: lipgloss.NewStyle().Reverse(true),
CursorReplace: lipgloss.NewStyle().Underline(true),
Gutter: lipgloss.NewStyle().
Background(lipgloss.Color("236")).
Foreground(lipgloss.Color("243")),
GutterCurrentLine: lipgloss.NewStyle().
Background(lipgloss.Color("236")).
Foreground(lipgloss.Color("#d69d00")),
VisualHighlight: lipgloss.NewStyle().
Background(lipgloss.Color("#7a6a00")),
VisualAnchor: lipgloss.NewStyle().
Background(lipgloss.Color("#a89020")),
StatusBar: lipgloss.NewStyle().
Background(lipgloss.Color("236")).
Foreground(lipgloss.Color("243")),
StatusBarActive: lipgloss.NewStyle().
Background(lipgloss.Color("62")).
Foreground(lipgloss.Color("230")),
CommandError: lipgloss.NewStyle().
Foreground(lipgloss.Color("#e3203a")),
CommandOutputBorder: lipgloss.NewStyle().
Background(lipgloss.Color("#000000")),
CommandContinueMessage: lipgloss.NewStyle().
Foreground(lipgloss.Color("#546fba")),
ChromaStyle: nil,
}
}
func ChromaStyles(chromaStyle *chroma.Style) Styles {
bgString := chromaStyle.Get(chroma.Background).Background.String()
lineNumbers := chromaStyle.Get(chroma.LineTableTD)
lineHighlight := chromaStyle.Get(chroma.LineHighlight)
return Styles{
CursorNormal: lipgloss.NewStyle().
Background(fg).
Foreground(bg),
Background(lipgloss.Color(bgString)).
Reverse(true),
CursorInsert: lipgloss.NewStyle().
Background(bg).
Foreground(fg).
Background(lipgloss.Color(bgString)).
Bold(true).
Underline(true),
CursorCommand: lipgloss.NewStyle().
Background(fg).
Foreground(bg),
Background(lipgloss.Color(bgString)).
Reverse(true),
CursorReplace: lipgloss.NewStyle().
Background(bg).
Foreground(fg).
Background(lipgloss.Color(bgString)).
Underline(true),
Gutter: lipgloss.NewStyle().
Background(lipgloss.Color("#181b2a")).
Foreground(lipgloss.Color("#7e8399")),
Background(lipgloss.Color(
darkenColor(lineNumbers.Background, 0.9).String()),
).
Foreground(lipgloss.Color(lineNumbers.Colour.String())),
GutterCurrentLine: lipgloss.NewStyle().
Background(lipgloss.Color("#181b2a")).
Foreground(lipgloss.Color("#e6c384")),
Background(lipgloss.Color(
darkenColor(lineNumbers.Background, 0.9).String()),
).
Foreground(lipgloss.Color(lineNumbers.Colour.String())),
VisualHighlight: lipgloss.NewStyle().
Background(lipgloss.Color("#2f334d")),
Background(lipgloss.Color(lineHighlight.Background.String())).
Foreground(lipgloss.Color(lineHighlight.Colour.String())),
VisualAnchor: lipgloss.NewStyle().
Background(lipgloss.Color("#3a3f5f")),
Background(lipgloss.Color(lineHighlight.Background.String())).
Foreground(lipgloss.Color(lineHighlight.Colour.String())),
StatusBar: lipgloss.NewStyle().
Background(lipgloss.Color("#181b2a")).
Foreground(lipgloss.Color("#8ea4a2")),
Background(lipgloss.Color(bgString)).
Foreground(lipgloss.Color("243")),
StatusBarActive: lipgloss.NewStyle().
Background(lipgloss.Color("#223249")).
Foreground(lipgloss.Color("#9ec1cf")),
Background(lipgloss.Color(bgString)).
Foreground(lipgloss.Color("230")),
CommandError: lipgloss.NewStyle().
Foreground(lipgloss.Color("#e82424")),
Background(lipgloss.Color(bgString)).
Foreground(lipgloss.Color("#e3203a")),
CommandOutputBorder: lipgloss.NewStyle().
Background(lipgloss.Color("#11131d")),
Background(
lipgloss.Color(
darkenColor(
chromaStyle.Get(chroma.Background).Background, 0.5).
String(),
),
),
CommandContinueMessage: lipgloss.NewStyle().
Foreground(lipgloss.Color("#7aa2f7")),
Background(lipgloss.Color(bgString)).
Foreground(lipgloss.Color("#546fba")),
LineStyle: lipgloss.NewStyle().
Foreground(fg).
Background(bg),
Foreground(lipgloss.Color(chromaStyle.Get(chroma.Line).Colour.String())).
Background(lipgloss.Color(bgString)),
BackgroundStyle: lipgloss.NewStyle().
Background(bg),
BackgroundStyle: lipgloss.NewStyle().Background(lipgloss.Color(bgString)),
ChromaStyle: chromaStyle,
}
}
@ -113,29 +177,106 @@ func (s Styles) DefaultCursorStyle(mode core.Mode) lipgloss.Style {
}
}
// Styles.CursorStyle: Returns a cursor style derived from the text style.
func (s Styles) CursorStyle(mode core.Mode, textStyle lipgloss.Style) lipgloss.Style {
// Styles.CursorStyle: Returns a cursor style derived from a chroma style. This function should preferred
// over the DefaultCursorStyle, but in cases where there is no style to apply, the DefaultCursorStyle
// will always work.
func (s Styles) CursorStyle(mode core.Mode, style lipgloss.Style) lipgloss.Style {
switch mode {
case core.NormalMode, core.VisualLineMode, core.VisualBlockMode, core.VisualMode:
return lipgloss.NewStyle().
Background(textStyle.GetForeground()).
Foreground(textStyle.GetBackground())
Background(style.GetForeground()).
Foreground(style.GetBackground())
case core.ReplaceMode, core.WaitingMode:
return lipgloss.NewStyle().
Background(textStyle.GetBackground()).
Foreground(textStyle.GetForeground()).
Background(style.GetBackground()).
Foreground(style.GetForeground()).
Underline(true)
default:
return lipgloss.NewStyle().
Background(s.BackgroundStyle.GetBackground()).
Foreground(textStyle.GetForeground()).
Foreground(style.GetForeground()).
Underline(true)
}
}
// Styles.VisualHighlightWithTextColor: Applies visual background while preserving text color.
func (s Styles) VisualHighlightWithTextColor(textStyle lipgloss.Style) lipgloss.Style {
// Styles.VisualHighlightWithTextColor: Works analogously to CursorStyle vs DefaultCursorStyle. When a
// style is available, this function should be used, so the text color will be rendered in front
// of the background. Otherwise, the VisualHighlight property will always work.
func (s Styles) VisualHighlightWithTextColor(style lipgloss.Style) lipgloss.Style {
return lipgloss.NewStyle().
Background(s.VisualHighlight.GetBackground()).
Foreground(textStyle.GetForeground())
Foreground(style.GetForeground())
}
// Styles.MakeStyleMap: Generates a style map for a single line. A style map is a mapping from
// column a lipgloss style. Cursor styles are not handled by this map, but they can be derived
// by inverting the background and foreground (and rolling back to the default).
func (s Styles) MakeStyleMap(lexer chroma.Lexer, line string) []lipgloss.Style {
m := make([]lipgloss.Style, len(line))
if s.ChromaStyle == nil {
return m
}
iter, err := lexer.Tokenise(nil, line)
if err != nil {
panic(err)
}
col := 0
for _, token := range iter.Tokens() {
entry := s.ChromaStyle.Get(token.Type)
s := lipgloss.NewStyle().
Background(lipgloss.Color(entry.Background.String())).
Foreground(lipgloss.Color(entry.Colour.String()))
for _, char := range token.Value {
if char == '\n' {
continue
}
if col < len(m) {
m[col] = s
}
col++
}
}
return m
}
// darkenColor: Uses a factor (0.0 to 1.0) to darken a color using its opacity.
func darkenColor(c chroma.Colour, factor float64) chroma.Colour {
r := uint8(float64(c.Red()) * factor)
g := uint8(float64(c.Green()) * factor)
b := uint8(float64(c.Blue()) * factor)
return chroma.NewColour(r, g, b)
}
// GetLexer: Uses buffer meta data or content to pick a lexer for use in applying
// highlights.
func GetLexer(buf *core.Buffer) chroma.Lexer {
var lexer chroma.Lexer
if buf.Filetype != "" {
lexer = lexers.Get(strings.TrimPrefix(buf.Filetype, "."))
}
if lexer == nil && buf.Filename != "" {
lexer = lexers.Match(buf.Filename)
}
if lexer == nil && len(buf.Lines) > 0 {
// Get first few lines for content analysis
var content strings.Builder
for i := 0; i < min(len(buf.Lines), 10); i++ {
content.WriteString(buf.Lines[i].String() + "\n")
}
lexer = lexers.Analyse(content.String())
}
if lexer == nil {
lexer = lexers.Fallback
}
lexer = chroma.Coalesce(lexer) // Merge tokens together
return lexer
}

View File

@ -1,10 +0,0 @@
# How to add more languages now (quick workflow)
1. Add binding dependency in go.mod
2. Add query file under internal/syntax/queries/<lang>/highlights.scm
3. Embed it in internal/syntax/query_assets.go
4. Add one register(...) block in internal/syntax/registry.go
5. Update internal/syntax/query_assets_test.go with another test
## Where to get .scm files
[nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter/tree/master/queries)

View File

@ -1,27 +0,0 @@
package syntax
import (
"git.gophernest.net/azpect/TextEditor/internal/core"
"github.com/charmbracelet/lipgloss"
)
// Engine provides syntax highlight data for buffers.
//
// The renderer should consume this interface rather than doing parse/token work
// directly.
type Engine interface {
// Engine.PrepareBuffer: Ensure syntax state for a buffer is ready.
PrepareBuffer(buf *core.Buffer)
// Engine.ApplyEdit: Apply an incremental text edit to syntax state.
ApplyEdit(buf *core.Buffer, edit *core.BufferEdit)
// Engine.LineStyleMap: Returns per-rune styles for a line.
LineStyleMap(buf *core.Buffer, line int) []lipgloss.Style
// Engine.InvalidateBuffer: Marks all syntax state for a buffer as stale.
InvalidateBuffer(buf *core.Buffer)
// Engine.InvalidateLines: Marks a line range as stale.
InvalidateLines(buf *core.Buffer, startLine, endLine int)
}

View File

@ -1,261 +0,0 @@
[
"("
")"
"{"
"}"
"["
"]"
"[["
"]]"
"(("
"))"
] @punctuation.bracket
[
";"
";;"
";&"
";;&"
"&"
] @punctuation.delimiter
[
">"
">>"
"<"
"<<"
"&&"
"|"
"|&"
"||"
"="
"+="
"=~"
"=="
"!="
"&>"
"&>>"
"<&"
">&"
">|"
"<&-"
">&-"
"<<-"
"<<<"
".."
"!"
] @operator
; Do *not* spell check strings since they typically have some sort of
; interpolation in them, or, are typically used for things like filenames, URLs,
; flags and file content.
[
(string)
(raw_string)
(ansi_c_string)
(heredoc_body)
] @string
[
(heredoc_start)
(heredoc_end)
] @label
(variable_assignment
(word) @string)
(command
argument: "$" @string) ; bare dollar
(concatenation
(word) @string)
[
"if"
"then"
"else"
"elif"
"fi"
"case"
"in"
"esac"
] @keyword.conditional
[
"for"
"do"
"done"
"select"
"until"
"while"
] @keyword.repeat
[
"declare"
"typeset"
"readonly"
"local"
"unset"
"unsetenv"
] @keyword
"export" @keyword.import
"function" @keyword.function
(special_variable_name) @constant
; trap -l
((word) @constant.builtin
(#any-of? @constant.builtin
"SIGHUP" "SIGINT" "SIGQUIT" "SIGILL" "SIGTRAP" "SIGABRT" "SIGBUS" "SIGFPE" "SIGKILL" "SIGUSR1"
"SIGSEGV" "SIGUSR2" "SIGPIPE" "SIGALRM" "SIGTERM" "SIGSTKFLT" "SIGCHLD" "SIGCONT" "SIGSTOP"
"SIGTSTP" "SIGTTIN" "SIGTTOU" "SIGURG" "SIGXCPU" "SIGXFSZ" "SIGVTALRM" "SIGPROF" "SIGWINCH"
"SIGIO" "SIGPWR" "SIGSYS" "SIGRTMIN" "SIGRTMIN+1" "SIGRTMIN+2" "SIGRTMIN+3" "SIGRTMIN+4"
"SIGRTMIN+5" "SIGRTMIN+6" "SIGRTMIN+7" "SIGRTMIN+8" "SIGRTMIN+9" "SIGRTMIN+10" "SIGRTMIN+11"
"SIGRTMIN+12" "SIGRTMIN+13" "SIGRTMIN+14" "SIGRTMIN+15" "SIGRTMAX-14" "SIGRTMAX-13"
"SIGRTMAX-12" "SIGRTMAX-11" "SIGRTMAX-10" "SIGRTMAX-9" "SIGRTMAX-8" "SIGRTMAX-7" "SIGRTMAX-6"
"SIGRTMAX-5" "SIGRTMAX-4" "SIGRTMAX-3" "SIGRTMAX-2" "SIGRTMAX-1" "SIGRTMAX"))
((word) @boolean
(#any-of? @boolean "true" "false"))
(comment) @comment @spell
(test_operator) @operator
(command_substitution
"$(" @punctuation.special
")" @punctuation.special)
(process_substitution
[
"<("
">("
] @punctuation.special
")" @punctuation.special)
(arithmetic_expansion
[
"$(("
"(("
] @punctuation.special
"))" @punctuation.special)
(arithmetic_expansion
"," @punctuation.delimiter)
(ternary_expression
[
"?"
":"
] @keyword.conditional.ternary)
(binary_expression
operator: _ @operator)
(unary_expression
operator: _ @operator)
(postfix_expression
operator: _ @operator)
(function_definition
name: (word) @function)
(command_name
(word) @function.call)
(command_name
(word) @function.builtin
(#any-of? @function.builtin
"." ":" "alias" "bg" "bind" "break" "builtin" "caller" "cd" "command" "compgen" "complete"
"compopt" "continue" "coproc" "dirs" "disown" "echo" "enable" "eval" "exec" "exit" "false" "fc"
"fg" "getopts" "hash" "help" "history" "jobs" "kill" "let" "logout" "mapfile" "popd" "printf"
"pushd" "pwd" "read" "readarray" "return" "set" "shift" "shopt" "source" "suspend" "test" "time"
"times" "trap" "true" "type" "typeset" "ulimit" "umask" "unalias" "wait"))
(command
argument: [
(word) @variable.parameter
(concatenation
(word) @variable.parameter)
])
(declaration_command
(word) @variable.parameter)
(unset_command
(word) @variable.parameter)
(number) @number
((word) @number
(#lua-match? @number "^[0-9]+$"))
(file_redirect
(word) @string.special.path)
(herestring_redirect
(word) @string)
(file_descriptor) @operator
(simple_expansion
"$" @punctuation.special) @none
(expansion
"${" @punctuation.special
"}" @punctuation.special) @none
(expansion
operator: _ @punctuation.special)
(expansion
"@"
.
operator: _ @character.special)
((expansion
(subscript
index: (word) @character.special))
(#any-of? @character.special "@" "*"))
"``" @punctuation.special
(variable_name) @variable
((variable_name) @constant
(#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
((variable_name) @variable.builtin
(#any-of? @variable.builtin
; https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Variables.html
"CDPATH" "HOME" "IFS" "MAIL" "MAILPATH" "OPTARG" "OPTIND" "PATH" "PS1" "PS2"
; https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html
"_" "BASH" "BASHOPTS" "BASHPID" "BASH_ALIASES" "BASH_ARGC" "BASH_ARGV" "BASH_ARGV0" "BASH_CMDS"
"BASH_COMMAND" "BASH_COMPAT" "BASH_ENV" "BASH_EXECUTION_STRING" "BASH_LINENO"
"BASH_LOADABLES_PATH" "BASH_REMATCH" "BASH_SOURCE" "BASH_SUBSHELL" "BASH_VERSINFO"
"BASH_VERSION" "BASH_XTRACEFD" "CHILD_MAX" "COLUMNS" "COMP_CWORD" "COMP_LINE" "COMP_POINT"
"COMP_TYPE" "COMP_KEY" "COMP_WORDBREAKS" "COMP_WORDS" "COMPREPLY" "COPROC" "DIRSTACK" "EMACS"
"ENV" "EPOCHREALTIME" "EPOCHSECONDS" "EUID" "EXECIGNORE" "FCEDIT" "FIGNORE" "FUNCNAME"
"FUNCNEST" "GLOBIGNORE" "GROUPS" "histchars" "HISTCMD" "HISTCONTROL" "HISTFILE" "HISTFILESIZE"
"HISTIGNORE" "HISTSIZE" "HISTTIMEFORMAT" "HOSTFILE" "HOSTNAME" "HOSTTYPE" "IGNOREEOF" "INPUTRC"
"INSIDE_EMACS" "LANG" "LC_ALL" "LC_COLLATE" "LC_CTYPE" "LC_MESSAGES" "LC_NUMERIC" "LC_TIME"
"LINENO" "LINES" "MACHTYPE" "MAILCHECK" "MAPFILE" "OLDPWD" "OPTERR" "OSTYPE" "PIPESTATUS"
"POSIXLY_CORRECT" "PPID" "PROMPT_COMMAND" "PROMPT_DIRTRIM" "PS0" "PS3" "PS4" "PWD" "RANDOM"
"READLINE_ARGUMENT" "READLINE_LINE" "READLINE_MARK" "READLINE_POINT" "REPLY" "SECONDS" "SHELL"
"SHELLOPTS" "SHLVL" "SRANDOM" "TIMEFORMAT" "TMOUT" "TMPDIR" "UID"))
(case_item
value: (word) @variable.parameter)
[
(regex)
(extglob_pattern)
] @string.regexp
((program
.
(comment) @keyword.directive @nospell)
(#lua-match? @keyword.directive "^#!/"))

View File

@ -1,341 +0,0 @@
; Lower priority to prefer @variable.parameter when identifier appears in parameter_declaration.
((identifier) @variable
(#set! priority 95))
(preproc_def
(preproc_arg) @variable)
[
"default"
"goto"
"asm"
"__asm__"
] @keyword
[
"enum"
"struct"
"union"
"typedef"
] @keyword.type
[
"sizeof"
"offsetof"
] @keyword.operator
(alignof_expression
.
_ @keyword.operator)
"return" @keyword.return
[
"while"
"for"
"do"
"continue"
"break"
] @keyword.repeat
[
"if"
"else"
"case"
"switch"
] @keyword.conditional
[
"#if"
"#ifdef"
"#ifndef"
"#else"
"#elif"
"#endif"
"#elifdef"
"#elifndef"
(preproc_directive)
] @keyword.directive
"#define" @keyword.directive.define
"#include" @keyword.import
[
";"
":"
","
"."
"::"
] @punctuation.delimiter
"..." @punctuation.special
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
[
"="
"-"
"*"
"/"
"+"
"%"
"~"
"|"
"&"
"^"
"<<"
">>"
"->"
"<"
"<="
">="
">"
"=="
"!="
"!"
"&&"
"||"
"-="
"+="
"*="
"/="
"%="
"|="
"&="
"^="
">>="
"<<="
"--"
"++"
] @operator
; Make sure the comma operator is given a highlight group after the comma
; punctuator so the operator is highlighted properly.
(comma_expression
"," @operator)
[
(true)
(false)
] @boolean
(conditional_expression
[
"?"
":"
] @keyword.conditional.ternary)
(string_literal) @string
(system_lib_string) @string
(escape_sequence) @string.escape
(null) @constant.builtin
(number_literal) @number
(char_literal) @character
(preproc_defined) @function.macro
((field_expression
(field_identifier) @property) @_parent
(#not-has-parent? @_parent template_method function_declarator call_expression))
(field_designator) @property
((field_identifier) @property
(#has-ancestor? @property field_declaration)
(#not-has-ancestor? @property function_declarator))
(statement_identifier) @label
(declaration
type: (type_identifier) @_type
declarator: (identifier) @label
(#eq? @_type "__label__"))
[
(type_identifier)
(type_descriptor)
] @type
(storage_class_specifier) @keyword.modifier
[
(type_qualifier)
(gnu_asm_qualifier)
"__extension__"
] @keyword.modifier
(linkage_specification
"extern" @keyword.modifier)
(type_definition
declarator: (type_identifier) @type.definition)
(primitive_type) @type.builtin
(sized_type_specifier
_ @type.builtin
type: _?)
((identifier) @constant
(#lua-match? @constant "^[A-Z][A-Z0-9_]+$"))
(preproc_def
(preproc_arg) @constant
(#lua-match? @constant "^[A-Z][A-Z0-9_]+$"))
(enumerator
name: (identifier) @constant)
(case_statement
value: (identifier) @constant)
((identifier) @constant.builtin
; format-ignore
(#any-of? @constant.builtin
"stderr" "stdin" "stdout"
"__FILE__" "__LINE__" "__DATE__" "__TIME__"
"__STDC__" "__STDC_VERSION__" "__STDC_HOSTED__"
"__cplusplus" "__OBJC__" "__ASSEMBLER__"
"__BASE_FILE__" "__FILE_NAME__" "__INCLUDE_LEVEL__"
"__TIMESTAMP__" "__clang__" "__clang_major__"
"__clang_minor__" "__clang_patchlevel__"
"__clang_version__" "__clang_literal_encoding__"
"__clang_wide_literal_encoding__"
"__FUNCTION__" "__func__" "__PRETTY_FUNCTION__"
"__VA_ARGS__" "__VA_OPT__"))
(preproc_def
(preproc_arg) @constant.builtin
; format-ignore
(#any-of? @constant.builtin
"stderr" "stdin" "stdout"
"__FILE__" "__LINE__" "__DATE__" "__TIME__"
"__STDC__" "__STDC_VERSION__" "__STDC_HOSTED__"
"__cplusplus" "__OBJC__" "__ASSEMBLER__"
"__BASE_FILE__" "__FILE_NAME__" "__INCLUDE_LEVEL__"
"__TIMESTAMP__" "__clang__" "__clang_major__"
"__clang_minor__" "__clang_patchlevel__"
"__clang_version__" "__clang_literal_encoding__"
"__clang_wide_literal_encoding__"
"__FUNCTION__" "__func__" "__PRETTY_FUNCTION__"
"__VA_ARGS__" "__VA_OPT__"))
(attribute_specifier
(argument_list
(identifier) @variable.builtin))
(attribute_specifier
(argument_list
(call_expression
function: (identifier) @variable.builtin)))
((call_expression
function: (identifier) @function.builtin)
(#lua-match? @function.builtin "^__builtin_"))
((call_expression
function: (identifier) @function.builtin)
(#has-ancestor? @function.builtin attribute_specifier))
; Preproc def / undef
(preproc_def
name: (_) @constant.macro)
(preproc_call
directive: (preproc_directive) @_u
argument: (_) @constant.macro
(#eq? @_u "#undef"))
(preproc_ifdef
name: (identifier) @constant.macro)
(preproc_elifdef
name: (identifier) @constant.macro)
(preproc_defined
(identifier) @constant.macro)
(call_expression
function: (identifier) @function.call)
(call_expression
function: (field_expression
field: (field_identifier) @function.call))
(function_declarator
declarator: (identifier) @function)
(function_declarator
declarator: (parenthesized_declarator
(pointer_declarator
declarator: (field_identifier) @function)))
(preproc_function_def
name: (identifier) @function.macro)
(comment) @comment @spell
((comment) @comment.documentation
(#lua-match? @comment.documentation "^/[*][*][^*].*[*]/$"))
; Parameters
(parameter_declaration
declarator: (identifier) @variable.parameter)
(parameter_declaration
declarator: (array_declarator) @variable.parameter)
(parameter_declaration
declarator: (pointer_declarator) @variable.parameter)
; K&R functions
; To enable support for K&R functions,
; add the following lines to your own query config and uncomment them.
; They are commented out as they'll conflict with C++
; Note that you'll need to have `; extends` at the top of your query file.
;
; (parameter_list (identifier) @variable.parameter)
;
; (function_definition
; declarator: _
; (declaration
; declarator: (identifier) @variable.parameter))
;
; (function_definition
; declarator: _
; (declaration
; declarator: (array_declarator) @variable.parameter))
;
; (function_definition
; declarator: _
; (declaration
; declarator: (pointer_declarator) @variable.parameter))
(preproc_params
(identifier) @variable.parameter)
[
"__attribute__"
"__declspec"
"__based"
"__cdecl"
"__clrcall"
"__stdcall"
"__fastcall"
"__thiscall"
"__vectorcall"
(ms_pointer_modifier)
(attribute_declaration)
] @attribute

View File

@ -1,608 +0,0 @@
; Lower priority to prefer @variable.parameter when identifier appears in parameter_declaration.
((identifier) @variable
(#set! priority 95))
(preproc_def
(preproc_arg) @variable)
[
"default"
"goto"
"asm"
"__asm__"
] @keyword
[
"enum"
"struct"
"union"
"typedef"
] @keyword.type
[
"sizeof"
"offsetof"
] @keyword.operator
(alignof_expression
.
_ @keyword.operator)
"return" @keyword.return
[
"while"
"for"
"do"
"continue"
"break"
] @keyword.repeat
[
"if"
"else"
"case"
"switch"
] @keyword.conditional
[
"#if"
"#ifdef"
"#ifndef"
"#else"
"#elif"
"#endif"
"#elifdef"
"#elifndef"
(preproc_directive)
] @keyword.directive
"#define" @keyword.directive.define
"#include" @keyword.import
[
";"
":"
","
"."
"::"
] @punctuation.delimiter
"..." @punctuation.special
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
[
"="
"-"
"*"
"/"
"+"
"%"
"~"
"|"
"&"
"^"
"<<"
">>"
"->"
"<"
"<="
">="
">"
"=="
"!="
"!"
"&&"
"||"
"-="
"+="
"*="
"/="
"%="
"|="
"&="
"^="
">>="
"<<="
"--"
"++"
] @operator
; Make sure the comma operator is given a highlight group after the comma
; punctuator so the operator is highlighted properly.
(comma_expression
"," @operator)
[
(true)
(false)
] @boolean
(conditional_expression
[
"?"
":"
] @keyword.conditional.ternary)
(string_literal) @string
(system_lib_string) @string
(escape_sequence) @string.escape
(null) @constant.builtin
(number_literal) @number
(char_literal) @character
(preproc_defined) @function.macro
((field_expression
(field_identifier) @property) @_parent
(#not-has-parent? @_parent template_method function_declarator call_expression))
(field_designator) @property
((field_identifier) @property
(#has-ancestor? @property field_declaration)
(#not-has-ancestor? @property function_declarator))
(statement_identifier) @label
(declaration
type: (type_identifier) @_type
declarator: (identifier) @label
(#eq? @_type "__label__"))
[
(type_identifier)
(type_descriptor)
] @type
(storage_class_specifier) @keyword.modifier
[
(type_qualifier)
(gnu_asm_qualifier)
"__extension__"
] @keyword.modifier
(linkage_specification
"extern" @keyword.modifier)
(type_definition
declarator: (type_identifier) @type.definition)
(primitive_type) @type.builtin
(sized_type_specifier
_ @type.builtin
type: _?)
((identifier) @constant
(#lua-match? @constant "^[A-Z][A-Z0-9_]+$"))
(preproc_def
(preproc_arg) @constant
(#lua-match? @constant "^[A-Z][A-Z0-9_]+$"))
(enumerator
name: (identifier) @constant)
(case_statement
value: (identifier) @constant)
((identifier) @constant.builtin
; format-ignore
(#any-of? @constant.builtin
"stderr" "stdin" "stdout"
"__FILE__" "__LINE__" "__DATE__" "__TIME__"
"__STDC__" "__STDC_VERSION__" "__STDC_HOSTED__"
"__cplusplus" "__OBJC__" "__ASSEMBLER__"
"__BASE_FILE__" "__FILE_NAME__" "__INCLUDE_LEVEL__"
"__TIMESTAMP__" "__clang__" "__clang_major__"
"__clang_minor__" "__clang_patchlevel__"
"__clang_version__" "__clang_literal_encoding__"
"__clang_wide_literal_encoding__"
"__FUNCTION__" "__func__" "__PRETTY_FUNCTION__"
"__VA_ARGS__" "__VA_OPT__"))
(preproc_def
(preproc_arg) @constant.builtin
; format-ignore
(#any-of? @constant.builtin
"stderr" "stdin" "stdout"
"__FILE__" "__LINE__" "__DATE__" "__TIME__"
"__STDC__" "__STDC_VERSION__" "__STDC_HOSTED__"
"__cplusplus" "__OBJC__" "__ASSEMBLER__"
"__BASE_FILE__" "__FILE_NAME__" "__INCLUDE_LEVEL__"
"__TIMESTAMP__" "__clang__" "__clang_major__"
"__clang_minor__" "__clang_patchlevel__"
"__clang_version__" "__clang_literal_encoding__"
"__clang_wide_literal_encoding__"
"__FUNCTION__" "__func__" "__PRETTY_FUNCTION__"
"__VA_ARGS__" "__VA_OPT__"))
(attribute_specifier
(argument_list
(identifier) @variable.builtin))
(attribute_specifier
(argument_list
(call_expression
function: (identifier) @variable.builtin)))
((call_expression
function: (identifier) @function.builtin)
(#lua-match? @function.builtin "^__builtin_"))
((call_expression
function: (identifier) @function.builtin)
(#has-ancestor? @function.builtin attribute_specifier))
; Preproc def / undef
(preproc_def
name: (_) @constant.macro)
(preproc_call
directive: (preproc_directive) @_u
argument: (_) @constant.macro
(#eq? @_u "#undef"))
(preproc_ifdef
name: (identifier) @constant.macro)
(preproc_elifdef
name: (identifier) @constant.macro)
(preproc_defined
(identifier) @constant.macro)
(call_expression
function: (identifier) @function.call)
(call_expression
function: (field_expression
field: (field_identifier) @function.call))
(function_declarator
declarator: (identifier) @function)
(function_declarator
declarator: (parenthesized_declarator
(pointer_declarator
declarator: (field_identifier) @function)))
(preproc_function_def
name: (identifier) @function.macro)
(comment) @comment @spell
((comment) @comment.documentation
(#lua-match? @comment.documentation "^/[*][*][^*].*[*]/$"))
; Parameters
(parameter_declaration
declarator: (identifier) @variable.parameter)
(parameter_declaration
declarator: (array_declarator) @variable.parameter)
(parameter_declaration
declarator: (pointer_declarator) @variable.parameter)
; K&R functions
; To enable support for K&R functions,
; add the following lines to your own query config and uncomment them.
; They are commented out as they'll conflict with C++
; Note that you'll need to have `; extends` at the top of your query file.
;
; (parameter_list (identifier) @variable.parameter)
;
; (function_definition
; declarator: _
; (declaration
; declarator: (identifier) @variable.parameter))
;
; (function_definition
; declarator: _
; (declaration
; declarator: (array_declarator) @variable.parameter))
;
; (function_definition
; declarator: _
; (declaration
; declarator: (pointer_declarator) @variable.parameter))
(preproc_params
(identifier) @variable.parameter)
[
"__attribute__"
"__declspec"
"__based"
"__cdecl"
"__clrcall"
"__stdcall"
"__fastcall"
"__thiscall"
"__vectorcall"
(ms_pointer_modifier)
(attribute_declaration)
] @attribute
((identifier) @variable.member
(#lua-match? @variable.member "^m_.*$"))
(parameter_declaration
declarator: (reference_declarator) @variable.parameter)
; function(Foo ...foo)
(variadic_parameter_declaration
declarator: (variadic_declarator
(_) @variable.parameter))
; int foo = 0
(optional_parameter_declaration
declarator: (_) @variable.parameter)
;(field_expression) @variable.parameter ;; How to highlight this?
((field_expression
(field_identifier) @function.method) @_parent
(#has-parent? @_parent template_method function_declarator))
(field_declaration
(field_identifier) @variable.member)
(field_initializer
(field_identifier) @property)
(function_declarator
declarator: (field_identifier) @function.method)
(concept_definition
name: (identifier) @type.definition)
(alias_declaration
name: (type_identifier) @type.definition)
(auto) @type.builtin
(namespace_identifier) @module
((namespace_identifier) @type
(#lua-match? @type "^[%u]"))
(case_statement
value: (qualified_identifier
(identifier) @constant))
(using_declaration
.
"using"
.
"namespace"
.
[
(qualified_identifier)
(identifier)
] @module)
(destructor_name
(identifier) @function.method)
; functions
(function_declarator
(qualified_identifier
(identifier) @function))
(function_declarator
(qualified_identifier
(qualified_identifier
(identifier) @function)))
(function_declarator
(qualified_identifier
(qualified_identifier
(qualified_identifier
(identifier) @function))))
((qualified_identifier
(qualified_identifier
(qualified_identifier
(qualified_identifier
(identifier) @function)))) @_parent
(#has-ancestor? @_parent function_declarator))
(function_declarator
(template_function
(identifier) @function))
(operator_name) @function
"operator" @function
"static_assert" @function.builtin
(call_expression
(qualified_identifier
(identifier) @function.call))
(call_expression
(qualified_identifier
(qualified_identifier
(identifier) @function.call)))
(call_expression
(qualified_identifier
(qualified_identifier
(qualified_identifier
(identifier) @function.call))))
((qualified_identifier
(qualified_identifier
(qualified_identifier
(qualified_identifier
(identifier) @function.call)))) @_parent
(#has-ancestor? @_parent call_expression))
(call_expression
(template_function
(identifier) @function.call))
(call_expression
(qualified_identifier
(template_function
(identifier) @function.call)))
(call_expression
(qualified_identifier
(qualified_identifier
(template_function
(identifier) @function.call))))
(call_expression
(qualified_identifier
(qualified_identifier
(qualified_identifier
(template_function
(identifier) @function.call)))))
((qualified_identifier
(qualified_identifier
(qualified_identifier
(qualified_identifier
(template_function
(identifier) @function.call))))) @_parent
(#has-ancestor? @_parent call_expression))
; methods
(function_declarator
(template_method
(field_identifier) @function.method))
(call_expression
(field_expression
(field_identifier) @function.method.call))
; constructors
((function_declarator
(qualified_identifier
(identifier) @constructor))
(#lua-match? @constructor "^%u"))
((call_expression
function: (identifier) @constructor)
(#lua-match? @constructor "^%u"))
((call_expression
function: (qualified_identifier
name: (identifier) @constructor))
(#lua-match? @constructor "^%u"))
((call_expression
function: (field_expression
field: (field_identifier) @constructor))
(#lua-match? @constructor "^%u"))
; constructing a type in an initializer list: Constructor (): **SuperType (1)**
((field_initializer
(field_identifier) @constructor
(argument_list))
(#lua-match? @constructor "^%u"))
; Constants
(this) @variable.builtin
(null
"nullptr" @constant.builtin)
(true) @boolean
(false) @boolean
; Literals
(raw_string_literal) @string
; Keywords
[
"try"
"catch"
"noexcept"
"throw"
] @keyword.exception
[
"decltype"
"explicit"
"friend"
"override"
"using"
"requires"
"constexpr"
] @keyword
[
"class"
"namespace"
"template"
"typename"
"concept"
] @keyword.type
[
"co_await"
"co_yield"
"co_return"
] @keyword.coroutine
[
"public"
"private"
"protected"
"final"
"virtual"
] @keyword.modifier
[
"new"
"delete"
"xor"
"bitand"
"bitor"
"compl"
"not"
"xor_eq"
"and_eq"
"or_eq"
"not_eq"
"and"
"or"
] @keyword.operator
"<=>" @operator
"::" @punctuation.delimiter
(template_argument_list
[
"<"
">"
] @punctuation.bracket)
(template_parameter_list
[
"<"
">"
] @punctuation.bracket)
(literal_suffix) @operator

View File

@ -1,577 +0,0 @@
[
(identifier)
(preproc_arg)
] @variable
((preproc_arg) @constant.macro
(#lua-match? @constant.macro "^[_A-Z][_A-Z0-9]*$"))
((identifier) @keyword
(#eq? @keyword "value")
(#has-ancestor? @keyword accessor_declaration))
(method_declaration
name: (identifier) @function.method)
(local_function_statement
name: (identifier) @function.method)
(method_declaration
returns: [
(identifier) @type
(generic_name
(identifier) @type)
])
(event_declaration
type: (identifier) @type)
(event_declaration
name: (identifier) @variable.member)
(event_field_declaration
(variable_declaration
(variable_declarator
name: (identifier) @variable.member)))
(declaration_pattern
type: (identifier) @type)
(local_function_statement
type: (identifier) @type)
(interpolation) @none
(member_access_expression
name: (identifier) @variable.member)
(invocation_expression
(member_access_expression
name: (identifier) @function.method.call))
(invocation_expression
function: (conditional_access_expression
(member_binding_expression
name: (identifier) @function.method.call)))
(namespace_declaration
name: [
(qualified_name)
(identifier)
] @module)
(qualified_name
(identifier) @type)
(namespace_declaration
name: (identifier) @module)
(file_scoped_namespace_declaration
name: (identifier) @module)
(qualified_name
(identifier) @module
(#not-has-ancestor? @module method_declaration)
(#not-has-ancestor? @module record_declaration)
(#has-ancestor? @module namespace_declaration file_scoped_namespace_declaration))
(invocation_expression
(identifier) @function.method.call)
(field_declaration
(variable_declaration
(variable_declarator
(identifier) @variable.member)))
(initializer_expression
(assignment_expression
left: (identifier) @variable.member))
(parameter
name: (identifier) @variable.parameter)
(parameter_list
name: (identifier) @variable.parameter)
(bracketed_parameter_list
name: (identifier) @variable.parameter)
(implicit_parameter) @variable.parameter
(parameter_list
(parameter
type: (identifier) @type))
(integer_literal) @number
(real_literal) @number.float
(null_literal) @constant.builtin
(calling_convention
[
(identifier)
"Cdecl"
"Stdcall"
"Thiscall"
"Fastcall"
] @attribute.builtin)
(character_literal) @character
[
(string_literal)
(raw_string_literal)
(verbatim_string_literal)
(interpolated_string_expression)
] @string
(escape_sequence) @string.escape
[
"true"
"false"
] @boolean
(predefined_type) @type.builtin
(implicit_type) @keyword
(comment) @comment @spell
((comment) @comment.documentation
(#lua-match? @comment.documentation "^/[*][*][^*].*[*]/$"))
((comment) @comment.documentation
(#lua-match? @comment.documentation "^///[^/]"))
((comment) @comment.documentation
(#lua-match? @comment.documentation "^///$"))
(using_directive
(identifier) @type)
(using_directive
(type) @type.definition)
(property_declaration
name: (identifier) @property)
(property_declaration
type: (identifier) @type)
(nullable_type
type: (identifier) @type)
(array_type
type: (identifier) @type)
(ref_type
type: (identifier) @type)
(pointer_type
type: (identifier) @type)
(catch_declaration
type: (identifier) @type)
(interface_declaration
name: (identifier) @type)
(class_declaration
name: (identifier) @type)
(record_declaration
name: (identifier) @type)
(struct_declaration
name: (identifier) @type)
(enum_declaration
name: (identifier) @type)
(enum_member_declaration
name: (identifier) @variable.member)
(operator_declaration
type: (identifier) @type)
(conversion_operator_declaration
type: (identifier) @type)
(explicit_interface_specifier
[
(identifier) @type
(generic_name
(identifier) @type)
])
(explicit_interface_specifier
(identifier) @type)
(primary_constructor_base_type
type: (identifier) @type)
[
"assembly"
"module"
"this"
"base"
] @variable.builtin
(constructor_declaration
name: (identifier) @constructor)
(destructor_declaration
name: (identifier) @constructor)
(constructor_initializer
"base" @constructor)
(variable_declaration
(identifier) @type)
(object_creation_expression
(identifier) @type)
; Generic Types.
(typeof_expression
(generic_name
(identifier) @type))
(type_argument_list
(generic_name
(identifier) @type))
(base_list
(generic_name
(identifier) @type))
(type_parameter_constraint
[
(identifier) @type
(type
(generic_name
(identifier) @type))
])
(object_creation_expression
(generic_name
(identifier) @type))
(property_declaration
(generic_name
(identifier) @type))
(_
type: (generic_name
(identifier) @type))
; Generic Method invocation with generic type
(invocation_expression
function: (generic_name
.
(identifier) @function.method.call))
(invocation_expression
(member_access_expression
(generic_name
(identifier) @function.method)))
(base_list
(identifier) @type)
(type_argument_list
(identifier) @type)
(type_parameter_list
(type_parameter) @type)
(type_parameter
name: (identifier) @type)
(type_parameter_constraints_clause
"where"
.
(identifier) @type)
(attribute
name: (identifier) @attribute)
(foreach_statement
type: (identifier) @type)
(goto_statement
(identifier) @label)
(labeled_statement
(identifier) @label)
(tuple_element
type: (identifier) @type)
(tuple_expression
(argument
(declaration_expression
type: (identifier) @type)))
(cast_expression
type: (identifier) @type)
(lambda_expression
type: (identifier) @type)
(as_expression
right: (identifier) @type)
(typeof_expression
(identifier) @type)
(preproc_error) @keyword.exception
[
"#define"
"#undef"
] @keyword.directive.define
[
"#if"
"#elif"
"#else"
"#endif"
"#region"
"#endregion"
"#line"
"#pragma"
"#nullable"
"#error"
(shebang_directive)
] @keyword.directive
[
(preproc_line)
(preproc_pragma)
(preproc_nullable)
] @constant.macro
(preproc_pragma
(identifier) @constant)
(preproc_if
(identifier) @constant)
[
"if"
"else"
"switch"
"break"
"case"
"when"
] @keyword.conditional
[
"while"
"for"
"do"
"continue"
"goto"
"foreach"
] @keyword.repeat
[
"try"
"catch"
"throw"
"finally"
] @keyword.exception
[
"+"
"?"
":"
"++"
"-"
"--"
"&"
"&&"
"|"
"||"
"!"
"!="
"=="
"*"
"/"
"%"
"<"
"<="
">"
">="
"="
"-="
"+="
"*="
"/="
"%="
"^"
"^="
"&="
"|="
"~"
">>"
">>>"
"<<"
"<<="
">>="
">>>="
"=>"
"??"
"??="
".."
] @operator
(list_pattern
".." @character.special)
(discard) @character.special
[
";"
"."
","
":"
] @punctuation.delimiter
(conditional_expression
[
"?"
":"
] @keyword.conditional.ternary)
[
"["
"]"
"{"
"}"
"("
")"
] @punctuation.bracket
(interpolation_brace) @punctuation.special
(type_argument_list
[
"<"
">"
] @punctuation.bracket)
[
"using"
"as"
] @keyword.import
(alias_qualified_name
(identifier
"global") @keyword.import)
[
"with"
"new"
"typeof"
"sizeof"
"is"
"and"
"or"
"not"
"stackalloc"
"__makeref"
"__reftype"
"__refvalue"
"in"
"out"
"ref"
] @keyword.operator
[
"lock"
"params"
"operator"
"default"
"implicit"
"explicit"
"override"
"get"
"set"
"init"
"where"
"add"
"remove"
"checked"
"unchecked"
"fixed"
"alias"
"file"
"unsafe"
] @keyword
(attribute_target_specifier
.
_ @keyword)
[
"enum"
"record"
"class"
"struct"
"interface"
"namespace"
"event"
"delegate"
] @keyword.type
[
"async"
"await"
] @keyword.coroutine
[
"const"
"extern"
"readonly"
"static"
"volatile"
"required"
"managed"
"unmanaged"
"notnull"
"abstract"
"private"
"protected"
"internal"
"public"
"partial"
"sealed"
"virtual"
"global"
] @keyword.modifier
(scoped_type
"scoped" @keyword.modifier)
(query_expression
(_
[
"from"
"orderby"
"select"
"group"
"by"
"ascending"
"descending"
"equals"
"let"
] @keyword))
[
"return"
"yield"
] @keyword.return

View File

@ -1,109 +0,0 @@
[
"@media"
"@charset"
"@namespace"
"@supports"
"@keyframes"
(at_keyword)
] @keyword.directive
"@import" @keyword.import
[
(to)
(from)
] @keyword
(comment) @comment @spell
(tag_name) @tag
(class_name) @type
(id_name) @constant
[
(property_name)
(feature_name)
] @property
[
(nesting_selector)
(universal_selector)
] @character.special
(function_name) @function
[
"~"
">"
"+"
"-"
"*"
"/"
"="
"^="
"|="
"~="
"$="
"*="
] @operator
[
"and"
"or"
"not"
"only"
] @keyword.operator
(important) @keyword.modifier
(attribute_selector
(plain_value) @string)
(pseudo_element_selector
"::"
(tag_name) @attribute)
(pseudo_class_selector
(class_name) @attribute)
(attribute_name) @tag.attribute
(namespace_name) @module
(keyframes_name) @variable
((property_name) @variable
(#lua-match? @variable "^[-][-]"))
((plain_value) @variable
(#lua-match? @variable "^[-][-]"))
[
(string_value)
(color_value)
(unit)
] @string
(integer_value) @number
(float_value) @number.float
[
"#"
","
"."
":"
"::"
";"
] @punctuation.delimiter
[
"{"
")"
"("
"}"
"["
"]"
] @punctuation.bracket

View File

@ -1,254 +0,0 @@
; Forked from tree-sitter-go
; Copyright (c) 2014 Max Brunsfeld (The MIT License)
;
; Identifiers
(type_identifier) @type
(type_spec
name: (type_identifier) @type.definition)
(field_identifier) @property
(identifier) @variable
(package_identifier) @module
(parameter_declaration
(identifier) @variable.parameter)
(variadic_parameter_declaration
(identifier) @variable.parameter)
(label_name) @label
(const_spec
name: (identifier) @constant)
; Function calls
(call_expression
function: (identifier) @function.call)
(call_expression
function: (selector_expression
field: (field_identifier) @function.method.call))
; Function definitions
(function_declaration
name: (identifier) @function)
(method_declaration
name: (field_identifier) @function.method)
(method_elem
name: (field_identifier) @function.method)
; Constructors
((call_expression
(identifier) @constructor)
(#lua-match? @constructor "^[nN]ew.+$"))
((call_expression
(identifier) @constructor)
(#lua-match? @constructor "^[mM]ake.+$"))
; Operators
[
"--"
"-"
"-="
":="
"!"
"!="
"..."
"*"
"*"
"*="
"/"
"/="
"&"
"&&"
"&="
"&^"
"&^="
"%"
"%="
"^"
"^="
"+"
"++"
"+="
"<-"
"<"
"<<"
"<<="
"<="
"="
"=="
">"
">="
">>"
">>="
"|"
"|="
"||"
"~"
] @operator
; Keywords
[
"break"
"const"
"continue"
"default"
"defer"
"goto"
"range"
"select"
"var"
"fallthrough"
] @keyword
[
"type"
"struct"
"interface"
] @keyword.type
"func" @keyword.function
"return" @keyword.return
"go" @keyword.coroutine
"for" @keyword.repeat
[
"import"
"package"
] @keyword.import
[
"else"
"case"
"switch"
"if"
] @keyword.conditional
; Builtin types
[
"chan"
"map"
] @type.builtin
((type_identifier) @type.builtin
(#any-of? @type.builtin
"any" "bool" "byte" "comparable" "complex128" "complex64" "error" "float32" "float64" "int"
"int16" "int32" "int64" "int8" "rune" "string" "uint" "uint16" "uint32" "uint64" "uint8"
"uintptr"))
; Builtin functions
((identifier) @function.builtin
(#any-of? @function.builtin
"append" "cap" "clear" "close" "complex" "copy" "delete" "imag" "len" "make" "max" "min" "new"
"panic" "print" "println" "real" "recover"))
; Delimiters
"." @punctuation.delimiter
"," @punctuation.delimiter
":" @punctuation.delimiter
";" @punctuation.delimiter
"(" @punctuation.bracket
")" @punctuation.bracket
"{" @punctuation.bracket
"}" @punctuation.bracket
"[" @punctuation.bracket
"]" @punctuation.bracket
; Literals
(interpreted_string_literal) @string
(raw_string_literal) @string
(rune_literal) @string
(escape_sequence) @string.escape
(int_literal) @number
(float_literal) @number.float
(imaginary_literal) @number
[
(true)
(false)
] @boolean
[
(nil)
(iota)
] @constant.builtin
(keyed_element
.
(literal_element
(identifier) @variable.member))
(field_declaration
name: (field_identifier) @variable.member)
; Comments
(comment) @comment @spell
; Doc Comments
(source_file
.
(comment)+ @comment.documentation)
(source_file
(comment)+ @comment.documentation
.
(const_declaration))
(source_file
(comment)+ @comment.documentation
.
(function_declaration))
(source_file
(comment)+ @comment.documentation
.
(type_declaration))
(source_file
(comment)+ @comment.documentation
.
(var_declaration))
; Spell
((interpreted_string_literal) @spell
(#not-has-parent? @spell import_spec))
; Regex
(call_expression
(selector_expression) @_function
(#any-of? @_function
"regexp.Match" "regexp.MatchReader" "regexp.MatchString" "regexp.Compile" "regexp.CompilePOSIX"
"regexp.MustCompile" "regexp.MustCompilePOSIX")
(argument_list
.
[
(raw_string_literal
(raw_string_literal_content) @string.regexp)
(interpreted_string_literal
(interpreted_string_literal_content) @string.regexp)
]))

View File

@ -1,105 +0,0 @@
(tag_name) @tag
; (erroneous_end_tag_name) @error ; we do not lint syntax errors
(comment) @comment @spell
(attribute_name) @tag.attribute
((attribute
(quoted_attribute_value) @string)
(#set! priority 99))
(text) @none @spell
((element
(start_tag
(tag_name) @_tag)
(text) @markup.heading)
(#eq? @_tag "title"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.heading.1)
(#eq? @_tag "h1"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.heading.2)
(#eq? @_tag "h2"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.heading.3)
(#eq? @_tag "h3"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.heading.4)
(#eq? @_tag "h4"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.heading.5)
(#eq? @_tag "h5"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.heading.6)
(#eq? @_tag "h6"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.strong)
(#any-of? @_tag "strong" "b"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.italic)
(#any-of? @_tag "em" "i"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.strikethrough)
(#any-of? @_tag "s" "del"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.underline)
(#eq? @_tag "u"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.raw)
(#any-of? @_tag "code" "kbd"))
((element
(start_tag
(tag_name) @_tag)
(text) @markup.link.label)
(#eq? @_tag "a"))
((attribute
(attribute_name) @_attr
(quoted_attribute_value
(attribute_value) @string.special.url))
(#any-of? @_attr "href" "src"))
[
"<"
">"
"</"
"/>"
] @tag.delimiter
"=" @operator

View File

@ -1,330 +0,0 @@
; CREDITS @maxbrunsfeld (maxbrunsfeld@gmail.com)
; Variables
(identifier) @variable
(underscore_pattern) @character.special
; Methods
(method_declaration
name: (identifier) @function.method)
(method_invocation
name: (identifier) @function.method.call)
(super) @function.builtin
; Parameters
(formal_parameter
name: (identifier) @variable.parameter)
(spread_parameter
(variable_declarator
name: (identifier) @variable.parameter)) ; int... foo
; Lambda parameter
(inferred_parameters
(identifier) @variable.parameter) ; (x,y) -> ...
(lambda_expression
parameters: (identifier) @variable.parameter) ; x -> ...
; Operators
[
"+"
":"
"++"
"-"
"--"
"&"
"&&"
"|"
"||"
"!"
"!="
"=="
"*"
"/"
"%"
"<"
"<="
">"
">="
"="
"-="
"+="
"*="
"/="
"%="
"->"
"^"
"^="
"&="
"|="
"~"
">>"
">>>"
"<<"
"::"
] @operator
; Types
(interface_declaration
name: (identifier) @type)
(annotation_type_declaration
name: (identifier) @type)
(class_declaration
name: (identifier) @type)
(record_declaration
name: (identifier) @type)
(enum_declaration
name: (identifier) @type)
(constructor_declaration
name: (identifier) @type)
(compact_constructor_declaration
name: (identifier) @type)
(type_identifier) @type
((type_identifier) @type.builtin
(#eq? @type.builtin "var"))
((method_invocation
object: (identifier) @type)
(#lua-match? @type "^[A-Z]"))
((method_reference
.
(identifier) @type)
(#lua-match? @type "^[A-Z]"))
((field_access
object: (identifier) @type)
(#lua-match? @type "^[A-Z]"))
(scoped_identifier
(identifier) @type
(#lua-match? @type "^[A-Z]"))
; Fields
(field_declaration
declarator: (variable_declarator
name: (identifier) @variable.member))
(field_access
field: (identifier) @variable.member)
[
(boolean_type)
(integral_type)
(floating_point_type)
(void_type)
] @type.builtin
; Variables
((identifier) @constant
(#lua-match? @constant "^[A-Z_][A-Z%d_]+$"))
(this) @variable.builtin
; Annotations
(annotation
"@" @attribute
name: (identifier) @attribute)
(marker_annotation
"@" @attribute
name: (identifier) @attribute)
; Literals
(string_literal) @string
(escape_sequence) @string.escape
(character_literal) @character
[
(hex_integer_literal)
(decimal_integer_literal)
(octal_integer_literal)
(binary_integer_literal)
] @number
[
(decimal_floating_point_literal)
(hex_floating_point_literal)
] @number.float
[
(true)
(false)
] @boolean
(null_literal) @constant.builtin
; Keywords
[
"assert"
"default"
"extends"
"implements"
"instanceof"
"@interface"
"permits"
"to"
"with"
] @keyword
[
"record"
"class"
"enum"
"interface"
] @keyword.type
(synchronized_statement
"synchronized" @keyword)
[
"abstract"
"final"
"native"
"non-sealed"
"open"
"private"
"protected"
"public"
"sealed"
"static"
"strictfp"
"transitive"
] @keyword.modifier
(modifiers
"synchronized" @keyword.modifier)
[
"transient"
"volatile"
] @keyword.modifier
[
"return"
"yield"
] @keyword.return
"new" @keyword.operator
; Conditionals
[
"if"
"else"
"switch"
"case"
"when"
] @keyword.conditional
(ternary_expression
[
"?"
":"
] @keyword.conditional.ternary)
; Loops
[
"for"
"while"
"do"
"continue"
"break"
] @keyword.repeat
; Includes
[
"exports"
"import"
"module"
"opens"
"package"
"provides"
"requires"
"uses"
] @keyword.import
(import_declaration
(asterisk
"*" @character.special))
; Punctuation
[
";"
"."
"..."
","
] @punctuation.delimiter
[
"{"
"}"
] @punctuation.bracket
[
"["
"]"
] @punctuation.bracket
[
"("
")"
] @punctuation.bracket
(type_arguments
[
"<"
">"
] @punctuation.bracket)
(type_parameters
[
"<"
">"
] @punctuation.bracket)
(string_interpolation
[
"\\{"
"}"
] @punctuation.special)
; Exceptions
[
"throw"
"throws"
"finally"
"try"
"catch"
] @keyword.exception
; Labels
(labeled_statement
(identifier) @label)
; Comments
[
(line_comment)
(block_comment)
] @comment @spell
((block_comment) @comment.documentation
(#lua-match? @comment.documentation "^/[*][*][^*].*[*]/$"))
((line_comment) @comment.documentation
(#lua-match? @comment.documentation "^///[^/]"))
((line_comment) @comment.documentation
(#lua-match? @comment.documentation "^///$"))

View File

@ -1,605 +0,0 @@
; Types
; Javascript
; Variables
;-----------
(identifier) @variable
; Properties
;-----------
(property_identifier) @variable.member
(shorthand_property_identifier) @variable.member
(private_property_identifier) @variable.member
(object_pattern
(shorthand_property_identifier_pattern) @variable)
(object_pattern
(object_assignment_pattern
(shorthand_property_identifier_pattern) @variable))
; Special identifiers
;--------------------
((identifier) @type
(#lua-match? @type "^[A-Z]"))
((identifier) @constant
(#lua-match? @constant "^_*[A-Z][A-Z%d_]*$"))
((shorthand_property_identifier) @constant
(#lua-match? @constant "^_*[A-Z][A-Z%d_]*$"))
((identifier) @variable.builtin
(#any-of? @variable.builtin "arguments" "module" "console" "window" "document"))
((identifier) @type.builtin
(#any-of? @type.builtin
"Object" "Function" "Boolean" "Symbol" "Number" "Math" "Date" "String" "RegExp" "Map" "Set"
"WeakMap" "WeakSet" "Promise" "Array" "Int8Array" "Uint8Array" "Uint8ClampedArray" "Int16Array"
"Uint16Array" "Int32Array" "Uint32Array" "Float32Array" "Float64Array" "ArrayBuffer" "DataView"
"Error" "EvalError" "InternalError" "RangeError" "ReferenceError" "SyntaxError" "TypeError"
"URIError"))
(statement_identifier) @label
; Function and method definitions
;--------------------------------
(function_expression
name: (identifier) @function)
(function_declaration
name: (identifier) @function)
(generator_function
name: (identifier) @function)
(generator_function_declaration
name: (identifier) @function)
(method_definition
name: [
(property_identifier)
(private_property_identifier)
] @function.method)
(method_definition
name: (property_identifier) @constructor
(#eq? @constructor "constructor"))
(pair
key: (property_identifier) @function.method
value: (function_expression))
(pair
key: (property_identifier) @function.method
value: (arrow_function))
(assignment_expression
left: (member_expression
property: (property_identifier) @function.method)
right: (arrow_function))
(assignment_expression
left: (member_expression
property: (property_identifier) @function.method)
right: (function_expression))
(variable_declarator
name: (identifier) @function
value: (arrow_function))
(variable_declarator
name: (identifier) @function
value: (function_expression))
(assignment_expression
left: (identifier) @function
right: (arrow_function))
(assignment_expression
left: (identifier) @function
right: (function_expression))
; Function and method calls
;--------------------------
(call_expression
function: (identifier) @function.call)
(call_expression
function: (member_expression
property: [
(property_identifier)
(private_property_identifier)
] @function.method.call))
(call_expression
function: (await_expression
(identifier) @function.call))
(call_expression
function: (await_expression
(member_expression
property: [
(property_identifier)
(private_property_identifier)
] @function.method.call)))
; Builtins
;---------
((identifier) @module.builtin
(#eq? @module.builtin "Intl"))
((identifier) @function.builtin
(#any-of? @function.builtin
"eval" "isFinite" "isNaN" "parseFloat" "parseInt" "decodeURI" "decodeURIComponent" "encodeURI"
"encodeURIComponent" "require"))
; Constructor
;------------
(new_expression
constructor: (identifier) @constructor)
; Decorators
;----------
(decorator
"@" @attribute
(identifier) @attribute)
(decorator
"@" @attribute
(call_expression
(identifier) @attribute))
(decorator
"@" @attribute
(member_expression
(property_identifier) @attribute))
(decorator
"@" @attribute
(call_expression
(member_expression
(property_identifier) @attribute)))
; Literals
;---------
[
(this)
(super)
] @variable.builtin
((identifier) @variable.builtin
(#eq? @variable.builtin "self"))
[
(true)
(false)
] @boolean
[
(null)
(undefined)
] @constant.builtin
[
(comment)
(html_comment)
] @comment @spell
((comment) @comment.documentation
(#lua-match? @comment.documentation "^/[*][*][^*].*[*]/$"))
(hash_bang_line) @keyword.directive
((string_fragment) @keyword.directive
(#eq? @keyword.directive "use strict"))
(string) @string
(template_string) @string
(escape_sequence) @string.escape
(regex_pattern) @string.regexp
(regex_flags) @character.special
(regex
"/" @punctuation.bracket) ; Regex delimiters
(number) @number
((identifier) @number
(#any-of? @number "NaN" "Infinity"))
; Punctuation
;------------
[
";"
"."
","
":"
] @punctuation.delimiter
[
"--"
"-"
"-="
"&&"
"+"
"++"
"+="
"&="
"/="
"**="
"<<="
"<"
"<="
"<<"
"="
"=="
"==="
"!="
"!=="
"=>"
">"
">="
">>"
"||"
"%"
"%="
"*"
"**"
">>>"
"&"
"|"
"^"
"??"
"*="
">>="
">>>="
"^="
"|="
"&&="
"||="
"??="
"..."
] @operator
(binary_expression
"/" @operator)
(ternary_expression
[
"?"
":"
] @keyword.conditional.ternary)
(unary_expression
[
"!"
"~"
"-"
"+"
] @operator)
(unary_expression
[
"delete"
"void"
] @keyword.operator)
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
(template_substitution
[
"${"
"}"
] @punctuation.special) @none
; Imports
;----------
(namespace_import
"*" @character.special
(identifier) @module)
(namespace_export
"*" @character.special
(identifier) @module)
(export_statement
"*" @character.special)
; Keywords
;----------
[
"if"
"else"
"switch"
"case"
] @keyword.conditional
[
"import"
"from"
"as"
"export"
] @keyword.import
[
"for"
"of"
"do"
"while"
"continue"
] @keyword.repeat
[
"break"
"const"
"debugger"
"extends"
"get"
"let"
"set"
"static"
"target"
"var"
"with"
] @keyword
"class" @keyword.type
[
"async"
"await"
] @keyword.coroutine
[
"return"
"yield"
] @keyword.return
"function" @keyword.function
[
"new"
"delete"
"in"
"instanceof"
"typeof"
] @keyword.operator
[
"throw"
"try"
"catch"
"finally"
] @keyword.exception
(export_statement
"default" @keyword)
(switch_default
"default" @keyword.conditional)
(jsx_element
open_tag: (jsx_opening_element
[
"<"
">"
] @tag.delimiter))
(jsx_element
close_tag: (jsx_closing_element
[
"</"
">"
] @tag.delimiter))
(jsx_self_closing_element
[
"<"
"/>"
] @tag.delimiter)
(jsx_attribute
(property_identifier) @tag.attribute)
(jsx_opening_element
name: (identifier) @tag.builtin)
(jsx_closing_element
name: (identifier) @tag.builtin)
(jsx_self_closing_element
name: (identifier) @tag.builtin)
(jsx_opening_element
((identifier) @tag
(#lua-match? @tag "^[A-Z]")))
; Handle the dot operator effectively - <My.Component>
(jsx_opening_element
(member_expression
(identifier) @tag.builtin
(property_identifier) @tag))
(jsx_closing_element
((identifier) @tag
(#lua-match? @tag "^[A-Z]")))
; Handle the dot operator effectively - </My.Component>
(jsx_closing_element
(member_expression
(identifier) @tag.builtin
(property_identifier) @tag))
(jsx_self_closing_element
((identifier) @tag
(#lua-match? @tag "^[A-Z]")))
; Handle the dot operator effectively - <My.Component />
(jsx_self_closing_element
(member_expression
(identifier) @tag.builtin
(property_identifier) @tag))
(html_character_reference) @tag
(jsx_text) @none @spell
(html_character_reference) @character.special
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading)
(#eq? @_tag "title"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.1)
(#eq? @_tag "h1"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.2)
(#eq? @_tag "h2"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.3)
(#eq? @_tag "h3"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.4)
(#eq? @_tag "h4"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.5)
(#eq? @_tag "h5"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.6)
(#eq? @_tag "h6"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.strong)
(#any-of? @_tag "strong" "b"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.italic)
(#any-of? @_tag "em" "i"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.strikethrough)
(#any-of? @_tag "s" "del"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.underline)
(#eq? @_tag "u"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.raw)
(#any-of? @_tag "code" "kbd"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.link.label)
(#eq? @_tag "a"))
((jsx_attribute
(property_identifier) @_attr
(string
(string_fragment) @string.special.url))
(#any-of? @_attr "href" "src"))
((jsx_element) @_jsx_element
(#set! @_jsx_element bo.commentstring "{/* %s */}"))
((jsx_attribute) @_jsx_attribute
(#set! @_jsx_attribute bo.commentstring "// %s"))
; Parameters
(formal_parameters
(identifier) @variable.parameter)
(formal_parameters
(rest_pattern
(identifier) @variable.parameter))
; ({ a }) => null
(formal_parameters
(object_pattern
(shorthand_property_identifier_pattern) @variable.parameter))
; ({ a = b }) => null
(formal_parameters
(object_pattern
(object_assignment_pattern
(shorthand_property_identifier_pattern) @variable.parameter)))
; ({ a: b }) => null
(formal_parameters
(object_pattern
(pair_pattern
value: (identifier) @variable.parameter)))
; ([ a ]) => null
(formal_parameters
(array_pattern
(identifier) @variable.parameter))
; ({ a } = { a }) => null
(formal_parameters
(assignment_pattern
(object_pattern
(shorthand_property_identifier_pattern) @variable.parameter)))
; ({ a = b } = { a }) => null
(formal_parameters
(assignment_pattern
(object_pattern
(object_assignment_pattern
(shorthand_property_identifier_pattern) @variable.parameter))))
; a => null
(arrow_function
parameter: (identifier) @variable.parameter)
; optional parameters
(formal_parameters
(assignment_pattern
left: (identifier) @variable.parameter))
; punctuation
(optional_chain) @punctuation.delimiter

View File

@ -1,38 +0,0 @@
[
(true)
(false)
] @boolean
(null) @constant.builtin
(number) @number
(pair
key: (string) @property)
(pair
value: (string) @string)
(array
(string) @string)
[
","
":"
] @punctuation.delimiter
[
"["
"]"
"{"
"}"
] @punctuation.bracket
("\"" @conceal
(#set! conceal ""))
(escape_sequence) @string.escape
((escape_sequence) @conceal
(#eq? @conceal "\\\"")
(#set! conceal "\""))

View File

@ -1,443 +0,0 @@
; From tree-sitter-python licensed under MIT License
; Copyright (c) 2016 Max Brunsfeld
; Variables
(identifier) @variable
; Reset highlighting in f-string interpolations
(interpolation) @none
; Identifier naming conventions
((identifier) @type
(#lua-match? @type "^[A-Z].*[a-z]"))
((identifier) @constant
(#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
((identifier) @constant.builtin
(#lua-match? @constant.builtin "^__[a-zA-Z0-9_]*__$"))
((identifier) @constant.builtin
(#any-of? @constant.builtin
; https://docs.python.org/3/library/constants.html
"NotImplemented" "Ellipsis" "quit" "exit" "copyright" "credits" "license"))
"_" @character.special ; match wildcard
((assignment
left: (identifier) @type.definition
(type
(identifier) @_annotation))
(#eq? @_annotation "TypeAlias"))
((assignment
left: (identifier) @type.definition
right: (call
function: (identifier) @_func))
(#any-of? @_func "TypeVar" "NewType"))
; Function definitions
(function_definition
name: (identifier) @function)
(type
(identifier) @type)
(type
(subscript
(identifier) @type)) ; type subscript: Tuple[int]
((call
function: (identifier) @_isinstance
arguments: (argument_list
(_)
(identifier) @type))
(#eq? @_isinstance "isinstance"))
; Literals
(none) @constant.builtin
[
(true)
(false)
] @boolean
(integer) @number
(float) @number.float
(comment) @comment @spell
((module
.
(comment) @keyword.directive @nospell)
(#lua-match? @keyword.directive "^#!/"))
(string) @string
[
(escape_sequence)
(escape_interpolation)
] @string.escape
; doc-strings
(expression_statement
(string
(string_content) @spell) @string.documentation)
; Tokens
[
"-"
"-="
":="
"!="
"*"
"**"
"**="
"*="
"/"
"//"
"//="
"/="
"&"
"&="
"%"
"%="
"^"
"^="
"+"
"+="
"<"
"<<"
"<<="
"<="
"<>"
"="
"=="
">"
">="
">>"
">>="
"@"
"@="
"|"
"|="
"~"
"->"
] @operator
; Keywords
[
"and"
"in"
"is"
"not"
"or"
"is not"
"not in"
"del"
] @keyword.operator
[
"def"
"lambda"
] @keyword.function
[
"assert"
"exec"
"global"
"nonlocal"
"pass"
"print"
"with"
"as"
] @keyword
[
"type"
"class"
] @keyword.type
[
"async"
"await"
] @keyword.coroutine
[
"return"
"yield"
] @keyword.return
(yield
"from" @keyword.return)
(future_import_statement
"from" @keyword.import
"__future__" @module.builtin)
(import_from_statement
"from" @keyword.import)
"import" @keyword.import
(aliased_import
"as" @keyword.import)
(wildcard_import
"*" @character.special)
(import_statement
name: (dotted_name
(identifier) @module))
(import_statement
name: (aliased_import
name: (dotted_name
(identifier) @module)
alias: (identifier) @module))
(import_from_statement
module_name: (dotted_name
(identifier) @module))
(import_from_statement
module_name: (relative_import
(dotted_name
(identifier) @module)))
[
"if"
"elif"
"else"
"match"
"case"
] @keyword.conditional
[
"for"
"while"
"break"
"continue"
] @keyword.repeat
[
"try"
"except"
; "except*"
"raise"
"finally"
] @keyword.exception
(raise_statement
"from" @keyword.exception)
(try_statement
(else_clause
"else" @keyword.exception))
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
(interpolation
"{" @punctuation.special
"}" @punctuation.special)
(type_conversion) @function.macro
[
","
"."
":"
";"
(ellipsis)
] @punctuation.delimiter
((identifier) @type.builtin
(#any-of? @type.builtin
; https://docs.python.org/3/library/exceptions.html
"BaseException" "Exception" "ArithmeticError" "BufferError" "LookupError" "AssertionError"
"AttributeError" "EOFError" "FloatingPointError" "GeneratorExit" "ImportError"
"ModuleNotFoundError" "IndexError" "KeyError" "KeyboardInterrupt" "MemoryError" "NameError"
"NotImplementedError" "OSError" "OverflowError" "RecursionError" "ReferenceError" "RuntimeError"
"StopIteration" "StopAsyncIteration" "SyntaxError" "IndentationError" "TabError" "SystemError"
"SystemExit" "TypeError" "UnboundLocalError" "UnicodeError" "UnicodeEncodeError"
"UnicodeDecodeError" "UnicodeTranslateError" "ValueError" "ZeroDivisionError" "EnvironmentError"
"IOError" "WindowsError" "BlockingIOError" "ChildProcessError" "ConnectionError"
"BrokenPipeError" "ConnectionAbortedError" "ConnectionRefusedError" "ConnectionResetError"
"FileExistsError" "FileNotFoundError" "InterruptedError" "IsADirectoryError"
"NotADirectoryError" "PermissionError" "ProcessLookupError" "TimeoutError" "Warning"
"UserWarning" "DeprecationWarning" "PendingDeprecationWarning" "SyntaxWarning" "RuntimeWarning"
"FutureWarning" "ImportWarning" "UnicodeWarning" "BytesWarning" "ResourceWarning"
; https://docs.python.org/3/library/stdtypes.html
"bool" "int" "float" "complex" "list" "tuple" "range" "str" "bytes" "bytearray" "memoryview"
"set" "frozenset" "dict" "type" "object"))
; Normal parameters
(parameters
(identifier) @variable.parameter)
; Lambda parameters
(lambda_parameters
(identifier) @variable.parameter)
(lambda_parameters
(tuple_pattern
(identifier) @variable.parameter))
; Default parameters
(keyword_argument
name: (identifier) @variable.parameter)
; Naming parameters on call-site
(default_parameter
name: (identifier) @variable.parameter)
(typed_parameter
(identifier) @variable.parameter)
(typed_default_parameter
name: (identifier) @variable.parameter)
; Variadic parameters *args, **kwargs
(parameters
(list_splat_pattern ; *args
(identifier) @variable.parameter))
(parameters
(dictionary_splat_pattern ; **kwargs
(identifier) @variable.parameter))
; Typed variadic parameters
(parameters
(typed_parameter
(list_splat_pattern ; *args: type
(identifier) @variable.parameter)))
(parameters
(typed_parameter
(dictionary_splat_pattern ; *kwargs: type
(identifier) @variable.parameter)))
; Lambda parameters
(lambda_parameters
(list_splat_pattern
(identifier) @variable.parameter))
(lambda_parameters
(dictionary_splat_pattern
(identifier) @variable.parameter))
((identifier) @variable.builtin
(#eq? @variable.builtin "self"))
((identifier) @variable.builtin
(#eq? @variable.builtin "cls"))
; After @type.builtin bacause builtins (such as `type`) are valid as attribute name
((attribute
attribute: (identifier) @variable.member)
(#lua-match? @variable.member "^[%l_].*$"))
; Class definitions
(class_definition
name: (identifier) @type)
(class_definition
body: (block
(function_definition
name: (identifier) @function.method)))
(class_definition
superclasses: (argument_list
(identifier) @type))
((class_definition
body: (block
(expression_statement
(assignment
left: (identifier) @variable.member))))
(#lua-match? @variable.member "^[%l_].*$"))
((class_definition
body: (block
(expression_statement
(assignment
left: (_
(identifier) @variable.member)))))
(#lua-match? @variable.member "^[%l_].*$"))
((class_definition
(block
(function_definition
name: (identifier) @constructor)))
(#any-of? @constructor "__new__" "__init__"))
; Function calls
(call
function: (identifier) @function.call)
(call
function: (attribute
attribute: (identifier) @function.method.call))
((call
function: (identifier) @constructor)
(#lua-match? @constructor "^%u"))
((call
function: (attribute
attribute: (identifier) @constructor))
(#lua-match? @constructor "^%u"))
; Builtin functions
((call
function: (identifier) @function.builtin)
(#any-of? @function.builtin
"abs" "all" "any" "ascii" "bin" "bool" "breakpoint" "bytearray" "bytes" "callable" "chr"
"classmethod" "compile" "complex" "delattr" "dict" "dir" "divmod" "enumerate" "eval" "exec"
"filter" "float" "format" "frozenset" "getattr" "globals" "hasattr" "hash" "help" "hex" "id"
"input" "int" "isinstance" "issubclass" "iter" "len" "list" "locals" "map" "max" "memoryview"
"min" "next" "object" "oct" "open" "ord" "pow" "print" "property" "range" "repr" "reversed"
"round" "set" "setattr" "slice" "sorted" "staticmethod" "str" "sum" "super" "tuple" "type"
"vars" "zip" "__import__"))
; Regex from the `re` module
(call
function: (attribute
object: (identifier) @_re)
arguments: (argument_list
.
(string
(string_content) @string.regexp))
(#eq? @_re "re"))
; Decorators
((decorator
"@" @attribute)
(#set! priority 101))
(decorator
(identifier) @attribute)
(decorator
(attribute
attribute: (identifier) @attribute))
(decorator
(call
(identifier) @attribute))
(decorator
(call
(attribute
attribute: (identifier) @attribute)))
((decorator
(identifier) @attribute.builtin)
(#any-of? @attribute.builtin "classmethod" "property" "staticmethod"))

View File

@ -1,309 +0,0 @@
; Variables
[
(identifier)
(global_variable)
] @variable
; Keywords
[
"alias"
"begin"
"do"
"end"
"ensure"
"module"
"rescue"
"then"
] @keyword
"class" @keyword.type
[
"return"
"yield"
] @keyword.return
[
"and"
"or"
"in"
"not"
] @keyword.operator
[
"def"
"undef"
] @keyword.function
(method
"end" @keyword.function)
[
"case"
"else"
"elsif"
"if"
"unless"
"when"
"then"
] @keyword.conditional
(if
"end" @keyword.conditional)
[
"for"
"until"
"while"
"break"
"redo"
"retry"
"next"
] @keyword.repeat
(constant) @constant
((identifier) @keyword.modifier
(#any-of? @keyword.modifier "private" "protected" "public"))
[
"rescue"
"ensure"
] @keyword.exception
; Function calls
"defined?" @function
(call
receiver: (constant)? @type
method: [
(identifier)
(constant)
] @function.call)
(program
(call
(identifier) @keyword.import)
(#any-of? @keyword.import "require" "require_relative" "load"))
; Function definitions
(alias
(identifier) @function)
(setter
(identifier) @function)
(method
name: [
(identifier) @function
(constant) @type
])
(singleton_method
name: [
(identifier) @function
(constant) @type
])
(class
name: (constant) @type)
(module
name: (constant) @type)
(superclass
(constant) @type)
; Identifiers
[
(class_variable)
(instance_variable)
] @variable.member
((identifier) @constant.builtin
(#any-of? @constant.builtin
"__callee__" "__dir__" "__id__" "__method__" "__send__" "__ENCODING__" "__FILE__" "__LINE__"))
((identifier) @function.builtin
(#any-of? @function.builtin "attr_reader" "attr_writer" "attr_accessor" "module_function"))
((call
!receiver
method: (identifier) @function.builtin)
(#any-of? @function.builtin "include" "extend" "prepend" "refine" "using"))
((identifier) @keyword.exception
(#any-of? @keyword.exception "raise" "fail" "catch" "throw"))
((constant) @type
(#not-lua-match? @type "^[A-Z0-9_]+$"))
[
(self)
(super)
] @variable.builtin
(method_parameters
(identifier) @variable.parameter)
(lambda_parameters
(identifier) @variable.parameter)
(block_parameters
(identifier) @variable.parameter)
(splat_parameter
(identifier) @variable.parameter)
(hash_splat_parameter
(identifier) @variable.parameter)
(optional_parameter
(identifier) @variable.parameter)
(destructured_parameter
(identifier) @variable.parameter)
(block_parameter
(identifier) @variable.parameter)
(keyword_parameter
(identifier) @variable.parameter)
; TODO: Re-enable this once it is supported
; ((identifier) @function
; (#is-not? local))
; Literals
[
(string_content)
(heredoc_content)
"\""
"`"
] @string
[
(heredoc_beginning)
(heredoc_end)
] @label
[
(bare_symbol)
(simple_symbol)
(delimited_symbol)
(hash_key_symbol)
] @string.special.symbol
(regex
(string_content) @string.regexp)
(escape_sequence) @string.escape
(integer) @number
(float) @number.float
[
(true)
(false)
] @boolean
(nil) @constant.builtin
(comment) @comment @spell
((program
.
(comment) @keyword.directive @nospell)
(#lua-match? @keyword.directive "^#!/"))
(program
(comment)+ @comment.documentation
(class))
(module
(comment)+ @comment.documentation
(body_statement
(class)))
(class
(comment)+ @comment.documentation
(body_statement
(method)))
(body_statement
(comment)+ @comment.documentation
(method))
; Operators
[
"!"
"="
"=="
"==="
"<=>"
"=>"
"->"
">>"
"<<"
">"
"<"
">="
"<="
"**"
"*"
"/"
"%"
"+"
"-"
"&"
"|"
"^"
"&&"
"||"
"||="
"&&="
"!="
"%="
"+="
"-="
"*="
"/="
"=~"
"!~"
"?"
":"
".."
"..."
] @operator
[
","
";"
"."
"&."
"::"
] @punctuation.delimiter
(regex
"/" @punctuation.bracket)
(pair
":" @punctuation.delimiter)
[
"("
")"
"["
"]"
"{"
"}"
"%w("
"%i("
] @punctuation.bracket
(block_parameters
"|" @punctuation.bracket)
(interpolation
"#{" @punctuation.special
"}" @punctuation.special)

View File

@ -1,531 +0,0 @@
; Forked from https://github.com/tree-sitter/tree-sitter-rust
; Copyright (c) 2017 Maxim Sokolov
; Licensed under the MIT license.
; Identifier conventions
(shebang) @keyword.directive
(identifier) @variable
((identifier) @type
(#lua-match? @type "^[A-Z]"))
(const_item
name: (identifier) @constant)
; Assume all-caps names are constants
((identifier) @constant
(#lua-match? @constant "^[A-Z][A-Z%d_]*$"))
; Other identifiers
(type_identifier) @type
(primitive_type) @type.builtin
(field_identifier) @variable.member
(shorthand_field_identifier) @variable.member
(shorthand_field_initializer
(identifier) @variable.member)
(mod_item
name: (identifier) @module)
(self) @variable.builtin
"_" @character.special
(label
[
"'"
(identifier)
] @label)
; Function definitions
(function_item
(identifier) @function)
(function_signature_item
(identifier) @function)
(parameter
[
(identifier)
"_"
] @variable.parameter)
(parameter
(ref_pattern
[
(mut_pattern
(identifier) @variable.parameter)
(identifier) @variable.parameter
]))
(closure_parameters
(_) @variable.parameter)
; Function calls
(call_expression
function: (identifier) @function.call)
(call_expression
function: (scoped_identifier
(identifier) @function.call .))
(call_expression
function: (field_expression
field: (field_identifier) @function.call))
(generic_function
function: (identifier) @function.call)
(generic_function
function: (scoped_identifier
name: (identifier) @function.call))
(generic_function
function: (field_expression
field: (field_identifier) @function.call))
; Assume other uppercase names are enum constructors
((field_identifier) @constant
(#lua-match? @constant "^[A-Z]"))
(enum_variant
name: (identifier) @constant)
; Assume that uppercase names in paths are types
(scoped_identifier
path: (identifier) @module)
(scoped_identifier
(scoped_identifier
name: (identifier) @module))
(scoped_type_identifier
path: (identifier) @module)
(scoped_type_identifier
path: (identifier) @type
(#lua-match? @type "^[A-Z]"))
(scoped_type_identifier
(scoped_identifier
name: (identifier) @module))
((scoped_identifier
path: (identifier) @type)
(#lua-match? @type "^[A-Z]"))
((scoped_identifier
name: (identifier) @type)
(#lua-match? @type "^[A-Z]"))
((scoped_identifier
name: (identifier) @constant)
(#lua-match? @constant "^[A-Z][A-Z%d_]*$"))
((scoped_identifier
path: (identifier) @type
name: (identifier) @constant)
(#lua-match? @type "^[A-Z]")
(#lua-match? @constant "^[A-Z]"))
((scoped_type_identifier
path: (identifier) @type
name: (type_identifier) @constant)
(#lua-match? @type "^[A-Z]")
(#lua-match? @constant "^[A-Z]"))
[
(crate)
(super)
] @module
(scoped_use_list
path: (identifier) @module)
(scoped_use_list
path: (scoped_identifier
(identifier) @module))
(use_list
(scoped_identifier
(identifier) @module
.
(_)))
(use_list
(identifier) @type
(#lua-match? @type "^[A-Z]"))
(use_as_clause
alias: (identifier) @type
(#lua-match? @type "^[A-Z]"))
; Correct enum constructors
(call_expression
function: (scoped_identifier
"::"
name: (identifier) @constant)
(#lua-match? @constant "^[A-Z]"))
; Assume uppercase names in a match arm are constants.
((match_arm
pattern: (match_pattern
(identifier) @constant))
(#lua-match? @constant "^[A-Z]"))
((match_arm
pattern: (match_pattern
(scoped_identifier
name: (identifier) @constant)))
(#lua-match? @constant "^[A-Z]"))
((identifier) @constant.builtin
(#any-of? @constant.builtin "Some" "None" "Ok" "Err"))
; Macro definitions
"$" @function.macro
(metavariable) @function.macro
(macro_definition
"macro_rules!" @function.macro)
; Attribute macros
(attribute_item
(attribute
(identifier) @function.macro))
(inner_attribute_item
(attribute
(identifier) @function.macro))
(attribute
(scoped_identifier
(identifier) @function.macro .))
; Derive macros (assume all arguments are types)
; (attribute
; (identifier) @_name
; arguments: (attribute (attribute (identifier) @type))
; (#eq? @_name "derive"))
; Function-like macros
(macro_invocation
macro: (identifier) @function.macro)
(macro_invocation
macro: (scoped_identifier
(identifier) @function.macro .))
; Literals
(boolean_literal) @boolean
(integer_literal) @number
(float_literal) @number.float
[
(raw_string_literal)
(string_literal)
] @string
(escape_sequence) @string.escape
(char_literal) @character
; Keywords
[
"use"
"mod"
] @keyword.import
(use_as_clause
"as" @keyword.import)
[
"default"
"impl"
"let"
"move"
"unsafe"
"where"
] @keyword
[
"enum"
"struct"
"union"
"trait"
"type"
] @keyword.type
[
"async"
"await"
"gen"
] @keyword.coroutine
"try" @keyword.exception
[
"ref"
"pub"
"raw"
(mutable_specifier)
"const"
"static"
"dyn"
"extern"
] @keyword.modifier
(lifetime
"'" @keyword.modifier)
(lifetime
(identifier) @attribute)
(lifetime
(identifier) @attribute.builtin
(#any-of? @attribute.builtin "static" "_"))
"fn" @keyword.function
[
"return"
"yield"
] @keyword.return
(type_cast_expression
"as" @keyword.operator)
(qualified_type
"as" @keyword.operator)
(use_list
(self) @module)
(scoped_use_list
(self) @module)
(scoped_identifier
[
(crate)
(super)
(self)
] @module)
(visibility_modifier
[
(crate)
(super)
(self)
] @module)
[
"if"
"else"
"match"
] @keyword.conditional
[
"break"
"continue"
"in"
"loop"
"while"
] @keyword.repeat
"for" @keyword
(for_expression
"for" @keyword.repeat)
; Operators
[
"!"
"!="
"%"
"%="
"&"
"&&"
"&="
"*"
"*="
"+"
"+="
"-"
"-="
".."
"..="
"..."
"/"
"/="
"<"
"<<"
"<<="
"<="
"="
"=="
">"
">="
">>"
">>="
"?"
"@"
"^"
"^="
"|"
"|="
"||"
] @operator
(use_wildcard
"*" @character.special)
(remaining_field_pattern
".." @character.special)
(range_pattern
[
".."
"..="
"..."
] @character.special)
; Punctuation
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
(closure_parameters
"|" @punctuation.bracket)
(type_arguments
[
"<"
">"
] @punctuation.bracket)
(type_parameters
[
"<"
">"
] @punctuation.bracket)
(bracketed_type
[
"<"
">"
] @punctuation.bracket)
(for_lifetimes
[
"<"
">"
] @punctuation.bracket)
[
","
"."
":"
"::"
";"
"->"
"=>"
] @punctuation.delimiter
(attribute_item
"#" @punctuation.special)
(inner_attribute_item
[
"!"
"#"
] @punctuation.special)
(macro_invocation
"!" @function.macro)
(never_type
"!" @type.builtin)
(macro_invocation
macro: (identifier) @_identifier @keyword.exception
"!" @keyword.exception
(#eq? @_identifier "panic"))
(macro_invocation
macro: (identifier) @_identifier @keyword.exception
"!" @keyword.exception
(#contains? @_identifier "assert"))
(macro_invocation
macro: (identifier) @_identifier @keyword.debug
"!" @keyword.debug
(#eq? @_identifier "dbg"))
; Comments
[
(line_comment)
(block_comment)
(outer_doc_comment_marker)
(inner_doc_comment_marker)
] @comment @spell
(line_comment
(doc_comment)) @comment.documentation
(block_comment
(doc_comment)) @comment.documentation
(call_expression
function: (scoped_identifier
path: (identifier) @_regex
(#any-of? @_regex "Regex" "ByteRegexBuilder")
name: (identifier) @_new
(#eq? @_new "new"))
arguments: (arguments
(raw_string_literal
(string_content) @string.regexp)))
(call_expression
function: (scoped_identifier
path: (scoped_identifier
(identifier) @_regex
(#any-of? @_regex "Regex" "ByteRegexBuilder") .)
name: (identifier) @_new
(#eq? @_new "new"))
arguments: (arguments
(raw_string_literal
(string_content) @string.regexp)))
(call_expression
function: (scoped_identifier
path: (identifier) @_regex
(#any-of? @_regex "RegexSet" "RegexSetBuilder")
name: (identifier) @_new
(#eq? @_new "new"))
arguments: (arguments
(array_expression
(raw_string_literal
(string_content) @string.regexp))))
(call_expression
function: (scoped_identifier
path: (scoped_identifier
(identifier) @_regex
(#any-of? @_regex "RegexSet" "RegexSetBuilder") .)
name: (identifier) @_new
(#eq? @_new "new"))
arguments: (arguments
(array_expression
(raw_string_literal
(string_content) @string.regexp))))

View File

@ -1,757 +0,0 @@
(jsx_element
open_tag: (jsx_opening_element
[
"<"
">"
] @tag.delimiter))
(jsx_element
close_tag: (jsx_closing_element
[
"</"
">"
] @tag.delimiter))
(jsx_self_closing_element
[
"<"
"/>"
] @tag.delimiter)
(jsx_attribute
(property_identifier) @tag.attribute)
(jsx_opening_element
name: (identifier) @tag.builtin)
(jsx_closing_element
name: (identifier) @tag.builtin)
(jsx_self_closing_element
name: (identifier) @tag.builtin)
(jsx_opening_element
((identifier) @tag
(#lua-match? @tag "^[A-Z]")))
; Handle the dot operator effectively - <My.Component>
(jsx_opening_element
(member_expression
(identifier) @tag.builtin
(property_identifier) @tag))
(jsx_closing_element
((identifier) @tag
(#lua-match? @tag "^[A-Z]")))
; Handle the dot operator effectively - </My.Component>
(jsx_closing_element
(member_expression
(identifier) @tag.builtin
(property_identifier) @tag))
(jsx_self_closing_element
((identifier) @tag
(#lua-match? @tag "^[A-Z]")))
; Handle the dot operator effectively - <My.Component />
(jsx_self_closing_element
(member_expression
(identifier) @tag.builtin
(property_identifier) @tag))
(html_character_reference) @tag
(jsx_text) @none @spell
(html_character_reference) @character.special
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading)
(#eq? @_tag "title"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.1)
(#eq? @_tag "h1"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.2)
(#eq? @_tag "h2"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.3)
(#eq? @_tag "h3"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.4)
(#eq? @_tag "h4"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.5)
(#eq? @_tag "h5"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.heading.6)
(#eq? @_tag "h6"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.strong)
(#any-of? @_tag "strong" "b"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.italic)
(#any-of? @_tag "em" "i"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.strikethrough)
(#any-of? @_tag "s" "del"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.underline)
(#eq? @_tag "u"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.raw)
(#any-of? @_tag "code" "kbd"))
((jsx_element
(jsx_opening_element
name: (identifier) @_tag)
(jsx_text) @markup.link.label)
(#eq? @_tag "a"))
((jsx_attribute
(property_identifier) @_attr
(string
(string_fragment) @string.special.url))
(#any-of? @_attr "href" "src"))
((jsx_element) @_jsx_element
(#set! @_jsx_element bo.commentstring "{/* %s */}"))
((jsx_attribute) @_jsx_attribute
(#set! @_jsx_attribute bo.commentstring "// %s"))
; Types
; Javascript
; Variables
;-----------
(identifier) @variable
; Properties
;-----------
(property_identifier) @variable.member
(shorthand_property_identifier) @variable.member
(private_property_identifier) @variable.member
(object_pattern
(shorthand_property_identifier_pattern) @variable)
(object_pattern
(object_assignment_pattern
(shorthand_property_identifier_pattern) @variable))
; Special identifiers
;--------------------
((identifier) @type
(#lua-match? @type "^[A-Z]"))
((identifier) @constant
(#lua-match? @constant "^_*[A-Z][A-Z%d_]*$"))
((shorthand_property_identifier) @constant
(#lua-match? @constant "^_*[A-Z][A-Z%d_]*$"))
((identifier) @variable.builtin
(#any-of? @variable.builtin "arguments" "module" "console" "window" "document"))
((identifier) @type.builtin
(#any-of? @type.builtin
"Object" "Function" "Boolean" "Symbol" "Number" "Math" "Date" "String" "RegExp" "Map" "Set"
"WeakMap" "WeakSet" "Promise" "Array" "Int8Array" "Uint8Array" "Uint8ClampedArray" "Int16Array"
"Uint16Array" "Int32Array" "Uint32Array" "Float32Array" "Float64Array" "ArrayBuffer" "DataView"
"Error" "EvalError" "InternalError" "RangeError" "ReferenceError" "SyntaxError" "TypeError"
"URIError"))
(statement_identifier) @label
; Function and method definitions
;--------------------------------
(function_expression
name: (identifier) @function)
(function_declaration
name: (identifier) @function)
(generator_function
name: (identifier) @function)
(generator_function_declaration
name: (identifier) @function)
(method_definition
name: [
(property_identifier)
(private_property_identifier)
] @function.method)
(method_definition
name: (property_identifier) @constructor
(#eq? @constructor "constructor"))
(pair
key: (property_identifier) @function.method
value: (function_expression))
(pair
key: (property_identifier) @function.method
value: (arrow_function))
(assignment_expression
left: (member_expression
property: (property_identifier) @function.method)
right: (arrow_function))
(assignment_expression
left: (member_expression
property: (property_identifier) @function.method)
right: (function_expression))
(variable_declarator
name: (identifier) @function
value: (arrow_function))
(variable_declarator
name: (identifier) @function
value: (function_expression))
(assignment_expression
left: (identifier) @function
right: (arrow_function))
(assignment_expression
left: (identifier) @function
right: (function_expression))
; Function and method calls
;--------------------------
(call_expression
function: (identifier) @function.call)
(call_expression
function: (member_expression
property: [
(property_identifier)
(private_property_identifier)
] @function.method.call))
(call_expression
function: (await_expression
(identifier) @function.call))
(call_expression
function: (await_expression
(member_expression
property: [
(property_identifier)
(private_property_identifier)
] @function.method.call)))
; Builtins
;---------
((identifier) @module.builtin
(#eq? @module.builtin "Intl"))
((identifier) @function.builtin
(#any-of? @function.builtin
"eval" "isFinite" "isNaN" "parseFloat" "parseInt" "decodeURI" "decodeURIComponent" "encodeURI"
"encodeURIComponent" "require"))
; Constructor
;------------
(new_expression
constructor: (identifier) @constructor)
; Decorators
;----------
(decorator
"@" @attribute
(identifier) @attribute)
(decorator
"@" @attribute
(call_expression
(identifier) @attribute))
(decorator
"@" @attribute
(member_expression
(property_identifier) @attribute))
(decorator
"@" @attribute
(call_expression
(member_expression
(property_identifier) @attribute)))
; Literals
;---------
[
(this)
(super)
] @variable.builtin
((identifier) @variable.builtin
(#eq? @variable.builtin "self"))
[
(true)
(false)
] @boolean
[
(null)
(undefined)
] @constant.builtin
[
(comment)
(html_comment)
] @comment @spell
((comment) @comment.documentation
(#lua-match? @comment.documentation "^/[*][*][^*].*[*]/$"))
(hash_bang_line) @keyword.directive
((string_fragment) @keyword.directive
(#eq? @keyword.directive "use strict"))
(string) @string
(template_string) @string
(escape_sequence) @string.escape
(regex_pattern) @string.regexp
(regex_flags) @character.special
(regex
"/" @punctuation.bracket) ; Regex delimiters
(number) @number
((identifier) @number
(#any-of? @number "NaN" "Infinity"))
; Punctuation
;------------
[
";"
"."
","
":"
] @punctuation.delimiter
[
"--"
"-"
"-="
"&&"
"+"
"++"
"+="
"&="
"/="
"**="
"<<="
"<"
"<="
"<<"
"="
"=="
"==="
"!="
"!=="
"=>"
">"
">="
">>"
"||"
"%"
"%="
"*"
"**"
">>>"
"&"
"|"
"^"
"??"
"*="
">>="
">>>="
"^="
"|="
"&&="
"||="
"??="
"..."
] @operator
(binary_expression
"/" @operator)
(ternary_expression
[
"?"
":"
] @keyword.conditional.ternary)
(unary_expression
[
"!"
"~"
"-"
"+"
] @operator)
(unary_expression
[
"delete"
"void"
] @keyword.operator)
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
(template_substitution
[
"${"
"}"
] @punctuation.special) @none
; Imports
;----------
(namespace_import
"*" @character.special
(identifier) @module)
(namespace_export
"*" @character.special
(identifier) @module)
(export_statement
"*" @character.special)
; Keywords
;----------
[
"if"
"else"
"switch"
"case"
] @keyword.conditional
[
"import"
"from"
"as"
"export"
] @keyword.import
[
"for"
"of"
"do"
"while"
"continue"
] @keyword.repeat
[
"break"
"const"
"debugger"
"extends"
"get"
"let"
"set"
"static"
"target"
"var"
"with"
] @keyword
"class" @keyword.type
[
"async"
"await"
] @keyword.coroutine
[
"return"
"yield"
] @keyword.return
"function" @keyword.function
[
"new"
"delete"
"in"
"instanceof"
"typeof"
] @keyword.operator
[
"throw"
"try"
"catch"
"finally"
] @keyword.exception
(export_statement
"default" @keyword)
(switch_default
"default" @keyword.conditional)
"require" @keyword.import
(import_require_clause
source: (string) @string.special.url)
[
"declare"
"implements"
"type"
"override"
"module"
"asserts"
"infer"
"is"
"using"
] @keyword
[
"namespace"
"interface"
"enum"
] @keyword.type
[
"keyof"
"satisfies"
] @keyword.operator
(as_expression
"as" @keyword.operator)
(mapped_type_clause
"as" @keyword.operator)
[
"abstract"
"private"
"protected"
"public"
"readonly"
] @keyword.modifier
; types
(type_identifier) @type
(predefined_type) @type.builtin
(import_statement
"type"
(import_clause
(named_imports
(import_specifier
name: (identifier) @type))))
(template_literal_type) @string
(non_null_expression
"!" @operator)
; punctuation
(type_arguments
[
"<"
">"
] @punctuation.bracket)
(type_parameters
[
"<"
">"
] @punctuation.bracket)
(object_type
[
"{|"
"|}"
] @punctuation.bracket)
(union_type
"|" @punctuation.delimiter)
(intersection_type
"&" @punctuation.delimiter)
(type_annotation
":" @punctuation.delimiter)
(type_predicate_annotation
":" @punctuation.delimiter)
(index_signature
":" @punctuation.delimiter)
(omitting_type_annotation
"-?:" @punctuation.delimiter)
(adding_type_annotation
"+?:" @punctuation.delimiter)
(opting_type_annotation
"?:" @punctuation.delimiter)
"?." @punctuation.delimiter
(abstract_method_signature
"?" @punctuation.special)
(method_signature
"?" @punctuation.special)
(method_definition
"?" @punctuation.special)
(property_signature
"?" @punctuation.special)
(optional_parameter
"?" @punctuation.special)
(optional_type
"?" @punctuation.special)
(public_field_definition
[
"?"
"!"
] @punctuation.special)
(flow_maybe_type
"?" @punctuation.special)
(template_type
[
"${"
"}"
] @punctuation.special)
(conditional_type
[
"?"
":"
] @keyword.conditional.ternary)
; Parameters
(required_parameter
pattern: (identifier) @variable.parameter)
(optional_parameter
pattern: (identifier) @variable.parameter)
(required_parameter
(rest_pattern
(identifier) @variable.parameter))
; ({ a }) => null
(required_parameter
(object_pattern
(shorthand_property_identifier_pattern) @variable.parameter))
; ({ a = b }) => null
(required_parameter
(object_pattern
(object_assignment_pattern
(shorthand_property_identifier_pattern) @variable.parameter)))
; ({ a: b }) => null
(required_parameter
(object_pattern
(pair_pattern
value: (identifier) @variable.parameter)))
; ([ a ]) => null
(required_parameter
(array_pattern
(identifier) @variable.parameter))
; a => null
(arrow_function
parameter: (identifier) @variable.parameter)
; global declaration
(ambient_declaration
"global" @module)
; function signatures
(ambient_declaration
(function_signature
name: (identifier) @function))
; method signatures
(method_signature
name: (_) @function.method)
(abstract_method_signature
name: (property_identifier) @function.method)
; property signatures
(property_signature
name: (property_identifier) @function.method
type: (type_annotation
[
(union_type
(parenthesized_type
(function_type)))
(function_type)
]))

View File

@ -1,599 +0,0 @@
; Types
; Javascript
; Variables
;-----------
(identifier) @variable
; Properties
;-----------
(property_identifier) @variable.member
(shorthand_property_identifier) @variable.member
(private_property_identifier) @variable.member
(object_pattern
(shorthand_property_identifier_pattern) @variable)
(object_pattern
(object_assignment_pattern
(shorthand_property_identifier_pattern) @variable))
; Special identifiers
;--------------------
((identifier) @type
(#lua-match? @type "^[A-Z]"))
((identifier) @constant
(#lua-match? @constant "^_*[A-Z][A-Z%d_]*$"))
((shorthand_property_identifier) @constant
(#lua-match? @constant "^_*[A-Z][A-Z%d_]*$"))
((identifier) @variable.builtin
(#any-of? @variable.builtin "arguments" "module" "console" "window" "document"))
((identifier) @type.builtin
(#any-of? @type.builtin
"Object" "Function" "Boolean" "Symbol" "Number" "Math" "Date" "String" "RegExp" "Map" "Set"
"WeakMap" "WeakSet" "Promise" "Array" "Int8Array" "Uint8Array" "Uint8ClampedArray" "Int16Array"
"Uint16Array" "Int32Array" "Uint32Array" "Float32Array" "Float64Array" "ArrayBuffer" "DataView"
"Error" "EvalError" "InternalError" "RangeError" "ReferenceError" "SyntaxError" "TypeError"
"URIError"))
(statement_identifier) @label
; Function and method definitions
;--------------------------------
(function_expression
name: (identifier) @function)
(function_declaration
name: (identifier) @function)
(generator_function
name: (identifier) @function)
(generator_function_declaration
name: (identifier) @function)
(method_definition
name: [
(property_identifier)
(private_property_identifier)
] @function.method)
(method_definition
name: (property_identifier) @constructor
(#eq? @constructor "constructor"))
(pair
key: (property_identifier) @function.method
value: (function_expression))
(pair
key: (property_identifier) @function.method
value: (arrow_function))
(assignment_expression
left: (member_expression
property: (property_identifier) @function.method)
right: (arrow_function))
(assignment_expression
left: (member_expression
property: (property_identifier) @function.method)
right: (function_expression))
(variable_declarator
name: (identifier) @function
value: (arrow_function))
(variable_declarator
name: (identifier) @function
value: (function_expression))
(assignment_expression
left: (identifier) @function
right: (arrow_function))
(assignment_expression
left: (identifier) @function
right: (function_expression))
; Function and method calls
;--------------------------
(call_expression
function: (identifier) @function.call)
(call_expression
function: (member_expression
property: [
(property_identifier)
(private_property_identifier)
] @function.method.call))
(call_expression
function: (await_expression
(identifier) @function.call))
(call_expression
function: (await_expression
(member_expression
property: [
(property_identifier)
(private_property_identifier)
] @function.method.call)))
; Builtins
;---------
((identifier) @module.builtin
(#eq? @module.builtin "Intl"))
((identifier) @function.builtin
(#any-of? @function.builtin
"eval" "isFinite" "isNaN" "parseFloat" "parseInt" "decodeURI" "decodeURIComponent" "encodeURI"
"encodeURIComponent" "require"))
; Constructor
;------------
(new_expression
constructor: (identifier) @constructor)
; Decorators
;----------
(decorator
"@" @attribute
(identifier) @attribute)
(decorator
"@" @attribute
(call_expression
(identifier) @attribute))
(decorator
"@" @attribute
(member_expression
(property_identifier) @attribute))
(decorator
"@" @attribute
(call_expression
(member_expression
(property_identifier) @attribute)))
; Literals
;---------
[
(this)
(super)
] @variable.builtin
((identifier) @variable.builtin
(#eq? @variable.builtin "self"))
[
(true)
(false)
] @boolean
[
(null)
(undefined)
] @constant.builtin
[
(comment)
(html_comment)
] @comment @spell
((comment) @comment.documentation
(#lua-match? @comment.documentation "^/[*][*][^*].*[*]/$"))
(hash_bang_line) @keyword.directive
((string_fragment) @keyword.directive
(#eq? @keyword.directive "use strict"))
(string) @string
(template_string) @string
(escape_sequence) @string.escape
(regex_pattern) @string.regexp
(regex_flags) @character.special
(regex
"/" @punctuation.bracket) ; Regex delimiters
(number) @number
((identifier) @number
(#any-of? @number "NaN" "Infinity"))
; Punctuation
;------------
[
";"
"."
","
":"
] @punctuation.delimiter
[
"--"
"-"
"-="
"&&"
"+"
"++"
"+="
"&="
"/="
"**="
"<<="
"<"
"<="
"<<"
"="
"=="
"==="
"!="
"!=="
"=>"
">"
">="
">>"
"||"
"%"
"%="
"*"
"**"
">>>"
"&"
"|"
"^"
"??"
"*="
">>="
">>>="
"^="
"|="
"&&="
"||="
"??="
"..."
] @operator
(binary_expression
"/" @operator)
(ternary_expression
[
"?"
":"
] @keyword.conditional.ternary)
(unary_expression
[
"!"
"~"
"-"
"+"
] @operator)
(unary_expression
[
"delete"
"void"
] @keyword.operator)
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
(template_substitution
[
"${"
"}"
] @punctuation.special) @none
; Imports
;----------
(namespace_import
"*" @character.special
(identifier) @module)
(namespace_export
"*" @character.special
(identifier) @module)
(export_statement
"*" @character.special)
; Keywords
;----------
[
"if"
"else"
"switch"
"case"
] @keyword.conditional
[
"import"
"from"
"as"
"export"
] @keyword.import
[
"for"
"of"
"do"
"while"
"continue"
] @keyword.repeat
[
"break"
"const"
"debugger"
"extends"
"get"
"let"
"set"
"static"
"target"
"var"
"with"
] @keyword
"class" @keyword.type
[
"async"
"await"
] @keyword.coroutine
[
"return"
"yield"
] @keyword.return
"function" @keyword.function
[
"new"
"delete"
"in"
"instanceof"
"typeof"
] @keyword.operator
[
"throw"
"try"
"catch"
"finally"
] @keyword.exception
(export_statement
"default" @keyword)
(switch_default
"default" @keyword.conditional)
"require" @keyword.import
(import_require_clause
source: (string) @string.special.url)
[
"declare"
"implements"
"type"
"override"
"module"
"asserts"
"infer"
"is"
"using"
] @keyword
[
"namespace"
"interface"
"enum"
] @keyword.type
[
"keyof"
"satisfies"
] @keyword.operator
(as_expression
"as" @keyword.operator)
(mapped_type_clause
"as" @keyword.operator)
[
"abstract"
"private"
"protected"
"public"
"readonly"
] @keyword.modifier
; types
(type_identifier) @type
(predefined_type) @type.builtin
(import_statement
"type"
(import_clause
(named_imports
(import_specifier
name: (identifier) @type))))
(template_literal_type) @string
(non_null_expression
"!" @operator)
; punctuation
(type_arguments
[
"<"
">"
] @punctuation.bracket)
(type_parameters
[
"<"
">"
] @punctuation.bracket)
(object_type
[
"{|"
"|}"
] @punctuation.bracket)
(union_type
"|" @punctuation.delimiter)
(intersection_type
"&" @punctuation.delimiter)
(type_annotation
":" @punctuation.delimiter)
(type_predicate_annotation
":" @punctuation.delimiter)
(index_signature
":" @punctuation.delimiter)
(omitting_type_annotation
"-?:" @punctuation.delimiter)
(adding_type_annotation
"+?:" @punctuation.delimiter)
(opting_type_annotation
"?:" @punctuation.delimiter)
"?." @punctuation.delimiter
(abstract_method_signature
"?" @punctuation.special)
(method_signature
"?" @punctuation.special)
(method_definition
"?" @punctuation.special)
(property_signature
"?" @punctuation.special)
(optional_parameter
"?" @punctuation.special)
(optional_type
"?" @punctuation.special)
(public_field_definition
[
"?"
"!"
] @punctuation.special)
(flow_maybe_type
"?" @punctuation.special)
(template_type
[
"${"
"}"
] @punctuation.special)
(conditional_type
[
"?"
":"
] @keyword.conditional.ternary)
; Parameters
(required_parameter
pattern: (identifier) @variable.parameter)
(optional_parameter
pattern: (identifier) @variable.parameter)
(required_parameter
(rest_pattern
(identifier) @variable.parameter))
; ({ a }) => null
(required_parameter
(object_pattern
(shorthand_property_identifier_pattern) @variable.parameter))
; ({ a = b }) => null
(required_parameter
(object_pattern
(object_assignment_pattern
(shorthand_property_identifier_pattern) @variable.parameter)))
; ({ a: b }) => null
(required_parameter
(object_pattern
(pair_pattern
value: (identifier) @variable.parameter)))
; ([ a ]) => null
(required_parameter
(array_pattern
(identifier) @variable.parameter))
; a => null
(arrow_function
parameter: (identifier) @variable.parameter)
; global declaration
(ambient_declaration
"global" @module)
; function signatures
(ambient_declaration
(function_signature
name: (identifier) @function))
; method signatures
(method_signature
name: (_) @function.method)
(abstract_method_signature
name: (property_identifier) @function.method)
; property signatures
(property_signature
name: (property_identifier) @function.method
type: (type_annotation
[
(union_type
(parenthesized_type
(function_type)))
(function_type)
]))

View File

@ -1,108 +0,0 @@
package syntax
import _ "embed"
//go:embed queries/go/highlights.scm
var goHighlightsQuery string
//go:embed queries/javascript/highlights.scm
var javascriptHighlightsQuery string
//go:embed queries/python/highlights.scm
var pythonHighlightsQuery string
//go:embed queries/rust/highlights.scm
var rustHighlightsQuery string
//go:embed queries/typescript/highlights.scm
var typescriptHighlightsQuery string
//go:embed queries/tsx/highlights.scm
var tsxHighlightsQuery string
//go:embed queries/bash/highlights.scm
var bashHighlightsQuery string
//go:embed queries/json/highlights.scm
var jsonHighlightsQuery string
//go:embed queries/css/highlights.scm
var cssHighlightsQuery string
//go:embed queries/html/highlights.scm
var htmlHighlightsQuery string
//go:embed queries/c/highlights.scm
var cHighlightsQuery string
//go:embed queries/cpp/highlights.scm
var cppHighlightsQuery string
//go:embed queries/java/highlights.scm
var javaHighlightsQuery string
//go:embed queries/csharp/highlights.scm
var csharpHighlightsQuery string
//go:embed queries/ruby/highlights.scm
var rubyHighlightsQuery string
func loadGoHighlightsQuery() ([]byte, error) {
return []byte(goHighlightsQuery), nil
}
func loadJavaScriptHighlightsQuery() ([]byte, error) {
return []byte(javascriptHighlightsQuery), nil
}
func loadPythonHighlightsQuery() ([]byte, error) {
return []byte(pythonHighlightsQuery), nil
}
func loadRustHighlightsQuery() ([]byte, error) {
return []byte(rustHighlightsQuery), nil
}
func loadTypeScriptHighlightsQuery() ([]byte, error) {
return []byte(typescriptHighlightsQuery), nil
}
func loadTSXHighlightsQuery() ([]byte, error) {
return []byte(tsxHighlightsQuery), nil
}
func loadBashHighlightsQuery() ([]byte, error) {
return []byte(bashHighlightsQuery), nil
}
func loadJSONHighlightsQuery() ([]byte, error) {
return []byte(jsonHighlightsQuery), nil
}
func loadCSSHighlightsQuery() ([]byte, error) {
return []byte(cssHighlightsQuery), nil
}
func loadHTMLHighlightsQuery() ([]byte, error) {
return []byte(htmlHighlightsQuery), nil
}
func loadCHighlightsQuery() ([]byte, error) {
return []byte(cHighlightsQuery), nil
}
func loadCppHighlightsQuery() ([]byte, error) {
return []byte(cppHighlightsQuery), nil
}
func loadJavaHighlightsQuery() ([]byte, error) {
return []byte(javaHighlightsQuery), nil
}
func loadCSharpHighlightsQuery() ([]byte, error) {
return []byte(csharpHighlightsQuery), nil
}
func loadRubyHighlightsQuery() ([]byte, error) {
return []byte(rubyHighlightsQuery), nil
}

View File

@ -1,101 +0,0 @@
package syntax
import (
"testing"
sitter "github.com/tree-sitter/go-tree-sitter"
ts_bash "github.com/tree-sitter/tree-sitter-bash/bindings/go"
ts_csharp "github.com/tree-sitter/tree-sitter-c-sharp/bindings/go"
ts_c "github.com/tree-sitter/tree-sitter-c/bindings/go"
ts_cpp "github.com/tree-sitter/tree-sitter-cpp/bindings/go"
ts_css "github.com/tree-sitter/tree-sitter-css/bindings/go"
ts_go "github.com/tree-sitter/tree-sitter-go/bindings/go"
ts_html "github.com/tree-sitter/tree-sitter-html/bindings/go"
ts_java "github.com/tree-sitter/tree-sitter-java/bindings/go"
ts_js "github.com/tree-sitter/tree-sitter-javascript/bindings/go"
ts_json "github.com/tree-sitter/tree-sitter-json/bindings/go"
ts_python "github.com/tree-sitter/tree-sitter-python/bindings/go"
ts_ruby "github.com/tree-sitter/tree-sitter-ruby/bindings/go"
ts_rust "github.com/tree-sitter/tree-sitter-rust/bindings/go"
ts_ts "github.com/tree-sitter/tree-sitter-typescript/bindings/go"
)
func TestEmbeddedQueriesLoadAndCompile(t *testing.T) {
tests := []struct {
name string
loadQuery func() ([]byte, error)
expectNonNil bool
}{
{name: "go", loadQuery: loadGoHighlightsQuery, expectNonNil: true},
{name: "javascript", loadQuery: loadJavaScriptHighlightsQuery, expectNonNil: true},
{name: "typescript", loadQuery: loadTypeScriptHighlightsQuery, expectNonNil: true},
{name: "tsx", loadQuery: loadTSXHighlightsQuery, expectNonNil: true},
{name: "python", loadQuery: loadPythonHighlightsQuery, expectNonNil: true},
{name: "rust", loadQuery: loadRustHighlightsQuery, expectNonNil: true},
{name: "bash", loadQuery: loadBashHighlightsQuery, expectNonNil: true},
{name: "json", loadQuery: loadJSONHighlightsQuery, expectNonNil: true},
{name: "css", loadQuery: loadCSSHighlightsQuery, expectNonNil: true},
{name: "html", loadQuery: loadHTMLHighlightsQuery, expectNonNil: true},
{name: "c", loadQuery: loadCHighlightsQuery, expectNonNil: true},
{name: "cpp", loadQuery: loadCppHighlightsQuery, expectNonNil: true},
{name: "java", loadQuery: loadJavaHighlightsQuery, expectNonNil: true},
{name: "csharp", loadQuery: loadCSharpHighlightsQuery, expectNonNil: true},
{name: "ruby", loadQuery: loadRubyHighlightsQuery, expectNonNil: true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
b, err := tc.loadQuery()
if err != nil {
t.Fatalf("failed loading embedded query: %v", err)
}
if tc.expectNonNil && b == nil {
t.Fatalf("expected non-nil embedded query bytes")
}
})
}
}
func TestEmbeddedQueriesCompileKnownGood(t *testing.T) {
compileTests := []struct {
name string
loadQuery func() ([]byte, error)
newLanguage func() *sitter.Language
}{
{name: "go", loadQuery: loadGoHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_go.Language()) }},
{name: "javascript", loadQuery: loadJavaScriptHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_js.Language()) }},
{name: "typescript", loadQuery: loadTypeScriptHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_ts.LanguageTypescript()) }},
{name: "tsx", loadQuery: loadTSXHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_ts.LanguageTSX()) }},
{name: "python", loadQuery: loadPythonHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_python.Language()) }},
{name: "rust", loadQuery: loadRustHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_rust.Language()) }},
{name: "bash", loadQuery: loadBashHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_bash.Language()) }},
{name: "json", loadQuery: loadJSONHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_json.Language()) }},
{name: "css", loadQuery: loadCSSHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_css.Language()) }},
{name: "html", loadQuery: loadHTMLHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_html.Language()) }},
{name: "c", loadQuery: loadCHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_c.Language()) }},
{name: "cpp", loadQuery: loadCppHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_cpp.Language()) }},
{name: "java", loadQuery: loadJavaHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_java.Language()) }},
{name: "csharp", loadQuery: loadCSharpHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_csharp.Language()) }},
{name: "ruby", loadQuery: loadRubyHighlightsQuery, newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_ruby.Language()) }},
}
for _, tc := range compileTests {
t.Run(tc.name, func(t *testing.T) {
b, err := tc.loadQuery()
if err != nil {
t.Fatalf("failed loading embedded query: %v", err)
}
lang := tc.newLanguage()
if lang == nil {
t.Fatalf("language handle is nil")
}
q, qErr := sitter.NewQuery(lang, string(b))
if qErr != nil {
t.Fatalf("embedded query failed to compile: %v", qErr)
}
q.Close()
})
}
}

View File

@ -1,291 +0,0 @@
package syntax
import (
"fmt"
"strings"
sitter "github.com/tree-sitter/go-tree-sitter"
ts_bash "github.com/tree-sitter/tree-sitter-bash/bindings/go"
ts_csharp "github.com/tree-sitter/tree-sitter-c-sharp/bindings/go"
ts_c "github.com/tree-sitter/tree-sitter-c/bindings/go"
ts_cpp "github.com/tree-sitter/tree-sitter-cpp/bindings/go"
ts_css "github.com/tree-sitter/tree-sitter-css/bindings/go"
ts_go "github.com/tree-sitter/tree-sitter-go/bindings/go"
ts_html "github.com/tree-sitter/tree-sitter-html/bindings/go"
ts_java "github.com/tree-sitter/tree-sitter-java/bindings/go"
ts_js "github.com/tree-sitter/tree-sitter-javascript/bindings/go"
ts_json "github.com/tree-sitter/tree-sitter-json/bindings/go"
ts_python "github.com/tree-sitter/tree-sitter-python/bindings/go"
ts_ruby "github.com/tree-sitter/tree-sitter-ruby/bindings/go"
ts_rust "github.com/tree-sitter/tree-sitter-rust/bindings/go"
ts_ts "github.com/tree-sitter/tree-sitter-typescript/bindings/go"
)
type languagePack struct {
// languagePack.id is the stable registry identifier (for example, "go").
id string
// languagePack.filetypes are normalized aliases resolved from buffer filetype.
filetypes []string
// languagePack.extensions are normalized filename extensions (for example, ".go").
extensions []string
// languagePack.newLanguage constructs the tree-sitter language handle.
newLanguage func() *sitter.Language
// languagePack.loadQuery returns highlights query source for this language.
loadQuery func() ([]byte, error)
}
// resolvedLanguage stores compiled runtime assets for one language.
//
// Instances are cached in languageRegistry.compiledByLang and reused by all
// buffers that resolve to the same language id.
type resolvedLanguage struct {
id string
language *sitter.Language
query *sitter.Query
}
// languageRegistry maps buffer metadata to language packs and lazily compiles
// tree-sitter language/query assets.
type languageRegistry struct {
packs []languagePack
byFiletype map[string]languagePack
byExtension map[string]languagePack
compiledByLang map[string]*resolvedLanguage
}
// newLanguageRegistry constructs the default in-process language registry.
//
// It registers built-in packs and prepares lookup maps for filetype and
// extension resolution.
func newLanguageRegistry() *languageRegistry {
r := &languageRegistry{
packs: []languagePack{},
byFiletype: map[string]languagePack{},
byExtension: map[string]languagePack{},
compiledByLang: map[string]*resolvedLanguage{},
}
r.register(languagePack{
id: "go",
filetypes: []string{"go", "golang"},
extensions: []string{".go"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_go.Language()) },
loadQuery: loadGoHighlightsQuery,
})
r.register(languagePack{
id: "javascript",
filetypes: []string{"javascript", "js", "jsx"},
extensions: []string{".js", ".mjs", ".cjs", ".jsx"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_js.Language()) },
loadQuery: loadJavaScriptHighlightsQuery,
})
r.register(languagePack{
id: "typescript",
filetypes: []string{"typescript", "ts"},
extensions: []string{".ts"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_ts.LanguageTypescript()) },
loadQuery: loadTypeScriptHighlightsQuery,
})
r.register(languagePack{
id: "tsx",
filetypes: []string{"tsx"},
extensions: []string{".tsx"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_ts.LanguageTSX()) },
loadQuery: loadTSXHighlightsQuery,
})
r.register(languagePack{
id: "python",
filetypes: []string{"python", "py"},
extensions: []string{".py"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_python.Language()) },
loadQuery: loadPythonHighlightsQuery,
})
r.register(languagePack{
id: "rust",
filetypes: []string{"rust", "rs"},
extensions: []string{".rs"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_rust.Language()) },
loadQuery: loadRustHighlightsQuery,
})
r.register(languagePack{
id: "bash",
filetypes: []string{"bash", "sh", "shell"},
extensions: []string{".sh", ".bash"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_bash.Language()) },
loadQuery: loadBashHighlightsQuery,
})
r.register(languagePack{
id: "json",
filetypes: []string{"json"},
extensions: []string{".json"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_json.Language()) },
loadQuery: loadJSONHighlightsQuery,
})
r.register(languagePack{
id: "css",
filetypes: []string{"css"},
extensions: []string{".css"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_css.Language()) },
loadQuery: loadCSSHighlightsQuery,
})
r.register(languagePack{
id: "html",
filetypes: []string{"html"},
extensions: []string{".html", ".htm"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_html.Language()) },
loadQuery: loadHTMLHighlightsQuery,
})
r.register(languagePack{
id: "c",
filetypes: []string{"c"},
extensions: []string{".c", ".h"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_c.Language()) },
loadQuery: loadCHighlightsQuery,
})
r.register(languagePack{
id: "cpp",
filetypes: []string{"cpp", "c++", "hpp"},
extensions: []string{".cc", ".cpp", ".cxx", ".hpp", ".hh", ".hxx"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_cpp.Language()) },
loadQuery: loadCppHighlightsQuery,
})
r.register(languagePack{
id: "java",
filetypes: []string{"java"},
extensions: []string{".java"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_java.Language()) },
loadQuery: loadJavaHighlightsQuery,
})
r.register(languagePack{
id: "csharp",
filetypes: []string{"csharp", "cs"},
extensions: []string{".cs"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_csharp.Language()) },
loadQuery: loadCSharpHighlightsQuery,
})
r.register(languagePack{
id: "ruby",
filetypes: []string{"ruby", "rb"},
extensions: []string{".rb"},
newLanguage: func() *sitter.Language { return sitter.NewLanguage(ts_ruby.Language()) },
loadQuery: loadRubyHighlightsQuery,
})
return r
}
// register adds a language pack and indexes it by normalized keys.
func (r *languageRegistry) register(pack languagePack) {
r.packs = append(r.packs, pack)
for _, ft := range pack.filetypes {
n := normalizeKey(ft)
if n != "" {
r.byFiletype[n] = pack
}
}
for _, ext := range pack.extensions {
n := normalizeExtension(ext)
if n != "" {
r.byExtension[n] = pack
}
}
}
// resolve returns compiled language/query assets for a buffer identity.
//
// Resolution is filetype-first, extension-second. Results are compiled once
// per language id and cached in compiledByLang.
func (r *languageRegistry) resolve(filetype, filename string) (*resolvedLanguage, bool, error) {
pack, ok := r.resolvePack(filetype, filename)
if !ok {
return nil, false, nil
}
if cached, ok := r.compiledByLang[pack.id]; ok {
return cached, true, nil
}
lang := pack.newLanguage()
if lang == nil {
return nil, false, fmt.Errorf("language %q did not provide a language handle", pack.id)
}
qBytes, err := pack.loadQuery()
if err != nil {
return nil, false, fmt.Errorf("load query for %q: %w", pack.id, err)
}
q, qErr := sitter.NewQuery(lang, string(qBytes))
if qErr != nil {
return nil, false, fmt.Errorf("compile query for %q: %w", pack.id, qErr)
}
resolved := &resolvedLanguage{id: pack.id, language: lang, query: q}
r.compiledByLang[pack.id] = resolved
return resolved, true, nil
}
// resolvePack finds a registered language pack using normalized buffer
// metadata without compiling queries.
func (r *languageRegistry) resolvePack(filetype, filename string) (languagePack, bool) {
if p, ok := r.byFiletype[normalizeKey(filetype)]; ok {
return p, true
}
if p, ok := r.byExtension[extensionOf(filename)]; ok {
return p, true
}
return languagePack{}, false
}
// normalizeKey canonicalizes filetype-like keys for registry lookups.
func normalizeKey(s string) string {
s = strings.TrimSpace(strings.ToLower(s))
s = strings.TrimPrefix(s, ".")
return s
}
// normalizeExtension canonicalizes extension keys and guarantees a leading
// dot for non-empty values.
func normalizeExtension(ext string) string {
ext = strings.TrimSpace(strings.ToLower(ext))
if ext == "" {
return ""
}
if !strings.HasPrefix(ext, ".") {
ext = "." + ext
}
return ext
}
// extensionOf extracts a normalized extension from a filename.
// Returns empty string when no usable extension is present.
func extensionOf(filename string) string {
name := strings.TrimSpace(strings.ToLower(filename))
if name == "" {
return ""
}
i := strings.LastIndex(name, ".")
if i <= 0 || i == len(name)-1 {
return ""
}
return name[i:]
}

View File

@ -1,45 +0,0 @@
package syntax
import "testing"
func TestLanguageRegistryResolveByFiletype(t *testing.T) {
r := newLanguageRegistry()
res, ok, err := r.resolve("go", "")
if err != nil {
t.Fatalf("resolve error: %v", err)
}
if !ok || res == nil {
t.Fatalf("expected go to resolve")
}
if res.id != "go" {
t.Fatalf("expected go id, got %q", res.id)
}
}
func TestLanguageRegistryResolveByExtension(t *testing.T) {
r := newLanguageRegistry()
res, ok, err := r.resolve("", "main.js")
if err != nil {
t.Fatalf("resolve error: %v", err)
}
if !ok || res == nil {
t.Fatalf("expected javascript to resolve")
}
if res.id != "javascript" {
t.Fatalf("expected javascript id, got %q", res.id)
}
}
func TestLanguageRegistryUnknown(t *testing.T) {
r := newLanguageRegistry()
res, ok, err := r.resolve("txt", "notes.txt")
if err != nil {
t.Fatalf("expected no error for unknown language, got: %v", err)
}
if ok || res != nil {
t.Fatalf("expected unknown language to not resolve")
}
}

View File

@ -1,520 +0,0 @@
package syntax
import (
"bytes"
"sort"
"strings"
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/style"
"github.com/charmbracelet/lipgloss"
sitter "github.com/tree-sitter/go-tree-sitter"
)
type TreeSitterEngine struct {
styles style.Styles
registry *languageRegistry
cache map[*core.Buffer]*bufferCache
}
type bufferCache struct {
built bool
lines map[int][]lipgloss.Style
count int
parser *sitter.Parser
tree *sitter.Tree
source []byte
dirtyAll bool
dirty []lineRange
langID string
language *sitter.Language
query *sitter.Query
}
type lineRange struct {
start int
end int
}
type captureRange struct {
startRow uint
startCol uint
endRow uint
endCol uint
name string
}
// NewTreeSitterEngine: Creates a new tree sitter engine with the styles
// provided attached.
//
// Currently, this engine only support GoLang. But more languages can be
// added with easy.
func NewTreeSitterEngine(styles style.Styles) *TreeSitterEngine {
return &TreeSitterEngine{
styles: styles,
registry: newLanguageRegistry(),
cache: map[*core.Buffer]*bufferCache{},
}
}
func (e *TreeSitterEngine) PrepareBuffer(buf *core.Buffer) {
// Cannot prepare a nil buffer
if buf == nil {
return
}
// Get the buffers cache and return if we are already "built" (ready to render).
bc := e.getCache(buf)
if bc.count != buf.LineCount() {
bc.dirtyAll = true
}
if bc.dirtyAll {
bc.built = false
}
if bc.built {
return
}
// If we do no support the buffer, load empty styles into the cache
lang, ok, err := e.resolveBufferLanguage(buf, bc)
if err != nil || !ok {
bc.lines = map[int][]lipgloss.Style{}
bc.built = true
return
}
_ = lang
e.buildFullBuffer(buf, bc)
}
func (e *TreeSitterEngine) LineStyleMap(buf *core.Buffer, line int) []lipgloss.Style {
if buf == nil {
return nil
}
e.PrepareBuffer(buf)
bc := e.getCache(buf)
if s, ok := bc.lines[line]; ok {
return s
}
runes := []rune(buf.Line(line))
out := make([]lipgloss.Style, len(runes))
for i := range out {
out[i] = e.styles.LineStyle
}
bc.lines[line] = out
return out
}
func (e *TreeSitterEngine) ApplyEdit(buf *core.Buffer, edit *core.BufferEdit) {
if buf == nil || edit == nil {
return
}
bc := e.getCache(buf)
lang, ok, err := e.resolveBufferLanguage(buf, bc)
if err != nil || !ok {
bc.built = false
bc.dirtyAll = true
return
}
_ = lang
if bc.parser == nil {
bc.parser = sitter.NewParser()
bc.parser.SetLanguage(bc.language)
}
if bc.tree == nil || len(bc.source) == 0 {
bc.dirtyAll = true
return
}
bc.tree.Edit(&sitter.InputEdit{
StartByte: edit.StartByte,
OldEndByte: edit.OldEndByte,
NewEndByte: edit.NewEndByte,
StartPosition: sitter.NewPoint(edit.StartPoint.Row, edit.StartPoint.Column),
OldEndPosition: sitter.NewPoint(edit.OldEndPoint.Row, edit.OldEndPoint.Column),
NewEndPosition: sitter.NewPoint(edit.NewEndPoint.Row, edit.NewEndPoint.Column),
})
newSource := buildBufferSource(buf)
newTree := bc.parser.Parse(newSource, bc.tree)
if newTree == nil {
bc.dirtyAll = true
return
}
changed := bc.tree.ChangedRanges(newTree)
newLineCount := buf.LineCount()
if newLineCount != bc.count {
bc.dirtyAll = true
bc.dirty = nil
} else {
startRow := int(edit.StartPoint.Row)
endRow := int(max(edit.OldEndPoint.Row, edit.NewEndPoint.Row))
addDirtyRange(bc, startRow, endRow)
for _, r := range changed {
addDirtyRange(bc, int(r.StartPoint.Row), int(r.EndPoint.Row))
}
}
bc.source = newSource
bc.tree.Close()
bc.tree = newTree
bc.built = false
}
// TreeSitterEngine.InvalidateBuffer: Deletes the entire buffers cache from the engine. If the
// buffer provided is nil, this function does nothing.
func (e *TreeSitterEngine) InvalidateBuffer(buf *core.Buffer) {
if buf == nil {
return
}
bc := e.getCache(buf)
bc.built = false
bc.dirtyAll = true
bc.dirty = nil
}
// TreeSitterEngine.InvalidateLines: Deletes lines between start and end (inclusive) from the
// buffers cache. Then marks the cache as "unbuilt." If the buffer provided is nil, this function
// does nothing.
func (e *TreeSitterEngine) InvalidateLines(buf *core.Buffer, startLine, endLine int) {
if buf == nil {
return
}
bc := e.getCache(buf)
addDirtyRange(bc, startLine, endLine)
bc.built = false
}
// TreeSitterEngine.supportsBuffer: Returns whether the buffer can be parsed and highlighted
// by the engine. When false, there should be a fallback.
func (e *TreeSitterEngine) resolveBufferLanguage(buf *core.Buffer, bc *bufferCache) (*resolvedLanguage, bool, error) {
if e.registry == nil {
e.registry = newLanguageRegistry()
}
resolved, ok, err := e.registry.resolve(buf.Filetype, buf.Filename)
if err != nil || !ok {
return nil, ok, err
}
if bc.langID != resolved.id {
bc.langID = resolved.id
bc.language = resolved.language
bc.query = resolved.query
if bc.parser != nil {
bc.parser.SetLanguage(bc.language)
}
bc.dirtyAll = true
bc.built = false
}
return resolved, true, nil
}
// TreeSitterEngine.getCache: Returns the buffers cache. If the cache does not exist, a new one
// is created and applied to the engines cache map.
func (e *TreeSitterEngine) getCache(buf *core.Buffer) *bufferCache {
if bc, ok := e.cache[buf]; ok {
return bc
}
bc := &bufferCache{lines: map[int][]lipgloss.Style{}}
e.cache[buf] = bc
return bc
}
func (e *TreeSitterEngine) buildFullBuffer(buf *core.Buffer, bc *bufferCache) {
lineCount := buf.LineCount()
// Load the lines into memory. There is no method for this due to the buffers
// internal implementation using a gap buffer. So the "Lines" property is of
// type []*GapBuffer.
lines := make([]string, lineCount)
for i := range lineCount {
lines[i] = buf.Line(i)
}
fullRebuild := bc.dirtyAll || len(bc.lines) == 0 || len(bc.dirty) == 0
if fullRebuild {
bc.lines = map[int][]lipgloss.Style{}
for i := range lineCount {
bc.lines[i] = defaultLineStyles(lines[i], e.styles.LineStyle)
}
} else {
dirty := normalizedDirtyRanges(bc.dirty, lineCount)
for _, r := range dirty {
for i := r.start; i <= r.end; i++ {
bc.lines[i] = defaultLineStyles(lines[i], e.styles.LineStyle)
}
}
}
source := buildBufferSource(buf)
useCurrentTree := bc.tree != nil && bytes.Equal(bc.source, source)
if bc.parser == nil {
bc.parser = sitter.NewParser()
bc.parser.SetLanguage(bc.language)
}
if !useCurrentTree {
var baseTree *sitter.Tree
if bc.tree != nil {
baseTree = bc.tree
}
tree := bc.parser.Parse(source, baseTree)
if tree == nil {
bc.built = true
return
}
if bc.tree != nil {
bc.tree.Close()
}
bc.tree = tree
bc.source = source
}
root := bc.tree.RootNode()
cursor := sitter.NewQueryCursor()
defer cursor.Close()
var captures []captureRange
if fullRebuild {
iter := cursor.Captures(bc.query, root, source)
captures = append(captures, collectCaptures(iter, bc.query)...)
} else {
dirty := normalizedDirtyRanges(bc.dirty, lineCount)
for _, r := range dirty {
queryStart := max(0, r.start-1)
queryEnd := min(lineCount-1, r.end+1)
rangeCursor := sitter.NewQueryCursor()
rangeCursor.SetPointRange(
sitter.NewPoint(uint(queryStart), 0),
sitter.NewPoint(uint(queryEnd+1), 0),
)
iter := rangeCursor.Captures(bc.query, root, source)
captures = append(captures, collectCaptures(iter, bc.query)...)
rangeCursor.Close()
}
}
// Sort the captures in order of their character occurrence in the file
sort.Slice(captures, func(i, j int) bool {
if captures[i].startRow == captures[j].startRow {
if captures[i].startCol == captures[j].startCol {
if captures[i].endRow == captures[j].endRow {
return captures[i].endCol > captures[j].endCol
}
return captures[i].endRow > captures[j].endRow
}
return captures[i].startCol < captures[j].startCol
}
return captures[i].startRow < captures[j].startRow
})
// Basically, this code works by rewriting the same range and the last capture wins.
// This is a great spot for optimization: No need to draw many times, just pick the best one.
// Or maybe when we sort, if we find ones that are the same, remove the first one, and then
// we just keep the last one. Then this code can stay the same but will not suffer so many
// rewrites.
targetDirty := normalizedDirtyRanges(bc.dirty, lineCount)
for _, c := range captures {
sty := style.CaptureStyle(e.styles.LineStyle, c.name)
for row := c.startRow; row <= c.endRow; row++ {
if int(row) >= len(lines) {
break
}
if !fullRebuild && !rowInRanges(int(row), targetDirty) {
continue
}
lineBytes := []byte(lines[row])
startByteCol := uint(0)
if row == c.startRow {
startByteCol = c.startCol
}
endByteCol := uint(len(lineBytes))
if row == c.endRow {
endByteCol = min(c.endCol, uint(len(lineBytes)))
}
startRune := byteColToRuneIndex(lineBytes, int(startByteCol))
endRune := byteColToRuneIndex(lineBytes, int(endByteCol))
rowStyles := bc.lines[int(row)]
if startRune < 0 {
startRune = 0
}
if endRune > len(rowStyles) {
endRune = len(rowStyles)
}
if startRune >= endRune {
continue
}
for i := startRune; i < endRune; i++ {
rowStyles[i] = sty
}
bc.lines[int(row)] = rowStyles
}
}
bc.dirtyAll = false
bc.dirty = nil
bc.count = lineCount
bc.built = true
}
func addDirtyRange(bc *bufferCache, start, end int) {
if bc == nil {
return
}
if end < start {
start, end = end, start
}
if start < 0 {
start = 0
}
if end < 0 {
end = 0
}
bc.dirty = append(bc.dirty, lineRange{start: start, end: end})
bc.dirty = mergeRanges(bc.dirty)
}
func normalizedDirtyRanges(ranges []lineRange, lineCount int) []lineRange {
if lineCount <= 0 || len(ranges) == 0 {
return nil
}
clamped := make([]lineRange, 0, len(ranges))
for _, r := range ranges {
start := max(0, r.start)
end := min(lineCount-1, r.end)
if start > end {
continue
}
clamped = append(clamped, lineRange{start: start, end: end})
}
return mergeRanges(clamped)
}
func mergeRanges(ranges []lineRange) []lineRange {
if len(ranges) == 0 {
return nil
}
sort.Slice(ranges, func(i, j int) bool {
if ranges[i].start == ranges[j].start {
return ranges[i].end < ranges[j].end
}
return ranges[i].start < ranges[j].start
})
merged := make([]lineRange, 0, len(ranges))
cur := ranges[0]
for i := 1; i < len(ranges); i++ {
n := ranges[i]
if n.start <= cur.end+1 {
if n.end > cur.end {
cur.end = n.end
}
continue
}
merged = append(merged, cur)
cur = n
}
merged = append(merged, cur)
return merged
}
func rowInRanges(row int, ranges []lineRange) bool {
for _, r := range ranges {
if row >= r.start && row <= r.end {
return true
}
}
return false
}
func defaultLineStyles(line string, base lipgloss.Style) []lipgloss.Style {
runes := []rune(line)
row := make([]lipgloss.Style, len(runes))
for i := range row {
row[i] = base
}
return row
}
func collectCaptures(iter sitter.QueryCaptures, query *sitter.Query) []captureRange {
if query == nil {
return nil
}
names := query.CaptureNames()
out := []captureRange{}
for match, captureIdx := iter.Next(); match != nil; match, captureIdx = iter.Next() {
capture := match.Captures[captureIdx]
if int(capture.Index) >= len(names) {
continue
}
name := names[capture.Index]
if name == "spell" {
continue
}
node := capture.Node
start := node.StartPosition()
end := node.EndPosition()
out = append(out, captureRange{
startRow: start.Row,
startCol: start.Column,
endRow: end.Row,
endCol: end.Column,
name: name,
})
}
return out
}
func buildBufferSource(buf *core.Buffer) []byte {
lineCount := buf.LineCount()
if lineCount == 0 {
return []byte{}
}
lines := make([]string, lineCount)
for i := range lineCount {
lines[i] = buf.Line(i)
}
return []byte(strings.Join(lines, "\n"))
}
func byteColToRuneIndex(line []byte, byteCol int) int {
if byteCol <= 0 {
return 0
}
if byteCol >= len(line) {
return len([]rune(string(line)))
}
prefix := line[:byteCol]
return len([]rune(string(prefix)))
}

View File

@ -1,242 +0,0 @@
package syntax
import (
"fmt"
"strings"
"testing"
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/style"
"github.com/charmbracelet/lipgloss"
)
func TestTreeSitterEngineHighlightsGoKeywordAndString(t *testing.T) {
b := core.NewBufferBuilder().
WithFilename("sample.go").
WithFiletype("go").
WithLines([]string{
"package main",
"func main() {",
" s := \"hi\"",
"}",
}).
Build()
buf := &b
engine := NewTreeSitterEngine(style.DefaultStyles())
engine.PrepareBuffer(buf)
base := engine.styles.LineStyle
line0 := buf.Line(0)
map0 := engine.LineStyleMap(buf, 0)
if len(map0) != len([]rune(line0)) {
t.Fatalf("line 0 style map length mismatch")
}
if len(map0) == 0 || styleEquivalent(map0[0], base) {
t.Fatalf("expected 'package' keyword to be highlighted")
}
line2 := buf.Line(2)
stringStart := strings.Index(line2, "\"hi\"")
if stringStart < 0 {
t.Fatalf("test setup failed: string literal not found")
}
map2 := engine.LineStyleMap(buf, 2)
if styleEquivalent(map2[stringStart+1], base) {
t.Fatalf("expected string contents to be highlighted")
}
}
func TestTreeSitterEngineHighlightsMultilineRawString(t *testing.T) {
b := core.NewBufferBuilder().
WithFilename("sample.go").
WithFiletype("go").
WithLines([]string{
"package main",
"func main() {",
" s := `hello",
"world`",
" println(s)",
"}",
}).
Build()
buf := &b
engine := NewTreeSitterEngine(style.DefaultStyles())
engine.PrepareBuffer(buf)
base := engine.styles.LineStyle
map3 := engine.LineStyleMap(buf, 3)
if len(map3) == 0 {
t.Fatalf("expected style map on multiline raw string line")
}
if styleEquivalent(map3[0], base) {
t.Fatalf("expected multiline raw string line to be highlighted")
}
}
func TestTreeSitterEngineApplyEditUpdatesStyleCategory(t *testing.T) {
b := core.NewBufferBuilder().
WithFilename("sample.go").
WithFiletype("go").
WithLines([]string{
"package main",
"func main() {",
" x := 123",
"}",
}).
Build()
buf := &b
engine := NewTreeSitterEngine(style.DefaultStyles())
engine.PrepareBuffer(buf)
oldLine := buf.Line(2)
oldIdx := strings.Index(oldLine, "123")
if oldIdx < 0 {
t.Fatalf("test setup failed: number not found")
}
oldMap := engine.LineStyleMap(buf, 2)
oldStyle := oldMap[oldIdx]
var edit *core.BufferEdit
buf.OnChange = func(change core.BufferChange) {
edit = change.Edit
}
buf.SetLine(2, " x := \"abc\"")
if edit == nil {
t.Fatalf("expected edit metadata from SetLine")
}
engine.ApplyEdit(buf, edit)
engine.PrepareBuffer(buf)
newLine := buf.Line(2)
newIdx := strings.Index(newLine, "abc")
if newIdx < 0 {
t.Fatalf("test setup failed: string not found")
}
newMap := engine.LineStyleMap(buf, 2)
newStyle := newMap[newIdx]
if styleEquivalent(newStyle, engine.styles.LineStyle) {
t.Fatalf("expected updated string to be highlighted")
}
if styleEquivalent(oldStyle, newStyle) {
t.Fatalf("expected style category to change from number to string")
}
}
func TestTreeSitterEngineApplyEditLineCountChangeForcesFullDirty(t *testing.T) {
b := core.NewBufferBuilder().
WithFilename("sample.go").
WithFiletype("go").
WithLines([]string{"package main", "func main() {}"}).
Build()
buf := &b
engine := NewTreeSitterEngine(style.DefaultStyles())
engine.PrepareBuffer(buf)
bc := engine.getCache(buf)
var edit *core.BufferEdit
buf.OnChange = func(change core.BufferChange) {
edit = change.Edit
}
buf.InsertLine(1, "var x = 1")
if edit == nil {
t.Fatalf("expected edit metadata from InsertLine")
}
engine.ApplyEdit(buf, edit)
if !bc.dirtyAll {
t.Fatalf("expected line count change to set dirtyAll")
}
engine.PrepareBuffer(buf)
if !bc.built {
t.Fatalf("expected cache rebuilt after prepare")
}
if bc.count != buf.LineCount() {
t.Fatalf("expected cache line count to match buffer")
}
if bc.dirtyAll {
t.Fatalf("expected dirtyAll to clear after rebuild")
}
}
func TestTreeSitterEngineUnsupportedBufferFallsBackToDefaultStyles(t *testing.T) {
b := core.NewBufferBuilder().
WithFilename("notes.txt").
WithFiletype("txt").
WithLines([]string{"just text", "with no language"}).
Build()
buf := &b
engine := NewTreeSitterEngine(style.DefaultStyles())
engine.PrepareBuffer(buf)
base := engine.styles.LineStyle
line := buf.Line(0)
m := engine.LineStyleMap(buf, 0)
if len(m) != len([]rune(line)) {
t.Fatalf("style map length mismatch on fallback buffer")
}
for i := range m {
if !styleEquivalent(m[i], base) {
t.Fatalf("expected default style for unsupported filetype at rune %d", i)
}
}
}
func TestTreeSitterEngineLastLineEditDoesNotPanicAndRebuilds(t *testing.T) {
b := core.NewBufferBuilder().
WithFilename("sample.go").
WithFiletype("go").
WithLines([]string{"package main", "func main() {", " return", "}"}).
Build()
buf := &b
engine := NewTreeSitterEngine(style.DefaultStyles())
engine.PrepareBuffer(buf)
bc := engine.getCache(buf)
var edit *core.BufferEdit
buf.OnChange = func(change core.BufferChange) {
edit = change.Edit
}
buf.SetLine(3, "// end")
if edit == nil {
t.Fatalf("expected edit metadata for last line change")
}
engine.ApplyEdit(buf, edit)
engine.PrepareBuffer(buf)
if !bc.built {
t.Fatalf("expected cache built after last-line edit")
}
if len(bc.dirty) != 0 {
t.Fatalf("expected dirty ranges cleared after rebuild")
}
}
func styleEquivalent(a, b lipgloss.Style) bool {
return styleSignature(a) == styleSignature(b)
}
func styleSignature(s lipgloss.Style) string {
return fmt.Sprintf(
"fg=%v,bg=%v,bold=%v,italic=%v,underline=%v,reverse=%v",
s.GetForeground(),
s.GetBackground(),
s.GetBold(),
s.GetItalic(),
s.GetUnderline(),
s.GetReverse(),
)
}

View File

@ -1,37 +0,0 @@
package syntax
import (
"fmt"
"testing"
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/style"
)
func BenchmarkTreeSitterPrepareAndIncrementalEdit(b *testing.B) {
lines := make([]string, 0, 2000)
lines = append(lines, "package main", "", "func main() {")
for i := 0; i < 1990; i++ {
lines = append(lines, fmt.Sprintf(" v%d := %d", i, i))
}
lines = append(lines, "}")
bld := core.NewBufferBuilder().WithFilename("bench.go").WithFiletype("go").WithLines(lines).Build()
buf := &bld
eng := NewTreeSitterEngine(style.DefaultStyles())
eng.PrepareBuffer(buf)
b.ResetTimer()
for i := 0; i < b.N; i++ {
lineIdx := 10 + (i % 1000)
buf.SetLine(lineIdx, fmt.Sprintf(" v%d := %d", lineIdx, i))
oldSource := buildBufferSource(buf)
_ = oldSource
// Synthetic direct invalidate path benchmark (current API entrypoints)
eng.InvalidateLines(buf, lineIdx, lineIdx)
eng.PrepareBuffer(buf)
}
}

View File

@ -1,84 +0,0 @@
package syntax
import (
"testing"
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/style"
)
func TestTreeSitterEngineApplyEditMarksDirtyWithoutFullInvalidation(t *testing.T) {
b := core.NewBufferBuilder().
WithFilename("x.go").
WithFiletype("go").
WithLines([]string{"package main", "func main() {", "println(1)", "}"}).
Build()
buf := &b
engine := NewTreeSitterEngine(style.DefaultStyles())
engine.PrepareBuffer(buf)
bc := engine.getCache(buf)
if !bc.built {
t.Fatalf("expected cache to be built after prepare")
}
var edit *core.BufferEdit
buf.OnChange = func(change core.BufferChange) {
edit = change.Edit
}
buf.SetLine(2, "println(22)")
if edit == nil {
t.Fatalf("expected setline to emit edit metadata")
}
engine.ApplyEdit(buf, edit)
if bc.built {
t.Fatalf("expected cache to become unbuilt after apply edit")
}
if bc.dirtyAll {
t.Fatalf("expected non-structural edit to avoid dirtyAll")
}
if len(bc.dirty) == 0 {
t.Fatalf("expected dirty ranges after apply edit")
}
engine.PrepareBuffer(buf)
if !bc.built {
t.Fatalf("expected cache rebuilt after prepare")
}
if len(bc.dirty) != 0 {
t.Fatalf("expected dirty ranges cleared after rebuild")
}
}
func TestTreeSitterEngineInvalidateLinesAndBuffer(t *testing.T) {
b := core.NewBufferBuilder().
WithFilename("x.go").
WithFiletype("go").
WithLines([]string{"package main", "func main() {}"}).
Build()
buf := &b
engine := NewTreeSitterEngine(style.DefaultStyles())
engine.PrepareBuffer(buf)
bc := engine.getCache(buf)
engine.InvalidateLines(buf, 1, 1)
if bc.built {
t.Fatalf("expected invalidate lines to unset built")
}
if bc.dirtyAll {
t.Fatalf("expected line invalidation to avoid dirtyAll")
}
if len(bc.dirty) == 0 {
t.Fatalf("expected dirty line ranges")
}
engine.InvalidateBuffer(buf)
if !bc.dirtyAll {
t.Fatalf("expected invalidate buffer to set dirtyAll")
}
}

View File

@ -1,35 +0,0 @@
package syntax
import "testing"
func FuzzByteColToRuneIndexInvariants(f *testing.F) {
f.Add("abc", 0)
f.Add("aéb", 1)
f.Add("こんにちは", 5)
f.Add("", 0)
f.Fuzz(func(t *testing.T, s string, col int) {
line := []byte(s)
idx := byteColToRuneIndex(line, col)
runes := []rune(s)
if idx < 0 || idx > len(runes) {
t.Fatalf("rune index out of bounds: idx=%d len=%d", idx, len(runes))
}
if col <= 0 && idx != 0 {
t.Fatalf("expected idx 0 for non-positive col, got %d", idx)
}
if col >= len(line) && idx != len(runes) {
t.Fatalf("expected idx len(runes) for col>=len(bytes), got %d", idx)
}
if col > 0 && col < len(line) {
expected := len([]rune(string(line[:col])))
if idx != expected {
t.Fatalf("expected idx %d got %d", expected, idx)
}
}
})
}

View File

@ -1,50 +0,0 @@
package syntax
import "testing"
func TestMergeRanges(t *testing.T) {
in := []lineRange{{start: 5, end: 8}, {start: 1, end: 2}, {start: 2, end: 4}, {start: 10, end: 10}}
out := mergeRanges(in)
if len(out) != 2 {
t.Fatalf("expected 2 merged ranges, got %d", len(out))
}
if out[0].start != 1 || out[0].end != 8 {
t.Fatalf("unexpected first merged range: %+v", out[0])
}
if out[1].start != 10 || out[1].end != 10 {
t.Fatalf("unexpected second merged range: %+v", out[1])
}
}
func TestNormalizedDirtyRanges(t *testing.T) {
ranges := []lineRange{{start: -2, end: 1}, {start: 3, end: 99}}
out := normalizedDirtyRanges(ranges, 5)
if len(out) != 2 {
t.Fatalf("expected 2 normalized ranges, got %d", len(out))
}
if out[0].start != 0 || out[0].end != 1 {
t.Fatalf("unexpected first normalized range: %+v", out[0])
}
if out[1].start != 3 || out[1].end != 4 {
t.Fatalf("unexpected second normalized range: %+v", out[1])
}
}
func TestByteColToRuneIndexUTF8(t *testing.T) {
line := []byte("aéb")
if got := byteColToRuneIndex(line, 0); got != 0 {
t.Fatalf("expected 0, got %d", got)
}
if got := byteColToRuneIndex(line, 1); got != 1 {
t.Fatalf("expected 1, got %d", got)
}
if got := byteColToRuneIndex(line, 3); got != 2 {
t.Fatalf("expected 2, got %d", got)
}
if got := byteColToRuneIndex(line, len(line)); got != 3 {
t.Fatalf("expected 3, got %d", got)
}
}

View File

@ -1,112 +0,0 @@
package syntax
import (
"fmt"
"testing"
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/style"
)
type seqOp func(*core.Buffer, *core.Window)
func TestTreeSitterEngineEditSequences(t *testing.T) {
cases := []struct {
name string
lines []string
opList []seqOp
}{
{
name: "setline and insertline",
lines: []string{"package main", "func main() {", " x := 1", "}"},
opList: []seqOp{
func(b *core.Buffer, _ *core.Window) { b.SetLine(2, " x := 2") },
func(b *core.Buffer, _ *core.Window) { b.InsertLine(3, " println(x)") },
},
},
{
name: "delete middle line",
lines: []string{"package main", "func main() {", " x := 1", " y := 2", "}"},
opList: []seqOp{
func(b *core.Buffer, _ *core.Window) { b.DeleteLine(3) },
},
},
{
name: "multiline string evolve",
lines: []string{"package main", "func main() {", " s := `a", "b`", " _ = s", "}"},
opList: []seqOp{
func(b *core.Buffer, _ *core.Window) { b.SetLine(2, " s := `alpha") },
func(b *core.Buffer, _ *core.Window) { b.SetLine(3, "beta`") },
},
},
{
name: "undo redo sequence",
lines: []string{"package main", "func main() {", " v := 3", "}"},
opList: []seqOp{
func(b *core.Buffer, w *core.Window) {
b.UndoStack.BeginBlock(w.Cursor)
b.SetLine(2, " v := 9")
b.UndoStack.EndBlock(core.Position{Line: 2, Col: 8})
},
func(b *core.Buffer, w *core.Window) { _ = b.Undo(w) },
func(b *core.Buffer, w *core.Window) { _ = b.Redo(w) },
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
b := core.NewBufferBuilder().
WithFilename("seq.go").
WithFiletype("go").
WithLines(tc.lines).
Build()
buf := &b
w := core.NewWindowBuilder().WithBuffer(buf).WithDimensions(120, 40).Build()
win := &w
engine := NewTreeSitterEngine(style.DefaultStyles())
buf.OnChange = func(change core.BufferChange) {
if change.Edit != nil {
engine.ApplyEdit(buf, change.Edit)
} else {
engine.InvalidateBuffer(buf)
}
}
engine.PrepareBuffer(buf)
assertEngineInvariants(t, engine, buf, "initial")
for i, op := range tc.opList {
op(buf, win)
engine.PrepareBuffer(buf)
assertEngineInvariants(t, engine, buf, fmt.Sprintf("after op %d", i+1))
}
})
}
}
func assertEngineInvariants(t *testing.T, engine *TreeSitterEngine, buf *core.Buffer, phase string) {
t.Helper()
bc := engine.getCache(buf)
if !bc.built {
t.Fatalf("%s: expected built cache", phase)
}
if bc.dirtyAll {
t.Fatalf("%s: expected dirtyAll=false after prepare", phase)
}
if len(bc.dirty) != 0 {
t.Fatalf("%s: expected no pending dirty ranges", phase)
}
for i := 0; i < buf.LineCount(); i++ {
line := buf.Line(i)
m := engine.LineStyleMap(buf, i)
if len(m) != len([]rune(line)) {
t.Fatalf("%s: line %d style length mismatch: got %d want %d", phase, i, len(m), len([]rune(line)))
}
}
}

View File

@ -1,7 +1,43 @@
package theme
// RegisterAll is retained as a no-op for compatibility while Chroma-based
// theme loading is removed.
import (
"embed"
"fmt"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/styles"
)
//go:embed themes/*
var themeFS embed.FS
// RegisterAll: Registers all XML theme files embedded in the themes/ directory
// with chroma's style registry. After calling this, styles.Get() will recognize
// any theme defined in those files.
func RegisterAll() error {
entries, err := themeFS.ReadDir("themes")
if err != nil {
return fmt.Errorf("failed to read embedded themes directory: %w", err)
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
f, err := themeFS.Open("themes/" + entry.Name())
if err != nil {
return fmt.Errorf("failed to open theme %s: %w", entry.Name(), err)
}
style, err := chroma.NewXMLStyle(f)
f.Close()
if err != nil {
return fmt.Errorf("failed to parse theme %s: %w", entry.Name(), err)
}
styles.Register(style)
}
return nil
}

373
main.go
View File

@ -1,373 +0,0 @@
package main
import (
"fmt"
"os"
"sort"
"strings"
sitter "github.com/tree-sitter/go-tree-sitter"
ts_go "github.com/tree-sitter/tree-sitter-go/bindings/go"
)
// Sample Go source to highlight
const source = `
package main
func main () {
println("Hello" + 5)
}
`
type Highlight struct {
StartRow uint // 0-indexed line number
StartCol uint // 0-indexed column (bytes)
EndRow uint
EndCol uint
Capture string
}
// Theme maps capture names to ANSI escape codes.
// In your editor you'd use lipgloss styles instead.
var theme = map[string]string{
"keyword": "\033[1;35m", // bold magenta
"keyword.type": "\033[1;35m",
"keyword.function": "\033[1;35m",
"keyword.return": "\033[1;35m",
"keyword.coroutine": "\033[1;35m",
"keyword.repeat": "\033[1;35m",
"keyword.import": "\033[1;35m",
"keyword.conditional": "\033[1;35m",
"type": "\033[33m", // yellow
"type.builtin": "\033[33m",
"type.definition": "\033[1;33m", // bold yellow
"function": "\033[1;34m", // bold blue
"function.call": "\033[34m", // blue
"function.method": "\033[34m",
"function.method.call": "\033[34m",
"function.builtin": "\033[1;31m",
"variable": "\033[37m", // white
"variable.parameter": "\033[3;37m", // italic white
"variable.member": "\033[37m",
"constant": "\033[1;36m", // bold cyan
"constant.builtin": "\033[1;36m",
"string": "\033[32m", // green
"string.escape": "\033[1;32m",
"number": "\033[36m", // cyan
"number.float": "\033[36m",
"boolean": "\033[36m",
"operator": "\033[93m", // bright yellow
"comment": "\033[2;37m", // dim
"comment.documentation": "\033[2;37m",
"module": "\033[35m", // magenta
"label": "\033[33m",
"property": "\033[37m",
"constructor": "\033[1;33m",
"punctuation.delimiter": "\033[37m",
"punctuation.bracket": "\033[37m",
}
const reset = "\033[0m"
func main() {
code := []byte(source)
lines := strings.Split(source, "\n")
// --- Step 1: Parse ---
lang := sitter.NewLanguage(ts_go.Language())
parser := sitter.NewParser()
defer parser.Close()
parser.SetLanguage(lang)
tree := parser.Parse(code, nil)
defer tree.Close()
root := tree.RootNode()
// --- Step 2: Load query from highlights.scm ---
queryBytes, err := os.ReadFile("queries/go/highlights.scm")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to read highlights.scm: %v\n", err)
return
}
query, queryErr := sitter.NewQuery(lang, string(queryBytes))
if queryErr != nil {
fmt.Fprintf(os.Stderr, "Query error: %v\n", queryErr)
return
}
defer query.Close()
// --- Step 3: Run query ---
cursor := sitter.NewQueryCursor()
defer cursor.Close()
captures := cursor.Captures(query, root, code)
var highlights []Highlight
for match, captureIdx := captures.Next(); match != nil; match, captureIdx = captures.Next() {
capture := match.Captures[captureIdx]
captureName := query.CaptureNames()[capture.Index]
// Skip @spell — it's a nvim spellcheck hint, not a highlight
if captureName == "spell" {
continue
}
node := capture.Node
start := node.StartPosition()
end := node.EndPosition()
highlights = append(highlights, Highlight{
StartRow: start.Row,
StartCol: start.Column,
EndRow: end.Row,
EndCol: end.Column,
Capture: captureName,
})
}
// --- Step 4: Show captures with positions ---
fmt.Println("=== Captures (row:col → row:col) ===")
for _, h := range highlights {
// Extract text for display using the source lines
text := extractText(lines, h)
fmt.Printf(" %d:%-2d → %d:%-2d @%-22s %q\n",
h.StartRow, h.StartCol, h.EndRow, h.EndCol, h.Capture, text)
}
fmt.Println()
// --- Step 5: Render with colors using row:col positions ---
// Build a per-line map of column ranges to capture names.
// Sort so wider (less specific) ranges come first — last writer wins.
sort.Slice(highlights, func(i, j int) bool {
if highlights[i].StartRow == highlights[j].StartRow {
if highlights[i].StartCol == highlights[j].StartCol {
// Wider range first so more specific overwrites it
if highlights[i].EndRow == highlights[j].EndRow {
return highlights[i].EndCol > highlights[j].EndCol
}
return highlights[i].EndRow > highlights[j].EndRow
}
return highlights[i].StartCol < highlights[j].StartCol
}
return highlights[i].StartRow < highlights[j].StartRow
})
// captureAt[row][col] = capture name (last writer wins)
captureAt := make(map[uint]map[uint]string)
for _, h := range highlights {
for row := h.StartRow; row <= h.EndRow; row++ {
if captureAt[row] == nil {
captureAt[row] = make(map[uint]string)
}
startCol := uint(0)
if row == h.StartRow {
startCol = h.StartCol
}
endCol := uint(len(lines[row]))
if row == h.EndRow {
endCol = h.EndCol
}
for col := startCol; col < endCol; col++ {
captureAt[row][col] = h.Capture
}
}
}
fmt.Println("=== Colored output ===")
printColored(lines, captureAt)
// =====================================================================
// INCREMENTAL PARSING DEMO
// =====================================================================
// When a user types in your editor, you don't re-parse the whole file.
// Instead you:
// 1. Tell the OLD tree what changed (tree.Edit)
// 2. Parse the new source, passing the old tree
// 3. Tree-sitter reuses unchanged nodes and only re-parses the edit
// 4. Use ChangedRanges to know which lines need re-highlighting
//
// This is O(edit size + log(file size)) instead of O(file size).
fmt.Println("\n========================================")
fmt.Println("=== INCREMENTAL PARSE DEMO ===")
fmt.Println("========================================")
fmt.Println()
// Simulate: user changes "Hello" → "Goodbye" on row 4
// Before: println("Hello" + 5)
// After: println("Goodbye" + 5)
oldSource := source
newSource := strings.Replace(oldSource, `"Hello"`, `"Goodbye"`, 1)
// Find where the edit happened (in a real editor you already know this
// from the keystroke — you don't need to search for it)
editStart := strings.Index(oldSource, `"Hello"`)
oldEnd := editStart + len(`"Hello"`)
newEnd := editStart + len(`"Goodbye"`)
// Convert byte offset to row:col for the InputEdit
editStartPoint := byteToPoint(oldSource, uint(editStart))
oldEndPoint := byteToPoint(oldSource, uint(oldEnd))
newEndPoint := byteToPoint(newSource, uint(newEnd))
fmt.Printf("Edit: replaced %q → %q\n", "Hello", "Goodbye")
fmt.Printf(" at byte %d, row %d col %d\n", editStart, editStartPoint.Row, editStartPoint.Column)
fmt.Println()
// Step 1: Tell the old tree what changed
tree.Edit(&sitter.InputEdit{
StartByte: uint(editStart),
OldEndByte: uint(oldEnd),
NewEndByte: uint(newEnd),
StartPosition: editStartPoint,
OldEndPosition: oldEndPoint,
NewEndPosition: newEndPoint,
})
// Step 2: Parse the new source, passing the old (edited) tree.
// Tree-sitter will REUSE all nodes that weren't affected by the edit
// and only re-parse the region around the change.
newCode := []byte(newSource)
newTree := parser.Parse(newCode, tree)
defer newTree.Close()
// Step 3: See exactly which ranges changed
changedRanges := newTree.ChangedRanges(tree)
fmt.Printf("Changed ranges: %d\n", len(changedRanges))
for i, r := range changedRanges {
fmt.Printf(" range %d: row %d:%d → row %d:%d\n",
i, r.StartPoint.Row, r.StartPoint.Column, r.EndPoint.Row, r.EndPoint.Column)
}
fmt.Println()
// In your editor, you'd ONLY re-run the highlight query on the changed
// ranges (using cursor.SetByteRange or cursor.SetPointRange), then
// update just those lines in your display. Everything else stays cached.
// For this demo, let's re-highlight the full new tree to show the result
newRoot := newTree.RootNode()
newLines := strings.Split(newSource, "\n")
cursor2 := sitter.NewQueryCursor()
defer cursor2.Close()
newCaptures := cursor2.Captures(query, newRoot, newCode)
var newHighlights []Highlight
for match, captureIdx := newCaptures.Next(); match != nil; match, captureIdx = newCaptures.Next() {
capture := match.Captures[captureIdx]
captureName := query.CaptureNames()[capture.Index]
if captureName == "spell" {
continue
}
node := capture.Node
start := node.StartPosition()
end := node.EndPosition()
newHighlights = append(newHighlights, Highlight{
StartRow: start.Row, StartCol: start.Column,
EndRow: end.Row, EndCol: end.Column,
Capture: captureName,
})
}
sort.Slice(newHighlights, func(i, j int) bool {
if newHighlights[i].StartRow == newHighlights[j].StartRow {
if newHighlights[i].StartCol == newHighlights[j].StartCol {
if newHighlights[i].EndRow == newHighlights[j].EndRow {
return newHighlights[i].EndCol > newHighlights[j].EndCol
}
return newHighlights[i].EndRow > newHighlights[j].EndRow
}
return newHighlights[i].StartCol < newHighlights[j].StartCol
}
return newHighlights[i].StartRow < newHighlights[j].StartRow
})
newCaptureAt := make(map[uint]map[uint]string)
for _, h := range newHighlights {
for row := h.StartRow; row <= h.EndRow; row++ {
if newCaptureAt[row] == nil {
newCaptureAt[row] = make(map[uint]string)
}
startCol := uint(0)
if row == h.StartRow {
startCol = h.StartCol
}
endCol := uint(len(newLines[row]))
if row == h.EndRow {
endCol = h.EndCol
}
for col := startCol; col < endCol; col++ {
newCaptureAt[row][col] = h.Capture
}
}
}
fmt.Println("=== After edit (colored output) ===")
printColored(newLines, newCaptureAt)
}
// printColored renders source lines with ANSI colors based on the capture map.
func printColored(lines []string, captureAt map[uint]map[uint]string) {
for row, line := range lines {
currentCapture := ""
for col := uint(0); col < uint(len(line)); col++ {
cap := ""
if rowMap, ok := captureAt[uint(row)]; ok {
cap = rowMap[col]
}
if cap != currentCapture {
if currentCapture != "" {
fmt.Print(reset)
}
if color, ok := theme[cap]; ok {
fmt.Print(color)
}
currentCapture = cap
}
fmt.Print(string(line[col]))
}
if currentCapture != "" {
fmt.Print(reset)
}
fmt.Println()
}
}
// byteToPoint converts a byte offset into a row:col Point.
func byteToPoint(src string, offset uint) sitter.Point {
row := uint(0)
col := uint(0)
for i := range offset {
if src[i] == '\n' {
row++
col = 0
} else {
col++
}
}
return sitter.NewPoint(row, col)
}
// extractText pulls the highlighted text from source lines using row:col positions.
func extractText(lines []string, h Highlight) string {
if h.StartRow == h.EndRow {
line := lines[h.StartRow]
end := min(h.EndCol, uint(len(line)))
return line[h.StartCol:end]
}
// Multi-line highlight (rare, but possible for block comments etc.)
var result string
for row := h.StartRow; row <= h.EndRow; row++ {
line := lines[row]
start := uint(0)
if row == h.StartRow {
start = h.StartCol
}
end := uint(len(line))
if row == h.EndRow {
end = h.EndCol
}
if result != "" {
result += "\n"
}
result += line[start:end]
}
return result
}