Files
go/files/files.go
Thomas Sindt 8b1006819d pwsetup v0
2026-02-28 22:35:11 +01:00

277 lines
5.8 KiB
Go

package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/dustin/go-humanize"
"github.com/gdamore/tcell"
"github.com/rivo/tview"
)
var (
// Global channel to send the current directory to
cwdch = make(chan string)
// Global channel to send info about the current file, and
// general status updates. Has to be buffered else the program
// enters a deadlock for some reason
statusch = make(chan string, 1)
)
func main() {
var (
// Main application
app = tview.NewApplication()
// Using a Grid layout allows us to put many primitives next to
// each other and have them respond to the terminal size
mainview = tview.NewGrid()
// Show the files and directories inside the current, previous,
// and next directory panels
previousdir = tview.NewTable()
currentdir = tview.NewTable()
nextdir = tview.NewTable()
// Small title showing the whole directory name
cwd = tview.NewTextView()
// Simple status bar showing current file permissions and other info
fileinfo = tview.NewTextView()
)
// Init widgets
initPanels(previousdir, currentdir, nextdir, app)
initDirTitle(cwd, app)
initFileInfo(fileinfo, app)
// Set our mainview to resemble ranger
mainview.
SetBorders(true).
SetColumns(10, 0, 40).
SetRows(1, 0, 1)
// Add the widgets
mainview.
AddItem(cwd, 0, 0, 1, 3, 1, 1, false).
AddItem(previousdir, 1, 0, 1, 1, 1, 1, false).
AddItem(currentdir, 1, 1, 1, 1, 1, 1, false).
AddItem(nextdir, 1, 2, 1, 1, 1, 1, false).
AddItem(fileinfo, 2, 0, 1, 3, 1, 1, true)
// Make the mainview the root widget, and fullscreen it
app.SetRoot(mainview, true)
// Focus on the directory we are in
app.SetFocus(currentdir)
// Run the application
err := app.Run()
if err != nil {
panic(err)
}
}
// Preparation to properly initialize each panel upon start
func initPanels(previousdir, currentdir, nextdir *tview.Table, app *tview.Application) {
// Make each selectable
previousdir.SetSelectable(true, false)
currentdir.SetSelectable(true, false)
nextdir.SetSelectable(true, false)
// Get the files and dirs of the current and previous directory
getDirContents(previousdir, "..")
getDirContents(currentdir, ".")
// Define the function to be called each time we move to a new file or dir
preview := func(row, column int) {
currentselect := currentdir.GetCell(row, 0).Text
previewDir(currentselect, nextdir)
}
// Use it on the first item
preview(0, 0)
// Install it
currentdir.SetSelectionChangedFunc(preview)
// Moving between dirs, and exiting
currentdir.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Rune() {
case 'h':
// Move up
err := os.Chdir("..")
printError(err)
copyDirToPanel(currentdir, nextdir)
copyDirToPanel(previousdir, currentdir)
getDirContents(previousdir, "..")
// Update directory bar
printCwd()
case 'l':
// Move into a dir
r, _ := currentdir.GetSelection()
currentItem := currentdir.GetCell(r, 0).Text
// Get info about the current item
fi, err := os.Stat(currentItem)
printError(err)
if fi.IsDir() {
err = os.Chdir(currentItem)
printError(err)
copyDirToPanel(currentdir, previousdir)
copyDirToPanel(nextdir, currentdir)
preview(0, 0)
}
// Update directory bar
printCwd()
case 'q':
app.Stop()
}
return event
})
}
func initFileInfo(fi *tview.TextView, app *tview.Application) {
// Display colors given by text
fi.SetDynamicColors(true)
// Constantly check and print file permissions etc
go func() {
var info string
for {
info = <-statusch
fi.SetText(info)
app.Draw()
}
}()
}
func initDirTitle(cwd *tview.TextView, app *tview.Application) {
// Make the title blue and accept colors
cwd.SetDynamicColors(true)
cwd.SetTextColor(tcell.ColorBlue)
// Get initial dir
wd, err := os.Getwd()
printError(err)
// Check and print info about the current dir
go func() {
var dir string
for {
dir = <-cwdch
cwd.SetText("[::b]" + filepath.Dir(dir) + "/[white]" + filepath.Base(dir))
app.Draw()
}
}()
cwdch <- wd
}
func copyDirToPanel(previousdir, currentdir *tview.Table) {
// Make sure there are no elements in the panel
currentdir.Clear()
for i := 0; i < previousdir.GetRowCount(); i++ {
currentdir.SetCell(i, 0, previousdir.GetCell(i, 0))
}
}
func previewDir(path string, paneldir *tview.Table) {
// Clean the panel
paneldir.Clear()
// Get info about the given path
fi, err := os.Stat(path)
if err != nil {
return
}
if fi.IsDir() {
getDirContents(paneldir, path)
}
// Send info about the current file
statusch <- fmt.Sprintf("[fuchsia]%s[white]\t%s\t%s",
fi.Mode().String(),
fi.ModTime().Round(time.Second).String(),
humanize.Bytes(uint64(fi.Size())))
}
func getDirContents(dirtable *tview.Table, path string) {
// Make sure to the table is empty
dirtable.Clear()
// Get the contents of the directory
files, err := ioutil.ReadDir(path)
if err != nil {
return
}
for i, f := range files {
var color = tcell.ColorWhite
// Create a new cell for the table, and make sure it takes up
// all the horizontal space
cell := tview.NewTableCell(f.Name())
cell.SetExpansion(1)
// Colorize according to extension and mode
if f.IsDir() {
color = tcell.ColorNavy
cell.SetAttributes(tcell.AttrBold)
}
switch filepath.Ext(f.Name()) {
case ".png", ".jpg":
color = tcell.ColorYellow
case ".mp4", ".mkv", ".avi", ".webm", ".mp3", ".m4a", ".flac":
color = tcell.ColorFuchsia
case ".zip", ".gz", ".iso":
color = tcell.ColorRed
}
if f.Mode()&os.ModeSymlink != 0 {
color = tcell.ColorTeal
}
cell.SetTextColor(color)
// Add it to the table on the i row, first column
dirtable.SetCell(i, 0, cell)
}
}
func printCwd() {
dir, err := os.Getwd()
printError(err)
cwdch <- dir
}
func printError(err error) {
if err != nil {
statusch <- fmt.Sprintf("[red]%s", err)
}
}