diff --git a/commands/prompt.go b/commands/prompt.go new file mode 100644 index 0000000..3734881 --- /dev/null +++ b/commands/prompt.go @@ -0,0 +1,33 @@ +package commands + +import ( + "errors" + "fmt" + + "git.sr.ht/~sircmpwn/aerc/widgets" +) + +type Prompt struct{} + +func init() { + register(Prompt{}) +} + +func (_ Prompt) Aliases() []string { + return []string{"prompt"} +} + +func (_ Prompt) Complete(aerc *widgets.Aerc, args []string) []string { + return nil // TODO: add completions +} + +func (_ Prompt) Execute(aerc *widgets.Aerc, args []string) error { + if len(args) < 3 { + return errors.New(fmt.Sprintf("Usage: %s ", args[0])) + } + + prompt := args[1] + cmd := args[2:] + aerc.RegisterPrompt(prompt, cmd) + return nil +} diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd index 4aa777c..225ded7 100644 --- a/doc/aerc.1.scd +++ b/doc/aerc.1.scd @@ -67,6 +67,12 @@ These commands work in any context. Cycles to the previous or next tab in the list, repeating n times (default: 1). +*prompt* + Displays the prompt on the status bar, waits for user input, then appends + that input as the last argument to the command and executes it. The input is + passed as one argument to the command, unless it is empty, in which case no + extra argument is added. + *quit* Exits aerc. diff --git a/widgets/aerc.go b/widgets/aerc.go index 5a7914a..345f3ea 100644 --- a/widgets/aerc.go +++ b/widgets/aerc.go @@ -30,6 +30,7 @@ type Aerc struct { statusbar *libui.Stack statusline *StatusLine pendingKeys []config.KeyStroke + prompts *libui.Stack tabs *libui.Tabs beep func() error } @@ -65,6 +66,7 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger, logger: logger, statusbar: statusbar, statusline: statusline, + prompts: libui.NewStack(), tabs: tabs, } @@ -105,6 +107,20 @@ func (aerc *Aerc) Tick() bool { for _, acct := range aerc.accounts { more = acct.Tick() || more } + + if len(aerc.prompts.Children()) > 0 { + more = true + previous := aerc.focused + prompt := aerc.prompts.Pop().(*ExLine) + prompt.finish = func() { + aerc.statusbar.Pop() + aerc.focus(previous) + } + + aerc.statusbar.Push(prompt) + aerc.focus(prompt) + } + return more } @@ -358,8 +374,6 @@ func (aerc *Aerc) BeginExCommand() { if aerc.simulating == 0 { aerc.cmdHistory.Add(cmd) } - aerc.statusbar.Pop() - aerc.focus(previous) }, func() { aerc.statusbar.Pop() aerc.focus(previous) @@ -370,6 +384,22 @@ func (aerc *Aerc) BeginExCommand() { aerc.focus(exline) } +func (aerc *Aerc) RegisterPrompt(prompt string, cmd []string) { + p := NewPrompt(prompt, func(text string) { + if text != "" { + cmd = append(cmd, text) + } + err := aerc.cmd(cmd) + if err != nil { + aerc.PushStatus(" "+err.Error(), 10*time.Second). + Color(tcell.ColorDefault, tcell.ColorRed) + } + }, func(cmd string) []string { + return nil // TODO: completions + }) + aerc.prompts.Push(p) +} + func (aerc *Aerc) Mailto(addr *url.URL) error { acct := aerc.SelectedAccount() if acct == nil { diff --git a/widgets/exline.go b/widgets/exline.go index be1cde1..8ec69d6 100644 --- a/widgets/exline.go +++ b/widgets/exline.go @@ -9,21 +9,21 @@ import ( type ExLine struct { ui.Invalidatable - cancel func() commit func(cmd string) + finish func() tabcomplete func(cmd string) []string cmdHistory lib.History input *ui.TextInput } -func NewExLine(commit func(cmd string), cancel func(), +func NewExLine(commit func(cmd string), finish func(), tabcomplete func(cmd string) []string, cmdHistory lib.History) *ExLine { input := ui.NewTextInput("").Prompt(":").TabComplete(tabcomplete) exline := &ExLine{ - cancel: cancel, commit: commit, + finish: finish, tabcomplete: tabcomplete, cmdHistory: cmdHistory, input: input, @@ -34,6 +34,22 @@ func NewExLine(commit func(cmd string), cancel func(), return exline } +func NewPrompt(prompt string, commit func(text string), + tabcomplete func(cmd string) []string) *ExLine { + + input := ui.NewTextInput("").Prompt(prompt).TabComplete(tabcomplete) + exline := &ExLine{ + commit: commit, + tabcomplete: tabcomplete, + cmdHistory: &nullHistory{input: input}, + input: input, + } + input.OnInvalidate(func(d ui.Drawable) { + exline.Invalidate() + }) + return exline +} + func (ex *ExLine) Invalidate() { ex.DoInvalidate(ex) } @@ -54,6 +70,7 @@ func (ex *ExLine) Event(event tcell.Event) bool { cmd := ex.input.String() ex.input.Focus(false) ex.commit(cmd) + ex.finish() case tcell.KeyUp: ex.input.Set(ex.cmdHistory.Prev()) ex.Invalidate() @@ -63,10 +80,26 @@ func (ex *ExLine) Event(event tcell.Event) bool { case tcell.KeyEsc, tcell.KeyCtrlC: ex.input.Focus(false) ex.cmdHistory.Reset() - ex.cancel() + 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() {}