m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-17 15:53:39 -05:00

Experimental Sixel support (#2544)

This commit is contained in:
Junegunn Choi
2023-10-23 01:01:47 +09:00
parent a33749eb71
commit b1a0ab8086
9 changed files with 117 additions and 11 deletions

View File

@@ -219,6 +219,7 @@ type previewOpts struct {
scroll string
hidden bool
wrap bool
clear bool
cycle bool
follow bool
border tui.BorderShape
@@ -340,7 +341,7 @@ type Options struct {
}
func defaultPreviewOpts(command string) previewOpts {
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.DefaultBorderShape, 0, 0, nil}
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, false, tui.DefaultBorderShape, 0, 0, nil}
}
func defaultOptions() *Options {
@@ -1454,6 +1455,10 @@ func parsePreviewWindowImpl(opts *previewOpts, input string, exit func(string))
opts.wrap = true
case "nowrap":
opts.wrap = false
case "clear":
opts.clear = true
case "noclear":
opts.clear = false
case "cycle":
opts.cycle = true
case "nocycle":
@@ -1788,7 +1793,7 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Preview.command = ""
case "--preview-window":
parsePreviewWindow(&opts.Preview,
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"))
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,clear][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"))
case "--height":
opts.Height = parseHeight(nextString(allArgs, &i, "height required: [~]HEIGHT[%]"))
case "--min-height":

View File

@@ -65,7 +65,8 @@ func init() {
// Parts of the preview output that should be passed through to the terminal
// * https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it
// * https://sw.kovidgoyal.net/kitty/graphics-protocol
passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b_G.*?\x1b\\`)
// * https://en.wikipedia.org/wiki/Sixel
passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\`)
}
type jumpMode int
@@ -1929,11 +1930,15 @@ func (t *Terminal) renderPreviewSpinner() {
}
func (t *Terminal) renderPreviewArea(unchanged bool) {
if unchanged {
if t.previewOpts.clear {
t.pwindow.Erase()
} else if unchanged {
t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display
} else {
t.previewed.filled = false
t.pwindow.Erase()
// We don't erase the window here to avoid flickering during scroll
t.pwindow.DrawBorder()
t.pwindow.Move(0, 0)
}
height := t.pwindow.Height()
@@ -1946,11 +1951,15 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
body = t.previewer.lines[headerLines:]
// Always redraw header
t.renderPreviewText(height, header, 0, false)
t.pwindow.MoveAndClear(t.pwindow.Y(), 0)
if t.previewOpts.clear {
t.pwindow.Move(t.pwindow.Y(), 0)
} else {
t.pwindow.MoveAndClear(t.pwindow.Y(), 0)
}
}
t.renderPreviewText(height, body, -t.previewer.offset+headerLines, unchanged)
if !unchanged {
if !unchanged && !t.previewOpts.clear {
t.pwindow.FinishFill()
}
@@ -1994,6 +2003,10 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
for _, passThrough := range passThroughs {
t.tui.PassThrough(passThrough)
}
if len(passThroughs) > 0 && len(line) == 0 {
continue
}
var fillRet tui.FillReturn
prefixWidth := 0
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
@@ -2686,6 +2699,11 @@ func (t *Terminal) Loop() {
env = append(env, "FZF_PREVIEW_"+lines)
env = append(env, columns)
env = append(env, "FZF_PREVIEW_"+columns)
size, err := t.tui.Size()
if err == nil {
env = append(env, fmt.Sprintf("FZF_PREVIEW_WIDTH=%d", pwindow.Width()*size.Width/size.Columns))
env = append(env, fmt.Sprintf("FZF_PREVIEW_HEIGHT=%d", height*size.Height/size.Lines))
}
}
cmd.Env = env

View File

@@ -38,6 +38,9 @@ func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) Size() (termSize, error) {
return termSize{}, nil
}
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) MaxX() int { return 0 }

View File

@@ -32,7 +32,7 @@ var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
func (r *LightRenderer) PassThrough(str string) {
r.queued.WriteString(str)
r.queued.WriteString("\x1b7" + str + "\x1b8")
r.flush()
}
@@ -756,6 +756,10 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
return w
}
func (w *LightWindow) DrawBorder() {
w.drawBorder(false)
}
func (w *LightWindow) DrawHBorder() {
w.drawBorder(true)
}
@@ -1095,7 +1099,8 @@ func (w *LightWindow) FinishFill() {
}
func (w *LightWindow) Erase() {
w.drawBorder(false)
// We don't erase the window here to avoid flickering during scroll
w.DrawBorder()
w.Move(0, 0)
w.FinishFill()
w.Move(0, 0)
}

View File

@@ -8,6 +8,7 @@ import (
"os/exec"
"strings"
"syscall"
"unsafe"
"github.com/junegunn/fzf/src/util"
"golang.org/x/term"
@@ -108,3 +109,19 @@ func (r *LightRenderer) getch(nonblock bool) (int, bool) {
}
return int(b[0]), true
}
type window struct {
lines uint16
columns uint16
width uint16
height uint16
}
func (r *LightRenderer) Size() (termSize, error) {
w := new(window)
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, r.ttyin.Fd(), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(w)))
if err != 0 {
return termSize{}, err
}
return termSize{int(w.lines), int(w.columns), int(w.width), int(w.height)}, nil
}

View File

@@ -203,6 +203,11 @@ func (r *FullscreenRenderer) Refresh() {
// noop
}
func (r *FullscreenRenderer) Size() (termSize, error) {
cols, lines := _screen.Size()
return termSize{lines, cols, 0, 0}, error("Not implemented")
}
func (r *FullscreenRenderer) GetChar() Event {
ev := _screen.PollEvent()
switch ev := ev.(type) {
@@ -541,6 +546,7 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
}
func (w *TcellWindow) Erase() {
w.drawBorder(false)
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
}
@@ -692,6 +698,10 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
return w.fillString(str, NewColorPair(fg, bg, a))
}
func (w *TcellWindow) DrawBorder() {
w.drawBorder(false)
}
func (w *TcellWindow) DrawHBorder() {
w.drawBorder(true)
}

View File

@@ -473,6 +473,13 @@ func MakeTransparentBorder() BorderStyle {
bottomRight: ' '}
}
type termSize struct {
Lines int
Columns int
Width int
Height int
}
type Renderer interface {
Init()
Resize(maxHeightFunc func(int) int)
@@ -490,6 +497,8 @@ type Renderer interface {
MaxX() int
MaxY() int
Size() (termSize, error)
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
}
@@ -499,6 +508,7 @@ type Window interface {
Width() int
Height() int
DrawBorder()
DrawHBorder()
Refresh()
FinishFill()