2019-05-26 21:37:39 +00:00
|
|
|
package commands
|
|
|
|
|
|
|
|
import (
|
2019-07-30 19:10:57 +00:00
|
|
|
"fmt"
|
2019-05-26 21:37:39 +00:00
|
|
|
"io"
|
2019-07-30 19:10:57 +00:00
|
|
|
"os"
|
2019-05-26 21:37:39 +00:00
|
|
|
"os/exec"
|
2019-07-30 19:10:57 +00:00
|
|
|
"path/filepath"
|
2019-09-20 16:38:09 +00:00
|
|
|
"sort"
|
2019-07-30 19:10:57 +00:00
|
|
|
"strings"
|
2020-05-28 14:32:32 +00:00
|
|
|
"time"
|
2019-05-26 21:37:39 +00:00
|
|
|
|
2020-05-09 09:50:31 +00:00
|
|
|
"git.sr.ht/~sircmpwn/aerc/lib"
|
2019-12-18 05:34:00 +00:00
|
|
|
"git.sr.ht/~sircmpwn/aerc/models"
|
2019-05-26 21:37:39 +00:00
|
|
|
"git.sr.ht/~sircmpwn/aerc/widgets"
|
2020-11-30 22:07:03 +00:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2019-07-30 19:10:57 +00:00
|
|
|
"github.com/mitchellh/go-homedir"
|
2019-05-26 21:37:39 +00:00
|
|
|
)
|
|
|
|
|
2020-05-20 00:48:23 +00:00
|
|
|
// QuickTerm is an ephemeral terminal for running a single command and quitting.
|
2019-05-26 21:37:39 +00:00
|
|
|
func QuickTerm(aerc *widgets.Aerc, args []string, stdin io.Reader) (*widgets.Terminal, error) {
|
|
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
|
|
pipe, err := cmd.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
term, err := widgets.NewTerminal(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
term.OnClose = func(err error) {
|
|
|
|
if err != nil {
|
2021-01-30 12:51:32 +00:00
|
|
|
aerc.PushError(err.Error())
|
2019-05-26 21:37:39 +00:00
|
|
|
// remove the tab on error, otherwise it gets stuck
|
|
|
|
aerc.RemoveTab(term)
|
|
|
|
} else {
|
2020-05-28 14:32:32 +00:00
|
|
|
aerc.PushStatus("Process complete, press any key to close.",
|
|
|
|
10*time.Second)
|
2019-05-26 21:37:39 +00:00
|
|
|
term.OnEvent = func(event tcell.Event) bool {
|
|
|
|
aerc.RemoveTab(term)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
term.OnStart = func() {
|
|
|
|
status := make(chan error, 1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
_, err := io.Copy(pipe, stdin)
|
|
|
|
defer pipe.Close()
|
|
|
|
status <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
err := <-status
|
|
|
|
if err != nil {
|
2021-01-30 12:51:32 +00:00
|
|
|
aerc.PushError(err.Error())
|
2019-05-26 21:37:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return term, nil
|
|
|
|
}
|
2019-07-30 19:10:57 +00:00
|
|
|
|
|
|
|
// CompletePath provides filesystem completions given a starting path.
|
|
|
|
func CompletePath(path string) []string {
|
|
|
|
if path == "" {
|
|
|
|
// default to cwd
|
|
|
|
cwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
path = cwd
|
|
|
|
}
|
|
|
|
|
|
|
|
path, err := homedir.Expand(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// strip trailing slashes, etc.
|
|
|
|
path = filepath.Clean(path)
|
|
|
|
|
|
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
|
|
// if the path doesn't exist, it is likely due to it being a partial path
|
|
|
|
// in this case, we want to return possible matches (ie /hom* should match
|
|
|
|
// /home)
|
|
|
|
matches, err := filepath.Glob(fmt.Sprintf("%s*", path))
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, m := range matches {
|
|
|
|
if isDir(m) {
|
|
|
|
matches[i] = m + "/"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-20 16:38:09 +00:00
|
|
|
sort.Strings(matches)
|
2019-07-30 19:10:57 +00:00
|
|
|
return matches
|
|
|
|
}
|
|
|
|
|
|
|
|
files := listDir(path, false)
|
|
|
|
|
|
|
|
for i, f := range files {
|
|
|
|
f = filepath.Join(path, f)
|
|
|
|
if isDir(f) {
|
|
|
|
f += "/"
|
|
|
|
}
|
|
|
|
|
|
|
|
files[i] = f
|
|
|
|
}
|
|
|
|
|
2019-09-20 16:38:09 +00:00
|
|
|
sort.Strings(files)
|
2019-07-30 19:10:57 +00:00
|
|
|
return files
|
|
|
|
}
|
|
|
|
|
|
|
|
func isDir(path string) bool {
|
|
|
|
info, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return info.IsDir()
|
|
|
|
}
|
|
|
|
|
|
|
|
// return all filenames in a directory, optionally including hidden files
|
|
|
|
func listDir(path string, hidden bool) []string {
|
|
|
|
f, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
files, err := f.Readdirnames(-1) // read all dir names
|
|
|
|
if err != nil {
|
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
if hidden {
|
|
|
|
return files
|
|
|
|
}
|
|
|
|
|
|
|
|
var filtered []string
|
|
|
|
for _, g := range files {
|
|
|
|
if !strings.HasPrefix(g, ".") {
|
|
|
|
filtered = append(filtered, g)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return filtered
|
|
|
|
}
|
2019-12-18 05:34:00 +00:00
|
|
|
|
|
|
|
// MarkedOrSelected returns either all marked messages if any are marked or the
|
|
|
|
// selected message instead
|
2020-05-09 09:50:31 +00:00
|
|
|
func MarkedOrSelected(pm widgets.ProvidesMessages) ([]uint32, error) {
|
2019-12-18 05:34:00 +00:00
|
|
|
// marked has priority over the selected message
|
|
|
|
marked, err := pm.MarkedMessages()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(marked) > 0 {
|
|
|
|
return marked, nil
|
|
|
|
}
|
|
|
|
msg, err := pm.SelectedMessage()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-09 09:50:31 +00:00
|
|
|
return []uint32{msg.Uid}, nil
|
2019-12-18 05:34:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UidsFromMessageInfos extracts a uid slice from a slice of MessageInfos
|
|
|
|
func UidsFromMessageInfos(msgs []*models.MessageInfo) []uint32 {
|
|
|
|
uids := make([]uint32, len(msgs))
|
|
|
|
i := 0
|
|
|
|
for _, msg := range msgs {
|
|
|
|
uids[i] = msg.Uid
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
return uids
|
|
|
|
}
|
2020-05-09 09:50:31 +00:00
|
|
|
|
|
|
|
func MsgInfoFromUids(store *lib.MessageStore, uids []uint32) ([]*models.MessageInfo, error) {
|
|
|
|
infos := make([]*models.MessageInfo, len(uids))
|
|
|
|
for i, uid := range uids {
|
|
|
|
var ok bool
|
|
|
|
infos[i], ok = store.Messages[uid]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("uid not found")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return infos, nil
|
|
|
|
}
|