Hey folks, this has been requested multiple times recently. I thought
I would tackle it since nobody stepped up :)
This is a follow up on https://todo.sr.ht/~rjarry/aerc/250. I changed my
mind about the way it is configured in the filters section.
The filter pattern was already pretty complex with all the headers and
regular expression stuff. Since the pager thing was more related to the
command itself, I chose to condition the paging based on an exclamation
mark prefix in the filter command itself. 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.
Robin Jarry (4):
viewer: limit indentation in writeMailHeaders
viewer: avoid potential race condition
viewer: set term size environment vars before filter command starts
viewer: allow filters to provide their own paging
app/msgviewer.go | 192 +++++++++++++++++++++++-------------------
config/aerc.conf | 1 +
config/filters.go | 22 +++--
doc/aerc-config.5.scd | 13 +++
4 files changed, 135 insertions(+), 93 deletions(-)
--
2.47.0
[PATCH aerc 1/4] viewer: limit indentation in writeMailHeaders
When the part viewer embedded terminal starts we take the opportunity to
initialize the COLUMNS and LINES environment variables of the filter
command before starting the terminal.
Unfortunately, the part viewer Draw() method will start the terminal
after fetching the body part which will (when complete) start the filter
command in attemptCopy(). There is a race which can lead to the env
variables being updated after the filter command has been started.
Set the environment variables before triggering the part fetch. This
will guarantee that they are present when the filter command starts.
Signed-off-by: Robin Jarry <robin@jarry.cc>
---
app/msgviewer.go | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/app/msgviewer.go b/app/msgviewer.go
index f4bc5801b0c2..2785d03cb7a4 100644
--- a/app/msgviewer.go+++ b/app/msgviewer.go
@@ -560,16 +560,6 @@ func NewPartViewer(
uiConfig: acct.UiConfig(),
}
- if term != nil {- term.OnStart = func() {- if term.ctx != nil {- filter.Env = append(filter.Env, fmt.Sprintf("COLUMNS=%d", term.ctx.Window().Width))- filter.Env = append(filter.Env, fmt.Sprintf("LINES=%d", term.ctx.Window().Height))- }- pv.attemptCopy()- }- }- return pv, nil
}
@@ -782,6 +772,10 @@ func (pv *PartViewer) Draw(ctx *ui.Context) {
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style)
pv.noFilter.Draw(ctx)
return
+ case !pv.fetched:+ w, h := ctx.Window().Size()+ pv.filter.Env = append(pv.filter.Env, fmt.Sprintf("COLUMNS=%d", w))+ pv.filter.Env = append(pv.filter.Env, fmt.Sprintf("LINES=%d", h)) }
if !pv.fetched {
pv.msg.FetchBodyPart(pv.index, pv.SetSource)
--
2.47.0
[PATCH aerc 4/4] viewer: allow filters to provide their own paging
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 <robin@jarry.cc>
---
app/msgviewer.go | 71 +++++++++++++++++++++++++++++--------------config/aerc.conf | 1 +config/filters.go | 22 +++++++++-----doc/aerc-config.5.scd | 13 ++++++++
4 files changed, 77 insertions(+), 30 deletions(-)
diff --git a/app/msgviewer.go b/app/msgviewer.go
index 2785d03cb7a4..22cc1f0ae06a 100644
--- a/app/msgviewer.go+++ b/app/msgviewer.go
@@ -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()
@@ -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 {
@@ -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
}
@@ -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 }
}()
}
@@ -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 {
diff --git a/config/aerc.conf b/config/aerc.conf
index 9070bcf3f4a8..3a6cf25159fc 100644
--- a/config/aerc.conf+++ b/config/aerc.conf
@@ -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) -
diff --git a/config/filters.go b/config/filters.go
index e843c5faa1d4..60372664e8fc 100644
--- a/config/filters.go+++ b/config/filters.go
@@ -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
@@ -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 {
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index 56c4e2099fa0..1d49f11a3a47 100644
--- a/doc/aerc-config.5.scd+++ b/doc/aerc-config.5.scd
@@ -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.
@@ -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:
--
2.47.0
Robin Jarry <robin@jarry.cc> wrote:
> Hey folks, this has been requested multiple times recently. I thought> I would tackle it since nobody stepped up :)>> This is a follow up on https://todo.sr.ht/~rjarry/aerc/250. I changed my> mind about the way it is configured in the filters section.>> The filter pattern was already pretty complex with all the headers and> regular expression stuff. Since the pager thing was more related to the> command itself, I chose to condition the paging based on an exclamation> mark prefix in the filter command itself. 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.>> Robin Jarry (4):> viewer: limit indentation in writeMailHeaders> viewer: avoid potential race condition> viewer: set term size environment vars before filter command starts> viewer: allow filters to provide their own paging
Acked-by: Robin Jarry <robin@jarry.cc>
Applied, thanks.
To git@git.sr.ht:~rjarry/aerc
c2048ef30452..5a119d7c6419 master -> master