From 9c2f6cae88aa46b5dd54edd813dd1604aa6d5386 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Tue, 7 Jan 2025 19:16:16 +0900 Subject: [PATCH 1/5] Fix adaptive height with --header-border --- src/terminal.go | 3 +++ test/test_go.rb | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/terminal.go b/src/terminal.go index f22ec1c..cb560b9 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -1093,6 +1093,9 @@ func (t *Terminal) extraLines() int { if t.listBorderShape.Visible() { extra += borderLines(t.listBorderShape) } + if t.headerBorderShape.Visible() { + extra += borderLines(t.headerBorderShape) + } if !t.noSeparatorLine() { extra++ } diff --git a/test/test_go.rb b/test/test_go.rb index 8f52fd6..edefebd 100755 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -3702,6 +3702,22 @@ class TestGoFZF < TestBase BLOCK tmux.until { assert_block(block, _1) } end + + def test_style_full_adaptive_height + tmux.send_keys %(seq 1| #{FZF} --style=full --height=~100% --header-lines=1 --info=default), :Enter + block = <<~BLOCK + ╭──────── + ╰──────── + ╭──────── + │ 1 + ╰──────── + ╭──────── + │ 0/0 + │ > + ╰──────── + BLOCK + tmux.until { assert_block(block, _1) } + end end module TestShell From fa3f706e71496af5e2c8bfc6bbf6c70de9414827 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Tue, 7 Jan 2025 19:16:41 +0900 Subject: [PATCH 2/5] Refactor option parser --- src/options.go | 529 +++++++++++++++++-------------------------------- 1 file changed, 177 insertions(+), 352 deletions(-) diff --git a/src/options.go b/src/options.go index 12861b0..11731b7 100644 --- a/src/options.go +++ b/src/options.go @@ -700,54 +700,11 @@ func defaultOptions() *Options { Version: false} } -func optString(arg string, prefixes ...string) (bool, string) { - for _, prefix := range prefixes { - if strings.HasPrefix(arg, prefix) { - return true, arg[len(prefix):] - } - } - return false, "" -} - -func nextString(args []string, i *int, message string) (string, error) { - if len(args) > *i+1 { - *i++ - } else { - return "", errors.New(message) - } - return args[*i], nil -} - -func optionalNextString(args []string, i *int) (bool, string) { - if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") && !strings.HasPrefix(args[*i+1], "+") { - *i++ - return true, args[*i] - } - return false, "" -} - func isDir(path string) bool { stat, err := os.Stat(path) return err == nil && stat.IsDir() } -func nextDirs(args []string, i *int) ([]string, error) { - dirs := []string{} - for *i < len(args)-1 { - arg := args[*i+1] - if isDir(arg) { - dirs = append(dirs, arg) - *i++ - } else { - break - } - } - if len(dirs) == 0 { - return nil, errors.New("no directory specified") - } - return dirs, nil -} - func atoi(str string) (int, error) { num, err := strconv.Atoi(str) if err != nil { @@ -764,33 +721,6 @@ func atof(str string) (float64, error) { return num, nil } -func nextInt(args []string, i *int, message string) (int, error) { - if len(args) > *i+1 { - *i++ - } else { - return 0, errors.New(message) - } - n, err := atoi(args[*i]) - if err != nil { - return 0, errors.New(message) - } - return n, nil -} - -func optionalNumeric(args []string, i *int, defaultValue int) (int, error) { - if len(args) > *i+1 { - if strings.IndexAny(args[*i+1], "0123456789") == 0 { - *i++ - n, err := atoi(args[*i]) - if err != nil { - return 0, err - } - return n, nil - } - } - return defaultValue, nil -} - func splitNth(str string) ([]Range, error) { if match, _ := regexp.MatchString("^[0-9,-.]+$", str); !match { return nil, errors.New("invalid format: " + str) @@ -2077,6 +2007,13 @@ func parseMarkerMultiLine(str string) (*[3]string, error) { return &result, nil } +func optString(arg string, prefix string) (bool, string) { + if strings.HasPrefix(arg, prefix) { + return true, arg[len(prefix):] + } + return false, "" +} + func parseOptions(index *int, opts *Options, allArgs []string) error { var err error var historyMax int @@ -2113,10 +2050,101 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { opts.Version = false opts.Man = false } + startIndex := *index - for i := 0; i < len(allArgs); i++ { + + var i int + var val *string = nil + nextString := func(message string) (string, error) { + defer func() { val = nil }() + if val != nil { + return *val, nil + } + if len(allArgs) > i+1 { + i++ + } else { + return "", errors.New(message) + } + return allArgs[i], nil + } + + optionalNextString := func() (bool, string) { + defer func() { val = nil }() + if val != nil { + return true, *val + } + if len(allArgs) > i+1 && !strings.HasPrefix(allArgs[i+1], "-") && !strings.HasPrefix(allArgs[i+1], "+") { + i++ + return true, allArgs[i] + } + return false, "" + } + + nextDirs := func() ([]string, error) { + defer func() { val = nil }() + dirs := []string{} + if val != nil { + dirs = append(dirs, *val) + } + for i < len(allArgs)-1 { + arg := allArgs[i+1] + if isDir(arg) { + dirs = append(dirs, arg) + i++ + } else { + break + } + } + if len(dirs) == 0 { + return nil, errors.New("no directory specified") + } + return dirs, nil + } + + nextInt := func(message string) (int, error) { + defer func() { val = nil }() + var str string + if val != nil { + str = *val + } else if len(allArgs) > i+1 { + i++ + str = allArgs[i] + } else { + return 0, errors.New(message) + } + n, err := atoi(str) + if err != nil { + return 0, errors.New(message) + } + return n, nil + } + + optionalNumeric := func(defaultValue int) (int, error) { + defer func() { val = nil }() + var str string + if val != nil { + str = *val + } else if len(allArgs) > i+1 && strings.IndexAny(allArgs[i+1], "0123456789") == 0 { + i++ + str = allArgs[i] + } else { + return defaultValue, nil + } + n, err := atoi(str) + if err != nil { + return 0, err + } + return n, nil + } + + for ; i < len(allArgs); i++ { arg := allArgs[i] index := i + startIndex + if strings.HasPrefix(arg, "--") && strings.IndexRune(arg, '=') > 0 { + tokens := strings.SplitN(arg, "=", 2) + arg = tokens[0] + val = &tokens[1] + } switch arg { case "--man": clearExitingOpts() @@ -2139,7 +2167,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-winpty": opts.NoWinpty = true case "--tmux": - given, str := optionalNextString(allArgs, &i) + given, str := optionalNextString() if given { if opts.Tmux, err = parseTmuxOptions(str, index); err != nil { return err @@ -2156,7 +2184,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-force-tty-in": opts.ForceTtyIn = false case "--proxy-script": - if opts.ProxyScript, err = nextString(allArgs, &i, ""); err != nil { + if opts.ProxyScript, err = nextString(""); err != nil { return err } case "-x", "--extended": @@ -2172,11 +2200,11 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "+e", "--no-exact": opts.Fuzzy = true case "-q", "--query": - if opts.Query, err = nextString(allArgs, &i, "query string required"); err != nil { + if opts.Query, err = nextString("query string required"); err != nil { return err } case "-f", "--filter": - filter, err := nextString(allArgs, &i, "query string required") + filter, err := nextString("query string required") if err != nil { return err } @@ -2186,7 +2214,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-literal": opts.Normalize = true case "--algo": - str, err := nextString(allArgs, &i, "algorithm required (v1|v2)") + str, err := nextString("algorithm required (v1|v2)") if err != nil { return err } @@ -2194,13 +2222,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--scheme": - str, err := nextString(allArgs, &i, "scoring scheme required (default|path|history)") + str, err := nextString("scoring scheme required (default|path|history)") if err != nil { return err } opts.Scheme = strings.ToLower(str) case "--expect": - str, err := nextString(allArgs, &i, "key names required") + str, err := nextString("key names required") if err != nil { return err } @@ -2218,7 +2246,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--disabled", "--phony": opts.Phony = true case "--tiebreak": - str, err := nextString(allArgs, &i, "sort criterion required") + str, err := nextString("sort criterion required") if err != nil { return err } @@ -2226,7 +2254,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--bind": - str, err := nextString(allArgs, &i, "bind expression required") + str, err := nextString("bind expression required") if err != nil { return err } @@ -2234,7 +2262,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--color": - _, spec := optionalNextString(allArgs, &i) + _, spec := optionalNextString() if len(spec) == 0 { opts.Theme = tui.EmptyTheme() } else { @@ -2243,7 +2271,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { } } case "--toggle-sort": - str, err := nextString(allArgs, &i, "key name required") + str, err := nextString("key name required") if err != nil { return err } @@ -2251,13 +2279,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "-d", "--delimiter": - str, err := nextString(allArgs, &i, "delimiter required") + str, err := nextString("delimiter required") if err != nil { return err } opts.Delimiter = delimiterRegexp(str) case "-n", "--nth": - str, err := nextString(allArgs, &i, "nth expression required") + str, err := nextString("nth expression required") if err != nil { return err } @@ -2265,7 +2293,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--with-nth": - str, err := nextString(allArgs, &i, "nth expression required") + str, err := nextString("nth expression required") if err != nil { return err } @@ -2273,7 +2301,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "-s", "--sort": - if opts.Sort, err = optionalNumeric(allArgs, &i, 1); err != nil { + if opts.Sort, err = optionalNumeric(1); err != nil { return err } case "+s", "--no-sort": @@ -2287,7 +2315,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-tac": opts.Tac = false case "--tail": - if opts.Tail, err = nextInt(allArgs, &i, "number of items to keep required"); err != nil { + if opts.Tail, err = nextInt("number of items to keep required"); err != nil { return err } if opts.Tail <= 0 { @@ -2302,7 +2330,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "+i", "--no-ignore-case": opts.Case = CaseRespect case "-m", "--multi": - if opts.Multi, err = optionalNumeric(allArgs, &i, maxMulti); err != nil { + if opts.Multi, err = optionalNumeric(maxMulti); err != nil { return err } case "+m", "--no-multi": @@ -2326,7 +2354,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-bold": opts.Bold = false case "--layout": - str, err := nextString(allArgs, &i, "layout required (default / reverse / reverse-list)") + str, err := nextString("layout required (default / reverse / reverse-list)") if err != nil { return err } @@ -2350,7 +2378,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-wrap": opts.Wrap = false case "--wrap-sign": - str, err := nextString(allArgs, &i, "wrap sign required") + str, err := nextString("wrap sign required") if err != nil { return err } @@ -2368,11 +2396,11 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-hscroll": opts.Hscroll = false case "--hscroll-off": - if opts.HscrollOff, err = nextInt(allArgs, &i, "hscroll offset required"); err != nil { + if opts.HscrollOff, err = nextInt("hscroll offset required"); err != nil { return err } case "--scroll-off": - if opts.ScrollOff, err = nextInt(allArgs, &i, "scroll offset required"); err != nil { + if opts.ScrollOff, err = nextInt("scroll offset required"); err != nil { return err } case "--filepath-word": @@ -2380,7 +2408,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-filepath-word": opts.FileWord = false case "--info": - str, err := nextString(allArgs, &i, "info style required") + str, err := nextString("info style required") if err != nil { return err } @@ -2388,7 +2416,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--info-command": - if opts.InfoCommand, err = nextString(allArgs, &i, "info command required"); err != nil { + if opts.InfoCommand, err = nextString("info command required"); err != nil { return err } case "--no-info-command": @@ -2401,7 +2429,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-inline-info": opts.InfoStyle = infoDefault case "--separator": - separator, err := nextString(allArgs, &i, "separator character required") + separator, err := nextString("separator character required") if err != nil { return err } @@ -2410,7 +2438,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { nosep := "" opts.Separator = &nosep case "--scrollbar": - given, bar := optionalNextString(allArgs, &i) + given, bar := optionalNextString() if given { opts.Scrollbar = &bar } else { @@ -2420,7 +2448,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { noBar := "" opts.Scrollbar = &noBar case "--jump-labels": - if opts.JumpLabels, err = nextString(allArgs, &i, "label characters required"); err != nil { + if opts.JumpLabels, err = nextString("label characters required"); err != nil { return err } validateJumpLabels = true @@ -2447,26 +2475,26 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-print-query": opts.PrintQuery = false case "--prompt": - opts.Prompt, err = nextString(allArgs, &i, "prompt string required") + opts.Prompt, err = nextString("prompt string required") if err != nil { return err } case "--pointer": - str, err := nextString(allArgs, &i, "pointer sign required") + str, err := nextString("pointer sign required") if err != nil { return err } str = firstLine(str) opts.Pointer = &str case "--marker": - str, err := nextString(allArgs, &i, "marker sign required") + str, err := nextString("marker sign required") if err != nil { return err } str = firstLine(str) opts.Marker = &str case "--marker-multi-line": - str, err := nextString(allArgs, &i, "marker sign for multi-line entries required") + str, err := nextString("marker sign for multi-line entries required") if err != nil { return err } @@ -2480,7 +2508,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-history": opts.History = nil case "--history": - str, err := nextString(allArgs, &i, "history file path required") + str, err := nextString("history file path required") if err != nil { return err } @@ -2488,7 +2516,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--history-size": - n, err := nextInt(allArgs, &i, "history max size required") + n, err := nextInt("history max size required") if err != nil { return err } @@ -2500,13 +2528,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-header-lines": opts.HeaderLines = 0 case "--header": - str, err := nextString(allArgs, &i, "header string required") + str, err := nextString("header string required") if err != nil { return err } opts.Header = strLines(str) case "--header-lines": - if opts.HeaderLines, err = nextInt(allArgs, &i, "number of header lines required"); err != nil { + if opts.HeaderLines, err = nextInt("number of header lines required"); err != nil { return err } case "--header-first": @@ -2514,26 +2542,26 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-header-first": opts.HeaderFirst = false case "--gap": - if opts.Gap, err = optionalNumeric(allArgs, &i, 1); err != nil { + if opts.Gap, err = optionalNumeric(1); err != nil { return err } case "--no-gap": opts.Gap = 0 case "--ellipsis": - str, err := nextString(allArgs, &i, "ellipsis string required") + str, err := nextString("ellipsis string required") if err != nil { return err } str = firstLine(str) opts.Ellipsis = &str case "--preview": - if opts.Preview.command, err = nextString(allArgs, &i, "preview command required"); err != nil { + if opts.Preview.command, err = nextString("preview command required"); err != nil { return err } case "--no-preview": opts.Preview.command = "" case "--preview-window": - str, err := nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-STYLE][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]") + str, err := nextString("preview window layout required: [up|down|left|right][,SIZE[%]][,border-STYLE][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]") if err != nil { return err } @@ -2543,12 +2571,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-preview-border": opts.Preview.border = tui.BorderNone case "--preview-border": - hasArg, arg := optionalNextString(allArgs, &i) + hasArg, arg := optionalNextString() if opts.Preview.border, err = parseBorder(arg, !hasArg, true); err != nil { return err } case "--height": - str, err := nextString(allArgs, &i, "height required: [~]HEIGHT[%]") + str, err := nextString("height required: [~]HEIGHT[%]") if err != nil { return err } @@ -2556,7 +2584,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--min-height": - if opts.MinHeight, err = nextInt(allArgs, &i, "height required: HEIGHT"); err != nil { + if opts.MinHeight, err = nextInt("height required: HEIGHT"); err != nil { return err } case "--no-height": @@ -2568,12 +2596,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-border": opts.BorderShape = tui.BorderNone case "--border": - hasArg, arg := optionalNextString(allArgs, &i) + hasArg, arg := optionalNextString() if opts.BorderShape, err = parseBorder(arg, !hasArg, false); err != nil { return err } case "--list-border": - hasArg, arg := optionalNextString(allArgs, &i) + hasArg, arg := optionalNextString() if opts.ListBorderShape, err = parseBorder(arg, !hasArg, false); err != nil { return err } @@ -2582,12 +2610,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-list-label": opts.ListLabel.label = "" case "--list-label": - opts.ListLabel.label, err = nextString(allArgs, &i, "label required") + opts.ListLabel.label, err = nextString("label required") if err != nil { return err } case "--list-label-pos": - pos, err := nextString(allArgs, &i, "label position required (positive or negative integer or 'center')") + pos, err := nextString("label position required (positive or negative integer or 'center')") if err != nil { return err } @@ -2597,18 +2625,18 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-header-border": opts.HeaderBorderShape = tui.BorderNone case "--header-border": - hasArg, arg := optionalNextString(allArgs, &i) + hasArg, arg := optionalNextString() if opts.HeaderBorderShape, err = parseBorder(arg, !hasArg, false); err != nil { return err } case "--no-header-label": opts.HeaderLabel.label = "" case "--header-label": - if opts.HeaderLabel.label, err = nextString(allArgs, &i, "header label required"); err != nil { + if opts.HeaderLabel.label, err = nextString("header label required"); err != nil { return err } case "--header-label-pos": - pos, err := nextString(allArgs, &i, "header label position required (positive or negative integer or 'center')") + pos, err := nextString("header label position required (positive or negative integer or 'center')") if err != nil { return err } @@ -2618,18 +2646,18 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-input-border": opts.InputBorderShape = tui.BorderNone case "--input-border": - hasArg, arg := optionalNextString(allArgs, &i) + hasArg, arg := optionalNextString() if opts.InputBorderShape, err = parseBorder(arg, !hasArg, false); err != nil { return err } case "--no-input-label": opts.InputLabel.label = "" case "--input-label": - if opts.InputLabel.label, err = nextString(allArgs, &i, "input label required"); err != nil { + if opts.InputLabel.label, err = nextString("input label required"); err != nil { return err } case "--input-label-pos": - pos, err := nextString(allArgs, &i, "input label position required (positive or negative integer or 'center')") + pos, err := nextString("input label position required (positive or negative integer or 'center')") if err != nil { return err } @@ -2639,12 +2667,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-border-label": opts.BorderLabel.label = "" case "--border-label": - opts.BorderLabel.label, err = nextString(allArgs, &i, "label required") + opts.BorderLabel.label, err = nextString("label required") if err != nil { return err } case "--border-label-pos": - pos, err := nextString(allArgs, &i, "label position required (positive or negative integer or 'center')") + pos, err := nextString("label position required (positive or negative integer or 'center')") if err != nil { return err } @@ -2654,11 +2682,11 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-preview-label": opts.PreviewLabel.label = "" case "--preview-label": - if opts.PreviewLabel.label, err = nextString(allArgs, &i, "preview label required"); err != nil { + if opts.PreviewLabel.label, err = nextString("preview label required"); err != nil { return err } case "--preview-label-pos": - pos, err := nextString(allArgs, &i, "preview label position required (positive or negative integer or 'center')") + pos, err := nextString("preview label position required (positive or negative integer or 'center')") if err != nil { return err } @@ -2666,7 +2694,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--style": - preset, err := nextString(allArgs, &i, "preset name required: [default|minimal|full]") + preset, err := nextString("preset name required: [default|minimal|full]") if err != nil { return err } @@ -2682,7 +2710,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-ambidouble": opts.Ambidouble = false case "--margin": - str, err := nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)") + str, err := nextString("margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)") if err != nil { return err } @@ -2690,7 +2718,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--padding": - str, err := nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)") + str, err := nextString("padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)") if err != nil { return err } @@ -2698,15 +2726,15 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--tabstop": - if opts.Tabstop, err = nextInt(allArgs, &i, "tab stop required"); err != nil { + if opts.Tabstop, err = nextInt("tab stop required"); err != nil { return err } case "--with-shell": - if opts.WithShell, err = nextString(allArgs, &i, "shell command and flags required"); err != nil { + if opts.WithShell, err = nextString("shell command and flags required"); err != nil { return err } case "--listen", "--listen-unsafe": - given, str := optionalNextString(allArgs, &i) + given, str := optionalNextString() addr := defaultListenAddr if given { var err error @@ -2725,7 +2753,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { case "--no-clear": opts.ClearOnExit = false case "--walker": - str, err := nextString(allArgs, &i, "walker options required [file][,dir][,follow][,hidden]") + str, err := nextString("walker options required [file][,dir][,follow][,hidden]") if err != nil { return err } @@ -2733,261 +2761,58 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--walker-root": - if opts.WalkerRoot, err = nextDirs(allArgs, &i); err != nil { + if opts.WalkerRoot, err = nextDirs(); err != nil { return err } case "--walker-skip": - str, err := nextString(allArgs, &i, "directory names to ignore required") + str, err := nextString("directory names to ignore required") if err != nil { return err } opts.WalkerSkip = filterNonEmpty(strings.Split(str, ",")) case "--profile-cpu": - if opts.CPUProfile, err = nextString(allArgs, &i, "file path required: cpu"); err != nil { + if opts.CPUProfile, err = nextString("file path required: cpu"); err != nil { return err } case "--profile-mem": - if opts.MEMProfile, err = nextString(allArgs, &i, "file path required: mem"); err != nil { + if opts.MEMProfile, err = nextString("file path required: mem"); err != nil { return err } case "--profile-block": - if opts.BlockProfile, err = nextString(allArgs, &i, "file path required: block"); err != nil { + if opts.BlockProfile, err = nextString("file path required: block"); err != nil { return err } case "--profile-mutex": - if opts.MutexProfile, err = nextString(allArgs, &i, "file path required: mutex"); err != nil { + if opts.MutexProfile, err = nextString("file path required: mutex"); err != nil { return err } case "--": // Ignored default: - if match, value := optString(arg, "--algo="); match { - if opts.FuzzyAlgo, err = parseAlgo(value); err != nil { - return err - } - } else if match, value := optString(arg, "--tmux="); match { - if opts.Tmux, err = parseTmuxOptions(value, index); err != nil { - return err - } - } else if match, value := optString(arg, "--style="); match { - if err := applyPreset(opts, value); err != nil { - return err - } - } else if match, value := optString(arg, "--scheme="); match { - opts.Scheme = strings.ToLower(value) - } else if match, value := optString(arg, "-q", "--query="); match { + if match, value := optString(arg, "-q"); match { opts.Query = value - } else if match, value := optString(arg, "-f", "--filter="); match { + } else if match, value := optString(arg, "-f"); match { opts.Filter = &value - } else if match, value := optString(arg, "-d", "--delimiter="); match { + } else if match, value := optString(arg, "-d"); match { opts.Delimiter = delimiterRegexp(value) - } else if match, value := optString(arg, "--border="); match { - if opts.BorderShape, err = parseBorder(value, false, false); err != nil { - return err - } - } else if match, value := optString(arg, "--preview-border="); match { - if opts.Preview.border, err = parseBorder(value, false, true); err != nil { - return err - } - } else if match, value := optString(arg, "--list-border="); match { - if opts.ListBorderShape, err = parseBorder(value, false, false); err != nil { - return err - } - } else if match, value := optString(arg, "--list-label="); match { - opts.ListLabel.label = value - } else if match, value := optString(arg, "--list-label-pos="); match { - if err := parseLabelPosition(&opts.ListLabel, value); err != nil { - return err - } - } else if match, value := optString(arg, "--input-border="); match { - if opts.InputBorderShape, err = parseBorder(value, false, false); err != nil { - return err - } - } else if match, value := optString(arg, "--input-label="); match { - opts.InputLabel.label = value - } else if match, value := optString(arg, "--input-label-pos="); match { - if err := parseLabelPosition(&opts.InputLabel, value); err != nil { - return err - } - } else if match, value := optString(arg, "--border-label="); match { - opts.BorderLabel.label = value - } else if match, value := optString(arg, "--border-label-pos="); match { - if err := parseLabelPosition(&opts.BorderLabel, value); err != nil { - return err - } - } else if match, value := optString(arg, "--preview-label="); match { - opts.PreviewLabel.label = value - } else if match, value := optString(arg, "--preview-label-pos="); match { - if err := parseLabelPosition(&opts.PreviewLabel, value); err != nil { - return err - } - } else if match, value := optString(arg, "--wrap-sign="); match { - opts.WrapSign = &value - } else if match, value := optString(arg, "--prompt="); match { - opts.Prompt = value - } else if match, value := optString(arg, "--pointer="); match { - str := firstLine(value) - opts.Pointer = &str - } else if match, value := optString(arg, "--marker="); match { - str := firstLine(value) - opts.Marker = &str - } else if match, value := optString(arg, "--marker-multi-line="); match { - if opts.MarkerMulti, err = parseMarkerMultiLine(firstLine(value)); err != nil { - return err - } - } else if match, value := optString(arg, "-n", "--nth="); match { + } else if match, value := optString(arg, "-n"); match { if opts.Nth, err = splitNth(value); err != nil { return err } - } else if match, value := optString(arg, "--with-nth="); match { - if opts.WithNth, err = splitNth(value); err != nil { - return err - } - } else if match, _ := optString(arg, "-s", "--sort="); match { + } else if match, _ := optString(arg, "-s"); match { opts.Sort = 1 // Don't care - } else if match, value := optString(arg, "-m", "--multi="); match { + } else if match, value := optString(arg, "-m"); match { if opts.Multi, err = atoi(value); err != nil { return err } - } else if match, value := optString(arg, "--height="); match { - if opts.Height, err = parseHeight(value, index); err != nil { - return err - } - } else if match, value := optString(arg, "--min-height="); match { - if opts.MinHeight, err = atoi(value); err != nil { - return err - } - } else if match, value := optString(arg, "--layout="); match { - if opts.Layout, err = parseLayout(value); err != nil { - return err - } - } else if match, value := optString(arg, "--info="); match { - if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(value); err != nil { - return err - } - } else if match, value := optString(arg, "--info-command="); match { - opts.InfoCommand = value - } else if match, value := optString(arg, "--separator="); match { - opts.Separator = &value - } else if match, value := optString(arg, "--scrollbar="); match { - opts.Scrollbar = &value - } else if match, value := optString(arg, "--toggle-sort="); match { - if err := parseToggleSort(opts.Keymap, value); err != nil { - return err - } - } else if match, value := optString(arg, "--expect="); match { - chords, err := parseKeyChords(value, "key names required") - if err != nil { - return err - } - for k, v := range chords { - opts.Expect[k] = v - } - } else if match, value := optString(arg, "--tiebreak="); match { - if opts.Criteria, err = parseTiebreak(value); err != nil { - return err - } - } else if match, value := optString(arg, "--color="); match { - if opts.Theme, err = parseTheme(opts.Theme, value); err != nil { - return err - } - } else if match, value := optString(arg, "--bind="); match { - if err := parseKeymap(opts.Keymap, value); err != nil { - return err - } - } else if match, value := optString(arg, "--history="); match { - if err := setHistory(value); err != nil { - return err - } - } else if match, value := optString(arg, "--history-size="); match { - n, err := atoi(value) - if err != nil { - return err - } - if err := setHistoryMax(n); err != nil { - return err - } - } else if match, value := optString(arg, "--header="); match { - opts.Header = strLines(value) - } else if match, value := optString(arg, "--header-lines="); match { - if opts.HeaderLines, err = atoi(value); err != nil { - return err - } - } else if match, value := optString(arg, "--gap="); match { - if opts.Gap, err = atoi(value); err != nil { - return err - } - } else if match, value := optString(arg, "--ellipsis="); match { - str := firstLine(value) - opts.Ellipsis = &str - } else if match, value := optString(arg, "--preview="); match { - opts.Preview.command = value - } else if match, value := optString(arg, "--preview-window="); match { - if err := parsePreviewWindow(&opts.Preview, value); err != nil { - return err - } - } else if match, value := optString(arg, "--margin="); match { - if opts.Margin, err = parseMargin("margin", value); err != nil { - return err - } - } else if match, value := optString(arg, "--padding="); match { - if opts.Padding, err = parseMargin("padding", value); err != nil { - return err - } - } else if match, value := optString(arg, "--tabstop="); match { - if opts.Tabstop, err = atoi(value); err != nil { - return err - } - } else if match, value := optString(arg, "--with-shell="); match { - opts.WithShell = value - } else if match, value := optString(arg, "--listen="); match { - addr, err := parseListenAddress(value) - if err != nil { - return err - } - opts.ListenAddr = &addr - opts.Unsafe = false - } else if match, value := optString(arg, "--listen-unsafe="); match { - addr, err := parseListenAddress(value) - if err != nil { - return err - } - opts.ListenAddr = &addr - opts.Unsafe = true - } else if match, value := optString(arg, "--walker="); match { - if opts.WalkerOpts, err = parseWalkerOpts(value); err != nil { - return err - } - } else if match, value := optString(arg, "--walker-root="); match { - if !isDir(value) { - return errors.New("not a directory: " + value) - } - dirs, _ := nextDirs(allArgs, &i) - opts.WalkerRoot = append([]string{value}, dirs...) - } else if match, value := optString(arg, "--walker-skip="); match { - opts.WalkerSkip = filterNonEmpty(strings.Split(value, ",")) - } else if match, value := optString(arg, "--hscroll-off="); match { - if opts.HscrollOff, err = atoi(value); err != nil { - return err - } - } else if match, value := optString(arg, "--scroll-off="); match { - if opts.ScrollOff, err = atoi(value); err != nil { - return err - } - } else if match, value := optString(arg, "--jump-labels="); match { - opts.JumpLabels = value - validateJumpLabels = true - } else if match, value := optString(arg, "--tail="); match { - if opts.Tail, err = atoi(value); err != nil { - return err - } - if opts.Tail <= 0 { - return errors.New("number of items to keep must be a positive integer") - } } else { return errors.New("unknown option: " + arg) } } + + if val != nil { + return errors.New("unexpected value for " + arg + ": " + *val) + } } *index += len(allArgs) From 6783417504a2e2a378b8a55a5dd111c18a051425 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Wed, 8 Jan 2025 10:00:57 +0900 Subject: [PATCH 3/5] Do not export $LINES and $COLUMNS for non-preview processes Fix #4164 --- src/terminal.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/terminal.go b/src/terminal.go index cb560b9..2723d9d 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -1012,6 +1012,14 @@ func (t *Terminal) deferActivation() bool { } func (t *Terminal) environ() []string { + return t.environImpl(false) +} + +func (t *Terminal) environForPreview() []string { + return t.environImpl(true) +} + +func (t *Terminal) environImpl(forPreview bool) []string { env := os.Environ() if t.listenPort != nil { env = append(env, fmt.Sprintf("FZF_PORT=%d", *t.listenPort)) @@ -1037,9 +1045,11 @@ func (t *Terminal) environ() []string { if pwindowSize.Lines > 0 { lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines) columns := fmt.Sprintf("COLUMNS=%d", pwindowSize.Columns) - env = append(env, lines) + if forPreview { + env = append(env, lines) + env = append(env, columns) + } env = append(env, "FZF_PREVIEW_"+lines) - env = append(env, columns) env = append(env, "FZF_PREVIEW_"+columns) env = append(env, fmt.Sprintf("FZF_PREVIEW_TOP=%d", t.tui.Top()+t.pwindow.Top())) env = append(env, fmt.Sprintf("FZF_PREVIEW_LEFT=%d", t.pwindow.Left())) @@ -4132,7 +4142,7 @@ func (t *Terminal) Loop() error { if len(command) > 0 && t.canPreview() { _, list := t.buildPlusList(command, false) t.cancelPreview() - t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.evaluateScrollOffset(), list, t.environ()}) + t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.evaluateScrollOffset(), list, t.environForPreview()}) } } @@ -4524,7 +4534,7 @@ func (t *Terminal) Loop() error { if valid { t.cancelPreview() t.previewBox.Set(reqPreviewEnqueue, - previewRequest{t.previewOpts.command, t.evaluateScrollOffset(), list, t.environ()}) + previewRequest{t.previewOpts.command, t.evaluateScrollOffset(), list, t.environForPreview()}) } } else { // Discard the preview content so that it won't accidentally appear From 55e3c73221e1f6e4c48f2c3f7517befc7d0945cf Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Thu, 9 Jan 2025 17:00:21 +0900 Subject: [PATCH 4/5] fzf-preview.sh: Support FILEPATH:LINE[:COL] argument --- bin/fzf-preview.sh | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/bin/fzf-preview.sh b/bin/fzf-preview.sh index ecec41a..e74e46e 100755 --- a/bin/fzf-preview.sh +++ b/bin/fzf-preview.sh @@ -9,11 +9,23 @@ # - https://iterm2.com/utilities/imgcat if [[ $# -ne 1 ]]; then - >&2 echo "usage: $0 FILENAME" + >&2 echo "usage: $0 FILENAME[:LINENO][:IGNORED]" exit 1 fi file=${1/#\~\//$HOME/} + +center=0 +if [[ ! -r $file ]]; then + if [[ $file =~ ^(.+):([0-9]+)\ *$ ]] && [[ -r ${BASH_REMATCH[1]} ]]; then + file=${BASH_REMATCH[1]} + center=${BASH_REMATCH[2]} + elif [[ $file =~ ^(.+):([0-9]+):[0-9]+\ *$ ]] && [[ -r ${BASH_REMATCH[1]} ]]; then + file=${BASH_REMATCH[1]} + center=${BASH_REMATCH[2]} + fi +fi + type=$(file --brief --dereference --mime -- "$file") if [[ ! $type =~ image/ ]]; then @@ -32,7 +44,7 @@ if [[ ! $type =~ image/ ]]; then exit fi - ${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never -- "$file" + ${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never --highlight-line="${center:-0}" -- "$file" exit fi From 93c029960666de2d1784f351ac37e5a74cf82e2e Mon Sep 17 00:00:00 2001 From: Kid <44045911+kidonng@users.noreply.github.com> Date: Thu, 9 Jan 2025 18:16:24 +0800 Subject: [PATCH 5/5] [fish] remove defunct bind feature detection (#4165) --- shell/key-bindings.fish | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/shell/key-bindings.fish b/shell/key-bindings.fish index cedf3f2..aadcffb 100644 --- a/shell/key-bindings.fish +++ b/shell/key-bindings.fish @@ -133,14 +133,12 @@ function fzf_key_bindings bind \ec fzf-cd-widget end - if bind -M insert &> /dev/null - bind -M insert \cr fzf-history-widget - if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND" - bind -M insert \ct fzf-file-widget - end - if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND" - bind -M insert \ec fzf-cd-widget - end + bind -M insert \cr fzf-history-widget + if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND" + bind -M insert \ct fzf-file-widget + end + if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND" + bind -M insert \ec fzf-cd-widget end function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'