diff --git a/commands/pin-tab.go b/commands/pin-tab.go new file mode 100644 index 0000000..164a68b --- /dev/null +++ b/commands/pin-tab.go @@ -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 +} diff --git a/config/aerc.conf.in b/config/aerc.conf.in index 39548cd..2f9f264 100644 --- a/config/aerc.conf.in +++ b/config/aerc.conf.in @@ -43,6 +43,11 @@ mouse-enabled=false # Default: yes 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 # # Default: %n %>r diff --git a/config/config.go b/config/config.go index 0393e46..5794388 100644 --- a/config/config.go +++ b/config/config.go @@ -31,6 +31,7 @@ type UIConfig struct { TimestampFormat string `ini:"timestamp-format"` ShowHeaders []string `delim:","` RenderAccountTabs string `ini:"render-account-tabs"` + PinnedTabMarker string `ini:"pinned-tab-marker"` SidebarWidth int `ini:"sidebar-width"` PreviewHeight int `ini:"preview-height"` EmptyMessage string `ini:"empty-message"` @@ -446,6 +447,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) { "From", "To", "Cc", "Bcc", "Subject", "Date", }, RenderAccountTabs: "auto", + PinnedTabMarker: "`", SidebarWidth: 20, PreviewHeight: 12, EmptyMessage: "(no messages)", diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd index 687ea3a..36ac9c6 100644 --- a/doc/aerc-config.5.scd +++ b/doc/aerc-config.5.scd @@ -113,6 +113,11 @@ These options are configured in the *[ui]* section of aerc.conf. Default: true +*pinned-tab-marker* + Marker to show before a pinned tab's name. + + Default: ` + *spinner* Animation shown while loading, split by spinner-delimiter (below) diff --git a/lib/ui/tab.go b/lib/ui/tab.go index e1c53ea..7d1ce63 100644 --- a/lib/ui/tab.go +++ b/lib/ui/tab.go @@ -3,6 +3,8 @@ package ui import ( "github.com/gdamore/tcell" "github.com/mattn/go-runewidth" + + "git.sr.ht/~sircmpwn/aerc/config" ) type Tabs struct { @@ -12,6 +14,8 @@ type Tabs struct { Selected int history []int + uiConfig *config.UIConfig + onInvalidateStrip func(d Drawable) onInvalidateContent func(d Drawable) @@ -20,16 +24,19 @@ type Tabs struct { } type Tab struct { - Content Drawable - Name string - invalid bool + Content Drawable + Name string + invalid bool + pinned bool + indexBeforePin int } type TabStrip Tabs type TabContent Tabs -func NewTabs() *Tabs { +func NewTabs(uiConf *config.UIConfig) *Tabs { tabs := &Tabs{} + tabs.uiConfig = uiConf tabs.TabStrip = (*TabStrip)(tabs) tabs.TabStrip.parent = tabs tabs.TabContent = (*TabContent)(tabs) @@ -173,6 +180,52 @@ func (tabs *Tabs) MoveTab(to int) { 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() { next := tabs.Selected + 1 if next >= len(tabs.Tabs) { @@ -233,7 +286,11 @@ func (strip *TabStrip) Draw(ctx *Context) { if ctx.Width()-x < tabWidth { 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) if x >= ctx.Width() { break diff --git a/widgets/aerc.go b/widgets/aerc.go index b7071e1..4c8d09d 100644 --- a/widgets/aerc.go +++ b/widgets/aerc.go @@ -43,7 +43,7 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger, cmd func(cmd []string) error, complete func(cmd string) []string, cmdHistory lib.History) *Aerc { - tabs := ui.NewTabs() + tabs := ui.NewTabs(&conf.Ui) statusbar := ui.NewStack() statusline := NewStatusLine() @@ -321,6 +321,14 @@ func (aerc *Aerc) MoveTab(i int) { aerc.tabs.MoveTab(i) } +func (aerc *Aerc) PinTab() { + aerc.tabs.PinTab() +} + +func (aerc *Aerc) UnpinTab() { + aerc.tabs.UnpinTab() +} + func (aerc *Aerc) NextTab() { aerc.tabs.NextTab() }