src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/edit/complete/complete.go (about) 1 // Package complete implements the code completion algorithm for Elvish. 2 package complete 3 4 import ( 5 "errors" 6 "sort" 7 8 "src.elv.sh/pkg/cli/modes" 9 "src.elv.sh/pkg/diag" 10 "src.elv.sh/pkg/eval" 11 "src.elv.sh/pkg/parse" 12 "src.elv.sh/pkg/parse/np" 13 ) 14 15 // An error returned by Complete as well as the completers if there is no 16 // applicable completion. 17 var errNoCompletion = errors.New("no completion") 18 19 // Config stores the configuration required for code completion. 20 type Config struct { 21 // A function for filtering raw candidates. If nil, no filtering is done. 22 Filterer Filterer 23 // Used to generate candidates for a command argument. Defaults to 24 // GenerateFileNames. 25 ArgGenerator ArgGenerator 26 } 27 28 // Filterer is the type of functions that filter raw candidates. 29 type Filterer func(ctxName, seed string, rawItems []RawItem) []RawItem 30 31 // ArgGenerator is the type of functions that generate raw candidates for a 32 // command argument. It takes all the existing arguments, the last being the 33 // argument to complete, and returns raw candidates or an error. 34 type ArgGenerator func(args []string) ([]RawItem, error) 35 36 // Result keeps the result of the completion algorithm. 37 type Result struct { 38 Name string 39 Replace diag.Ranging 40 Items []modes.CompletionItem 41 } 42 43 // RawItem represents completion items before the quoting pass. 44 type RawItem interface { 45 String() string 46 Cook(parse.PrimaryType) modes.CompletionItem 47 } 48 49 // CodeBuffer is the same the type in src.elv.sh/pkg/el/codearea, 50 // replicated here to avoid an unnecessary dependency. 51 type CodeBuffer struct { 52 Content string 53 Dot int 54 } 55 56 // Complete runs the code completion algorithm in the given context, and returns 57 // the completion type, items and any error encountered. 58 func Complete(code CodeBuffer, ev *eval.Evaler, cfg Config) (*Result, error) { 59 if cfg.Filterer == nil { 60 cfg.Filterer = FilterPrefix 61 } 62 if cfg.ArgGenerator == nil { 63 cfg.ArgGenerator = GenerateFileNames 64 } 65 66 // Ignore the error; the function always returns a valid *ChunkNode. 67 tree, _ := parse.Parse(parse.Source{Name: "[interactive]", Code: code.Content}, parse.Config{}) 68 path := np.FindLeft(tree.Root, code.Dot) 69 if len(path) == 0 { 70 // This can happen when there is a parse error. 71 return nil, errNoCompletion 72 } 73 for _, completer := range completers { 74 ctx, rawItems, err := completer(path, ev, cfg) 75 if err == errNoCompletion { 76 continue 77 } 78 rawItems = cfg.Filterer(ctx.name, ctx.seed, rawItems) 79 sort.Slice(rawItems, func(i, j int) bool { 80 return rawItems[i].String() < rawItems[j].String() 81 }) 82 items := make([]modes.CompletionItem, len(rawItems)) 83 for i, rawCand := range rawItems { 84 items[i] = rawCand.Cook(ctx.quote) 85 } 86 items = dedup(items) 87 return &Result{Name: ctx.name, Items: items, Replace: ctx.interval}, nil 88 } 89 return nil, errNoCompletion 90 } 91 92 func dedup(items []modes.CompletionItem) []modes.CompletionItem { 93 var result []modes.CompletionItem 94 for i, item := range items { 95 if i == 0 || item.ToInsert != items[i-1].ToInsert { 96 result = append(result, item) 97 } 98 } 99 return result 100 }