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 }