binds: display active keybinds in a dialog box

Show contextual keybinds in a textbox when using the ':help keys'
command. This command is bound to '?' by default.

Fixes: https://todo.sr.ht/~rjarry/aerc/42
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
Koni Marti 2022-08-08 22:04:04 +02:00 committed by Robin Jarry
parent e31dbe9f31
commit 5c8a749cfa
5 changed files with 88 additions and 5 deletions

View file

@ -19,6 +19,7 @@ var pages = []string{
"stylesets", "stylesets",
"templates", "templates",
"tutorial", "tutorial",
"keys",
} }
func init() { func init() {
@ -40,5 +41,29 @@ func (Help) Execute(aerc *widgets.Aerc, args []string) error {
} else if len(args) > 2 { } else if len(args) > 2 {
return errors.New("Usage: help [topic]") return errors.New("Usage: help [topic]")
} }
if page == "aerc-keys" {
aerc.AddDialog(widgets.NewDialog(
widgets.NewListBox(
"Bindings: Press <Esc> or <Enter> to close. "+
"Start typing to filter bindings.",
aerc.HumanReadableBindings(),
aerc.SelectedAccountUiConfig(),
func(_ string) {
helpClose(aerc)
aerc.CloseDialog()
},
),
func(h int) int { return h / 4 },
func(h int) int { return h / 2 },
))
}
return TermCore(aerc, []string{"term", "man", page}) return TermCore(aerc, []string{"term", "man", page})
} }
func helpClose(aerc *widgets.Aerc) {
if content, ok := aerc.SelectedTabContent().(*widgets.MessageViewer); ok {
content.UpdateScreen()
}
}

View file

@ -22,7 +22,7 @@ type Binding struct {
} }
type KeyBindings struct { type KeyBindings struct {
bindings []*Binding Bindings []*Binding
// If false, disable global keybindings in this context // If false, disable global keybindings in this context
Globals bool Globals bool
@ -48,7 +48,7 @@ func NewKeyBindings() *KeyBindings {
func MergeBindings(bindings ...*KeyBindings) *KeyBindings { func MergeBindings(bindings ...*KeyBindings) *KeyBindings {
merged := NewKeyBindings() merged := NewKeyBindings()
for _, b := range bindings { for _, b := range bindings {
merged.bindings = append(merged.bindings, b.bindings...) merged.Bindings = append(merged.Bindings, b.Bindings...)
} }
merged.ExKey = bindings[0].ExKey merged.ExKey = bindings[0].ExKey
merged.Globals = bindings[0].Globals merged.Globals = bindings[0].Globals
@ -79,7 +79,7 @@ func (config AercConfig) MergeContextualBinds(baseBinds *KeyBindings,
func (bindings *KeyBindings) Add(binding *Binding) { func (bindings *KeyBindings) Add(binding *Binding) {
// TODO: Search for conflicts? // TODO: Search for conflicts?
bindings.bindings = append(bindings.bindings, binding) bindings.Bindings = append(bindings.Bindings, binding)
} }
func (bindings *KeyBindings) GetBinding( func (bindings *KeyBindings) GetBinding(
@ -88,7 +88,7 @@ func (bindings *KeyBindings) GetBinding(
incomplete := false incomplete := false
// TODO: This could probably be a sorted list to speed things up // TODO: This could probably be a sorted list to speed things up
// TODO: Deal with bindings that share a prefix // TODO: Deal with bindings that share a prefix
for _, binding := range bindings.bindings { for _, binding := range bindings.Bindings {
if len(binding.Input) < len(input) { if len(binding.Input) < len(input) {
continue continue
} }
@ -121,7 +121,7 @@ func (bindings *KeyBindings) GetBinding(
func (bindings *KeyBindings) GetReverseBindings(output []KeyStroke) [][]KeyStroke { func (bindings *KeyBindings) GetReverseBindings(output []KeyStroke) [][]KeyStroke {
var inputs [][]KeyStroke var inputs [][]KeyStroke
for _, binding := range bindings.bindings { for _, binding := range bindings.Bindings {
if len(binding.Output) != len(output) { if len(binding.Output) != len(output) {
continue continue
} }

View file

@ -4,6 +4,7 @@
<C-p> = :prev-tab<Enter> <C-p> = :prev-tab<Enter>
<C-n> = :next-tab<Enter> <C-n> = :next-tab<Enter>
<C-t> = :term<Enter> <C-t> = :term<Enter>
? = :help keys<Enter>
[messages] [messages]
q = :quit<Enter> q = :quit<Enter>

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/url" "net/url"
"sort"
"strings" "strings"
"time" "time"
@ -199,6 +200,40 @@ func (aerc *Aerc) Draw(ctx *ui.Context) {
} }
} }
func (aerc *Aerc) HumanReadableBindings() []string {
var result []string
binds := aerc.getBindings()
format := func(s string) string {
s = strings.ReplaceAll(s, "<space>", " ")
return strings.ReplaceAll(s, "%", "%%")
}
fmtStr := "%10s %s"
for _, bind := range binds.Bindings {
result = append(result, fmt.Sprintf(fmtStr,
format(config.FormatKeyStrokes(bind.Input)),
format(config.FormatKeyStrokes(bind.Output)),
))
}
if binds.Globals && aerc.conf.Bindings.Global != nil {
for _, bind := range aerc.conf.Bindings.Global.Bindings {
result = append(result, fmt.Sprintf(fmtStr+" (Globals)",
format(config.FormatKeyStrokes(bind.Input)),
format(config.FormatKeyStrokes(bind.Output)),
))
}
}
result = append(result, fmt.Sprintf(fmtStr,
"$ex",
fmt.Sprintf("'%c'", binds.ExKey.Rune),
))
result = append(result, fmt.Sprintf(fmtStr,
"Globals",
fmt.Sprintf("%v", binds.Globals),
))
sort.Strings(result)
return result
}
func (aerc *Aerc) getBindings() *config.KeyBindings { func (aerc *Aerc) getBindings() *config.KeyBindings {
selectedAccountName := "" selectedAccountName := ""
if aerc.SelectedAccount() != nil { if aerc.SelectedAccount() != nil {

View file

@ -392,6 +392,22 @@ func (mv *MessageViewer) Close() error {
return nil return nil
} }
func (mv *MessageViewer) UpdateScreen() {
if mv.switcher == nil {
return
}
parts := mv.switcher.parts
selected := mv.switcher.selected
if selected < 0 {
return
}
if len(parts) > 0 && selected < len(parts) {
if part := parts[selected]; part != nil {
part.UpdateScreen()
}
}
}
func (ps *PartSwitcher) Invalidate() { func (ps *PartSwitcher) Invalidate() {
ps.DoInvalidate(ps) ps.DoInvalidate(ps)
} }
@ -628,6 +644,12 @@ func (pv *PartViewer) SetSource(reader io.Reader) {
pv.attemptCopy() pv.attemptCopy()
} }
func (pv *PartViewer) UpdateScreen() {
if pv.term != nil {
pv.term.Invalidate()
}
}
func (pv *PartViewer) attemptCopy() { func (pv *PartViewer) attemptCopy() {
if pv.source == nil || pv.pager == nil || pv.pager.Process == nil { if pv.source == nil || pv.pager == nil || pv.pager.Process == nil {
return return