Introduce the ability to configure stylesets, allowing customization of
aerc's look (color scheme, font weight, etc). Default styleset is
installed to /path/to/aerc/stylesets/default.
This changes the scrolling to be done on the draw, when the height is
updated, ensuring that the selected item is kept on screen during
resizing.
Also, this ensures that messages will fill the screen when resizing the
window, for instance, shrinking and then growing drags down more
messages if possible.
This is a transplant of the dirlist scrolling logic.
The following functionalities are added to configure aerc ui styles.
- Read stylesets from file with very basic fnmatch wildcard matching
- Add default styleset
- Support different stylesets as part of UiConfig allowing contextual
styles.
- Move widgets/ui elements to use the stylesets.
- Add configuration manual for the styleset
This changes the ui to show the spinner while we are sorting. It only
shows one line of the spinner since there are an unknown number of
messages at this time.
The go compiler can't help much with untyped int constants.
Even though the only valid constants are 0-3 it will happily accept 4 as input.
Let's let the go compiler worry about correctness here. This also allows people
not very familiar with the code to use it properly via auto completion.
+ Adds parsing of contextual ui sections to aerc config.
+ Add GetUiConfig method for AercConfig that is used to get the
specialized UI config.
+ Add UiConfig method to AccountView to get specialized UI Config.
+ Modifies Aerc codebase to use specialized UIConfig instead.
+ Adds documentation for Contextual UI Configuration
Note that, until we get color configuration, this means that the user *must*
have the %Z verb in the index format else it'll be horribly confusing
as no visual indication is provided
This adds the Mouseable interface. When this is implemented for a
component that item can accept and process mouseevents.
At the top level when a mouse event is received it is passed to the
grid's handler and then it trickles down until it reaches a component
that can actually handle it, such as the tablist, dirlist or msglist.
A mouse event is passed so that components can handle other things such
as scrolling with the mousewheel. The components themselves then perform
the necessary actions.
Clicking emails in the messagelist opens them in a new tab.
Textinputs can be clicked to position the cursor inside them.
Mouseevents are not forwarded to the terminal at the moment.
Elements which do not handle mouse events are not required to implement
the Mouseable interface.
Allows to set `ui.spinner=` to a string which is then split by
`ui.spinner-delimiter=` (Default: comma) instead of having a hard coded
animation.
This implementation doesn't use INIs capabilities to split strings as
it trims whitespaces breaking the default animation.
Signed-off-by: Paul Spooren <mail@aparcar.org>
Sometimes I observe out-of-order messages when using a maildir inbox. It
appears that the UIDs for these messages are returned out of order by
the MessageStore. In order for a maildir MessageStore to return messages
in most recently received order, it must have already opened all
messages and parsed the date to use as a sort key. Rather than implement
that, simply sort messages by time as we display. This fix shows my
emails in order.
Make the msglist aware of whether we are still initializing or not.
We never stopped spinning the msglist if we didn't get any Directories back
from types.ListDirectories.
With this change, we can set the init state from the account and display
the spinner only if we don't know whether we have directories or not and else
the "no messages" string from the config.
This patch sets up the trigger config section of aerc.conf.
Each trigger has its own function which is called from the place where
it is triggered. Currently only the new-email trigger is implemented.
The triggers make use of format strings. For instance, in the new-email
trigger this allows the user to select the trigger command and also the
information extracted from the command and placed into their command.
To actually execute the trigger commands the keypresses are simulated.
Further triggers can be implemented in the future.
Formatting of the command is moved to a new package.
Before, we were using several IMAP-specific concepts to represent
information being displayed in the UI. Factor these structures out of
the IMAP package to make it easier for other backends to provide the
required information.
Before, the information needed to display different parts of the UI was
tightly coupled to the specific messages being sent back and forth to
the backend worker. Separating out a models package allows us to be more
specific about exactly what a backend is able to and required to
provide for the UI.
This commit introduces a new Aerc.Tick function that should be called to
refresh the internal state. This in turn makes each AccountView process worker
events.
The UI goroutine repeatedly refreshes the internal state before drawing a new
frame. The reason for this is that many worker messages may need to be
processed for a single frame, and drawing the UI is far slower than refreshing
the internal state. This has been confirmed in my testing (calling Aerc.Tick
only once per frame results in a slower display).
Many synchronization code has been removed. We can now write widgets without
having to care so much about races. The remaining sync users are:
- widgets/spinner: the spinner value is updated from inside an internal
goroutine
- lib/ui/invalidatable: Invalidate may be called from any goroutine
- lib/ui/grid: same
- lib/ui/ui: an internal goroutine needs read access to UI.exit
- worker/types/worker: Worker.callbacks is used for both worker and UI
callbacks
The exact goroutine requirements for Drawable have been documented.
MessageStore has a lot of exported fields that can be read from the outside.
Each read must be protected, because a call from Update could happen at any
time.
Many Drawable implementations have their own Invalidate and OnInvalidate
functions, with an unexported onInvalidate field. However OnInvalidate and
Invalidate are usually not called in the same goroutine. This results in a race
on this field, e.g.:
Read at 0x00c000094748 by goroutine 7:
git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList.func1()
/home/simon/src/aerc2/widgets/dirlist.go:85 +0x56
git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start.func1()
/home/simon/src/aerc2/widgets/spinner.go:93 +0x1bb
Previous write at 0x00c000094748 by main goroutine:
[failed to restore the stack]
Goroutine 7 (running) created at:
git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start()
/home/simon/src/aerc2/widgets/spinner.go:46 +0x8f
git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList()
/home/simon/src/aerc2/widgets/dirlist.go:37 +0x286
git.sr.ht/~sircmpwn/aerc2/widgets.NewAccountView()
/home/simon/src/aerc2/widgets/account.go:50 +0x5ca
git.sr.ht/~sircmpwn/aerc2/widgets.NewAerc()
/home/simon/src/aerc2/widgets/aerc.go:60 +0x800
main.main()
/home/simon/src/aerc2/aerc.go:65 +0x33e
To fix this, introduce a new type, Invalidatable, which protects the field.
Unfortunately the Drawable must be passed to the callback function in
Invalidate, so we still need to re-implement this in each Invalidatable user.