From 4a4050ee0f3344824ee99c53747b68afeadcaceb Mon Sep 17 00:00:00 2001 From: Koni Marti Date: Sat, 7 May 2022 05:29:43 +0200 Subject: [PATCH] ui: fix panic in selector when resizing terminal Fix panic when resizing the terminal by dynamically adjusting the width of the option selector. The selector does not check the width of the terminal before printing. This can lead to a panic in the account wizard when reducing the terminal width. If the terminal width is not large enough, the space between the options is reduced. If this is still not enough, then the selector will only show the focused option and arrows indicating the alternatives. Fixes: https://todo.sr.ht/~rjarry/aerc/41 Reported-by: Omar Polo Signed-off-by: Koni Marti Acked-by: Robin Jarry --- widgets/selector.go | 68 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/widgets/selector.go b/widgets/selector.go index e773c93..46a026d 100644 --- a/widgets/selector.go +++ b/widgets/selector.go @@ -1,7 +1,10 @@ package widgets import ( + "fmt" + "github.com/gdamore/tcell/v2" + "github.com/mattn/go-runewidth" "git.sr.ht/~rjarry/aerc/config" "git.sr.ht/~rjarry/aerc/lib/ui" @@ -37,11 +40,44 @@ func (sel *Selector) Invalidate() { } func (sel *Selector) Draw(ctx *ui.Context) { - ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', - sel.uiConfig.GetStyle(config.STYLE_SELECTOR_DEFAULT)) + defaultSelectorStyle := sel.uiConfig.GetStyle(config.STYLE_SELECTOR_DEFAULT) + w, h := ctx.Width(), ctx.Height() + ctx.Fill(0, 0, w, h, ' ', defaultSelectorStyle) + + if w < 5 || h < 1 { + // if width and height are that small, don't even try to draw + // something + return + } + + y := 1 + if h == 1 { + y = 0 + } + + format := "[%s]" + + calculateWidth := func(space int) int { + neededWidth := 2 + for i, option := range sel.options { + neededWidth += runewidth.StringWidth(fmt.Sprintf(format, option)) + if i < len(sel.options)-1 { + neededWidth += space + } + } + return neededWidth - space + } + + space := 5 + for ; space > 0; space-- { + if w > calculateWidth(space) { + break + } + } + x := 2 for i, option := range sel.options { - style := sel.uiConfig.GetStyle(config.STYLE_SELECTOR_DEFAULT) + style := defaultSelectorStyle if sel.focus == i { if sel.focused { style = sel.uiConfig.GetStyle(config.STYLE_SELECTOR_FOCUSED) @@ -49,8 +85,30 @@ func (sel *Selector) Draw(ctx *ui.Context) { style = sel.uiConfig.GetStyle(config.STYLE_SELECTOR_CHOOSER) } } - x += ctx.Printf(x, 1, style, "[%s]", option) - x += 5 + + if space == 0 { + if sel.focus == i { + leftArrow, rightArrow := ' ', ' ' + if i > 0 { + leftArrow = '❮' + } + if i < len(sel.options)-1 { + rightArrow = '❯' + } + + s := runewidth.Truncate(option, + w-runewidth.RuneWidth(leftArrow)-runewidth.RuneWidth(rightArrow)-runewidth.StringWidth(fmt.Sprintf(format, "")), + "…") + + nextPos := 0 + nextPos += ctx.Printf(nextPos, y, defaultSelectorStyle, "%c", leftArrow) + nextPos += ctx.Printf(nextPos, y, style, format, s) + ctx.Printf(nextPos, y, defaultSelectorStyle, "%c", rightArrow) + } + } else { + x += ctx.Printf(x, y, style, format, option) + x += space + } } }