aboutsummaryrefslogblamecommitdiffstats
path: root/commands/menu.go
blob: 953c6b459b74bb5412c5259fc420293f65c4d097 (plain) (tree)


























































































































































































                                                                                       
package commands

import (
	"errors"
	"io"
	"os"
	"os/exec"
	"strings"

	"git.sr.ht/~rjarry/aerc/app"
	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/go-opt"
)

type Menu struct {
	ErrExit     bool   `opt:"-e"`
	Background  bool   `opt:"-b"`
	Accounts    bool   `opt:"-a"`
	Directories bool   `opt:"-d"`
	Command     string `opt:"-c"`
	Xargs       string `opt:"..." complete:"CompleteXargs"`
}

func init() {
	Register(Menu{})
}

func (Menu) Context() CommandContext {
	return GLOBAL
}

func (Menu) Aliases() []string {
	return []string{"menu"}
}

func (*Menu) CompleteXargs(arg string) []string {
	return FilterList(ActiveCommandNames(), arg, nil)
}

func (m Menu) Execute([]string) error {
	if m.Command == "" {
		m.Command = config.General.DefaultMenuCmd
	}
	if m.Command == "" {
		return errors.New(
			"Either -c <command> or default-menu-cmd is required.")
	}
	if _, _, err := ResolveCommand(m.Xargs, nil, nil); err != nil {
		return err
	}

	lines, err := m.feedLines()
	if err != nil {
		return err
	}

	pick, err := os.CreateTemp("", "aerc-menu-*")
	if err != nil {
		return err
	}

	var proc *exec.Cmd
	if strings.Contains(m.Command, "%f") {
		proc = exec.Command("sh", "-c",
			strings.ReplaceAll(m.Command, "%f", opt.QuoteArg(pick.Name())))
	} else {
		proc = exec.Command("sh", "-c", m.Command+" >&3")
		proc.ExtraFiles = append(proc.ExtraFiles, pick)
	}
	if len(lines) > 0 {
		proc.Stdin = strings.NewReader(strings.Join(lines, "\n"))
	}

	xargs := func(err error) {
		var buf []byte
		if err == nil {
			_, err = pick.Seek(0, io.SeekStart)
		}
		if err == nil {
			buf, err = io.ReadAll(pick)
		}
		pick.Close()
		os.Remove(pick.Name())
		if err != nil {
			app.PushError("command failed: " + err.Error())
			return
		}
		if len(buf) == 0 {
			return
		}
		var cmd Command
		var cmdline string

		for _, line := range strings.Split(string(buf), "\n") {
			line = strings.TrimSpace(line)
			if line == "" {
				continue
			}
			cmdline = m.Xargs + " " + line
			cmdline, cmd, err = ResolveCommand(cmdline, nil, nil)
			if err == nil {
				err = ExecuteCommand(cmd, cmdline)
			}
			if err != nil {
				app.PushError(m.Xargs + ": " + err.Error())
				if m.ErrExit {
					return
				}
			}
		}
	}

	if m.Background {
		go func() {
			defer log.PanicHandler()
			xargs(proc.Run())
		}()
	} else {
		term, err := app.NewTerminal(proc)
		if err != nil {
			return err
		}
		term.Focus(true)
		term.OnClose = func(err error) {
			app.CloseDialog()
			xargs(err)
		}

		title := " :" + strings.TrimLeft(m.Xargs, ": \t") + " ... "

		app.AddDialog(app.NewDialog(
			ui.NewBox(term, title, "", app.SelectedAccountUiConfig()),
			// start pos on screen
			func(h int) int {
				return h / 4
			},
			// dialog height
			func(h int) int {
				return h / 2
			},
		))
	}

	return nil
}

func (m Menu) feedLines() ([]string, error) {
	var lines []string

	switch {
	case m.Accounts && m.Directories:
		for _, a := range app.AccountNames() {
			account, _ := app.Account(a)
			a = opt.QuoteArg(a)
			for _, d := range account.Directories().List() {
				dir := account.Directories().Directory(d)
				if dir != nil && dir.Role != models.QueryRole {
					d = opt.QuoteArg(d)
				}
				lines = append(lines, a+" "+d)
			}
		}

	case m.Accounts:
		for _, account := range app.AccountNames() {
			lines = append(lines, opt.QuoteArg(account))
		}

	case m.Directories:
		account := app.SelectedAccount()
		if account == nil {
			return nil, errors.New("No account selected.")
		}
		for _, d := range account.Directories().List() {
			dir := account.Directories().Directory(d)
			if dir != nil && dir.Role != models.QueryRole {
				d = opt.QuoteArg(d)
			}
			lines = append(lines, d)
		}
	}

	return lines, nil
}