Skip to content

Commit

Permalink
viewer: allow filters to provide their own paging
Browse files Browse the repository at this point in the history
Allow filter commands to declare that they will provide their own paging
functionality. To do so, a filter command must start with an exclamation
mark "!". For example:

	text/html = ! w3m -I UTF-8 -T text/html

This will effectively run w3m as the main process running in the
embedded terminal of the part viewer. It will provide interactive access
to w3m and will effectively allow navigating using w3m paging and
coloring.

Implements: https://todo.sr.ht/~rjarry/aerc/250
Signed-off-by: Robin Jarry <[email protected]>
Tested-by: Inwit <[email protected]>
  • Loading branch information
rjarry committed Dec 5, 2024
1 parent 88b9065 commit 5a119d7
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 30 deletions.
71 changes: 48 additions & 23 deletions app/msgviewer.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,14 +452,6 @@ func NewPartViewer(
pagerin io.WriteCloser
term *Terminal
)
pagerCmd, err := CmdFallbackSearch(config.PagerCmds(), false)
if err != nil {
acct.PushError(fmt.Errorf("could not start pager: %w", err))
return nil, err
}
cmd := opt.SplitArgs(pagerCmd)
pager = exec.Command(cmd[0], cmd[1:]...)

info := msg.MessageInfo()
mime := part.FullMIMEType()

Expand Down Expand Up @@ -492,9 +484,21 @@ func NewPartViewer(
log.Tracef("command %v", f.Command)
}
}
if filter != nil {
if filter == nil {
continue
}
if !f.NeedsPager {
pager = filter
break
}
pagerCmd, err := CmdFallbackSearch(config.PagerCmds(), false)
if err != nil {
acct.PushError(fmt.Errorf("could not start pager: %w", err))
return nil, err
}
cmd := opt.SplitArgs(pagerCmd)
pager = exec.Command(cmd[0], cmd[1:]...)
break
}
var noFilter *ui.Grid
if filter != nil {
Expand Down Expand Up @@ -524,8 +528,14 @@ func NewPartViewer(
if config.General.EnableOSC8 {
filter.Env = append(filter.Env, "AERC_OSC8_URLS=1")
}
log.Debugf("<%s> part=%v %s: %v | %v",
info.Envelope.MessageId, curindex, mime, filter, pager)
if pager == filter {
log.Debugf("<%s> part=%v %s: %v",
info.Envelope.MessageId, curindex, mime, filter)
} else {
log.Debugf("<%s> part=%v %s: %v | %v",
info.Envelope.MessageId, curindex, mime, filter, pager)
}
var err error
if pagerin, err = pager.StdinPipe(); err != nil {
return nil, err
}
Expand Down Expand Up @@ -598,26 +608,36 @@ func (pv *PartViewer) attemptCopy() {
if strings.EqualFold(pv.part.MIMEType, "text") {
pv.source = parse.StripAnsi(pv.hyperlinks(pv.source))
}
pv.filter.Stdin = pv.source
pv.filter.Stdout = pv.pagerin
pv.filter.Stderr = pv.pagerin
err := pv.filter.Start()
if err != nil {
log.Errorf("error running filter: %v", err)
return
if pv.filter != pv.pager {
// Filter is a separate process that needs to output to the pager.
pv.filter.Stdin = pv.source
pv.filter.Stdout = pv.pagerin
pv.filter.Stderr = pv.pagerin
err := pv.filter.Start()
if err != nil {
log.Errorf("error running filter: %v", err)
return
}
}
go func() {
defer log.PanicHandler()
defer atomic.StoreInt32(&pv.copying, 0)
err = pv.filter.Wait()
if err != nil {
log.Errorf("error waiting for filter: %v", err)
return
var err error
if pv.filter == pv.pager {
// Filter already implements its own paging.
_, err = io.Copy(pv.pagerin, pv.source)
if err != nil {
log.Errorf("io.Copy: %s", err)
}
} else {
err = pv.filter.Wait()
if err != nil {
log.Errorf("filter.Wait: %v", err)
}
}
err = pv.pagerin.Close()
if err != nil {
log.Errorf("error closing pager pipe: %v", err)
return
}
}()
}
Expand All @@ -627,6 +647,11 @@ func (pv *PartViewer) writeMailHeaders() {
if !config.Viewer.ShowHeaders || info.RFC822Headers == nil {
return
}
if pv.filter == pv.pager {
// Filter already implements its own paging.
// Piping another filter into it will cause mayhem.
return
}
var file io.WriteCloser

for _, f := range config.Filters {
Expand Down
1 change: 1 addition & 0 deletions config/aerc.conf
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,7 @@ message/delivery-status=colorize
message/rfc822=colorize
#text/html=pandoc -f html -t plain | colorize
#text/html=html | colorize
#text/html=! w3m -T text/html -I UTF-8
#text/*=bat -fP --file-name="$AERC_FILENAME"
#application/x-sh=bat -fP -l sh
#image/*=catimg -w $(tput cols) -
Expand Down
22 changes: 15 additions & 7 deletions config/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ const (
)

type FilterConfig struct {
Type FilterType
Filter string
Command string
Header string
Regex *regexp.Regexp
Type FilterType
Filter string
Command string
NeedsPager bool
Header string
Regex *regexp.Regexp
}

var Filters []*FilterConfig
Expand All @@ -34,9 +35,16 @@ func parseFilters(file *ini.File) error {
}

for _, key := range filters.Keys() {
pager := true
cmd := key.Value()
if strings.HasPrefix(cmd, "!") {
cmd = strings.TrimLeft(cmd, "! \t")
pager = false
}
filter := FilterConfig{
Command: key.Value(),
Filter: key.Name(),
Command: cmd,
NeedsPager: pager,
Filter: key.Name(),
}

switch {
Expand Down
13 changes: 13 additions & 0 deletions doc/aerc-config.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,13 @@ filters need to be able to read from standard input. Many programs support
reading from stdin by putting _-_ instead of a path to a file. You can also
chain together multiple filters by piping with _|_.

Some filter commands may require interactive user input. If a filter command
starts with an exclamation mark _!_, the configured *pager* will *not* be used.
Instead, the filter command will be executed as the main process in the embedded
terminal of the part viewer. The filter command standard input, output and error
will be set to the terminal TTY. The filter is expected to implement its own
paging.

aerc ships with some default filters installed in the libexec directory (usually
_/usr/libexec/aerc/filters_). Note that these may have additional dependencies
that aerc does not have alone.
Expand Down Expand Up @@ -1100,6 +1107,12 @@ _text/html_
text/html=pandoc -f html -t plain
```

Use w3m internal pager to interactively view an HTML part with coloring:

```
text/html=! w3m -I UTF-8 -T text/html
```

_text/calendar_
Parse calendar invites:

Expand Down

0 comments on commit 5a119d7

Please sign in to comment.