github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/edit/complete/completers.go (about)

     1  package complete
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/markusbkk/elvish/pkg/diag"
     7  	"github.com/markusbkk/elvish/pkg/eval"
     8  	"github.com/markusbkk/elvish/pkg/parse"
     9  )
    10  
    11  var completers = []func(nodePath, Config) (*context, []RawItem, error){
    12  	completeCommand,
    13  	completeIndex,
    14  	completeRedir,
    15  	completeVariable,
    16  	completeArg,
    17  }
    18  
    19  type context struct {
    20  	name     string
    21  	seed     string
    22  	quote    parse.PrimaryType
    23  	interval diag.Ranging
    24  }
    25  
    26  func completeArg(np nodePath, cfg Config) (*context, []RawItem, error) {
    27  	ev := cfg.PureEvaler
    28  
    29  	var form *parse.Form
    30  	if np.match(aSep, store(&form)) && form.Head != nil {
    31  		// Case 1: starting a new argument.
    32  		ctx := &context{"argument", "", parse.Bareword, range0(np[0].Range().To)}
    33  		args := purelyEvalForm(form, "", np[0].Range().To, ev)
    34  		items, err := generateArgs(args, cfg)
    35  		return ctx, items, err
    36  	}
    37  
    38  	expr := simpleExpr(ev)
    39  	if np.match(expr, store(&form)) && form.Head != nil && form.Head != expr.compound {
    40  		// Case 2: in an incomplete argument.
    41  		ctx := &context{"argument", expr.s, expr.quote, expr.compound.Range()}
    42  		args := purelyEvalForm(form, expr.s, expr.compound.Range().From, ev)
    43  		items, err := generateArgs(args, cfg)
    44  		return ctx, items, err
    45  	}
    46  
    47  	return nil, nil, errNoCompletion
    48  }
    49  
    50  func completeCommand(np nodePath, cfg Config) (*context, []RawItem, error) {
    51  	ev := cfg.PureEvaler
    52  	generateForEmpty := func(pos int) (*context, []RawItem, error) {
    53  		ctx := &context{"command", "", parse.Bareword, range0(pos)}
    54  		items, err := generateCommands("", ev)
    55  		return ctx, items, err
    56  	}
    57  
    58  	if np.match(aChunk) {
    59  		// Case 1: The leaf is a Chunk. That means that the chunk is empty
    60  		// (nothing entered at all) and it is a correct place for completing a
    61  		// command.
    62  		return generateForEmpty(np[0].Range().To)
    63  	}
    64  	if np.match(aSep, aChunk) || np.match(aSep, aPipeline) {
    65  		// Case 2: Just after a newline, semicolon, or a pipe.
    66  		return generateForEmpty(np[0].Range().To)
    67  	}
    68  
    69  	var primary *parse.Primary
    70  	if np.match(aSep, store(&primary)) {
    71  		t := primary.Type
    72  		if t == parse.OutputCapture || t == parse.ExceptionCapture || t == parse.Lambda {
    73  			// Case 3: At the beginning of output, exception capture or lambda.
    74  			//
    75  			// TODO: Don't trigger after "{|".
    76  			return generateForEmpty(np[0].Range().To)
    77  		}
    78  	}
    79  
    80  	expr := simpleExpr(ev)
    81  	var form *parse.Form
    82  	if np.match(expr, store(&form)) && form.Head == expr.compound {
    83  		// Case 4: At an already started command.
    84  		ctx := &context{"command", expr.s, expr.quote, expr.compound.Range()}
    85  		items, err := generateCommands(expr.s, ev)
    86  		return ctx, items, err
    87  	}
    88  
    89  	return nil, nil, errNoCompletion
    90  }
    91  
    92  // NOTE: This now only supports a single level of indexing; for instance,
    93  // $a[<Tab> is supported, but $a[x][<Tab> is not.
    94  func completeIndex(np nodePath, cfg Config) (*context, []RawItem, error) {
    95  	ev := cfg.PureEvaler
    96  	generateForEmpty := func(v interface{}, pos int) (*context, []RawItem, error) {
    97  		ctx := &context{"index", "", parse.Bareword, range0(pos)}
    98  		return ctx, generateIndices(v), nil
    99  	}
   100  
   101  	var indexing *parse.Indexing
   102  	if np.match(aSep, store(&indexing)) || np.match(aSep, aArray, store(&indexing)) {
   103  		// We are at a new index, either directly after the opening bracket, or
   104  		// after an existing index and some spaces.
   105  		if len(indexing.Indices) == 1 {
   106  			if indexee := ev.PurelyEvalPrimary(indexing.Head); indexee != nil {
   107  				return generateForEmpty(indexee, np[0].Range().To)
   108  			}
   109  		}
   110  	}
   111  
   112  	expr := simpleExpr(ev)
   113  	if np.match(expr, aArray, store(&indexing)) {
   114  		// We are just after an incomplete index.
   115  		if len(indexing.Indices) == 1 {
   116  			if indexee := ev.PurelyEvalPrimary(indexing.Head); indexee != nil {
   117  				ctx := &context{
   118  					"index", expr.s, expr.quote, expr.compound.Range()}
   119  				return ctx, generateIndices(indexee), nil
   120  			}
   121  		}
   122  	}
   123  
   124  	return nil, nil, errNoCompletion
   125  }
   126  
   127  func completeRedir(np nodePath, cfg Config) (*context, []RawItem, error) {
   128  	ev := cfg.PureEvaler
   129  	if np.match(aSep, aRedir) {
   130  		// Empty redirection target.
   131  		ctx := &context{"redir", "", parse.Bareword, range0(np[0].Range().To)}
   132  		items, err := generateFileNames("", false)
   133  		return ctx, items, err
   134  	}
   135  
   136  	expr := simpleExpr(ev)
   137  	if np.match(expr, aRedir) {
   138  		// Non-empty redirection target.
   139  		ctx := &context{"redir", expr.s, expr.quote, expr.compound.Range()}
   140  		items, err := generateFileNames(expr.s, false)
   141  		return ctx, items, err
   142  	}
   143  
   144  	return nil, nil, errNoCompletion
   145  }
   146  
   147  func completeVariable(np nodePath, cfg Config) (*context, []RawItem, error) {
   148  	ev := cfg.PureEvaler
   149  	primary, ok := np[0].(*parse.Primary)
   150  	if !ok || primary.Type != parse.Variable {
   151  		return nil, nil, errNoCompletion
   152  	}
   153  	sigil, qname := eval.SplitSigil(primary.Value)
   154  	ns, nameSeed := eval.SplitIncompleteQNameNs(qname)
   155  	// Move past "$", "@" and "<ns>:".
   156  	begin := primary.Range().From + 1 + len(sigil) + len(ns)
   157  
   158  	ctx := &context{
   159  		"variable", nameSeed, parse.Bareword,
   160  		diag.Ranging{From: begin, To: primary.Range().To}}
   161  
   162  	var items []RawItem
   163  	ev.EachVariableInNs(ns, func(varname string) {
   164  		items = append(items, noQuoteItem(parse.QuoteVariableName(varname)))
   165  	})
   166  
   167  	ev.EachNs(func(thisNs string) {
   168  		// This is to match namespaces that are "nested" under the current
   169  		// namespace.
   170  		if hasProperPrefix(thisNs, ns) {
   171  			items = append(items, noQuoteItem(parse.QuoteVariableName(thisNs[len(ns):])))
   172  		}
   173  	})
   174  
   175  	return ctx, items, nil
   176  }
   177  
   178  func purelyEvalForm(form *parse.Form, seed string, upto int, ev PureEvaler) []string {
   179  	// Find out head of the form and preceding arguments.
   180  	// If form.Head is not a simple compound, head will be "", just what we want.
   181  	head, _ := ev.PurelyEvalPartialCompound(form.Head, -1)
   182  	words := []string{head}
   183  	for _, compound := range form.Args {
   184  		if compound.Range().From >= upto {
   185  			break
   186  		}
   187  		if arg, ok := ev.PurelyEvalCompound(compound); ok {
   188  			// TODO(xiaq): Arguments that are not simple compounds are simply ignored.
   189  			words = append(words, arg)
   190  		}
   191  	}
   192  
   193  	words = append(words, seed)
   194  	return words
   195  }
   196  
   197  func range0(pos int) diag.Ranging {
   198  	return diag.Ranging{From: pos, To: pos}
   199  }
   200  
   201  func hasProperPrefix(s, p string) bool {
   202  	return len(s) > len(p) && strings.HasPrefix(s, p)
   203  }