src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/cli/modes/completion.go (about)

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