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) } }