mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-14 02:57:52 +08:00
6564b14898
This fixes #1530.
102 lines
3.0 KiB
Go
102 lines
3.0 KiB
Go
// Package complete implements the code completion algorithm for Elvish.
|
|
package complete
|
|
|
|
import (
|
|
"errors"
|
|
"sort"
|
|
|
|
"src.elv.sh/pkg/cli/modes"
|
|
"src.elv.sh/pkg/diag"
|
|
"src.elv.sh/pkg/eval"
|
|
"src.elv.sh/pkg/parse"
|
|
)
|
|
|
|
type item = modes.CompletionItem
|
|
|
|
// An error returned by Complete as well as the completers if there is no
|
|
// applicable completion.
|
|
var errNoCompletion = errors.New("no completion")
|
|
|
|
// Config stores the configuration required for code completion.
|
|
type Config struct {
|
|
// A function for filtering raw candidates. If nil, no filtering is done.
|
|
Filterer Filterer
|
|
// Used to generate candidates for a command argument. Defaults to
|
|
// GenerateFileNames.
|
|
ArgGenerator ArgGenerator
|
|
}
|
|
|
|
// Filterer is the type of functions that filter raw candidates.
|
|
type Filterer func(ctxName, seed string, rawItems []RawItem) []RawItem
|
|
|
|
// ArgGenerator is the type of functions that generate raw candidates for a
|
|
// command argument. It takes all the existing arguments, the last being the
|
|
// argument to complete, and returns raw candidates or an error.
|
|
type ArgGenerator func(args []string) ([]RawItem, error)
|
|
|
|
// Result keeps the result of the completion algorithm.
|
|
type Result struct {
|
|
Name string
|
|
Replace diag.Ranging
|
|
Items []modes.CompletionItem
|
|
}
|
|
|
|
// RawItem represents completion items before the quoting pass.
|
|
type RawItem interface {
|
|
String() string
|
|
Cook(parse.PrimaryType) modes.CompletionItem
|
|
}
|
|
|
|
// CodeBuffer is the same the type in src.elv.sh/pkg/el/codearea,
|
|
// replicated here to avoid an unnecessary dependency.
|
|
type CodeBuffer struct {
|
|
Content string
|
|
Dot int
|
|
}
|
|
|
|
// Complete runs the code completion algorithm in the given context, and returns
|
|
// the completion type, items and any error encountered.
|
|
func Complete(code CodeBuffer, ev *eval.Evaler, cfg Config) (*Result, error) {
|
|
if cfg.Filterer == nil {
|
|
cfg.Filterer = FilterPrefix
|
|
}
|
|
if cfg.ArgGenerator == nil {
|
|
cfg.ArgGenerator = GenerateFileNames
|
|
}
|
|
|
|
// Ignore the error; the function always returns a valid *ChunkNode.
|
|
tree, _ := parse.Parse(parse.Source{Name: "[interactive]", Code: code.Content}, parse.Config{})
|
|
path := findNodePath(tree.Root, code.Dot)
|
|
if len(path) == 0 {
|
|
// This can happen when there is a parse error.
|
|
return nil, errNoCompletion
|
|
}
|
|
for _, completer := range completers {
|
|
ctx, rawItems, err := completer(path, ev, cfg)
|
|
if err == errNoCompletion {
|
|
continue
|
|
}
|
|
rawItems = cfg.Filterer(ctx.name, ctx.seed, rawItems)
|
|
sort.Slice(rawItems, func(i, j int) bool {
|
|
return rawItems[i].String() < rawItems[j].String()
|
|
})
|
|
items := make([]modes.CompletionItem, len(rawItems))
|
|
for i, rawCand := range rawItems {
|
|
items[i] = rawCand.Cook(ctx.quote)
|
|
}
|
|
items = dedup(items)
|
|
return &Result{Name: ctx.name, Items: items, Replace: ctx.interval}, nil
|
|
}
|
|
return nil, errNoCompletion
|
|
}
|
|
|
|
func dedup(items []modes.CompletionItem) []modes.CompletionItem {
|
|
var result []modes.CompletionItem
|
|
for i, item := range items {
|
|
if i == 0 || item.ToInsert != items[i-1].ToInsert {
|
|
result = append(result, item)
|
|
}
|
|
}
|
|
return result
|
|
}
|