github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/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 "github.com/markusbkk/elvish/pkg/cli/modes" 9 "github.com/markusbkk/elvish/pkg/diag" 10 "github.com/markusbkk/elvish/pkg/parse" 11 ) 12 13 type item = modes.CompletionItem 14 15 // An error returned by Complete if the config has not supplied a PureEvaler. 16 var errNoPureEvaler = errors.New("no PureEvaler supplied") 17 18 // An error returned by Complete as well as the completers if there is no 19 // applicable completion. 20 var errNoCompletion = errors.New("no completion") 21 22 // Config stores the configuration required for code completion. 23 type Config struct { 24 // An interface to access the runtime. Complete will return an error if this 25 // is nil. 26 PureEvaler PureEvaler 27 // A function for filtering raw candidates. If nil, no filtering is done. 28 Filterer Filterer 29 // Used to generate candidates for a command argument. Defaults to 30 // Filenames. 31 ArgGenerator ArgGenerator 32 } 33 34 // Filterer is the type of functions that filter raw candidates. 35 type Filterer func(ctxName, seed string, rawItems []RawItem) []RawItem 36 37 // ArgGenerator is the type of functions that generate raw candidates for a 38 // command argument. It takes all the existing arguments, the last being the 39 // argument to complete, and returns raw candidates or an error. 40 type ArgGenerator func(args []string) ([]RawItem, error) 41 42 // Result keeps the result of the completion algorithm. 43 type Result struct { 44 Name string 45 Replace diag.Ranging 46 Items []modes.CompletionItem 47 } 48 49 // RawItem represents completion items before the quoting pass. 50 type RawItem interface { 51 String() string 52 Cook(parse.PrimaryType) modes.CompletionItem 53 } 54 55 // PureEvaler encapsulates the functionality the completion algorithm needs from 56 // the language runtime. 57 type PureEvaler interface { 58 EachExternal(func(cmd string)) 59 EachSpecial(func(special string)) 60 EachNs(func(string)) 61 EachVariableInNs(string, func(string)) 62 PurelyEvalPrimary(pn *parse.Primary) interface{} 63 PurelyEvalCompound(*parse.Compound) (string, bool) 64 PurelyEvalPartialCompound(*parse.Compound, int) (string, bool) 65 } 66 67 // CodeBuffer is the same the type in github.com/markusbkk/elvish/pkg/el/codearea, 68 // replicated here to avoid an unnecessary dependency. 69 type CodeBuffer struct { 70 Content string 71 Dot int 72 } 73 74 // Complete runs the code completion algorithm in the given context, and returns 75 // the completion type, items and any error encountered. 76 func Complete(code CodeBuffer, cfg Config) (*Result, error) { 77 if cfg.PureEvaler == nil { 78 return nil, errNoPureEvaler 79 } 80 if cfg.Filterer == nil { 81 cfg.Filterer = FilterPrefix 82 } 83 if cfg.ArgGenerator == nil { 84 cfg.ArgGenerator = GenerateFileNames 85 } 86 87 // Ignore the error; the function always returns a valid *ChunkNode. 88 tree, _ := parse.Parse(parse.Source{Name: "[interactive]", Code: code.Content}, parse.Config{}) 89 path := findNodePath(tree.Root, code.Dot) 90 for _, completer := range completers { 91 ctx, rawItems, err := completer(path, cfg) 92 if err == errNoCompletion { 93 continue 94 } 95 rawItems = cfg.Filterer(ctx.name, ctx.seed, rawItems) 96 sort.Slice(rawItems, func(i, j int) bool { 97 return rawItems[i].String() < rawItems[j].String() 98 }) 99 items := make([]modes.CompletionItem, len(rawItems)) 100 for i, rawCand := range rawItems { 101 items[i] = rawCand.Cook(ctx.quote) 102 } 103 items = dedup(items) 104 return &Result{Name: ctx.name, Items: items, Replace: ctx.interval}, nil 105 } 106 return nil, errNoCompletion 107 } 108 109 func dedup(items []modes.CompletionItem) []modes.CompletionItem { 110 var result []modes.CompletionItem 111 for i, item := range items { 112 if i == 0 || item.ToInsert != items[i-1].ToInsert { 113 result = append(result, item) 114 } 115 } 116 return result 117 }