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  }