aerc

Working clone of aerc-mail.org
git clone git://git.danielmoch.com/aerc.git
Log | Files | Refs | README | LICENSE

aerc.go (6471B)


      1 package widgets
      2 
      3 import (
      4 	"log"
      5 	"time"
      6 
      7 	"github.com/gdamore/tcell"
      8 
      9 	"git.sr.ht/~sircmpwn/aerc/config"
     10 	"git.sr.ht/~sircmpwn/aerc/lib/ui"
     11 	libui "git.sr.ht/~sircmpwn/aerc/lib/ui"
     12 )
     13 
     14 type Aerc struct {
     15 	accounts    map[string]*AccountView
     16 	cmd         func(cmd string) error
     17 	conf        *config.AercConfig
     18 	focused     libui.Interactive
     19 	grid        *libui.Grid
     20 	logger      *log.Logger
     21 	simulating  int
     22 	statusbar   *libui.Stack
     23 	statusline  *StatusLine
     24 	pendingKeys []config.KeyStroke
     25 	tabs        *libui.Tabs
     26 }
     27 
     28 func NewAerc(conf *config.AercConfig, logger *log.Logger,
     29 	cmd func(cmd string) error) *Aerc {
     30 
     31 	tabs := libui.NewTabs()
     32 
     33 	statusbar := ui.NewStack()
     34 	statusline := NewStatusLine()
     35 	statusbar.Push(statusline)
     36 
     37 	grid := libui.NewGrid().Rows([]libui.GridSpec{
     38 		{libui.SIZE_EXACT, 1},
     39 		{libui.SIZE_WEIGHT, 1},
     40 		{libui.SIZE_EXACT, 1},
     41 	}).Columns([]libui.GridSpec{
     42 		{libui.SIZE_WEIGHT, 1},
     43 	})
     44 	grid.AddChild(tabs.TabStrip)
     45 	grid.AddChild(tabs.TabContent).At(1, 0)
     46 	grid.AddChild(statusbar).At(2, 0)
     47 
     48 	aerc := &Aerc{
     49 		accounts:   make(map[string]*AccountView),
     50 		conf:       conf,
     51 		cmd:        cmd,
     52 		grid:       grid,
     53 		logger:     logger,
     54 		statusbar:  statusbar,
     55 		statusline: statusline,
     56 		tabs:       tabs,
     57 	}
     58 
     59 	for i, acct := range conf.Accounts {
     60 		view := NewAccountView(conf, &conf.Accounts[i], logger, aerc)
     61 		aerc.accounts[acct.Name] = view
     62 		tabs.Add(view, acct.Name)
     63 	}
     64 
     65 	if len(conf.Accounts) == 0 {
     66 		wizard := NewAccountWizard(aerc.Config(), aerc)
     67 		wizard.Focus(true)
     68 		aerc.NewTab(wizard, "New account")
     69 	}
     70 
     71 	return aerc
     72 }
     73 
     74 func (aerc *Aerc) Tick() bool {
     75 	more := false
     76 	for _, acct := range aerc.accounts {
     77 		more = acct.Tick() || more
     78 	}
     79 	return more
     80 }
     81 
     82 func (aerc *Aerc) Children() []ui.Drawable {
     83 	return aerc.grid.Children()
     84 }
     85 
     86 func (aerc *Aerc) OnInvalidate(onInvalidate func(d libui.Drawable)) {
     87 	aerc.grid.OnInvalidate(func(_ libui.Drawable) {
     88 		onInvalidate(aerc)
     89 	})
     90 }
     91 
     92 func (aerc *Aerc) Invalidate() {
     93 	aerc.grid.Invalidate()
     94 }
     95 
     96 func (aerc *Aerc) Focus(focus bool) {
     97 	// who cares
     98 }
     99 
    100 func (aerc *Aerc) Draw(ctx *libui.Context) {
    101 	aerc.grid.Draw(ctx)
    102 }
    103 
    104 func (aerc *Aerc) getBindings() *config.KeyBindings {
    105 	switch view := aerc.SelectedTab().(type) {
    106 	case *AccountView:
    107 		return aerc.conf.Bindings.MessageList
    108 	case *AccountWizard:
    109 		return aerc.conf.Bindings.AccountWizard
    110 	case *Composer:
    111 		switch view.Bindings() {
    112 		case "compose::editor":
    113 			return aerc.conf.Bindings.ComposeEditor
    114 		case "compose::review":
    115 			return aerc.conf.Bindings.ComposeReview
    116 		default:
    117 			return aerc.conf.Bindings.Compose
    118 		}
    119 	case *MessageViewer:
    120 		return aerc.conf.Bindings.MessageView
    121 	case *Terminal:
    122 		return aerc.conf.Bindings.Terminal
    123 	default:
    124 		return aerc.conf.Bindings.Global
    125 	}
    126 }
    127 
    128 func (aerc *Aerc) simulate(strokes []config.KeyStroke) {
    129 	aerc.pendingKeys = []config.KeyStroke{}
    130 	aerc.simulating += 1
    131 	for _, stroke := range strokes {
    132 		simulated := tcell.NewEventKey(
    133 			stroke.Key, stroke.Rune, tcell.ModNone)
    134 		aerc.Event(simulated)
    135 	}
    136 	aerc.simulating -= 1
    137 }
    138 
    139 func (aerc *Aerc) Event(event tcell.Event) bool {
    140 	if aerc.focused != nil {
    141 		return aerc.focused.Event(event)
    142 	}
    143 
    144 	switch event := event.(type) {
    145 	case *tcell.EventKey:
    146 		aerc.statusline.Expire()
    147 		aerc.pendingKeys = append(aerc.pendingKeys, config.KeyStroke{
    148 			Key:  event.Key(),
    149 			Rune: event.Rune(),
    150 		})
    151 		bindings := aerc.getBindings()
    152 		incomplete := false
    153 		result, strokes := bindings.GetBinding(aerc.pendingKeys)
    154 		switch result {
    155 		case config.BINDING_FOUND:
    156 			aerc.simulate(strokes)
    157 			return true
    158 		case config.BINDING_INCOMPLETE:
    159 			incomplete = true
    160 		case config.BINDING_NOT_FOUND:
    161 		}
    162 		if bindings.Globals {
    163 			result, strokes = aerc.conf.Bindings.Global.
    164 				GetBinding(aerc.pendingKeys)
    165 			switch result {
    166 			case config.BINDING_FOUND:
    167 				aerc.simulate(strokes)
    168 				return true
    169 			case config.BINDING_INCOMPLETE:
    170 				incomplete = true
    171 			case config.BINDING_NOT_FOUND:
    172 			}
    173 		}
    174 		if !incomplete {
    175 			aerc.pendingKeys = []config.KeyStroke{}
    176 			exKey := bindings.ExKey
    177 			if aerc.simulating > 0 {
    178 				// Keybindings still use : even if you change the ex key
    179 				exKey = aerc.conf.Bindings.Global.ExKey
    180 			}
    181 			if event.Key() == exKey.Key && event.Rune() == exKey.Rune {
    182 				aerc.BeginExCommand()
    183 				return true
    184 			}
    185 			interactive, ok := aerc.tabs.Tabs[aerc.tabs.Selected].Content.(ui.Interactive)
    186 			if ok {
    187 				return interactive.Event(event)
    188 			}
    189 			return false
    190 		}
    191 	}
    192 	return false
    193 }
    194 
    195 func (aerc *Aerc) Config() *config.AercConfig {
    196 	return aerc.conf
    197 }
    198 
    199 func (aerc *Aerc) SelectedAccount() *AccountView {
    200 	acct, ok := aerc.accounts[aerc.tabs.Tabs[aerc.tabs.Selected].Name]
    201 	if !ok {
    202 		return nil
    203 	}
    204 	return acct
    205 }
    206 
    207 func (aerc *Aerc) SelectedTab() ui.Drawable {
    208 	return aerc.tabs.Tabs[aerc.tabs.Selected].Content
    209 }
    210 
    211 func (aerc *Aerc) NewTab(drawable ui.Drawable, name string) *ui.Tab {
    212 	tab := aerc.tabs.Add(drawable, name)
    213 	aerc.tabs.Select(len(aerc.tabs.Tabs) - 1)
    214 	return tab
    215 }
    216 
    217 func (aerc *Aerc) RemoveTab(tab ui.Drawable) {
    218 	aerc.tabs.Remove(tab)
    219 }
    220 
    221 func (aerc *Aerc) NextTab() {
    222 	next := aerc.tabs.Selected + 1
    223 	if next >= len(aerc.tabs.Tabs) {
    224 		next = 0
    225 	}
    226 	aerc.tabs.Select(next)
    227 }
    228 
    229 func (aerc *Aerc) PrevTab() {
    230 	next := aerc.tabs.Selected - 1
    231 	if next < 0 {
    232 		next = len(aerc.tabs.Tabs) - 1
    233 	}
    234 	aerc.tabs.Select(next)
    235 }
    236 
    237 // TODO: Use per-account status lines, but a global ex line
    238 func (aerc *Aerc) SetStatus(status string) *StatusMessage {
    239 	return aerc.statusline.Set(status)
    240 }
    241 
    242 func (aerc *Aerc) PushStatus(text string, expiry time.Duration) *StatusMessage {
    243 	return aerc.statusline.Push(text, expiry)
    244 }
    245 
    246 func (aerc *Aerc) PushError(text string) {
    247 	aerc.PushStatus(text, 10*time.Second).Color(tcell.ColorDefault, tcell.ColorRed)
    248 }
    249 
    250 func (aerc *Aerc) focus(item libui.Interactive) {
    251 	if aerc.focused == item {
    252 		return
    253 	}
    254 	if aerc.focused != nil {
    255 		aerc.focused.Focus(false)
    256 	}
    257 	aerc.focused = item
    258 	interactive, ok := aerc.tabs.Tabs[aerc.tabs.Selected].Content.(ui.Interactive)
    259 	if item != nil {
    260 		item.Focus(true)
    261 		if ok {
    262 			interactive.Focus(false)
    263 		}
    264 	} else {
    265 		if ok {
    266 			interactive.Focus(true)
    267 		}
    268 	}
    269 }
    270 
    271 func (aerc *Aerc) BeginExCommand() {
    272 	previous := aerc.focused
    273 	exline := NewExLine(func(cmd string) {
    274 		err := aerc.cmd(cmd)
    275 		if err != nil {
    276 			aerc.PushStatus(" "+err.Error(), 10*time.Second).
    277 				Color(tcell.ColorDefault, tcell.ColorRed)
    278 		}
    279 		aerc.statusbar.Pop()
    280 		aerc.focus(previous)
    281 	}, func() {
    282 		aerc.statusbar.Pop()
    283 		aerc.focus(previous)
    284 	})
    285 	aerc.statusbar.Push(exline)
    286 	aerc.focus(exline)
    287 }