ui: add :split and :vsplit view options
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>
This commit is contained in:
parent
7016c6f86a
commit
bad694e466
5 changed files with 252 additions and 0 deletions
|
@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
defined in their environment.
|
defined in their environment.
|
||||||
- Warn before sending emails that may need an attachment with
|
- Warn before sending emails that may need an attachment with
|
||||||
`no-attachment-warning` in `aerc.conf`.
|
`no-attachment-warning` in `aerc.conf`.
|
||||||
|
- 3 panel view via `:split` and `:vsplit`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
63
commands/account/split.go
Normal file
63
commands/account/split.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.sr.ht/~rjarry/aerc/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Split struct{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register(Split{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Split) Aliases() []string {
|
||||||
|
return []string{"split", "vsplit"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Split) Complete(aerc *widgets.Aerc, args []string) []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Split) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
|
if len(args) > 2 {
|
||||||
|
return errors.New("Usage: [v]split n")
|
||||||
|
}
|
||||||
|
acct := aerc.SelectedAccount()
|
||||||
|
if acct == nil {
|
||||||
|
return errors.New("No account selected")
|
||||||
|
}
|
||||||
|
n := 0
|
||||||
|
var err error
|
||||||
|
if len(args) > 1 {
|
||||||
|
delta := false
|
||||||
|
if strings.HasPrefix(args[1], "+") || strings.HasPrefix(args[1], "-") {
|
||||||
|
delta = true
|
||||||
|
}
|
||||||
|
n, err = strconv.Atoi(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Usage: [v]split n")
|
||||||
|
}
|
||||||
|
if delta {
|
||||||
|
n = acct.SplitSize() + n
|
||||||
|
// Maintain split direction when using deltas
|
||||||
|
if acct.SplitSize() > 0 {
|
||||||
|
args[0] = acct.SplitDirection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n == acct.SplitSize() {
|
||||||
|
// Repeated commands of the same size have the effect of
|
||||||
|
// toggling the split
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
|
if args[0] == "split" {
|
||||||
|
acct.Split(n)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
acct.Vsplit(n)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -355,6 +355,14 @@ message list, the message in the message viewer, etc).
|
||||||
Selects the nth message in the message list (and scrolls it into view if
|
Selects the nth message in the message list (and scrolls it into view if
|
||||||
necessary).
|
necessary).
|
||||||
|
|
||||||
|
*split* [+|-] [<n>]
|
||||||
|
Creates a horizontal split, showing a <n> messages and a message view
|
||||||
|
below the message list. If a + or - is prepended, the message list size
|
||||||
|
will grow or shrink accordingly. The split can be cleared by calling
|
||||||
|
:split 0, or just :split. The split can be toggled by calling split with
|
||||||
|
the same size repeatedly. For example, :split 10 will create a split.
|
||||||
|
Calling :split 10 again will remove the split. Also see *vsplit*
|
||||||
|
|
||||||
*sort* [[-r] <criterion>]...
|
*sort* [[-r] <criterion>]...
|
||||||
Sorts the message list by the given criteria. *-r* sorts the
|
Sorts the message list by the given criteria. *-r* sorts the
|
||||||
immediately following criterion in reverse order.
|
immediately following criterion in reverse order.
|
||||||
|
@ -389,6 +397,15 @@ message list, the message in the message viewer, etc).
|
||||||
flag -p is set, the message will not be marked as "seen" and ignores the
|
flag -p is set, the message will not be marked as "seen" and ignores the
|
||||||
"auto-mark-read" config.
|
"auto-mark-read" config.
|
||||||
|
|
||||||
|
*vsplit* [+|-] [<n>]
|
||||||
|
Creates a vertical split of the message list. The message list will be
|
||||||
|
<n> columns wide, and a vertical message view will be shown to the right
|
||||||
|
of the message list. If a + or - is prepended, the message list size
|
||||||
|
will grow or shrink accordingly. The split can be cleared by calling
|
||||||
|
:vsplit 0, or just :vsplit. The split can be toggled by calling split
|
||||||
|
with the same size repeatedly. For example, :vsplit 10 will create a
|
||||||
|
split. Calling :vsplit 10 again will remove the split. Also see *split*
|
||||||
|
|
||||||
## MESSAGE VIEW COMMANDS
|
## MESSAGE VIEW COMMANDS
|
||||||
|
|
||||||
*close*
|
*close*
|
||||||
|
|
|
@ -234,6 +234,24 @@ func (grid *Grid) RemoveChild(content Drawable) {
|
||||||
grid.Invalidate()
|
grid.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (grid *Grid) ReplaceChild(old Drawable, new Drawable) {
|
||||||
|
grid.mutex.Lock()
|
||||||
|
for i, cell := range grid.cells {
|
||||||
|
if cell.Content == old {
|
||||||
|
grid.cells[i] = &GridCell{
|
||||||
|
RowSpan: cell.RowSpan,
|
||||||
|
ColSpan: cell.ColSpan,
|
||||||
|
Row: cell.Row,
|
||||||
|
Column: cell.Column,
|
||||||
|
Content: new,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grid.mutex.Unlock()
|
||||||
|
grid.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
func Const(i int) func() int {
|
func Const(i int) func() int {
|
||||||
return func() int { return i }
|
return func() int { return i }
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,12 @@ type AccountView struct {
|
||||||
newConn bool // True if this is a first run after a new connection/reconnection
|
newConn bool // True if this is a first run after a new connection/reconnection
|
||||||
uiConf *config.UIConfig
|
uiConf *config.UIConfig
|
||||||
|
|
||||||
|
split *MessageViewer
|
||||||
|
splitSize int
|
||||||
|
splitDebounce *time.Timer
|
||||||
|
splitMsg *models.MessageInfo
|
||||||
|
splitDir string
|
||||||
|
|
||||||
// Check-mail ticker
|
// Check-mail ticker
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
checkingMail bool
|
checkingMail bool
|
||||||
|
@ -151,6 +157,9 @@ func (acct *AccountView) Draw(ctx *ui.Context) {
|
||||||
if acct.state.SetWidth(ctx.Width()) {
|
if acct.state.SetWidth(ctx.Width()) {
|
||||||
acct.UpdateStatus()
|
acct.UpdateStatus()
|
||||||
}
|
}
|
||||||
|
if acct.SplitSize() > 0 {
|
||||||
|
acct.UpdateSplitView()
|
||||||
|
}
|
||||||
acct.grid.Draw(ctx)
|
acct.grid.Draw(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,3 +475,147 @@ func (acct *AccountView) CheckMailTimer(d time.Duration) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (acct *AccountView) clearSplit() {
|
||||||
|
if acct.split != nil {
|
||||||
|
acct.split.Close()
|
||||||
|
}
|
||||||
|
acct.splitSize = 0
|
||||||
|
acct.splitDir = ""
|
||||||
|
acct.split = nil
|
||||||
|
acct.grid = ui.NewGrid().Rows([]ui.GridSpec{
|
||||||
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
||||||
|
}).Columns([]ui.GridSpec{
|
||||||
|
{Strategy: ui.SIZE_EXACT, Size: func() int {
|
||||||
|
return acct.UiConfig().SidebarWidth
|
||||||
|
}},
|
||||||
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
||||||
|
})
|
||||||
|
|
||||||
|
if acct.uiConf.SidebarWidth > 0 {
|
||||||
|
acct.grid.AddChild(ui.NewBordered(acct.dirlist, ui.BORDER_RIGHT, acct.uiConf))
|
||||||
|
}
|
||||||
|
acct.grid.AddChild(acct.msglist).At(0, 1)
|
||||||
|
ui.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acct *AccountView) UpdateSplitView() {
|
||||||
|
if acct.Store() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if acct.splitMsg == acct.msglist.Selected() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if acct.splitDebounce != nil {
|
||||||
|
acct.splitDebounce.Stop()
|
||||||
|
}
|
||||||
|
fn := func() {
|
||||||
|
if acct.split != nil {
|
||||||
|
acct.split.Close()
|
||||||
|
}
|
||||||
|
msg, err := acct.SelectedMessage()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lib.NewMessageStoreView(msg, false, acct.Store(), acct.aerc.Crypto, acct.aerc.DecryptKeys,
|
||||||
|
func(view lib.MessageView, err error) {
|
||||||
|
if err != nil {
|
||||||
|
acct.aerc.PushError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
orig := acct.split
|
||||||
|
acct.split = NewMessageViewer(acct, acct.conf, view)
|
||||||
|
acct.grid.ReplaceChild(orig, acct.split)
|
||||||
|
})
|
||||||
|
acct.splitMsg = msg
|
||||||
|
ui.Invalidate()
|
||||||
|
}
|
||||||
|
acct.splitDebounce = time.AfterFunc(100*time.Millisecond, func() {
|
||||||
|
ui.QueueFunc(fn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acct *AccountView) SplitSize() int {
|
||||||
|
return acct.splitSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acct *AccountView) SplitDirection() string {
|
||||||
|
return acct.splitDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split splits the message list view horizontally. The message list will be n
|
||||||
|
// rows high. If n is 0, any existing split is removed
|
||||||
|
func (acct *AccountView) Split(n int) {
|
||||||
|
if n == 0 {
|
||||||
|
acct.clearSplit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acct.splitSize = n
|
||||||
|
acct.splitDir = "split"
|
||||||
|
if acct.split != nil {
|
||||||
|
acct.split.Close()
|
||||||
|
}
|
||||||
|
acct.grid = ui.NewGrid().Rows([]ui.GridSpec{
|
||||||
|
// Add 1 so that the splitSize is the number of visible messages
|
||||||
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(acct.splitSize + 1)},
|
||||||
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
||||||
|
}).Columns([]ui.GridSpec{
|
||||||
|
{Strategy: ui.SIZE_EXACT, Size: func() int {
|
||||||
|
return acct.UiConfig().SidebarWidth
|
||||||
|
}},
|
||||||
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
||||||
|
})
|
||||||
|
|
||||||
|
if acct.uiConf.SidebarWidth > 0 {
|
||||||
|
acct.grid.AddChild(ui.NewBordered(acct.dirlist, ui.BORDER_RIGHT, acct.uiConf)).Span(2, 1)
|
||||||
|
}
|
||||||
|
acct.grid.AddChild(ui.NewBordered(acct.msglist, ui.BORDER_BOTTOM, acct.uiConf)).At(0, 1)
|
||||||
|
lib.NewMessageStoreView(acct.msglist.Selected(), false, acct.Store(), acct.aerc.Crypto, acct.aerc.DecryptKeys,
|
||||||
|
func(view lib.MessageView, err error) {
|
||||||
|
if err != nil {
|
||||||
|
acct.aerc.PushError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acct.split = NewMessageViewer(acct, acct.conf, view)
|
||||||
|
acct.grid.AddChild(acct.split).At(1, 1)
|
||||||
|
})
|
||||||
|
ui.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vsplit splits the message list view vertically. The message list will be n
|
||||||
|
// rows wide. If n is 0, any existing split is removed
|
||||||
|
func (acct *AccountView) Vsplit(n int) {
|
||||||
|
if n == 0 {
|
||||||
|
acct.clearSplit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acct.splitSize = n
|
||||||
|
acct.splitDir = "vsplit"
|
||||||
|
if acct.split != nil {
|
||||||
|
acct.split.Close()
|
||||||
|
}
|
||||||
|
acct.grid = ui.NewGrid().Rows([]ui.GridSpec{
|
||||||
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
||||||
|
}).Columns([]ui.GridSpec{
|
||||||
|
{Strategy: ui.SIZE_EXACT, Size: func() int {
|
||||||
|
return acct.UiConfig().SidebarWidth
|
||||||
|
}},
|
||||||
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(acct.splitSize)},
|
||||||
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
||||||
|
})
|
||||||
|
|
||||||
|
if acct.uiConf.SidebarWidth > 0 {
|
||||||
|
acct.grid.AddChild(ui.NewBordered(acct.dirlist, ui.BORDER_RIGHT, acct.uiConf)).At(0, 0)
|
||||||
|
}
|
||||||
|
acct.grid.AddChild(ui.NewBordered(acct.msglist, ui.BORDER_RIGHT, acct.uiConf)).At(0, 1)
|
||||||
|
lib.NewMessageStoreView(acct.msglist.Selected(), false, acct.Store(), acct.aerc.Crypto, acct.aerc.DecryptKeys,
|
||||||
|
func(view lib.MessageView, err error) {
|
||||||
|
if err != nil {
|
||||||
|
acct.aerc.PushError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acct.split = NewMessageViewer(acct, acct.conf, view)
|
||||||
|
acct.grid.AddChild(acct.split).At(0, 2)
|
||||||
|
})
|
||||||
|
ui.Invalidate()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue