Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fzf for openSUSE:Factory checked in at 2021-11-17 01:14:12 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fzf (Old) and /work/SRC/openSUSE:Factory/.fzf.new.1890 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fzf" Wed Nov 17 01:14:12 2021 rev:20 rq:931747 version:0.28.0 Changes: -------- --- /work/SRC/openSUSE:Factory/fzf/fzf.changes 2021-10-15 23:05:09.858148825 +0200 +++ /work/SRC/openSUSE:Factory/.fzf.new.1890/fzf.changes 2021-11-17 01:15:22.878191043 +0100 @@ -1,0 +2,9 @@ +Sat Nov 13 19:44:54 UTC 2021 - Dirk M??ller <dmuel...@suse.com> + +- update to 0.28.0: + * Added `--header-first` option to print header before the prompt line + * Added `--scroll-off=LINES` option + * Fixed bug where preview window is not updated on `reload` + * fzf on Windows will also use `$SHELL` to execute external programs + +------------------------------------------------------------------- Old: ---- 0.27.3.tar.gz New: ---- 0.28.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fzf.spec ++++++ --- /var/tmp/diff_new_pack.XVZOEY/_old 2021-11-17 01:15:23.474191071 +0100 +++ /var/tmp/diff_new_pack.XVZOEY/_new 2021-11-17 01:15:23.474191071 +0100 @@ -17,7 +17,7 @@ Name: fzf -Version: 0.27.3 +Version: 0.28.0 Release: 0 Summary: A command-line fuzzy finder License: MIT ++++++ 0.27.3.tar.gz -> 0.28.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/CHANGELOG.md new/fzf-0.28.0/CHANGELOG.md --- old/fzf-0.27.3/CHANGELOG.md 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/CHANGELOG.md 2021-11-03 17:05:07.000000000 +0100 @@ -1,6 +1,24 @@ CHANGELOG ========= +0.28.0 +------ +- Added `--header-first` option to print header before the prompt line + ```sh + fzf --header $'Welcome to fzf\n??????????????????????????????????????????' --reverse --height 30% --border --header-first + ``` +- Added `--scroll-off=LINES` option (similar to `scrolloff` option of Vim) + - You can set it to a very large number so that the cursor stays in the + middle of the screen while scrolling + ```sh + fzf --scroll-off=5 + fzf --scroll-off=999 + ``` +- Fixed bug where preview window is not updated on `reload` (#2644) +- fzf on Windows will also use `$SHELL` to execute external programs + - See #2638 and #2647 + - Thanks to @rashil2000, @vovcacik, and @janlazo + 0.27.3 ------ - Preview window is `hidden` by default when there are `preview` bindings but diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/Makefile new/fzf-0.28.0/Makefile --- old/fzf-0.27.3/Makefile 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/Makefile 2021-11-03 17:05:07.000000000 +0100 @@ -102,6 +102,7 @@ grep -qF $(VERSION) install.ps1 # Make release note out of CHANGELOG.md + mkdir -p tmp sed -n '/^$(VERSION_REGEX)$$/,/^[0-9]/p' CHANGELOG.md | tail -r | \ sed '1,/^ *$$/d' | tail -r | sed 1,2d | tee tmp/release-note diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/install new/fzf-0.28.0/install --- old/fzf-0.27.3/install 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/install 2021-11-03 17:05:07.000000000 +0100 @@ -2,7 +2,7 @@ set -u -version=0.27.3 +version=0.28.0 auto_completion= key_bindings= update_config=2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/install.ps1 new/fzf-0.28.0/install.ps1 --- old/fzf-0.27.3/install.ps1 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/install.ps1 2021-11-03 17:05:07.000000000 +0100 @@ -1,4 +1,4 @@ -$version="0.27.3" +$version="0.28.0" $fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/main.go new/fzf-0.28.0/main.go --- old/fzf-0.27.3/main.go 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/main.go 2021-11-03 17:05:07.000000000 +0100 @@ -1,11 +1,11 @@ package main import ( - "github.com/junegunn/fzf/src" + fzf "github.com/junegunn/fzf/src" "github.com/junegunn/fzf/src/protector" ) -var version string = "0.27" +var version string = "0.28" var revision string = "devel" func main() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/man/man1/fzf-tmux.1 new/fzf-0.28.0/man/man1/fzf-tmux.1 --- old/fzf-0.27.3/man/man1/fzf-tmux.1 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/man/man1/fzf-tmux.1 2021-11-03 17:05:07.000000000 +0100 @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. .. -.TH fzf-tmux 1 "Oct 2021" "fzf 0.27.3" "fzf-tmux - open fzf in tmux split pane" +.TH fzf-tmux 1 "Nov 2021" "fzf 0.28.0" "fzf-tmux - open fzf in tmux split pane" .SH NAME fzf-tmux - open fzf in tmux split pane diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/man/man1/fzf.1 new/fzf-0.28.0/man/man1/fzf.1 --- old/fzf-0.27.3/man/man1/fzf.1 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/man/man1/fzf.1 2021-11-03 17:05:07.000000000 +0100 @@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. .. -.TH fzf 1 "Oct 2021" "fzf 0.27.3" "fzf - a command-line fuzzy finder" +.TH fzf 1 "Nov 2021" "fzf 0.28.0" "fzf - a command-line fuzzy finder" .SH NAME fzf - a command-line fuzzy finder @@ -135,10 +135,14 @@ Keep the right end of the line visible when it's too long. Effective only when the query string is empty. .TP +.BI "--scroll-off=" "LINES" +Number of screen lines to keep above or below when scrolling to the top or to +the bottom (default: 0). +.TP .B "--no-hscroll" Disable horizontal scroll .TP -.BI "--hscroll-off=" "COL" +.BI "--hscroll-off=" "COLS" Number of screen columns to keep to the right of the highlighted substring (default: 10). Setting it to a large value will cause the text to be positioned on the center of the screen. @@ -295,6 +299,9 @@ The first N lines of the input are treated as the sticky header. When \fB--with-nth\fR is set, the lines are transformed just like the other lines that follow. +.TP +.B "--header-first" +Print header before the prompt line .SS Display .TP .B "--ansi" @@ -853,6 +860,7 @@ \fBpreview-top\fR \fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR) \fBprint-query\fR (print query and exit) + \fBput\fR (put the character to the prompt) \fBrefresh-preview\fR \fBreload(...)\fR (see below for the details) \fBreplace-query\fR (replace query string with the current selection) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/shell/completion.bash new/fzf-0.28.0/shell/completion.bash --- old/fzf-0.27.3/shell/completion.bash 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/shell/completion.bash 2021-11-03 17:05:07.000000000 +0100 @@ -32,7 +32,7 @@ ########################################################### # To redraw line after fzf closes (printf '\e[5n') -bind '"\e[0n": redraw-current-line' +bind '"\e[0n": redraw-current-line' 2> /dev/null __fzf_comprun() { if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/src/options.go new/fzf-0.28.0/src/options.go --- old/fzf-0.27.3/src/options.go 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/src/options.go 2021-11-03 17:05:07.000000000 +0100 @@ -44,8 +44,10 @@ --bind=KEYBINDS Custom key bindings. Refer to the man page. --cycle Enable cyclic scroll --keep-right Keep the right end of the line visible on overflow + --scroll-off=LINES Number of screen lines to keep above or below when + scrolling to the top or to the bottom (default: 0) --no-hscroll Disable horizontal scroll - --hscroll-off=COL Number of screen columns to keep to the right of the + --hscroll-off=COLS Number of screen columns to keep to the right of the highlighted substring (default: 10) --filepath-word Make word-wise movements respect path separators --jump-labels=CHARS Label characters for jump and jump-accept @@ -67,6 +69,7 @@ --marker=STR Multi-select marker (default: '>') --header=STR String to print as header --header-lines=N The first N lines of the input are treated as header + --header-first Print header before the prompt line Display --ansi Enable processing of ANSI color codes @@ -200,6 +203,7 @@ KeepRight bool Hscroll bool HscrollOff int + ScrollOff int FileWord bool InfoStyle infoStyle JumpLabels string @@ -222,6 +226,7 @@ History *History Header []string HeaderLines int + HeaderFirst bool Margin [4]sizeSpec Padding [4]sizeSpec BorderShape tui.BorderShape @@ -261,6 +266,7 @@ KeepRight: false, Hscroll: true, HscrollOff: 10, + ScrollOff: 0, FileWord: false, InfoStyle: infoDefault, JumpLabels: defaultJumpLabels, @@ -283,6 +289,7 @@ History: nil, Header: make([]string, 0), HeaderLines: 0, + HeaderFirst: false, Margin: defaultMargin(), Padding: defaultMargin(), Unicode: true, @@ -974,6 +981,12 @@ appendAction(actEnableSearch) case "disable-search": appendAction(actDisableSearch) + case "put": + if key.Type == tui.Rune && unicode.IsGraphic(key.Char) { + appendAction(actRune) + } else { + errorExit("unable to put non-printable character: " + pair[0]) + } default: t := isExecuteAction(specLower) if t == actIgnore { @@ -1354,6 +1367,8 @@ opts.Hscroll = false case "--hscroll-off": opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required") + case "--scroll-off": + opts.ScrollOff = nextInt(allArgs, &i, "scroll offset required") case "--filepath-word": opts.FileWord = true case "--no-filepath-word": @@ -1421,6 +1436,10 @@ case "--header-lines": opts.HeaderLines = atoi( nextString(allArgs, &i, "number of header lines required")) + case "--header-first": + opts.HeaderFirst = true + case "--no-header-first": + opts.HeaderFirst = false case "--preview": opts.Preview.command = nextString(allArgs, &i, "preview command required") case "--no-preview": @@ -1530,6 +1549,8 @@ opts.Tabstop = atoi(value) } else if match, value := optString(arg, "--hscroll-off="); match { opts.HscrollOff = atoi(value) + } else if match, value := optString(arg, "--scroll-off="); match { + opts.ScrollOff = atoi(value) } else if match, value := optString(arg, "--jump-labels="); match { opts.JumpLabels = value validateJumpLabels = true @@ -1547,6 +1568,10 @@ errorExit("hscroll offset must be a non-negative integer") } + if opts.ScrollOff < 0 { + errorExit("scroll offset must be a non-negative integer") + } + if opts.Tabstop < 1 { errorExit("tab stop must be a positive integer") } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/src/terminal.go new/fzf-0.28.0/src/terminal.go --- old/fzf-0.27.3/src/terminal.go 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/src/terminal.go 2021-11-03 17:05:07.000000000 +0100 @@ -121,6 +121,7 @@ keepRight bool hscroll bool hscrollOff int + scrollOff int wordRubout string wordNext string cx int @@ -139,6 +140,8 @@ printQuery bool history *History cycle bool + headerFirst bool + headerLines int header []string header0 []string ansi bool @@ -502,6 +505,7 @@ keepRight: opts.KeepRight, hscroll: opts.Hscroll, hscrollOff: opts.HscrollOff, + scrollOff: opts.ScrollOff, wordRubout: wordRubout, wordNext: wordNext, cx: len(input), @@ -527,6 +531,8 @@ paused: opts.Phony, strong: strongAttr, cycle: opts.Cycle, + headerFirst: opts.HeaderFirst, + headerLines: opts.HeaderLines, header: header, header0: header, ansi: opts.Ansi, @@ -974,12 +980,23 @@ return before, after } +func (t *Terminal) promptLine() int { + if t.headerFirst { + max := t.window.Height() - 1 + if !t.noInfoLine() { + max-- + } + return util.Min(len(t.header0)+t.headerLines, max) + } + return 0 +} + func (t *Terminal) placeCursor() { - t.move(0, t.promptLen+t.queryLen[0], false) + t.move(t.promptLine(), t.promptLen+t.queryLen[0], false) } func (t *Terminal) printPrompt() { - t.move(0, 0, true) + t.move(t.promptLine(), 0, true) t.prompt() before, after := t.updatePromptOffset() @@ -1001,22 +1018,23 @@ func (t *Terminal) printInfo() { pos := 0 + line := t.promptLine() switch t.infoStyle { case infoDefault: - t.move(1, 0, true) + t.move(line+1, 0, true) if t.reading { duration := int64(spinnerDuration) idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration t.window.CPrint(tui.ColSpinner, t.spinner[idx]) } - t.move(1, 2, false) + t.move(line+1, 2, false) pos = 2 case infoInline: pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1 if pos+len(" < ") > t.window.Width() { return } - t.move(0, pos, true) + t.move(line, pos, true) if t.reading { t.window.CPrint(tui.ColSpinner, " < ") } else { @@ -1059,11 +1077,20 @@ return } max := t.window.Height() + if t.headerFirst { + max-- + if !t.noInfoLine() { + max-- + } + } var state *ansiState for idx, lineStr := range t.header { - line := idx + 2 - if t.noInfoLine() { - line-- + line := idx + if !t.headerFirst { + line++ + if !t.noInfoLine() { + line++ + } } if line >= max { continue @@ -2642,7 +2669,7 @@ } } } else if me.Down { - if my == 0 && mx >= 0 { + if my == t.promptLine() && mx >= 0 { // Prompt t.cx = mx + t.xoffset } else if my >= min { @@ -2673,6 +2700,7 @@ command := t.replacePlaceholder(a.a, false, string(t.input), list) newCommand = &command t.reading = true + t.version++ } case actUnbind: keys := parseKeyChords(a.a, "PANIC") @@ -2748,9 +2776,26 @@ t.cy = util.Constrain(t.cy, 0, count-1) - minOffset := t.cy - height + 1 + minOffset := util.Max(t.cy-height+1, 0) maxOffset := util.Max(util.Min(count-height, t.cy), 0) t.offset = util.Constrain(t.offset, minOffset, maxOffset) + if t.scrollOff == 0 { + return + } + + scrollOff := util.Min(height/2, t.scrollOff) + for { + prevOffset := t.offset + if t.cy-t.offset < scrollOff { + t.offset = util.Max(minOffset, t.offset-1) + } + if t.cy-t.offset >= height-scrollOff { + t.offset = util.Min(maxOffset, t.offset+1) + } + if t.offset == prevOffset { + break + } + } } func (t *Terminal) vmove(o int, allowCycle bool) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/src/terminal_test.go new/fzf-0.28.0/src/terminal_test.go --- old/fzf-0.27.3/src/terminal_test.go 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/src/terminal_test.go 2021-11-03 17:05:07.000000000 +0100 @@ -288,6 +288,7 @@ {give{`grep {} ~/test`, ``, newItems(`~`)}, want{output: `grep '~' ~/test`}}, // 2) problematic examples + // (not necessarily unexpected) // paths that need to expand some part of it won't work (special characters and variables) {give{`cat {}`, ``, newItems(`~/test`)}, want{output: `cat '~/test'`}}, @@ -315,6 +316,7 @@ {give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- ^"\^"C:\\test.txt\^"^"`}}, // 2) problematic examples + // (not necessarily unexpected) // notepad++'s parser can't handle `-n"12"` generate by fzf, expects `-n12` {give{`notepad++ -n{1} {2}`, ``, newItems(`12 C:\Work\Test Folder\File.txt`)}, want{output: `notepad++ -n^"12^" ^"C:\\Work\\Test Folder\\File.txt^"`}}, @@ -327,11 +329,97 @@ // the "file" flag in the pattern won't create *.bat or *.cmd file so the command in the output tries to edit the file, instead of executing it // the temp file contains: `cat "C:\test.txt"` + // TODO this should actually work {give{`cmd /c {f}`, ``, newItems(`cat "C:\test.txt"`)}, want{match: `^cmd /c .*\fzf-preview-[0-9]{9}$`}}, } testCommands(t, tests) } +// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows in Powershell +func TestPowershellCommands(t *testing.T) { + if !util.IsWindows() { + t.SkipNow() + } + + tests := []testCase{ + // reference: give{template, query, items}, want{output OR match} + + /* + You can read each line in the following table as a pipeline that + consist of series of parsers that act upon your input (col. 1) and + each cell represents the output value. + + For example: + - exec.Command("program.exe", `\''`) + - goes to win32 api which will process it transparently as it contains no special characters, see [CommandLineToArgvW][]. + - powershell command will receive it as is, that is two arguments: a literal backslash and empty string in single quotes + - native command run via/from powershell will receive only one argument: a literal backslash. Because extra parsing rules apply, see [NativeCallsFromPowershell][]. + - some?? apps have internal parser, that requires one more level of escaping (yes, this is completely application-specific, but see terminal_test.go#TestWindowsCommands) + + Character??? CommandLineToArgvW Powershell commands Native commands from Powershell Apps requiring escapes?? | Being tested below + ---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------ + " empty string?? missing argument error ... ... | + \" literal " unbalanced quote error ... ... | + '\"' literal '"' literal " empty string empty string (match all) | yes + '\\\"' literal '\"' literal \" literal " literal " | + ---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------ + \ transparent transparent transparent regex error | + '\' transparent literal \ literal \ regex error | yes + \\ transparent transparent transparent literal \ | + '\\' transparent literal \\ literal \\ literal \ | + ---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------ + ' transparent unbalanced quote error ... ... | + \' transparent literal \ and unb. quote error ... ... | + \'' transparent literal \ and empty string literal \ regex error | no, but given as example above + ''' transparent unbalanced quote error ... ... | + '''' transparent literal ' literal ' literal ' | yes + ---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------ + + ???: charatecter or characters 'x' as an argument to a program in go's call: exec.Command("program.exe", `x`) + ??: native commands like grep, git grep, ripgrep + ??: interpreted as a grouping quote, affects argument parser and gets removed from the result + + [CommandLineToArgvW]: https://docs.microsoft.com/en-gb/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks + [NativeCallsFromPowershell]: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing?view=powershell-7.1#passing-arguments-that-contain-quote-characters + */ + + // 1) working examples + + {give{`Get-Content {}`, ``, newItems(`C:\test.txt`)}, want{output: `Get-Content 'C:\test.txt'`}}, + {give{`rg -- "package" {}`, ``, newItems(`.\test.go`)}, want{output: `rg -- "package" '.\test.go'`}}, + + // example of escaping single quotes + {give{`rg -- {}`, ``, newItems(`'foobar'`)}, want{output: `rg -- '''foobar'''`}}, + + // chaining powershells + {give{`powershell -NoProfile -Command {}`, ``, newItems(`cat "C:\test.txt"`)}, want{output: `powershell -NoProfile -Command 'cat \"C:\test.txt\"'`}}, + + // 2) problematic examples + // (not necessarily unexpected) + + // looking for a path string will only work with escaped backslashes + {give{`rg -- {}`, ``, newItems(`C:\test.txt`)}, want{output: `rg -- 'C:\test.txt'`}}, + // looking for a literal double quote will only work with triple escaped double quotes + {give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- '\"C:\test.txt\"'`}}, + + // Get-Content (i.e. cat alias) is parsing `"` as a part of the file path, returns an error: + // Get-Content : Cannot find drive. A drive with the name '"C:' does not exist. + {give{`cat {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `cat '\"C:\test.txt\"'`}}, + + // the "file" flag in the pattern won't create *.ps1 file so the powershell will offload this "unknown" filetype + // to explorer, which will prompt user to pick editing program for the fzf-preview file + // the temp file contains: `cat "C:\test.txt"` + // TODO this should actually work + {give{`powershell -NoProfile -Command {f}`, ``, newItems(`cat "C:\test.txt"`)}, want{match: `^powershell -NoProfile -Command .*\fzf-preview-[0-9]{9}$`}}, + } + + // to force powershell-style escaping we temporarily set environment variable that fzf honors + shellBackup := os.Getenv("SHELL") + os.Setenv("SHELL", "powershell") + testCommands(t, tests) + os.Setenv("SHELL", shellBackup) +} + /* Test typical valid placeholders and parsing of them. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/src/terminal_windows.go new/fzf-0.28.0/src/terminal_windows.go --- old/fzf-0.27.3/src/terminal_windows.go 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/src/terminal_windows.go 2021-11-03 17:05:07.000000000 +0100 @@ -21,10 +21,25 @@ } func quoteEntry(entry string) string { - escaped := strings.Replace(entry, `\`, `\\`, -1) - escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"` - r, _ := regexp.Compile(`[&|<>()@^%!"]`) - return r.ReplaceAllStringFunc(escaped, func(match string) string { - return "^" + match - }) + shell := os.Getenv("SHELL") + if len(shell) == 0 { + shell = "cmd" + } + + if strings.Contains(shell, "cmd") { + // backslash escaping is done here for applications + // (see ripgrep test case in terminal_test.go#TestWindowsCommands) + escaped := strings.Replace(entry, `\`, `\\`, -1) + escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"` + // caret is the escape character for cmd shell + r, _ := regexp.Compile(`[&|<>()@^%!"]`) + return r.ReplaceAllStringFunc(escaped, func(match string) string { + return "^" + match + }) + } else if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") { + escaped := strings.Replace(entry, `"`, `\"`, -1) + return "'" + strings.Replace(escaped, "'", "''", -1) + "'" + } else { + return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'" + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/src/util/util_windows.go new/fzf-0.28.0/src/util/util_windows.go --- old/fzf-0.27.3/src/util/util_windows.go 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/src/util/util_windows.go 2021-11-03 17:05:07.000000000 +0100 @@ -6,23 +6,57 @@ "fmt" "os" "os/exec" + "strings" + "sync/atomic" "syscall" ) -// ExecCommand executes the given command with cmd +var shellPath atomic.Value + +// ExecCommand executes the given command with $SHELL func ExecCommand(command string, setpgid bool) *exec.Cmd { - return ExecCommandWith("cmd", command, setpgid) + var shell string + if cached := shellPath.Load(); cached != nil { + shell = cached.(string) + } else { + shell = os.Getenv("SHELL") + if len(shell) == 0 { + shell = "cmd" + } else if strings.Contains(shell, "/") { + out, err := exec.Command("cygpath", "-w", shell).Output() + if err == nil { + shell = strings.Trim(string(out), "\n") + } + } + shellPath.Store(shell) + } + return ExecCommandWith(shell, command, setpgid) } -// ExecCommandWith executes the given command with cmd. _shell parameter is -// ignored on Windows. +// ExecCommandWith executes the given command with the specified shell // FIXME: setpgid is unused. We set it in the Unix implementation so that we // can kill preview process with its child processes at once. -func ExecCommandWith(_shell string, command string, setpgid bool) *exec.Cmd { - cmd := exec.Command("cmd") +// NOTE: For "powershell", we should ideally set output encoding to UTF8, +// but it is left as is now because no adverse effect has been observed. +func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd { + var cmd *exec.Cmd + if strings.Contains(shell, "cmd") { + cmd = exec.Command(shell) + cmd.SysProcAttr = &syscall.SysProcAttr{ + HideWindow: false, + CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command), + CreationFlags: 0, + } + return cmd + } + + if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") { + cmd = exec.Command(shell, "-NoProfile", "-Command", command) + } else { + cmd = exec.Command(shell, "-c", command) + } cmd.SysProcAttr = &syscall.SysProcAttr{ HideWindow: false, - CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command), CreationFlags: 0, } return cmd diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fzf-0.27.3/test/test_go.rb new/fzf-0.28.0/test/test_go.rb --- old/fzf-0.27.3/test/test_go.rb 2021-10-15 16:59:56.000000000 +0200 +++ new/fzf-0.28.0/test/test_go.rb 2021-11-03 17:05:07.000000000 +0100 @@ -2069,6 +2069,79 @@ tmux.send_keys :Up tmux.until { |lines| assert_includes lines[1], '[[99]]' } end + + def test_reload_should_update_preview + tmux.send_keys "seq 3 | #{FZF} --bind 'ctrl-t:reload:echo 4' --preview 'echo {}' --preview-window 'nohidden'", :Enter + tmux.until { |lines| assert_includes lines[1], '1' } + tmux.send_keys 'C-t' + tmux.until { |lines| assert_includes lines[1], '4' } + end + + def test_scroll_off + tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter + tmux.until { |lines| assert_equal 1000, lines.item_count } + height = tmux.until { |lines| lines }.first.to_i + tmux.send_keys :PgUp + tmux.until do |lines| + assert_equal height + 3, lines.first.to_i + assert_equal "> #{height}", lines[3].strip + end + tmux.send_keys :Up + tmux.until { |lines| assert_equal "> #{height + 1}", lines[3].strip } + tmux.send_keys 'l' + tmux.until { |lines| assert_equal '> 1000', lines.first.strip } + tmux.send_keys :PgDn + tmux.until { |lines| assert_equal "> #{1000 - height + 1}", lines.reverse[5].strip } + tmux.send_keys :Down + tmux.until { |lines| assert_equal "> #{1000 - height}", lines.reverse[5].strip } + end + + def test_scroll_off_large + tmux.send_keys "seq 1000 | #{FZF} --scroll-off=9999", :Enter + tmux.until { |lines| assert_equal 1000, lines.item_count } + height = tmux.until { |lines| lines }.first.to_i + tmux.send_keys :PgUp + tmux.until { |lines| assert_equal "> #{height}", lines[height / 2].strip } + tmux.send_keys :Up + tmux.until { |lines| assert_equal "> #{height + 1}", lines[height / 2].strip } + tmux.send_keys :Up + tmux.until { |lines| assert_equal "> #{height + 2}", lines[height / 2].strip } + tmux.send_keys :Down + tmux.until { |lines| assert_equal "> #{height + 1}", lines[height / 2].strip } + end + + def test_header_first + tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first", :Enter + tmux.until do |lines| + expected = <<~OUTPUT + > 4 + 997/997 + > + 3 + 2 + 1 + foobar + OUTPUT + + assert_equal expected.chomp, lines.reverse.take(7).reverse.join("\n") + end + end + + def test_header_first_reverse + tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first --reverse --inline-info", :Enter + tmux.until do |lines| + expected = <<~OUTPUT + foobar + 1 + 2 + 3 + > < 997/997 + > 4 + OUTPUT + + assert_equal expected.chomp, lines.take(6).join("\n") + end + end end module TestShell