1package commands
2
3import (
4	"fmt"
5	"io"
6	"os"
7	"os/exec"
8	"path/filepath"
9	"sort"
10	"strings"
11	"time"
12
13	"git.sr.ht/~sircmpwn/aerc/lib"
14	"git.sr.ht/~sircmpwn/aerc/models"
15	"git.sr.ht/~sircmpwn/aerc/widgets"
16	"github.com/gdamore/tcell"
17	"github.com/mitchellh/go-homedir"
18)
19
20// QuickTerm is an ephemeral terminal for running a single command and quiting.
21func QuickTerm(aerc *widgets.Aerc, args []string, stdin io.Reader) (*widgets.Terminal, error) {
22	cmd := exec.Command(args[0], args[1:]...)
23	pipe, err := cmd.StdinPipe()
24	if err != nil {
25		return nil, err
26	}
27
28	term, err := widgets.NewTerminal(cmd)
29	if err != nil {
30		return nil, err
31	}
32
33	term.OnClose = func(err error) {
34		if err != nil {
35			aerc.PushError(" " + err.Error())
36			// remove the tab on error, otherwise it gets stuck
37			aerc.RemoveTab(term)
38		} else {
39			aerc.PushStatus("Process complete, press any key to close.",
40				10*time.Second)
41			term.OnEvent = func(event tcell.Event) bool {
42				aerc.RemoveTab(term)
43				return true
44			}
45		}
46	}
47
48	term.OnStart = func() {
49		status := make(chan error, 1)
50
51		go func() {
52			_, err := io.Copy(pipe, stdin)
53			defer pipe.Close()
54			status <- err
55		}()
56
57		err := <-status
58		if err != nil {
59			aerc.PushError(" " + err.Error())
60		}
61	}
62
63	return term, nil
64}
65
66// CompletePath provides filesystem completions given a starting path.
67func CompletePath(path string) []string {
68	if path == "" {
69		// default to cwd
70		cwd, err := os.Getwd()
71		if err != nil {
72			return nil
73		}
74		path = cwd
75	}
76
77	path, err := homedir.Expand(path)
78	if err != nil {
79		return nil
80	}
81
82	// strip trailing slashes, etc.
83	path = filepath.Clean(path)
84
85	if _, err := os.Stat(path); os.IsNotExist(err) {
86		// if the path doesn't exist, it is likely due to it being a partial path
87		// in this case, we want to return possible matches (ie /hom* should match
88		// /home)
89		matches, err := filepath.Glob(fmt.Sprintf("%s*", path))
90		if err != nil {
91			return nil
92		}
93
94		for i, m := range matches {
95			if isDir(m) {
96				matches[i] = m + "/"
97			}
98		}
99
100		sort.Strings(matches)
101		return matches
102	}
103
104	files := listDir(path, false)
105
106	for i, f := range files {
107		f = filepath.Join(path, f)
108		if isDir(f) {
109			f += "/"
110		}
111
112		files[i] = f
113	}
114
115	sort.Strings(files)
116	return files
117}
118
119func isDir(path string) bool {
120	info, err := os.Stat(path)
121	if err != nil {
122		return false
123	}
124
125	return info.IsDir()
126}
127
128// return all filenames in a directory, optionally including hidden files
129func listDir(path string, hidden bool) []string {
130	f, err := os.Open(path)
131	if err != nil {
132		return []string{}
133	}
134
135	files, err := f.Readdirnames(-1) // read all dir names
136	if err != nil {
137		return []string{}
138	}
139
140	if hidden {
141		return files
142	}
143
144	var filtered []string
145	for _, g := range files {
146		if !strings.HasPrefix(g, ".") {
147			filtered = append(filtered, g)
148		}
149	}
150
151	return filtered
152}
153
154// MarkedOrSelected returns either all marked messages if any are marked or the
155// selected message instead
156func MarkedOrSelected(pm widgets.ProvidesMessages) ([]uint32, error) {
157	// marked has priority over the selected message
158	marked, err := pm.MarkedMessages()
159	if err != nil {
160		return nil, err
161	}
162	if len(marked) > 0 {
163		return marked, nil
164	}
165	msg, err := pm.SelectedMessage()
166	if err != nil {
167		return nil, err
168	}
169	return []uint32{msg.Uid}, nil
170}
171
172// UidsFromMessageInfos extracts a uid slice from a slice of MessageInfos
173func UidsFromMessageInfos(msgs []*models.MessageInfo) []uint32 {
174	uids := make([]uint32, len(msgs))
175	i := 0
176	for _, msg := range msgs {
177		uids[i] = msg.Uid
178		i++
179	}
180	return uids
181}
182
183func MsgInfoFromUids(store *lib.MessageStore, uids []uint32) ([]*models.MessageInfo, error) {
184	infos := make([]*models.MessageInfo, len(uids))
185	for i, uid := range uids {
186		var ok bool
187		infos[i], ok = store.Messages[uid]
188		if !ok {
189			return nil, fmt.Errorf("uid not found")
190		}
191	}
192	return infos, nil
193}
194