diff --git a/eval.go b/eval.go index 82a17a0c..8af2d8a1 100644 --- a/eval.go +++ b/eval.go @@ -91,9 +91,6 @@ func (e *setExpr) eval(app *app, args []string) { if app.nav.height != app.ui.wins[0].h { app.nav.height = app.ui.wins[0].h clear(app.nav.regCache) - if gOpts.sixel { - app.ui.sxScreen.lastFile = "" - } } app.ui.loadFile(app, true) } @@ -157,6 +154,9 @@ func (e *setExpr) eval(app *app, args []string) { err = errors.New("preview: 'ratios' should consist of at least two numbers before enabling 'preview'") } if err == nil { + if gOpts.sixel { + app.ui.sxScreen.forceClear = true + } gOpts.preview = preview app.ui.loadFile(app, true) } @@ -174,6 +174,9 @@ func (e *setExpr) eval(app *app, args []string) { err = applyBoolOpt(&gOpts.showbinds, e) case "sixel", "nosixel", "sixel!": err = applyBoolOpt(&gOpts.sixel, e) + clear(app.nav.regCache) + app.ui.sxScreen.forceClear = true + app.ui.loadFile(app, true) case "smartcase", "nosmartcase", "smartcase!": err = applyBoolOpt(&gOpts.smartcase, e) if err == nil { @@ -350,7 +353,6 @@ func (e *setExpr) eval(app *app, args []string) { app.ui.wins = getWins(app.ui.screen) if gOpts.sixel { clear(app.nav.regCache) - app.ui.sxScreen.lastFile = "" } app.ui.loadFile(app, true) case "scrolloff": @@ -1420,7 +1422,7 @@ func (e *callExpr) eval(app *app, args []string) { } if gOpts.sixel { clear(app.nav.regCache) - app.ui.sxScreen.lastFile = "" + app.ui.sxScreen.forceClear = true } for _, dir := range app.nav.dirs { dir.boundPos(app.nav.height) diff --git a/lf-31 b/lf-31 new file mode 100755 index 00000000..5edc862d Binary files /dev/null and b/lf-31 differ diff --git a/misc.go b/misc.go index 7dcc74c8..d08bfb75 100644 --- a/misc.go +++ b/misc.go @@ -325,8 +325,9 @@ func getFileExtension(file fs.FileInfo) string { } var ( - reModKey = regexp.MustCompile(`<(c|s|a)-(.+)>`) - reRulerSub = regexp.MustCompile(`%[apmcsfithd]|%\{[^}]+\}`) + reModKey = regexp.MustCompile(`<(c|s|a)-(.+)>`) + reRulerSub = regexp.MustCompile(`%[apmcsfithd]|%\{[^}]+\}`) + reSixelSize = regexp.MustCompile(`"1;1;(\d+);(\d+)`) ) var ( diff --git a/sixel.go b/sixel.go index d559cccd..ab300c09 100644 --- a/sixel.go +++ b/sixel.go @@ -1,69 +1,71 @@ package main import ( - "fmt" + "log" "os" - "strings" + "strconv" "github.com/gdamore/tcell/v2" ) -const ( - gSixelBegin = "\033P" - - // The filler character should be: - // - rarely used: the filler is used to trick tcell into redrawing, if the - // filler character appears in the user's preview, that cell might not - // be cleaned up properly - // - ideally renders as empty space: the filler alternates between bold - // and regular, using a non-space would look weird to the user. - gSixelFiller = '\u2000' -) +const gSixelBegin = "\033P" type sixelScreen struct { - xprev, yprev int - sixel *string - altFill bool - lastFile string // TODO maybe use hash of sixels instead to flip altFill + lastFile string + lastWin win + forceClear bool } -func (sxs *sixelScreen) fillerStyle(filePath string) tcell.Style { - if sxs.lastFile != filePath { - sxs.altFill = !sxs.altFill +func (sxs *sixelScreen) clearSixel(win *win, screen tcell.Screen, filePath string) { + if sxs.lastFile != "" && (filePath != sxs.lastFile || *win != sxs.lastWin || sxs.forceClear) { + screen.LockRegion(sxs.lastWin.x, sxs.lastWin.y, sxs.lastWin.w, sxs.lastWin.h, false) } +} - if sxs.altFill { - return tcell.StyleDefault.Bold(true) +func (sxs *sixelScreen) printSixel(win *win, screen tcell.Screen, reg *reg) { + if reg.path == sxs.lastFile && *win == sxs.lastWin && !sxs.forceClear { + return } - return tcell.StyleDefault -} -func (sxs *sixelScreen) showSixels() { - if sxs.sixel == nil { + if reg.sixel == nil { + sxs.lastFile = "" return } - // XXX: workaround for bug where quitting lf might leave the terminal in bold - fmt.Fprint(os.Stderr, "\033[0m") + ti, err := tcell.LookupTerminfo(os.Getenv("TERM")) + if err != nil { + log.Printf("sixel: failed to look up term into %s", err) + return + } - fmt.Fprint(os.Stderr, "\0337") // Save cursor position - fmt.Fprintf(os.Stderr, "\033[%d;%dH", sxs.yprev, sxs.xprev) // Move cursor to position - fmt.Fprint(os.Stderr, *sxs.sixel) // - fmt.Fprint(os.Stderr, "\0338") // Restore cursor position -} + tty, ok := screen.Tty() + if !ok { + log.Printf("sixel: failed to get tty") + return + } -func (sxs *sixelScreen) printSixel(win *win, screen tcell.Screen, reg *reg) { - if reg.sixel == nil { + ws, err := tty.WindowSize() + if err != nil { + log.Printf("sixel: failed to get window size %s", err) return } + cw, ch := ws.CellDimensions() - // HACK: fillers are used to control when tcell redraws the region where a sixel image is drawn. - // alternating between bold and regular is to clear the image before drawing a new one. - st := sxs.fillerStyle(reg.path) - for y := range win.h { - st = win.print(screen, 0, y, st, strings.Repeat(string(gSixelFiller), win.w)) + matches := reSixelSize.FindStringSubmatch(*reg.sixel) + if matches == nil { + log.Printf("sixel: failed to get image size") + return } + iw, _ := strconv.Atoi(matches[1]) + ih, _ := strconv.Atoi(matches[2]) + + // clear sixel area first before drawing the image to prevent residue from + // the previous preview + screen.LockRegion(win.x, win.y, iw/cw, ih/ch, true) + ti.TPuts(tty, ti.TGoto(win.x, win.y)) + ti.TPuts(tty, *reg.sixel) - sxs.xprev, sxs.yprev = win.x+1, win.y+1 - sxs.sixel = reg.sixel + sxs.lastFile = reg.path + sxs.lastWin = *win + sxs.forceClear = false } diff --git a/ui.go b/ui.go index 654e1d2a..9165935c 100644 --- a/ui.go +++ b/ui.go @@ -1020,7 +1020,6 @@ func (ui *ui) draw(nav *nav) { context := dirContext{selections: nav.selections, saves: nav.saves, tags: nav.tags} ui.screen.Clear() - ui.sxScreen.sixel = nil ui.drawPromptLine(nav) @@ -1058,14 +1057,15 @@ func (ui *ui) draw(nav *nav) { ui.screen.ShowCursor(ui.msgWin.x+runeSliceWidth(prefix)+runeSliceWidth(left), ui.msgWin.y) } - if gOpts.preview { - curr, err := nav.currFile() - if err == nil { - preview := ui.wins[len(ui.wins)-1] - + curr, err := nav.currFile() + if err == nil { + preview := ui.wins[len(ui.wins)-1] + ui.sxScreen.clearSixel(preview, ui.screen, curr.path) + if gOpts.preview { if curr.Mode().IsRegular() || (curr.IsDir() && gOpts.dirpreviews) { preview.printReg(ui.screen, ui.regPrev, nav.previewLoading, &ui.sxScreen) } else if curr.IsDir() { + ui.sxScreen.lastFile = "" preview.printDir(ui, ui.dirPrev, &context, &dirStyle{colors: ui.styles, icons: ui.icons, role: Preview}) } @@ -1097,10 +1097,6 @@ func (ui *ui) draw(nav *nav) { } ui.screen.Show() - if ui.menu == "" && ui.cmdPrefix == "" && ui.sxScreen.sixel != nil { - ui.sxScreen.lastFile = ui.regPrev.path - ui.sxScreen.showSixels() - } } func findBinds(keys map[string]expr, prefix string) (binds map[string]expr, ok bool) { @@ -1522,6 +1518,7 @@ func (ui *ui) readExpr() { } func (ui *ui) suspend() error { + ui.sxScreen.forceClear = true return ui.screen.Suspend() }