gitstack

grindlemire/go-tui code browser

12.2 KB Go 344 lines 2026-03-21 ยท baf5d13 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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
package tuigen

import "strings"

// Node is the interface implemented by all AST nodes.
type Node interface {
	node()         // marker method to ensure type safety
	Pos() Position // returns the source position of the node
}

// Comment represents a single comment (line or block).
type Comment struct {
	Text            string   // Raw text including delimiters (// or /* */)
	Position        Position // Start position
	EndLine         int      // End line (for multi-line block comments)
	EndCol          int      // End column
	IsBlock         bool     // true for /* */ comments, false for // comments
	BlankLineBefore bool     // true if there was a blank line before this comment
}

// CommentGroup represents a sequence of comments with no blank lines between them.
// Adjacent line comments or a single block comment form a group.
type CommentGroup struct {
	List []*Comment
}

// Text returns the text of the comment group, with comment markers removed
// and lines joined with newlines.
func (g *CommentGroup) Text() string {
	if g == nil || len(g.List) == 0 {
		return ""
	}
	var lines []string
	for _, c := range g.List {
		text := c.Text
		if c.IsBlock {
			// Remove /* and */
			text = strings.TrimPrefix(text, "/*")
			text = strings.TrimSuffix(text, "*/")
			text = strings.TrimSpace(text)
		} else {
			// Remove //
			text = strings.TrimPrefix(text, "//")
			text = strings.TrimSpace(text)
		}
		lines = append(lines, text)
	}
	return strings.Join(lines, "\n")
}

// File represents a complete .gsx source file.
type File struct {
	Package    string
	Imports    []Import
	Decls      []*GoDecl // top-level Go declarations (type, const, var)
	Components []*Component
	Funcs      []*GoFunc // top-level Go functions
	Position   Position
	// Comment fields
	LeadingComments *CommentGroup   // Comments before package declaration
	OrphanComments  []*CommentGroup // Comments not attached to any node
}

func (f *File) node()         {}
func (f *File) Pos() Position { return f.Position }

// Import represents a Go import statement.
type Import struct {
	Alias    string // optional alias (empty if none)
	Path     string // import path
	Position Position
	// Comment fields
	TrailingComments *CommentGroup // Inline comment on import line
}

func (i *Import) node()         {}
func (i *Import) Pos() Position { return i.Position }

// Component represents a @component definition.
type Component struct {
	Name            string
	Params          []*Param
	ReturnType      string // defaults to "*element.Element"
	Body            []Node // Element, GoCode, LetBinding, ForLoop, IfStmt
	AcceptsChildren bool   // true if body contains {children...}
	Position        Position
	// Method receiver fields (for templ (recv) Render() syntax)
	Receiver     string // Full receiver text, e.g. "s *sidebar" (empty for function components)
	ReceiverName string // Receiver variable name, e.g. "s" (empty for function components)
	ReceiverType string // Receiver type, e.g. "*sidebar" (empty for function components)
	// Comment fields
	LeadingComments  *CommentGroup   // Doc comments before component
	TrailingComments *CommentGroup   // Comments on same line after opening {
	OrphanComments   []*CommentGroup // Comments in body not attached to any node
}

func (c *Component) node()         {}
func (c *Component) Pos() Position { return c.Position }

// Param represents a function parameter.
type Param struct {
	Name     string
	Type     string
	Position Position
}

func (p *Param) node()         {}
func (p *Param) Pos() Position { return p.Position }

// Element represents an XML-like element: <tag attrs>children</tag> or <tag />
type Element struct {
	Tag        string
	RefExpr    *GoExpr // Expression from ref={expr} attribute (e.g., ref={content})
	RefKey     *GoExpr // Key expression for map-based refs (e.g., key={item.ID})
	Attributes []*Attribute
	Children   []Node // Elements, GoExpr, TextContent, ForLoop, IfStmt, LetBinding
	SelfClose  bool
	Position   Position
	// Layout hints (detected from source positions during parsing)
	MultiLineAttrs        bool // attrs span multiple source lines
	ClosingBracketNewLine bool // > or /> is on its own line (after last attr)
	InlineChildren        bool // children are on same line as opening/closing tags
	BlankLineBefore       bool // blank line before this node in source
	// Comment fields
	LeadingComments  *CommentGroup   // Comments immediately before this element
	TrailingComments *CommentGroup   // Comments on same line after this element
	OrphanComments   []*CommentGroup // Comments in children not attached to any node
}

func (e *Element) node()         {}
func (e *Element) Pos() Position { return e.Position }

// Attribute represents a tag attribute: name=value or name={expr}
type Attribute struct {
	Name          string
	Value         Node     // StringLit, IntLit, FloatLit, GoExpr, or BoolLit
	Position      Position // Position of the attribute name
	ValuePosition Position // Position of the attribute value (start of value, after '=')
}

func (a *Attribute) node()         {}
func (a *Attribute) Pos() Position { return a.Position }

// GoExpr represents a Go expression embedded in {braces}.
type GoExpr struct {
	Code            string
	Position        Position
	BlankLineBefore bool // blank line before this node in source
	// Comment fields
	LeadingComments  *CommentGroup // Comments immediately before this expression
	TrailingComments *CommentGroup // Comments on same line after this expression
}

func (g *GoExpr) node()         {}
func (g *GoExpr) Pos() Position { return g.Position }

// StringLit represents a string literal "...".
type StringLit struct {
	Value    string
	Position Position
}

func (s *StringLit) node()         {}
func (s *StringLit) Pos() Position { return s.Position }

// IntLit represents an integer literal.
type IntLit struct {
	Value    int64
	Position Position
}

func (i *IntLit) node()         {}
func (i *IntLit) Pos() Position { return i.Position }

// FloatLit represents a floating-point literal.
type FloatLit struct {
	Value    float64
	Position Position
}

func (f *FloatLit) node()         {}
func (f *FloatLit) Pos() Position { return f.Position }

// BoolLit represents a boolean literal (true/false).
type BoolLit struct {
	Value    bool
	Position Position
}

func (b *BoolLit) node()         {}
func (b *BoolLit) Pos() Position { return b.Position }

// TextContent represents literal text content inside an element.
type TextContent struct {
	Text            string
	Position        Position
	BlankLineBefore bool // blank line before this node in source
}

func (t *TextContent) node()         {}
func (t *TextContent) Pos() Position { return t.Position }

// LetBinding represents a variable binding: name := <element>, name := @Component(), name := expr,
// or var name = <element>.
type LetBinding struct {
	Name            string
	Element         *Element       // RHS is an element (e.g., <span>Hello</span>)
	Call            *ComponentCall // RHS is a component call (e.g., @MyComponent())
	Expr            string         // RHS is a Go expression (e.g., fmt.Sprintf(...))
	IsShortForm     bool           // true for :=, false for var
	IsVarForm       bool           // true for var form, false for := form
	Position        Position
	BlankLineBefore bool // blank line before this node in source
	// Comment fields
	LeadingComments  *CommentGroup // Comments immediately before binding
	TrailingComments *CommentGroup // Comments on same line after element
}

func (l *LetBinding) node()         {}
func (l *LetBinding) Pos() Position { return l.Position }

// ForLoop represents for i, v := range items { ... }
type ForLoop struct {
	Index           string // loop index variable (may be "_" or empty)
	Value           string // loop value variable
	Iterable        string // Go expression for the iterable
	Body            []Node // Elements and other nodes
	Position        Position
	BlankLineBefore bool // blank line before this node in source
	// Comment fields
	LeadingComments  *CommentGroup   // Comments immediately before for
	TrailingComments *CommentGroup   // Comments on same line after opening {
	OrphanComments   []*CommentGroup // Comments in body not attached to any node
}

func (f *ForLoop) node()         {}
func (f *ForLoop) Pos() Position { return f.Position }

// IfStmt represents if condition { ... } else { ... }
type IfStmt struct {
	Condition       string // Go expression for the condition
	Then            []Node
	Else            []Node // optional else branch
	Position        Position
	BlankLineBefore bool // blank line before this node in source
	// Comment fields
	LeadingComments  *CommentGroup   // Comments immediately before if
	TrailingComments *CommentGroup   // Comments on same line after opening {
	OrphanComments   []*CommentGroup // Comments in body not attached to any node
}

func (i *IfStmt) node()         {}
func (i *IfStmt) Pos() Position { return i.Position }

// GoCode represents a block of embedded Go code.
type GoCode struct {
	Code     string
	Position Position
	// Comment fields
	LeadingComments  *CommentGroup // Comments immediately before Go code
	TrailingComments *CommentGroup // Comments on same line after Go code
}

func (g *GoCode) node()         {}
func (g *GoCode) Pos() Position { return g.Position }

// GoFunc represents a top-level Go function definition in a .gsx file.
type GoFunc struct {
	Code     string // the entire function definition
	Position Position
	// Comment fields
	LeadingComments  *CommentGroup // Comments immediately before func
	TrailingComments *CommentGroup // Comments on same line after closing }
}

func (g *GoFunc) node()         {}
func (g *GoFunc) Pos() Position { return g.Position }

// GoDecl represents a top-level Go declaration (type, const, var) in a .gsx file.
type GoDecl struct {
	Kind     string // "type", "const", or "var"
	Code     string // the entire declaration
	Position Position
	// Comment fields
	LeadingComments  *CommentGroup // Comments immediately before declaration
	TrailingComments *CommentGroup // Comments on same line after declaration
}

func (g *GoDecl) node()         {}
func (g *GoDecl) Pos() Position { return g.Position }

// RawGoExpr represents a raw Go expression that should be emitted as-is
// (used for element references captured via := bindings)
type RawGoExpr struct {
	Code     string
	Position Position
}

func (r *RawGoExpr) node()         {}
func (r *RawGoExpr) Pos() Position { return r.Position }

// ComponentCall represents @ComponentName(args) { children }
type ComponentCall struct {
	Name            string   // component name (e.g., "Card", "Header")
	Args            string   // raw Go expression for arguments
	ArgsPosition    Position // source position of the first character of Args
	Children        []Node   // child elements (may be empty if no children block)
	IsStructMount   bool     // true when inside a method templ (generates app.Mount())
	MultiLineArgs   bool     // args span multiple source lines
	Position        Position
	BlankLineBefore bool // blank line before this node in source
	// Comment fields
	LeadingComments  *CommentGroup // Comments immediately before @ComponentName
	TrailingComments *CommentGroup // Comments on same line after )
}

func (c *ComponentCall) node()         {}
func (c *ComponentCall) Pos() Position { return c.Position }

// ComponentExpr represents @expr where expr is a Component field/variable.
// The expression's .Render() method is called to get the element.
type ComponentExpr struct {
	Expr            string // expression (e.g., "c.textarea", "myComponent")
	Position        Position
	BlankLineBefore bool // blank line before this node in source
	// Comment fields
	LeadingComments  *CommentGroup // Comments immediately before @expr
	TrailingComments *CommentGroup // Comments on same line after expr
}

func (c *ComponentExpr) node()         {}
func (c *ComponentExpr) Pos() Position { return c.Position }

// ChildrenSlot represents {children...} placeholder in a component body
type ChildrenSlot struct {
	Position        Position
	BlankLineBefore bool // blank line before this node in source
	// Comment fields
	LeadingComments  *CommentGroup // Comments immediately before {children...}
	TrailingComments *CommentGroup // Comments on same line after {children...}
}

func (c *ChildrenSlot) node()         {}
func (c *ChildrenSlot) Pos() Position { return c.Position }