diff --git a/CHANGELOG.md b/CHANGELOG.md index e6cf9ef..5e6e7cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,15 @@ CHANGELOG This version introduces three new border types, `--list-border`, `--input-border`, and `--header-border`, offering much greater flexibility for customizing the user interface. -Also, fzf now offers three "style presets" for easier customization, which can be activated using the `--style=[default|minimal|full]` option. + + +Also, fzf now offers "style presets" for quick customization, which can be activated using the `--style` option. + +| Preset | Screenshot | +| :--- | :--- | +| `default` | | +| `full` | | +| `minimal` | | - Style presets (#4160) - `--style=full` @@ -59,6 +67,10 @@ Also, fzf now offers three "style presets" for easier customization, which can b ``` - Added `toggle-multi-line` action - Added `toggle-hscroll` action +- A single-character delimiter is now treated as a plain string delimiter rather than a regular expression delimiter, even if it's a regular expression meta-character. + - This means you can just write `--delimiter '|'` instead of escaping it as `--delimiter '\|'` +- Bug fixes +- Bug fixes in fish scripts (thanks to @bitraid) 0.57.0 ------ diff --git a/README.md b/README.md index ae5c864..ffd5743 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ I would like to thank all the sponsors of this project who make it possible for If you'd like to sponsor this project, please visit https://github.com/sponsors/junegunn. -User avatar: miyanokomiyaUser avatar: Jon GjengsetUser avatar: Kyle L. DavisUser avatar: Frederick ZhangUser avatar: Moritz DietzUser avatar: Mikkel MalmbergUser avatar: Pierre DubouilhUser avatar: Fulvio ScapinUser avatar: Ryan Roden-CorrentUser avatar: Jordan ArentsenUser avatar: Alex ViscreanuUser avatar: David BalateroUser avatar: User avatar: Ben ElanUser avatar: Paweł DudaUser avatar: User avatar: Damien RajonUser avatar: ArtBITUser avatar: User avatar: HovisUser avatar: Yarden zamirUser avatar: Darius JondaUser avatar: Cristian DominguezUser avatar: Chang-Hung LiangUser avatar: Ben LechlitnerUser avatar: george looshchUser avatar: Takumi KAGIYAMAUser avatar: Paul OLeary McCannUser avatar: Robert BeegerUser avatar: Yoway BuornUser avatar: Josh ScalisiUser avatar: Alec ScottUser avatar: thanks.devUser avatar: Artur SapekUser avatar: Guillaume GelinUser avatar: User avatar: Rob LevyUser avatar: Gloria ZhaoUser avatar: Markus KollerUser avatar: User avatar: jamesobUser avatar: Johan Le BrayUser avatar: Panos LampropoulosUser avatar: bespinianUser avatar: Markus Schneider-PargmannUser avatar: Ben SmithUser avatar: Charlie EganUser avatar: Tyler HobbsUser avatar: Neil ParikhUser avatar: Jamie SchembriUser avatar: dockienUser avatar: Russell GilmoreUser avatar: Lukas WaymannUser avatar: Farzad SadeghiUser avatar: User avatar: NorbsUser avatar: YuUser avatar: Fotios ValasiadisUser avatar: Joonas KorhonenUser avatar: Bruno PazUser avatar: Filippos LanarasUser avatar: Freshleaf MediaUser avatar: User avatar: Timothy Bennett +User avatar: miyanokomiyaUser avatar: Jon GjengsetUser avatar: Kyle L. DavisUser avatar: Frederick ZhangUser avatar: Moritz DietzUser avatar: Mikkel MalmbergUser avatar: Pierre DubouilhUser avatar: Fulvio ScapinUser avatar: Ryan Roden-CorrentUser avatar: Jordan ArentsenUser avatar: Alex ViscreanuUser avatar: David BalateroUser avatar: User avatar: Ben ElanUser avatar: Paweł DudaUser avatar: User avatar: Damien RajonUser avatar: ArtBITUser avatar: User avatar: HovisUser avatar: Darius JondaUser avatar: Cristian DominguezUser avatar: Chang-Hung LiangUser avatar: Ben LechlitnerUser avatar: george looshchUser avatar: Takumi KAGIYAMAUser avatar: Paul OLeary McCannUser avatar: Robert BeegerUser avatar: Yoway BuornUser avatar: Josh ScalisiUser avatar: Alec ScottUser avatar: thanks.devUser avatar: Artur SapekUser avatar: Guillaume GelinUser avatar: User avatar: Rob LevyUser avatar: Gloria ZhaoUser avatar: Markus KollerUser avatar: User avatar: jamesobUser avatar: Johan Le BrayUser avatar: Panos LampropoulosUser avatar: bespinianUser avatar: Markus Schneider-PargmannUser avatar: Ben SmithUser avatar: Charlie EganUser avatar: Tyler HobbsUser avatar: Neil ParikhUser avatar: Jamie SchembriUser avatar: dockienUser avatar: Russell GilmoreUser avatar: Lukas WaymannUser avatar: Farzad SadeghiUser avatar: User avatar: YuUser avatar: Fotios ValasiadisUser avatar: Joonas KorhonenUser avatar: Bruno PazUser avatar: Filippos LanarasUser avatar: Freshleaf MediaUser avatar: User avatar: Timothy BennettUser avatar: Daniel HornerUser avatar: Cosimo MatteiniUser avatar: Paul Becker Table of Contents ----------------- 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 diff --git a/shell/key-bindings.fish b/shell/key-bindings.fish index cedf3f2..3155235 100644 --- a/shell/key-bindings.fish +++ b/shell/key-bindings.fish @@ -73,11 +73,10 @@ function fzf_key_bindings builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | string replace -r '^\d*\t' '' | read -lz result and commandline -- $result else - set -l line 0 - for i in (builtin history -z --reverse | string split0) - set line (math $line + 1) - string escape -n -- $line\t$i - end | string join0 | string replace -a '\n' '\n\t' | string unescape -n | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | string replace -r '^\d*\t' '' | read -lz result + set -l h (builtin history -z | string split0) + for i in (seq (count $h) -1 1) + string join0 -- $i\t(string replace -a -- \n \n\t $h[$i] | string collect) + end | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | string replace -r '^\d*\t' '' | read -lz result and commandline -- $result end else @@ -133,14 +132,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' diff --git a/src/ansi.go b/src/ansi.go index 638d7ef..37d9c76 100644 --- a/src/ansi.go +++ b/src/ansi.go @@ -98,11 +98,11 @@ func isPrint(c uint8) bool { return '\x20' <= c && c <= '\x7e' } -func matchOperatingSystemCommand(s string) int { +func matchOperatingSystemCommand(s string, start int) int { // `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)` - // ^ match starting here + // ^ match starting here after the first printable character // - i := 5 // prefix matched in nextAnsiEscapeSequence() + i := start // prefix matched in nextAnsiEscapeSequence() for ; i < len(s) && isPrint(s[i]); i++ { } if i < len(s) { @@ -156,7 +156,7 @@ func isCtrlSeqStart(c uint8) bool { // nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to // calling FindStringIndex() on the below regex (which was originally used): // -// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)" +// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)" func nextAnsiEscapeSequence(s string) (int, int) { // fast check for ANSI escape sequences i := 0 @@ -191,12 +191,20 @@ Loop: } } - // match: `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)` - if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) && - (s[i+3] == ';' || s[i+3] == ':') && isPrint(s[i+4]) { + // match: `\x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)` + if i+5 < len(s) && s[i+1] == ']' { + j := 2 + // \x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07) + // ------ + for ; i+j < len(s) && isNumeric(s[i+j]); j++ { + } - if j := matchOperatingSystemCommand(s[i:]); j != -1 { - return i, i + j + // \x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07) + // --------------- + if j > 2 && i+j+1 < len(s) && (s[i+j] == ';' || s[i+j] == ':') && isPrint(s[i+j+1]) { + if k := matchOperatingSystemCommand(s[i:], j+2); k != -1 { + return i, i + k + } } } @@ -310,20 +318,15 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo return trimmed, nil, state } -func parseAnsiCode(s string, delimiter byte) (int, byte, string) { +func parseAnsiCode(s string) (int, string) { var remaining string var i int - if delimiter == 0 { - // Faster than strings.IndexAny(";:") - i = strings.IndexByte(s, ';') - if i < 0 { - i = strings.IndexByte(s, ':') - } - } else { - i = strings.IndexByte(s, delimiter) + // Faster than strings.IndexAny(";:") + i = strings.IndexByte(s, ';') + if i < 0 { + i = strings.IndexByte(s, ':') } if i >= 0 { - delimiter = s[i] remaining = s[i+1:] s = s[:i] } @@ -335,14 +338,14 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) { for _, ch := range stringBytes(s) { ch -= '0' if ch > 9 { - return -1, delimiter, remaining + return -1, remaining } code = code*10 + int(ch) } - return code, delimiter, remaining + return code, remaining } - return -1, delimiter, remaining + return -1, remaining } func interpretCode(ansiCode string, prevState *ansiState) ansiState { @@ -378,11 +381,10 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState { state256 := 0 ptr := &state.fg - var delimiter byte count := 0 for len(ansiCode) != 0 { var num int - if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 { + if num, ansiCode = parseAnsiCode(ansiCode); num != -1 { count++ switch state256 { case 0: diff --git a/src/ansi_test.go b/src/ansi_test.go index 55f7fc0..e343123 100644 --- a/src/ansi_test.go +++ b/src/ansi_test.go @@ -335,6 +335,28 @@ func TestExtractColor(t *testing.T) { assert((*offsets)[0], 0, 6, 2, -1, true) assert((*offsets)[1], 6, 11, 200, 100, false) }) + + state = nil + var color24 tui.Color = (1 << 24) + (180 << 16) + (190 << 8) + 254 + src = "\x1b[1mhello \x1b[22;1;38:2:180:190:254mworld" + check(func(offsets *[]ansiOffset, state *ansiState) { + if len(*offsets) != 2 { + t.Fail() + } + if state.fg != color24 || state.attr != 1 { + t.Fail() + } + assert((*offsets)[0], 0, 6, -1, -1, true) + assert((*offsets)[1], 6, 11, color24, -1, true) + }) + + src = "\x1b]133;A\x1b\\hello \x1b]133;C\x1b\\world" + check(func(offsets *[]ansiOffset, state *ansiState) { + if len(*offsets) != 1 { + t.Fail() + } + assert((*offsets)[0], 0, 11, color24, -1, true) + }) } func TestAnsiCodeStringConversion(t *testing.T) { @@ -381,7 +403,7 @@ func TestParseAnsiCode(t *testing.T) { {"-2", "", -1}, } for _, x := range tests { - n, _, s := parseAnsiCode(x.In, 0) + n, s := parseAnsiCode(x.In) if n != x.N || s != x.Exp { t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp) } diff --git a/src/options.go b/src/options.go index 12861b0..ac4a1ac 100644 --- a/src/options.go +++ b/src/options.go @@ -332,8 +332,21 @@ func (o *previewOpts) Toggle() { o.hidden = !o.hidden } -func (o *previewOpts) HasBorderRight() bool { - return o.border.HasRight() || o.border == tui.BorderLine && o.position == posLeft +func (o *previewOpts) Border() tui.BorderShape { + shape := o.border + if shape == tui.BorderLine { + switch o.position { + case posUp: + shape = tui.BorderBottom + case posDown: + shape = tui.BorderTop + case posLeft: + shape = tui.BorderRight + case posRight: + shape = tui.BorderLeft + } + } + return shape } func defaultTmuxOptions(index int) *tmuxOptions { @@ -700,54 +713,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 +734,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) @@ -812,18 +755,23 @@ func delimiterRegexp(str string) Delimiter { // Special handling of \t str = strings.ReplaceAll(str, "\\t", "\t") - // 1. Pattern does not contain any special character + // 1. Pattern is a single character + if len([]rune(str)) == 1 { + return Delimiter{str: &str} + } + + // 2. Pattern does not contain any special character if regexp.QuoteMeta(str) == str { return Delimiter{str: &str} } rx, e := regexp.Compile(str) - // 2. Pattern is not a valid regular expression + // 3. Pattern is not a valid regular expression if e != nil { return Delimiter{str: &str} } - // 3. Pattern as regular expression. Slow. + // 4. Pattern as regular expression. Slow. return Delimiter{regex: rx} } @@ -2077,6 +2025,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 +2068,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 +2185,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 +2202,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 +2218,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 +2232,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 +2240,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 +2264,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 +2272,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 +2280,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 +2289,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 +2297,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 +2311,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 +2319,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 +2333,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 +2348,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 +2372,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 +2396,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 +2414,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 +2426,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 +2434,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 +2447,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 +2456,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 +2466,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 +2493,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 +2526,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 +2534,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 +2546,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 +2560,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 +2589,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 +2602,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 +2614,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 +2628,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 +2643,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 +2664,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 +2685,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 +2700,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 +2712,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 +2728,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 +2736,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 +2744,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 +2771,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 +2779,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) diff --git a/src/options_test.go b/src/options_test.go index 5def73d..40d2920 100644 --- a/src/options_test.go +++ b/src/options_test.go @@ -9,9 +9,13 @@ import ( ) func TestDelimiterRegex(t *testing.T) { - // Valid regex + // Valid regex, but a single character -> string delim := delimiterRegexp(".") - if delim.regex == nil || delim.str != nil { + if delim.regex != nil || *delim.str != "." { + t.Error(delim) + } + delim = delimiterRegexp("|") + if delim.regex != nil || *delim.str != "|" { t.Error(delim) } // Broken regex -> string diff --git a/src/terminal.go b/src/terminal.go index f22ec1c..853adb8 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -782,7 +782,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor // Minimum height required to render fzf excluding margin and padding effectiveMinHeight := minHeight if previewBox != nil && opts.Preview.aboveOrBelow() { - effectiveMinHeight += 1 + borderLines(opts.Preview.border) + effectiveMinHeight += 1 + borderLines(opts.Preview.Border()) } if noSeparatorLine(opts.InfoStyle, opts.Separator == nil || uniseg.StringWidth(*opts.Separator) > 0) { effectiveMinHeight-- @@ -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())) @@ -1093,6 +1103,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++ } @@ -1508,12 +1521,12 @@ func calculateSize(base int, size sizeSpec, occupied int, minSize int) int { } func (t *Terminal) minPreviewSize(opts *previewOpts) (int, int) { - minPreviewWidth := 1 + borderColumns(opts.border, t.borderWidth) - minPreviewHeight := 1 + borderLines(opts.border) + minPreviewWidth := 1 + borderColumns(opts.Border(), t.borderWidth) + minPreviewHeight := 1 + borderLines(opts.Border()) switch opts.position { case posLeft, posRight: - if len(t.scrollbar) > 0 && !opts.HasBorderRight() { + if len(t.scrollbar) > 0 && !opts.Border().HasRight() { // Need a column to show scrollbar minPreviewWidth++ } @@ -1743,11 +1756,14 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { t.areaLines = height t.areaColumns = width + // If none of the inner borders has the right side, but the outer border does, increase the list width by 1 column + listStickToRight := t.borderShape.HasRight() && !t.listBorderShape.HasRight() && !t.inputBorderShape.HasRight() && + (!t.headerVisible || !t.headerBorderShape.HasRight() || t.visibleHeaderLines() == 0) + // Set up preview window noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) if forcePreview || t.needPreviewWindow() { var resizePreviewWindows func(previewOpts *previewOpts) - stickToRight := false resizePreviewWindows = func(previewOpts *previewOpts) { t.activePreviewOpts = previewOpts if previewOpts.size.size == 0 { @@ -1757,19 +1773,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { createPreviewWindow := func(y int, x int, w int, h int) { pwidth := w pheight := h - shape := previewOpts.border - if shape == tui.BorderLine { - switch previewOpts.position { - case posUp: - shape = tui.BorderBottom - case posDown: - shape = tui.BorderTop - case posLeft: - shape = tui.BorderRight - case posRight: - shape = tui.BorderLeft - } - } + shape := previewOpts.Border() previewBorder := tui.MakeBorderStyle(shape, t.unicode) t.pborder = t.tui.NewWindow(y, x, w, h, tui.WindowPreview, previewBorder, false) pwidth -= borderColumns(shape, bw) @@ -1813,11 +1817,9 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { if previewOpts.hidden { return } - // If none of the inner borders has the right side, but the outer border does, increase the width by 1 column - stickToRight = t.borderShape.HasRight() && - !previewOpts.HasBorderRight() && !t.listBorderShape.HasRight() && !t.inputBorderShape.HasRight() && - (!t.headerVisible || !t.headerBorderShape.HasRight() || t.visibleHeaderLines() == 0) - if stickToRight { + + listStickToRight = listStickToRight && !previewOpts.Border().HasRight() + if listStickToRight { innerWidth++ width++ } @@ -1890,9 +1892,12 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { innerBorderFn(marginInt[0], marginInt[3]+pwidth, width-pwidth, height) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height) } else { - // NOTE: fzf --preview 'cat {}' --preview-window border-left --border - stickToRight = !previewOpts.HasBorderRight() && t.borderShape.HasRight() - if stickToRight { + // NOTE: Relaxed condition for the following cases + // fzf --preview 'seq 500' --preview-window border-left --border + // fzf --preview 'seq 500' --preview-window border-left --border --list-border + // fzf --preview 'seq 500' --preview-window border-left --border --input-border + listStickToRight = t.borderShape.HasRight() && !previewOpts.Border().HasRight() + if listStickToRight { innerWidth++ width++ } @@ -1906,7 +1911,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { } resizePreviewWindows(&t.previewOpts) - if t.borderShape.HasRight() && !stickToRight { + if t.borderShape.HasRight() && !listStickToRight { // Need to clear the extra margin between the borders // fzf --preview 'seq 1000' --preview-window border-left --bind space:change-preview-window:border-rounded --border vertical // fzf --preview 'seq 1000' --preview-window up,hidden --bind space:toggle-preview --border vertical @@ -1929,7 +1934,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { // Without preview window if t.window == nil { - if t.borderShape.HasRight() && !hasListBorder { + if listStickToRight { // Put scrollbar closer to the right border for consistent look innerWidth++ width++ @@ -2014,9 +2019,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { // Print border label t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, false) t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false) - if t.pborder != nil && t.pwindow.Height() != t.pborder.Height() { // To address --preview-border=line with different positions - t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.border, false) - } + t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.Border(), false) t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, false) t.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, false) } @@ -2031,7 +2034,7 @@ func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts label } switch borderShape { - case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble, tui.BorderLine: + case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble: if redrawBorder { window.DrawHBorder() } @@ -3214,11 +3217,11 @@ func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int) t.previewer.xw = xw } xshift := -1 - t.borderWidth - if !t.activePreviewOpts.HasBorderRight() { + if !t.activePreviewOpts.Border().HasRight() { xshift = -1 } yshift := 1 - if !t.activePreviewOpts.border.HasTop() { + if !t.activePreviewOpts.Border().HasTop() { yshift = 0 } for i := yoff; i < height; i++ { @@ -3880,13 +3883,13 @@ func (t *Terminal) Loop() error { if t.activePreviewOpts.aboveOrBelow() { if t.activePreviewOpts.size.percent { newContentHeight := int(float64(contentHeight) * 100. / (100. - t.activePreviewOpts.size.size)) - contentHeight = util.Max(contentHeight+1+borderLines(t.activePreviewOpts.border), newContentHeight) + contentHeight = util.Max(contentHeight+1+borderLines(t.activePreviewOpts.Border()), newContentHeight) } else { - contentHeight += int(t.activePreviewOpts.size.size) + borderLines(t.activePreviewOpts.border) + contentHeight += int(t.activePreviewOpts.size.size) + borderLines(t.activePreviewOpts.Border()) } } else { // Minimum height if preview window can appear - contentHeight = util.Max(contentHeight, 1+borderLines(t.activePreviewOpts.border)) + contentHeight = util.Max(contentHeight, 1+borderLines(t.activePreviewOpts.Border())) } } return util.Min(termHeight, contentHeight+pad) @@ -4129,7 +4132,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()}) } } @@ -4240,7 +4243,7 @@ func (t *Terminal) Loop() error { case reqRedrawBorderLabel: t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true) case reqRedrawPreviewLabel: - t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.border, true) + t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.Border(), true) case reqReinit: t.tui.Resume(t.fullscreen, true) t.fullRedraw() @@ -4521,7 +4524,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 diff --git a/src/tui/tui.go b/src/tui/tui.go index 2bd9caf..0ab4874 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -897,6 +897,10 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp theme.Border = o(baseTheme.Border, theme.Border) theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel) + undefined := NewColorAttr() + scrollbarDefined := theme.Scrollbar != undefined + previewBorderDefined := theme.PreviewBorder != undefined + // These colors are not defined in the base themes theme.ListFg = o(theme.Fg, theme.ListFg) theme.ListBg = o(theme.Bg, theme.ListBg) @@ -913,7 +917,17 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp theme.ListBorder = o(theme.Border, theme.ListBorder) theme.Separator = o(theme.ListBorder, theme.Separator) theme.Scrollbar = o(theme.ListBorder, theme.Scrollbar) - theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar) + /* + --color list-border:green + --color scrollbar:red + --color scrollbar:red,list-border:green + --color scrollbar:red,preview-border:green + */ + if scrollbarDefined && !previewBorderDefined { + theme.PreviewScrollbar = o(theme.Scrollbar, theme.PreviewScrollbar) + } else { + theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar) + } if hasInputWindow { theme.InputBg = o(theme.Bg, theme.InputBg) } else { 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