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) }