package syntax import ( "fmt" "strings" "testing" "git.gophernest.net/azpect/TextEditor/internal/core" "git.gophernest.net/azpect/TextEditor/internal/theme/themes" "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(themes.NewDefaultTheme()) engine.PrepareBuffer(buf) base := engine.editorTheme.Line 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(themes.NewDefaultTheme()) engine.PrepareBuffer(buf) base := engine.editorTheme.Line 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(themes.NewDefaultTheme()) 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.editorTheme.Line) { 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(themes.NewDefaultTheme()) 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(themes.NewDefaultTheme()) engine.PrepareBuffer(buf) base := engine.editorTheme.Line 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(themes.NewDefaultTheme()) 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(), ) }