diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index b1601c70e28..22e6568a5f0 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -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" diff --git a/src/core.go b/src/core.go index 89f40cebeb8..b3cfc66974f 100644 --- a/src/core.go +++ b/src/core.go @@ -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) { diff --git a/src/options.go b/src/options.go index c8e72ff17a5..1c9cacf5dde 100644 --- a/src/options.go +++ b/src/options.go @@ -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] @@ -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 } @@ -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 { @@ -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 @@ -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 diff --git a/src/terminal.go b/src/terminal.go index 9b805123299..1deadf05476 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -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 } diff --git a/src/tmux.go b/src/tmux.go index b2315dcd2c3..72f5c0113f3 100644 --- a/src/tmux.go +++ b/src/tmux.go @@ -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() diff --git a/src/zellij.go b/src/zellij.go new file mode 100644 index 00000000000..78f56d822d7 --- /dev/null +++ b/src/zellij.go @@ -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) +}