diff --git a/CHANGELOG.md b/CHANGELOG.md index bc38d8de..f5f22e05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ CHANGELOG 0.71.0 ------ +- Added `--popup` as a new name for `--tmux` with Zellij support + - `--popup` starts fzf in a tmux popup or a Zellij floating pane + - `--tmux` is now an alias for `--popup` + - Requires tmux 3.3+ or Zellij 0.44+ - Cross-reload item identity with `--id-nth` - Added `--id-nth=NTH` to define item identity fields for cross-reload operations - When a `reload` is triggered with tracking enabled, fzf searches for the tracked item by its identity fields in the new list. diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 00e272f7..709e96fb 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -415,25 +415,26 @@ layout options so that the specified number of items are visible in the list section (default: \fB10+\fR). Ignored when \fB\-\-height\fR is not specified or set as an absolute value. .TP -.BI "\-\-tmux" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]][,border-native]]" -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[%]][,border-native]]" +Start fzf in a tmux popup or in a Zellij floating pane (default +\fBcenter,50%\fR). Requires tmux 3.3+ or Zellij 0.44+. This option is ignored if you +are not running fzf inside tmux or Zellij. \fB\-\-tmux\fR is an alias for this option. 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% + fzf \-\-popup top,80%,40% - # Popup with a native tmux border in the center with 80% width and height - fzf \-\-tmux center,80%,border\-native\fR + # Popup with a native tmux or Zellij border in the center with 80% width and height + fzf \-\-popup center,80%,border\-native\fR .SS LAYOUT .TP diff --git a/src/core.go b/src/core.go index dfb12af7..9266b6cf 100644 --- a/src/core.go +++ b/src/core.go @@ -56,6 +56,9 @@ func Run(opts *Options) (int, error) { if opts.useTmux() { return runTmux(os.Args, opts) } + if opts.useZellij() { + return runZellij(os.Args, opts) + } if needWinpty(opts) { return runWinpty(os.Args, opts) diff --git a/src/options.go b/src/options.go index 36fd0d18..dcf55d8b 100644 --- a/src/options.go +++ b/src/options.go @@ -75,9 +75,10 @@ Usage: fzf [options] --min-height=HEIGHT[+] Minimum height when --height is given as a percentage. Add '+' to automatically increase the value according to the other layout options (default: 10+). - --tmux[=OPTS] Start fzf in a tmux popup (requires tmux 3.3+) + --popup[=OPTS] Start fzf in a popup window (requires tmux 3.3+ or Zellij 0.44+) [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]] [,border-native] (default: center,50%) + --tmux[=OPTS] Alias for --popup LAYOUT --layout=LAYOUT Choose layout: [default|reverse|reverse-list] @@ -417,7 +418,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[%][,border-native]])") + errorToReturn := errors.New("invalid popup option: " + arg + " (expected: [center|top|bottom|left|right][,SIZE[%]][,SIZE[%][,border-native]])") if len(tokens) == 0 || len(tokens) > 4 { return nil, errorToReturn } @@ -2636,7 +2637,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() if given { if opts.Tmux, err = parseTmuxOptions(str, index); err != nil { @@ -2645,7 +2646,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 "--tty-default": if opts.TtyDefault, err = nextString("tty device name required"); err != nil { @@ -3627,6 +3628,10 @@ func (opts *Options) useTmux() bool { return opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index } +func (opts *Options) useZellij() bool { + return opts.Tmux != nil && len(os.Getenv("ZELLIJ")) > 0 && opts.Tmux.index >= opts.Height.index +} + func (opts *Options) noSeparatorLine() bool { if opts.Inputless { return true diff --git a/src/terminal.go b/src/terminal.go index afad5b49..f3112a41 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -2243,7 +2243,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, max(1, screenHeight)) if t.border != nil && redrawBorder { t.border = nil } diff --git a/src/tmux.go b/src/tmux.go index 2e8499c5..47606688 100644 --- a/src/tmux.go +++ b/src/tmux.go @@ -27,7 +27,7 @@ func runTmux(args []string, opts *Options) (int, error) { for _, arg := range append(args, rest...) { 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 00000000..024009cd --- /dev/null +++ b/src/zellij.go @@ -0,0 +1,67 @@ +package fzf + +import ( + "os" + "os/exec" + + "github.com/junegunn/fzf/src/tui" +) + +func runZellij(args []string, opts *Options) (int, error) { + // Prepare arguments + fzf, rest := args[0], args[1:] + args = []string{"--bind=ctrl-z:ignore"} + if !opts.Tmux.border && (opts.BorderShape == tui.BorderUndefined || opts.BorderShape == tui.BorderLine) { + if tui.DefaultBorderShape == tui.BorderRounded { + rest = append(rest, "--border=rounded") + } else { + rest = append(rest, "--border=sharp") + } + } + if opts.Tmux.border && opts.Margin == defaultMargin() { + args = append(args, "--margin=0,1") + } + argStr := escapeSingleQuote(fzf) + for _, arg := range append(args, rest...) { + argStr += " " + escapeSingleQuote(arg) + } + argStr += ` --no-popup --no-height` + + // Get current directory + dir, err := os.Getwd() + if err != nil { + dir = "." + } + + zellijArgs := []string{ + "run", "--floating", "--close-on-exit", "--block-until-exit", + "--cwd", dir, + } + if !opts.Tmux.border { + zellijArgs = append(zellijArgs, "--borderless", "true") + } + switch opts.Tmux.position { + case posUp: + zellijArgs = append(zellijArgs, "-y", "0") + case posDown: + zellijArgs = append(zellijArgs, "-y", "9999") + case posLeft: + zellijArgs = append(zellijArgs, "-x", "0") + case posRight: + zellijArgs = append(zellijArgs, "-x", "9999") + case posCenter: + // Zellij centers floating panes by default + } + zellijArgs = append(zellijArgs, "--width", opts.Tmux.width.String()) + zellijArgs = append(zellijArgs, "--height", opts.Tmux.height.String()) + zellijArgs = append(zellijArgs, "--") + + return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) { + sh, err := sh(needBash) + if err != nil { + return nil, err + } + zellijArgs = append(zellijArgs, sh, temp) + return exec.Command("zellij", zellijArgs...), nil + }, opts, true) +}