aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/ui/textinput.go3
-rw-r--r--widgets/account-wizard.go110
-rw-r--r--widgets/aerc.go6
-rw-r--r--widgets/exline.go4
-rw-r--r--widgets/msgviewer.go58
-rw-r--r--widgets/selecter.go103
-rw-r--r--widgets/tabhost.go2
7 files changed, 163 insertions, 123 deletions
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index 39351735..e81e8361 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -63,9 +63,10 @@ func (ti *TextInput) StringRight() string {
return string(ti.text[ti.index:])
}
-func (ti *TextInput) Set(value string) {
+func (ti *TextInput) Set(value string) *TextInput {
ti.text = []rune(value)
ti.index = len(ti.text)
+ return ti
}
func (ti *TextInput) Invalidate() {
diff --git a/widgets/account-wizard.go b/widgets/account-wizard.go
index 904013f9..d7b46b9e 100644
--- a/widgets/account-wizard.go
+++ b/widgets/account-wizard.go
@@ -177,7 +177,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
At(7, 0)
basics.AddChild(wizard.email).
At(8, 0)
- selecter := newSelecter([]string{"Next"}, 0).
+ selecter := NewSelecter([]string{"Next"}, 0).
OnChoose(func(option string) {
email := wizard.email.String()
if strings.ContainsRune(email, '@') {
@@ -254,7 +254,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
incoming.AddChild(
ui.NewText("Connection mode").Bold(true)).
At(10, 0)
- imapMode := newSelecter([]string{
+ imapMode := NewSelecter([]string{
"IMAP over SSL/TLS",
"IMAP with STARTTLS",
"Insecure IMAP",
@@ -270,7 +270,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
wizard.imapUri()
})
incoming.AddChild(imapMode).At(11, 0)
- selecter = newSelecter([]string{"Previous", "Next"}, 1).
+ selecter = NewSelecter([]string{"Previous", "Next"}, 1).
OnChoose(wizard.advance)
incoming.AddChild(ui.NewFill(' ')).At(12, 0)
incoming.AddChild(wizard.imapStr).At(13, 0)
@@ -331,7 +331,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
outgoing.AddChild(
ui.NewText("Connection mode").Bold(true)).
At(10, 0)
- smtpMode := newSelecter([]string{
+ smtpMode := NewSelecter([]string{
"SMTP over SSL/TLS",
"SMTP with STARTTLS",
"Insecure SMTP",
@@ -347,7 +347,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
wizard.smtpUri()
})
outgoing.AddChild(smtpMode).At(11, 0)
- selecter = newSelecter([]string{"Previous", "Next"}, 1).
+ selecter = NewSelecter([]string{"Previous", "Next"}, 1).
OnChoose(wizard.advance)
outgoing.AddChild(ui.NewFill(' ')).At(12, 0)
outgoing.AddChild(wizard.smtpStr).At(13, 0)
@@ -355,7 +355,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
outgoing.AddChild(
ui.NewText("Copy sent messages to 'Sent' folder?").Bold(true)).
At(15, 0)
- copySent := newSelecter([]string{"Yes", "No"}, 0).
+ copySent := NewSelecter([]string{"Yes", "No"}, 0).
Chooser(true).OnChoose(func(option string) {
switch option {
case "Yes":
@@ -385,7 +385,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
"You can go back and double check your settings, or choose 'Finish' to\n" +
"save your settings to accounts.conf.\n\n" +
"To add another account in the future, run ':new-account'."))
- selecter = newSelecter([]string{
+ selecter = NewSelecter([]string{
"Previous",
"Finish & open tutorial",
"Finish",
@@ -716,102 +716,6 @@ func (wizard *AccountWizard) Event(event tcell.Event) bool {
return false
}
-type selecter struct {
- ui.Invalidatable
- chooser bool
- focused bool
- focus int
- options []string
-
- onChoose func(option string)
- onSelect func(option string)
-}
-
-func newSelecter(options []string, focus int) *selecter {
- return &selecter{
- focus: focus,
- options: options,
- }
-}
-
-func (sel *selecter) Chooser(chooser bool) *selecter {
- sel.chooser = chooser
- return sel
-}
-
-func (sel *selecter) Invalidate() {
- sel.DoInvalidate(sel)
-}
-
-func (sel *selecter) Draw(ctx *ui.Context) {
- ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
- x := 2
- for i, option := range sel.options {
- style := tcell.StyleDefault
- if sel.focus == i {
- if sel.focused {
- style = style.Reverse(true)
- } else if sel.chooser {
- style = style.Bold(true)
- }
- }
- x += ctx.Printf(x, 1, style, "[%s]", option)
- x += 5
- }
-}
-
-func (sel *selecter) OnChoose(fn func(option string)) *selecter {
- sel.onChoose = fn
- return sel
-}
-
-func (sel *selecter) OnSelect(fn func(option string)) *selecter {
- sel.onSelect = fn
- return sel
-}
-
-func (sel *selecter) Selected() string {
- return sel.options[sel.focus]
-}
-
-func (sel *selecter) Focus(focus bool) {
- sel.focused = focus
- sel.Invalidate()
-}
-
-func (sel *selecter) Event(event tcell.Event) bool {
- switch event := event.(type) {
- case *tcell.EventKey:
- switch event.Key() {
- case tcell.KeyCtrlH:
- fallthrough
- case tcell.KeyLeft:
- if sel.focus > 0 {
- sel.focus--
- sel.Invalidate()
- }
- if sel.onSelect != nil {
- sel.onSelect(sel.Selected())
- }
- case tcell.KeyCtrlL:
- fallthrough
- case tcell.KeyRight:
- if sel.focus < len(sel.options)-1 {
- sel.focus++
- sel.Invalidate()
- }
- if sel.onSelect != nil {
- sel.onSelect(sel.Selected())
- }
- case tcell.KeyEnter:
- if sel.onChoose != nil {
- sel.onChoose(sel.Selected())
- }
- }
- }
- return false
-}
-
func getSRV(host string, services []string) (string, string) {
var hostport, srv string
for _, srv = range services {
diff --git a/widgets/aerc.go b/widgets/aerc.go
index d324908b..9d955e11 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -240,7 +240,7 @@ func (aerc *Aerc) Event(event tcell.Event) bool {
exKey = aerc.conf.Bindings.Global.ExKey
}
if event.Key() == exKey.Key && event.Rune() == exKey.Rune {
- aerc.BeginExCommand()
+ aerc.BeginExCommand("")
return true
}
interactive, ok := aerc.tabs.Tabs[aerc.tabs.Selected].Content.(ui.Interactive)
@@ -370,9 +370,9 @@ func (aerc *Aerc) focus(item ui.Interactive) {
}
}
-func (aerc *Aerc) BeginExCommand() {
+func (aerc *Aerc) BeginExCommand(cmd string) {
previous := aerc.focused
- exline := NewExLine(func(cmd string) {
+ exline := NewExLine(cmd, func(cmd string) {
parts, err := shlex.Split(cmd)
if err != nil {
aerc.PushStatus(" "+err.Error(), 10*time.Second).
diff --git a/widgets/exline.go b/widgets/exline.go
index 1482f0e0..f2c7249f 100644
--- a/widgets/exline.go
+++ b/widgets/exline.go
@@ -16,11 +16,11 @@ type ExLine struct {
input *ui.TextInput
}
-func NewExLine(commit func(cmd string), finish func(),
+func NewExLine(cmd string, commit func(cmd string), finish func(),
tabcomplete func(cmd string) []string,
cmdHistory lib.History) *ExLine {
- input := ui.NewTextInput("").Prompt(":").TabComplete(tabcomplete)
+ input := ui.NewTextInput("").Prompt(":").TabComplete(tabcomplete).Set(cmd)
exline := &ExLine{
commit: commit,
finish: finish,
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
index 4d41923d..aca7dd44 100644
--- a/widgets/msgviewer.go
+++ b/widgets/msgviewer.go
@@ -68,7 +68,7 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
})
switcher := &PartSwitcher{}
- err := createSwitcher(switcher, conf, store, msg)
+ err := createSwitcher(acct, switcher, conf, store, msg)
if err != nil {
return &MessageViewer{
err: err,
@@ -112,7 +112,7 @@ func fmtHeader(msg *models.MessageInfo, header string) string {
}
}
-func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
+func enumerateParts(acct *AccountView, conf *config.AercConfig, store *lib.MessageStore,
msg *models.MessageInfo, body *models.BodyStructure,
index []int) ([]*PartViewer, error) {
@@ -124,14 +124,14 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
pv := &PartViewer{part: part}
parts = append(parts, pv)
subParts, err := enumerateParts(
- conf, store, msg, part, curindex)
+ acct, conf, store, msg, part, curindex)
if err != nil {
return nil, err
}
parts = append(parts, subParts...)
continue
}
- pv, err := NewPartViewer(conf, store, msg, part, curindex)
+ pv, err := NewPartViewer(acct, conf, store, msg, part, curindex)
if err != nil {
return nil, err
}
@@ -140,7 +140,7 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
return parts, nil
}
-func createSwitcher(switcher *PartSwitcher, conf *config.AercConfig,
+func createSwitcher(acct *AccountView, switcher *PartSwitcher, conf *config.AercConfig,
store *lib.MessageStore, msg *models.MessageInfo) error {
var err error
@@ -150,7 +150,7 @@ func createSwitcher(switcher *PartSwitcher, conf *config.AercConfig,
if len(msg.BodyStructure.Parts) == 0 {
switcher.selected = 0
- pv, err := NewPartViewer(conf, store, msg, msg.BodyStructure, []int{1})
+ pv, err := NewPartViewer(acct, conf, store, msg, msg.BodyStructure, []int{1})
if err != nil {
return err
}
@@ -159,7 +159,7 @@ func createSwitcher(switcher *PartSwitcher, conf *config.AercConfig,
switcher.Invalidate()
})
} else {
- switcher.parts, err = enumerateParts(conf, store,
+ switcher.parts, err = enumerateParts(acct, conf, store,
msg, msg.BodyStructure, []int{})
if err != nil {
return err
@@ -236,7 +236,7 @@ func (mv *MessageViewer) ToggleHeaders() {
switcher := mv.switcher
mv.conf.Viewer.ShowHeaders = !mv.conf.Viewer.ShowHeaders
err := createSwitcher(
- switcher, mv.conf, mv.store, mv.msg)
+ mv.acct, switcher, mv.conf, mv.store, mv.msg)
if err != nil {
mv.acct.Logger().Printf(
"warning: error during create switcher - %v", err)
@@ -299,10 +299,7 @@ func (ps *PartSwitcher) Focus(focus bool) {
}
func (ps *PartSwitcher) Event(event tcell.Event) bool {
- if ps.parts[ps.selected].term != nil {
- return ps.parts[ps.selected].term.Event(event)
- }
- return false
+ return ps.parts[ps.selected].Event(event)
}
func (ps *PartSwitcher) Draw(ctx *ui.Context) {
@@ -414,9 +411,11 @@ type PartViewer struct {
source io.Reader
store *lib.MessageStore
term *Terminal
+ selecter *Selecter
+ grid *ui.Grid
}
-func NewPartViewer(conf *config.AercConfig,
+func NewPartViewer(acct *AccountView, conf *config.AercConfig,
store *lib.MessageStore, msg *models.MessageInfo,
part *models.BodyStructure,
index []int) (*PartViewer, error) {
@@ -475,6 +474,26 @@ func NewPartViewer(conf *config.AercConfig,
}
}
+ grid := ui.NewGrid().Rows([]ui.GridSpec{
+ {ui.SIZE_EXACT, 3}, // Message
+ {ui.SIZE_EXACT, 1}, // Selector
+ {ui.SIZE_WEIGHT, 1},
+ }).Columns([]ui.GridSpec{
+ {ui.SIZE_WEIGHT, 1},
+ })
+
+ selecter := NewSelecter([]string{"Save message", "Pipe to command"}, 0).
+ OnChoose(func(option string) {
+ switch option {
+ case "Save message":
+ acct.aerc.BeginExCommand("save ")
+ case "Pipe to command":
+ acct.aerc.BeginExCommand("pipe ")
+ }
+ })
+
+ grid.AddChild(selecter).At(2, 0)
+
pv := &PartViewer{
filter: filter,
index: index,
@@ -486,6 +505,8 @@ func NewPartViewer(conf *config.AercConfig,
sink: pipe,
store: store,
term: term,
+ selecter: selecter,
+ grid: grid,
}
if term != nil {
@@ -590,6 +611,10 @@ func (pv *PartViewer) Draw(ctx *ui.Context) {
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
ctx.Printf(0, 0, tcell.StyleDefault.Foreground(tcell.ColorRed),
"No filter configured for this mimetype")
+ ctx.Printf(0, 2, tcell.StyleDefault,
+ "You can still :save the message or :pipe it to an external command")
+ pv.selecter.Focus(true)
+ pv.grid.Draw(ctx)
return
}
if !pv.fetched {
@@ -611,6 +636,13 @@ func (pv *PartViewer) Cleanup() {
}
}
+func (pv *PartViewer) Event(event tcell.Event) bool {
+ if pv.term != nil {
+ return pv.term.Event(event)
+ }
+ return pv.selecter.Event(event)
+}
+
type HeaderView struct {
ui.Invalidatable
Name string
diff --git a/widgets/selecter.go b/widgets/selecter.go
new file mode 100644
index 00000000..7fae9cda
--- /dev/null
+++ b/widgets/selecter.go
@@ -0,0 +1,103 @@
+package widgets
+
+import (
+ "github.com/gdamore/tcell"
+
+ "git.sr.ht/~sircmpwn/aerc/lib/ui"
+)
+
+type Selecter struct {
+ ui.Invalidatable
+ chooser bool
+ focused bool
+ focus int
+ options []string
+
+ onChoose func(option string)
+ onSelect func(option string)
+}
+
+func NewSelecter(options []string, focus int) *Selecter {
+ return &Selecter{
+ focus: focus,
+ options: options,
+ }
+}
+
+func (sel *Selecter) Chooser(chooser bool) *Selecter {
+ sel.chooser = chooser
+ return sel
+}
+
+func (sel *Selecter) Invalidate() {
+ sel.DoInvalidate(sel)
+}
+
+func (sel *Selecter) Draw(ctx *ui.Context) {
+ ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
+ x := 2
+ for i, option := range sel.options {
+ style := tcell.StyleDefault
+ if sel.focus == i {
+ if sel.focused {
+ style = style.Reverse(true)
+ } else if sel.chooser {
+ style = style.Bold(true)
+ }
+ }
+ x += ctx.Printf(x, 1, style, "[%s]", option)
+ x += 5
+ }
+}
+
+func (sel *Selecter) OnChoose(fn func(option string)) *Selecter {
+ sel.onChoose = fn
+ return sel
+}
+
+func (sel *Selecter) OnSelect(fn func(option string)) *Selecter {
+ sel.onSelect = fn
+ return sel
+}
+
+func (sel *Selecter) Selected() string {
+ return sel.options[sel.focus]
+}
+
+func (sel *Selecter) Focus(focus bool) {
+ sel.focused = focus
+ sel.Invalidate()
+}
+
+func (sel *Selecter) Event(event tcell.Event) bool {
+ switch event := event.(type) {
+ case *tcell.EventKey:
+ switch event.Key() {
+ case tcell.KeyCtrlH:
+ fallthrough
+ case tcell.KeyLeft:
+ if sel.focus > 0 {
+ sel.focus--
+ sel.Invalidate()
+ }
+ if sel.onSelect != nil {
+ sel.onSelect(sel.Selected())
+ }
+ case tcell.KeyCtrlL:
+ fallthrough
+ case tcell.KeyRight:
+ if sel.focus < len(sel.options)-1 {
+ sel.focus++
+ sel.Invalidate()
+ }
+ if sel.onSelect != nil {
+ sel.onSelect(sel.Selected())
+ }
+ case tcell.KeyEnter:
+ if sel.onChoose != nil {
+ sel.onChoose(sel.Selected())
+ }
+ }
+ }
+ return false
+}
diff --git a/widgets/tabhost.go b/widgets/tabhost.go
index 2c33cf8d..0ac67e5b 100644
--- a/widgets/tabhost.go
+++ b/widgets/tabhost.go
@@ -5,7 +5,7 @@ import (
)
type TabHost interface {
- BeginExCommand()
+ BeginExCommand(cmd string)
SetStatus(status string) *StatusMessage
PushStatus(text string, expiry time.Duration) *StatusMessage
Beep()