sorting preserves selection state
This commit is contained in:
parent
de81cfbfed
commit
0e147e283b
|
@ -1,6 +1,7 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -31,6 +32,10 @@ func (f DiskFile) Filesize() int64 {
|
|||
return f.filesize
|
||||
}
|
||||
|
||||
func (f DiskFile) String() string {
|
||||
return fmt.Sprintf(string_format, f.name, f.path, f.modified.Format(time.UnixDate), f.filesize, f.isdir)
|
||||
}
|
||||
|
||||
func NewDisk(path string) (DiskFile, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
|
|
|
@ -6,12 +6,15 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
const string_format = "%s %s %s %d %t"
|
||||
|
||||
type File interface {
|
||||
Name() string
|
||||
Path() string
|
||||
Date() time.Time
|
||||
Filesize() int64
|
||||
IsDir() bool
|
||||
String() string
|
||||
}
|
||||
|
||||
type Files []File
|
||||
|
|
|
@ -54,6 +54,10 @@ func (t TrashInfo) Filesize() int64 {
|
|||
return t.filesize
|
||||
}
|
||||
|
||||
func (t TrashInfo) String() string {
|
||||
return fmt.Sprintf(string_format, t.name, t.path, t.trashed.Format(time.UnixDate), t.filesize, t.isdir)
|
||||
}
|
||||
|
||||
func FindTrash(trashdir, ogdir string, f *filter.Filter) (files Files, outerr error) {
|
||||
outerr = filepath.WalkDir(trashdir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
|
|
|
@ -52,9 +52,8 @@ var (
|
|||
type model struct {
|
||||
table table.Model
|
||||
keys keyMap
|
||||
selected map[int]bool
|
||||
selected map[string]bool
|
||||
readonly bool
|
||||
preselected bool
|
||||
termheight int
|
||||
mode modes.Mode
|
||||
sorting sorting.Sorting
|
||||
|
@ -74,16 +73,15 @@ func newModel(fs []files.File, width, height int, readonly, preselected bool, wo
|
|||
m = model{
|
||||
keys: defaultKeyMap(),
|
||||
readonly: readonly,
|
||||
preselected: preselected,
|
||||
termheight: height,
|
||||
mode: mode,
|
||||
selected: map[int]bool{},
|
||||
selected: map[string]bool{},
|
||||
workdir: workdir,
|
||||
files: fs,
|
||||
}
|
||||
)
|
||||
|
||||
rows := m.makeRows()
|
||||
rows := m.freshRows(preselected)
|
||||
|
||||
columns := []table.Column{
|
||||
{Title: "filename", Width: fwidth},
|
||||
|
@ -97,7 +95,7 @@ func newModel(fs []files.File, width, height int, readonly, preselected bool, wo
|
|||
columns[0].Width += cwidth
|
||||
}
|
||||
|
||||
m.table = createTable(columns, rows, theight, m.readonlyOnePage())
|
||||
m.table = createTable(columns, rows, theight)
|
||||
|
||||
m.sorting = sorting.Name
|
||||
m.sort()
|
||||
|
@ -164,9 +162,6 @@ func defaultKeyMap() keyMap {
|
|||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
if m.readonlyOnePage() {
|
||||
return tea.Quit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -199,15 +194,11 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
return m.quit(false)
|
||||
}
|
||||
case key.Matches(msg, m.keys.sort):
|
||||
// if !m.readonly {
|
||||
m.sorting = m.sorting.Next()
|
||||
m.sort()
|
||||
// }
|
||||
case key.Matches(msg, m.keys.rort):
|
||||
// if !m.readonly {
|
||||
m.sorting = m.sorting.Prev()
|
||||
m.sort()
|
||||
// }
|
||||
case key.Matches(msg, m.keys.quit):
|
||||
return m.quit(true)
|
||||
}
|
||||
|
@ -226,11 +217,7 @@ func (m model) View() (out string) {
|
|||
}
|
||||
)
|
||||
|
||||
if m.readonlyOnePage() {
|
||||
n = "\n"
|
||||
} else {
|
||||
panels = append(panels, m.footer())
|
||||
}
|
||||
|
||||
if m.mode != modes.Listing {
|
||||
panels = append([]string{m.header()}, panels...)
|
||||
|
@ -240,10 +227,6 @@ func (m model) View() (out string) {
|
|||
return out + n
|
||||
}
|
||||
|
||||
func (m model) readonlyOnePage() bool {
|
||||
return m.readonly && m.termheight > m.table.Height()
|
||||
}
|
||||
|
||||
func (m model) showHelp() string {
|
||||
// TODO: maybe use bubbletea built in help
|
||||
var keys []string = []string{
|
||||
|
@ -307,27 +290,40 @@ func (m model) quit(unselect_all bool) (model, tea.Cmd) {
|
|||
return m, tea.Quit
|
||||
}
|
||||
|
||||
func (m *model) makeRows() (rows []table.Row) {
|
||||
for j, f := range m.files {
|
||||
func (m model) selectedFiles() (outfile files.Files) {
|
||||
for _, file := range m.files {
|
||||
if m.selected[file.String()] {
|
||||
outfile = append(outfile, file)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newRow(file files.File, workdir string) table.Row {
|
||||
var t, b string
|
||||
t = humanize.Time(f.Date())
|
||||
if f.IsDir() {
|
||||
t = humanize.Time(file.Date())
|
||||
if file.IsDir() {
|
||||
b = strings.Repeat("─", 3)
|
||||
} else {
|
||||
b = humanize.Bytes(uint64(f.Filesize()))
|
||||
b = humanize.Bytes(uint64(file.Filesize()))
|
||||
}
|
||||
r := table.Row{
|
||||
dirs.UnEscape(f.Name()),
|
||||
dirs.UnExpand(filepath.Dir(f.Path()), m.workdir),
|
||||
return table.Row{
|
||||
dirs.UnEscape(file.Name()),
|
||||
dirs.UnExpand(filepath.Dir(file.Path()), workdir),
|
||||
t,
|
||||
b,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) freshRows(preselected bool) (rows []table.Row) {
|
||||
for _, f := range m.files {
|
||||
r := newRow(f, m.workdir)
|
||||
|
||||
if !m.readonly {
|
||||
r = append(r, getCheck(m.preselected))
|
||||
r = append(r, getCheck(preselected))
|
||||
}
|
||||
if m.preselected {
|
||||
m.selected[j] = true
|
||||
if preselected {
|
||||
m.selected[f.String()] = true
|
||||
}
|
||||
rows = append(rows, r)
|
||||
}
|
||||
|
@ -377,20 +373,21 @@ func (m *model) updateRows(selected bool) {
|
|||
m.table.SetRows(newrows)
|
||||
}
|
||||
|
||||
// toggleItem toggles an item's selected state, and returns the state
|
||||
func (m *model) toggleItem(index int) (selected bool) {
|
||||
if m.readonly {
|
||||
return false
|
||||
}
|
||||
|
||||
name := m.files[index].String()
|
||||
|
||||
// select the thing
|
||||
if v, ok := m.selected[index]; v && ok {
|
||||
if v, ok := m.selected[name]; v && ok {
|
||||
// already selected
|
||||
delete(m.selected, index)
|
||||
delete(m.selected, name)
|
||||
selected = false
|
||||
} else {
|
||||
// not selected
|
||||
m.selected[index] = true
|
||||
m.selected[name] = true
|
||||
selected = true
|
||||
}
|
||||
|
||||
|
@ -404,9 +401,9 @@ func (m *model) selectAll() {
|
|||
return
|
||||
}
|
||||
|
||||
m.selected = map[int]bool{}
|
||||
m.selected = map[string]bool{}
|
||||
for i := range len(m.table.Rows()) {
|
||||
m.selected[i] = true
|
||||
m.selected[m.files[i].String()] = true
|
||||
}
|
||||
m.updateRows(true)
|
||||
}
|
||||
|
@ -416,16 +413,21 @@ func (m *model) unselectAll() {
|
|||
return
|
||||
}
|
||||
|
||||
m.selected = map[int]bool{}
|
||||
m.selected = map[string]bool{}
|
||||
m.updateRows(false)
|
||||
}
|
||||
|
||||
func (m *model) invertSelection() {
|
||||
if m.readonly {
|
||||
return
|
||||
}
|
||||
|
||||
var newrows []table.Row
|
||||
|
||||
for index, row := range m.table.Rows() {
|
||||
if v, ok := m.selected[index]; v && ok {
|
||||
delete(m.selected, index)
|
||||
name := m.files[index].String()
|
||||
if v, ok := m.selected[name]; v && ok {
|
||||
delete(m.selected, name)
|
||||
newrows = append(newrows, table.Row{
|
||||
row[0],
|
||||
row[1],
|
||||
|
@ -434,7 +436,7 @@ func (m *model) invertSelection() {
|
|||
getCheck(false),
|
||||
})
|
||||
} else {
|
||||
m.selected[index] = true
|
||||
m.selected[name] = true
|
||||
newrows = append(newrows, table.Row{
|
||||
row[0],
|
||||
row[1],
|
||||
|
@ -450,25 +452,29 @@ func (m *model) invertSelection() {
|
|||
|
||||
func (m *model) sort() {
|
||||
slices.SortStableFunc(m.files, m.sorting.Sorter())
|
||||
m.table.SetRows(m.makeRows())
|
||||
var rows []table.Row
|
||||
for _, file := range m.files {
|
||||
r := newRow(file, m.workdir)
|
||||
if !m.readonly {
|
||||
r = append(r, getCheck(m.selected[file.String()]))
|
||||
}
|
||||
rows = append(rows, r)
|
||||
}
|
||||
|
||||
func Show(fs []files.File, width, height int, readonly, preselected bool, workdir string, mode modes.Mode) ([]int, modes.Mode, error) {
|
||||
if endmodel, err := tea.NewProgram(newModel(fs, width, height, readonly, preselected, workdir, mode)).Run(); err != nil {
|
||||
return []int{}, 0, err
|
||||
} else {
|
||||
m.table.SetRows(rows)
|
||||
}
|
||||
|
||||
func Select(fs files.Files, width, height int, readonly, preselected, once bool, workdir string, mode modes.Mode) (files.Files, modes.Mode, error) {
|
||||
mdl := newModel(fs, width, height, readonly, preselected, once, workdir, mode)
|
||||
endmodel, err := tea.NewProgram(mdl).Run()
|
||||
if err != nil {
|
||||
return fs, 0, err
|
||||
}
|
||||
m, ok := endmodel.(model)
|
||||
if ok {
|
||||
selected := make([]int, 0, len(m.selected))
|
||||
for k := range m.selected {
|
||||
selected = append(selected, k)
|
||||
}
|
||||
|
||||
return selected, m.mode, nil
|
||||
} else {
|
||||
return []int{}, 0, fmt.Errorf("model isn't the right type??")
|
||||
}
|
||||
if !ok {
|
||||
return fs, 0, fmt.Errorf("model isn't the right type?? what has happened")
|
||||
}
|
||||
return m.selectedFiles(), m.mode, nil
|
||||
}
|
||||
|
||||
func getCheck(selected bool) (ourcheck string) {
|
||||
|
@ -480,7 +486,7 @@ func getCheck(selected bool) (ourcheck string) {
|
|||
return
|
||||
}
|
||||
|
||||
func createTable(columns []table.Column, rows []table.Row, height int, readonlyonepage bool) table.Model {
|
||||
func createTable(columns []table.Column, rows []table.Row, height int) table.Model {
|
||||
t := table.New(
|
||||
table.WithColumns(columns),
|
||||
table.WithRows(rows),
|
||||
|
@ -488,11 +494,7 @@ func createTable(columns []table.Column, rows []table.Row, height int, readonlyo
|
|||
table.WithHeight(height),
|
||||
)
|
||||
t.KeyMap = fixTableKeymap()
|
||||
if readonlyonepage {
|
||||
t.SetStyles(makeUnselectedStyle())
|
||||
} else {
|
||||
t.SetStyles(makeStyle())
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
|
|
39
main.go
39
main.go
|
@ -167,16 +167,11 @@ var (
|
|||
selectall = !f.Blank()
|
||||
}
|
||||
|
||||
indices, _, err := tables.Show(files_to_trash, termwidth, termheight, false, selectall, workdir, modes.Trashing)
|
||||
selected, _, err := tables.Select(files_to_trash, termwidth, termheight, false, selectall, false, workdir, modes.Trashing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var selected files.Files
|
||||
for _, i := range indices {
|
||||
selected = append(selected, files_to_trash[i])
|
||||
}
|
||||
|
||||
if len(selected) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -195,7 +190,6 @@ var (
|
|||
log.Debugf("searching in directory %s for files", trashDir)
|
||||
|
||||
// look for files
|
||||
// fls, err := trash.FindFiles(trashDir, ogdir, f)
|
||||
fls, err := files.FindTrash(trashDir, ogdir, f)
|
||||
|
||||
var msg string
|
||||
|
@ -214,7 +208,7 @@ var (
|
|||
}
|
||||
|
||||
// display them
|
||||
_, _, err = tables.Show(fls, termwidth, termheight, true, false, workdir, modes.Listing)
|
||||
_, _, err = tables.Select(fls, termwidth, termheight, true, false, ni, workdir, modes.Listing)
|
||||
|
||||
return err
|
||||
},
|
||||
|
@ -238,16 +232,11 @@ var (
|
|||
return err
|
||||
}
|
||||
|
||||
indices, _, err := tables.Show(fls, termwidth, termheight, false, !f.Blank(), workdir, modes.Restoring)
|
||||
selected, _, err := tables.Select(fls, termwidth, termheight, false, !f.Blank(), ni, workdir, modes.Restoring)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var selected files.Files
|
||||
for _, i := range indices {
|
||||
selected = append(selected, fls[i])
|
||||
}
|
||||
|
||||
if len(selected) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -271,16 +260,11 @@ var (
|
|||
return err
|
||||
}
|
||||
|
||||
indices, _, err := tables.Show(fls, termwidth, termheight, false, !f.Blank(), workdir, modes.Cleaning)
|
||||
selected, _, err := tables.Select(fls, termwidth, termheight, false, !f.Blank(), ni, workdir, modes.Cleaning)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var selected files.Files
|
||||
for _, i := range indices {
|
||||
selected = append(selected, fls[i])
|
||||
}
|
||||
|
||||
if len(selected) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -433,18 +417,18 @@ func main() {
|
|||
|
||||
func interactiveMode() error {
|
||||
var (
|
||||
fls files.Files
|
||||
indicies []int
|
||||
infiles files.Files
|
||||
selected files.Files
|
||||
mode modes.Mode
|
||||
err error
|
||||
)
|
||||
|
||||
fls, err = files.FindTrash(trashDir, ogdir, f)
|
||||
infiles, err = files.FindTrash(trashDir, ogdir, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(fls) <= 0 {
|
||||
if len(infiles) <= 0 {
|
||||
var msg string
|
||||
if f.Blank() {
|
||||
msg = "trash is empty"
|
||||
|
@ -455,16 +439,11 @@ func interactiveMode() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
indicies, mode, err = tables.Show(fls, termwidth, termheight, false, false, workdir, modes.Interactive)
|
||||
selected, mode, err = tables.Select(infiles, termwidth, termheight, false, false, false, workdir, modes.Interactive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var selected files.Files
|
||||
for _, i := range indicies {
|
||||
selected = append(selected, fls[i])
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case modes.Cleaning:
|
||||
for _, file := range selected {
|
||||
|
|
Loading…
Reference in a new issue