Skip to content

Commit

Permalink
Support zellij floating pane via --popup (new name for --tmux)
Browse files Browse the repository at this point in the history
  • Loading branch information
junegunn committed Dec 26, 2024
1 parent b2c3e56 commit 81366c5
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 15 deletions.
15 changes: 8 additions & 7 deletions man/man1/fzf.1
Original file line number Diff line number Diff line change
Expand Up @@ -271,22 +271,23 @@ Adaptive height has the following limitations:
Minimum height when \fB\-\-height\fR is given in percent (default: 10).
Ignored when \fB\-\-height\fR is not specified.
.TP
.BI "\-\-tmux" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]]"
Start fzf in a tmux popup (default \fBcenter,50%\fR). Requires tmux 3.3 or
later. This option is ignored if you are not running fzf inside tmux.
.BI "\-\-popup" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]]"
Start fzf in a tmux popup or in a zellij floating pane (default
\fBcenter,50%\fR). Requires tmux 3.3+ or zellij. This option is ignored if you
are not running fzf inside tmux or zellij.

e.g.
\fB# Popup in the center with 70% width and height
fzf \-\-tmux 70%
fzf \-\-popup 70%

# Popup on the left with 40% width and 100% height
fzf \-\-tmux right,40%
fzf \-\-popup right,40%

# Popup on the bottom with 100% width and 30% height
fzf \-\-tmux bottom,30%
fzf \-\-popup bottom,30%

# Popup on the top with 80% width and 40% height
fzf \-\-tmux top,80%,40%\fR
fzf \-\-popup top,80%,40%\fR

.TP
.BI "\-\-layout=" "LAYOUT"
Expand Down
9 changes: 7 additions & 2 deletions src/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ func (r revision) compatible(other revision) bool {
// Run starts fzf
func Run(opts *Options) (int, error) {
if opts.Filter == nil {
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
return runTmux(os.Args, opts)
if opts.Tmux != nil && opts.Tmux.index >= opts.Height.index {
if len(os.Getenv("TMUX")) > 0 {
return runTmux(os.Args, opts)
}
if len(os.Getenv("ZELLIJ")) > 0 {
return runZellij(os.Args, opts)
}
}

if needWinpty(opts) {
Expand Down
12 changes: 8 additions & 4 deletions src/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Usage: fzf [options]
according to the input size.
--min-height=HEIGHT Minimum height when --height is given in percent
(default: 10)
--tmux[=OPTS] Start fzf in a tmux popup (requires tmux 3.3+)
--popup[=OPTS] Start fzf in a popup (requires tmux 3.3+ or zellij)
[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
(default: center,50%)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
Expand Down Expand Up @@ -299,7 +299,7 @@ func parseTmuxOptions(arg string, index int) (*tmuxOptions, error) {
var err error
opts := defaultTmuxOptions(index)
tokens := splitRegexp.Split(arg, -1)
errorToReturn := errors.New("invalid tmux option: " + arg + " (expected: [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]])")
errorToReturn := errors.New("invalid popup option: " + arg + " (expected: [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]])")
if len(tokens) == 0 || len(tokens) > 3 {
return nil, errorToReturn
}
Expand Down Expand Up @@ -2031,7 +2031,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.Version = true
case "--no-winpty":
opts.NoWinpty = true
case "--tmux":
case "--tmux", "--popup":
given, str := optionalNextString(allArgs, &i)
if given {
if opts.Tmux, err = parseTmuxOptions(str, index); err != nil {
Expand All @@ -2040,7 +2040,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
} else {
opts.Tmux = defaultTmuxOptions(index)
}
case "--no-tmux":
case "--no-tmux", "--no-popup":
opts.Tmux = nil
case "--force-tty-in":
// NOTE: We need this because `system('fzf --tmux < /dev/tty')` doesn't
Expand Down Expand Up @@ -2577,6 +2577,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.FuzzyAlgo, err = parseAlgo(value); err != nil {
return err
}
} else if match, value := optString(arg, "--popup="); match {
if opts.Tmux, err = parseTmuxOptions(value, index); err != nil {
return err
}
} else if match, value := optString(arg, "--tmux="); match {
if opts.Tmux, err = parseTmuxOptions(value, index); err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion src/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -1539,7 +1539,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2]

t.prevLines = make([]itemLine, screenHeight)
t.prevLines = make([]itemLine, util.Max(1, screenHeight))
if t.border != nil && redrawBorder {
t.border = nil
}
Expand Down
2 changes: 1 addition & 1 deletion src/tmux.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func runTmux(args []string, opts *Options) (int, error) {
for _, arg := range args {
argStr += " " + escapeSingleQuote(arg)
}
argStr += ` --no-tmux --no-height`
argStr += ` --no-popup --no-height`

// Get current directory
dir, err := os.Getwd()
Expand Down
103 changes: 103 additions & 0 deletions src/zellij.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package fzf

import (
"fmt"
"os"
"os/exec"
"strconv"

"github.com/junegunn/fzf/src/tui"
)

func runZellij(args []string, opts *Options) (int, error) {
// Prepare arguments
fzf := args[0]
args = append([]string{"--bind=ctrl-z:ignore"}, args[1:]...)
if opts.BorderShape == tui.BorderUndefined {
args = append(args, "--no-border")
}
argStr := escapeSingleQuote(fzf)
for _, arg := range args {
argStr += " " + escapeSingleQuote(arg)
}
argStr += ` --no-popup --no-height`

// Get current directory
dir, err := os.Getwd()
if err != nil {
dir = "."
}

sh, err := sh(false)
if err != nil {
return ExitError, err
}

fifo, err := fifo("zellij-fifo")
if err != nil {
return ExitError, err
}

zopts := " --width " + opts.Tmux.width.String() + " --height " + opts.Tmux.height.String()
centerX := func() {
// TODO: Handle non-percent values
if opts.Tmux.width.percent {
x := (100.0 - opts.Tmux.width.size) / 2
if x <= 0 {
zopts += " -x0"
} else {
zopts += fmt.Sprintf(" -x%d%%", int(x))
}
} else if cols := os.Getenv("COLUMNS"); len(cols) > 0 {
if w, e := strconv.Atoi(cols); e == nil {
x := (float64(w) - opts.Tmux.width.size) / 2
zopts += fmt.Sprintf(" -x%d", int(x))
}
}

}
centerY := func() {
if opts.Tmux.height.percent {
y := (100.0 - opts.Tmux.height.size) / 2
if y <= 0 {
zopts += " -y0"
} else {
zopts += fmt.Sprintf(" -y%d%%", int(y))
}
} else if lines := os.Getenv("LINES"); len(lines) > 0 {
if h, e := strconv.Atoi(lines); e == nil {
y := (float64(h) - opts.Tmux.height.size) / 2
zopts += fmt.Sprintf(" -y%d", int(y))
}
}
}
switch opts.Tmux.position {
case posUp:
zopts += " -y0"
centerX()
case posDown:
zopts += " -y9999"
centerX()
case posLeft:
zopts += " -x0"
centerY()
case posRight:
zopts += " -x9999"
centerY()
case posCenter:
centerX()
centerY()
}

lines := []string{
"#!/bin/sh",
fmt.Sprintf(`zellij run --name '' --floating --close-on-exit --cwd %s %s -- %s -c "%s $1; echo \$? > %s" || exit $?`, dir, zopts, sh, sh, fifo),
fmt.Sprintf(`exit $(cat %s)`, fifo),
}
temptemp := WriteTemporaryFile(lines, "\n")
defer os.Remove(temptemp)

return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
return exec.Command(sh, temptemp, temp), nil
}, opts, true)
}

0 comments on commit 81366c5

Please sign in to comment.