aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVitaly Ovchinnikov <v@postbox.nz>2023-09-29 18:48:47 +0000
committerRobin Jarry <robin@jarry.cc>2023-10-22 14:56:27 +0200
commit14fc59f189775dbe95daf582c08c9c1b3cd949b2 (patch)
tree285a1b8bb0d4a27a0d04ebd09287d0cabd9654ce
parent6bbb477d441b17756f89e0034c26c4c3cdc941af (diff)
downloadaerc-14fc59f189775dbe95daf582c08c9c1b3cd949b2.tar.gz
aerc-14fc59f189775dbe95daf582c08c9c1b3cd949b2.zip
terminal: add `:send-keys` command
Add a new command for sending keystrokes to the active terminal, if there is one visible. Covers split preview, message viewer, composer and the terminal mode. This can be used to navigate the embedded applications to scroll or safely quit them when needed. Signed-off-by: Vitaly Ovchinnikov <v@postbox.nz> Acked-by: Robin Jarry <robin@jarry.cc>
-rw-r--r--CHANGELOG.md1
-rw-r--r--app/account.go8
-rw-r--r--app/compose.go4
-rw-r--r--app/msgviewer.go18
-rw-r--r--app/terminal.go8
-rw-r--r--commands/send-keys.go51
-rw-r--r--doc/aerc.1.scd17
7 files changed, 107 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df55324e..707409ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
- New `flagged` criteria for `:sort`
+- New `:send-keys` command to control embedded terminals.
### Fixed
diff --git a/app/account.go b/app/account.go
index 312ba89b..817b9b74 100644
--- a/app/account.go
+++ b/app/account.go
@@ -220,6 +220,14 @@ func (acct *AccountView) SelectedMessagePart() *PartInfo {
return nil
}
+func (acct *AccountView) Terminal() *Terminal {
+ if acct.split == nil {
+ return nil
+ }
+
+ return acct.split.Terminal()
+}
+
func (acct *AccountView) isSelected() bool {
return acct == SelectedAccount()
}
diff --git a/app/compose.go b/app/compose.go
index 546184d8..76f28881 100644
--- a/app/compose.go
+++ b/app/compose.go
@@ -769,6 +769,10 @@ func (c *Composer) OnClose(fn func(composer *Composer)) {
c.onClose = append(c.onClose, fn)
}
+func (c *Composer) Terminal() *Terminal {
+ return c.editor
+}
+
func (c *Composer) Draw(ctx *ui.Context) {
c.setTitle()
c.width = ctx.Width()
diff --git a/app/msgviewer.go b/app/msgviewer.go
index bb63b28d..8ac22bcc 100644
--- a/app/msgviewer.go
+++ b/app/msgviewer.go
@@ -276,6 +276,24 @@ func (mv *MessageViewer) Invalidate() {
ui.Invalidate()
}
+func (mv *MessageViewer) Terminal() *Terminal {
+ if mv.switcher == nil {
+ return nil
+ }
+
+ nparts := len(mv.switcher.parts)
+ if nparts == 0 || mv.switcher.selected < 0 || mv.switcher.selected >= nparts {
+ return nil
+ }
+
+ pv := mv.switcher.parts[mv.switcher.selected]
+ if pv == nil {
+ return nil
+ }
+
+ return pv.term
+}
+
func (mv *MessageViewer) Store() *lib.MessageStore {
return mv.msg.Store()
}
diff --git a/app/terminal.go b/app/terminal.go
index 1b177f3e..85d84db6 100644
--- a/app/terminal.go
+++ b/app/terminal.go
@@ -12,6 +12,10 @@ import (
"github.com/gdamore/tcell/v2"
)
+type HasTerminal interface {
+ Terminal() *Terminal
+}
+
type Terminal struct {
closed int32
cmd *exec.Cmd
@@ -113,6 +117,10 @@ func (term *Terminal) Show(visible bool) {
term.visible = visible
}
+func (term *Terminal) Terminal() *Terminal {
+ return term
+}
+
func (term *Terminal) MouseEvent(localX int, localY int, event tcell.Event) {
ev, ok := event.(*tcell.EventMouse)
if !ok {
diff --git a/commands/send-keys.go b/commands/send-keys.go
new file mode 100644
index 00000000..f9cae392
--- /dev/null
+++ b/commands/send-keys.go
@@ -0,0 +1,51 @@
+package commands
+
+import (
+ "strings"
+
+ "git.sr.ht/~rjarry/aerc/app"
+ "git.sr.ht/~rjarry/aerc/config"
+ "github.com/gdamore/tcell/v2"
+ "github.com/pkg/errors"
+)
+
+type SendKeys struct{}
+
+func init() {
+ register(SendKeys{})
+}
+
+func (SendKeys) Aliases() []string {
+ return []string{"send-keys"}
+}
+
+func (SendKeys) Complete(args []string) []string {
+ return nil
+}
+
+func (SendKeys) Execute(args []string) error {
+ tab, ok := app.SelectedTabContent().(app.HasTerminal)
+ if !ok {
+ return errors.New("There is no terminal here")
+ }
+
+ term := tab.Terminal()
+ if term == nil {
+ return errors.New("The terminal is not active")
+ }
+
+ text2send := strings.Join(args[1:], "")
+ keys2send, err := config.ParseKeyStrokes(text2send)
+ if err != nil {
+ return errors.Wrapf(err, "Unable to parse keystroke: '%s'", text2send)
+ }
+
+ for _, key := range keys2send {
+ ev := tcell.NewEventKey(key.Key, key.Rune, key.Modifiers)
+ term.Event(ev)
+ }
+
+ term.Invalidate()
+
+ return nil
+}
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index e51ae8d1..a48735d7 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -119,6 +119,23 @@ These commands work in any context.
*:pwd*
Displays aerc's current working directory in the status bar.
+*:send-keys* _<keystrokes>_
+ Send keystrokes to the currently visible terminal, if any. Can be used to
+ control embedded editors to save drafts or quit in a safe manner.
+
+ Here's an example of quiting a Vim-like editor:
+
+ *:send-keys* _<Esc>:wq!<Enter>_
+
+ Note: when used in _binds.conf_ (see *aerc-binds*(5)), angle brackets
+ need to be escaped in order to make their way to the command:
+
+ <C-q> = :send-keys \\<Esc\\>:wq!\\<Enter\\><Enter>
+
+ This way the _<Esc>_ and the first _<Enter>_ keystrokes are passed to
+ *:send-keys*, while the last _<Enter>_ keystroke is executed directly,
+ committing the *:send-keys* command's execution.
+
*:term* [_<command>..._]++
*:terminal*
Opens a new terminal tab with a shell running in the current working