Skip to content

Commit

Permalink
feat: full delete support
Browse files Browse the repository at this point in the history
  • Loading branch information
ALX99 committed May 21, 2024
1 parent 674ffa5 commit 55afe77
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 86 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ go install github.com/alx99/sail/cmd/sail@latest
- [x] LS_COLORS support
- [x] Customizable keybindings
- [x] *Sail* into directories
- [x] Delete files (*partial* support)
- [x] Delete files
- [x] Select files
- [ ] Move files
- [ ] Copy files
Expand Down
49 changes: 31 additions & 18 deletions internal/models/main_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path"
"slices"
"strings"
"sync"
"time"

"github.com/alx99/sail/internal/config"
Expand Down Expand Up @@ -46,6 +47,10 @@ type Model struct {
maxRows int // the maximum number of rows to display
lastError error // last error that occurred

// lock since I do weird things such as updating
// arrays/maps outside of the update function
*sync.RWMutex

// for performance purposes
sb strings.Builder
}
Expand All @@ -58,6 +63,7 @@ func NewMain(cwd string, cfg config.Config) Model {
cachedDirSelections: make(map[string]string, 100),
selectedFiles: make(map[string]any, 100),
sb: strings.Builder{},
RWMutex: &sync.RWMutex{},
}
}

Expand Down Expand Up @@ -154,9 +160,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if len(m.files) <= 0 {
return m, nil
}
return m, sequentially(
return m, tea.Sequence(
func() tea.Msg {
return osi.RemoveAll(path.Join(m.cwd, m.currFile().Name()))
return m.do(func(path string) error { return osi.RemoveAll(path) })
},
m.loadDir(m.cwd),
)
Expand All @@ -165,6 +171,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if len(m.files) <= 0 {
return m, nil
}
m.Lock()
defer m.Unlock()

fName := path.Join(m.cwd, m.currFile().Name())
if _, ok := m.selectedFiles[fName]; ok {
Expand Down Expand Up @@ -404,27 +412,32 @@ func (m Model) goUp(wrap bool) Model {
}

func (m Model) isSelected(name string) bool {
m.RLock()
_, ok := m.selectedFiles[path.Join(m.cwd, name)]
m.RUnlock()
return ok
}

func (m Model) currFile() fs.DirEntry {
return m.files[m.cursorOffset()]
}
// do runs the given function on all selected files, or the file under the cursor
// if no files are selected. If the function is successful, the selected files are
// removed from the selected files map.
func (m Model) do(do func(string) error) error {
m.Lock()
defer m.Unlock()
if len(m.selectedFiles) == 0 {
return do(path.Join(m.cwd, m.currFile().Name()))
}

// sequentially produces a command that sequentially executes the given
// commands.
// The tea.Msg returned is the first non-nil message returned by a Cmd.
func sequentially(cmds ...tea.Cmd) tea.Cmd {
return func() tea.Msg {
for _, cmd := range cmds {
if cmd == nil {
continue
}
if msg := cmd(); msg != nil {
return msg
}
for f := range m.selectedFiles {
if err := do(f); err != nil {
return err
}
return nil
delete(m.selectedFiles, f)
}

return nil
}

func (m Model) currFile() fs.DirEntry {
return m.files[m.cursorOffset()]
}
88 changes: 21 additions & 67 deletions internal/models/main_model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"
"slices"
"strings"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -444,9 +445,6 @@ func TestModel_Update(t *testing.T) {
{
name: "Delete last file in a column",
fields: fields{
cfg: config.Config{
Settings: config.Settings{Keymap: config.Keymap{Delete: "d"}},
},
cwd: "/test",
files: []fs.DirEntry{
dirEntry{name: "file1", isDir: false},
Expand All @@ -456,32 +454,20 @@ func TestModel_Update(t *testing.T) {
cachedDirSelections: map[string]string{},
maxRows: 1,
},
mocks: mocks{
fs: mockOS{},
},
args: args{
msg: tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}},
msg: dirLoaded{"/test", []fs.DirEntry{dirEntry{name: "file1", isDir: false}}},
},
wantFunc: func(m Model) Model {
m.cursor = position{}
m.files = []fs.DirEntry{dirEntry{name: "file1", isDir: false}}

return m
},
wantMsgs: []tea.Msg{
dirLoaded{
path: "/test",
files: []fs.DirEntry{dirEntry{name: "file1", isDir: false}},
},
},
filterMsgs: []tea.Msg{clearPrevCWD{}},
},
{
name: "Delete last file in a row",
fields: fields{
cfg: config.Config{
Settings: config.Settings{Keymap: config.Keymap{Delete: "d"}},
},
cwd: "/test",
files: []fs.DirEntry{
dirEntry{name: "file1", isDir: false},
Expand All @@ -492,34 +478,22 @@ func TestModel_Update(t *testing.T) {
cachedDirSelections: map[string]string{},
maxRows: 3,
},
mocks: mocks{
fs: mockOS{},
},
args: args{
msg: tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}},
msg: dirLoaded{"/test", []fs.DirEntry{
dirEntry{name: "file1", isDir: false},
dirEntry{name: "file2", isDir: false},
}},
},
wantFunc: func(m Model) Model {
m.cursor = position{r: 1, c: 0}
m.files = slices.Delete(m.files, 2, 3)
return m
},
wantMsgs: []tea.Msg{
dirLoaded{
path: "/test",
files: []fs.DirEntry{
dirEntry{name: "file1", isDir: false},
dirEntry{name: "file2", isDir: false},
},
},
},
filterMsgs: []tea.Msg{clearPrevCWD{}},
},
{
name: "Delete last file in a column 2",
fields: fields{
cfg: config.Config{
Settings: config.Settings{Keymap: config.Keymap{Delete: "d"}},
},
cwd: "/test",
files: []fs.DirEntry{
dirEntry{name: "file1", isDir: false},
Expand All @@ -530,34 +504,22 @@ func TestModel_Update(t *testing.T) {
cachedDirSelections: map[string]string{},
maxRows: 2,
},
mocks: mocks{
fs: mockOS{},
},
args: args{
msg: tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}},
msg: dirLoaded{"/test", []fs.DirEntry{
dirEntry{name: "file1", isDir: false},
dirEntry{name: "file2", isDir: false},
}},
},
wantFunc: func(m Model) Model {
m.cursor = position{r: 1, c: 0}
m.files = slices.Delete(m.files, 2, 3)
return m
},
wantMsgs: []tea.Msg{
dirLoaded{
path: "/test",
files: []fs.DirEntry{
dirEntry{name: "file1", isDir: false},
dirEntry{name: "file2", isDir: false},
},
},
},
filterMsgs: []tea.Msg{clearPrevCWD{}},
},
{
name: "Delete file in the middle",
fields: fields{
cfg: config.Config{
Settings: config.Settings{Keymap: config.Keymap{Delete: "d"}},
},
cwd: "/test",
files: []fs.DirEntry{
dirEntry{name: "file1", isDir: false},
Expand All @@ -574,32 +536,23 @@ func TestModel_Update(t *testing.T) {
cachedDirSelections: map[string]string{},
maxRows: 3,
},
mocks: mocks{
fs: mockOS{},
},
args: args{
msg: tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}},
msg: dirLoaded{"/test", []fs.DirEntry{
dirEntry{name: "file1", isDir: false},
dirEntry{name: "file2", isDir: false},
dirEntry{name: "file3", isDir: false},
dirEntry{name: "file4", isDir: false},
dirEntry{name: "file6", isDir: false},
dirEntry{name: "file7", isDir: false},
dirEntry{name: "file8", isDir: false},
dirEntry{name: "file9", isDir: false},
}},
},
wantFunc: func(m Model) Model {
m.cursor = position{r: 1, c: 1}
m.files = slices.Delete(m.files, 4, 5)
return m
},
wantMsgs: []tea.Msg{
dirLoaded{
path: "/test",
files: []fs.DirEntry{
dirEntry{name: "file1", isDir: false},
dirEntry{name: "file2", isDir: false},
dirEntry{name: "file3", isDir: false},
dirEntry{name: "file4", isDir: false},
dirEntry{name: "file6", isDir: false},
dirEntry{name: "file7", isDir: false},
dirEntry{name: "file8", isDir: false},
dirEntry{name: "file9", isDir: false},
},
},
},
filterMsgs: []tea.Msg{clearPrevCWD{}},
},
{
Expand Down Expand Up @@ -730,6 +683,7 @@ func TestModel_Update(t *testing.T) {
lastError: tt.fields.lastError,
selectedFiles: tt.fields.selectedFiles,
}
m.RWMutex = &sync.RWMutex{}

if mock, ok := tt.mocks.fs.(mockOS); ok {
mock.addFromModel(m)
Expand Down

0 comments on commit 55afe77

Please sign in to comment.