aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/aerc.go15
-rw-r--r--app/app.go3
-rw-r--r--app/exline.go10
-rw-r--r--commands/completion_helpers.go3
-rw-r--r--commands/compose/send.go3
-rw-r--r--completer/completer.go17
-rw-r--r--lib/ui/textinput.go54
-rw-r--r--main.go2
8 files changed, 67 insertions, 40 deletions
diff --git a/app/aerc.go b/app/aerc.go
index 72a60562..12bb6893 100644
--- a/app/aerc.go
+++ b/app/aerc.go
@@ -1,6 +1,7 @@
package app
import (
+ "context"
"errors"
"fmt"
"io"
@@ -29,7 +30,7 @@ type Aerc struct {
accounts map[string]*AccountView
cmd func(string, *config.AccountConfig, *models.MessageInfo) error
cmdHistory lib.History
- complete func(cmd string) ([]opt.Completion, string)
+ complete func(ctx context.Context, cmd string) ([]opt.Completion, string)
focused ui.Interactive
grid *ui.Grid
simulating int
@@ -54,7 +55,7 @@ type Choice struct {
func (aerc *Aerc) Init(
crypto crypto.Provider,
cmd func(string, *config.AccountConfig, *models.MessageInfo) error,
- complete func(cmd string) ([]opt.Completion, string), cmdHistory lib.History,
+ complete func(ctx context.Context, cmd string) ([]opt.Completion, string), cmdHistory lib.History,
deferLoop chan struct{},
) {
tabs := ui.NewTabs(func(d ui.Drawable) *config.UIConfig {
@@ -317,8 +318,8 @@ func (aerc *Aerc) simulate(strokes []config.KeyStroke) {
aerc.simulating -= 1
if exline, ok := aerc.focused.(*ExLine); ok {
// we are still focused on the exline, turn on tab complete
- exline.TabComplete(func(cmd string) ([]opt.Completion, string) {
- return aerc.complete(cmd)
+ exline.TabComplete(func(ctx context.Context, cmd string) ([]opt.Completion, string) {
+ return aerc.complete(ctx, cmd)
})
if complete {
// force completion now
@@ -633,7 +634,7 @@ func (aerc *Aerc) focus(item ui.Interactive) {
func (aerc *Aerc) BeginExCommand(cmd string) {
previous := aerc.focused
- var tabComplete func(string) ([]opt.Completion, string)
+ var tabComplete func(context.Context, string) ([]opt.Completion, string)
if aerc.simulating != 0 {
// Don't try to draw completions for simulated events
tabComplete = nil
@@ -671,7 +672,7 @@ func (aerc *Aerc) RegisterPrompt(prompt string, cmd string) {
if err != nil {
aerc.PushError(err.Error())
}
- }, func(cmd string) ([]opt.Completion, string) {
+ }, func(ctx context.Context, cmd string) ([]opt.Completion, string) {
return nil, "" // TODO: completions
})
aerc.prompts.Push(p)
@@ -698,7 +699,7 @@ func (aerc *Aerc) RegisterChoices(choices []Choice) {
if err != nil {
aerc.PushError(err.Error())
}
- }, func(cmd string) ([]opt.Completion, string) {
+ }, func(ctx context.Context, cmd string) ([]opt.Completion, string) {
return nil, "" // TODO: completions
})
aerc.prompts.Push(p)
diff --git a/app/app.go b/app/app.go
index 071aac74..3af6677f 100644
--- a/app/app.go
+++ b/app/app.go
@@ -1,6 +1,7 @@
package app
import (
+ "context"
"time"
"git.sr.ht/~rjarry/aerc/config"
@@ -19,7 +20,7 @@ var aerc Aerc
func Init(
crypto crypto.Provider,
cmd func(string, *config.AccountConfig, *models.MessageInfo) error,
- complete func(cmd string) ([]opt.Completion, string), history lib.History,
+ complete func(ctx context.Context, cmd string) ([]opt.Completion, string), history lib.History,
deferLoop chan struct{},
) {
aerc.Init(crypto, cmd, complete, history, deferLoop)
diff --git a/app/exline.go b/app/exline.go
index 5f8e4280..20be8a4b 100644
--- a/app/exline.go
+++ b/app/exline.go
@@ -1,6 +1,8 @@
package app
import (
+ "context"
+
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/lib/ui"
@@ -11,13 +13,13 @@ import (
type ExLine struct {
commit func(cmd string)
finish func()
- tabcomplete func(cmd string) ([]opt.Completion, string)
+ 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(cmd string) ([]opt.Completion, string),
+ tabcomplete func(ctx context.Context, cmd string) ([]opt.Completion, string),
cmdHistory lib.History,
) *ExLine {
input := ui.NewTextInput("", config.Ui).Prompt(":").Set(cmd)
@@ -39,7 +41,7 @@ func NewExLine(cmd string, commit func(cmd string), finish func(),
return exline
}
-func (x *ExLine) TabComplete(tabComplete func(string) ([]opt.Completion, string)) {
+func (x *ExLine) TabComplete(tabComplete func(context.Context, string) ([]opt.Completion, string)) {
x.input.TabComplete(
tabComplete,
config.Ui.CompletionDelay,
@@ -49,7 +51,7 @@ func (x *ExLine) TabComplete(tabComplete func(string) ([]opt.Completion, string)
}
func NewPrompt(prompt string, commit func(text string),
- tabcomplete func(cmd string) ([]opt.Completion, string),
+ tabcomplete func(ctx context.Context, cmd string) ([]opt.Completion, string),
) *ExLine {
input := ui.NewTextInput("", config.Ui).Prompt(prompt)
if config.Ui.CompletionPopovers {
diff --git a/commands/completion_helpers.go b/commands/completion_helpers.go
index 8d293a32..6017035e 100644
--- a/commands/completion_helpers.go
+++ b/commands/completion_helpers.go
@@ -1,6 +1,7 @@
package commands
import (
+ "context"
"fmt"
"net/mail"
"strings"
@@ -30,7 +31,7 @@ func GetAddress(search string) []string {
})
if cmpl != nil {
- addrList, _ := cmpl.ForHeader("to")(search)
+ addrList, _ := cmpl.ForHeader("to")(context.Background(), search)
for _, full := range addrList {
addr, err := mail.ParseAddress(full.Value)
if err != nil {
diff --git a/commands/compose/send.go b/commands/compose/send.go
index 73e3e13a..f9faf1b5 100644
--- a/commands/compose/send.go
+++ b/commands/compose/send.go
@@ -2,6 +2,7 @@ package compose
import (
"bytes"
+ "context"
"fmt"
"io"
"net/url"
@@ -147,7 +148,7 @@ func (s Send) Execute(args []string) error {
from, rcpts, tab.Name, s.CopyTo,
s.Archive, copyToReplied)
}
- }, func(cmd string) ([]opt.Completion, string) {
+ }, func(ctx context.Context, cmd string) ([]opt.Completion, string) {
var comps []opt.Completion
if cmd == "" {
comps = append(comps, opt.Completion{Value: "y"})
diff --git a/completer/completer.go b/completer/completer.go
index c5b61528..670db34d 100644
--- a/completer/completer.go
+++ b/completer/completer.go
@@ -2,6 +2,7 @@ package completer
import (
"bufio"
+ "context"
"errors"
"fmt"
"io"
@@ -31,7 +32,7 @@ type Completer struct {
// A CompleteFunc accepts a string to be completed and returns a slice of
// completions candidates with a prefix to prepend to the chosen candidate
-type CompleteFunc func(string) ([]opt.Completion, string)
+type CompleteFunc func(context.Context, string) ([]opt.Completion, string)
// New creates a new Completer with the specified address book command.
func New(addressBookCmd string, errHandler func(error)) *Completer {
@@ -51,8 +52,8 @@ func (c *Completer) ForHeader(h string) CompleteFunc {
return nil
}
// wrap completeAddress in an error handler
- return func(s string) ([]opt.Completion, string) {
- completions, prefix, err := c.completeAddress(s)
+ return func(ctx context.Context, s string) ([]opt.Completion, string) {
+ completions, prefix, err := c.completeAddress(ctx, s)
if err != nil {
c.handleErr(err)
return []opt.Completion{}, ""
@@ -80,9 +81,9 @@ var tooManyLines = fmt.Errorf("returned more than %d lines", maxCompletionLines)
// completeAddress uses the configured address book completion command to fetch
// completions for the specified string, returning a slice of completions and
// a prefix to be prepended to the selected completion, or an error.
-func (c *Completer) completeAddress(s string) ([]opt.Completion, string, error) {
+func (c *Completer) completeAddress(ctx context.Context, s string) ([]opt.Completion, string, error) {
prefix, candidate := c.parseAddress(s)
- cmd, err := c.getAddressCmd(candidate)
+ cmd, err := c.getAddressCmd(ctx, candidate)
if err != nil {
return nil, "", err
}
@@ -137,7 +138,7 @@ func (c *Completer) parseAddress(s string) (string, string) {
// getAddressCmd constructs an exec.Cmd based on the configured command and
// specified query.
-func (c *Completer) getAddressCmd(s string) (*exec.Cmd, error) {
+func (c *Completer) getAddressCmd(ctx context.Context, s string) (*exec.Cmd, error) {
if strings.TrimSpace(c.AddressBookCmd) == "" {
return nil, fmt.Errorf("no command configured")
}
@@ -147,9 +148,9 @@ func (c *Completer) getAddressCmd(s string) (*exec.Cmd, error) {
return nil, fmt.Errorf("empty command")
}
if len(parts) > 1 {
- return exec.Command(parts[0], parts[1:]...), nil
+ return exec.CommandContext(ctx, parts[0], parts[1:]...), nil
}
- return exec.Command(parts[0]), nil
+ return exec.CommandContext(ctx, parts[0]), nil
}
// readCompletions reads a slice of completions from r line by line. Each line
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index d52859b7..08fc2c92 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -1,6 +1,7 @@
package ui
import (
+ "context"
"math"
"strings"
"sync"
@@ -28,7 +29,8 @@ type TextInput struct {
text []vaxis.Character
change []func(ti *TextInput)
focusLost []func(ti *TextInput)
- tabcomplete func(s string) ([]opt.Completion, string)
+ tabcomplete func(ctx context.Context, s string) ([]opt.Completion, string)
+ tabcompleteCancel context.CancelFunc
completions []opt.Completion
prefix string
completeIndex int
@@ -45,10 +47,11 @@ type TextInput struct {
func NewTextInput(text string, ui *config.UIConfig) *TextInput {
chars := vaxis.Characters(text)
return &TextInput{
- cells: -1,
- text: chars,
- index: len(chars),
- uiConfig: ui,
+ cells: -1,
+ text: chars,
+ index: len(chars),
+ uiConfig: ui,
+ tabcompleteCancel: func() {},
}
}
@@ -63,7 +66,7 @@ func (ti *TextInput) Prompt(prompt string) *TextInput {
}
func (ti *TextInput) TabComplete(
- tabcomplete func(s string) ([]opt.Completion, string),
+ tabcomplete func(ctx context.Context, s string) ([]opt.Completion, string),
d time.Duration, minChars int, key *config.KeyStroke,
) *TextInput {
ti.tabcomplete = tabcomplete
@@ -342,17 +345,34 @@ func (ti *TextInput) showCompletions(explicit bool) {
// no completer
return
}
- ti.completions, ti.prefix = ti.tabcomplete(ti.StringLeft())
-
- if explicit && len(ti.completions) == 1 {
- // automatically accept if there is only one choice
- ti.completeIndex = 0
- ti.executeCompletion()
- ti.invalidateCompletions()
- } else {
- ti.completeIndex = -1
- }
- Invalidate()
+ if ti.tabcompleteCancel != nil {
+ // Cancel any inflight completions we currently have
+ ti.tabcompleteCancel()
+ }
+ ctx, cancel := context.WithCancel(context.Background())
+ ti.tabcompleteCancel = cancel
+ go func() {
+ defer log.PanicHandler()
+ matches, prefix := ti.tabcomplete(ctx, ti.StringLeft())
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ ti.Lock()
+ defer ti.Unlock()
+ ti.completions = matches
+ ti.prefix = prefix
+ if explicit && len(ti.completions) == 1 {
+ // automatically accept if there is only one choice
+ ti.completeIndex = 0
+ ti.executeCompletion()
+ ti.invalidateCompletions()
+ } else {
+ ti.completeIndex = -1
+ }
+ Invalidate()
+ }
+ }()
}
func (ti *TextInput) OnChange(onChange func(ti *TextInput)) {
diff --git a/main.go b/main.go
index 55580f88..36094ecd 100644
--- a/main.go
+++ b/main.go
@@ -50,7 +50,7 @@ func execCommand(
return err
}
-func getCompletions(cmdline string) ([]opt.Completion, string) {
+func getCompletions(ctx context.Context, cmdline string) ([]opt.Completion, string) {
// complete template terms
if options, prefix, ok := commands.GetTemplateCompletion(cmdline); ok {
sort.Strings(options)