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 }