mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2025-01-08 17:24:05 +00:00
Experimental Sixel support (#2544)
This commit is contained in:
parent
a33749eb71
commit
b1a0ab8086
20
CHANGELOG.md
20
CHANGELOG.md
@ -1,6 +1,26 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.43.1
|
||||
------
|
||||
- (Experimental) Added support for Sixel graphics in the preview window
|
||||
```sh
|
||||
# 1. $FZF_PREVIEW_WIDTH and $FZF_PREVIEW_HEIGHT will be set to the pixel width
|
||||
# and height of the preview window
|
||||
# 2. Special preview window flag 'clear' is added to always completely
|
||||
# erase the preview window. This is similar to https://github.com/vifm/vifm/issues/588.
|
||||
fzf --preview='
|
||||
if file --mime-type {} | grep -qvF image/; then
|
||||
bat --color=always {}
|
||||
elif [[ -n $FZF_PREVIEW_WIDTH ]]; then
|
||||
convert {} -resize ${FZF_PREVIEW_WIDTH}x${FZF_PREVIEW_HEIGHT} sixel:-
|
||||
else
|
||||
echo "Cannot display image data (unsupported platform)"
|
||||
fi
|
||||
' --preview-window clear
|
||||
```
|
||||
- Bug fixes
|
||||
|
||||
0.43.0
|
||||
------
|
||||
- (Experimental) Added support for Kitty image protocol in the preview window
|
||||
|
@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Oct 2023" "fzf 0.43.0" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Oct 2023" "fzf 0.43.1" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@ -603,6 +603,24 @@ e.g.
|
||||
bat --color=always {}
|
||||
fi
|
||||
'\fR
|
||||
|
||||
fzf also has experimental support for Sixel graphics.
|
||||
|
||||
e.g.
|
||||
\fB# 1. $FZF_PREVIEW_WIDTH and $FZF_PREVIEW_HEIGHT will be set to
|
||||
# the pixel width and height of the preview window
|
||||
# 2. Special preview window flag 'clear' is needed to always completely
|
||||
# erase the preview window
|
||||
fzf --preview='
|
||||
if file --mime-type {} | grep -qvF image/; then
|
||||
bat --color=always {}
|
||||
elif [[ -n $FZF_PREVIEW_WIDTH ]]; then
|
||||
convert {} -resize ${FZF_PREVIEW_WIDTH}x${FZF_PREVIEW_HEIGHT} sixel:-
|
||||
else
|
||||
echo "Cannot display image data (unsupported platform)"
|
||||
fi
|
||||
' --preview-window clear\fR
|
||||
|
||||
.RE
|
||||
|
||||
.TP
|
||||
|
@ -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":
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user