Переглянути джерело

nothing worksuih34truhitr

Johan Rong 1 місяць тому
батько
коміт
385bf98a65

+ 2 - 2
cmd/moose/main.go

@@ -10,7 +10,6 @@ import (
 )
 
 func main() {
-	fmt.Println("hi")
 	s, err := tcell.NewScreen()
 	if err != nil {
 		fmt.Printf("[moose-error] %v", err)
@@ -28,7 +27,8 @@ func main() {
 	}
 
 	m := editor.NewModel(s)
-	m.AddBuffer(false)
+	//m.AddBuffer(false)
+	m.AddBufferFromPath("test.lua", false)
 
 	init_extensions(&m)
 

+ 6 - 2
go.mod

@@ -5,7 +5,8 @@ go 1.27
 require (
 	github.com/creasty/defaults v1.8.0
 	github.com/davecgh/go-spew v1.1.1
-	github.com/gdamore/tcell/v3 v3.4.2
+	github.com/ebitengine/purego v0.11.0
+	github.com/gdamore/tcell/v3 v3.5.0
 	github.com/tree-sitter/go-tree-sitter v0.25.0
 	github.com/yuin/gopher-lua v1.1.2
 	github.com/zyedidia/rope v0.0.0-20210616205215-37fbf22eab3a
@@ -15,10 +16,13 @@ require (
 require (
 	github.com/clipperhouse/displaywidth v0.11.0 // indirect
 	github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
-	github.com/ebitengine/purego v0.10.2 // indirect
 	github.com/gdamore/encoding v1.0.1 // indirect
+	github.com/gdamore/tcell/v2 v2.13.10 // indirect
+	github.com/kreuzberg-dev/tree-sitter-language-pack v1.20.0 // indirect
 	github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
 	github.com/mattn/go-pointer v0.0.1 // indirect
+	github.com/rivo/uniseg v0.4.7 // indirect
+	github.com/xberg-io/tree-sitter-language-pack/packages/go v1.20.0 // indirect
 	golang.design/x/x11 v0.2.0 // indirect
 	golang.org/x/exp/shiny v0.0.0-20260820142414-ca536658362e // indirect
 	golang.org/x/image v0.45.0 // indirect

+ 12 - 2
go.sum

@@ -6,18 +6,26 @@ github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYK
 github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
 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/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
-github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/ebitengine/purego v0.11.0 h1:jhp/D+Nyv7UUW8HAcmcjt2N2rYrYi9m3SL21k0Ua/NI=
+github.com/ebitengine/purego v0.11.0/go.mod h1:DCHPP08djqhNSoTfImcnHYQRZmd0qhakvrozqaEYhGQ=
 github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
 github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
+github.com/gdamore/tcell/v2 v2.13.10 h1:Afs3JKt83HnhuUKdZ3MnxUgOqQRWftj5JyDqv1LLynA=
+github.com/gdamore/tcell/v2 v2.13.10/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
 github.com/gdamore/tcell/v3 v3.4.2 h1:gGW+6z2Bz5Wl2mNwFlm9+eRmg2JQrWcKjSkL1LRfpNU=
 github.com/gdamore/tcell/v3 v3.4.2/go.mod h1:Oe5U3S3jm3NzypswDNUhe+LUnF5CoFq2b4sepD++QHo=
+github.com/gdamore/tcell/v3 v3.5.0 h1:SCp9czLv2K2aPORgD6+4fjV0xNAzKIkiezCkp6bHLe4=
+github.com/gdamore/tcell/v3 v3.5.0/go.mod h1:Oe5U3S3jm3NzypswDNUhe+LUnF5CoFq2b4sepD++QHo=
+github.com/kreuzberg-dev/tree-sitter-language-pack v1.20.0 h1:QdM6nOXsRR8f8YfEAa7wLuw3xQ9IlbK9G2iJFxjhI+A=
+github.com/kreuzberg-dev/tree-sitter-language-pack v1.20.0/go.mod h1:gESAJ1jOHyilgzyDuKDl3yn+tEQAeRhQHdWEJmlQfvU=
 github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
 github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
 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/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=
@@ -46,6 +54,8 @@ github.com/tree-sitter/tree-sitter-ruby v0.23.1 h1:T/NKHUA+iVbHM440hFx+lzVOzS4dV
 github.com/tree-sitter/tree-sitter-ruby v0.23.1/go.mod h1:kUS4kCCQloFcdX6sdpr8p6r2rogbM6ZjTox5ZOQy8cA=
 github.com/tree-sitter/tree-sitter-rust v0.23.2 h1:6AtoooCW5GqNrRpfnvl0iUhxTAZEovEmLKDbyHlfw90=
 github.com/tree-sitter/tree-sitter-rust v0.23.2/go.mod h1:hfeGWic9BAfgTrc7Xf6FaOAguCFJRo3RBbs7QJ6D7MI=
+github.com/xberg-io/tree-sitter-language-pack/packages/go v1.20.0 h1:S14u+Z0dXoXFoLHUyWjQRO1uviZx7YkyGKK0BtlMfHo=
+github.com/xberg-io/tree-sitter-language-pack/packages/go v1.20.0/go.mod h1:elmEHB132S8m85Rafgt1JV3Z5IM8lxYSobmRGT8TRSg=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
 github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=

+ 17 - 0
internal/buffer/buffer.go

@@ -6,7 +6,9 @@ import (
 	"slices"
 	"unicode"
 	"unicode/utf8"
+	"moose/internal/highlight"
 
+	tree_sitter "github.com/tree-sitter/go-tree-sitter"
 	"github.com/zyedidia/rope"
 )
 
@@ -42,6 +44,12 @@ type Buffer struct {
 	Path    string
 	TopLine int
 	History UndoStack
+
+	Language string
+	HighlightQuery []byte
+	Tree          *tree_sitter.Tree
+	Spans         []highlight.HighlightSpan
+	IsDirty       bool
 }
 
 func NewBuffer() Buffer {
@@ -52,6 +60,8 @@ func NewBuffer() Buffer {
 			Cursors:    []Cursor{{Offset: 0, Goal: 0}},
 			PrimaryIdx: 0,
 		},
+		IsDirty: true,
+		Language: "lua",
 	}
 }
 
@@ -69,6 +79,13 @@ func NewBufferFromPath(path string) Buffer {
 	b.Path = path
 	b.History = UndoStack{}
 
+	b.Language = highlight.DetectLanguage(path)
+	if b.Language != "" {
+		if queryData, err := highlight.GetQuery(b.Language); err == nil {
+			b.HighlightQuery = queryData
+		}
+	}
+
 	return b
 }
 

+ 21 - 0
internal/editor/config.go

@@ -5,6 +5,8 @@ import (
 	"reflect"
 	"strings"
 
+	"moose/internal/highlight"
+
 	"github.com/creasty/defaults"
 	"github.com/gdamore/tcell/v3"
 	lua "github.com/yuin/gopher-lua"
@@ -49,6 +51,25 @@ type Colors struct {
 	TabForegroundActive       string `default:"#dddddd"`
 }
 
+var DefaultTheme = highlight.StyleMap{
+	"keyword":          tcell.StyleDefault.Foreground(tcell.GetColor("#c678dd")).Bold(true),
+	"keyword.function": tcell.StyleDefault.Foreground(tcell.GetColor("#c678dd")).Bold(true),
+	"keyword.modifier": tcell.StyleDefault.Foreground(tcell.GetColor("#c678dd")),
+	"string":           tcell.StyleDefault.Foreground(tcell.GetColor("#98c379")),
+	"string.escape":    tcell.StyleDefault.Foreground(tcell.GetColor("#56b6c2")),
+	"comment":          tcell.StyleDefault.Foreground(tcell.GetColor("#5c6370")).Italic(true),
+	"function":         tcell.StyleDefault.Foreground(tcell.GetColor("#61afef")),
+	"function.method":  tcell.StyleDefault.Foreground(tcell.GetColor("#61afef")),
+	"variable":         tcell.StyleDefault.Foreground(tcell.GetColor("#abb2bf")),
+	"variable.builtin": tcell.StyleDefault.Foreground(tcell.GetColor("#e5c07b")),
+	"variable.parameter": tcell.StyleDefault.Foreground(tcell.GetColor("#e06c75")),
+	"property":         tcell.StyleDefault.Foreground(tcell.GetColor("#e06c75")),
+	"number":           tcell.StyleDefault.Foreground(tcell.GetColor("#d19a66")),
+	"constant.builtin": tcell.StyleDefault.Foreground(tcell.GetColor("#d19a66")),
+	"module":           tcell.StyleDefault.Foreground(tcell.GetColor("#e5c07b")),
+	"operator":         tcell.StyleDefault.Foreground(tcell.GetColor("#56b6c2")),
+}
+
 func DefaultConfig() Config {
 	config := Config{}
 	if err := defaults.Set(&config); err != nil {

+ 140 - 50
internal/editor/draw.go

@@ -1,15 +1,19 @@
 package editor
 
 import (
+	"os"
 	"fmt"
 	"math"
 	"moose/internal/buffer"
 	"moose/internal/layout"
 	"moose/internal/util"
+	"moose/internal/highlight"
 	"slices"
 	"strconv"
 	"strings"
 
+	"github.com/davecgh/go-spew/spew"
+
 	"github.com/gdamore/tcell/v3"
 )
 
@@ -190,7 +194,7 @@ func (m *Model) DrawContainerTabs(c *layout.ContainerBuffers, rect layout.Rect)
 
 		buf := m.BM.Buffers[bufIdx]
 		if buf.Path == "" {
-			tabs = append(tabs, "Buffer "+strconv.Itoa(bufIdx))
+			tabs = append(tabs, "Buffer " + strconv.Itoa(bufIdx))
 		} else {
 			tabs = append(tabs, buf.Path)
 		}
@@ -226,60 +230,146 @@ func (m *Model) DrawContainerBuffers(c *layout.ContainerBuffers, rect layout.Rec
 	m.DrawBuffer(&m.BM.Buffers[c.Buffers[c.ActiveIdx]], c.Buffers[c.ActiveIdx] == m.BM.CurrentIdx, rect)
 }
 
-func (m *Model) DrawBuffer(buf *buffer.Buffer, isActive bool, rect layout.Rect) {
-	primary := buf.CM.Cursors[buf.CM.PrimaryIdx]
-	curLine, _ := buffer.LineCol(buf, primary.Offset)
-	buf.ScrollToShow(curLine, rect.Height)
-
-	startOffset := buffer.OffsetForLine(buf, buf.TopLine)
-	endOffset := buf.Rope.Len()
-	if endLine := buf.TopLine + rect.Height; endLine < buffer.LineCount(buf) {
-		endOffset = buffer.OffsetForLine(buf, endLine)
-	}
-
-	visible := string(buf.Rope.Slice(startOffset, endOffset))
-	table := strings.SplitAfter(visible, "\n")
-	for j, line := range table {
-		if j+1 > rect.Height {
-			break
-		}
+const tabWidth = 4
 
-		lineNum := j + buf.TopLine
-		relLine := lineNum - curLine
-		nums := fmt.Sprintf("%*d ", m.Config.Properties.GutterWidth, int(math.Abs(float64(relLine))))
-		r := []rune(nums + expandTabs(line))
-		if len(r)+1 > rect.Width {
-			r = r[:rect.Width]
+func (m *Model) DrawBuffer(buf *buffer.Buffer, isActive bool, rect layout.Rect) {
+    primary := buf.CM.Cursors[buf.CM.PrimaryIdx]
+    curLine, _ := buffer.LineCol(buf, primary.Offset)
+    buf.ScrollToShow(curLine, rect.Height)
+
+    startOffset := buffer.OffsetForLine(buf, buf.TopLine)
+    endOffset := buf.Rope.Len()
+    if endLine := buf.TopLine + rect.Height; endLine < buffer.LineCount(buf) {
+        endOffset = buffer.OffsetForLine(buf, endLine)
+    }
+
+    defaultStyle := m.Config.StyleDefault.
+        Foreground(tcell.GetColor(m.Config.Colors.MainForeground)).
+        Background(tcell.GetColor(m.Config.Colors.MainBackground))
+
+	if buf.IsDirty && m.Highlighter != nil && buf.Language != "" && len(buf.HighlightQuery) > 0 {
+        fullCode := []byte(buf.String())
+        spans, newTree, err := m.Highlighter.HighlightBuffer(buf.Language, fullCode, buf.HighlightQuery)
+		if err != nil {
+			os.WriteFile("output.txt", []byte(spew.Sdump(err)), 0644)
 		}
 
-		m.Screen.PutStrStyled(rect.X, rect.Y+j, string(r), m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.MainForeground)).Background(tcell.GetColor(m.Config.Colors.MainBackground)))
-	}
-
-	if isActive {
-		for _, cur := range buf.CM.Cursors {
-			line, col := buffer.LineCol(buf, cur.Offset)
-			screenLine := line - buf.TopLine
-			if screenLine < 0 || screenLine+1 > rect.Height {
-				continue
-			}
-
-			visCol := visualCol(buffer.LineText(buf, line), col)
-			if visCol+1 > rect.Width {
-				continue
-			}
-
-			ch := buffer.RuneAt(buf, cur.Offset)
-
-			if m.Mode == ModeWrite {
-				m.Screen.SetContent(rect.X+visCol+m.Config.Properties.GutterWidth+1, rect.Y+screenLine, ch, nil, m.Config.StyleDefault.Background(tcell.GetColor(m.Config.Colors.CursorColorWrite)))
-			} else {
-				m.Screen.SetContent(rect.X+visCol+m.Config.Properties.GutterWidth+1, rect.Y+screenLine, ch, nil, m.Config.StyleDefault.Background(tcell.GetColor(m.Config.Colors.CursorColor)))
-			}
-		}
-	}
+        if err == nil {
+            if buf.Tree != nil {
+                buf.Tree.Close()
+            }
+            buf.Tree = newTree
+            buf.Spans = spans
+        }
+        // buf.IsDirty = false
+    }
+
+    visible := string(buf.Rope.Slice(startOffset, endOffset))
+    table := strings.SplitAfter(visible, "\n")
+    
+    currentLineByteOffset := uint32(startOffset)
+    // tabWidth := m.Config.Properties.TabWidth
+    // if tabWidth <= 0 {
+    //     tabWidth = 4
+    // }
+
+    for j, line := range table {
+        if j+1 > rect.Height {
+            break
+        }
+
+        lineNum := j + buf.TopLine
+        relLine := lineNum - curLine
+        gutterText := fmt.Sprintf("%*d ", m.Config.Properties.GutterWidth, int(math.Abs(float64(relLine))))
+
+        gutterStyle := defaultStyle.Foreground(tcell.ColorGray)
+        for gIdx, gCh := range gutterText {
+            if gIdx >= rect.Width {
+                break
+            }
+            m.Screen.SetContent(rect.X+gIdx, rect.Y+j, gCh, nil, gutterStyle)
+        }
+
+        m.DrawStyledLine(
+            rect.X+len(gutterText),
+            rect.Y+j,
+            line,
+            currentLineByteOffset,
+            rect.Width-len(gutterText),
+            buf.Spans,
+            defaultStyle,
+            tabWidth,
+        )
+
+        currentLineByteOffset += uint32(len([]byte(line)))
+    }
+
+    if isActive {
+        for _, cur := range buf.CM.Cursors {
+            line, col := buffer.LineCol(buf, cur.Offset)
+            screenLine := line - buf.TopLine
+            if screenLine < 0 || screenLine+1 > rect.Height {
+                continue
+            }
+
+            visCol := visualCol(buffer.LineText(buf, line), col)
+            if visCol+1 > rect.Width {
+                continue
+            }
+
+            ch := buffer.RuneAt(buf, cur.Offset)
+            cursorBg := tcell.GetColor(m.Config.Colors.CursorColor)
+            if m.Mode == ModeWrite {
+                cursorBg = tcell.GetColor(m.Config.Colors.CursorColorWrite)
+            }
+
+            m.Screen.SetContent(
+                rect.X+visCol+m.Config.Properties.GutterWidth+1,
+                rect.Y+screenLine,
+                ch,
+                nil,
+                defaultStyle.Background(cursorBg),
+            )
+        }
+    }
 }
 
-const tabWidth = 4
+func (m *Model) DrawStyledLine(startX, y int, rawLine string, lineStartByte uint32, maxWidth int, spans []highlight.HighlightSpan, defaultStyle tcell.Style, tabWidth int) {
+    currentByte := lineStartByte
+    screenCol := 0
+
+    for _, ch := range rawLine {
+        if screenCol >= maxWidth {
+            break
+        }
+
+        chByteLen := uint32(len(string(ch)))
+
+        activeStyle := defaultStyle
+        for _, span := range spans {
+            if currentByte >= span.StartByte && currentByte < span.EndByte {
+                activeStyle = span.Style
+                break
+            }
+        }
+
+        if ch == '\t' {
+            spacesToDraw := tabWidth - (screenCol % tabWidth)
+            for s := 0; s < spacesToDraw; s++ {
+                if screenCol >= maxWidth {
+                    break
+                }
+                m.Screen.SetContent(startX+screenCol, y, ' ', nil, activeStyle)
+                screenCol++
+            }
+        } else {
+            m.Screen.SetContent(startX+screenCol, y, ch, nil, activeStyle)
+            screenCol++
+        }
+
+        currentByte += chByteLen
+    }
+}
 
 func expandTabs(s string) string {
 	var b strings.Builder

+ 0 - 40
internal/editor/extension.go

@@ -3,8 +3,6 @@ package editor
 import (
 	"embed"
 	"fmt"
-	"moose/internal/editor/highlight"
-	"os"
 	"path/filepath"
 	"strings"
 
@@ -129,7 +127,6 @@ func (em *ExtensionManager) registerAPI() {
 	moose := em.L.NewTable()
 	em.L.SetGlobal("ms", moose)
 	moose.RawSetString("config", GetConfigTable(em))
-	moose.RawSetString("syntax", GetSyntaxTable(em))
 }
 
 func (em *ExtensionManager) LoadFile(path string) error {
@@ -180,40 +177,3 @@ func (em *ExtensionManager) LoadString(name string, src string) error {
 	em.LoadedFiles = append(em.LoadedFiles, name)
 	return nil
 }
-
-func GetSyntaxTable(em *ExtensionManager) *lua.LTable {
-	syntax := em.L.NewTable()
-	em.L.SetFuncs(syntax, map[string]lua.LGFunction{
-		"registerLang": luaRegisterLanguage,
-	})
-	return syntax
-}
-
-func luaRegisterLanguage(L *lua.LState) int {
-	idx := 1
-	if L.GetTop() >= 2 && L.Get(1).Type() == lua.LTTable {
-		tb1 := L.ToTable(1)
-		if tb1.RawGetString("name") == lua.LNil {
-			idx = 2
-		}
-	}
-
-	tb := L.CheckTable(idx)
-
-	langName := tb.RawGetString("name").String()
-	parserPath := tb.RawGetString("parser_path").String()
-	queryScm := tb.RawGetString("query").String()
-
-	if langName == "" || langName == "nil" || parserPath == "" || parserPath == "nil" {
-		L.ArgError(idx, "expected table with 'name' and 'parser_path' fields")
-		return 0
-	}
-
-	err := highlight.RegisterTreeSitterLang(langName, parserPath, queryScm)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "warning: failed to register language %s: %v\n", langName, err)
-		return 0
-	}
-
-	return 0
-}

+ 0 - 106
internal/editor/highlight/registry.go

@@ -1,106 +0,0 @@
-package highlight
-
-/*
-#cgo LDFLAGS: -ldl
-#include <dlfcn.h>
-#include <stdlib.h>
-
-// Helper to resolve the C function pointer for tree_sitter_<lang>()
-void* load_symbol(void* handle, const char* symbol) {
-    return dlsym(handle, symbol);
-}
-*/
-import "C"
-
-import (
-	"fmt"
-	"path/filepath"
-	"strings"
-	"sync"
-	"unicode"
-	"unsafe"
-
-	sitter "github.com/tree-sitter/go-tree-sitter"
-)
-
-type LanguageSpec struct {
-	Name     string
-	Language *sitter.Language
-	Query    *sitter.Query
-}
-
-type Registry struct {
-	mu        sync.RWMutex
-	languages map[string]*LanguageSpec
-}
-
-var GlobalRegistry = &Registry{
-	languages: make(map[string]*LanguageSpec),
-}
-
-func CleanScmQuery(scm string) string {
-	cleaned := strings.Map(func(r rune) rune {
-		if r == '\uFEFF' { // Remove UTF-8 Byte Order Mark (BOM)
-			return -1
-		}
-		if unicode.IsSpace(r) && r != '\n' && r != '\r' && r != '\t' {
-			return ' '
-		}
-		return r
-	}, scm)
-
-	return strings.TrimSpace(cleaned)
-}
-
-func RegisterTreeSitterLang(langName, parserPath, queryScm string) error {
-	absPath, err := filepath.Abs(parserPath)
-	if err == nil {
-		parserPath = absPath
-	}
-
-	cPath := C.CString(parserPath)
-	defer C.free(unsafe.Pointer(cPath))
-
-	handle := C.dlopen(cPath, C.RTLD_NOW)
-	if handle == nil {
-		errStr := C.GoString(C.dlerror())
-		return fmt.Errorf("failed to load parser library at %s: %s", parserPath, errStr)
-	}
-
-	symbolName := C.CString(fmt.Sprintf("tree_sitter_%s", langName))
-	defer C.free(unsafe.Pointer(symbolName))
-
-	sym := C.load_symbol(handle, symbolName)
-	if sym == nil {
-		return fmt.Errorf("symbol 'tree_sitter_%s' not found in %s", langName, parserPath)
-	}
-
-	ptr := unsafe.Pointer(sym)
-	sitterLang := sitter.NewLanguage(ptr)
-	if sitterLang == nil {
-		return fmt.Errorf("invalid TSLanguage pointer returned for %s", langName)
-	}
-
-	cleanScm := CleanScmQuery(queryScm)
-	query, err := sitter.NewQuery(sitterLang, string([]byte(cleanScm)))
-	if err != nil {
-		return fmt.Errorf("failed to compile highlight query for %s: %w", langName, err)
-	}
-
-	GlobalRegistry.mu.Lock()
-	GlobalRegistry.languages[langName] = &LanguageSpec{
-		Name:     langName,
-		Language: sitterLang,
-		Query:    query,
-	}
-	GlobalRegistry.mu.Unlock()
-
-	return nil
-}
-
-func (r *Registry) GetLanguage(langName string) (*LanguageSpec, bool) {
-	r.mu.RLock()
-	defer r.mu.RUnlock()
-	spec, ok := r.languages[langName]
-	return spec, ok
-}

+ 12 - 2
internal/editor/model.go

@@ -1,8 +1,11 @@
 package editor
 
 import (
+	"os"
+
 	"moose/internal/buffer"
 	"moose/internal/layout"
+	"moose/internal/highlight"
 
 	"github.com/gdamore/tcell/v3"
 )
@@ -16,17 +19,18 @@ type Model struct {
 	LM         layout.LayoutManager
 	ShouldQuit bool
 	DebugLog   string
+	Highlighter *highlight.Highlighter
 }
 
 func NewModel(screen tcell.Screen) Model {
-	blank := buffer.NewBuffer()
+	// blank := buffer.NewBuffer()
 
 	model := Model{
 		Screen: screen,
 		Config: DefaultConfig(),
 		Mode:   ModeNormal,
 		BM: buffer.BufferManager{
-			Buffers:       []buffer.Buffer{blank},
+			Buffers:       []buffer.Buffer{},
 			CurrentIdx:    0,
 			PaletteBuffer: buffer.NewBuffer(),
 		},
@@ -35,6 +39,12 @@ func NewModel(screen tcell.Screen) Model {
 		ShouldQuit: false,
 	}
 
+	highlighter, err := highlight.NewHighlighter(DefaultTheme)
+	if err != nil {
+		os.Exit(1)
+	}
+	model.Highlighter = highlighter
+
 	model.ReloadConfig()
 
 	return model

+ 82 - 0
internal/highlight/highlight.go

@@ -0,0 +1,82 @@
+package highlight
+
+import (
+	"fmt"
+
+	tcell "github.com/gdamore/tcell/v3"
+	tree_sitter "github.com/tree-sitter/go-tree-sitter"
+	tspack "github.com/xberg-io/tree-sitter-language-pack/packages/go"
+)
+
+type StyleMap map[string]tcell.Style
+
+type Highlighter struct {
+	parser *tree_sitter.Parser
+	theme  StyleMap
+}
+
+func NewHighlighter(theme StyleMap) (*Highlighter, error) {
+	parser := tree_sitter.NewParser()
+
+	return &Highlighter{
+		parser: parser,
+		theme:  theme,
+	}, nil
+}
+
+func (h *Highlighter) Close() {
+	if h.parser != nil {
+		h.parser.Close()
+	}
+}
+
+func (h *Highlighter) HighlightBuffer(langName string, code []byte, highlightScm []byte) ([]HighlightSpan, *tree_sitter.Tree, error) {
+	lang, err := tspack.GetLanguage(langName)
+	if err != nil || lang == nil {
+		return nil, nil, fmt.Errorf("could not load language %s: %w", langName, err)
+	}
+
+	if err := h.parser.SetLanguage(lang); err != nil {
+		return nil, nil, fmt.Errorf("failed to set language: %w", err)
+	}
+
+	tree := h.parser.Parse(code, nil)
+	if tree == nil {
+		return nil, nil, fmt.Errorf("failed to parse document")
+	}
+
+	query, err := tree_sitter.NewQuery(lang, string(highlightScm))
+	if err != nil {
+		tree.Close()
+		return nil, nil, fmt.Errorf("invalid query file: %w", err)
+	}
+	defer query.Close()
+
+	cursor := tree_sitter.NewQueryCursor()
+	defer cursor.Close()
+
+	captures := cursor.Captures(query, tree.RootNode(), code)
+	var spans []HighlightSpan
+
+	for match, captureIdx := captures.Next(); match != nil; match, captureIdx = captures.Next() {
+		capture := match.Captures[captureIdx]
+		node := capture.Node
+		captureName := query.CaptureNames()[capture.Index]
+
+		if style, ok := h.theme[captureName]; ok {
+			spans = append(spans, HighlightSpan{
+				StartByte: uint32(node.StartByte()),
+				EndByte:   uint32(node.EndByte()),
+				Style:     style,
+			})
+		}
+	}
+
+	return spans, tree, nil
+}
+
+type HighlightSpan struct {
+	StartByte uint32
+	EndByte   uint32
+	Style     tcell.Style
+}

+ 46 - 0
internal/highlight/queries.go

@@ -0,0 +1,46 @@
+package highlight
+
+import (
+	"embed"
+	"fmt"
+	"path/filepath"
+	"strings"
+)
+
+//go:embed queries/*.scm
+var QueryFS embed.FS
+
+func GetQuery(lang string) ([]byte, error) {
+	path := fmt.Sprintf("queries/%s.scm", lang)
+	data, err := QueryFS.ReadFile(path)
+	if err != nil {
+		return nil, fmt.Errorf("no highlight query found for language %q: %w", lang, err)
+	}
+	return data, nil
+}
+
+func DetectLanguage(filePath string) string {
+	ext := strings.ToLower(filepath.Ext(filePath))
+	switch ext { // TODO: set this in configuration
+	case ".go":
+		return "go"
+	case ".py":
+		return "python"
+	case ".rs":
+		return "rust"
+	case ".js", ".jsx":
+		return "javascript"
+	case ".ts", ".tsx":
+		return "typescript"
+	case ".c", ".h":
+		return "c"
+	case ".cpp", ".hpp", ".cc":
+		return "cpp"
+	case ".json":
+		return "json"
+	case  ".lua":
+		return "lua"
+	default:
+		return "" // Unknown/Plaintext
+	}
+}

+ 33 - 0
internal/highlight/queries/lua.scm

@@ -0,0 +1,33 @@
+;; ============================================================================
+;; Tree-sitter Query File for Lua (highlights.scm)
+;; ============================================================================
+
+;; Keywords
+[
+  "and" "do" "else" "elseif" "end" 
+  "for" "function" "goto" "if" "in" 
+  "local" "not" "or" "repeat" "return" 
+  "then" "until" "while"
+] @keyword
+
+;; Literals and Constants
+(string) @string
+(number) @number
+
+;; Comments
+[
+  (comment)
+  (hash_bang_line)
+] @comment
+
+;; Parameters and Variables
+(parameters (identifier) @variable.parameter)
+
+;; Table Fields
+(field name: (identifier) @property)
+(dot_index_expression field: (identifier) @property)
+
+;; Punctuation & Operators
+[ "," "." ";" ":" "#" ] @punctuation.delimiter
+[ "(" ")" "{" "}" "[" "]" ] @punctuation.bracket
+[ "=" "==" "~=" "<" "<=" ">" ">=" "+" "-" "*" "/" "//" "^" "%" ".." ] @operator

+ 1 - 0
output.txt

@@ -0,0 +1 @@
+(*fmt.wrapError)(0x2beeaaeee080)(invalid query file: <nil>)

+ 5 - 0
test.lua

@@ -0,0 +1,5 @@
+local test = 5.5
+
+function test()
+	print("hello")
+end