package codeview import ( "image" "strings" "gioui.org/f32" "gioui.org/io/pointer" "gioui.org/layout" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" "gioui.org/widget" "loov.dev/lensm/internal/comments" "loov.dev/lensm/internal/disasm" "loov.dev/lensm/internal/gui" "loov.dev/lensm/internal/syntax" ) type UI struct { *disasm.Code asm gui.ScrollRegion src gui.ScrollRegion hl highlightCache mousePosition f32.Point SelectedAsm int SelectedView View SelectedFile string SelectedLine int Selection TextSelection selecting bool selectionPointer pointer.ID selectionStart f32.Point selectionMoved bool } // highlightCache holds the syntax highlight spans for a Code. Layout // runs every frame; highlighting is a pure function of the immutable // code and the palette, so it is rebuilt only when either changes — // re-highlighting per frame allocates a go/scanner per source line. type highlightCache struct { code *disasm.Code syntax syntax.Palette asm [][]syntax.Span nativeText []string native [][]syntax.Span // source is indexed by source file, block, and line within the block. source [][][][]syntax.Span } func (hl *highlightCache) update(code *disasm.Code, palette syntax.Palette) { if hl.code == code && hl.syntax == palette { return } hl.code = code hl.syntax = palette hl.asm = make([][]syntax.Span, len(code.Insts)) hl.nativeText = make([]string, len(code.Insts)) hl.native = make([][]syntax.Span, len(code.Insts)) for i := range code.Insts { ix := &code.Insts[i] hl.asm[i] = syntax.HighlightAsm(ix.Text, ix.Call, palette) hl.nativeText[i] = strings.ToUpper(ix.NativeText) hl.native[i] = syntax.HighlightAsm(hl.nativeText[i], "", palette) } hl.source = make([][][][]syntax.Span, len(code.Source)) for i, src := range code.Source { blocks := make([][][]syntax.Span, len(src.Blocks)) for j, block := range src.Blocks { lines := make([][]syntax.Span, len(block.Lines)) for k, line := range block.Lines { lines[k] = syntax.HighlightSource(block.From+k, line, palette) } blocks[j] = lines } hl.source[i] = blocks } } func (ui *UI) Loaded() bool { return ui.Code != nil } func (ui *UI) ResetScroll() { ui.asm.Offset = 100000 ui.src.Offset = 100000 } type Style struct { *UI TryOpen func(gtx layout.Context, funcname string) CopyText func(gtx layout.Context, text string) // OnInteract fires on a primary press in the content, used to keep a // preview tab open once the user acts on it. OnInteract func() // Comments backs inline comment display and editing. Reads go through // the store directly; SetComment records an edit (buffered write plus // flush scheduling), and CommentKey/CommentEditor drive the single // shared inline editor. Comments *comments.Store SetComment func(comments.Coord, string) CommentKey *string CommentEditor *widget.Editor Theme *gui.Theme Syntax syntax.Palette ShowNative bool ShowHelp bool TextHeight unit.Sp } func (ui Style) Layout(gtx layout.Context) layout.Dimensions { gtx.Constraints = layout.Exact(gtx.Constraints.Max) if ui.Code == nil { return layout.Dimensions{Size: gtx.Constraints.Max} } ui.UI.hl.update(ui.Code, ui.Syntax) paint.FillShape(gtx.Ops, ui.Theme.Colors.Background, clip.Rect{Max: gtx.Constraints.Max}.Op()) defer clip.Rect{Max: gtx.Constraints.Max}.Push(gtx.Ops).Pop() c := ui.columns(gtx) mouseClicked := ui.handleInput(gtx, c) // draw gutter paint.FillShape(gtx.Ops, ui.Theme.Colors.Gutter, clip.Rect{ Min: image.Pt(int(c.gutter.Min), 0), Max: image.Pt(int(c.gutter.Max), gtx.Constraints.Max.Y), }.Op()) if scroll, ok := ui.asm.Anim.Update(gtx); ok { ui.asm.Offset = scroll } hover := ui.resolveHover(gtx, c, mouseClicked) highlightRanges := ui.layoutRelations(gtx, c, hover) ui.layoutAssembly(gtx, c, hover, highlightRanges) sourceContentHeight := ui.layoutSource(gtx, c, hover, mouseClicked) ui.layoutScrollbars(gtx, c, sourceContentHeight) ui.layoutHelp(gtx, c, hover) return layout.Dimensions{Size: gtx.Constraints.Max} }