Skip to content

Commit

Permalink
Merge pull request #397 from rusq/i391
Browse files Browse the repository at this point in the history
Display current directory in file picker
  • Loading branch information
rusq authored Jan 9, 2025
2 parents 9b1b33e + 85e3e14 commit a3e8bf0
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 31 deletions.
8 changes: 7 additions & 1 deletion cmd/slackdump/internal/apiconfig/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,20 @@ func CheckFile(filename string) error {
}

func wizConfigCheck(ctx context.Context, cmd *base.Command, args []string) error {
f := filemgr.New(os.DirFS("."), ".", 15, ConfigExts...)
cwd, err := os.Getwd()
if err != nil {
return err
}

f := filemgr.New(os.DirFS(cwd), cwd, ".", 15, ConfigExts...)
f.Focus()
f.ShowHelp = true
f.Style = filemgr.Style{
Normal: ui.DefaultTheme().Focused.UnselectedFile,
Directory: ui.DefaultTheme().Focused.Directory,
Inverted: ui.DefaultTheme().Focused.SelectedFile,
Shaded: ui.DefaultTheme().Focused.DisabledFile,
CurDir: ui.DefaultTheme().Focused.Description,
}
vp := viewport.New(80-filemgr.Width, f.Height)
vp.Style = lipgloss.NewStyle().Margin(0, 2)
Expand Down
120 changes: 95 additions & 25 deletions cmd/slackdump/internal/ui/bubbles/filemgr/filemgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io/fs"
"log/slog"
"path/filepath"
"runtime"
"strings"
"time"

Expand All @@ -16,18 +17,20 @@ import (
)

type Model struct {
Globs []string
Selected string
FS fs.FS
Directory string
Height int
ShowHelp bool
Style Style
files []fs.FileInfo
finished bool
focus bool
st display.State
viewStack display.Stack[display.State]
Globs []string
Selected string
FS fs.FS
BaseDir string
Directory string
Height int
ShowHelp bool
ShowCurDir bool
Style Style
files []fs.FileInfo
finished bool
focus bool
st display.State
viewStack display.Stack[display.State]

Debug bool
last string // last key pressed
Expand All @@ -38,6 +41,7 @@ type Style struct {
Directory lipgloss.Style
Inverted lipgloss.Style
Shaded lipgloss.Style
CurDir lipgloss.Style
}

// Messages
Expand All @@ -54,17 +58,25 @@ type (
}
)

func New(fsys fs.FS, dir string, height int, globs ...string) Model {
// New creates a new file manager model over the filesystem fsys. The base
// directory is what will be displayed in the file manager. The dir is the
// current directory within the fsys. The height is the number of lines to
// display. The globs are the file globs to display.
func New(fsys fs.FS, base string, dir string, height int, globs ...string) Model {
return Model{
Globs: globs,
FS: fsys,
Directory: dir,
Height: height,
focus: false,
Globs: globs,
FS: fsys,
Directory: dir,
BaseDir: base,
Height: height,
focus: false,
ShowCurDir: true,
// Sensible defaults
Style: Style{
Normal: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
Directory: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
Inverted: lipgloss.NewStyle().Foreground(lipgloss.Color("7")).Background(lipgloss.Color("240")),
CurDir: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
},
}
}
Expand All @@ -83,19 +95,19 @@ func (m Model) Init() tea.Cmd {
}

func readFS(fsys fs.FS, dir string, globs ...string) (wmReadDir, error) {
sub, err := fs.Sub(fsys, dir)
sub, err := fs.Sub(fsys, toFSpath(dir))
if err != nil {
return wmReadDir{}, err
return wmReadDir{}, fmt.Errorf("sub: %w", err)
}
dirs, err := collectDirs(sub)
if err != nil {
return wmReadDir{}, err
return wmReadDir{}, fmt.Errorf("collect dirs: %w", err)
}
files, err := collectFiles(sub, globs...)
if err != nil {
return wmReadDir{}, err
return wmReadDir{}, fmt.Errorf("collectFiles: %w", err)
}
if !(dir == "." || dir == "/" || dir == "") {
if dir != "." && dir != string(filepath.Separator) && dir != "" {
files = append([]fs.FileInfo{specialDir{".."}}, files...)
}
return wmReadDir{dir, append(files, dirs...)}, nil
Expand Down Expand Up @@ -289,7 +301,7 @@ func printFile(fi fs.FileInfo) string {
filenameSz = Width - filesizeSz - dttmSz - 3
)

var sz = dirMarker
sz := dirMarker
if !fi.IsDir() {
sz = humanizeSize(fi.Size())
}
Expand Down Expand Up @@ -321,10 +333,20 @@ func (m Model) View() string {
if m.Debug {
m.printDebug(&buf)
}
if m.ShowCurDir {
buf.WriteString(
m.Style.CurDir.Render(
fmt.Sprintf("DIR: %s", m.shorten(filepath.Join(
filepath.Clean(m.BaseDir),
filepath.Clean(m.Directory),
))),
) + "\n",
)
}
if len(m.files) == 0 {
buf.WriteString(m.Style.Normal.Render("No files found, press [Backspace]") + "\n")
for i := 0; i < m.height()-1; i++ {
fmt.Fprintln(&buf, m.Style.Normal.Render(strings.Repeat(" ", Width-1))) //padding
fmt.Fprintln(&buf, m.Style.Normal.Render(strings.Repeat(" ", Width-1))) // padding
}
} else {
for i, file := range m.files {
Expand Down Expand Up @@ -407,3 +429,51 @@ func (s specialDir) IsDir() bool {
func (s specialDir) Sys() interface{} {
return s
}

// shorten returns a shortened version of a path.
func (m Model) shorten(dirpath string) string {
dirpath = filepath.Clean(dirpath)
if len(dirpath) < Width-1 {
return dirpath
}
dirpath = filepath.Clean(dirpath)
// split the path into parts
parts := strings.Split(dirpath, string(filepath.Separator))
var s []string
if len(parts) < 2 {
return dirpath
}
for i := 0; i < len(parts)-1; i++ {
if len(parts[i]) == 0 {
s = append(s, "")
continue
}
if strings.HasSuffix(parts[i], ":") {
s = append(s, parts[i])
continue
}
s = append(s, string(parts[i][0]))
}
s = append(s, parts[len(parts)-1])
if runtime.GOOS == "windows" {
s[1] = "\\" + s[1]
}
res := filepath.Join(s...)
if dirpath[0] == '/' {
res = "/" + res
}
if len(res) > Width-1 {
res = "…" + res[len(res)-Width+3:]
}
return res
}

var pathrepfn = strings.NewReplacer(string(filepath.Separator), "/").Replace

func toFSpath(p string) string {
if runtime.GOOS != "windows" {
// already '/'
return p
}
return pathrepfn(p)
}
110 changes: 109 additions & 1 deletion cmd/slackdump/internal/ui/bubbles/filemgr/filemgr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import (
"bytes"
"io/fs"
"reflect"
"runtime"
"slices"
"testing"
"testing/fstest"

"github.com/rusq/rbubbles/display"

"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -390,3 +390,111 @@ func TestModel_printDebug(t *testing.T) {
})
}
}

func TestModel_shorten(t *testing.T) {
type args struct {
dirpath string
}
tests := []struct {
name string
windows bool
args args
want string
}{
{
name: "very short path",
args: args{
dirpath: "/",
},
want: "/",
},
{
name: "longer path",
args: args{
dirpath: "/home/user/Downloads/Funky/Long/Path/Longer/Than/40/Characters",
},
want: "/h/u/D/F/L/P/L/T/4/Characters",
},
{
name: "really long path",
args: args{
dirpath: "/home/user/Downloads/Funky/Long/Path/Longer/Than/40/Characters/And/Even/Longer/Than/That/And/Then/Some/More/And/Even/Longer/Than/That/And/Then/Some",
},
want: "…/A/E/L/T/T/A/T/S/M/A/E/L/T/T/A/T/Some",
},
{
name: "windows",
windows: true,
args: args{
dirpath: "D:\\Users\\User\\Downloads",
},
want: "D:\\Users\\User\\Downloads",
},
{
name: "very long windows path",
windows: true,
args: args{
dirpath: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Some Funky\\Path That\\Nobody In Sane\\Mind Can\\Remember\\Or Type\\Without Making\\Over 9000\\Typos",
},
want: "C:\\P\\M\\2\\C\\S\\P\\N\\M\\R\\O\\W\\O\\Typos",
},
{
name: "longer than width",
windows: true,
args: args{
dirpath: "C:\\P\\M\\2\\C\\S\\P\\N\\M\\R\\O\\W\\O\\T\\S\\F\\K\\L\\M\\N\\O\\P\\Q\\R\\",
},
want: "…S\\P\\N\\M\\R\\O\\W\\O\\T\\S\\F\\K\\L\\M\\N\\O\\P\\Q\\R",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (runtime.GOOS == "windows") != tt.windows {
t.Skip("skipping test on non-windows OS")
}
m := Model{}
if got := m.shorten(tt.args.dirpath); got != tt.want {
t.Errorf("Model.shorten() = %v, want %v", got, tt.want)
}
})
}
}

func Test_toFSpath(t *testing.T) {
type args struct {
p string
}
tests := []struct {
name string
windows bool
args args
want string
}{
{
name: "updates path on windows",
windows: true,
args: args{
p: "C:\\Program Files\\Microsoft Office 95",
},
want: "C:/Program Files/Microsoft Office 95",
},
{
name: "returns as is on non-windows",
windows: false,
args: args{
p: "/var/spool/mail/root",
},
want: "/var/spool/mail/root",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (runtime.GOOS == "windows") != tt.windows {
t.Skip("skipping")
}
if got := toFSpath(tt.args.p); got != tt.want {
t.Errorf("toFSpath() = %v, want %v", got, tt.want)
}
})
}
}
5 changes: 3 additions & 2 deletions cmd/slackdump/internal/ui/cfgui/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/rusq/rbubbles/filemgr"

"github.com/rusq/slackdump/v3/cmd/slackdump/internal/apiconfig"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/bubbles/filemgr"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/updaters"
)

Expand All @@ -31,6 +31,7 @@ type Parameter struct {
}

func globalConfig() Configuration {
cwd, _ := os.Getwd()
return Configuration{
{
Name: "Timeframe",
Expand Down Expand Up @@ -82,7 +83,7 @@ func globalConfig() Configuration {
Description: "API limits file",
Updater: updaters.NewFilepickModel(
&cfg.ConfigFile,
filemgr.New(os.DirFS("."), ".", 15, apiconfig.ConfigExts...),
filemgr.New(os.DirFS(cwd), cwd, ".", 15, apiconfig.ConfigExts...),
validateAPIconfig,
),
},
Expand Down
4 changes: 3 additions & 1 deletion cmd/slackdump/internal/ui/updaters/filepick.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import (

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/rusq/rbubbles/filemgr"

"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/bubbles/filemgr"
)

type FilepickModel struct {
Expand All @@ -25,6 +26,7 @@ func NewFilepickModel(ptrStr *string, f filemgr.Model, validateFn func(s string)
Normal: ui.DefaultTheme().Focused.UnselectedFile,
Directory: ui.DefaultTheme().Focused.Directory,
Inverted: ui.DefaultTheme().Focused.SelectedFile,
CurDir: ui.DefaultTheme().Focused.Description,
}
return FilepickModel{
fp: f,
Expand Down
Loading

0 comments on commit a3e8bf0

Please sign in to comment.