258 lines
6.2 KiB
Go
258 lines
6.2 KiB
Go
package problems
|
|
|
|
import (
|
|
"path"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"aego/core/mathx"
|
|
"aego/editor/core/context"
|
|
"aego/editor/core/services"
|
|
"aego/editor/core/workspace"
|
|
"aego/ui/core"
|
|
"aego/ui/widgets"
|
|
)
|
|
|
|
type row struct {
|
|
file bool
|
|
path string
|
|
errs int
|
|
warns int
|
|
index int
|
|
}
|
|
|
|
type Panel struct {
|
|
src services.Problems
|
|
nav services.Navigation
|
|
showErr bool
|
|
showWarn bool
|
|
filter string
|
|
list []context.ScriptProblem
|
|
view []context.ScriptProblem
|
|
rows []row
|
|
collapsed map[string]bool
|
|
scroll float32
|
|
selKey string
|
|
Opened int
|
|
}
|
|
|
|
func New(src services.Problems, nav services.Navigation) *Panel {
|
|
return &Panel{src: src, nav: nav, showErr: true, showWarn: true, collapsed: map[string]bool{}}
|
|
}
|
|
|
|
func (p *Panel) ID() string {
|
|
return workspace.Problems
|
|
}
|
|
|
|
func (p *Panel) Title() string {
|
|
return "Problems"
|
|
}
|
|
|
|
func (p *Panel) Focus(filter string) {
|
|
p.filter = filter
|
|
p.scroll = 0
|
|
}
|
|
|
|
func key(pr *context.ScriptProblem) string {
|
|
return pr.Category + ":" + pr.Path + ":" + strconv.Itoa(pr.Line) + ":" + strconv.Itoa(pr.Col) + ":" + pr.Node.String() + ":" + pr.Code
|
|
}
|
|
|
|
func where(pr *context.ScriptProblem) string {
|
|
if !pr.Node.IsZero() {
|
|
return "node " + pr.Node.String()[:8]
|
|
}
|
|
return strconv.Itoa(pr.Line) + ":" + strconv.Itoa(pr.Col)
|
|
}
|
|
|
|
func (p *Panel) refresh() (int, int) {
|
|
p.list, _ = p.src.ScriptProblems(p.list)
|
|
q := strings.ToLower(strings.TrimSpace(p.filter))
|
|
errs, warns := 0, 0
|
|
p.view = p.view[:0]
|
|
for _, pr := range p.list {
|
|
if pr.Error {
|
|
errs++
|
|
} else {
|
|
warns++
|
|
}
|
|
if (pr.Error && !p.showErr) || (!pr.Error && !p.showWarn) {
|
|
continue
|
|
}
|
|
if q != "" && !strings.Contains(strings.ToLower(pr.Category+" "+pr.Path+" "+pr.Package+" "+pr.Code+" "+pr.Message), q) {
|
|
continue
|
|
}
|
|
p.view = append(p.view, pr)
|
|
}
|
|
sort.SliceStable(p.view, func(i, j int) bool {
|
|
a, b := p.view[i], p.view[j]
|
|
if a.Path != b.Path {
|
|
return a.Path < b.Path
|
|
}
|
|
if a.Line != b.Line {
|
|
return a.Line < b.Line
|
|
}
|
|
if a.Col != b.Col {
|
|
return a.Col < b.Col
|
|
}
|
|
return a.Node.String() < b.Node.String()
|
|
})
|
|
p.rows = p.rows[:0]
|
|
for i := 0; i < len(p.view); {
|
|
j := i
|
|
fr := row{file: true, path: p.view[i].Path}
|
|
for j < len(p.view) && p.view[j].Path == fr.path {
|
|
if p.view[j].Error {
|
|
fr.errs++
|
|
} else {
|
|
fr.warns++
|
|
}
|
|
j++
|
|
}
|
|
p.rows = append(p.rows, fr)
|
|
if !p.collapsed[fr.path] {
|
|
for k := i; k < j; k++ {
|
|
p.rows = append(p.rows, row{path: fr.path, index: k})
|
|
}
|
|
}
|
|
i = j
|
|
}
|
|
return errs, warns
|
|
}
|
|
|
|
func (p *Panel) selected() *context.ScriptProblem {
|
|
if p.selKey == "" {
|
|
return nil
|
|
}
|
|
for i := range p.view {
|
|
if key(&p.view[i]) == p.selKey {
|
|
return &p.view[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Panel) Draw(f *services.Frame, r mathx.Rect) {
|
|
ui := f.UI
|
|
th := &ui.Theme
|
|
errs, warns := p.refresh()
|
|
bar := ui.Cursor().Next(0)
|
|
x, right := bar.X, bar.X+bar.W
|
|
place := func(w float32) mathx.Rect {
|
|
rc := mathx.Rect{X: x, Y: bar.Y, W: w, H: bar.H}
|
|
x += w + 4
|
|
return rc
|
|
}
|
|
widgets.ToggleButton(ui, ui.ID("err"), place(96), "Errors "+strconv.Itoa(errs), &p.showErr)
|
|
widgets.ToggleButton(ui, ui.ID("warn"), place(116), "Warnings "+strconv.Itoa(warns), &p.showWarn)
|
|
if right-x >= 80 {
|
|
widgets.SearchField(ui, ui.ID("search"), mathx.Rect{X: x, Y: bar.Y, W: right - x, H: bar.H}, &p.filter)
|
|
}
|
|
top := bar.Y + bar.H + th.Spacing
|
|
sel := p.selected()
|
|
detailH := float32(0)
|
|
if sel != nil {
|
|
detailH = min(th.RowHeight*4.5, (r.Y+r.H-top)/2)
|
|
}
|
|
list := mathx.Rect{X: r.X, Y: top, W: r.W, H: max(0, r.Y+r.H-top-detailH)}
|
|
if len(p.rows) == 0 {
|
|
msg := "No problems"
|
|
if len(p.list) > 0 {
|
|
msg = "No problems match the filter"
|
|
}
|
|
ui.TextIn(list, msg, th.TextDisabled, core.AlignCenter)
|
|
}
|
|
widgets.VirtualList(ui, ui.ID("list"), list, len(p.rows), th.RowHeight, &p.scroll, func(i int, rr mathx.Rect) {
|
|
p.drawRow(ui, i, rr)
|
|
})
|
|
if sel == nil || detailH <= 0 {
|
|
return
|
|
}
|
|
dr := mathx.Rect{X: r.X, Y: r.Y + r.H - detailH, W: r.W, H: detailH}
|
|
ui.Rect(dr, th.PanelAlt)
|
|
ui.PushClip(dr)
|
|
ui.PushCursor(core.Cursor{X: dr.X + th.Padding, Y: dr.Y + th.Padding, W: dr.W - 2*th.Padding, RowH: th.RowHeight, Pad: th.Padding})
|
|
widgets.LabelWrapped(ui, sel.Code+" "+sel.Message, false)
|
|
if sel.Help != "" {
|
|
widgets.LabelWrapped(ui, sel.Help, true)
|
|
}
|
|
info := sel.Path + " " + where(sel)
|
|
if sel.Script() {
|
|
info = sel.Path + ":" + where(sel) + " package " + sel.Package
|
|
} else {
|
|
info += " " + sel.Category
|
|
}
|
|
widgets.LabelDim(ui, info)
|
|
ui.PopCursor()
|
|
ui.PopClip()
|
|
}
|
|
|
|
func (p *Panel) drawRow(ui *core.Context, i int, rr mathx.Rect) {
|
|
th := &ui.Theme
|
|
rw := p.rows[i]
|
|
if rw.file {
|
|
id := core.IDOf("problems.file/" + rw.path)
|
|
if ui.Pressed(id, rr) {
|
|
p.collapsed[rw.path] = !p.collapsed[rw.path]
|
|
}
|
|
if ui.Hot() == id {
|
|
ui.Rect(rr, th.WidgetHot)
|
|
}
|
|
widgets.Chevron(ui, mathx.Rect{X: rr.X, Y: rr.Y, W: th.Indent, H: rr.H}, !p.collapsed[rw.path], th.TextDim)
|
|
x := rr.X + th.Indent + 4
|
|
name := path.Base(rw.path)
|
|
ui.Text(name, mathx.Vec2{X: x, Y: ui.TextY(rr)}, th.Text)
|
|
x += ui.TextSize(name).X + 10
|
|
ui.Text(path.Dir(rw.path), mathx.Vec2{X: x, Y: ui.TextY(rr)}, th.TextDim)
|
|
counts := ""
|
|
if rw.errs > 0 {
|
|
counts = strconv.Itoa(rw.errs) + " errors"
|
|
}
|
|
if rw.warns > 0 {
|
|
if counts != "" {
|
|
counts += ", "
|
|
}
|
|
counts += strconv.Itoa(rw.warns) + " warnings"
|
|
}
|
|
col := th.Warn
|
|
if rw.errs > 0 {
|
|
col = th.Error
|
|
}
|
|
ui.TextIn(rr, counts, col, core.AlignRight)
|
|
return
|
|
}
|
|
pr := &p.view[rw.index]
|
|
k := key(pr)
|
|
id := core.IDSeq(ui.ID("row"), i)
|
|
if ui.Pressed(id, rr) {
|
|
p.selKey = k
|
|
p.Opened++
|
|
p.nav.Open(pr.Location())
|
|
}
|
|
switch {
|
|
case p.selKey == k:
|
|
ui.Rect(rr, th.SelectionDim)
|
|
case ui.Hot() == id:
|
|
ui.Rect(rr, th.WidgetHot)
|
|
}
|
|
icon, col := "log.warn", th.Warn
|
|
if pr.Error {
|
|
icon, col = "log.error", th.Error
|
|
}
|
|
x := rr.X + th.Indent*1.5
|
|
widgets.IconAt(ui, mathx.Rect{X: x, Y: rr.Y + (rr.H-16)/2, W: 16, H: 16}, icon, col)
|
|
x += 20
|
|
code := pr.Code
|
|
if !pr.Script() {
|
|
code = pr.Category + " " + pr.Code
|
|
}
|
|
loc := where(pr)
|
|
lw := ui.TextSize(loc).X + 12
|
|
ui.PushClip(mathx.Rect{X: x, Y: rr.Y, W: max(0, rr.X+rr.W-x-lw), H: rr.H})
|
|
ui.Text(code, mathx.Vec2{X: x, Y: ui.TextY(rr)}, th.TextDim)
|
|
ui.Text(pr.Message, mathx.Vec2{X: x + ui.TextSize(code).X + 10, Y: ui.TextY(rr)}, th.Text)
|
|
ui.PopClip()
|
|
ui.TextIn(rr, loc, th.TextDim, core.AlignRight)
|
|
widgets.Tooltip(ui, id, pr.Help)
|
|
}
|