1
0
Fork 0
mirror of https://git.sr.ht/~rjarry/aerc synced 2025-02-22 23:23:57 +01:00
aerc/app/listbox.go
Tristan Partin 2d6b2c0e7b lint: update golangci-lint to 1.61.0
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>
2024-09-20 09:23:36 +02:00

325 lines
6.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}