ea2646fc03
Especially if one tries to interact with all marked messages there could be the case that not all headers are fetched yet, hence the messageInfo is still nil. This segfaults a lot of commands which in principle only need the uid to complete. If we switch to uids, this issue can be alleviated for those commands.
193 lines
3.7 KiB
Go
193 lines
3.7 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.sr.ht/~sircmpwn/aerc/lib"
|
|
"git.sr.ht/~sircmpwn/aerc/models"
|
|
"git.sr.ht/~sircmpwn/aerc/widgets"
|
|
"github.com/gdamore/tcell"
|
|
"github.com/mitchellh/go-homedir"
|
|
)
|
|
|
|
// QuickTerm is an ephemeral terminal for running a single command and quiting.
|
|
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 {
|
|
aerc.PushError(" " + err.Error())
|
|
// remove the tab on error, otherwise it gets stuck
|
|
aerc.RemoveTab(term)
|
|
} else {
|
|
aerc.PushStatus("Process complete, press any key to close.",
|
|
10*time.Second)
|
|
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 {
|
|
aerc.PushError(" " + err.Error())
|
|
}
|
|
}
|
|
|
|
return term, nil
|
|
}
|
|
|
|
// 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 + "/"
|
|
}
|
|
}
|
|
|
|
sort.Strings(matches)
|
|
return matches
|
|
}
|
|
|
|
files := listDir(path, false)
|
|
|
|
for i, f := range files {
|
|
f = filepath.Join(path, f)
|
|
if isDir(f) {
|
|
f += "/"
|
|
}
|
|
|
|
files[i] = f
|
|
}
|
|
|
|
sort.Strings(files)
|
|
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
|
|
}
|
|
|
|
// MarkedOrSelected returns either all marked messages if any are marked or the
|
|
// selected message instead
|
|
func MarkedOrSelected(pm widgets.ProvidesMessages) ([]uint32, error) {
|
|
// 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
|
|
}
|
|
return []uint32{msg.Uid}, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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
|
|
}
|