277 lines
5.8 KiB
Go
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)
|
|
}
|
|
}
|