gitstack

grindlemire/go-tui code browser

3.1 KB Go 106 lines 2026-02-07 · 617390f raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package gopls

import (
	"fmt"
	"strings"

	"github.com/grindlemire/go-tui/internal/lsp/log"
	"github.com/grindlemire/go-tui/internal/tuigen"
)

// emitStateVarDeclarations scans a component body for tui.NewState(...) declarations
// and emits corresponding Go variable declarations so gopls can resolve state types.
func (g *generator) emitStateVarDeclarations(comp *tuigen.Component) {
	for _, node := range comp.Body {
		goCode, ok := node.(*tuigen.GoCode)
		if !ok || goCode == nil {
			continue
		}
		matches := stateNewStateRegex.FindStringSubmatch(goCode.Code)
		if len(matches) < 3 {
			continue
		}
		varName := matches[1]
		initExpr := matches[2]

		// Map the variable name position from .gsx to .go
		tuiLine := goCode.Position.Line - 1
		tuiCol := goCode.Position.Column - 1
		// Find the variable name offset in the original code
		varIdx := strings.Index(goCode.Code, varName)
		if varIdx >= 0 {
			tuiCol += varIdx
		}

		goVarStartCol := 1 + 0 // "\t" + start of varName in "varName := tui.NewState(...)"
		g.sourceMap.AddMapping(Mapping{
			TuiLine: tuiLine,
			TuiCol:  tuiCol,
			GoLine:  g.goLine,
			GoCol:   goVarStartCol,
			Length:  len(varName),
		})
		log.Generate("STATE mapping: %s := tui.NewState(%s) -> TuiLine=%d TuiCol=%d GoLine=%d",
			varName, initExpr, tuiLine, tuiCol, g.goLine)

		// Emit: varName := tui.NewState(initExpr)
		g.writeLine(fmt.Sprintf("\t%s := tui.NewState(%s)", varName, initExpr))
	}
}

// emitRefDeclarations scans a component body for elements with ref={} attributes
// and emits Go variable declarations so gopls understands ref types.
func (g *generator) emitRefDeclarations(comp *tuigen.Component) {
	g.emitRefFromNodes(comp.Body, false, false)
}

// emitRefFromNodes recursively finds ref attributes in AST nodes.
func (g *generator) emitRefFromNodes(nodes []tuigen.Node, inLoop, inConditional bool) {
	for _, node := range nodes {
		switch n := node.(type) {
		case *tuigen.Element:
			if n == nil {
				continue
			}
			if n.RefExpr != nil {
				refExpr := n.RefExpr.Code

				tuiLine := n.RefExpr.Position.Line - 1
				tuiCol := n.RefExpr.Position.Column

				// Emit "_ = c.decrementBtn" to validate the ref expression
				// without creating an unused variable
				goExprStartCol := 1 + 4 // "\t" + "_ = "
				g.sourceMap.AddMapping(Mapping{
					TuiLine: tuiLine,
					TuiCol:  tuiCol,
					GoLine:  g.goLine,
					GoCol:   goExprStartCol,
					Length:  len(refExpr),
				})
				log.Generate("REF mapping: ref={%s} -> TuiLine=%d TuiCol=%d GoLine=%d",
					refExpr, tuiLine, tuiCol, g.goLine)

				g.writeLine(fmt.Sprintf("\t_ = %s", refExpr))
			}
			g.emitRefFromNodes(n.Children, inLoop, inConditional)
		case *tuigen.ForLoop:
			if n != nil {
				g.emitRefFromNodes(n.Body, true, inConditional)
			}
		case *tuigen.IfStmt:
			if n != nil {
				g.emitRefFromNodes(n.Then, inLoop, true)
				g.emitRefFromNodes(n.Else, inLoop, true)
			}
		case *tuigen.LetBinding:
			if n != nil && n.Element != nil {
				g.emitRefFromNodes([]tuigen.Node{n.Element}, inLoop, inConditional)
			}
		case *tuigen.ComponentCall:
			if n != nil {
				g.emitRefFromNodes(n.Children, inLoop, inConditional)
			}
		}
	}
}