github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/edit/completers.go (about)

     1  package edit
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"sort"
     9  	"strings"
    10  	"unicode/utf8"
    11  
    12  	"github.com/elves/elvish/eval"
    13  	"github.com/elves/elvish/parse"
    14  	"github.com/elves/elvish/util"
    15  	"github.com/elves/getopt"
    16  )
    17  
    18  // completer takes the current Node (always a leaf in the AST) and an Editor,
    19  // and should returns an interval and a list of candidates, meaning that the
    20  // text within the interval may be replaced by any of the candidates. If the
    21  // completer is not applicable, it should return an invalid interval [-1, end).
    22  type completer func(parse.Node, *Editor) (int, int, []*candidate)
    23  
    24  var completers = []struct {
    25  	name string
    26  	completer
    27  }{
    28  	{"variable", complVariable},
    29  	{"command name", complFormHead},
    30  	{"argument", complArg},
    31  }
    32  
    33  func complVariable(n parse.Node, ed *Editor) (int, int, []*candidate) {
    34  	begin, end := n.Begin(), n.End()
    35  
    36  	primary, ok := n.(*parse.Primary)
    37  	if !ok || primary.Type != parse.Variable {
    38  		return -1, -1, nil
    39  	}
    40  
    41  	splice, ns, head := eval.ParseVariable(primary.Value)
    42  
    43  	// Collect matching variables.
    44  	var varnames []string
    45  	iterateVariables(ed.evaler, ns, func(varname string) {
    46  		if strings.HasPrefix(varname, head) {
    47  			varnames = append(varnames, varname)
    48  		}
    49  	})
    50  	sort.Strings(varnames)
    51  
    52  	cands := make([]*candidate, len(varnames))
    53  	// Build candidates.
    54  	for i, varname := range varnames {
    55  		cands[i] = &candidate{text: "$" + eval.MakeVariableName(splice, ns, varname)}
    56  	}
    57  	return begin, end, cands
    58  }
    59  
    60  func iterateVariables(ev *eval.Evaler, ns string, f func(string)) {
    61  	if ns == "" {
    62  		for varname := range eval.Builtin() {
    63  			f(varname)
    64  		}
    65  		for varname := range ev.Global {
    66  			f(varname)
    67  		}
    68  		// TODO Include local names as well.
    69  	} else {
    70  		for varname := range ev.Modules[ns] {
    71  			f(varname)
    72  		}
    73  	}
    74  }
    75  
    76  func complFormHead(n parse.Node, ed *Editor) (int, int, []*candidate) {
    77  	begin, end, head, q := findFormHeadContext(n)
    78  	if begin == -1 {
    79  		return -1, -1, nil
    80  	}
    81  	cands, err := complFormHeadInner(head, ed)
    82  	if err != nil {
    83  		ed.notify("%v", err)
    84  	}
    85  	fixCandidates(cands, q)
    86  	return begin, end, cands
    87  }
    88  
    89  func findFormHeadContext(n parse.Node) (int, int, string, parse.PrimaryType) {
    90  	_, isChunk := n.(*parse.Chunk)
    91  	_, isPipeline := n.(*parse.Pipeline)
    92  	if isChunk || isPipeline {
    93  		return n.Begin(), n.End(), "", parse.Bareword
    94  	}
    95  
    96  	if primary, ok := n.(*parse.Primary); ok {
    97  		if compound, head := primaryInSimpleCompound(primary); compound != nil {
    98  			if form, ok := compound.Parent().(*parse.Form); ok {
    99  				if form.Head == compound {
   100  					return compound.Begin(), compound.End(), head, primary.Type
   101  				}
   102  			}
   103  		}
   104  	}
   105  	return -1, -1, "", 0
   106  }
   107  
   108  func complFormHeadInner(head string, ed *Editor) ([]*candidate, error) {
   109  	if util.DontSearch(head) {
   110  		return complFilenameInner(head, true)
   111  	}
   112  
   113  	var commands []string
   114  	got := func(s string) {
   115  		if strings.HasPrefix(s, head) {
   116  			commands = append(commands, s)
   117  		}
   118  	}
   119  	for special := range isBuiltinSpecial {
   120  		got(special)
   121  	}
   122  	splice, ns, _ := eval.ParseVariable(head)
   123  	iterateVariables(ed.evaler, ns, func(varname string) {
   124  		if strings.HasPrefix(varname, eval.FnPrefix) {
   125  			got(eval.MakeVariableName(splice, ns, varname[len(eval.FnPrefix):]))
   126  		}
   127  	})
   128  	for command := range ed.isExternal {
   129  		got(command)
   130  	}
   131  	sort.Strings(commands)
   132  
   133  	cands := []*candidate{}
   134  	for _, cmd := range commands {
   135  		cands = append(cands, &candidate{text: cmd})
   136  	}
   137  	return cands, nil
   138  }
   139  
   140  func complArg(n parse.Node, ed *Editor) (int, int, []*candidate) {
   141  	begin, end, current, q, form := findArgContext(n)
   142  	if begin == -1 {
   143  		return -1, -1, nil
   144  	}
   145  
   146  	// Find out head of the form and preceding arguments.
   147  	// If Form.Head is not a simple compound, head will be "", just what we want.
   148  	_, head := simpleCompound(form.Head, nil)
   149  	var args []string
   150  	for _, compound := range form.Args {
   151  		if compound.Begin() >= begin {
   152  			break
   153  		}
   154  		ok, arg := simpleCompound(compound, nil)
   155  		if ok {
   156  			// XXX Arguments that are not simple compounds are simply ignored.
   157  			args = append(args, arg)
   158  		}
   159  	}
   160  
   161  	words := make([]string, len(args)+2)
   162  	words[0] = head
   163  	words[len(words)-1] = current
   164  	copy(words[1:len(words)-1], args[:])
   165  
   166  	cands, err := completeArg(words, ed)
   167  	if err != nil {
   168  		ed.notify("%v", err)
   169  	}
   170  	fixCandidates(cands, q)
   171  	return begin, end, cands
   172  }
   173  
   174  func findArgContext(n parse.Node) (int, int, string, parse.PrimaryType, *parse.Form) {
   175  	if sep, ok := n.(*parse.Sep); ok {
   176  		if form, ok := sep.Parent().(*parse.Form); ok {
   177  			return n.End(), n.End(), "", parse.Bareword, form
   178  		}
   179  	}
   180  	if primary, ok := n.(*parse.Primary); ok {
   181  		if compound, head := primaryInSimpleCompound(primary); compound != nil {
   182  			if form, ok := compound.Parent().(*parse.Form); ok {
   183  				if form.Head != compound {
   184  					return compound.Begin(), compound.End(), head, primary.Type, form
   185  				}
   186  			}
   187  		}
   188  	}
   189  	return -1, -1, "", 0, nil
   190  }
   191  
   192  // TODO: getStyle does redundant stats.
   193  func complFilenameInner(head string, executableOnly bool) ([]*candidate, error) {
   194  	dir, fileprefix := path.Split(head)
   195  	if dir == "" {
   196  		dir = "."
   197  	}
   198  
   199  	infos, err := ioutil.ReadDir(dir)
   200  	if err != nil {
   201  		return nil, fmt.Errorf("cannot list directory %s: %v", dir, err)
   202  	}
   203  
   204  	cands := []*candidate{}
   205  	// Make candidates out of elements that match the file component.
   206  	for _, info := range infos {
   207  		name := info.Name()
   208  		// Irrevelant file.
   209  		if !strings.HasPrefix(name, fileprefix) {
   210  			continue
   211  		}
   212  		// Hide dot files unless file starts with a dot.
   213  		if !dotfile(fileprefix) && dotfile(name) {
   214  			continue
   215  		}
   216  		// Only accept searchable directories and executable files if
   217  		// executableOnly is true.
   218  		if executableOnly && !(info.IsDir() || (info.Mode()&0111) != 0) {
   219  			continue
   220  		}
   221  
   222  		// Full filename for source and getStyle.
   223  		full := head + name[len(fileprefix):]
   224  
   225  		suffix := " "
   226  		if info.IsDir() {
   227  			suffix = "/"
   228  		} else if info.Mode()&os.ModeSymlink != 0 {
   229  			stat, err := os.Stat(full)
   230  			if err == nil && stat.IsDir() {
   231  				// Symlink to directory.
   232  				suffix = "/"
   233  			}
   234  		}
   235  
   236  		cands = append(cands, &candidate{
   237  			text: full, suffix: suffix,
   238  			display: styled{name, defaultLsColor.getStyle(full)},
   239  		})
   240  	}
   241  
   242  	return cands, nil
   243  }
   244  
   245  func fixCandidates(cands []*candidate, q parse.PrimaryType) []*candidate {
   246  	for _, cand := range cands {
   247  		quoted, _ := parse.QuoteAs(cand.text, q)
   248  		cand.text = quoted + cand.suffix
   249  	}
   250  	return cands
   251  }
   252  
   253  func dotfile(fname string) bool {
   254  	return strings.HasPrefix(fname, ".")
   255  }
   256  
   257  func complGetopt(ec *eval.EvalCtx, elemsv eval.IteratorValue, optsv eval.IteratorValue, argsv eval.IteratorValue) {
   258  	var (
   259  		elems    []string
   260  		opts     []*getopt.Option
   261  		args     []eval.FnValue
   262  		variadic bool
   263  	)
   264  	// Convert arguments.
   265  	elemsv.Iterate(func(v eval.Value) bool {
   266  		elem, ok := v.(eval.String)
   267  		if !ok {
   268  			throwf("arg should be string, got %s", v.Kind())
   269  		}
   270  		elems = append(elems, string(elem))
   271  		return true
   272  	})
   273  	optsv.Iterate(func(v eval.Value) bool {
   274  		m, ok := v.(eval.MapLike)
   275  		if !ok {
   276  			throwf("opt should be map-like, got %s", v.Kind())
   277  		}
   278  		opt := &getopt.Option{}
   279  		vshort := maybeIndex(m, eval.String("short"))
   280  		if vshort != nil {
   281  			sv, ok := vshort.(eval.String)
   282  			if !ok {
   283  				throwf("short option should be string, got %s", vshort.Kind())
   284  			}
   285  			s := string(sv)
   286  			r, size := utf8.DecodeRuneInString(s)
   287  			if r == utf8.RuneError || size != len(s) {
   288  				throwf("short option should be exactly one rune, got %v", parse.Quote(s))
   289  			}
   290  			opt.Short = r
   291  		}
   292  		vlong := maybeIndex(m, eval.String("long"))
   293  		if vlong != nil {
   294  			s, ok := vlong.(eval.String)
   295  			if !ok {
   296  				throwf("long option should be string, got %s", vlong.Kind())
   297  			}
   298  			opt.Long = string(s)
   299  		}
   300  		if vshort == nil && vlong == nil {
   301  			throwf("opt should have at least one of short and long as keys")
   302  		}
   303  		// TODO support &desc
   304  		opts = append(opts, opt)
   305  		return true
   306  	})
   307  	argsv.Iterate(func(v eval.Value) bool {
   308  		sv, ok := v.(eval.String)
   309  		if ok {
   310  			if string(sv) == "..." {
   311  				variadic = true
   312  				return true
   313  			}
   314  			throwf("string except for ... not allowed as argument handler, got %s", parse.Quote(string(sv)))
   315  		}
   316  		arg, ok := v.(eval.FnValue)
   317  		if !ok {
   318  			throwf("argument handler should be fn, got %s", v.Kind())
   319  		}
   320  		args = append(args, arg)
   321  		return true
   322  	})
   323  	// TODO Configurable config
   324  	g := getopt.Getopt{opts, getopt.GNUGetoptLong}
   325  	_, _, ctx := g.Parse(elems)
   326  	out := ec.OutputChan()
   327  	_ = variadic // XXX
   328  	switch ctx.Type {
   329  	case getopt.NewOptionOrArgument, getopt.Argument:
   330  	case getopt.NewOption:
   331  		for _, opt := range opts {
   332  			if opt.Short != 0 {
   333  				out <- eval.String("-" + string(opt.Short))
   334  			}
   335  			if opt.Long != "" {
   336  				out <- eval.String("--" + opt.Long)
   337  			}
   338  		}
   339  	case getopt.NewLongOption:
   340  		for _, opt := range opts {
   341  			if opt.Long != "" {
   342  				out <- eval.String("--" + opt.Long)
   343  			}
   344  		}
   345  	case getopt.LongOption:
   346  		for _, opt := range opts {
   347  			if strings.HasPrefix(opt.Long, ctx.Text) {
   348  				out <- eval.String("--" + opt.Long)
   349  			}
   350  		}
   351  	case getopt.ChainShortOption:
   352  		for _, opt := range opts {
   353  			if opt.Short != 0 {
   354  				// XXX loses chained options
   355  				out <- eval.String("-" + string(opt.Short))
   356  			}
   357  		}
   358  	case getopt.OptionArgument:
   359  	}
   360  }
   361  
   362  func maybeIndex(m eval.MapLike, k eval.Value) eval.Value {
   363  	if !m.HasKey(k) {
   364  		return nil
   365  	}
   366  	return m.IndexOne(k)
   367  }