elvish/edit/completers.go
2016-02-24 13:18:16 +01:00

196 lines
4.3 KiB
Go

package edit
import (
"io/ioutil"
"path"
"sort"
"strings"
"github.com/elves/elvish/eval"
"github.com/elves/elvish/parse"
"github.com/elves/elvish/util"
)
// A completer takes the current Node and an Editor and returns a list of
// candidates. the Node is always a leaf in the parsed AST.
type completer func(parse.Node, *Editor) []*candidate
var completers = []struct {
name string
completer
}{
{"variable", complVariable},
{"command name", complNewForm},
{"command name", makeCompoundCompleter(complFormHead)},
{"argument", complNewArg},
{"argument", makeCompoundCompleter(complArg)},
}
func complVariable(n parse.Node, ed *Editor) []*candidate {
primary, ok := n.(*parse.Primary)
if !ok || primary.Type != parse.Variable {
return nil
}
head := primary.Value
// Collect matching variables.
var varnames []string
for varname := range ed.evaler.Builtin() {
if strings.HasPrefix(varname, head) {
varnames = append(varnames, varname)
}
}
for varname := range ed.evaler.Global() {
if strings.HasPrefix(varname, head) {
varnames = append(varnames, varname)
}
}
sort.Strings(varnames)
// Build candidates.
cands := []*candidate{}
for _, varname := range varnames {
cands = append(cands, &candidate{
source: styled{varname[len(head):], styleForType[Variable]},
menu: styled{"$" + varname, ""}})
}
return cands
}
func complNewForm(n parse.Node, ed *Editor) []*candidate {
if _, ok := n.(*parse.Chunk); ok {
return complFormHeadInner("", ed)
}
if _, ok := n.Parent().(*parse.Chunk); ok {
return complFormHeadInner("", ed)
}
return nil
}
func makeCompoundCompleter(
f func(*parse.Compound, string, *Editor) []*candidate) completer {
return func(n parse.Node, ed *Editor) []*candidate {
pn, ok := n.(*parse.Primary)
if !ok {
return nil
}
cn, head := simpleCompound(pn)
if cn == nil {
return nil
}
return f(cn, head, ed)
}
}
func complFormHead(cn *parse.Compound, head string, ed *Editor) []*candidate {
if isFormHead(cn) {
return complFormHeadInner(head, ed)
}
return nil
}
func complFormHeadInner(head string, ed *Editor) []*candidate {
if util.DontSearch(head) {
return complArgInner(head, ed, true)
}
cands := []*candidate{}
foundCommand := func(s string) {
if strings.HasPrefix(s, head) {
cands = append(cands, &candidate{
source: styled{s[len(head):], styleForGoodCommand},
menu: styled{s, ""},
})
}
}
for special := range isBuiltinSpecial {
foundCommand(special)
}
for variable := range ed.evaler.Builtin() {
if strings.HasPrefix(variable, eval.FnPrefix) {
foundCommand(variable[len(eval.FnPrefix):])
}
}
for variable := range ed.evaler.Global() {
if strings.HasPrefix(variable, eval.FnPrefix) {
foundCommand(variable[len(eval.FnPrefix):])
}
}
for command := range ed.isExternal {
foundCommand(command)
}
return cands
}
func complNewArg(n parse.Node, ed *Editor) []*candidate {
sn, ok := n.(*parse.Sep)
if !ok {
return nil
}
if _, ok := sn.Parent().(*parse.Form); !ok {
return nil
}
return complArgInner("", ed, false)
}
func complArg(cn *parse.Compound, head string, ed *Editor) []*candidate {
return complArgInner(head, ed, false)
}
// TODO: getStyle does redundant stats.
func complArgInner(head string, ed *Editor, formHead bool) []*candidate {
dir, fileprefix := path.Split(head)
if dir == "" {
dir = "."
}
infos, err := ioutil.ReadDir(dir)
cands := []*candidate{}
if err != nil {
ed.addTip("cannot list directory %s: %v", dir, err)
return cands
}
// Make candidates out of elements that match the file component.
for _, info := range infos {
name := info.Name()
// Irrevelant file.
if !strings.HasPrefix(name, fileprefix) {
continue
}
// Hide dot files unless file starts with a dot.
if !dotfile(fileprefix) && dotfile(name) {
continue
}
// Only accept searchable directories and executable files if
// completing head.
if formHead && !(info.IsDir() || (info.Mode()&0111) != 0) {
continue
}
// Full filename for .getStyle.
full := head + name[len(fileprefix):]
if info.IsDir() {
name += "/"
} else {
name += " "
}
cands = append(cands, &candidate{
source: styled{name[len(fileprefix):], ""},
menu: styled{name, defaultLsColor.getStyle(full)},
})
}
return cands
}
func dotfile(fname string) bool {
return strings.HasPrefix(fname, ".")
}