picker.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. package picker
  2. import (
  3. "github.com/ayn2op/tview"
  4. "github.com/gdamore/tcell/v3"
  5. "github.com/sahilm/fuzzy"
  6. )
  7. type (
  8. SelectedFunc func(item Item)
  9. CancelFunc func()
  10. )
  11. type Picker struct {
  12. *tview.Flex
  13. input *tview.InputField
  14. list *tview.List
  15. onSelected SelectedFunc
  16. onCancel CancelFunc
  17. keyMap *KeyMap
  18. items Items
  19. filtered Items
  20. }
  21. func New() *Picker {
  22. p := &Picker{
  23. Flex: tview.NewFlex(),
  24. input: tview.NewInputField(),
  25. list: tview.NewList(),
  26. }
  27. // Show a horizontal bottom border to visually separate input from list.
  28. borderSet := tview.BorderSet{
  29. Bottom: tview.BoxDrawingsLightHorizontal,
  30. }
  31. borderSet.BottomLeft = borderSet.Bottom
  32. borderSet.BottomRight = borderSet.Bottom
  33. p.input.
  34. SetChangedFunc(p.onInputChanged).
  35. SetLabel("> ").
  36. SetBorders(tview.BordersBottom).
  37. SetBorderSet(borderSet).
  38. SetBorderStyle(tcell.StyleDefault.Dim(true)).
  39. SetInputCapture(p.onInputCapture)
  40. p.list.
  41. SetSelectedFunc(p.onListSelected).
  42. ShowSecondaryText(false).
  43. SetHighlightFullLine(true)
  44. p.
  45. SetDirection(tview.FlexRow).
  46. // bottom border + value
  47. AddItem(p.input, 2, 0, true).
  48. AddItem(p.list, 0, 1, false)
  49. p.Update()
  50. return p
  51. }
  52. func (p *Picker) SetKeyMap(keyMap *KeyMap) {
  53. p.keyMap = keyMap
  54. }
  55. func (p *Picker) SetSelectedFunc(onSelected SelectedFunc) {
  56. p.onSelected = onSelected
  57. }
  58. func (p *Picker) SetCancelFunc(onCancel CancelFunc) {
  59. p.onCancel = onCancel
  60. }
  61. func (p *Picker) ClearInput() {
  62. p.input.SetText("")
  63. }
  64. func (p *Picker) ClearList() {
  65. p.list.Clear()
  66. }
  67. func (p *Picker) ClearItems() {
  68. p.items = nil
  69. p.filtered = nil
  70. }
  71. func (p *Picker) AddItem(item Item) {
  72. p.items = append(p.items, item)
  73. }
  74. func (p *Picker) Update() {
  75. p.ClearInput()
  76. p.onInputChanged("")
  77. }
  78. func (p *Picker) onListSelected(index int, text, _ string, _ rune) {
  79. if p.onSelected != nil {
  80. if index >= 0 && index < len(p.filtered) {
  81. item := p.filtered[index]
  82. p.onSelected(item)
  83. }
  84. }
  85. }
  86. func (p *Picker) onInputChanged(text string) {
  87. var fuzzied Items
  88. if text == "" {
  89. fuzzied = append(fuzzied, p.items...)
  90. } else {
  91. matches := fuzzy.FindFrom(text, p.items)
  92. for _, match := range matches {
  93. fuzzied = append(fuzzied, p.items[match.Index])
  94. }
  95. }
  96. p.filtered = fuzzied
  97. p.ClearList()
  98. for _, item := range fuzzied {
  99. p.list.AddItem(item.Text, "", 0, nil)
  100. }
  101. }
  102. func (p *Picker) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
  103. if p.keyMap == nil {
  104. return nil
  105. }
  106. handler := p.list.InputHandler()
  107. switch event.Name() {
  108. case p.keyMap.Up:
  109. handler(tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone), nil)
  110. return nil
  111. case p.keyMap.Down:
  112. handler(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone), nil)
  113. return nil
  114. case p.keyMap.Top:
  115. handler(tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone), nil)
  116. return nil
  117. case p.keyMap.Bottom:
  118. handler(tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone), nil)
  119. case p.keyMap.Select:
  120. handler(tcell.NewEventKey(tcell.KeyEnter, "", tcell.ModNone), nil)
  121. case p.keyMap.Cancel:
  122. if p.onCancel != nil {
  123. p.onCancel()
  124. }
  125. return nil
  126. }
  127. return event
  128. }