about summary refs log tree commit diff stats
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 3935173..e81e836 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 904013f..d7b46b9 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 d324908..9d955e1 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 1482f0e..f2c7249 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 4d41923..aca7dd4 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 0000000..7fae9cd
--- /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 2c33cf8..0ac67e5 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()