mirror of
https://git.sr.ht/~rjarry/aerc
synced 2025-02-22 23:23:57 +01:00

golangci-lint 1.56 does not work with go 1.23. It causes obscure errors: [linters_context/goanalysis] buildir: panic during analysis: Cannot range over: func(yield func(K, V) bool), goroutine 4743 [running]: runtime/debug.Stack() /usr/lib/go/src/runtime/debug/stack.go:26 +0x5e github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*action).analyzeSafe.func1() /home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_action.go:104 +0x5a panic({0x164b260?, 0xc00669b4a0?}) /usr/lib/go/src/runtime/panic.go:785 +0x132 honnef.co/go/tools/go/ir.(*builder).rangeStmt(0xc000051910, 0xc00a29cf00, 0xc009bf55c0, 0x0, {0x1af1960, 0xc009bf55c0}) /home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2214 +0x894 honnef.co/go/tools/go/ir.(*builder).stmt(0xc000051910, 0xc00a29cf00, {0x1af6970?, 0xc009bf55c0?}) /home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2427 +0x20a honnef.co/go/tools/go/ir.(*builder).stmtList(...) /home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:847 honnef.co/go/tools/go/ir.(*builder).stmt(0xc000051910, 0xc00a29cf00, {0x1af6880?, 0xc004f52ed0?}) /home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2385 +0x1415 honnef.co/go/tools/go/ir.(*builder).buildFunction(0xc000051910, 0xc00a29cf00) /home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2497 +0x417 honnef.co/go/tools/go/ir.(*builder).buildFuncDecl(0xc000051910, 0xc00622eea0, 0xc004f52f00) /home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2534 +0x189 honnef.co/go/tools/go/ir.(*Package).build(0xc00622eea0) /home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2638 +0xb46 sync.(*Once).doSlow(0xc009b81260?, 0xc009bf5bc0?) /usr/lib/go/src/sync/once.go:76 +0xb4 sync.(*Once).Do(...) /usr/lib/go/src/sync/once.go:67 honnef.co/go/tools/go/ir.(*Package).Build(...) /home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/go/ir/builder.go:2556 honnef.co/go/tools/internal/passes/buildir.run(0xc000cf61a0) /home/build/go/pkg/mod/honnef.co/go/tools@v0.4.6/internal/passes/buildir/buildir.go:86 +0x18b github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*action).analyze(0xc002d77d70) /home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_action.go:190 +0x9cd github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*action).analyzeSafe.func2() /home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_action.go:112 +0x17 github.com/golangci/golangci-lint/pkg/timeutils.(*Stopwatch).TrackStage(0xc0007a5c70, {0x1859190, 0x7}, 0xc001c28f48) /home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/timeutils/stopwatch.go:111 +0x44 github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*action).analyzeSafe(0xc00212f680?) /home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_action.go:111 +0x6e github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*loadingPackage).analyze.func2(0xc002d77d70) /home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_loadingpackage.go:80 +0xa5 created by github.com/golangci/golangci-lint/pkg/golinters/goanalysis.(*loadingPackage).analyze in goroutine 3468 /home/build/go/pkg/mod/github.com/golangci/golangci-lint@v1.56.1/pkg/golinters/goanalysis/runner_loadingpackage.go:75 +0x1e9 Update golangci-lint to 1.61.0 that works with go 1.23. It has new checkers that report errors that we need to fix: lib/crypto/gpg/gpgbin/gpgbin.go:226:22: printf: non-constant format string in call to fmt.Errorf (govet) return fmt.Errorf(strings.TrimPrefix(line, "[GNUPG:] ")) ^ worker/imap/observer.go:142:21: printf: non-constant format string in call to fmt.Errorf (govet) Error: fmt.Errorf(errMsg), ^ app/dirlist.go:409:5: S1009: should omit nil check; len() for []string is defined as zero (gosimple) if dirlist.dirs == nil || len(dirlist.dirs) == 0 { ^ app/dirtree.go:181:5: S1009: should omit nil check; len() for []*git.sr.ht/~rjarry/aerc/worker/types.Thread is defined as zero (gosimple) if dt.list == nil || len(dt.list) == 0 || dt.countVisible(dt.list) < y+dt.Scroll() { ^ app/authinfo.go:30:34: printf: non-constant format string in call to (*git.sr.ht/~rjarry/aerc/lib/ui.Context).Printf (govet) ctx.Printf(0, 0, defaultStyle, text) ^ app/authinfo.go:34:27: printf: non-constant format string in call to (*git.sr.ht/~rjarry/aerc/lib/ui.Context).Printf (govet) ctx.Printf(0, 0, style, text) ^ app/authinfo.go:62:34: printf: non-constant format string in call to (*git.sr.ht/~rjarry/aerc/lib/ui.Context).Printf (govet) x += ctx.Printf(x, 0, style, text) ^ Pretty much all of these errors are us passing non-const format strings to various methods. In C land, this is a large security issue. I would assume the same stands in Go. Thank you golangci-lint! Link: https://builds.sr.ht/~rjarry/job/1332376#task-validate-500 Signed-off-by: Tristan Partin <tristan@partin.io> Acked-by: Robin Jarry <robin@jarry.cc>
325 lines
6.9 KiB
Go
325 lines
6.9 KiB
Go
package app
|
||
|
||
import (
|
||
"math"
|
||
"strings"
|
||
"sync"
|
||
|
||
"git.sr.ht/~rjarry/aerc/config"
|
||
"git.sr.ht/~rjarry/aerc/lib/log"
|
||
"git.sr.ht/~rjarry/aerc/lib/ui"
|
||
"git.sr.ht/~rockorager/vaxis"
|
||
"github.com/mattn/go-runewidth"
|
||
)
|
||
|
||
type ListBox struct {
|
||
Scrollable
|
||
title string
|
||
lines []string
|
||
selected string
|
||
cursorPos int
|
||
horizPos int
|
||
jump int
|
||
showCursor bool
|
||
showFilter bool
|
||
filterMutex sync.Mutex
|
||
filter *ui.TextInput
|
||
uiConfig *config.UIConfig
|
||
textFilter func([]string, string) []string
|
||
cb func(string)
|
||
}
|
||
|
||
func NewListBox(title string, lines []string, uiConfig *config.UIConfig, cb func(string)) *ListBox {
|
||
lb := &ListBox{
|
||
title: title,
|
||
lines: lines,
|
||
cursorPos: -1,
|
||
jump: -1,
|
||
uiConfig: uiConfig,
|
||
textFilter: nil,
|
||
cb: cb,
|
||
filter: ui.NewTextInput("", uiConfig),
|
||
}
|
||
lb.filter.OnChange(func(ti *ui.TextInput) {
|
||
var show bool
|
||
if ti.String() == "" {
|
||
show = false
|
||
} else {
|
||
show = true
|
||
}
|
||
lb.setShowFilterField(show)
|
||
lb.filter.Focus(show)
|
||
lb.Invalidate()
|
||
})
|
||
lb.dedup()
|
||
return lb
|
||
}
|
||
|
||
func (lb *ListBox) SetTextFilter(fn func([]string, string) []string) *ListBox {
|
||
lb.textFilter = fn
|
||
return lb
|
||
}
|
||
|
||
func (lb *ListBox) dedup() {
|
||
dedupped := make([]string, 0, len(lb.lines))
|
||
dedup := make(map[string]struct{})
|
||
for _, line := range lb.lines {
|
||
if _, dup := dedup[line]; dup {
|
||
log.Warnf("ignore duplicate: %s", line)
|
||
continue
|
||
}
|
||
dedup[line] = struct{}{}
|
||
dedupped = append(dedupped, line)
|
||
}
|
||
lb.lines = dedupped
|
||
}
|
||
|
||
func (lb *ListBox) setShowFilterField(b bool) {
|
||
lb.filterMutex.Lock()
|
||
defer lb.filterMutex.Unlock()
|
||
lb.showFilter = b
|
||
}
|
||
|
||
func (lb *ListBox) showFilterField() bool {
|
||
lb.filterMutex.Lock()
|
||
defer lb.filterMutex.Unlock()
|
||
return lb.showFilter
|
||
}
|
||
|
||
func (lb *ListBox) Draw(ctx *ui.Context) {
|
||
defaultStyle := lb.uiConfig.GetStyle(config.STYLE_DEFAULT)
|
||
titleStyle := lb.uiConfig.GetStyle(config.STYLE_TITLE)
|
||
w, h := ctx.Width(), ctx.Height()
|
||
ctx.Fill(0, 0, w, h, ' ', defaultStyle)
|
||
ctx.Fill(0, 0, w, 1, ' ', titleStyle)
|
||
ctx.Printf(0, 0, titleStyle, "%s", lb.title)
|
||
|
||
y := 0
|
||
if lb.showFilterField() {
|
||
y = 1
|
||
x := ctx.Printf(0, y, defaultStyle, "Filter (%d/%d): ",
|
||
len(lb.filtered()), len(lb.lines))
|
||
lb.filter.Draw(ctx.Subcontext(x, y, w-x, 1))
|
||
}
|
||
|
||
lb.drawBox(ctx.Subcontext(0, y+1, w, h-(y+1)))
|
||
}
|
||
|
||
func (lb *ListBox) moveCursor(delta int) {
|
||
list := lb.filtered()
|
||
if len(list) == 0 {
|
||
return
|
||
}
|
||
lb.cursorPos += delta
|
||
if lb.cursorPos < 0 {
|
||
lb.cursorPos = 0
|
||
}
|
||
if lb.cursorPos >= len(list) {
|
||
lb.cursorPos = len(list) - 1
|
||
}
|
||
lb.selected = list[lb.cursorPos]
|
||
lb.showCursor = true
|
||
lb.horizPos = 0
|
||
}
|
||
|
||
func (lb *ListBox) moveHorizontal(delta int) {
|
||
lb.horizPos += delta
|
||
if lb.horizPos > len(lb.selected) {
|
||
lb.horizPos = len(lb.selected)
|
||
}
|
||
if lb.horizPos < 0 {
|
||
lb.horizPos = 0
|
||
}
|
||
}
|
||
|
||
func (lb *ListBox) filtered() []string {
|
||
term := lb.filter.String()
|
||
|
||
if lb.textFilter != nil {
|
||
return lb.textFilter(lb.lines, term)
|
||
}
|
||
|
||
list := make([]string, 0, len(lb.lines))
|
||
for _, line := range lb.lines {
|
||
if strings.Contains(line, term) {
|
||
list = append(list, line)
|
||
}
|
||
}
|
||
return list
|
||
}
|
||
|
||
func (lb *ListBox) drawBox(ctx *ui.Context) {
|
||
defaultStyle := lb.uiConfig.GetStyle(config.STYLE_DEFAULT)
|
||
selectedStyle := lb.uiConfig.GetComposedStyleSelected(config.STYLE_MSGLIST_DEFAULT, nil)
|
||
|
||
w, h := ctx.Width(), ctx.Height()
|
||
lb.jump = h
|
||
list := lb.filtered()
|
||
|
||
lb.UpdateScroller(ctx.Height(), len(list))
|
||
scroll := 0
|
||
lb.cursorPos = -1
|
||
for i := 0; i < len(list); i++ {
|
||
if lb.selected == list[i] {
|
||
scroll = i
|
||
lb.cursorPos = i
|
||
break
|
||
}
|
||
}
|
||
lb.EnsureScroll(scroll)
|
||
|
||
needScrollbar := lb.NeedScrollbar()
|
||
if needScrollbar {
|
||
w -= 1
|
||
if w < 0 {
|
||
w = 0
|
||
}
|
||
}
|
||
|
||
if lb.lines == nil || len(list) == 0 {
|
||
return
|
||
}
|
||
|
||
y := 0
|
||
for i := lb.Scroll(); i < len(list) && y < h; i++ {
|
||
style := defaultStyle
|
||
line := runewidth.Truncate(list[i], w-1, "❯")
|
||
if lb.selected == list[i] && lb.showCursor {
|
||
style = selectedStyle
|
||
if len(list[i]) > w {
|
||
if len(list[i])-lb.horizPos < w {
|
||
lb.horizPos = len(list[i]) - w + 1
|
||
}
|
||
rest := list[i][lb.horizPos:]
|
||
line = runewidth.Truncate(rest,
|
||
w-1, "❯")
|
||
if lb.horizPos > 0 && len(line) > 0 {
|
||
line = "❮" + line[1:]
|
||
}
|
||
}
|
||
}
|
||
ctx.Printf(1, y, style, "%s", line)
|
||
y += 1
|
||
}
|
||
|
||
if needScrollbar {
|
||
scrollBarCtx := ctx.Subcontext(w, 0, 1, ctx.Height())
|
||
lb.drawScrollbar(scrollBarCtx)
|
||
}
|
||
}
|
||
|
||
func (lb *ListBox) drawScrollbar(ctx *ui.Context) {
|
||
gutterStyle := vaxis.Style{}
|
||
pillStyle := vaxis.Style{Attribute: vaxis.AttrReverse}
|
||
|
||
// gutter
|
||
h := ctx.Height()
|
||
ctx.Fill(0, 0, 1, h, ' ', gutterStyle)
|
||
|
||
// pill
|
||
pillSize := int(math.Ceil(float64(h) * lb.PercentVisible()))
|
||
pillOffset := int(math.Floor(float64(h) * lb.PercentScrolled()))
|
||
ctx.Fill(0, pillOffset, 1, pillSize, ' ', pillStyle)
|
||
}
|
||
|
||
func (lb *ListBox) Invalidate() {
|
||
ui.Invalidate()
|
||
}
|
||
|
||
func (lb *ListBox) Event(event vaxis.Event) bool {
|
||
showFilter := lb.showFilterField()
|
||
if key, ok := event.(vaxis.Key); ok {
|
||
switch {
|
||
case key.Matches(vaxis.KeyLeft):
|
||
if showFilter {
|
||
break
|
||
}
|
||
lb.moveHorizontal(-1)
|
||
lb.Invalidate()
|
||
return true
|
||
case key.Matches(vaxis.KeyRight):
|
||
if showFilter {
|
||
break
|
||
}
|
||
lb.moveHorizontal(+1)
|
||
lb.Invalidate()
|
||
return true
|
||
case key.Matches('b', vaxis.ModCtrl):
|
||
line := lb.selected[:lb.horizPos]
|
||
fds := strings.Fields(line)
|
||
if len(fds) > 1 {
|
||
lb.moveHorizontal(
|
||
strings.LastIndex(line,
|
||
fds[len(fds)-1]) - lb.horizPos - 1)
|
||
} else {
|
||
lb.horizPos = 0
|
||
}
|
||
lb.Invalidate()
|
||
return true
|
||
case key.Matches('w', vaxis.ModCtrl):
|
||
line := lb.selected[lb.horizPos+1:]
|
||
fds := strings.Fields(line)
|
||
if len(fds) > 1 {
|
||
lb.moveHorizontal(strings.Index(line, fds[1]))
|
||
}
|
||
lb.Invalidate()
|
||
return true
|
||
case key.Matches('a', vaxis.ModCtrl), key.Matches(vaxis.KeyHome):
|
||
if showFilter {
|
||
break
|
||
}
|
||
lb.horizPos = 0
|
||
lb.Invalidate()
|
||
return true
|
||
case key.Matches('e', vaxis.ModCtrl), key.Matches(vaxis.KeyEnd):
|
||
if showFilter {
|
||
break
|
||
}
|
||
lb.horizPos = len(lb.selected)
|
||
lb.Invalidate()
|
||
return true
|
||
case key.Matches('p', vaxis.ModCtrl), key.Matches(vaxis.KeyUp):
|
||
lb.moveCursor(-1)
|
||
lb.Invalidate()
|
||
return true
|
||
case key.Matches('n', vaxis.ModCtrl), key.Matches(vaxis.KeyDown):
|
||
lb.moveCursor(+1)
|
||
lb.Invalidate()
|
||
return true
|
||
case key.Matches(vaxis.KeyPgUp):
|
||
if lb.jump >= 0 {
|
||
lb.moveCursor(-lb.jump)
|
||
lb.Invalidate()
|
||
}
|
||
return true
|
||
case key.Matches(vaxis.KeyPgDown):
|
||
if lb.jump >= 0 {
|
||
lb.moveCursor(+lb.jump)
|
||
lb.Invalidate()
|
||
}
|
||
return true
|
||
case key.Matches(vaxis.KeyEnter):
|
||
return lb.quit(lb.selected)
|
||
case key.Matches(vaxis.KeyEsc):
|
||
return lb.quit("")
|
||
}
|
||
}
|
||
if lb.filter != nil {
|
||
handled := lb.filter.Event(event)
|
||
lb.Invalidate()
|
||
return handled
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (lb *ListBox) quit(s string) bool {
|
||
lb.filter.Focus(false)
|
||
if lb.cb != nil {
|
||
lb.cb(s)
|
||
}
|
||
return true
|
||
}
|
||
|
||
func (lb *ListBox) Focus(f bool) {
|
||
lb.filter.Focus(f)
|
||
}
|