github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/cli/mode/completion.go (about)

     1  package mode
     2  
     3  import (
     4  	"errors"
     5  
     6  	"src.elv.sh/pkg/cli"
     7  	"src.elv.sh/pkg/cli/tk"
     8  	"src.elv.sh/pkg/diag"
     9  	"src.elv.sh/pkg/ui"
    10  )
    11  
    12  // Completion is a mode specialized for viewing and inserting completion
    13  // candidates. It is based on the ComboBox widget.
    14  type Completion interface {
    15  	tk.ComboBox
    16  }
    17  
    18  // CompletionSpec specifies the configuration for the completion mode.
    19  type CompletionSpec struct {
    20  	Bindings tk.Bindings
    21  	Name     string
    22  	Replace  diag.Ranging
    23  	Items    []CompletionItem
    24  	Filter   FilterSpec
    25  }
    26  
    27  // CompletionItem represents a completion item, also known as a candidate.
    28  type CompletionItem struct {
    29  	// Used in the UI and for filtering.
    30  	ToShow string
    31  	// Style to use in the UI.
    32  	ShowStyle ui.Style
    33  	// Used when inserting a candidate.
    34  	ToInsert string
    35  }
    36  
    37  type completion struct {
    38  	tk.ComboBox
    39  	attached tk.CodeArea
    40  }
    41  
    42  var errNoCandidates = errors.New("no candidates")
    43  
    44  // NewCompletion starts the completion UI.
    45  func NewCompletion(app cli.App, cfg CompletionSpec) (Completion, error) {
    46  	if len(cfg.Items) == 0 {
    47  		return nil, errNoCandidates
    48  	}
    49  	w := tk.NewComboBox(tk.ComboBoxSpec{
    50  		CodeArea: tk.CodeAreaSpec{
    51  			Prompt:      modePrompt(" COMPLETING "+cfg.Name+" ", true),
    52  			Highlighter: cfg.Filter.Highlighter,
    53  		},
    54  		ListBox: tk.ListBoxSpec{
    55  			Horizontal: true,
    56  			Bindings:   cfg.Bindings,
    57  			OnSelect: func(it tk.Items, i int) {
    58  				text := it.(completionItems)[i].ToInsert
    59  				app.CodeArea().MutateState(func(s *tk.CodeAreaState) {
    60  					s.Pending = tk.PendingCode{
    61  						From: cfg.Replace.From, To: cfg.Replace.To, Content: text}
    62  				})
    63  			},
    64  			OnAccept: func(it tk.Items, i int) {
    65  				app.SetAddon(nil, true)
    66  			},
    67  			ExtendStyle: true,
    68  		},
    69  		OnFilter: func(w tk.ComboBox, p string) {
    70  			w.ListBox().Reset(filterCompletionItems(cfg.Items, cfg.Filter.makePredicate(p)), 0)
    71  		},
    72  	})
    73  	return completion{w, app.CodeArea()}, nil
    74  }
    75  
    76  func (w completion) Close(accept bool) {
    77  	w.attached.MutateState(func(s *tk.CodeAreaState) {
    78  		if accept {
    79  			s.ApplyPending()
    80  		} else {
    81  			s.Pending = tk.PendingCode{}
    82  		}
    83  	})
    84  }
    85  
    86  type completionItems []CompletionItem
    87  
    88  func filterCompletionItems(all []CompletionItem, p func(string) bool) completionItems {
    89  	var filtered []CompletionItem
    90  	for _, candidate := range all {
    91  		if p(candidate.ToShow) {
    92  			filtered = append(filtered, candidate)
    93  		}
    94  	}
    95  	return filtered
    96  }
    97  
    98  func (it completionItems) Show(i int) ui.Text {
    99  	return ui.Text{&ui.Segment{Style: it[i].ShowStyle, Text: it[i].ToShow}}
   100  }
   101  
   102  func (it completionItems) Len() int { return len(it) }