package tui import ( "errors" "os" "strings" "time" "git.jingrow.com/charmbracelet/bubbles/filepicker" tea "git.jingrow.com/charmbracelet/bubbletea" "git.jingrow.com/charmbracelet/lipgloss" ) var BUBBLE_TEA_FILEPICKER_STYLE filepicker.Styles = filepicker.DefaultStylesWithRenderer(lipgloss.DefaultRenderer()) func PickFile(prompt string, allowedExtensions []string) (selectedFile string, err error) { fp := filepicker.New() fp.AllowedTypes = allowedExtensions fp.CurrentDirectory, _ = os.UserHomeDir() fp.Styles = BUBBLE_TEA_FILEPICKER_STYLE m := &pickerModel{ filepicker: fp, prompt: prompt, } p := tea.NewProgram(m) if _, err := p.Run(); err != nil { return "", err } // If we have a selected file, return it (regardless of quit status) if m.selectedFile != "" { return m.selectedFile, nil } // No file selected - return empty string return "", nil } // Internal model implementation type pickerModel struct { filepicker filepicker.Model selectedFile string quitting bool err error prompt string } type clearErrorMsg struct{} func clearErrorAfter(t time.Duration) tea.Cmd { return tea.Tick(t, func(_ time.Time) tea.Msg { return clearErrorMsg{} }) } func (m *pickerModel) Init() tea.Cmd { return m.filepicker.Init() } func (m *pickerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "ctrl+q": m.quitting = true return m, tea.Quit } case clearErrorMsg: m.err = nil } var cmd tea.Cmd m.filepicker, cmd = m.filepicker.Update(msg) if didSelect, path := m.filepicker.DidSelectFile(msg); didSelect { m.selectedFile = path m.quitting = true return m, tea.Quit } if didSelect, path := m.filepicker.DidSelectDisabledFile(msg); didSelect { m.err = errors.New(path + " is not valid") m.selectedFile = "" return m, tea.Batch(cmd, clearErrorAfter(2*time.Second)) } return m, cmd } func (m *pickerModel) View() string { if m.quitting { return "" } var s strings.Builder s.WriteString("\n ") if m.err != nil { s.WriteString(titleStyle.Render(m.filepicker.Styles.DisabledFile.Render(m.err.Error()))) } else if m.selectedFile == "" { s.WriteString(titleStyle.Render(m.prompt)) } else { s.WriteString("Selected file: " + m.filepicker.Styles.Selected.Render(m.selectedFile)) } s.WriteString("\n\n" + m.filepicker.View() + "\n") s.WriteString(" to navigate • to select • to quit\n") return s.String() }