Reverse the order of the messages in the message list. The complexity of
reversing the order is abstracted away by the iterators. To reverse the
message list, add the following to your aerc.conf:
[ui]
reverse-msglist-order=true
Thanks to |cos| for sharing his initial implementation of reversing the
order in the message list [0].
[0]: https://git.netizen.se/aerc/commit/?h=topic/asc_sort_imap
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Implement an iterator framework for uid and thread data. Iterators
ensure that the underlying data is accessed in the right order and
provide an abstraction of the index handling.
Iterators should be used when the order of the uids/threads is
important.
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Avoid panic when part decoding fails:
panic: quotedprintable: invalid unescaped byte 0x0c in body
User-friendlier fallback when a (decoding) error occurs while reading a
message part.
Link: https://lists.sr.ht/~rjarry/aerc-discuss/%3CCNJRVKUG8T68.3TVA2T10DTTBA%40guix-framework%3E
Reported-by: "(" <paren@disroot.org>
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
The ThisDayTimeFormat and friends are missing from the message view
which just uses the message list's default setting. This might not be
desirable since the amount of space available is different. Introduce
separate settings for formatting dates in the message view.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Add :split and :vsplit commands, which split the message list view to
include a message viewer. Each command takes an int, or a delta value
("+1", "-1"). The int value is the resulting size of the message list,
and a new message viewer will be displayed below / to the right of the
message list. This viewer *does not* set seen flags.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Introduce AercFuncMsg and QueueFunc. These are used in combination to
queue a function to be run in the main goroutine. This can be used to
prevent data races in delayed function calls (ie from debounce
functions).
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Refactor the filtering and paging logic to use several fewer goroutines.
Fixes data race condition with the timing of filter -> pager ->
terminal, can be found when switching message views fast.
Check if filter -> pager process is currently running before calling it
to start again. Fixes data race between fetching message body and
terminal starting. Both can initiate the copying process, and for long
running filters and fast message fetching, it is possible to have
fetched a message but still be running the filter when the terminal
starts.
Move StripAnsi to it's own file in lib/parse, similar to the hyperlinks
parser.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Remove invalidatable type and all associated calls. All items can
directly invalidate the UI.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
The Invalidatable struct is designed so that a widget can have a
callback function ran when it is Invalidated. This is used to cascade up
the widget tree, marking things as Invalid along the way so that only
Invalid widgets are drawn. However, this is only implemented at the grid
cell level for checks if the cell is invalidated -- and the grid cells
are never set back to a "valid" state. The effect of this is that no
matter what is invalidated, the entire UI gets drawn again.
The calling through the Invalidate callbacks creates *several* race
conditions, as Invalidate is called from several different goroutines,
and many widgets call invalidate on their parent or children.
Tcell has optimizations to only rerender screen cells that have changed
their rune and style. The only performance penalty by redrawing the
entire screen for aerc is the operations *within the aerc draw methods*.
Most of these are not expensive and have relatively no impact on
performance.
Skip all of the OnInvalidates, and directly invalidate the UI when
DoInvalidate is called by a widget. This reduces data races, and
simplifies the widget redraw logic signficantly.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
The render method sets everything as invalid if there was a popover.
This is no longer necessary, as everything is redrawn anyways.
Remove the check and extra atomic set of dirty and invalidate.
Remove unused return value
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Combine tcell events with WorkerMessages to better synchronize state
with IO and UI. Remove Tick loop for rendering. Use events to trigger
renders.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Add AercMsg as a main interface for internal communication in aerc in
preparation for a main event loop. Add a QueueRedraw function to to
trigger a redraw. This will be needed for widgets which should be drawn
after some delay (completions, terminal, for example)
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Fix charset to UTF-8 in part attachments. The forward and recall
commands fetch message parts with the go-message package which decodes
to UTF-8. Hence, we should set the charset of the part attachment to
utf-8 and not just copying over the one from the original message.
Reported-by: Bence Ferdinandy <bence@ferdinandy.com>
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Add a peek flag -p to the view commands to open the message viewer
without setting the "seen" flag. If the flag is set, it would ignore the
"auto-mark-read" config.
The SetSeen flag will be propagated in case the message viewer moves on
to other messages, i.e. with the delete or archive commands.
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Add option to open a message in the message viewer without setting the
seen flag. Enables the message viewer to be used as a preview pane
without changing the message flags unintentionally. Before, the message
viewer would set the seen flag by default. The IMAP backend will now
always fetch the message body with the peek option enabled (same as we
fetch the headers).
An "auto-mark-read" option is added to the ui config which is set to
true by default. If set the false, the seen flag is not set by the
message viewer.
Co-authored-by: "James Cook" <falsifian@falsifian.org>
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Flag fetching is debounced in the UI, creating a race condition where
fields are accessed in the AfterFunc. Protect the needsFlags field with
a mutex.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Subsitute the format specifier %w for %v in the logging facility. The
logging functions use a fmt.Sprintf call behind the scene which does not
recognize %w. %w should be used in fmt.Errorf when you want to wrap
errors. Hence, the log entries that use %w are improperly formatted like
this:
ERROR 2022/10/02 09:13:57.724529 worker.go:439: could not get message
info %!w(*fmt.wrapError=&{could not get structure: [snip] })
^
Links: https://go.dev/blog/go1.13-errors
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Moritz Poldrack <moritz@poldrack.dev>
Add XOAUTH2 authentication support for IMAP and SMTP. Although XOAUTH2
is now deprecated in favor of OAuthBearer, it is the only way to connect
to Office365 since Basic Auth is now completely removed.
Since XOAUTH2 is very similar to OAuthBearer and uses the same
configuration parameters, this is basically a copy-paste of the existing
OAuthBearer code.
However, XOAUTH2 support was removed from go-sasl library, so this
change reimports the code that was removed from go-sasl and offers it
a new home in lib/xoauth2.go. Hopefully it shouldn't be too hard to
maintain, being less than 50 SLOC.
Link: https://github.com/emersion/go-sasl/commit/7bfe0ed36a21
Implements: https://todo.sr.ht/~rjarry/aerc/78
Signed-off-by: Julian Pidancet <julian.pidancet@oracle.com>
Tested-by: Inwit <inwit@sindominio.net>
Acked-by: Tim Culverhouse <tim@timculverhouse.com>
Instead of xdg-open (or open on MacOS), allow forcing a program to open
a message part. The program is determined in that order of priority:
1) If :open has arguments, they will be used as command to open the
attachment. If the arguments contain the {} placeholder, the
temporary file will be substituted, otherwise the file path is added
at the end of the arguments.
2) If a command is specified in the [openers] section of aerc.conf for
the part MIME type, then it is used with the same rules of {}
substitution.
3) Finally, fallback to xdg-open/open with the file path as argument.
Update the docs and default config accordingly with examples.
Fixes: https://todo.sr.ht/~rjarry/aerc/64
Co-authored-by: Jason Stewart <support@eggplantsd.com>
Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Moritz Poldrack <moritz@poldrack.dev>
There is no need for convoluted channels and other async fanciness.
Expose a single XDGOpen static function that runs a command and returns
an error if any.
Caller is responsible of running this in an async goroutine if needed.
Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Moritz Poldrack <moritz@poldrack.dev>
When using a tiling window manager, aerc terminal dimensions may be
greatly reduced after a new window has been created by :open. When the
ui attempts to render to formerly-valid coordinates, SetCell & Printf
may panic. Replace panic() with no-op in both functions to prevent
aerc from crashing after a window shrink.
Signed-off-by: Jason Stewart <support@eggplantsd.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Protect access to fields in textinput. Concurrent access can happen in
the main event loop and the completion debounce function.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Commit 9fdc7acf5b ("cache: fetch flags from UI") introduced a
regression where all messages were marked as erroneous if a single one
in the fetch request had an error.
Reported-by: Jose Lombera <jose@lombera.dev>
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Do not throw an error when the charset is unknown; the message entity
can still be read, but log the error instead.
Reported-by: falsifian
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
When cached headers are fetched, an action is posted back to the Worker
to immediately fetch the flags for the message from the server (we can't
know the flags state, therefore it's not cached). When scrolling, a lag
occurs when loading cached headers because the n+1 message has to wait
for the flag request to return before the cached headers are retrieved.
Collect the message UIDs in the UI that need flags, and fetch them based
off a debounce timer in a single request. Post the action from the UI to
eliminate an (ugly) go routine in the worker.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Many panics occur from calling Draw on a nil widget, stemming from the
grid ui element. Protect the calls to Draw from within grid to prevent
this method of panic.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
The grid method Children returns the children of a grid, and is never
used. The function is reimplemented in both aerc.go and account.go, also
never called.
Remove these unused methods.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
When errors occur during a fetch header request, the requested headers
are deleted from pending and no information is given to the UI. Spinners
keep spinning, and ultimately as the view is refreshed, the headers are
fetched again. This can lead to infinite loops, and extremely long logs.
Update the store with a MessageInfo message when an error is received.
Have the UI display that the header couldn't be fetched in the message
list.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Allow showing the current working directory in the statusline via
[statusline] render-format=%p, which is useful if the user changes
directories often.
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
Tested-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Allow forwarding paste events to embedded applications. When a bracketed
paste is in progress, do not process any command bindings.
Signed-off-by: Robin Jarry <robin@jarry.cc>
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Export context.viewport for use in implementing tcell-term.
Bump tcell version to enable SetCursorStyle feature. Add this function
to the ui for future use with tcell-term.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Now that tcell events are handled in a goroutine, no need for a channel
to buffer them.
Rename ui.Tick() to ui.Render() and ui.Run() to ui.ProcessEvents() to
better reflect what these functions do.
Move screen.PollEvent() into ui.ProcessEvents(). Register the panic
handler in ui.ProcessEvents().
Remove aerc.ui.Tick() from DecryptKeys(). What the hell was that?
Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Tim Culverhouse <tim@timculverhouse.com>
When using ephemeral aerc sessions – for example while writing patches
for it – the mailto: functionality breaks if the socket is removed.
Try to send a ping (an empty message) to the socket and remove it, if
the send fails or no noop reply is received.
Signed-off-by: Moritz Poldrack <git@moritz.sh>
Acked-by: Tim Culverhouse <tim@timculverhouse.com>
The UI runs off a 16 ms ticker. If no render is required, and no event
is seen, aerc waits 16 ms before checking for new events or render
requests. This severely limits handling of events from tcell, and is
particularly noticeable on pasting of large quantities of text.
Process tcell events in a separate go routine from the render loop.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Moritz Poldrack <moritz@poldrack.dev>
Acked-by: Robin Jarry <robin@jarry.cc>
URLs are extremely loosely defined and can take many shapes which may
not be parsed at all if unusual characters like the exclamation mark are
present. To ensure lists and odd use of spaces are not parsed as links
some sanity-checks are in place:
- the URL's schema must be at least two characters long
- the URL's authority, path, and fragment must have a combined
length of 8 characters or longer
- the URL must not contain a whitespace character, >, ), or "
- the URL may only contain a ] when followed by a different allowed
character or at the end of the line (necessary for IPv6
authorities)
The tests for this function now include links with an exclamation point
and IPv6 addresses. The tests are given names to be easier identifiable.
Link: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
Reported-by: "Bence Ferdinandy" <bence@ferdinandy.com>
Cc: "Koni Marti" <koni.marti@gmail.com>
Fixes: e1d8bc4d17 ("msgviewer: open http links from messages")
Signed-off-by: Moritz Poldrack <git@moritz.sh>
Acked-by: Tim Culverhouse <tim@timculverhouse.com>
When quoting an encrypted message for reply, the quoted text is shown as
"Version: 1.0". This is due to this being the first non-multipart text
portion of the message, which is what the quoted reply logic looks for.
Properly quote replies to encrypted messages by decrypting the message,
and quoting the content. The message must be open in a message view in
order to quote it (it must be decrypted, which is handled by the message
viewer).
Suggested-by: Moritz Poldrack <moritz@poldrack.dev>
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Tested-by: Jens Grassel <jens@wegtam.com>
Commit 380cf13cff ("msgstore: run threadBuilder with no debounce on
DirectoryContents msg") fixed an issue related to running the
threadbuilder with a debounce. This fix causes the thread builder to run
for any DirectoryContents message, even if the view is not threaded.
Don't call runThreadBuilderNow unless in a threaded view.
Fixes: 380cf13cff ("msgstore: run threadBuilder with no debounce on DirectoryContents msg")
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Enable the msgstore to specify if an update should also update the
threadbuilder. Certain calls to msgstore.update() did not require the
threadbuilder to run, as it was either called implicitly through other
means (nextPrev during a search) OR it was not needed (IE in the case of
a DirectoryInfo message).
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Commit 54a0a377e0 ("threads: debounce client-side thread building")
introduced the option to debounce threadbuilding to improve performance
of client side threads. This caused a UI regression during filtering of
mailboxes:
1. The mailbox contains the full set of message UIDs
2. User filters mailbox
3. A DirectoryContents message is received from worker with the proper
UIDs to display
4. Debounce is started (in a separate go routine)
5. The value of msgStore.Uids() is used to show pending headers, but is
not updated until the threadbuilder runs. The full set of UIDs is
shown briefly
6. Debounce is over, threadbuilder runs, updates msgStore.Uids(), proper
messages show.
Run the thread builder immediately upon receipt of a DirectoryContents
message, bypassing any debounce. Performance will remain the same: the
debounce is meant to prevent multiple sequential calls to the thread
builder during scrolling. This is unlikely to occur in rapid succession
from filtering or sorting.
Fixes: https://todo.sr.ht/~rjarry/aerc/76
Fixes: 54a0a377e0 ("threads: debounce client-side thread building")
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Enable the use of MoveMessages worker messages from the UI to the
backend. Completes implemention of MoveMessages for all supported
backends.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Improve cursor re-positioning while filtering with and without threads.
Reposition cursor in client-side threading mode with a callback that is
set during store.NextPrev(). Run callback when the threads are
constructed in order to reposition the cursor correctly. The callback is
deactivated when store.Select() is called.
Steps to reproduce two issues:
* Reproduce issue 1:
1. Activate client-side threading
2. Apply a filter, e.g. :filter -f Koni
3. Move cursor around so that a message is highlighted
4. clear filter with :clear
5. The cursor is expected to remain on the selected message but is
actually not
* Reproduce issue 2:
1. Activate client-side threading
2. Go the end of the message list
2. Apply a filter, e.g. :filter -f Koni
5. The cursor is now at the end of the filtered results instead of at
the beginning
This patch fixes both of those issues. Tested in regular and threaded
view according to the following check list (expected behavior in
parenthesis):
1. Apply filter from a message that remains in the filter (cursor on message,
message selected)
2. Apply filter from a message that will not remain (cursor at the top,
no message selected)
3. Clear filter (cursor remains on message, message selected)
4. Scroll line-by-line (threads: cursor remains on line, does not "jump"
with message)
5. Search (cursor on first result)
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Tested-by: Tim Culverhouse <tim@timculverhouse.com>
Mark or unmark the shown message threads. Threads must be available in the
message store. Use the -T option for the mark or unmark commands. Can be
used in combination with the toggle flag (-t).
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
When entering visual selection mode, the current selection is deleted.
This patch extends the visual mode behavior to select multiple blocks of
messages.
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Fix large archive operations that covers messages in the store with
unfetched headers. Commit e5ad877af5 ("msgstore: fetch missing headers
in visual mode") fixed this for the visual selection mode but omitted
the case when 'mark -a' is used to mark all messages.
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
The message store keeps a map of callbacks for headers that are being
fetched. This is not used anywhere in the code base. Instead of a
func(*types.MessageInfo) callback use a general
func(types.WorkerMessage) in the worker.PostAction function to make it
more useful. This callback allows now to get a feedback when all headers
are fetched successfully. Note that the pending header map remains so
that the same header is not fetched multiple times.
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Separate the marking functions from the message store and extract the
marking behavior into its own class with tests.
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Since the minimum required version of Go has been bumped to 1.16, the
deprecation of io/ioutil can now be acted upon. This Commit removes the
remaining dependencies on ioutil and replaces them with their io or os
counterparts.
Signed-off-by: Moritz Poldrack <git@moritz.sh>
Acked-by: Robin Jarry <robin@jarry.cc>
Delete operations are not supported by the notmuch backend. Revert
deleted messages when the operation is not supported, and reselect the
original selection.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Tested-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Fix the following test failures:
FAIL: TestMessageInfoHandledError (0.00s)
parse_test.go:53: could not parse envelope: date parsing failed:
unrecognized date format:
FAIL: TestReader (0.07s)
gpg_test.go:27: using GNUPGHOME = /tmp/TestReader2384941142/001
reader_test.go:108: Test case: Invalid Signature
reader_test.go:112: gpg.Read() = gpgmail: failed to read PGP
message: gpg: failed to run verification: exit status 1
Fixes: 5ca6022d00 ("lint: ensure errors are at least logged (errcheck)")
Fixes: 70bfcfef42 ("lint: work nicely with wrapped errors (errorlint)")
Signed-off-by: Robin Jarry <robin@jarry.cc>
Signed-off-by: Moritz Poldrack <moritz@poldrack.dev>