Commit graph

73 commits

Author SHA1 Message Date
Jeffas
3156d481fe Add pinned tabs
This adds the commands pin-tab and unpin-tab. Once pinned a tab lives on
the left of the tabstrip and has a configurable marker, defaulting to `
before its name.
2020-03-09 09:31:02 -04:00
Drew DeVault
f3158b36f1 Initial support for PGP decryption & signatures 2020-03-03 16:49:52 -05:00
Jeffas
68f179021d Add move-tab command 2020-03-03 20:08:29 +01:00
Timmy Douglas
eca3863242 compose: don't call ti.tabcomplete when it is nil 2020-01-09 14:32:12 -05:00
Ben Burwell
07a9b9204e Don't select completions until tab has been pressed
Before, pressing <Enter> when completions were visible would execute the
selected completion. As soon as completions were provided, the first
item would be selected. This could cause issues e.g. when changing
folders:

  :cf <Enter>

Previously, this would have selected the first folder in the list. Now,
since <Tab>, <C-n>, etc have not been pressed to select the first
completion, the command above simply executes `:cf `.

To accomplish this, a "no-op completion" has been added at index -1.
2020-01-05 16:01:50 -05:00
Ben Burwell
7160f98a90 Show textinput completions in popovers
Rather than showing completions inline in the text input, show them in a
popover which can be scrolled by repeatedly pressing the tab key. The
selected completion can be executed by pressing enter.
2019-12-21 09:23:21 -05:00
Ben Burwell
bcd03c4c4a Add popovers
A popover is a special UI element which can be layered over the rest of
the UI (i.e. it is painted last) and can fall anywhere on the screen,
not just with the bounds of its parent's viewport/context. With these
special abilities comes the restriction that only one popover may be
visible on screen at once.

Popovers are requested from the UI context passed to Draw calls and
specify the anchor point and the desired dimensions. The popover is then
fit to the available space and placed relative to the anchor point.
2019-12-21 09:23:20 -05:00
Greg Anders
4bdef7d860 Add UI options to save/pipe messages with unsupported mimetypes
Adds a message indicating the user's ability to :save or :pipe a message
with an unsupported mimetype and also adds a selector widget (similar to
the tutorial).

The selector widget was previously defined in the account wizard module,
so this commit breaks it out into its own module to allow for re-use.

Further, modify the BeginExLine() function to take an argument that
pre-populates the command line, allowing functions to initiate an ex
command without executing it.

Closes #95.
2019-11-17 13:19:13 -05:00
Jeffas
03d182ca88 Fix tab refocus on remove
Previously removing a tab would always pop from the history of tabs.
This checks to see if the closing tab is the one selected, if it is then
we use the history, otherwise we only need to change the selected tab if
it was after (to the right of) the closing tab, in which case we just
decrement the selected index.
2019-10-14 10:38:04 -04:00
Jeffas
2542c65af2 Fix pushing invalid tabs to history
A tab can now only be pushed onto the history if it is a selectable tab.
2019-10-14 10:38:02 -04:00
Jeffas
0ce1d42bda Focus new tab after remove
After removing a tab we should focus the newly selected tab if it is
Interactive. This ensures things like the terminal get drawn properly.
2019-09-18 19:20:49 -04:00
Jeffas
f6216bb621 Add Mouseable
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.
2019-09-11 11:41:34 -04:00
Christopher Vittal
2b6ec504e5 Add delete forward <C-k> and backward <C-u>
Choose the readline defaults for the behavior of these two
functions/keybindings. Depending on the program, either of these can
delete the whole line.

Note that by default in [compose], <C-k> is bound to :prev-field<Enter>.
Leave it up to the user whether or not they want to rebind the key in
[compose].
2019-08-12 09:27:48 +09:00
Nicolai Dagestad
0847464da1 Remove aerc specific code from the ui
Separatiing the ui code from aerc makes it usable as a library in
other projects.
2019-08-03 16:50:49 -04:00
Ben Burwell
152f8c9519 Ring bell when new messages arrive
Add a "new-message-bell" option to the UI section of aerc.conf. A new
hook into the message store allows the msglist widget to detect new
messages being added to the displayed list. When new messages are
delivered, and the new-message-bell option is enabled (as it is by
default), the terminal will beep.
2019-07-29 15:01:49 -04:00
Jeffas
7a3765a36b Fix tabstrip over-drawing when not enough space
Tabstrip didn't take into account the width of the context. Now, it just
shows as many tabs as can fit and truncates the last one if necessary.

In future it probably would be best to ensure that the selected tab is
rendered on the screen.
2019-07-26 15:12:24 -04:00
Jeffas
cded067bc3 Add tab completion to textinputs
This adds tab completion to textinput components. They can be configured
with a completion function. This function is called when the user
presses <tab>. The first completion is initially shown to the user
inserted into the text. Repeated presses of <tab> or <backtab> cycle
through the completions list. The completions list is invalidated when
any other non-tab-like key is pressed.

Also changed is some logic for current completion generation so that
all available commands are returned when <tab> is pressed with no
current text and similarly for arguments of commands.
2019-07-26 14:39:42 -04:00
Drew DeVault
aabe3d9b3a Fix invalid tab state when deselecting removed tab 2019-07-26 14:37:45 -04:00
Jeffas
ee5b537d53 Fix :close on terminal panic
Executing :close on a terminal would panic due to it already having been
removed.

This is also related to the fact that removing a tab doesn't check for
whether it actually found a tab to remove or not.
2019-07-25 18:12:08 -04:00
Jeffas
1cf90897f7 Fix grid creating too large subcontexts
The grid was not checking there was enough space for the cells so would
just attempt to create subcontexts that don't actually fit.

This attempts to use the remaining space and then if there is no space
then it just skips drawing this cell.
2019-07-25 18:12:00 -04:00
Drew DeVault
2f626ddd18 Fix panic when tabs.popHistory is nonexistent 2019-07-25 08:29:10 -04:00
Jeffas
e42b95a617 Add change tab command
This command allows the user to change tab by giving the tab name. This
can be tab completed too. The previous tab is stored in the tabs module
so that when a new tab is created it is still possible to go to the
previous one.

Normal invocation is :ct folder
Previous tab is :ct -
2019-07-23 10:27:59 -04:00
Daniel Bridges
dfc048fe28 Display user specified headers in viewer if present 2019-07-17 17:26:43 -04:00
Jeffas
4991c344ab Fix grid widths when using weighted widths
If the column weights do not collectively divide the extent of the grid
layout then some width was not used and so would not be redrawn,
resulting in previous drawings showing through.

This fixes this by checking if there is any remainingExact width and if
there is it is assigned to the weighted columns by their proportion from
left to right.
2019-07-17 16:00:49 -04:00
Jeffas
06af5391a3 Add MouseEnabled config setting
This patch adds the ability to control whether aerc captures mouseevents
or not. By default it will be set to not capture events.
2019-07-17 16:00:44 -04:00
Tuomas Siipola
038bf711fa Fix text input cursor position with non-ASCII text
Fixes #171
2019-07-17 15:59:52 -04:00
Jeffas
3b09c07e7a Add clickable tabs
This introduces a new interface `Clickable`. I'd imagine this would be
implemented for most widgets eventually and would allow for programs run
in the terminal to also have their mouse events forwarded to them.

For the tabs it was relatively simple to check that the position of the
click is within the boxes for the tabs. For other components I'd imagine
that some state representing their currently drawn bounding box would be
useful.
2019-07-11 19:45:53 -04:00
Gregory Mullen
2a0961701c Implement basic tab completion support
Tab completion currently only works on commands. Contextual completion
will be added in the future.
2019-06-29 14:24:19 -04:00
Kevin Kuehler
a54f4adb8f lib/ui/tab: Add Replace method
Also expose a light wrapper method in aerc.go for tab replacement

Signed-off-by: Kevin Kuehler <kkuehler@brave.com>
2019-06-11 09:34:45 -04:00
Drew DeVault
207ecc39cd Fix #116 2019-06-03 07:56:08 -04:00
Reto Brunner
92b10fcef5 Add Tabs history
Fixes #77: When closing a tab, bring you back to the one you last had focused
2019-06-02 14:22:04 -04:00
Drew DeVault
1b11a96302 ensureScroll on text input frames 2019-05-25 15:54:01 -04:00
Drew DeVault
9947ea125c Implement scrolling in text input 2019-05-25 15:52:37 -04:00
Drew DeVault
9b19e3ad05 Show account wizard if no accounts configured 2019-05-22 11:35:55 -04:00
Drew DeVault
6811143925 New account wizard, part one 2019-05-21 16:53:50 -04:00
Simon Ser
7c6325977b lib/ui/ui: use atomic instead of channel
This makes it so an atomic `invalid` value is used instead of an unbuffered
channel. When many invalidations kick in, a lot of values were sent to the
channel.

(Since OnInvalidate's callback can be run in any goroutine, we need to be
careful about races here.)
2019-05-19 11:51:18 -04:00
Simon Ser
a15ea01cfb Update internal state and draw from the same goroutine
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.
2019-05-19 11:51:16 -04:00
Drew DeVault
98da4c9509 s/aerc2/aerc/g 2019-05-17 20:57:10 -04:00
Drew DeVault
2b3e123cb8 Let caller pass in custom headers to compose 2019-05-16 10:49:50 -04:00
Drew DeVault
2c486cb7f5 Update tab name as subject changes
Also moves truncation to the tab widget
2019-05-14 16:18:59 -04:00
Drew DeVault
bb46b2b7e1 Spec out review message screen 2019-05-13 16:53:02 -04:00
Drew DeVault
f37508a539 Implement :{next,prev}-field in compose view 2019-05-12 11:21:28 -04:00
Drew DeVault
577248f5e1 Add initial compose widget 2019-05-12 00:06:09 -04:00
Drew DeVault
72e4b5e2b2 Refactor ctx stashing out of exline 2019-05-11 13:20:29 -04:00
Drew DeVault
8fa4583230 Split ex line text handling into dedicated widget 2019-05-11 13:12:44 -04:00
Simon Ser
de122b16ee lib/ui: fix UI.Exit race condition
UI.Exit can be accessed from goroutines drawing, goroutines executing
commands and goroutines waiting for events.

    Write at 0x00c0002b2040 by main goroutine:
      main.main.func1()
          /home/simon/src/aerc2/aerc.go:76 +0x33d
      git.sr.ht/~sircmpwn/aerc2/widgets.(*Aerc).BeginExCommand.func1()
          /home/simon/src/aerc2/widgets/aerc.go:245 +0x89
      git.sr.ht/~sircmpwn/aerc2/widgets.(*ExLine).Event()
          /home/simon/src/aerc2/widgets/exline.go:131 +0x442
      git.sr.ht/~sircmpwn/aerc2/widgets.(*Aerc).Event()
          /home/simon/src/aerc2/widgets/aerc.go:116 +0x83c
      git.sr.ht/~sircmpwn/aerc2/widgets.(*Aerc).simulate()
          /home/simon/src/aerc2/widgets/aerc.go:109 +0x12a
      git.sr.ht/~sircmpwn/aerc2/widgets.(*Aerc).Event()
          /home/simon/src/aerc2/widgets/aerc.go:142 +0x722
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*UI).Tick()
          /home/simon/src/aerc2/lib/ui/ui.go:75 +0x33f
      main.main()
          /home/simon/src/aerc2/aerc.go:94 +0x497

    Previous read at 0x00c0002b2040 by goroutine 19:
      git.sr.ht/~sircmpwn/aerc2/lib/ui.Initialize.func1()
          /home/simon/src/aerc2/lib/ui/ui.go:45 +0x97

    Goroutine 19 (running) created at:
      git.sr.ht/~sircmpwn/aerc2/lib/ui.Initialize()
          /home/simon/src/aerc2/lib/ui/ui.go:44 +0x372
      main.main()
          /home/simon/src/aerc2/aerc.go:87 +0x3a9
2019-05-05 01:07:44 -04:00
Simon Ser
5feb7dede9 lib/ui: fix Grid race condition
This was is more complicated than others. The cells list is accessed by
multiple goroutines:

- Some change the Grid's contents via AddChild/RemoveChild
- Some call Draw
- Some invalidate the grid via Invalidate

Invalidate calls are tricky to handle because they will also invalidate all
child cells. This will inturn trigger the cellInvalidated callback, which needs
to read the list of cells.

For this reason, we use a sync.RWLock which allows multiple concurrent reads.

Below is the race fixed by this commit.

    Read at 0x00c0000bc3d0 by goroutine 7:
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Grid).cellInvalidated()
          /home/simon/src/aerc2/lib/ui/grid.go:181 +0x45
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Grid).cellInvalidated-fm()
          /home/simon/src/aerc2/lib/ui/grid.go:179 +0x55
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
          /home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Bordered).contentInvalidated-fm()
          /home/simon/src/aerc2/lib/ui/borders.go:39 +0x56
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
          /home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
      git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList.func1()
          /home/simon/src/aerc2/widgets/dirlist.go:81 +0x55
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
          /home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
      git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start.func1()
          /home/simon/src/aerc2/widgets/spinner.go:88 +0x82

    Previous write at 0x00c0000bc3d0 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 +0x98
      git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList()
          /home/simon/src/aerc2/widgets/dirlist.go:37 +0x28b
      git.sr.ht/~sircmpwn/aerc2/widgets.NewAccountView()
          /home/simon/src/aerc2/widgets/account.go:49 +0x5ca
      git.sr.ht/~sircmpwn/aerc2/widgets.NewAerc()
          /home/simon/src/aerc2/widgets/aerc.go:60 +0x807
      main.main()
          /home/simon/src/aerc2/aerc.go:65 +0x33e
2019-05-05 01:06:39 -04:00
Simon Ser
335db0402d lib/ui: fix GridCell.invalid race
This is read/written from different goroutines.

    Write at 0x00c00009c6f0 by goroutine 7:
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Grid).cellInvalidated()
          /home/simon/src/aerc2/lib/ui/grid.go:189 +0x122
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Grid).cellInvalidated-fm()
          /home/simon/src/aerc2/lib/ui/grid.go:178 +0x55
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
          /home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Bordered).contentInvalidated-fm()
          /home/simon/src/aerc2/lib/ui/borders.go:39 +0x56
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
          /home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
      git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList.func1()
          /home/simon/src/aerc2/widgets/dirlist.go:81 +0x55
      git.sr.ht/~sircmpwn/aerc2/lib/ui.(*Invalidatable).DoInvalidate()
          /home/simon/src/aerc2/lib/ui/invalidatable.go:22 +0x85
      git.sr.ht/~sircmpwn/aerc2/widgets.(*Spinner).Start.func1()
          /home/simon/src/aerc2/widgets/spinner.go:88 +0x82

    Previous write at 0x00c00009c6f0 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 +0x98
      git.sr.ht/~sircmpwn/aerc2/widgets.NewDirectoryList()
          /home/simon/src/aerc2/widgets/dirlist.go:37 +0x28b
      git.sr.ht/~sircmpwn/aerc2/widgets.NewAccountView()
          /home/simon/src/aerc2/widgets/account.go:49 +0x5ca
      git.sr.ht/~sircmpwn/aerc2/widgets.NewAerc()
          /home/simon/src/aerc2/widgets/aerc.go:60 +0x807
      main.main()
          /home/simon/src/aerc2/aerc.go:65 +0x33e
2019-04-29 09:29:37 -04:00
Simon Ser
5685a17674 lib/ui: introduce Invalidatable
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.
2019-04-27 14:30:28 -04:00
Drew DeVault
fa04a1e036 Add basic message viewer mockup 2019-03-30 14:12:04 -04:00