src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/edit/complete/completers.go (about)

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