Add ability to use image previewers (#531)

* Return early on error in nav.preview

* nav.checkReg now returns a boolean instead of calling nav.preview

* Pass width, height, x, and y parameters to preview script

* Check previewer for exit code

If non-zero the preview will be assumed to have side-effects.

* Add the cleaner option

This is called upon selection changes if the previous preview was
volatile. To this end, volatilePreview was added to the nav struct
This commit is contained in:
neeshy 2020-12-24 13:13:20 +00:00 committed by GitHub
parent c2fe88e9a0
commit 82f03102a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 98 additions and 25 deletions

8
app.go
View File

@ -213,6 +213,8 @@ func (app *app) loop() {
continue
}
go app.nav.previewClear()
log.Print("bye!")
if err := app.writeHistory(); err != nil {
@ -310,7 +312,10 @@ func (app *app) loop() {
app.ui.draw(app.nav)
case r := <-app.nav.regChan:
app.nav.checkReg(r)
if app.nav.checkReg(r) {
win := app.ui.wins[len(app.ui.wins)-1]
go app.nav.preview(r.path, win)
}
app.nav.regCache[r.path] = r
@ -402,6 +407,7 @@ func (app *app) runShell(s string, args []string, prefix string) {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
go app.nav.previewClear()
app.ui.pause()
defer app.ui.resume()
defer app.nav.renew()

View File

@ -131,6 +131,7 @@ var (
"ifs",
"info",
"previewer",
"cleaner",
"promptfmt",
"ratios",
"shell",

10
doc.go
View File

@ -118,6 +118,7 @@ The following options can be used to customize the behavior of lf:
period int (default 0)
preview bool (default on)
previewer string (default '')
cleaner string (default '')
promptfmt string (default "\033[32;1m%u@%h\033[0m:\033[34;1m%w\033[0m\033[1m%f\033[0m")
ratios []int (default '1:2:3')
relativenumber bool (default off)
@ -591,10 +592,17 @@ Files containing the null character (U+0000) in the read portion are considered
Set the path of a previewer file to filter the content of regular files for previewing.
The file should be executable.
Two arguments are passed to the file, first is the current file name, and second is the height of preview pane.
Five arguments are passed to the file, first is the current file name; the second, third, fourth, and fifth are height, width, horizontal position, and vertical position of preview pane respectively.
SIGPIPE signal is sent when enough lines are read.
If the previewer returns a non-zero exit code, then the preview cache for the given file is disabled. This means that if the file is selected in the future, the previewer is called once again.
Preview filtering is disabled and files are displayed as they are when the value of this option is left empty.
cleaner string (default '') (not called if empty)
Set the path of a cleaner file. This file will be called if previewing is enabled, the previewer is set, and the previously selected file had its preview cache disabled.
The file should be executable.
Preview clearing is disabled when the value of this option is left empty.
promptfmt string (default "\033[32;1m%u@%h\033[0m:\033[34;1m%w/\033[0m\033[1m%f\033[0m")
Format string of the prompt shown in the top line.

View File

@ -121,6 +121,7 @@ The following options can be used to customize the behavior of lf:
period int (default 0)
preview bool (default on)
previewer string (default '')
cleaner string (default '')
promptfmt string (default "\033[32;1m%u@%h\033[0m:\033[34;1m%w\033[0m\033[1m%f\033[0m")
ratios []int (default '1:2:3')
relativenumber bool (default off)
@ -624,11 +625,21 @@ binary files and displayed as 'binary'.
previewer string (default '') (not filtered if empty)
Set the path of a previewer file to filter the content of regular files for
previewing. The file should be executable. Two arguments are passed to the
file, first is the current file name, and second is the height of preview
pane. SIGPIPE signal is sent when enough lines are read. Preview filtering
is disabled and files are displayed as they are when the value of this
option is left empty.
previewing. The file should be executable. Five arguments are passed to the
file, first is the current file name; the second, third, fourth, and fifth
are height, width, horizontal position, and vertical position of preview
pane respectively. SIGPIPE signal is sent when enough lines are read. If the
previewer returns a non-zero exit code, then the preview cache for the given
file is disabled. This means that if the file is selected in the future, the
previewer is called once again. Preview filtering is disabled and files are
displayed as they are when the value of this option is left empty.
cleaner string (default '') (not called if empty)
Set the path of a cleaner file. This file will be called if previewing is
enabled, the previewer is set, and the previously selected file had its
preview cache disabled. The file should be executable. Preview clearing is
disabled when the value of this option is left empty.
promptfmt string (default "\033[32;1m%u@%h\033[0m:\033[34;1m%w/\033[0m\033[1m%f\033[0m")

View File

@ -263,6 +263,8 @@ func (e *setExpr) eval(app *app, args []string) {
gOpts.info = toks
case "previewer":
gOpts.previewer = replaceTilde(e.val)
case "cleaner":
gOpts.cleaner = replaceTilde(e.val)
case "promptfmt":
gOpts.promptfmt = e.val
case "ratios":

9
lf.1
View File

@ -132,6 +132,7 @@ The following options can be used to customize the behavior of lf:
period int (default 0)
preview bool (default on)
previewer string (default '')
cleaner string (default '')
promptfmt string (default "\e033[32;1m%u@%h\e033[0m:\e033[34;1m%w\e033[0m\e033[1m%f\e033[0m")
ratios []int (default '1:2:3')
relativenumber bool (default off)
@ -712,7 +713,13 @@ Show previews of files and directories at the right most pane. If the file has m
previewer string (default '') (not filtered if empty)
.EE
.PP
Set the path of a previewer file to filter the content of regular files for previewing. The file should be executable. Two arguments are passed to the file, first is the current file name, and second is the height of preview pane. SIGPIPE signal is sent when enough lines are read. Preview filtering is disabled and files are displayed as they are when the value of this option is left empty.
Set the path of a previewer file to filter the content of regular files for previewing. The file should be executable. Five arguments are passed to the file, first is the current file name; the second, third, fourth, and fifth are height, width, horizontal position, and vertical position of preview pane respectively. SIGPIPE signal is sent when enough lines are read. If the previewer returns a non-zero exit code, then the preview cache for the given file is disabled. This means that if the file is selected in the future, the previewer is called once again. Preview filtering is disabled and files are displayed as they are when the value of this option is left empty.
.PP
.EX
cleaner string (default '') (not called if empty)
.EE
.PP
Set the path of a cleaner file. This file will be called if previewing is enabled, the previewer is set, and the previously selected file had its preview cache disabled. The file should be executable. Preview clearing is disabled when the value of this option is left empty.
.PP
.EX
promptfmt string (default "\e033[32;1m%u@%h\e033[0m:\e033[34;1m%w/\e033[0m\e033[1m%f\e033[0m")

65
nav.go
View File

@ -302,6 +302,7 @@ type nav struct {
searchBack bool
searchInd int
searchPos int
volatilePreview bool
}
func (nav *nav) loadDir(path string) *dir {
@ -456,31 +457,51 @@ func (nav *nav) position() {
}
}
func (nav *nav) preview(path string) {
func (nav *nav) preview(path string, win *win) {
reg := &reg{loadTime: time.Now(), path: path}
defer func() { nav.regChan <- reg }()
var reader io.Reader
if len(gOpts.previewer) != 0 {
exportOpts()
cmd := exec.Command(gOpts.previewer, path, strconv.Itoa(nav.height))
cmd := exec.Command(gOpts.previewer, 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 file: %s", err)
return
}
if err := cmd.Start(); err != nil {
log.Printf("previewing file: %s", err)
out.Close()
return
}
defer cmd.Wait()
defer func() {
if err := cmd.Wait(); err != nil {
if e, ok := err.(*exec.ExitError); ok {
if e.ExitCode() != 0 {
nav.volatilePreview = true
reg.volatile = true
}
} else {
log.Printf("loading file: %s", err)
}
}
}()
defer out.Close()
reader = out
} else {
f, err := os.Open(path)
if err != nil {
log.Printf("opening file: %s", err)
return
}
defer f.Close()
@ -489,11 +510,10 @@ func (nav *nav) preview(path string) {
buf := bufio.NewScanner(reader)
for i := 0; i < nav.height && buf.Scan(); i++ {
for i := 0; i < win.h && buf.Scan(); i++ {
for _, r := range buf.Text() {
if r == 0 {
reg.lines = []string{"\033[7mbinary\033[0m"}
nav.regChan <- reg
return
}
}
@ -503,28 +523,39 @@ func (nav *nav) preview(path string) {
if buf.Err() != nil {
log.Printf("loading file: %s", buf.Err())
}
nav.regChan <- reg
}
func (nav *nav) loadReg(path string) *reg {
func (nav *nav) previewClear() {
if len(gOpts.cleaner) != 0 && nav.volatilePreview {
nav.volatilePreview = false
cmd := exec.Command(gOpts.cleaner)
if err := cmd.Run(); err != nil {
log.Printf("cleaning preview: %s", err)
}
}
}
func (nav *nav) loadReg(path string, win *win) *reg {
r, ok := nav.regCache[path]
if !ok {
r := &reg{loading: true, loadTime: time.Now(), path: path}
if !ok || r.volatile {
r := &reg{loading: true, loadTime: time.Now(), path: path, volatile: true}
nav.regCache[path] = r
go nav.preview(path)
go nav.preview(path, win)
return r
}
nav.checkReg(r)
if nav.checkReg(r) {
go nav.preview(path, win)
}
return r
}
func (nav *nav) checkReg(reg *reg) {
func (nav *nav) checkReg(reg *reg) bool {
s, err := os.Stat(reg.path)
if err != nil {
return
return false
}
now := time.Now()
@ -532,13 +563,15 @@ func (nav *nav) checkReg(reg *reg) {
// XXX: Linux builtin exFAT drivers are able to predict modifications in the future
// https://bugs.launchpad.net/ubuntu/+source/ubuntu-meta/+bug/1872504
if s.ModTime().After(now) {
return
return false
}
if s.ModTime().After(reg.loadTime) {
reg.loadTime = now
go nav.preview(reg.path)
return true
}
return false
}
func (nav *nav) sort() {

View File

@ -51,6 +51,7 @@ var gOpts struct {
filesep string
ifs string
previewer string
cleaner string
promptfmt string
shell string
timefmt string
@ -89,6 +90,7 @@ func init() {
gOpts.filesep = "\n"
gOpts.ifs = ""
gOpts.previewer = ""
gOpts.cleaner = ""
gOpts.promptfmt = "\033[32;1m%u@%h\033[0m:\033[34;1m%w\033[0m\033[1m%f\033[0m"
gOpts.shell = gDefaultShell
gOpts.timefmt = time.ANSIC

5
ui.go
View File

@ -609,6 +609,7 @@ func (ui *ui) echoerrf(format string, a ...interface{}) {
type reg struct {
loading bool
volatile bool
loadTime time.Time
path string
lines []string
@ -624,10 +625,12 @@ func (ui *ui) loadFile(nav *nav) {
return
}
go nav.previewClear()
if curr.IsDir() {
ui.dirPrev = nav.loadDir(curr.path)
} else if curr.Mode().IsRegular() {
ui.regPrev = nav.loadReg(curr.path)
win := ui.wins[len(ui.wins)-1]
ui.regPrev = nav.loadReg(curr.path, win)
}
}