Commit Graph

120 Commits

Author SHA1 Message Date
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
Drew DeVault 84965d680c Use tcell.Style.Reverse instead of black on white 2019-03-30 12:59:18 -04:00
Drew DeVault dee0f8938b Add :term-close 2019-03-17 17:23:53 -04:00
Drew DeVault 589db742cb Move exline handling up to aerc, add :term 2019-03-17 16:19:15 -04:00
Drew DeVault 1170893e39 Add basic terminal widget 2019-03-17 14:02:33 -04:00
Drew DeVault f406bf5a3b Add :quit command 2019-03-15 20:32:09 -04:00
Drew DeVault a0c2b1caf0 Implement the Container interface in lib/ui/ 2019-01-20 15:06:44 -05:00
Drew DeVault 87fa305848 Merge lib/ui/interfaces.go and add another 2019-01-20 13:51:34 -05:00
Drew DeVault 0b37441f17 Make repeated invalidations more efficient 2019-01-14 21:11:33 -05:00
Drew DeVault d35213eaab Add cursor handling in ex line 2019-01-14 08:07:24 -05:00
Drew DeVault 257affcd48 Revert "Add abstract list, update dirlist accordingly"
This reverts commit 3157897c1a.
2019-01-13 15:27:56 -05:00
Drew DeVault 24196d2c6f Revert "Render selected list item differently"
This reverts commit 60284850f2.
2019-01-13 15:27:48 -05:00
Drew DeVault 60284850f2 Render selected list item differently 2019-01-13 15:12:52 -05:00
Drew DeVault 3157897c1a Add abstract list, update dirlist accordingly 2019-01-13 15:10:47 -05:00
Markus Ongyerth 2f5c1db63c refactor lib/ui/tab to ensure staying in bounds
Fix a few potential out of bounds by placing proper checks, which should
be relevant if all tabs are removed for some reason.

Also avoid iterating all tabs in the invalidate handler, since we are
only interested in whether it's the selected tab either way
2018-06-13 07:00:12 -04:00
Drew DeVault dc90be24de Fix tab invalidation bugs 2018-06-11 20:13:02 -04:00
Drew DeVault 6728a11fdf Apply gofmt 2018-06-11 20:04:21 -04:00
Drew DeVault d67c8a60ef Clean up some old code 2018-06-11 19:52:21 -04:00
Drew DeVault 1c41b63f08 Move sidebar into account tabs
This is accomplished through a bit of a hack, the statusbar is able to
be a child of multiple dudes
2018-06-11 19:23:09 -04:00
Drew DeVault e463c38476 Use default color for selected tab 2018-06-07 13:43:17 -04:00
Markus Ongyerth 80e891a802 switch to tcell from termbox
This is a simple mostly straight forward switch to tcell in favor of
termbox.
It uses the tcell native api (not the compat layer) but does not make
use of most features.

Further changes should include moving to tcell's views.TextArea and the
general built in widget behaviour instead of the current ad hoc
implementation.

Regression: Cursor isn't shown in ex-line
2018-06-01 16:04:43 -07:00
Drew DeVault cab3771e17 Pull main aerc UI into widget 2018-02-27 21:17:26 -05:00
Drew DeVault 46756487fb Add stack UI container 2018-02-27 19:31:09 -05:00
Drew DeVault 1418e1b9dc Split UI library and widgets 2018-02-26 22:54:39 -05:00