The --preview is a very nice feature. I wanted to take it further and display images (mostly small png icons). I found termpix. Here's a link to my fork (with the fixed alpha blending) https://github.com/fimkap/termpix. Now that the latest neovim supports true color in terminal emulation I thought that fzf preview will display images well. I use something like this:
let g:fzf_files_options =
'--preview "(termpix --height '.&lines.' --true-color {} || cat {}) 2> /dev/null "'
But the images don't display well. If I use only ANSI colors - still there are some issues. I saw some discussions over other preview tools and thought that the issues can be caused by not using stdout as tty. I have created a file with the output of termpix and just cat it from the preview options and it worked. However, when I did:
--preview "(~/termpix.sh 50 {} || cat {}) 2> /dev/null | head -'.&lines.'"'
with the termpix.sh
termpix --width $1 --true-color $2 > /tmp/termpixdump
cat /tmp/termpixdump
hoping to solve the issue, it didn't work. I use iTerm2 with true color support.
Correction: true color images do not work at all in the preview (although they work in neovim's terminal emulation).
fzf currently does not support 24-bit colors due to the limitation of ncurses. But we now have an alternative implementation based on tcell and tcell supports 24-bit colors so it might be possible in the future. But since I noticed some issues with tcell, such as poor rendering performance, I currently don't have a plan to switch to it.
I understand this issue with ncurses. But 256 color mode doesn't work well either. The behaviour looks a bit strange to me. Only the first image open in the preview works correctly. If you scroll (Ctrl-k) - all other images are not displayed well. But if you return to the first file, it is ok.
In order to exclude issues with running termpix in the context of fzf preview, I used a temporary file with the subsequent cat.
'--preview "((termpix --width 50 {} > /tmp/termpixdump && cat /tmp/termpixdump) || cat {}) 2> /dev/null "'
Then, to be sure that the preview can display image well and I can scroll, I used a hardcoded 'cat /tmp/termpixdump' so it is called for every file. It works!
The last test I did was to prepare 5 files each being the output of termpix (256 colors). Then I used that:
'--preview "(cat {}) 2> /dev/null "'
just to cat this in preview. Again, the first file works and all the others don't. It looks like this:

And the next that isn't working, like this:

I know that all files are valid output (if I change the order of them, the one that wasn't displayed well will be ok in case it is the first one).
See https://github.com/junegunn/fzf/issues/357. Probably a limitation of ncurses 5. Try building fzf with 6 and see if it helps.
I was able to fix the issue by building fzf with ncurses 6.

With the patch:
diff --git a/src/Makefile b/src/Makefile
index 4d4bd1b..00df49d 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -70,10 +70,10 @@ clean:
cd fzf && rm -f fzf-*
fzf/$(BINARY32): deps
- cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -ldflags -w -tags "$(TAGS)" -o $(BINARY32)
+ cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY32)
fzf/$(BINARY64): deps
- cd fzf && go build -a -ldflags -w -tags "$(TAGS)" -o $(BINARY64)
+ cd fzf && go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY64)
$(BINDIR)/fzf: fzf/$(BINARY) | $(BINDIR)
cp -f fzf/$(BINARY) $(BINDIR)
diff --git a/src/tui/ncurses.go b/src/tui/ncurses.go
index 8603fd3..29afdc4 100644
--- a/src/tui/ncurses.go
+++ b/src/tui/ncurses.go
@@ -65,7 +65,7 @@ const (
var (
_screen *C.SCREEN
_colorMap map[int]ColorPair
- _colorFn func(ColorPair, Attr) C.int
+ _colorFn func(ColorPair, Attr) (C.short, C.attr_t)
)
func init() {
@@ -164,10 +164,10 @@ func NewWindow(top int, left int, width int, height int, border bool) *Window {
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
}
if border {
- attr := _colorFn(ColBorder, 0)
- C.wattron(win, attr)
+ pair, attr := _colorFn(ColBorder, 0)
+ C.wattr_set(win, attr, pair, nil)
C.box(win, 0, 0)
- C.wattroff(win, attr)
+ C.wattr_set(win, 0, 0, nil)
}
return &Window{
@@ -179,15 +179,11 @@ func NewWindow(top int, left int, width int, height int, border bool) *Window {
}
}
-func attrColored(pair ColorPair, a Attr) C.int {
- var attr C.int
- if pair > 0 {
- attr = C.COLOR_PAIR(C.int(pair))
- }
- return attr | C.int(a)
+func attrColored(pair ColorPair, a Attr) (C.short, C.attr_t) {
+ return C.short(pair), C.attr_t(a)
}
-func attrMono(pair ColorPair, a Attr) C.int {
+func attrMono(pair ColorPair, a Attr) (C.short, C.attr_t) {
var attr C.int
switch pair {
case ColCurrent:
@@ -200,7 +196,7 @@ func attrMono(pair ColorPair, a Attr) C.int {
if C.int(a)&C.A_BOLD == C.A_BOLD {
attr = attr | C.A_BOLD
}
- return attr
+ return 0, C.attr_t(attr)
}
func MaxX() int {
@@ -241,11 +237,11 @@ func (w *Window) Print(text string) {
}, text)))
}
-func (w *Window) CPrint(pair ColorPair, a Attr, text string) {
- attr := _colorFn(pair, a)
- C.wattron(w.win(), attr)
+func (w *Window) CPrint(pair ColorPair, attr Attr, text string) {
+ p, a := _colorFn(pair, attr)
+ C.wattr_set(w.win(), a, p, nil)
w.Print(text)
- C.wattroff(w.win(), attr)
+ C.wattr_set(w.win(), 0, 0, nil)
}
func Clear() {
@@ -265,11 +261,11 @@ func (w *Window) Fill(str string) bool {
return C.waddstr(w.win(), C.CString(str)) == C.OK
}
-func (w *Window) CFill(str string, fg Color, bg Color, a Attr) bool {
- attr := _colorFn(PairFor(fg, bg), a)
- C.wattron(w.win(), attr)
+func (w *Window) CFill(str string, fg Color, bg Color, attr Attr) bool {
+ pair := PairFor(fg, bg)
+ C.wattr_set(w.win(), C.attr_t(attr), C.short(pair), nil)
ret := w.Fill(str)
- C.wattroff(w.win(), attr)
+ C.wattr_set(w.win(), 0, 0, nil)
return ret
}
```
Unfortunately macOS comes with ncurses 5 and does not support statically linked binaries, one has to install ncurses 6.
```sh
brew install homebrew/dupes/ncurses
LDFLAGS="-L/usr/local/opt/ncurses/lib" make install
Great! Thanks. I'll check that. Regarding the tcell branch, I just wanted to see how it handles 24 bit color and checked it out and built with install, but wasn't able to tell the difference from ncurses. Any changes needed to use tcell?
Building fzf with tcell does not make it support 24-bit colors. ANSI processor of fzf needs to be updated to be able to parse 24-bit ANSI escape sequences in the input.
It works! I have built fzf with ncurses 6 and the patch as you suggested and 256-color mode works great.
In parallel, I am working on a small utility (in Swift) to display images (transparent background, always on-top, no focus transfer). I am trying to use it as external program for the fzf preview. While testing from the terminal - it works. But trying to run it in the background with & - doesn't work in the fzf preview context. I saw some similar issues with the background job but it didn't help me. So what's the correct way to run a background job in the fzf preview?
The sample command:
ls | ~/dev/fzf/bin/fzf --preview '~/Prevu.app/Contents/MacOS/Prevu {} & 2> /dev/null'
Opening a mac app from --preview seems to work for me. Check whether if other apps are working correctly or not.
By the way, I was able to make fzf support 24-bit colors when built with tcell.

I'll push the code later in the day, so you can try it.
Great news about 24-bit colors! Thank you! Regarding opening a mac program, you probably use 'open' command with the app bundle. As I see it, 'open' is a launcher that takes care of putting the process into the background. I was trying to launch the executable directly avoiding the overhead. But of course, using 'open' would be an option.
The problem with the background job is easily shown with:
ls | ~/dev/fzf/bin/fzf --preview 'sleep 3 & echo {} 2> /dev/null'
You can see that echoing is delayed for 3 seconds. Probably, '&' is being escaped.
Updated the ANSI processor and build instructions, see:
https://github.com/junegunn/fzf/blob/master/src/README.md#build
Regarding the shelling out issue, I think it's because fzf is listening to the standard output of the child processes until their completion. I'll see if there's anything we can do about it. But anyway, using open or nohup should be considered for now.
Oh, I can make fzf get the result immediately by detaching STDOUT and STDERR by redirecting them to /dev/null.
fzf --preview 'sleep 1 > /dev/null 2>&1 & echo {}'
fzf can preview images with w3mimgdisplay from the w3m browser if you use the image preview script written for vifm based the one by dead cat.
fzf --preview '/path/to/imgt {}'
I can't get the image to appear in the preview screen though. It is offset. If anyone has a better way I would like to hear about it.
Most helpful comment
I was able to fix the issue by building fzf with ncurses 6.
With the patch: