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.
This commit is contained in:
Jeffas 2020-03-07 16:42:41 +00:00 committed by Drew DeVault
parent 258a3f11ae
commit 3156d481fe
6 changed files with 119 additions and 6 deletions

36
commands/pin-tab.go Normal file
View file

@ -0,0 +1,36 @@
package commands
import (
"fmt"
"git.sr.ht/~sircmpwn/aerc/widgets"
)
type PinTab struct{}
func init() {
register(PinTab{})
}
func (PinTab) Aliases() []string {
return []string{"pin-tab", "unpin-tab"}
}
func (PinTab) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
func (PinTab) Execute(aerc *widgets.Aerc, args []string) error {
if len(args) != 1 {
return fmt.Errorf("Usage: %s", args[0])
}
switch args[0] {
case "pin-tab":
aerc.PinTab()
case "unpin-tab":
aerc.UnpinTab()
}
return nil
}

View file

@ -43,6 +43,11 @@ mouse-enabled=false
# Default: yes # Default: yes
new-message-bell=true new-message-bell=true
# Marker to show before a pinned tab's name.
#
# Default: `
pinned-tab-marker=`
# Describes the format string to use for the directory list # Describes the format string to use for the directory list
# #
# Default: %n %>r # Default: %n %>r

View file

@ -31,6 +31,7 @@ type UIConfig struct {
TimestampFormat string `ini:"timestamp-format"` TimestampFormat string `ini:"timestamp-format"`
ShowHeaders []string `delim:","` ShowHeaders []string `delim:","`
RenderAccountTabs string `ini:"render-account-tabs"` RenderAccountTabs string `ini:"render-account-tabs"`
PinnedTabMarker string `ini:"pinned-tab-marker"`
SidebarWidth int `ini:"sidebar-width"` SidebarWidth int `ini:"sidebar-width"`
PreviewHeight int `ini:"preview-height"` PreviewHeight int `ini:"preview-height"`
EmptyMessage string `ini:"empty-message"` EmptyMessage string `ini:"empty-message"`
@ -446,6 +447,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
"From", "To", "Cc", "Bcc", "Subject", "Date", "From", "To", "Cc", "Bcc", "Subject", "Date",
}, },
RenderAccountTabs: "auto", RenderAccountTabs: "auto",
PinnedTabMarker: "`",
SidebarWidth: 20, SidebarWidth: 20,
PreviewHeight: 12, PreviewHeight: 12,
EmptyMessage: "(no messages)", EmptyMessage: "(no messages)",

View file

@ -113,6 +113,11 @@ These options are configured in the *[ui]* section of aerc.conf.
Default: true Default: true
*pinned-tab-marker*
Marker to show before a pinned tab's name.
Default: `
*spinner* *spinner*
Animation shown while loading, split by spinner-delimiter (below) Animation shown while loading, split by spinner-delimiter (below)

View file

@ -3,6 +3,8 @@ package ui
import ( import (
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"git.sr.ht/~sircmpwn/aerc/config"
) )
type Tabs struct { type Tabs struct {
@ -12,6 +14,8 @@ type Tabs struct {
Selected int Selected int
history []int history []int
uiConfig *config.UIConfig
onInvalidateStrip func(d Drawable) onInvalidateStrip func(d Drawable)
onInvalidateContent func(d Drawable) onInvalidateContent func(d Drawable)
@ -23,13 +27,16 @@ type Tab struct {
Content Drawable Content Drawable
Name string Name string
invalid bool invalid bool
pinned bool
indexBeforePin int
} }
type TabStrip Tabs type TabStrip Tabs
type TabContent Tabs type TabContent Tabs
func NewTabs() *Tabs { func NewTabs(uiConf *config.UIConfig) *Tabs {
tabs := &Tabs{} tabs := &Tabs{}
tabs.uiConfig = uiConf
tabs.TabStrip = (*TabStrip)(tabs) tabs.TabStrip = (*TabStrip)(tabs)
tabs.TabStrip.parent = tabs tabs.TabStrip.parent = tabs
tabs.TabContent = (*TabContent)(tabs) tabs.TabContent = (*TabContent)(tabs)
@ -173,6 +180,52 @@ func (tabs *Tabs) MoveTab(to int) {
tabs.TabStrip.Invalidate() tabs.TabStrip.Invalidate()
} }
func (tabs *Tabs) PinTab() {
if tabs.Tabs[tabs.Selected].pinned {
return
}
pinEnd := len(tabs.Tabs)
for i, t := range tabs.Tabs {
if !t.pinned {
pinEnd = i
break
}
}
for _, t := range tabs.Tabs {
if t.pinned && t.indexBeforePin > tabs.Selected-pinEnd {
t.indexBeforePin -= 1
}
}
tabs.Tabs[tabs.Selected].pinned = true
tabs.Tabs[tabs.Selected].indexBeforePin = tabs.Selected - pinEnd
tabs.MoveTab(pinEnd)
}
func (tabs *Tabs) UnpinTab() {
if !tabs.Tabs[tabs.Selected].pinned {
return
}
pinEnd := len(tabs.Tabs)
for i, t := range tabs.Tabs {
if i != tabs.Selected && t.pinned && t.indexBeforePin > tabs.Tabs[tabs.Selected].indexBeforePin {
t.indexBeforePin += 1
}
if !t.pinned {
pinEnd = i
break
}
}
tabs.Tabs[tabs.Selected].pinned = false
tabs.MoveTab(tabs.Tabs[tabs.Selected].indexBeforePin + pinEnd - 1)
}
func (tabs *Tabs) NextTab() { func (tabs *Tabs) NextTab() {
next := tabs.Selected + 1 next := tabs.Selected + 1
if next >= len(tabs.Tabs) { if next >= len(tabs.Tabs) {
@ -233,7 +286,11 @@ func (strip *TabStrip) Draw(ctx *Context) {
if ctx.Width()-x < tabWidth { if ctx.Width()-x < tabWidth {
tabWidth = ctx.Width() - x - 2 tabWidth = ctx.Width() - x - 2
} }
trunc := runewidth.Truncate(tab.Name, tabWidth, "…") name := tab.Name
if tab.pinned {
name = strip.uiConfig.PinnedTabMarker + name
}
trunc := runewidth.Truncate(name, tabWidth, "…")
x += ctx.Printf(x, 0, style, " %s ", trunc) x += ctx.Printf(x, 0, style, " %s ", trunc)
if x >= ctx.Width() { if x >= ctx.Width() {
break break

View file

@ -43,7 +43,7 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
cmd func(cmd []string) error, complete func(cmd string) []string, cmd func(cmd []string) error, complete func(cmd string) []string,
cmdHistory lib.History) *Aerc { cmdHistory lib.History) *Aerc {
tabs := ui.NewTabs() tabs := ui.NewTabs(&conf.Ui)
statusbar := ui.NewStack() statusbar := ui.NewStack()
statusline := NewStatusLine() statusline := NewStatusLine()
@ -321,6 +321,14 @@ func (aerc *Aerc) MoveTab(i int) {
aerc.tabs.MoveTab(i) aerc.tabs.MoveTab(i)
} }
func (aerc *Aerc) PinTab() {
aerc.tabs.PinTab()
}
func (aerc *Aerc) UnpinTab() {
aerc.tabs.UnpinTab()
}
func (aerc *Aerc) NextTab() { func (aerc *Aerc) NextTab() {
aerc.tabs.NextTab() aerc.tabs.NextTab()
} }