Compare commits

...

10 commits

8 changed files with 668 additions and 94 deletions

View file

@ -1,19 +1,29 @@
# gt # gt
g(o)t(rash) or something like that
small cli for xdg trash specification g(o)t(rash) is a simple, command line program to interface with the XDG Trash. Files in the trash may be listed, cleaned, or restored via an interactive table, and filtered with various flags.
## commands ## Interactive Mode
Run with no args to start interactive mode. In interactive mode, files in the trash are displayed, and may be selected to either restore or remove permanently.
## rm-like Trashing
Run with no command and only filename(s) as argument(s) to skip displaying files, sending them straight to the trash, in a quick, rm-like way.
## Commands
Files are displayed in an interactive table, allowing them to be sorted, filtered, and selectively operated on.
### trash / tr ### trash / tr
moves files to trash Find files on the filesystem based on the filter flags and any filename args.
### list / ls ### list / ls
list files in trash Find files in the trash based on the filter flags and any filename args.
### restore / re ### restore / re
restore files from trash Find files in the trash based on the filter flags and any filename args.
### clean / cl ### clean / cl
clean files out of trash Find files in the trash based on the filter flags and any filename args.
See gt(1) or `gt --help` for more in depth information on all command line flags.

13
go.mod
View file

@ -6,12 +6,13 @@ require (
github.com/adrg/xdg v0.5.0 github.com/adrg/xdg v0.5.0
github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.26.6 github.com/charmbracelet/bubbletea v0.26.6
github.com/charmbracelet/huh v0.5.1 github.com/charmbracelet/huh v0.5.2
github.com/charmbracelet/lipgloss v0.12.1 github.com/charmbracelet/lipgloss v0.12.1
github.com/charmbracelet/log v0.4.0 github.com/charmbracelet/log v0.4.0
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/ijt/go-anytime v1.9.2 github.com/ijt/go-anytime v1.9.2
github.com/urfave/cli/v2 v2.27.2 github.com/lithammer/fuzzysearch v1.1.8
github.com/urfave/cli/v2 v2.27.3
gitlab.com/tymonx/go-formatter v1.5.1 gitlab.com/tymonx/go-formatter v1.5.1
golang.org/x/term v0.22.0 golang.org/x/term v0.22.0
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
@ -22,8 +23,8 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.2.0 // indirect github.com/catppuccin/go v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.1.4 // indirect github.com/charmbracelet/x/ansi v0.1.4 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240715153702-9ba8adf781c4 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240725160154-f9f6568126ec // indirect
github.com/charmbracelet/x/input v0.1.2 // indirect github.com/charmbracelet/x/input v0.1.3 // indirect
github.com/charmbracelet/x/term v0.1.1 // indirect github.com/charmbracelet/x/term v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.1.2 // indirect github.com/charmbracelet/x/windows v0.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
@ -33,7 +34,7 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
@ -42,7 +43,7 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/exp v0.0.0-20240716160929-1d5bc16f04a8 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.16.0 // indirect

55
go.sum
View file

@ -10,20 +10,20 @@ github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s= github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s=
github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk= github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk=
github.com/charmbracelet/huh v0.5.1 h1:t5j6g9sMjAE2a9AQuc4lNL7pf/0X4WdHiiMGkL8v/aM= github.com/charmbracelet/huh v0.5.2 h1:ofeNkJ4iaFnzv46Njhx896DzLUe/j0L2QAf8znwzX4c=
github.com/charmbracelet/huh v0.5.1/go.mod h1:gs7b2brpzXkY0PBWUqJrlzvOowTCL0vNAR6OTItc+kA= github.com/charmbracelet/huh v0.5.2/go.mod h1:Sf7dY0oAn6N/e3sXJFtFX9hdQLrUdO3z7AYollG9bAM=
github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs= github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs=
github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=
github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/strings v0.0.0-20240715153702-9ba8adf781c4 h1:MuGfwxVD0M9WHNVHUBDs4UneZcvcIJyihHlffXcC3FY= github.com/charmbracelet/x/exp/strings v0.0.0-20240725160154-f9f6568126ec h1:G79PnmxvVeo5FbAip3VPqAaBkOeXExwvhIBUfrXD5MA=
github.com/charmbracelet/x/exp/strings v0.0.0-20240715153702-9ba8adf781c4/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= github.com/charmbracelet/x/exp/strings v0.0.0-20240725160154-f9f6568126ec/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/exp/term v0.0.0-20240524151031-ff83003bf67a h1:k/s6UoOSVynWiw7PlclyGO2VdVs5ZLbMIHiGp4shFZE= github.com/charmbracelet/x/exp/term v0.0.0-20240524151031-ff83003bf67a h1:k/s6UoOSVynWiw7PlclyGO2VdVs5ZLbMIHiGp4shFZE=
github.com/charmbracelet/x/exp/term v0.0.0-20240524151031-ff83003bf67a/go.mod h1:YBotIGhfoWhHDlnUpJMkjebGV2pdGRCn1Y4/Nk/vVcU= github.com/charmbracelet/x/exp/term v0.0.0-20240524151031-ff83003bf67a/go.mod h1:YBotIGhfoWhHDlnUpJMkjebGV2pdGRCn1Y4/Nk/vVcU=
github.com/charmbracelet/x/input v0.1.2 h1:QJAZr33eOhDowkkEQ24rsJy4Llxlm+fRDf/cQrmqJa0= github.com/charmbracelet/x/input v0.1.3 h1:oy4TMhyGQsYs/WWJwu1ELUMFnjiUAXwtDf048fHbCkg=
github.com/charmbracelet/x/input v0.1.2/go.mod h1:LGBim0maUY4Pitjn/4fHnuXb4KirU3DODsyuHuXdOyA= github.com/charmbracelet/x/input v0.1.3/go.mod h1:1gaCOyw1KI9e2j00j/BBZ4ErzRZqa05w0Ghn83yIhKU=
github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI= github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI=
github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw= github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw=
github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg= github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg=
@ -45,6 +45,8 @@ github.com/ijt/go-anytime v1.9.2 h1:DmYgVwUiFPNR+n6c1T5P070tlGATRZG4aYNJs6XDUfU=
github.com/ijt/go-anytime v1.9.2/go.mod h1:egBT6FhVjNlXNHUN2wTPi6ILCNKXeeXFy04pWJjw/LI= github.com/ijt/go-anytime v1.9.2/go.mod h1:egBT6FhVjNlXNHUN2wTPi6ILCNKXeeXFy04pWJjw/LI=
github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d h1:LFOmpWrSbtolg0YqYC9hQjj5WSLtRGb6aZ3JAugLfgg= github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d h1:LFOmpWrSbtolg0YqYC9hQjj5WSLtRGb6aZ3JAugLfgg=
github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d/go.mod h1:112TOyA+aruNSUBlyBWlKBdLVYTdhjiO2CKD0j/URSU= github.com/ijt/goparsify v0.0.0-20221203142333-3a5276334b8d/go.mod h1:112TOyA+aruNSUBlyBWlKBdLVYTdhjiO2CKD0j/URSU=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
@ -52,8 +54,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
@ -75,35 +77,60 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160 h1:NSWpaDaurcAJY7PkL8Xt0PhZE7qpvbZl5ljd8r6U0bI= github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160 h1:NSWpaDaurcAJY7PkL8Xt0PhZE7qpvbZl5ljd8r6U0bI=
github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v2 v2.27.3 h1:/POWahRmdh7uztQ3CYnaDddk0Rm90PyOgIxgW2rr41M=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= github.com/urfave/cli/v2 v2.27.3/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/tymonx/go-formatter v1.5.1 h1:gmn5rJqR6LlI1DkpBmiCo0MZ3ges581A14GZcXlGe60= gitlab.com/tymonx/go-formatter v1.5.1 h1:gmn5rJqR6LlI1DkpBmiCo0MZ3ges581A14GZcXlGe60=
gitlab.com/tymonx/go-formatter v1.5.1/go.mod h1:z1E064wx+cgg5ChY+1E+hrJf8uKY//kF1Q1As+w5WRQ= gitlab.com/tymonx/go-formatter v1.5.1/go.mod h1:z1E064wx+cgg5ChY+1E+hrJf8uKY//kF1Q1As+w5WRQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240716160929-1d5bc16f04a8 h1:Z+vTUQyBb738QmIhbJx3z4htsxDeI+rd0EHvNm8jHkg= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/exp v0.0.0-20240716160929-1d5bc16f04a8/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

238
gt.1 Normal file
View file

@ -0,0 +1,238 @@
.\" Generated by scdoc 1.11.3
.\" Complete documentation for this program is not available as a GNU info page
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.nh
.ad l
.\" Begin generated content:
.TH "gt" "1" "2024-07-31" "gt version v0.0.1" "User Commands"
.PP
.SH NAME
.PP
gt - manual page for gt version 0.\&0.\&1
.PP
.SH DESCRIPTION
.PP
.SS NAME:
.PP
gt - Simple command line XDG Trash application
.PP
.SS USAGE:
.PP
gt [global options] [command [command options] / filename(s)]
.PP
.SS INFO:
.PP
g(o)t(rash) is a simple, command line program to interface with the XDG Trash.\& Files in the trash may be listed, cleaned, or restored via an interactive table, and filtered with various flags.\&
.PP
.SS VERSION:
.PP
0.\&0.\&1
.PP
.SS AUTHOR:
.PP
Lilian Jónsdóttir <lilian.\&jonsdottir@gmail.\&com>
.PP
.SH INTERACTIVE MODE
.PP
Run with no args to start interactive mode.\& In interactive mode, files in the trash are displayed, and may be selected to either restore or remove permanently.\&
.PP
.SH RM-LIKE TRASHING
.PP
Run with no command and only filename(s) as argument(s) to skip displaying files, sending them straight to the trash, in a quick, rm-like way.\&
.PP
.SH COMMANDS
.PP
.SS TRASH:
\fIcommand\fR: trash, tr
.RS 4
Trash a file or files
.PP
.RE
\fIusage\fR:
.RS 4
trash [command options] [filename(s)]
.PP
.RE
\fIinfo\fR:
.RS 4
The trash command finds files on the filesystem based on the filter flags and any filename args, and displays them in an interactive table, allowing them to be sorted, filtered, and selectively trashed.\&
.PP
.RE
\fIflags\fR:
.RS 4
\fB--recursive\fR, \fB-r\fR
.RS 4
operate on files recursively
.PP
.RE
\fB--work-dir\fR dir, \fB-w\fR dir
.RS 4
operate on files in this `DIRECTORY`
.PP
.RE
\fB--hidden\fR, \fB-h\fR
.RS 4
operate on hidden files
.PP
.RE
.RE
.SS LIST:
\fIcommand\fR: list, ls
.RS 4
List trashed files
.PP
.RE
\fIusage\fR:
.RS 4
list [command options] [filename(s)]
.PP
.RE
\fIinfo\fR:
.RS 4
The list command finds files in the trash based on the filter flags and any filename args, and displays them in an interactive table, allowing them to be sorted, filtered.\&
.PP
.RE
\fIflags:\fR
.RS 4
\fB--non-interactive\fR, \fB-n\fR
.RS 4
list files and quit
.PP
.RE
\fB--original-path\fR dir, \fB-O\fR dir
.RS 4
list files trashed from this directory
.PP
.RE
.RE
.SS RESTORE:
\fIcommand\fR: restore, re
.RS 4
Restore a trashed file or files
.PP
.RE
\fIusage\fR:
.RS 4
restore [command options] [filename(s)]
.PP
.RE
\fIinfo\fR:
.RS 4
The restore command finds files in the trash based on the filter flags and any filename args, and displays them in an interactive table, allowing them to be sorted, filtered, and selectively restored.\&
.PP
.RE
\fIflags:\fR
.RS 4
\fB--all\fR, \fB-a\fR
.RS 4
operate on all files in trash
.PP
.RE
\fB--original-path\fR dir, \fB-O\fR dir
.RS 4
restore files trashed from this directory
.PP
.RE
.RE
.SS CLEAN:
\fIcommand\fR: clean, cl
.RS 4
Clean files from trash
.PP
.RE
\fIusage\fR:
.RS 4
clean [command options] [filename(s)]
.PP
.RE
\fIinfo\fR:
.RS 4
The clean command finds files in the trash based on the filter flags and any filename args, and displays them in an interactive table, allowing them to be sorted, filtered, and selectively removed permanently.\&
.PP
.RE
\fIflags:\fR
.RS 4
\fB--all\fR, \fB-a\fR
.RS 4
operate on all files in trash
.PP
.RE
\fB--original-path\fR dir, \fB-O\fR dir
.RS 4
remove files trashed from this directory
.PP
.RE
.RE
.SH GLOBAL FLAGS
.PP
\fB--confirm\fR, \fB-c\fR
.RS 4
ask for confirmation before executing any action
.PP
.RE
\fB--log\fR level, \fB-l\fR level
.RS 4
set log level
.PP
.RE
.SH FILTER FLAGS (USABLE WITH ALL COMMANDS)
.PP
\fB--match\fR pattern, \fB-m\fR pattern
.RS 4
operate on files matching regex pattern
.PP
.RE
\fB--glob\fR pattern, \fB-m\fR pattern
.RS 4
operate on files matching glob
.PP
.RE
\fB--not-match\fR pattern, \fB-M\fR pattern
.RS 4
operate on files not matching regex pattern
.PP
.RE
\fB--not-glob\fR pattern, \fB-G\fR pattern
.RS 4
operate on files not matching glob
.PP
.RE
\fB--on\fR date, \fB-O\fR date
.RS 4
operate on files modified on date
.PP
.RE
\fB--before\fR date, \fB-B\fR date
.RS 4
operate on files modified before date
.PP
.RE
\fB--after\fR date, \fB-A\fR date
.RS 4
operate on files modified after date
.PP
.RE
\fB--files-only\fR, \fB-F\fR
.RS 4
operate on files only
.PP
.RE
\fB--dirs-only\fR, \fB-D\fR
.RS 4
operate on directories only
.PP
.RE
\fB--min-size\fR size, \fB-N\fR size
.RS 4
operate on files larger than size
.PP
.RE
\fB--max-size\fR size, \fB-X\fR size
.RS 4
operate on files smaller than size
.PP
.RE
\fB--mode\fR mode, \fB-x\fR mode
.RS 4
operate on files matching mode mode

154
gt.1.scd Normal file
View file

@ -0,0 +1,154 @@
gt(1) ["gt version v0.0.1" ["User Commands"]]
# NAME
gt \- manual page for gt version 0.0.1
# DESCRIPTION
## NAME:
gt \- Simple command line XDG Trash application
## USAGE:
gt [global options] [command [command options] / filename(s)]
## INFO:
g(o)t(rash) is a simple, command line program to interface with the XDG Trash. Files in the trash may be listed, cleaned, or restored via an interactive table, and filtered with various flags.
## VERSION:
0.0.1
## AUTHOR:
Lilian Jónsdóttir <lilian.jonsdottir@gmail.com>
# INTERACTIVE MODE
Run with no args to start interactive mode. In interactive mode, files in the trash are displayed, and may be selected to either restore or remove permanently.
# RM-LIKE TRASHING
Run with no command and only filename(s) as argument(s) to skip displaying files, sending them straight to the trash, in a quick, rm-like way.
# COMMANDS
## TRASH:
_command_: trash, tr
Trash a file or files
_usage_:
trash [command options] [filename(s)]
_info_:
The trash command finds files on the filesystem based on the filter flags and any filename args, and displays them in an interactive table, allowing them to be sorted, filtered, and selectively trashed.
_flags_:
*--recursive*, *-r*
operate on files recursively
*--work-dir* dir, *-w* dir
operate on files in this `DIRECTORY`
*--hidden*, *-h*
operate on hidden files
## LIST:
_command_: list, ls
List trashed files
_usage_:
list [command options] [filename(s)]
_info_:
The list command finds files in the trash based on the filter flags and any filename args, and displays them in an interactive table, allowing them to be sorted, filtered.
_flags:_
*--non-interactive*, *-n*
list files and quit
*--original-path* dir, *-O* dir
list files trashed from this directory
## RESTORE:
_command_: restore, re
Restore a trashed file or files
_usage_:
restore [command options] [filename(s)]
_info_:
The restore command finds files in the trash based on the filter flags and any filename args, and displays them in an interactive table, allowing them to be sorted, filtered, and selectively restored.
_flags:_
*--all*, *-a*
operate on all files in trash
*--original-path* dir, *-O* dir
restore files trashed from this directory
## CLEAN:
_command_: clean, cl
Clean files from trash
_usage_:
clean [command options] [filename(s)]
_info_:
The clean command finds files in the trash based on the filter flags and any filename args, and displays them in an interactive table, allowing them to be sorted, filtered, and selectively removed permanently.
_flags:_
*--all*, *-a*
operate on all files in trash
*--original-path* dir, *-O* dir
remove files trashed from this directory
# GLOBAL FLAGS
*--confirm*, *-c*
ask for confirmation before executing any action
*--log* level, *-l* level
set log level
# FILTER FLAGS (USABLE WITH ALL COMMANDS)
*--match* pattern, *-m* pattern
operate on files matching regex pattern
*--glob* pattern, *-m* pattern
operate on files matching glob
*--not-match* pattern, *-M* pattern
operate on files not matching regex pattern
*--not-glob* pattern, *-G* pattern
operate on files not matching glob
*--on* date, *-O* date
operate on files modified on date
*--before* date, *-B* date
operate on files modified before date
*--after* date, *-A* date
operate on files modified after date
*--files-only*, *-F*
operate on files only
*--dirs-only*, *-D*
operate on directories only
*--min-size* size, *-N* size
operate on files larger than size
*--max-size* size, *-X* size
operate on files smaller than size
*--mode* mode, *-x* mode
operate on files matching mode mode

View file

@ -18,6 +18,7 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/lithammer/fuzzysearch/fuzzy"
) )
const ( const (
@ -28,12 +29,19 @@ const (
hoffset int = 6 hoffset int = 6
poffset int = 2 poffset int = 2
filenameColumn string = "filename"
pathColumn string = "path"
modifiedColumn string = "modified"
trashedColumn string = "trashed"
sizeColumn string = "size"
bar string = "───"
// TODO: figure these out dynamically based on longest of each // TODO: figure these out dynamically based on longest of each
filenameColumn float64 = 0.46 filenameColumnW float64 = 0.46
pathColumn float64 = 0.25 pathColumnW float64 = 0.25
dateColumn float64 = 0.15 dateColumnW float64 = 0.15
sizeColumn float64 = 0.12 sizeColumnW float64 = 0.12
checkColumn float64 = 0.02 checkColumnW float64 = 0.02
// TODO: make these configurable or something // TODO: make these configurable or something
borderbg string = "5" borderbg string = "5"
@ -65,21 +73,24 @@ type model struct {
selectsize int64 selectsize int64
readonly bool readonly bool
once bool once bool
filtering bool
filter string
termheight int termheight int
termwidth int termwidth int
mode modes.Mode mode modes.Mode
sorting sorting.Sorting sorting sorting.Sorting
workdir string workdir string
files files.Files files files.Files
fltrfiles files.Files
} }
func newModel(fls []files.File, width, height int, readonly, once bool, workdir string, mode modes.Mode) model { func newModel(fls []files.File, width, height int, selectall, readonly, once bool, workdir string, mode modes.Mode) model {
var ( var (
fwidth = int(math.Round(float64(width-woffset) * filenameColumn)) fwidth = int(math.Round(float64(width-woffset) * filenameColumnW))
owidth = int(math.Round(float64(width-woffset) * pathColumn)) owidth = int(math.Round(float64(width-woffset) * pathColumnW))
dwidth = int(math.Round(float64(width-woffset) * dateColumn)) dwidth = int(math.Round(float64(width-woffset) * dateColumnW))
swidth = int(math.Round(float64(width-woffset) * sizeColumn)) swidth = int(math.Round(float64(width-woffset) * sizeColumnW))
cwidth = int(math.Round(float64(width-woffset) * checkColumn)) cwidth = int(math.Round(float64(width-woffset) * checkColumnW))
theight = min(height-hoffset, len(fls)) theight = min(height-hoffset, len(fls))
mdl = model{ mdl = model{
@ -104,16 +115,16 @@ func newModel(fls []files.File, width, height int, readonly, once bool, workdir
var datecolumn string var datecolumn string
switch mdl.mode { switch mdl.mode {
case modes.Trashing: case modes.Trashing:
datecolumn = "modified" datecolumn = modifiedColumn
default: default:
datecolumn = "trashed" datecolumn = trashedColumn
} }
columns := []table.Column{ columns := []table.Column{
{Title: "filename", Width: fwidth}, {Title: filenameColumn, Width: fwidth},
{Title: "path", Width: owidth}, {Title: pathColumn, Width: owidth},
{Title: datecolumn, Width: dwidth}, {Title: datecolumn, Width: dwidth},
{Title: "size", Width: swidth}, {Title: sizeColumn, Width: swidth},
} }
if !mdl.readonly { if !mdl.readonly {
columns = append(columns, table.Column{Title: uncheck, Width: cwidth}) columns = append(columns, table.Column{Title: uncheck, Width: cwidth})
@ -126,6 +137,10 @@ func newModel(fls []files.File, width, height int, readonly, once bool, workdir
mdl.sorting = sorting.Name mdl.sorting = sorting.Name
mdl.sort() mdl.sort()
if selectall {
mdl.selectAll()
}
return mdl return mdl
} }
@ -139,6 +154,10 @@ type keyMap struct {
clen key.Binding clen key.Binding
sort key.Binding sort key.Binding
rort key.Binding rort key.Binding
fltr key.Binding
clfl key.Binding
apfl key.Binding
bksp key.Binding
quit key.Binding quit key.Binding
} }
@ -180,6 +199,22 @@ func defaultKeyMap() keyMap {
key.WithKeys("S"), key.WithKeys("S"),
key.WithHelp("S", "sort (reverse)"), key.WithHelp("S", "sort (reverse)"),
), ),
fltr: key.NewBinding(
key.WithKeys("/"),
key.WithHelp("/", "filter"),
),
apfl: key.NewBinding(
key.WithKeys("enter"),
key.WithHelp("enter", "apply filter"),
),
clfl: key.NewBinding(
key.WithKeys("esc"),
key.WithHelp("esc", "clear filter"),
),
bksp: key.NewBinding(
key.WithKeys("backspace"),
key.WithHelp("backspace", "backspace"),
),
quit: key.NewBinding( quit: key.NewBinding(
key.WithKeys("q", "ctrl+c"), key.WithKeys("q", "ctrl+c"),
key.WithHelp("q", "quit"), key.WithHelp("q", "quit"),
@ -205,11 +240,29 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
if m.filtering {
switch {
case key.Matches(msg, m.keys.clfl):
m.filter = ""
m.filtering = false
case key.Matches(msg, m.keys.apfl):
m.filtering = false
case key.Matches(msg, m.keys.bksp):
if len(m.filter) > 0 {
m.filter = m.filter[:len(m.filter)-1]
}
default:
m.filter += msg.String()
}
m.applyFilter()
return m, cmd
}
switch { switch {
case key.Matches(msg, m.keys.mark): case key.Matches(msg, m.keys.mark):
m.toggleItem(m.table.Cursor()) m.toggleItem(m.table.Cursor())
case key.Matches(msg, m.keys.doit): case key.Matches(msg, m.keys.doit):
if !m.readonly && m.mode != modes.Interactive { if !m.readonly && m.mode != modes.Interactive && len(m.fltrfiles) > 1 {
return m.quit(false) return m.quit(false)
} }
case key.Matches(msg, m.keys.nada): case key.Matches(msg, m.keys.nada):
@ -228,6 +281,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keys.rort): case key.Matches(msg, m.keys.rort):
m.sorting = m.sorting.Prev() m.sorting = m.sorting.Prev()
m.sort() m.sort()
case key.Matches(msg, m.keys.fltr):
m.filtering = true
case key.Matches(msg, m.keys.clfl):
if m.filter != "" {
m.filter = ""
m.applyFilter()
}
case key.Matches(msg, m.keys.quit): case key.Matches(msg, m.keys.quit):
return m.quit(true) return m.quit(true)
} }
@ -240,46 +300,60 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m model) View() string { func (m model) View() string {
var panels []string var panels []string
if !m.once {
if m.mode != modes.Listing {
panels = append(panels, m.header()) panels = append(panels, m.header())
} }
panels = append(panels, style.Render(m.table.View()), m.footer()) panels = append(panels, style.Render(m.table.View()), m.footer())
return lipgloss.JoinVertical(lipgloss.Top, panels...) return lipgloss.JoinVertical(lipgloss.Top,
panels...,
)
} }
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{ var filterText string
fmt.Sprintf("%s %s (%s)", darktext.Render(m.keys.sort.Help().Key), darkertext.Render(m.keys.sort.Help().Desc), m.sorting.String()), if m.filter != "" {
fmt.Sprintf("%s %s", darktext.Render(m.keys.quit.Help().Key), darkertext.Render(m.keys.quit.Help().Desc)), filterText = fmt.Sprintf(" (%s)", m.filter)
} }
keys := []string{
fmt.Sprintf("%s %s%s", darktext.Render(m.keys.fltr.Help().Key), darkertext.Render(m.keys.fltr.Help().Desc), filterText),
fmt.Sprintf("%s %s (%s)", darktext.Render(m.keys.sort.Help().Key), darkertext.Render(m.keys.sort.Help().Desc), m.sorting.String()),
styleKey(m.keys.quit),
}
if !m.readonly { if !m.readonly {
if m.mode != modes.Interactive { if m.mode != modes.Interactive {
keys = append([]string{ keys = append([]string{
fmt.Sprintf("%s %s", darktext.Render(m.keys.doit.Help().Key), darkertext.Render(m.keys.doit.Help().Desc)), styleKey(m.keys.doit),
}, keys...) }, keys...)
} }
keys = append([]string{ keys = append([]string{
fmt.Sprintf("%s %s", darktext.Render(m.keys.mark.Help().Key), darkertext.Render(m.keys.mark.Help().Desc)), styleKey(m.keys.mark),
}, keys...) }, keys...)
} }
return strings.Join(keys, darkesttext.Render(" • ")) return strings.Join(keys, darkesttext.Render(" • "))
} }
func (m model) header() string { func (m model) header() string {
// TODO: clean this ugly thing up
var ( var (
right, left string right, left string
spacerWidth int spacerWidth int
keys = []string{ keys = []string{
fmt.Sprintf("%s %s", darktext.Render(m.keys.rstr.Help().Key), darkertext.Render(m.keys.rstr.Help().Desc)), styleKey(m.keys.rstr),
fmt.Sprintf("%s %s", darktext.Render(m.keys.clen.Help().Key), darkertext.Render(m.keys.clen.Help().Desc)), styleKey(m.keys.clen),
} }
selectKeys = []string{ selectKeys = []string{
fmt.Sprintf("%s %s", darktext.Render(m.keys.todo.Help().Key), darkertext.Render(m.keys.todo.Help().Desc)), styleKey(m.keys.todo),
fmt.Sprintf("%s %s", darktext.Render(m.keys.nada.Help().Key), darkertext.Render(m.keys.nada.Help().Desc)), styleKey(m.keys.nada),
fmt.Sprintf("%s %s", darktext.Render(m.keys.invr.Help().Key), darkertext.Render(m.keys.invr.Help().Desc)), styleKey(m.keys.invr),
}
filterKeys = []string{
styleKey(m.keys.clfl),
styleKey(m.keys.apfl),
} }
dot = darkesttext.Render("•") dot = darkesttext.Render("•")
wideDot = darkesttext.Render(" • ") wideDot = darkesttext.Render(" • ")
@ -297,7 +371,20 @@ func (m model) header() string {
} }
right += fmt.Sprintf(" %s %s", dot, strings.Join(selectKeys, wideDot)) right += fmt.Sprintf(" %s %s", dot, strings.Join(selectKeys, wideDot))
left = fmt.Sprintf("%d/%d %s %s", len(m.selected), len(m.table.Rows()), dot, humanize.Bytes(uint64(m.selectsize))) left = fmt.Sprintf("%d/%d %s %s", len(m.selected), len(m.fltrfiles), dot, humanize.Bytes(uint64(m.selectsize)))
if m.mode == modes.Listing {
title := "Showing"
if m.filter != "" || m.filtering {
title += " (filtered)"
}
right = fmt.Sprintf(" %s %d files in trash", title, len(m.fltrfiles))
left = ""
}
if m.filtering {
right = fmt.Sprintf(" Filtering %s %s", dot, strings.Join(filterKeys, wideDot))
}
// offset of 2 again because of table border // offset of 2 again because of table border
spacerWidth = m.termwidth - lipgloss.Width(right) - lipgloss.Width(left) - poffset spacerWidth = m.termwidth - lipgloss.Width(right) - lipgloss.Width(left) - poffset
@ -323,7 +410,7 @@ func (m model) quit(unselectAll bool) (model, tea.Cmd) {
} }
func (m model) execute(mode modes.Mode) (model, tea.Cmd) { func (m model) execute(mode modes.Mode) (model, tea.Cmd) {
if m.mode != modes.Interactive || len(m.selected) <= 0 { if m.mode != modes.Interactive || len(m.selected) <= 0 || len(m.fltrfiles) <= 0 {
var cmd tea.Cmd var cmd tea.Cmd
return m, cmd return m, cmd
} }
@ -335,7 +422,7 @@ func (m model) execute(mode modes.Mode) (model, tea.Cmd) {
} }
func (m model) selectedFiles() (outfile files.Files) { func (m model) selectedFiles() (outfile files.Files) {
for _, file := range m.files { for _, file := range m.fltrfiles {
if m.selected[file.String()] { if m.selected[file.String()] {
outfile = append(outfile, file) outfile = append(outfile, file)
} }
@ -408,12 +495,12 @@ func (m *model) updateRows(selected bool) {
} }
func (m *model) toggleItem(index int) (selected bool) { func (m *model) toggleItem(index int) (selected bool) {
if m.readonly { if m.readonly || len(m.fltrfiles) == 0 {
return false return false
} }
name := m.files[index].String() name := m.fltrfiles[index].String()
size := m.files[index].Filesize() size := m.fltrfiles[index].Filesize()
// select the thing // select the thing
if v, ok := m.selected[name]; v && ok { if v, ok := m.selected[name]; v && ok {
@ -434,15 +521,15 @@ func (m *model) toggleItem(index int) (selected bool) {
} }
func (m *model) selectAll() { func (m *model) selectAll() {
if m.readonly { if m.readonly || len(m.fltrfiles) == 0 {
return return
} }
m.selected = map[string]bool{} m.selected = map[string]bool{}
m.selectsize = 0 m.selectsize = 0
for i := range m.table.Rows() { for i := range m.table.Rows() {
m.selected[m.files[i].String()] = true m.selected[m.fltrfiles[i].String()] = true
m.selectsize += m.files[i].Filesize() m.selectsize += m.fltrfiles[i].Filesize()
} }
m.updateRows(true) m.updateRows(true)
} }
@ -458,15 +545,15 @@ func (m *model) unselectAll() {
} }
func (m *model) invertSelection() { func (m *model) invertSelection() {
if m.readonly { if m.readonly || len(m.fltrfiles) == 0 {
return return
} }
var newrows []table.Row var newrows []table.Row
for index, row := range m.table.Rows() { for index, row := range m.table.Rows() {
name := m.files[index].String() name := m.fltrfiles[index].String()
size := m.files[index].Filesize() size := m.fltrfiles[index].Filesize()
if v, ok := m.selected[name]; v && ok { if v, ok := m.selected[name]; v && ok {
delete(m.selected, name) delete(m.selected, name)
m.selectsize -= size m.selectsize -= size
@ -495,20 +582,55 @@ 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.applyFilter()
}
func (m *model) applyFilter() {
m.fltrfiles = m.filteredFiles()
var rows = []table.Row{} var rows = []table.Row{}
for _, file := range m.files { for _, file := range m.fltrfiles {
r := newRow(file, m.workdir) row := newRow(file, m.workdir)
if !m.readonly { if !m.readonly {
r = append(r, getCheck(m.selected[file.String()])) row = append(row, getCheck(m.selected[file.String()]))
} }
rows = append(rows, r) rows = append(rows, row)
} }
if len(rows) < 1 {
row := table.Row{"no files matched filter!", bar, bar, bar}
if !m.readonly {
row = append(row, uncheck)
}
rows = append(rows, row)
}
m.table.SetRows(rows) m.table.SetRows(rows)
m.updateTableHeight()
} }
func Select(fls files.Files, width, height int, readonly, once bool, workdir string, mode modes.Mode) (files.Files, modes.Mode, error) { func (m *model) filteredFiles() (filteredFiles files.Files) {
mdl := newModel(fls, width, height, readonly, once, workdir, mode) for _, file := range m.files {
if isMatch(m.filter, file.Name()) {
filteredFiles = append(filteredFiles, file)
} else {
if _, ok := m.selected[file.String()]; ok {
delete(m.selected, file.String())
m.selectsize -= file.Filesize()
}
}
}
return
}
func (m *model) updateTableHeight() {
h := min(m.termheight-hoffset, len(m.table.Rows()))
m.table.SetHeight(h)
if m.table.Cursor() >= h {
m.table.SetCursor(h - 1)
}
}
func Select(fls files.Files, width, height int, selectall, once bool, workdir string, mode modes.Mode) (files.Files, modes.Mode, error) {
mdl := newModel(fls, width, height, selectall, false, once, workdir, mode)
endmodel, err := tea.NewProgram(mdl).Run() endmodel, err := tea.NewProgram(mdl).Run()
if err != nil { if err != nil {
return fls, 0, err return fls, 0, err
@ -520,19 +642,27 @@ func Select(fls files.Files, width, height int, readonly, once bool, workdir str
return m.selectedFiles(), m.mode, nil return m.selectedFiles(), m.mode, nil
} }
func Show(fls files.Files, width, height int, once bool, workdir string) error {
mdl := newModel(fls, width, height, false, true, once, workdir, modes.Listing)
if _, err := tea.NewProgram(mdl).Run(); err != nil {
return err
}
return nil
}
func newRow(file files.File, workdir string) table.Row { func newRow(file files.File, workdir string) table.Row {
var time, bar string var time, size string
time = humanize.Time(file.Date()) time = humanize.Time(file.Date())
if file.IsDir() { if file.IsDir() {
bar = strings.Repeat("─", poffset+1) size = bar
} else { } else {
bar = humanize.Bytes(uint64(file.Filesize())) size = humanize.Bytes(uint64(file.Filesize()))
} }
return table.Row{ return table.Row{
dirs.UnEscape(file.Name()), dirs.UnEscape(file.Name()),
dirs.UnExpand(filepath.Dir(file.Path()), workdir), dirs.UnExpand(filepath.Dir(file.Path()), workdir),
time, time,
bar, size,
} }
} }
@ -593,3 +723,13 @@ func makeUnselectedStyle() table.Styles {
Bold(false) Bold(false)
return style return style
} }
func styleKey(key key.Binding) string {
return fmt.Sprintf("%s %s", darktext.Render(key.Help().Key), darkertext.Render(key.Help().Desc))
}
func isMatch(pattern, filename string) bool {
p := strings.ToLower(pattern)
f := strings.ToLower(filename)
return fuzzy.Match(p, f)
}

View file

@ -30,6 +30,11 @@ run-args args:
install: install:
go install {{cmd}} go install {{cmd}}
# install manpages into $HOME/.local/share/man
install-man:
mkdir -p $HOME/.local/share/man/man1
cp {{binary}}.1 $HOME/.local/share/man/man1/{{binary}}.1
# clean up after yourself # clean up after yourself
clean: clean:
rm {{output}} rm {{output}}
@ -41,3 +46,7 @@ test:
# run linter # run linter
check: check:
staticcheck ./... staticcheck ./...
# generate manpage
man:
scdoc < {{binary}}.1.scd > {{binary}}.1

13
main.go
View file

@ -220,7 +220,7 @@ var (
filesToTrash = append(filesToTrash, file) filesToTrash = append(filesToTrash, file)
} }
// if none of the args were files, then process find files based on filter // if none of the args were files, then find files based on filter
if len(filesToTrash) == 0 { if len(filesToTrash) == 0 {
fls, err := files.FindDisk(workdir, recursive, fltr) fls, err := files.FindDisk(workdir, recursive, fltr)
if err != nil { if err != nil {
@ -255,7 +255,6 @@ var (
Action: func(_ *cli.Context) error { Action: func(_ *cli.Context) error {
log.Debugf("searching in directory %s for files", trashDir) log.Debugf("searching in directory %s for files", trashDir)
// look for files
fls, err := files.FindTrash(trashDir, ogdir, fltr) fls, err := files.FindTrash(trashDir, ogdir, fltr)
var msg string var msg string
@ -273,10 +272,7 @@ var (
return err return err
} }
// display them return interactive.Show(fls, termwidth, termheight, noInterArg, workdir)
_, _, err = interactive.Select(fls, termwidth, termheight, true, noInterArg, workdir, modes.Listing)
return err
}, },
} }
@ -290,7 +286,6 @@ var (
Action: func(_ *cli.Context) error { Action: func(_ *cli.Context) error {
log.Debugf("searching in directory %s for files", trashDir) log.Debugf("searching in directory %s for files", trashDir)
// look for files
fls, err := files.FindTrash(trashDir, ogdir, fltr) fls, err := files.FindTrash(trashDir, ogdir, fltr)
if len(fls) == 0 { if len(fls) == 0 {
fmt.Fprintln(os.Stdout, "no files to restore") fmt.Fprintln(os.Stdout, "no files to restore")
@ -299,7 +294,7 @@ var (
return err return err
} }
selected, _, err := interactive.Select(fls, termwidth, termheight, false, all, workdir, modes.Restoring) selected, _, err := interactive.Select(fls, termwidth, termheight, all, all, workdir, modes.Restoring)
if err != nil { if err != nil {
return err return err
} }
@ -328,7 +323,7 @@ var (
return err return err
} }
selected, _, err := interactive.Select(fls, termwidth, termheight, false, all, workdir, modes.Cleaning) selected, _, err := interactive.Select(fls, termwidth, termheight, all, all, workdir, modes.Cleaning)
if err != nil { if err != nil {
return err return err
} }