Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions filepicker/filepicker.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package filepicker

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
Expand All @@ -25,9 +26,11 @@ func nextID() int {

// New returns a new filepicker model with default styling and key bindings.
func New() Model {
dir, _ := os.Getwd()
return Model{
id: nextID(),
CurrentDirectory: ".",
FS: os.DirFS(dir),
Cursor: ">",
AllowedTypes: []string{},
selected: 0,
Expand Down Expand Up @@ -104,6 +107,7 @@ type Styles struct {
DisabledSelected lipgloss.Style
FileSize lipgloss.Style
EmptyDirectory lipgloss.Style
HelpStyle lipgloss.Style
}

// DefaultStyles defines the default styling for the file picker.
Expand Down Expand Up @@ -133,6 +137,9 @@ type Model struct {
// CurrentDirectory is the directory that the user is currently in.
CurrentDirectory string

// FS is the filesystem the file picker can walk.
FS fs.FS

// AllowedTypes specifies which file types the user may select.
// If empty the user may select any file.
AllowedTypes []string
Expand Down Expand Up @@ -194,9 +201,9 @@ func (m *Model) popView() (int, int, int) {
return m.selectedStack.Pop(), m.minStack.Pop(), m.maxStack.Pop()
}

func (m Model) readDir(path string, showHidden bool) tea.Cmd {
func (m Model) readDir(fysy fs.FS, path string, showHidden bool) tea.Cmd {
return func() tea.Msg {
dirEntries, err := os.ReadDir(path)
dirEntries, err := fs.ReadDir(fysy, path)
if err != nil {
return errorMsg{err}
}
Expand Down Expand Up @@ -239,7 +246,7 @@ func (m Model) Height() int {

// Init initializes the file picker model.
func (m Model) Init() tea.Cmd {
return m.readDir(m.CurrentDirectory, m.ShowHidden)
return m.readDir(m.FS, m.CurrentDirectory, m.ShowHidden)
}

// Update handles user interactions within the file picker model.
Expand Down Expand Up @@ -317,7 +324,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.minIdx = 0
m.maxIdx = m.Height() - 1
}
return m, m.readDir(m.CurrentDirectory, m.ShowHidden)
return m, m.readDir(m.FS, m.CurrentDirectory, m.ShowHidden)
case key.Matches(msg, m.KeyMap.Open):
if len(m.files) == 0 {
break
Expand Down Expand Up @@ -358,7 +365,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.selected = 0
m.minIdx = 0
m.maxIdx = m.Height() - 1
return m, m.readDir(m.CurrentDirectory, m.ShowHidden)
return m, m.readDir(m.FS, m.CurrentDirectory, m.ShowHidden)
}
}
return m, nil
Expand Down
68 changes: 68 additions & 0 deletions filepicker/filepicker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package filepicker

import (
tea "github.com/charmbracelet/bubbletea/v2"
"strings"
"testing"
"testing/fstest"
)

func TestFS(t *testing.T) {
fp := New()
fp.FS = fstest.MapFS{
"bubbles/help.txt": {Data: []byte("1")},
"bubbles/list.txt": {Data: []byte("1")},
"charm.sh": {Data: []byte(" 4")},
"hello.txt": {Data: []byte(" 2")},
"huh.txt": {Data: []byte(" 3")},
}

fp.SetHeight(10)

cmd := fp.Init()
fp, _ = fp.Update(cmd())

lines := strings.Split(fp.View(), "\n")
for lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
expected := []string{"bubbles", "charm.sh", "hello.txt", "huh.txt"}
if len(lines) != len(expected) {
t.Fatalf("len(lines) != len(expected): got %d, want %d", len(lines), len(expected))
}
for i, line := range lines {
contains := expected[i]
if got := line; !strings.Contains(got, contains) {
t.Errorf("View() line %d = %v; must contains %v", i, got, contains)
}
}

expected = []string{"0B", "4B", "2B", "3B"}
if len(lines) != len(expected) {
t.Fatalf("len(lines) != len(expected): got %d, want %d", len(lines), len(expected))
}
for i, line := range lines {
contains := expected[i]
if got := line; !strings.Contains(got, contains) {
t.Errorf("View() line %d = %v; must contains %v", i, got, contains)
}
}

fp, cmd = fp.Update(tea.KeyPressMsg{Code: tea.KeyRight})
fp, _ = fp.Update(cmd())

lines = strings.Split(fp.View(), "\n")
for lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
expected = []string{"help.txt", "list.txt"}
if len(lines) != len(expected) {
t.Fatalf("len(lines) != len(expected): got %d, want %d", len(lines), len(expected))
}
for i, line := range lines {
contains := expected[i]
if got := line; !strings.Contains(got, contains) {
t.Errorf("View() line %d = %v; must contains %v", i, got, contains)
}
}
}