add keybind to sort table

lots of refactoring to do so
files on disk, and files in trash interface'd so table is agnostic
model keeps track of the files now, to facilitate sorting, and updates its own rows accordingly
This commit is contained in:
Lilian Jónsdóttir 2024-07-03 15:05:17 -07:00
parent dff6c62e56
commit 6af87e4d9d
7 changed files with 448 additions and 394 deletions

175
internal/files/disk.go Normal file
View file

@ -0,0 +1,175 @@
package files
import (
"io/fs"
"os"
"path/filepath"
"strings"
"time"
"git.burning.moe/celediel/gt/internal/filter"
"github.com/charmbracelet/log"
"github.com/dustin/go-humanize"
)
type DiskFile struct {
name, path string
filesize int64
modified time.Time
isdir bool
}
func (f DiskFile) Name() string { return f.name }
func (f DiskFile) Path() string { return filepath.Join(f.path, f.name) }
func (f DiskFile) Date() time.Time { return f.modified }
func (f DiskFile) Filesize() int64 { return f.filesize }
func (f DiskFile) IsDir() bool { return f.isdir }
func NewDisk(path string) (DiskFile, error) {
info, err := os.Stat(path)
if err != nil {
return DiskFile{}, err
}
abs, err := filepath.Abs(path)
if err != nil {
log.Errorf("couldn't get absolute path for %s", path)
abs = path
}
name := filepath.Base(abs)
base_path := filepath.Dir(abs)
log.Debugf("%s (base:%s) (size:%s) (modified:%s) exists",
name, base_path, humanize.Bytes(uint64(info.Size())), info.ModTime())
return DiskFile{
name: name,
path: base_path,
filesize: info.Size(),
modified: info.ModTime(),
isdir: info.IsDir(),
}, nil
}
func FindDisk(dir string, recursive bool, f *filter.Filter) (files Files, err error) {
if dir == "." || dir == "" {
var d string
if d, err = os.Getwd(); err != nil {
return
} else {
dir = d
}
}
var recursively string
if recursive {
recursively = " recursively"
}
log.Debugf("gonna find files%s in %s matching %s", recursively, dir, f)
if recursive {
files = append(files, walk_dir(dir, f)...)
} else {
files = append(files, read_dir(dir, f)...)
}
return
}
// is_in_recursive_dir checks `path` and parent directories
// of `path` up to `base` for a hidden parent
func is_in_recursive_dir(base, path string) bool {
me := path
for {
me = filepath.Clean(me)
if me == base {
break
}
if strings.HasPrefix(filepath.Base(me), ".") {
return true
}
me += string(os.PathSeparator) + ".."
}
return false
}
func walk_dir(dir string, f *filter.Filter) (files Files) {
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if dir == path {
return nil
}
if is_in_recursive_dir(dir, path) && !f.IgnoreHidden() {
return nil
}
p, e := filepath.Abs(path)
if e != nil {
return err
}
name := d.Name()
info, _ := d.Info()
if f.Match(name, info.ModTime(), info.Size(), info.IsDir()) {
log.Debugf("found matching file: %s %s", name, info.ModTime())
i, err := os.Stat(p)
if err != nil {
log.Debugf("error in file stat: %s", err)
return nil
}
files = append(files, DiskFile{
path: filepath.Dir(p),
name: name,
filesize: i.Size(),
modified: i.ModTime(),
isdir: i.IsDir(),
})
} else {
log.Debugf("ignoring file %s (%s)", name, info.ModTime())
}
return nil
})
if err != nil {
log.Errorf("error walking directory %s: %s", dir, err)
return Files{}
}
return
}
func read_dir(dir string, f *filter.Filter) (files Files) {
fs, err := os.ReadDir(dir)
if err != nil {
return Files{}
}
for _, file := range fs {
name := file.Name()
if name == dir {
continue
}
info, err := file.Info()
if err != nil {
return Files{}
}
path := filepath.Dir(filepath.Join(dir, name))
if f.Match(name, info.ModTime(), info.Size(), info.IsDir()) {
log.Debugf("found matching file: %s %s", name, info.ModTime())
files = append(files, DiskFile{
name: name,
path: path,
modified: info.ModTime(),
filesize: info.Size(),
isdir: info.IsDir(),
})
} else {
log.Debugf("ignoring file %s (%s)", name, info.ModTime())
}
}
return
}

View file

@ -1,186 +1,22 @@
// Package files finds and displays files on disk // Package files finds and displays files on disk
package files package files
import ( import "time"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
"git.burning.moe/celediel/gt/internal/filter" type File interface {
"github.com/charmbracelet/log" Name() string
"github.com/dustin/go-humanize" Path() string
) Date() time.Time
Filesize() int64
type File struct { IsDir() bool
name, path string
filesize int64
modified time.Time
isdir bool
} }
type Files []File type Files []File
func (f File) Name() string { return f.name }
func (f File) Path() string { return f.path }
func (f File) Filename() string { return filepath.Join(f.path, f.name) }
func (f File) Modified() time.Time { return f.modified }
func (f File) Filesize() int64 { return f.filesize }
func (f File) IsDir() bool { return f.isdir }
func New(path string) (File, error) {
info, err := os.Stat(path)
if err != nil {
return File{}, err
}
abs, err := filepath.Abs(path)
if err != nil {
log.Errorf("couldn't get absolute path for %s", path)
abs = path
}
name := filepath.Base(abs)
base_path := filepath.Dir(abs)
log.Debugf("%s (base:%s) (size:%s) (modified:%s) exists",
name, base_path, humanize.Bytes(uint64(info.Size())), info.ModTime())
return File{
name: name,
path: base_path,
filesize: info.Size(),
modified: info.ModTime(),
isdir: info.IsDir(),
}, nil
}
func Find(dir string, recursive bool, f *filter.Filter) (files Files, err error) {
if dir == "." || dir == "" {
var d string
if d, err = os.Getwd(); err != nil {
return
} else {
dir = d
}
}
var recursively string
if recursive {
recursively = " recursively"
}
log.Debugf("gonna find files%s in %s matching %s", recursively, dir, f)
if recursive {
files = append(files, walk_dir(dir, f)...)
} else {
files = append(files, read_dir(dir, f)...)
}
return
}
// is_in_recursive_dir checks `path` and parent directories
// of `path` up to `base` for a hidden parent
func is_in_recursive_dir(base, path string) bool {
me := path
for {
me = filepath.Clean(me)
if me == base {
break
}
if strings.HasPrefix(filepath.Base(me), ".") {
return true
}
me += string(os.PathSeparator) + ".."
}
return false
}
func walk_dir(dir string, f *filter.Filter) (files Files) {
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if dir == path {
return nil
}
if is_in_recursive_dir(dir, path) && !f.IgnoreHidden() {
return nil
}
p, e := filepath.Abs(path)
if e != nil {
return err
}
name := d.Name()
info, _ := d.Info()
if f.Match(name, info.ModTime(), info.Size(), info.IsDir()) {
log.Debugf("found matching file: %s %s", name, info.ModTime())
i, err := os.Stat(p)
if err != nil {
log.Debugf("error in file stat: %s", err)
return nil
}
files = append(files, File{
path: filepath.Dir(p),
name: name,
filesize: i.Size(),
modified: i.ModTime(),
isdir: i.IsDir(),
})
} else {
log.Debugf("ignoring file %s (%s)", name, info.ModTime())
}
return nil
})
if err != nil {
log.Errorf("error walking directory %s: %s", dir, err)
return []File{}
}
return
}
func read_dir(dir string, f *filter.Filter) (files Files) {
fs, err := os.ReadDir(dir)
if err != nil {
return []File{}
}
for _, file := range fs {
name := file.Name()
if name == dir {
continue
}
info, err := file.Info()
if err != nil {
return []File{}
}
path := filepath.Dir(filepath.Join(dir, name))
if f.Match(name, info.ModTime(), info.Size(), info.IsDir()) {
log.Debugf("found matching file: %s %s", name, info.ModTime())
files = append(files, File{
name: name,
path: path,
modified: info.ModTime(),
filesize: info.Size(),
isdir: info.IsDir(),
})
} else {
log.Debugf("ignoring file %s (%s)", name, info.ModTime())
}
}
return
}
func SortByModified(a, b File) int { func SortByModified(a, b File) int {
if a.modified.After(b.modified) { if a.Date().After(b.Date()) {
return 1 return 1
} else if a.modified.Before(b.modified) { } else if a.Date().Before(b.Date()) {
return -1 return -1
} else { } else {
return 0 return 0
@ -188,9 +24,9 @@ func SortByModified(a, b File) int {
} }
func SortByModifiedReverse(a, b File) int { func SortByModifiedReverse(a, b File) int {
if a.modified.Before(b.modified) { if a.Date().Before(b.Date()) {
return 1 return 1
} else if a.modified.After(b.modified) { } else if a.Date().After(b.Date()) {
return -1 return -1
} else { } else {
return 0 return 0
@ -198,9 +34,9 @@ func SortByModifiedReverse(a, b File) int {
} }
func SortBySize(a, b File) int { func SortBySize(a, b File) int {
if a.filesize > b.filesize { if a.Filesize() > b.Filesize() {
return 1 return 1
} else if a.filesize < b.filesize { } else if a.Filesize() < b.Filesize() {
return -1 return -1
} else { } else {
return 0 return 0
@ -208,9 +44,49 @@ func SortBySize(a, b File) int {
} }
func SortBySizeReverse(a, b File) int { func SortBySizeReverse(a, b File) int {
if a.filesize < b.filesize { if a.Filesize() < b.Filesize() {
return 1 return 1
} else if a.filesize > b.filesize { } else if a.Filesize() > b.Filesize() {
return -1
} else {
return 0
}
}
func SortByName(a, b File) int {
if a.Name() > b.Name() {
return 1
} else if a.Name() < b.Name() {
return -1
} else {
return 0
}
}
func SortByNameReverse(a, b File) int {
if a.Name() < b.Name() {
return 1
} else if a.Name() > b.Name() {
return -1
} else {
return 0
}
}
func SortByPath(a, b File) int {
if a.Path() > b.Path() {
return 1
} else if a.Path() < b.Path() {
return -1
} else {
return 0
}
}
func SortByPathReverse(a, b File) int {
if a.Path() < b.Path() {
return 1
} else if a.Path() > b.Path() {
return -1 return -1
} else { } else {
return 0 return 0

View file

@ -1,6 +1,4 @@
// Package trash finds and displays files located in the trash, and moves package files
// files into the trash, creating cooresponding .trashinfo files
package trash
import ( import (
"fmt" "fmt"
@ -14,6 +12,7 @@ import (
"git.burning.moe/celediel/gt/internal/dirs" "git.burning.moe/celediel/gt/internal/dirs"
"git.burning.moe/celediel/gt/internal/filter" "git.burning.moe/celediel/gt/internal/filter"
"git.burning.moe/celediel/gt/internal/prompt" "git.burning.moe/celediel/gt/internal/prompt"
"github.com/charmbracelet/huh" "github.com/charmbracelet/huh"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
@ -34,7 +33,7 @@ DeletionDate={date}
` `
) )
type Info struct { type TrashInfo struct {
name, ogpath string name, ogpath string
path, trashinfo string path, trashinfo string
isdir bool isdir bool
@ -42,17 +41,15 @@ type Info struct {
filesize int64 filesize int64
} }
type Infos []Info func (t TrashInfo) Name() string { return t.name }
func (t TrashInfo) TrashPath() string { return t.path }
func (t TrashInfo) Path() string { return t.ogpath }
func (t TrashInfo) TrashInfo() string { return t.trashinfo }
func (t TrashInfo) Date() time.Time { return t.trashed }
func (t TrashInfo) Filesize() int64 { return t.filesize }
func (t TrashInfo) IsDir() bool { return t.isdir }
func (i Info) Name() string { return i.name } func FindTrash(trashdir, ogdir string, f *filter.Filter) (files Files, outerr error) {
func (i Info) Path() string { return i.path }
func (i Info) OGPath() string { return i.ogpath }
func (i Info) TrashInfo() string { return i.trashinfo }
func (i Info) Trashed() time.Time { return i.trashed }
func (i Info) Filesize() int64 { return i.filesize }
func (i Info) IsDir() bool { return i.isdir }
func FindFiles(trashdir, ogdir string, f *filter.Filter) (files Infos, 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 {
log.Debugf("what happened?? what is %s?", err) log.Debugf("what happened?? what is %s?", err)
@ -91,7 +88,7 @@ func FindFiles(trashdir, ogdir string, f *filter.Filter) (files Infos, outerr er
if f.Match(filename, date, info.Size(), info.IsDir()) { if f.Match(filename, date, info.Size(), info.IsDir()) {
log.Debugf("%s: deleted on %s", filename, date.Format(trash_info_date_fmt)) log.Debugf("%s: deleted on %s", filename, date.Format(trash_info_date_fmt))
files = append(files, Info{ files = append(files, TrashInfo{
name: filename, name: filename,
path: trashedpath, path: trashedpath,
ogpath: basepath, ogpath: basepath,
@ -108,13 +105,18 @@ func FindFiles(trashdir, ogdir string, f *filter.Filter) (files Infos, outerr er
return nil return nil
}) })
if outerr != nil { if outerr != nil {
return []Info{}, outerr return Files{}, outerr
} }
return return
} }
func Restore(files []Info) (restored int, err error) { func Restore(files Files) (restored int, err error) {
for _, file := range files { for _, maybeFile := range files {
file, ok := maybeFile.(TrashInfo)
if !ok {
return restored, fmt.Errorf("bad file?? %s", maybeFile.Name())
}
var outpath string = dirs.UnEscape(file.ogpath) var outpath string = dirs.UnEscape(file.ogpath)
var cancel bool var cancel bool
log.Infof("restoring %s back to %s\n", file.name, outpath) log.Infof("restoring %s back to %s\n", file.name, outpath)
@ -135,8 +137,13 @@ func Restore(files []Info) (restored int, err error) {
return restored, err return restored, err
} }
func Remove(files []Info) (removed int, err error) { func Remove(files Files) (removed int, err error) {
for _, file := range files { for _, maybeFile := range files {
file, ok := maybeFile.(TrashInfo)
if !ok {
return removed, fmt.Errorf("bad file?? %s", maybeFile.Name())
}
log.Infof("removing %s permanently forever!!!", file.name) log.Infof("removing %s permanently forever!!!", file.name)
if err = os.Remove(file.path); err != nil { if err = os.Remove(file.path); err != nil {
if i, e := os.Stat(file.path); e == nil && i.IsDir() { if i, e := os.Stat(file.path); e == nil && i.IsDir() {
@ -191,46 +198,6 @@ func TrashFiles(trashDir string, files ...string) (trashed int, err error) {
return trashed, err return trashed, err
} }
func SortByTrashed(a, b Info) int {
if a.trashed.After(b.trashed) {
return 1
} else if a.trashed.Before(b.trashed) {
return -1
} else {
return 0
}
}
func SortByTrashedReverse(a, b Info) int {
if a.trashed.Before(b.trashed) {
return 1
} else if a.trashed.After(b.trashed) {
return -1
} else {
return 0
}
}
func SortBySize(a, b Info) int {
if a.filesize > b.filesize {
return 1
} else if a.filesize < b.filesize {
return -1
} else {
return 0
}
}
func SortBySizeReverse(a, b Info) int {
if a.filesize < b.filesize {
return 1
} else if a.filesize > b.filesize {
return -1
} else {
return 0
}
}
func randomFilename(length int) string { func randomFilename(length int) string {
out := strings.Builder{} out := strings.Builder{}
for range length { for range length {

View file

@ -0,0 +1,80 @@
package sorting
import "git.burning.moe/celediel/gt/internal/files"
type Sorting int
const (
Name Sorting = iota + 1
NameReverse
Date
DateReverse
Path
PathReverse
Size
SizeReverse
)
func (s Sorting) Next() Sorting {
switch s {
case SizeReverse:
return Name
default:
return s + 1
}
}
func (s Sorting) Prev() Sorting {
switch s {
case Name:
return SizeReverse
default:
return s - 1
}
}
func (s Sorting) String() string {
switch s {
case Name:
return "name"
case NameReverse:
return "name (r)"
case Date:
return "date"
case DateReverse:
return "date (r)"
case Path:
return "path"
case PathReverse:
return "path (r)"
case Size:
return "size"
case SizeReverse:
return "size (r)"
default:
return "0"
}
}
func (s Sorting) Sorter() func(a, b files.File) int {
switch s {
case Name:
return files.SortByName
case NameReverse:
return files.SortByNameReverse
case Date:
return files.SortByModified
case DateReverse:
return files.SortByModifiedReverse
case Path:
return files.SortByPath
case PathReverse:
return files.SortByPathReverse
case Size:
return files.SortBySize
case SizeReverse:
return files.SortBySizeReverse
default:
return files.SortByName
}
}

View file

@ -9,8 +9,9 @@ import (
"git.burning.moe/celediel/gt/internal/dirs" "git.burning.moe/celediel/gt/internal/dirs"
"git.burning.moe/celediel/gt/internal/files" "git.burning.moe/celediel/gt/internal/files"
"git.burning.moe/celediel/gt/internal/modes" "git.burning.moe/celediel/gt/internal/tables/modes"
"git.burning.moe/celediel/gt/internal/trash" "git.burning.moe/celediel/gt/internal/tables/sorting"
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
@ -49,79 +50,19 @@ var (
) )
type model struct { type model struct {
table table.Model table table.Model
keys keyMap keys keyMap
selected map[int]bool selected map[int]bool
readonly bool readonly bool
termheight int preselected bool
mode modes.Mode termheight int
subtitle string mode modes.Mode
sorting sorting.Sorting
workdir string
files files.Files
} }
// TODO: reconcile trash.Info and files.File into an interface so I can shorten this up func newModel(fs []files.File, width, height int, readonly, preselected bool, workdir string, mode modes.Mode) model {
func newInfosModel(is trash.Infos, width, height int, readonly, preselected bool, mode modes.Mode) model {
var (
fwidth int = int(math.Round(float64(width-woffset) * 0.4))
owidth int = int(math.Round(float64(width-woffset) * 0.2))
dwidth int = int(math.Round(float64(width-woffset) * 0.25))
swidth int = int(math.Round(float64(width-woffset) * 0.12))
cwidth int = int(math.Round(float64(width-woffset) * 0.03))
theight int = min(height-hoffset, len(is))
m = model{
keys: defaultKeyMap(),
readonly: readonly,
termheight: height,
mode: mode,
selected: map[int]bool{},
}
)
slices.SortStableFunc(is, trash.SortByTrashedReverse)
rows := []table.Row{}
for j, i := range is {
var t, b string
t = humanize.Time(i.Trashed())
if i.IsDir() {
b = strings.Repeat("─", 3)
} else {
b = humanize.Bytes(uint64(i.Filesize()))
}
r := table.Row{
dirs.UnEscape(i.Name()),
dirs.UnExpand(filepath.Dir(i.OGPath()), ""),
t,
b,
}
if !m.readonly {
r = append(r, getCheck(preselected))
}
if preselected {
m.selected[j] = true
}
rows = append(rows, r)
}
columns := []table.Column{
{Title: "filename", Width: fwidth},
{Title: "original path", Width: owidth},
{Title: "deleted", Width: dwidth},
{Title: "size", Width: swidth},
}
if !m.readonly {
columns = append(columns, table.Column{Title: uncheck, Width: cwidth})
} else {
columns[0].Width += cwidth
}
m.table = createTable(columns, rows, theight, m.readonlyOnePage())
return m
}
func newFilesModel(fs files.Files, width, height int, readonly, preselected bool, workdir string) model {
var ( var (
fwidth int = int(math.Round(float64(width-woffset) * 0.4)) fwidth int = int(math.Round(float64(width-woffset) * 0.4))
owidth int = int(math.Round(float64(width-woffset) * 0.2)) owidth int = int(math.Round(float64(width-woffset) * 0.2))
@ -131,40 +72,18 @@ func newFilesModel(fs files.Files, width, height int, readonly, preselected bool
theight int = min(height-hoffset, len(fs)) theight int = min(height-hoffset, len(fs))
m = model{ m = model{
keys: defaultKeyMap(), keys: defaultKeyMap(),
readonly: readonly, readonly: readonly,
mode: modes.Trashing, preselected: preselected,
selected: map[int]bool{}, termheight: height,
subtitle: workdir, mode: mode,
selected: map[int]bool{},
workdir: workdir,
files: fs,
} }
) )
slices.SortStableFunc(fs, files.SortByModifiedReverse) rows := m.makeRows()
rows := []table.Row{}
for j, f := range fs {
var t, b string
t = humanize.Time(f.Modified())
if f.IsDir() {
b = strings.Repeat("─", 3)
} else {
b = humanize.Bytes(uint64(f.Filesize()))
}
r := table.Row{
dirs.UnEscape(f.Name()),
dirs.UnExpand(f.Path(), workdir),
t,
b,
}
if !m.readonly {
r = append(r, getCheck(preselected))
}
if preselected {
m.selected[j] = true
}
rows = append(rows, r)
}
columns := []table.Column{ columns := []table.Column{
{Title: "filename", Width: fwidth}, {Title: "filename", Width: fwidth},
@ -180,6 +99,9 @@ func newFilesModel(fs files.Files, width, height int, readonly, preselected bool
m.table = createTable(columns, rows, theight, m.readonlyOnePage()) m.table = createTable(columns, rows, theight, m.readonlyOnePage())
m.sorting = sorting.Size
m.sort()
return m return m
} }
@ -191,6 +113,8 @@ type keyMap struct {
invr key.Binding invr key.Binding
rstr key.Binding rstr key.Binding
clen key.Binding clen key.Binding
sort key.Binding
rort key.Binding
quit key.Binding quit key.Binding
} }
@ -224,6 +148,14 @@ func defaultKeyMap() keyMap {
key.WithKeys("r"), key.WithKeys("r"),
key.WithHelp("r", "restore"), key.WithHelp("r", "restore"),
), ),
sort: key.NewBinding(
key.WithKeys("s"),
key.WithHelp("s", "sort"),
),
rort: key.NewBinding(
key.WithKeys("S"),
key.WithHelp("S", "change sort (reverse)"),
),
quit: key.NewBinding( quit: key.NewBinding(
key.WithKeys("q", "ctrl+c"), key.WithKeys("q", "ctrl+c"),
key.WithHelp("q", "quit"), key.WithHelp("q", "quit"),
@ -266,6 +198,16 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.mode = modes.Restoring m.mode = modes.Restoring
return m.quit(false) 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): case key.Matches(msg, m.keys.quit):
return m.quit(true) return m.quit(true)
} }
@ -305,6 +247,7 @@ func (m model) readonlyOnePage() bool {
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{
fmt.Sprintf("%s %s (%s)", darktext.Render(m.keys.sort.Help().Key), darkertext.Render(m.keys.sort.Help().Desc), m.sorting.String()),
fmt.Sprintf("%s %s", darktext.Render(m.keys.quit.Help().Key), darkertext.Render(m.keys.quit.Help().Desc)), fmt.Sprintf("%s %s", darktext.Render(m.keys.quit.Help().Key), darkertext.Render(m.keys.quit.Help().Desc)),
} }
if !m.readonly { if !m.readonly {
@ -341,8 +284,8 @@ func (m model) header() string {
mode = strings.Join(keys, wide_dot) mode = strings.Join(keys, wide_dot)
default: default:
mode = m.mode.String() mode = m.mode.String()
if m.subtitle != "" { if m.workdir != "" {
mode += fmt.Sprintf(" in %s ", dirs.UnExpand(m.subtitle, "")) mode += fmt.Sprintf(" in %s ", dirs.UnExpand(m.workdir, ""))
} }
} }
mode += fmt.Sprintf(" %s %s", dot, strings.Join(select_keys, wide_dot)) mode += fmt.Sprintf(" %s %s", dot, strings.Join(select_keys, wide_dot))
@ -364,6 +307,33 @@ 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) {
for j, f := range m.files {
var t, b string
t = humanize.Time(f.Date())
if f.IsDir() {
b = strings.Repeat("─", 3)
} else {
b = humanize.Bytes(uint64(f.Filesize()))
}
r := table.Row{
dirs.UnEscape(f.Name()),
dirs.UnExpand(filepath.Dir(f.Path()), m.workdir),
t,
b,
}
if !m.readonly {
r = append(r, getCheck(m.preselected))
}
if m.preselected {
m.selected[j] = true
}
rows = append(rows, r)
}
return
}
func (m *model) onlySelected() { func (m *model) onlySelected() {
var rows = make([]table.Row, 0) var rows = make([]table.Row, 0)
for _, row := range m.table.Rows() { for _, row := range m.table.Rows() {
@ -478,8 +448,13 @@ func (m *model) invertSelection() {
m.table.SetRows(newrows) m.table.SetRows(newrows)
} }
func InfoTable(is trash.Infos, width, height int, readonly, preselected bool, mode modes.Mode) ([]int, modes.Mode, error) { func (m *model) sort() {
if endmodel, err := tea.NewProgram(newInfosModel(is, width, height, readonly, preselected, mode)).Run(); err != nil { slices.SortStableFunc(m.files, m.sorting.Sorter())
m.table.SetRows(m.makeRows())
}
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 return []int{}, 0, err
} else { } else {
m, ok := endmodel.(model) m, ok := endmodel.(model)
@ -496,24 +471,6 @@ func InfoTable(is trash.Infos, width, height int, readonly, preselected bool, mo
} }
} }
func FilesTable(fs files.Files, width, height int, readonly, preselected bool, workdir string) ([]int, error) {
if endmodel, err := tea.NewProgram(newFilesModel(fs, width, height, readonly, preselected, workdir)).Run(); err != nil {
return []int{}, err
} else {
m, ok := endmodel.(model)
if ok {
selected := make([]int, 0, len(m.selected))
for k := range m.selected {
selected = append(selected, k)
}
return selected, nil
} else {
return []int{}, fmt.Errorf("model isn't the right type??")
}
}
}
func getCheck(selected bool) (ourcheck string) { func getCheck(selected bool) (ourcheck string) {
if selected { if selected {
ourcheck = check ourcheck = check

57
main.go
View file

@ -10,10 +10,9 @@ import (
"git.burning.moe/celediel/gt/internal/files" "git.burning.moe/celediel/gt/internal/files"
"git.burning.moe/celediel/gt/internal/filter" "git.burning.moe/celediel/gt/internal/filter"
"git.burning.moe/celediel/gt/internal/modes"
"git.burning.moe/celediel/gt/internal/prompt" "git.burning.moe/celediel/gt/internal/prompt"
"git.burning.moe/celediel/gt/internal/tables" "git.burning.moe/celediel/gt/internal/tables"
"git.burning.moe/celediel/gt/internal/trash" "git.burning.moe/celediel/gt/internal/tables/modes"
"github.com/adrg/xdg" "github.com/adrg/xdg"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
@ -101,7 +100,7 @@ var (
if len(ctx.Args().Slice()) != 0 { if len(ctx.Args().Slice()) != 0 {
var files_to_trash files.Files var files_to_trash files.Files
for _, arg := range ctx.Args().Slice() { for _, arg := range ctx.Args().Slice() {
file, e := files.New(arg) file, e := files.NewDisk(arg)
if e != nil { if e != nil {
log.Fatalf("cannot trash '%s': No such file or directory", arg) log.Fatalf("cannot trash '%s': No such file or directory", arg)
} }
@ -144,7 +143,7 @@ var (
var files_to_trash files.Files var files_to_trash files.Files
var selectall bool var selectall bool
for _, arg := range ctx.Args().Slice() { for _, arg := range ctx.Args().Slice() {
file, e := files.New(arg) file, e := files.NewDisk(arg)
if e != nil { if e != nil {
log.Debugf("%s wasn't really a file", arg) log.Debugf("%s wasn't really a file", arg)
f.AddFileName(arg) f.AddFileName(arg)
@ -156,7 +155,7 @@ var (
// if none of the args were files, then process find files based on filter // if none of the args were files, then process find files based on filter
if len(files_to_trash) == 0 { if len(files_to_trash) == 0 {
fls, err := files.Find(workdir, recursive, f) fls, err := files.FindDisk(workdir, recursive, f)
if err != nil { if err != nil {
return err return err
} }
@ -168,8 +167,7 @@ var (
selectall = !f.Blank() selectall = !f.Blank()
} }
log.Debugf("what is workdir? it's %s", workdir) indices, _, err := tables.Show(files_to_trash, termwidth, termheight, false, selectall, workdir, modes.Trashing)
indices, err := tables.FilesTable(files_to_trash, termwidth, termheight, false, selectall, workdir)
if err != nil { if err != nil {
return err return err
} }
@ -197,7 +195,8 @@ 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 := trash.FindFiles(trashDir, ogdir, f)
fls, err := files.FindTrash(trashDir, ogdir, f)
var msg string var msg string
log.Debugf("filter '%s' is blark? %t in %s", f, f.Blank(), ogdir) log.Debugf("filter '%s' is blark? %t in %s", f, f.Blank(), ogdir)
@ -215,7 +214,7 @@ var (
} }
// display them // display them
_, _, err = tables.InfoTable(fls, termwidth, termheight, true, false, modes.Listing) _, _, err = tables.Show(fls, termwidth, termheight, true, false, workdir, modes.Listing)
return err return err
}, },
@ -231,7 +230,7 @@ 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)
if len(fls) == 0 { if len(fls) == 0 {
fmt.Println("no files to restore") fmt.Println("no files to restore")
return nil return nil
@ -239,12 +238,12 @@ var (
return err return err
} }
indices, _, err := tables.InfoTable(fls, termwidth, termheight, false, !f.Blank(), modes.Restoring) indices, _, err := tables.Show(fls, termwidth, termheight, false, !f.Blank(), workdir, modes.Restoring)
if err != nil { if err != nil {
return err return err
} }
var selected trash.Infos var selected files.Files
for _, i := range indices { for _, i := range indices {
selected = append(selected, fls[i]) selected = append(selected, fls[i])
} }
@ -264,7 +263,7 @@ var (
Flags: slices.Concat(alreadyintrashFlags, filterFlags), Flags: slices.Concat(alreadyintrashFlags, filterFlags),
Before: beforeCommands, Before: beforeCommands,
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
fls, err := trash.FindFiles(trashDir, ogdir, f) fls, err := files.FindTrash(trashDir, ogdir, f)
if len(fls) == 0 { if len(fls) == 0 {
fmt.Println("no files to clean") fmt.Println("no files to clean")
return nil return nil
@ -272,12 +271,12 @@ var (
return err return err
} }
indices, _, err := tables.InfoTable(fls, termwidth, termheight, false, !f.Blank(), modes.Cleaning) indices, _, err := tables.Show(fls, termwidth, termheight, false, !f.Blank(), workdir, modes.Cleaning)
if err != nil { if err != nil {
return err return err
} }
var selected trash.Infos var selected files.Files
for _, i := range indices { for _, i := range indices {
selected = append(selected, fls[i]) selected = append(selected, fls[i])
} }
@ -434,13 +433,13 @@ func main() {
func interactiveMode() error { func interactiveMode() error {
var ( var (
fls trash.Infos fls files.Files
indicies []int indicies []int
mode modes.Mode mode modes.Mode
err error err error
) )
fls, err = trash.FindFiles(trashDir, ogdir, f) fls, err = files.FindTrash(trashDir, ogdir, f)
if err != nil { if err != nil {
return err return err
} }
@ -456,12 +455,12 @@ func interactiveMode() error {
return nil return nil
} }
indicies, mode, err = tables.InfoTable(fls, termwidth, termheight, false, false, modes.Interactive) indicies, mode, err = tables.Show(fls, termwidth, termheight, false, false, workdir, modes.Interactive)
if err != nil { if err != nil {
return err return err
} }
var selected trash.Infos var selected files.Files
for _, i := range indicies { for _, i := range indicies {
selected = append(selected, fls[i]) selected = append(selected, fls[i])
} }
@ -489,10 +488,10 @@ func interactiveMode() error {
return nil return nil
} }
func confirmRestore(is trash.Infos) error { func confirmRestore(fs files.Files) error {
if !askconfirm || prompt.YesNo(fmt.Sprintf("restore %d selected files?", len(is))) { if !askconfirm || prompt.YesNo(fmt.Sprintf("restore %d selected files?", len(fs))) {
log.Info("doing the thing") log.Info("doing the thing")
restored, err := trash.Restore(is) restored, err := files.Restore(fs)
if err != nil { if err != nil {
return fmt.Errorf("restored %d files before error %s", restored, err) return fmt.Errorf("restored %d files before error %s", restored, err)
} }
@ -503,11 +502,11 @@ func confirmRestore(is trash.Infos) error {
return nil return nil
} }
func confirmClean(is trash.Infos) error { func confirmClean(fs files.Files) error {
if prompt.YesNo(fmt.Sprintf("remove %d selected files permanently from the trash?", len(is))) && if prompt.YesNo(fmt.Sprintf("remove %d selected files permanently from the trash?", len(fs))) &&
(!askconfirm || prompt.YesNo(fmt.Sprintf("really remove all these %d selected files permanently from the trash forever??", len(is)))) { (!askconfirm || prompt.YesNo(fmt.Sprintf("really remove all these %d selected files permanently from the trash forever??", len(fs)))) {
log.Info("gonna remove some files forever") log.Info("gonna remove some files forever")
removed, err := trash.Remove(is) removed, err := files.Remove(fs)
if err != nil { if err != nil {
return fmt.Errorf("removed %d files before error %s", removed, err) return fmt.Errorf("removed %d files before error %s", removed, err)
} }
@ -522,11 +521,11 @@ func confirmTrash(fs files.Files) error {
if !askconfirm || prompt.YesNo(fmt.Sprintf("trash %d selected files?", len(fs))) { if !askconfirm || prompt.YesNo(fmt.Sprintf("trash %d selected files?", len(fs))) {
tfs := make([]string, 0, len(fs)) tfs := make([]string, 0, len(fs))
for _, file := range fs { for _, file := range fs {
log.Debugf("gonna trash %s", file.Filename()) log.Debugf("gonna trash %s", file.Path())
tfs = append(tfs, file.Filename()) tfs = append(tfs, file.Path())
} }
trashed, err := trash.TrashFiles(trashDir, tfs...) trashed, err := files.TrashFiles(trashDir, tfs...)
if err != nil { if err != nil {
return err return err
} }