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 }