picker.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package picker
  2. import (
  3. "github.com/ayn2op/tview"
  4. "github.com/ayn2op/tview/flex"
  5. "github.com/ayn2op/tview/keybind"
  6. "github.com/ayn2op/tview/list"
  7. "github.com/gdamore/tcell/v3"
  8. "github.com/sahilm/fuzzy"
  9. )
  10. // bottom border + value
  11. const inputHeight = 2
  12. type Picker struct {
  13. *flex.Model
  14. input *tview.InputField
  15. list *list.Model
  16. keyMap *KeyMap
  17. items Items
  18. filtered Items
  19. }
  20. func New() *Picker {
  21. p := &Picker{
  22. Model: flex.NewModel(),
  23. input: tview.NewInputField(),
  24. list: list.NewModel(),
  25. }
  26. // Show a horizontal bottom border to visually separate input from list.
  27. var borderSet tview.BorderSet
  28. borderSet.Bottom = tview.BoxDrawingsLightHorizontal
  29. borderSet.BottomLeft = borderSet.Bottom
  30. borderSet.BottomRight = borderSet.Bottom
  31. p.input.
  32. SetChangedFunc(p.onInputChanged).
  33. SetLabel("> ").
  34. SetBorders(tview.BordersBottom).
  35. SetBorderSet(borderSet).
  36. SetBorderStyle(tcell.StyleDefault.Dim(true))
  37. p.
  38. SetDirection(flex.DirectionRow).
  39. AddItem(p.input, inputHeight, 0, true).
  40. AddItem(p.list, 0, 1, false)
  41. p.Update()
  42. return p
  43. }
  44. func (p *Picker) setFilteredItems(filtered Items) {
  45. p.filtered = filtered
  46. p.list.SetBuilder(func(index int, cursor int) list.Item {
  47. if index < 0 || index >= len(p.filtered) {
  48. return nil
  49. }
  50. style := tcell.StyleDefault
  51. if index == cursor {
  52. style = style.Reverse(true)
  53. }
  54. return tview.NewTextView().
  55. SetScrollable(false).
  56. SetWrap(false).
  57. SetWordWrap(false).
  58. SetTextStyle(style).
  59. SetLines([]tview.Line{{{Text: p.filtered[index].Text, Style: style}}})
  60. })
  61. if len(filtered) == 0 {
  62. p.list.SetCursor(-1)
  63. } else {
  64. p.list.SetCursor(0)
  65. }
  66. }
  67. func (p *Picker) SetKeyMap(keyMap *KeyMap) {
  68. p.keyMap = keyMap
  69. }
  70. // SetScrollBarVisibility sets when the picker's list scrollBar is rendered.
  71. func (p *Picker) SetScrollBarVisibility(visibility list.ScrollBarVisibility) {
  72. p.list.SetScrollBarVisibility(visibility)
  73. }
  74. // SetScrollBar sets the scrollBar primitive used by the picker's list.
  75. func (p *Picker) SetScrollBar(scrollBar *tview.ScrollBar) {
  76. p.list.SetScrollBar(scrollBar)
  77. }
  78. func (p *Picker) ClearInput() {
  79. p.input.SetText("")
  80. }
  81. func (p *Picker) ClearList() {
  82. p.filtered = nil
  83. p.list.Clear()
  84. }
  85. func (p *Picker) ClearItems() {
  86. p.items = nil
  87. p.filtered = nil
  88. }
  89. func (p *Picker) AddItem(item Item) {
  90. p.items = append(p.items, item)
  91. }
  92. func (p *Picker) Update() {
  93. p.ClearInput()
  94. p.onInputChanged("")
  95. }
  96. func (p *Picker) onInputChanged(text string) {
  97. var fuzzied Items
  98. if text == "" {
  99. fuzzied = append(fuzzied, p.items...)
  100. } else {
  101. matches := fuzzy.FindFrom(text, p.items)
  102. for _, match := range matches {
  103. fuzzied = append(fuzzied, p.items[match.Index])
  104. }
  105. }
  106. p.setFilteredItems(fuzzied)
  107. }
  108. func (p *Picker) HandleEvent(event tcell.Event) tview.Command {
  109. switch event := event.(type) {
  110. case *tview.KeyEvent:
  111. if p.keyMap != nil {
  112. switch {
  113. case keybind.Matches(event, p.keyMap.Up):
  114. p.list.HandleEvent(tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone))
  115. return nil
  116. case keybind.Matches(event, p.keyMap.Down):
  117. p.list.HandleEvent(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone))
  118. return nil
  119. case keybind.Matches(event, p.keyMap.Top):
  120. p.list.HandleEvent(tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone))
  121. return nil
  122. case keybind.Matches(event, p.keyMap.Bottom):
  123. p.list.HandleEvent(tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone))
  124. return nil
  125. case keybind.Matches(event, p.keyMap.Select):
  126. return p._select()
  127. case keybind.Matches(event, p.keyMap.Cancel):
  128. return cancel()
  129. }
  130. }
  131. return p.Model.HandleEvent(event)
  132. }
  133. return p.Model.HandleEvent(event)
  134. }