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

View file

@ -0,0 +1,28 @@
package modes
type Mode int
const (
Trashing Mode = iota + 1
Listing
Restoring
Cleaning
Interactive
)
func (m Mode) String() string {
switch m {
case Trashing:
return "Trashing"
case Listing:
return "Listing"
case Restoring:
return "Restoring"
case Cleaning:
return "Cleaning"
case Interactive:
return "Interactive"
default:
return "0"
}
}

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/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