diff --git a/app.go b/app.go index 5ef53f34..37e01a24 100644 --- a/app.go +++ b/app.go @@ -237,6 +237,7 @@ func (app *app) writeHistory() error { // separate goroutines and sent here for update. func (app *app) loop() { go app.nav.previewLoop(app.ui) + go app.nav.dirPreviewLoop(app.ui) var serverChan <-chan expr if !gSingleMode { @@ -318,6 +319,7 @@ func (app *app) loop() { app.quit() app.nav.previewChan <- "" + app.nav.dirPreviewChan <- nil log.Print("bye!") @@ -382,6 +384,7 @@ func (app *app) loop() { } app.ui.draw(app.nav) case d := <-app.nav.dirChan: + app.nav.checkDir(d) if gOpts.dircache { @@ -482,6 +485,8 @@ func (app *app) runShell(s string, args []string, prefix string) { cmd.Stderr = os.Stderr app.nav.previewChan <- "" + app.nav.dirPreviewChan <- nil + if err := app.ui.suspend(); err != nil { log.Printf("suspend: %s", err) } diff --git a/complete.go b/complete.go index c48b35cd..cfd37386 100644 --- a/complete.go +++ b/complete.go @@ -90,6 +90,7 @@ var ( "nodirfirst", "dirfirst!", "dironly", + "dirpreviews", "nodironly", "dironly!", "drawbox", diff --git a/doc.go b/doc.go index 1704fcb3..b3bcfb62 100644 --- a/doc.go +++ b/doc.go @@ -117,6 +117,7 @@ The following options can be used to customize the behavior of lf: dircounts bool (default off) dirfirst bool (default on) dironly bool (default off) + dirpreviews bool (default off) drawbox bool (default off) errorfmt string (default "\033[7;31;47m%s\033[0m") filesep string (default "\n") @@ -591,6 +592,10 @@ Show directories first above regular files. dironly bool (default off) +If enabled, directories will also be passed to the previewer script. This allows custom previews for directories. + + dirpreviews bool (default off) + Show only directories. drawbox bool (default off) diff --git a/eval.go b/eval.go index 77de6891..1370103c 100644 --- a/eval.go +++ b/eval.go @@ -44,6 +44,8 @@ func (e *setExpr) eval(app *app, args []string) { app.nav.position() app.ui.sort() app.ui.loadFile(app.nav, true) + case "dirpreviews": + gOpts.dirpreviews = true case "nodironly": gOpts.dironly = false app.nav.sort() diff --git a/nav.go b/nav.go index 37fa540d..fbc5d205 100644 --- a/nav.go +++ b/nav.go @@ -150,6 +150,7 @@ type dir struct { ignorecase bool // ignorecase value from last sort ignoredia bool // ignoredia value from last sort noPerm bool // whether lf has no permission to open the directory + lines []string // lines of text to display if directory previews are enabled } func newDir(path string) *dir { @@ -161,6 +162,7 @@ func newDir(path string) *dir { } return &dir{ + loading: gOpts.dirpreviews, // Directory is loaded after previewer function exits. loadTime: time, path: path, files: files, @@ -365,6 +367,7 @@ type nav struct { deleteCountChan chan int deleteTotalChan chan int previewChan chan string + dirPreviewChan chan *dir dirChan chan *dir regChan chan *reg dirCache map[string]*dir @@ -403,7 +406,11 @@ func (nav *nav) loadDirInternal(path string) *dir { d := newDir(path) d.sort() d.ind, d.pos = 0, 0 + if gOpts.dirpreviews { + nav.dirPreviewChan <- d + } nav.dirChan <- d + }() return d } @@ -447,6 +454,9 @@ func (nav *nav) checkDir(dir *dir) { nd := newDir(dir.path) nd.filter = dir.filter nd.sort() + if gOpts.dirpreviews { + nav.dirPreviewChan <- nd + } nav.dirChan <- nd }() case dir.sortType != gOpts.sortType || @@ -490,6 +500,7 @@ func newNav(height int) *nav { deleteCountChan: make(chan int, 1024), deleteTotalChan: make(chan int, 1024), previewChan: make(chan string, 1024), + dirPreviewChan: make(chan *dir, 1024), dirChan: make(chan *dir), regChan: make(chan *reg), dirCache: make(map[string]*dir), @@ -599,6 +610,27 @@ func (nav *nav) exportFiles() { exportFiles(currFile, currSelections, nav.currDir().path) } +func (nav *nav) dirPreviewLoop(ui *ui) { + var prevPath string + for { + select { + case dir := <-nav.dirPreviewChan: + + if dir == nil && len(gOpts.previewer) != 0 && len(gOpts.cleaner) != 0 && nav.volatilePreview { + cmd := exec.Command(gOpts.cleaner, prevPath) + if err := cmd.Run(); err != nil { + log.Printf("cleaning preview: %s", err) + } + nav.volatilePreview = false + } else if dir != nil { + win := ui.wins[len(ui.wins)-1] + nav.previewDir(dir, win) + prevPath = dir.path + } + } + } +} + func (nav *nav) previewLoop(ui *ui) { var prev string for path := range nav.previewChan { @@ -644,7 +676,70 @@ func matchPattern(pattern, name, path string) bool { return matched } +func (nav *nav) previewDir(dir *dir, win *win) { + + defer func() { + dir.loading = false + nav.dirChan <- dir + }() + + var reader io.Reader + + if len(gOpts.previewer) != 0 { + nav.exportFiles() + exportOpts() + cmd := exec.Command(gOpts.previewer, dir.path, + strconv.Itoa(win.w), + strconv.Itoa(win.h), + strconv.Itoa(win.x), + strconv.Itoa(win.y)) + + out, err := cmd.StdoutPipe() + if err != nil { + log.Printf("previewing dir: %s", err) + return + } + + if err := cmd.Start(); err != nil { + log.Printf("previewing dir: %s", err) + out.Close() + return + } + + defer func() { + if err := cmd.Wait(); err != nil { + if e, ok := err.(*exec.ExitError); ok { + if e.ExitCode() != 0 { + nav.volatilePreview = true + } + } else { + log.Printf("loading dir: %s", err) + } + } + }() + defer out.Close() + reader = out + buf := bufio.NewScanner(reader) + + for i := 0; i < win.h && buf.Scan(); i++ { + for _, r := range buf.Text() { + if r == 0 { + dir.lines = []string{"\033[7mbinary\033[0m"} + return + } + } + dir.lines = append(dir.lines, buf.Text()) + } + + if buf.Err() != nil { + log.Printf("loading dir: %s", buf.Err()) + } + } + +} + func (nav *nav) preview(path string, win *win) { + reg := ®{loadTime: time.Now(), path: path} defer func() { nav.regChan <- reg }() diff --git a/opts.go b/opts.go index 2909809f..60e065da 100644 --- a/opts.go +++ b/opts.go @@ -33,6 +33,7 @@ var gOpts struct { dircache bool dircounts bool dironly bool + dirpreviews bool drawbox bool globsearch bool icons bool @@ -83,6 +84,7 @@ func init() { gOpts.dircache = true gOpts.dircounts = false gOpts.dironly = false + gOpts.dirpreviews = false gOpts.drawbox = false gOpts.globsearch = false gOpts.icons = false diff --git a/ui.go b/ui.go index 951c91b5..ecfc4565 100644 --- a/ui.go +++ b/ui.go @@ -324,7 +324,8 @@ func fileInfo(f *file, d *dir) string { return info } -func (win *win) printDir(screen tcell.Screen, dir *dir, selections map[string]int, saves map[string]bool, tags map[string]string, colors styleMap, icons iconMap) { +func (win *win) printDir(screen tcell.Screen, dir *dir, selections map[string]int, saves map[string]bool, tags map[string]string, colors styleMap, icons iconMap, previewAllowed bool) { + if win.w < 5 || dir == nil { return } @@ -334,11 +335,25 @@ func (win *win) printDir(screen tcell.Screen, dir *dir, selections map[string]in return } - if dir.loading && len(dir.files) == 0 { + if (dir.loading && len(dir.files) == 0) || (previewAllowed && dir.loading && gOpts.dirpreviews) { win.print(screen, 2, 0, tcell.StyleDefault.Reverse(true), "loading...") return } + if previewAllowed && gOpts.dirpreviews && len(gOpts.previewer) > 0 { + + // Print previewer result instead of default directory print operation. + st := tcell.StyleDefault + for i, l := range dir.lines { + if i > win.h-1 { + break + } + + st = win.print(screen, 2, i, st, l) + } + return + } + if len(dir.files) == 0 { win.print(screen, 2, 0, tcell.StyleDefault.Reverse(true), "empty") return @@ -848,7 +863,7 @@ func (ui *ui) draw(nav *nav) { doff := len(nav.dirs) - length for i := 0; i < length; i++ { - ui.wins[woff+i].printDir(ui.screen, nav.dirs[doff+i], nav.selections, nav.saves, nav.tags, ui.styles, ui.icons) + ui.wins[woff+i].printDir(ui.screen, nav.dirs[doff+i], nav.selections, nav.saves, nav.tags, ui.styles, ui.icons, false) } switch ui.cmdPrefix { @@ -882,7 +897,7 @@ func (ui *ui) draw(nav *nav) { preview := ui.wins[len(ui.wins)-1] if curr.IsDir() { - preview.printDir(ui.screen, ui.dirPrev, nav.selections, nav.saves, nav.tags, ui.styles, ui.icons) + preview.printDir(ui.screen, ui.dirPrev, nav.selections, nav.saves, nav.tags, ui.styles, ui.icons, true) } else if curr.Mode().IsRegular() { preview.printReg(ui.screen, ui.regPrev) }