1
0
Fork 0
mirror of https://git.sr.ht/~rjarry/aerc synced 2025-02-22 23:23:57 +01:00
aerc/app/exline.go
Tim Culverhouse 5b57d24afd textinput: make completions run async with cancellation
Make the Completer interface accept a context.Context. Provide a
cancellation feature on text input tab completion to cancel an inflight
completion command. This is particularly useful for address book
completion if the user has specified a network-accessing command, eg
carddav-query. The command is started according to the completion delay,
but is cancellable if another request comes in. We also check for
cancellation after the request is complete to ensure we only show valid
completion results.

Changelog-changed: Tab completions for text fields are run
 asynchronously. In-flight requests are cancelled when new input
 arrives.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
2024-10-24 22:13:51 +02:00

125 lines
2.8 KiB
Go

package app
import (
"context"
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/lib/ui"
"git.sr.ht/~rjarry/go-opt/v2"
"git.sr.ht/~rockorager/vaxis"
)
type ExLine struct {
commit func(cmd string)
finish func()
tabcomplete func(ctx context.Context, cmd string) ([]opt.Completion, string)
cmdHistory lib.History
input *ui.TextInput
}
func NewExLine(cmd string, commit func(cmd string), finish func(),
tabcomplete func(ctx context.Context, cmd string) ([]opt.Completion, string),
cmdHistory lib.History,
) *ExLine {
input := ui.NewTextInput("", config.Ui).Prompt(":").Set(cmd)
if config.Ui.CompletionPopovers {
input.TabComplete(
tabcomplete,
config.Ui.CompletionDelay,
config.Ui.CompletionMinChars,
&config.Binds.Global.CompleteKey,
)
}
exline := &ExLine{
commit: commit,
finish: finish,
tabcomplete: tabcomplete,
cmdHistory: cmdHistory,
input: input,
}
return exline
}
func (x *ExLine) TabComplete(tabComplete func(context.Context, string) ([]opt.Completion, string)) {
x.input.TabComplete(
tabComplete,
config.Ui.CompletionDelay,
config.Ui.CompletionMinChars,
&config.Binds.Global.CompleteKey,
)
}
func NewPrompt(prompt string, commit func(text string),
tabcomplete func(ctx context.Context, cmd string) ([]opt.Completion, string),
) *ExLine {
input := ui.NewTextInput("", config.Ui).Prompt(prompt)
if config.Ui.CompletionPopovers {
input.TabComplete(
tabcomplete,
config.Ui.CompletionDelay,
config.Ui.CompletionMinChars,
&config.Binds.Global.CompleteKey,
)
}
exline := &ExLine{
commit: commit,
tabcomplete: tabcomplete,
cmdHistory: &nullHistory{input: input},
input: input,
}
return exline
}
func (ex *ExLine) Invalidate() {
ui.Invalidate()
}
func (ex *ExLine) Draw(ctx *ui.Context) {
ex.input.Draw(ctx)
}
func (ex *ExLine) Focus(focus bool) {
ex.input.Focus(focus)
}
func (ex *ExLine) Event(event vaxis.Event) bool {
if key, ok := event.(vaxis.Key); ok {
switch {
case key.Matches(vaxis.KeyEnter), key.Matches('j', vaxis.ModCtrl):
cmd := ex.input.String()
ex.input.Focus(false)
ex.commit(cmd)
ex.finish()
case key.Matches(vaxis.KeyUp):
ex.input.Set(ex.cmdHistory.Prev())
ex.Invalidate()
case key.Matches(vaxis.KeyDown):
ex.input.Set(ex.cmdHistory.Next())
ex.Invalidate()
case key.Matches(vaxis.KeyEsc), key.Matches('c', vaxis.ModCtrl):
ex.input.Focus(false)
ex.cmdHistory.Reset()
ex.finish()
default:
return ex.input.Event(event)
}
}
return true
}
type nullHistory struct {
input *ui.TextInput
}
func (*nullHistory) Add(string) {}
func (h *nullHistory) Next() string {
return h.input.String()
}
func (h *nullHistory) Prev() string {
return h.input.String()
}
func (*nullHistory) Reset() {}