Skip to content

Commit

Permalink
Merge pull request #16 from koki-develop/hot-reload
Browse files Browse the repository at this point in the history
  • Loading branch information
koki-develop committed Mar 26, 2023
2 parents 19a92ae + 76ee2cf commit 51381c8
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 22 deletions.
29 changes: 21 additions & 8 deletions fzf.go
@@ -1,6 +1,7 @@
package fzf

import (
"errors"
"fmt"
"reflect"

Expand All @@ -13,7 +14,6 @@ var defaultFindOption = findOption{

// Fuzzy Finder.
type FZF struct {
option *option
model *model
program *tea.Program
}
Expand All @@ -28,7 +28,6 @@ func New(opts ...Option) *FZF {
m := newModel(&o)

return &FZF{
option: &o,
model: m,
program: tea.NewProgram(m),
}
Expand All @@ -42,18 +41,21 @@ func (fzf *FZF) Find(items interface{}, itemFunc func(i int) string, opts ...Fin
}

rv := reflect.ValueOf(items)
switch {
case rv.Kind() == reflect.Slice:
case rv.Kind() == reflect.Ptr && reflect.Indirect(rv).Kind() == reflect.Slice:
default:
return nil, fmt.Errorf("items must be a slice, but got %T", items)
if fzf.model.option.hotReloadLocker == nil {
if rv.Kind() != reflect.Slice {
return nil, fmt.Errorf("items must be a slice, but got %T", items)
}
} else {
if !(rv.Kind() == reflect.Ptr && reflect.Indirect(rv).Kind() == reflect.Slice) {
return nil, fmt.Errorf("items must be a pointer to slice, but got %T", items)
}
}

is, err := newItems(rv, itemFunc)
if err != nil {
return nil, err
}
fzf.model.setItems(is)
fzf.model.loadItems(is)
fzf.model.setFindOption(&findOption)

if _, err := fzf.program.Run(); err != nil {
Expand All @@ -67,6 +69,17 @@ func (fzf *FZF) Find(items interface{}, itemFunc func(i int) string, opts ...Fin
return fzf.model.choices, nil
}

// ForceReload forces the reload of items.
// HotReload must be enabled.
func (fzf *FZF) ForceReload() error {
if fzf.model.option.hotReloadLocker == nil {
return errors.New("hot reload is not enabled")
}

fzf.program.Send(forceReloadMsg{})
return nil
}

// Quit quits the Fuzzy Finder.
func (fzf *FZF) Quit() {
fzf.program.Quit()
Expand Down
68 changes: 54 additions & 14 deletions model.go
Expand Up @@ -2,6 +2,7 @@ package fzf

import (
"strings"
"time"

"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
Expand All @@ -15,6 +16,7 @@ var (

type model struct {
items *items
itemsLen int
option *option
findOption *findOption

Expand Down Expand Up @@ -84,27 +86,27 @@ func newModel(opt *option) *model {
}
}

func (m *model) setItems(items *items) {
var matches matches
for i := 0; i < items.Len(); i++ {
matches = append(matches, match{
Str: items.String(i),
Index: i,
})
}

func (m *model) loadItems(items *items) {
m.items = items
m.matches = matches
m.itemsLen = items.Len()
m.filter()
}

func (m *model) setFindOption(findOption *findOption) {
m.findOption = findOption
}

func (m *model) Init() tea.Cmd {
return tea.Batch(
cmds := []tea.Cmd{
textinput.Blink,
tea.EnterAltScreen,
}
if m.option.hotReloadLocker != nil {
cmds = append(cmds, m.watchReload())
}

return tea.Batch(
cmds...,
)
}

Expand All @@ -113,6 +115,11 @@ func (m *model) Init() tea.Cmd {
*/

func (m *model) View() string {
if m.option.hotReloadLocker != nil {
m.option.hotReloadLocker.Lock()
defer m.option.hotReloadLocker.Unlock()
}

var v strings.Builder

_, _ = v.WriteString(m.headerView())
Expand Down Expand Up @@ -145,8 +152,12 @@ func (m *model) itemsView() string {

headerHeight := m.headerHeight()

for i, match := range m.matches[m.windowYPosition:] {
cursorLine := m.cursorPosition == i+m.windowYPosition
for i, match := range m.matches {
if i < m.windowYPosition {
continue
}

cursorLine := m.cursorPosition == i

// write cursor
if cursorLine {
Expand Down Expand Up @@ -185,7 +196,7 @@ func (m *model) itemsView() string {
}
}

if i+1 >= m.windowHeight-headerHeight {
if i+1-m.windowYPosition >= m.windowHeight-headerHeight {
break
}
v.WriteString("\n")
Expand All @@ -198,7 +209,15 @@ func (m *model) itemsView() string {
* update
*/

type watchReloadMsg struct{}
type forceReloadMsg struct{}

func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.option.hotReloadLocker != nil {
m.option.hotReloadLocker.Lock()
defer m.option.hotReloadLocker.Unlock()
}

switch msg := msg.(type) {
case tea.KeyMsg:
// key
Expand Down Expand Up @@ -232,6 +251,13 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.input.Width = m.windowWidth - m.promptWidth
m.fixYPosition()
m.fixCursor()
case watchReloadMsg:
// watch reload
return m, m.watchReload()
case forceReloadMsg:
// force reload
m.forceReload()
return m, nil
}

var cmds []tea.Cmd
Expand Down Expand Up @@ -336,3 +362,17 @@ func (m *model) fixYPosition() {
return
}
}

func (m *model) forceReload() {
m.loadItems(m.items)
}

func (m *model) watchReload() tea.Cmd {
return tea.Tick(30*time.Millisecond, func(_ time.Time) tea.Msg {
if m.itemsLen != m.items.Len() {
m.loadItems(m.items)
}

return watchReloadMsg{}
})
}
12 changes: 12 additions & 0 deletions option.go
Expand Up @@ -3,6 +3,7 @@ package fzf
import (
"strconv"
"strings"
"sync"

"github.com/charmbracelet/bubbles/key"
)
Expand Down Expand Up @@ -36,6 +37,8 @@ var defaultOption = option{
_, _ = v.WriteString(strings.Repeat("─", max(windowWidth-v.Len(), 0)))
return v.String()
},

hotReloadLocker: nil,
}

type option struct {
Expand All @@ -53,6 +56,8 @@ type option struct {

countViewEnabled bool
countViewFunc func(itemsCount, matchesCount, windowWidth int) string

hotReloadLocker sync.Locker
}

func (o *option) multiple() bool {
Expand Down Expand Up @@ -152,3 +157,10 @@ func WithCountView(f func(itemsCount, matchesCount, windowWidth int) string) Opt
o.countViewFunc = f
}
}

// WithHotReload sets the locker for read items.
func WithHotReload(locker sync.Locker) Option {
return func(o *option) {
o.hotReloadLocker = locker
}
}

0 comments on commit 51381c8

Please sign in to comment.