charmbracelet / bubbles

TUI components for Bubble Tea 🫧
MIT License
5.35k stars 252 forks source link

Initial loading of Filepicker isn't working #564

Open justin-9rd opened 1 month ago

justin-9rd commented 1 month ago

Describe the bug Working on a tui http client Muninn and I'm using the filepicker bubble to find the .http files on my system. Well when the application initialize I get a "no files found" this isn't the same behaivor I get when working on the example you guys have built. I've rewritten the example using all the same steps I use in my code and it produces list of directory as expected but if I copy that code into my code base it still shows "no files found".

Setup Please complete the following information along with version numbers, if applicable.

To Reproduce Steps to reproduce the behavior:

  1. Clone Muninn
  2. Start muninn go run main.go
  3. Notice that it shows the filepicker is empty
  4. Hit h to go back a directory and see that the filepicker is working fine from there

Source Code Please include source code if needed to reproduce the behavior. muninn filepicker: /internal/tui/filepicker_view.go

package tui

import (
    "errors"
    "strings"
    "time"

    "github.com/charmbracelet/bubbles/filepicker"
    tea "github.com/charmbracelet/bubbletea"
)

type clearErrorMsg struct{}

func clearErrorAfter(t time.Duration) tea.Cmd {
    return tea.Tick(t, func(_ time.Time) tea.Msg {
        return clearErrorMsg{}
    })
}

type filepickerModel struct {
    picker   filepicker.Model
    selected string
    quitting bool
    err      error
}

func initFilepicker(path string) filepickerModel {
    fp := filepicker.New()
    fp.AllowedTypes = []string{".http"}
    fp.CurrentDirectory = path
    fp.ShowPermissions = false
    fp.ShowSize = false
    fp.AutoHeight = false

    return filepickerModel{
        picker: fp,
    }
}

func (m filepickerModel) Init() tea.Cmd {
    return m.picker.Init()
}

func (m filepickerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.WindowSizeMsg:
        m.picker.Height = msg.Height / 4
    case tea.KeyMsg:
        switch msg.String() {
        case "ctrl+c", "q":
            m.quitting = true
            return m, tea.Quit
        }
    case clearErrorMsg:
        m.err = nil
    }

    var cmd tea.Cmd
    m.picker, cmd = m.picker.Update(msg)

    // Did the user select a file?
    if didSelect, path := m.picker.DidSelectFile(msg); didSelect {
        // Get the path of the selected file.
        m.selected = path
    }

    // Did the user select a disabled file?
    // This is only necessary to display an error to the user.
    if didSelect, path := m.picker.DidSelectDisabledFile(msg); didSelect {
        // Let's clear the selectedFile and display an error.
        m.err = errors.New(path + " is not valid.")
        m.selected = ""
        return m, tea.Batch(cmd, clearErrorAfter(2*time.Second))
    }

    return m, cmd
}

func (m filepickerModel) View() string {
    if m.quitting {
        return ""
    }
    var s strings.Builder
    s.WriteString("\n  ")
    if m.err != nil {
        s.WriteString(m.picker.Styles.DisabledFile.Render(m.err.Error()))
    } else if m.selected == "" {
        s.WriteString("Pick a file:")
    } else {
        s.WriteString("Selected file: " + m.picker.Styles.Selected.Render(m.selected))
    }
    s.WriteString("\n\n" + m.picker.View() + "\n")
    return s.String()
}

Minimal standalone file

package main

import (
    "errors"
    "fmt"
    "os"
    "strings"
    "time"

    "github.com/charmbracelet/bubbles/filepicker"
    tea "github.com/charmbracelet/bubbletea"
)

type model struct {
    filepicker   filepicker.Model
    selectedFile string
    quitting     bool
    err          error
}

type clearErrorMsg struct{}

func clearErrorAfter(t time.Duration) tea.Cmd {
    return tea.Tick(t, func(_ time.Time) tea.Msg {
        return clearErrorMsg{}
    })
}

func (m model) Init() tea.Cmd {
    return m.filepicker.Init()
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.WindowSizeMsg:
        m.filepicker.Height = msg.Height / 4
    case tea.KeyMsg:
        switch msg.String() {
        case "ctrl+c", "q":
            m.quitting = true
            return m, tea.Quit
        }
    case clearErrorMsg:
        m.err = nil
    }

    var cmd tea.Cmd
    m.filepicker, cmd = m.filepicker.Update(msg)

    // Did the user select a file?
    if didSelect, path := m.filepicker.DidSelectFile(msg); didSelect {
        // Get the path of the selected file.
        m.selectedFile = path
    }

    // Did the user select a disabled file?
    // This is only necessary to display an error to the user.
    if didSelect, path := m.filepicker.DidSelectDisabledFile(msg); didSelect {
        // Let's clear the selectedFile and display an error.
        m.err = errors.New(path + " is not valid.")
        m.selectedFile = ""
        return m, tea.Batch(cmd, clearErrorAfter(2*time.Second))
    }

    return m, cmd
}

func (m model) View() string {
    if m.quitting {
        return ""
    }
    var s strings.Builder
    s.WriteString("\n  ")
    if m.err != nil {
        s.WriteString(m.filepicker.Styles.DisabledFile.Render(m.err.Error()))
    } else if m.selectedFile == "" {
        s.WriteString("Pick a file:")
    } else {
        s.WriteString("Selected file: " + m.filepicker.Styles.Selected.Render(m.selectedFile))
    }
    s.WriteString("\n\n" + m.filepicker.View() + "\n")
    return s.String()
}

func main() {
    fp := filepicker.New()
    path, _ := os.UserHomeDir()
    fp.AllowedTypes = []string{".http"}
    fp.CurrentDirectory = path
    fp.ShowPermissions = false
    fp.ShowSize = false
    fp.AutoHeight = false

    m := model{
        filepicker: fp,
    }
    tm, _ := tea.NewProgram(&m).Run()
    mm := tm.(model)
    fmt.Println("\n  You selected: " + m.filepicker.Styles.Selected.Render(mm.selectedFile) + "\n")
}

Expected behavior When my application starts I expect the files and directories to appear just like the minimal example

Screenshots What my application renders Screenshot from 2024-07-21 11-27-56

What I expect it to render in the first box Screenshot from 2024-07-21 11-29-03

justin-9rd commented 1 month ago

After some debugging and having to pull the filepicker into my own project I found that I can resolve this issue in my code base by up dated the WindowSize msg check

  case tea.WindowSizeMsg:
    if m.AutoHeight {
        m.Height = msg.Height - marginBottom
    }
    m.max = m.Height - 1
    if len(m.files) == 0 {
        return m, m.readDir(m.CurrentDirectory, m.ShowHidden)
    }

This does not feel like the right solution.

Overall seems that the readDirMsg isn't getting picked up in time as my WindowSizeMsg is begin the first thing I pick up in my logs even though I can see the Init function firing in my logs. Not sure what would be the best fix without understanding how the underlining system is processing the messages