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:
parent
dff6c62e56
commit
6af87e4d9d
175
internal/files/disk.go
Normal file
175
internal/files/disk.go
Normal 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
|
||||
}
|
|
@ -1,186 +1,22 @@
|
|||
// Package files finds and displays files on disk
|
||||
package files
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
import "time"
|
||||
|
||||
"git.burning.moe/celediel/gt/internal/filter"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
name, path string
|
||||
filesize int64
|
||||
modified time.Time
|
||||
isdir bool
|
||||
type File interface {
|
||||
Name() string
|
||||
Path() string
|
||||
Date() time.Time
|
||||
Filesize() int64
|
||||
IsDir() bool
|
||||
}
|
||||
|
||||
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 {
|
||||
if a.modified.After(b.modified) {
|
||||
if a.Date().After(b.Date()) {
|
||||
return 1
|
||||
} else if a.modified.Before(b.modified) {
|
||||
} else if a.Date().Before(b.Date()) {
|
||||
return -1
|
||||
} else {
|
||||
return 0
|
||||
|
@ -188,9 +24,9 @@ func SortByModified(a, b File) int {
|
|||
}
|
||||
|
||||
func SortByModifiedReverse(a, b File) int {
|
||||
if a.modified.Before(b.modified) {
|
||||
if a.Date().Before(b.Date()) {
|
||||
return 1
|
||||
} else if a.modified.After(b.modified) {
|
||||
} else if a.Date().After(b.Date()) {
|
||||
return -1
|
||||
} else {
|
||||
return 0
|
||||
|
@ -198,9 +34,9 @@ func SortByModifiedReverse(a, b File) int {
|
|||
}
|
||||
|
||||
func SortBySize(a, b File) int {
|
||||
if a.filesize > b.filesize {
|
||||
if a.Filesize() > b.Filesize() {
|
||||
return 1
|
||||
} else if a.filesize < b.filesize {
|
||||
} else if a.Filesize() < b.Filesize() {
|
||||
return -1
|
||||
} else {
|
||||
return 0
|
||||
|
@ -208,9 +44,49 @@ func SortBySize(a, b File) int {
|
|||
}
|
||||
|
||||
func SortBySizeReverse(a, b File) int {
|
||||
if a.filesize < b.filesize {
|
||||
if a.Filesize() < b.Filesize() {
|
||||
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
|
||||
} else {
|
||||
return 0
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// Package trash finds and displays files located in the trash, and moves
|
||||
// files into the trash, creating cooresponding .trashinfo files
|
||||
package trash
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -14,6 +12,7 @@ import (
|
|||
"git.burning.moe/celediel/gt/internal/dirs"
|
||||
"git.burning.moe/celediel/gt/internal/filter"
|
||||
"git.burning.moe/celediel/gt/internal/prompt"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/dustin/go-humanize"
|
||||
|
@ -34,7 +33,7 @@ DeletionDate={date}
|
|||
`
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
type TrashInfo struct {
|
||||
name, ogpath string
|
||||
path, trashinfo string
|
||||
isdir bool
|
||||
|
@ -42,17 +41,15 @@ type Info struct {
|
|||
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 (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) {
|
||||
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 {
|
||||
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()) {
|
||||
log.Debugf("%s: deleted on %s", filename, date.Format(trash_info_date_fmt))
|
||||
files = append(files, Info{
|
||||
files = append(files, TrashInfo{
|
||||
name: filename,
|
||||
path: trashedpath,
|
||||
ogpath: basepath,
|
||||
|
@ -108,13 +105,18 @@ func FindFiles(trashdir, ogdir string, f *filter.Filter) (files Infos, outerr er
|
|||
return nil
|
||||
})
|
||||
if outerr != nil {
|
||||
return []Info{}, outerr
|
||||
return Files{}, outerr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Restore(files []Info) (restored int, err error) {
|
||||
for _, file := range files {
|
||||
func Restore(files Files) (restored int, err error) {
|
||||
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 cancel bool
|
||||
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
|
||||
}
|
||||
|
||||
func Remove(files []Info) (removed int, err error) {
|
||||
for _, file := range files {
|
||||
func Remove(files Files) (removed int, err error) {
|
||||
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)
|
||||
if err = os.Remove(file.path); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
out := strings.Builder{}
|
||||
for range length {
|
80
internal/tables/sorting/sorting.go
Normal file
80
internal/tables/sorting/sorting.go
Normal 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
|
||||
}
|
||||
}
|
|
@ -9,8 +9,9 @@ import (
|
|||
|
||||
"git.burning.moe/celediel/gt/internal/dirs"
|
||||
"git.burning.moe/celediel/gt/internal/files"
|
||||
"git.burning.moe/celediel/gt/internal/modes"
|
||||
"git.burning.moe/celediel/gt/internal/trash"
|
||||
"git.burning.moe/celediel/gt/internal/tables/modes"
|
||||
"git.burning.moe/celediel/gt/internal/tables/sorting"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
@ -49,79 +50,19 @@ var (
|
|||
)
|
||||
|
||||
type model struct {
|
||||
table table.Model
|
||||
keys keyMap
|
||||
selected map[int]bool
|
||||
readonly bool
|
||||
termheight int
|
||||
mode modes.Mode
|
||||
subtitle string
|
||||
table table.Model
|
||||
keys keyMap
|
||||
selected map[int]bool
|
||||
readonly bool
|
||||
preselected bool
|
||||
termheight int
|
||||
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 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 {
|
||||
func newModel(fs []files.File, width, height int, readonly, preselected bool, workdir string, 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))
|
||||
|
@ -131,40 +72,18 @@ func newFilesModel(fs files.Files, width, height int, readonly, preselected bool
|
|||
theight int = min(height-hoffset, len(fs))
|
||||
|
||||
m = model{
|
||||
keys: defaultKeyMap(),
|
||||
readonly: readonly,
|
||||
mode: modes.Trashing,
|
||||
selected: map[int]bool{},
|
||||
subtitle: workdir,
|
||||
keys: defaultKeyMap(),
|
||||
readonly: readonly,
|
||||
preselected: preselected,
|
||||
termheight: height,
|
||||
mode: mode,
|
||||
selected: map[int]bool{},
|
||||
workdir: workdir,
|
||||
files: fs,
|
||||
}
|
||||
)
|
||||
|
||||
slices.SortStableFunc(fs, files.SortByModifiedReverse)
|
||||
|
||||
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)
|
||||
}
|
||||
rows := m.makeRows()
|
||||
|
||||
columns := []table.Column{
|
||||
{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.sorting = sorting.Size
|
||||
m.sort()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
|
@ -191,6 +113,8 @@ type keyMap struct {
|
|||
invr key.Binding
|
||||
rstr key.Binding
|
||||
clen key.Binding
|
||||
sort key.Binding
|
||||
rort key.Binding
|
||||
quit key.Binding
|
||||
}
|
||||
|
||||
|
@ -224,6 +148,14 @@ func defaultKeyMap() keyMap {
|
|||
key.WithKeys("r"),
|
||||
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(
|
||||
key.WithKeys("q", "ctrl+c"),
|
||||
key.WithHelp("q", "quit"),
|
||||
|
@ -266,6 +198,16 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.mode = modes.Restoring
|
||||
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)
|
||||
}
|
||||
|
@ -305,6 +247,7 @@ func (m model) readonlyOnePage() bool {
|
|||
func (m model) showHelp() string {
|
||||
// TODO: maybe use bubbletea built in help
|
||||
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)),
|
||||
}
|
||||
if !m.readonly {
|
||||
|
@ -341,8 +284,8 @@ func (m model) header() string {
|
|||
mode = strings.Join(keys, wide_dot)
|
||||
default:
|
||||
mode = m.mode.String()
|
||||
if m.subtitle != "" {
|
||||
mode += fmt.Sprintf(" in %s ", dirs.UnExpand(m.subtitle, ""))
|
||||
if m.workdir != "" {
|
||||
mode += fmt.Sprintf(" in %s ", dirs.UnExpand(m.workdir, ""))
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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() {
|
||||
var rows = make([]table.Row, 0)
|
||||
for _, row := range m.table.Rows() {
|
||||
|
@ -478,8 +448,13 @@ func (m *model) invertSelection() {
|
|||
m.table.SetRows(newrows)
|
||||
}
|
||||
|
||||
func InfoTable(is trash.Infos, width, height int, readonly, preselected bool, mode modes.Mode) ([]int, modes.Mode, error) {
|
||||
if endmodel, err := tea.NewProgram(newInfosModel(is, width, height, readonly, preselected, mode)).Run(); err != nil {
|
||||
func (m *model) sort() {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
if selected {
|
||||
ourcheck = check
|
||||
|
|
57
main.go
57
main.go
|
@ -10,10 +10,9 @@ import (
|
|||
|
||||
"git.burning.moe/celediel/gt/internal/files"
|
||||
"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/tables"
|
||||
"git.burning.moe/celediel/gt/internal/trash"
|
||||
"git.burning.moe/celediel/gt/internal/tables/modes"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/charmbracelet/log"
|
||||
|
@ -101,7 +100,7 @@ var (
|
|||
if len(ctx.Args().Slice()) != 0 {
|
||||
var files_to_trash files.Files
|
||||
for _, arg := range ctx.Args().Slice() {
|
||||
file, e := files.New(arg)
|
||||
file, e := files.NewDisk(arg)
|
||||
if e != nil {
|
||||
log.Fatalf("cannot trash '%s': No such file or directory", arg)
|
||||
}
|
||||
|
@ -144,7 +143,7 @@ var (
|
|||
var files_to_trash files.Files
|
||||
var selectall bool
|
||||
for _, arg := range ctx.Args().Slice() {
|
||||
file, e := files.New(arg)
|
||||
file, e := files.NewDisk(arg)
|
||||
if e != nil {
|
||||
log.Debugf("%s wasn't really a file", arg)
|
||||
f.AddFileName(arg)
|
||||
|
@ -156,7 +155,7 @@ var (
|
|||
|
||||
// if none of the args were files, then process find files based on filter
|
||||
if len(files_to_trash) == 0 {
|
||||
fls, err := files.Find(workdir, recursive, f)
|
||||
fls, err := files.FindDisk(workdir, recursive, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -168,8 +167,7 @@ var (
|
|||
selectall = !f.Blank()
|
||||
}
|
||||
|
||||
log.Debugf("what is workdir? it's %s", workdir)
|
||||
indices, err := tables.FilesTable(files_to_trash, termwidth, termheight, false, selectall, workdir)
|
||||
indices, _, err := tables.Show(files_to_trash, termwidth, termheight, false, selectall, workdir, modes.Trashing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -197,7 +195,8 @@ var (
|
|||
log.Debugf("searching in directory %s for files", trashDir)
|
||||
|
||||
// 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
|
||||
log.Debugf("filter '%s' is blark? %t in %s", f, f.Blank(), ogdir)
|
||||
|
@ -215,7 +214,7 @@ var (
|
|||
}
|
||||
|
||||
// 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
|
||||
},
|
||||
|
@ -231,7 +230,7 @@ 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)
|
||||
if len(fls) == 0 {
|
||||
fmt.Println("no files to restore")
|
||||
return nil
|
||||
|
@ -239,12 +238,12 @@ var (
|
|||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
var selected trash.Infos
|
||||
var selected files.Files
|
||||
for _, i := range indices {
|
||||
selected = append(selected, fls[i])
|
||||
}
|
||||
|
@ -264,7 +263,7 @@ var (
|
|||
Flags: slices.Concat(alreadyintrashFlags, filterFlags),
|
||||
Before: beforeCommands,
|
||||
Action: func(ctx *cli.Context) error {
|
||||
fls, err := trash.FindFiles(trashDir, ogdir, f)
|
||||
fls, err := files.FindTrash(trashDir, ogdir, f)
|
||||
if len(fls) == 0 {
|
||||
fmt.Println("no files to clean")
|
||||
return nil
|
||||
|
@ -272,12 +271,12 @@ var (
|
|||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
var selected trash.Infos
|
||||
var selected files.Files
|
||||
for _, i := range indices {
|
||||
selected = append(selected, fls[i])
|
||||
}
|
||||
|
@ -434,13 +433,13 @@ func main() {
|
|||
|
||||
func interactiveMode() error {
|
||||
var (
|
||||
fls trash.Infos
|
||||
fls files.Files
|
||||
indicies []int
|
||||
mode modes.Mode
|
||||
err error
|
||||
)
|
||||
|
||||
fls, err = trash.FindFiles(trashDir, ogdir, f)
|
||||
fls, err = files.FindTrash(trashDir, ogdir, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -456,12 +455,12 @@ func interactiveMode() error {
|
|||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
var selected trash.Infos
|
||||
var selected files.Files
|
||||
for _, i := range indicies {
|
||||
selected = append(selected, fls[i])
|
||||
}
|
||||
|
@ -489,10 +488,10 @@ func interactiveMode() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func confirmRestore(is trash.Infos) error {
|
||||
if !askconfirm || prompt.YesNo(fmt.Sprintf("restore %d selected files?", len(is))) {
|
||||
func confirmRestore(fs files.Files) error {
|
||||
if !askconfirm || prompt.YesNo(fmt.Sprintf("restore %d selected files?", len(fs))) {
|
||||
log.Info("doing the thing")
|
||||
restored, err := trash.Restore(is)
|
||||
restored, err := files.Restore(fs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("restored %d files before error %s", restored, err)
|
||||
}
|
||||
|
@ -503,11 +502,11 @@ func confirmRestore(is trash.Infos) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func confirmClean(is trash.Infos) error {
|
||||
if prompt.YesNo(fmt.Sprintf("remove %d selected files permanently from the trash?", len(is))) &&
|
||||
(!askconfirm || prompt.YesNo(fmt.Sprintf("really remove all these %d selected files permanently from the trash forever??", len(is)))) {
|
||||
func confirmClean(fs files.Files) error {
|
||||
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(fs)))) {
|
||||
log.Info("gonna remove some files forever")
|
||||
removed, err := trash.Remove(is)
|
||||
removed, err := files.Remove(fs)
|
||||
if err != nil {
|
||||
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))) {
|
||||
tfs := make([]string, 0, len(fs))
|
||||
for _, file := range fs {
|
||||
log.Debugf("gonna trash %s", file.Filename())
|
||||
tfs = append(tfs, file.Filename())
|
||||
log.Debugf("gonna trash %s", file.Path())
|
||||
tfs = append(tfs, file.Path())
|
||||
}
|
||||
|
||||
trashed, err := trash.TrashFiles(trashDir, tfs...)
|
||||
trashed, err := files.TrashFiles(trashDir, tfs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue