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