mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-01 20:52:06 -07:00
Compare commits
11 Commits
v0.55.0
...
leverage-b
Author | SHA1 | Date | |
---|---|---|---|
|
38040d43e4 | ||
|
4161403a1d | ||
|
53bcdc4294 | ||
|
30a8ef28cd | ||
|
855f90727a | ||
|
2191a44e36 | ||
|
952276dc2d | ||
|
2286edb329 | ||
|
a0f28583e7 | ||
|
8af0af3400 | ||
|
769e5cbb2d |
4
go.mod
4
go.mod
@@ -6,8 +6,8 @@ require (
|
||||
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/rivo/uniseg v0.4.7
|
||||
golang.org/x/sys v0.24.0
|
||||
golang.org/x/term v0.23.0
|
||||
golang.org/x/sys v0.25.0
|
||||
golang.org/x/term v0.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
8
go.sum
8
go.sum
@@ -36,14 +36,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Aug 2024" "fzf 0.55.0" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Sep 2024" "fzf 0.56.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -756,7 +756,7 @@ default value 0 (or \fBcenter\fR) will put the label at the center of the
|
||||
border line.
|
||||
|
||||
.TP
|
||||
.BI "\-\-preview\-window=" "[POSITION][,SIZE[%]][,border\-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
|
||||
.BI "\-\-preview\-window=" "[POSITION][,SIZE[%]][,border\-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]info][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
|
||||
|
||||
.RS
|
||||
.B POSITION: (default: right)
|
||||
@@ -790,6 +790,9 @@ e.g.
|
||||
|
||||
* Cyclic scrolling is enabled with \fBcycle\fR flag.
|
||||
|
||||
* To hide the scroll offset information on the top right corner, specify
|
||||
\fBnoinfo\fR.
|
||||
|
||||
* To change the style of the border of the preview window, specify one of
|
||||
the options for \fB\-\-border\fR with \fBborder\-\fR prefix.
|
||||
e.g. \fBborder\-rounded\fR (border with rounded edges, default),
|
||||
|
@@ -264,6 +264,7 @@ _fzf_handle_dynamic_completion() {
|
||||
# _completion_loader may not have updated completion for the command
|
||||
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
||||
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
||||
__fzf_orig_completion_get_orig_func "$cmd" || ret=1
|
||||
|
||||
# Update orig_complete by _fzf_orig_completion entry
|
||||
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
|
||||
@@ -282,7 +283,7 @@ _fzf_handle_dynamic_completion() {
|
||||
}
|
||||
|
||||
__fzf_generic_path_completion() {
|
||||
local cur base dir leftover matches trigger cmd
|
||||
local cur base dir leftover matches trigger cmd rest
|
||||
cmd="${COMP_WORDS[0]}"
|
||||
if [[ $cmd == \\* ]]; then
|
||||
cmd="${cmd:1}"
|
||||
@@ -294,6 +295,16 @@ __fzf_generic_path_completion() {
|
||||
base=${cur:0:${#cur}-${#trigger}}
|
||||
eval "base=$base" 2> /dev/null || return
|
||||
|
||||
# Try to leverage existing completion
|
||||
rest=("${@:4}")
|
||||
unset 'rest[${#rest[@]}-2]'
|
||||
COMP_LINE=${COMP_LINE:0:${#COMP_LINE}-${#trigger}}
|
||||
COMP_POINT=$((COMP_POINT-${#trigger}))
|
||||
COMP_WORDS[$COMP_CWORD]=$base
|
||||
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
|
||||
[[ $? -ne 0 ]] &&
|
||||
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
|
||||
|
||||
dir=
|
||||
[[ $base = *"/"* ]] && dir="$base"
|
||||
while true; do
|
||||
@@ -305,7 +316,11 @@ __fzf_generic_path_completion() {
|
||||
matches=$(
|
||||
export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-} $2")
|
||||
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
||||
if declare -F "$1" > /dev/null; then
|
||||
if [[ ${#COMPREPLY[@]} -gt 0 ]]; then
|
||||
for h in "${COMPREPLY[@]}"; do
|
||||
echo "$h"
|
||||
done | command sort -u | __fzf_comprun "$4" -q "$leftover"
|
||||
elif declare -F "$1" > /dev/null; then
|
||||
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
|
||||
else
|
||||
if [[ $1 =~ dir ]]; then
|
||||
@@ -373,7 +388,20 @@ _fzf_complete() {
|
||||
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||
cur=${cur:0:${#cur}-${#trigger}}
|
||||
|
||||
# Try to leverage existing completion
|
||||
COMP_LINE=${COMP_LINE:0:${#COMP_LINE}-${#trigger}}
|
||||
COMP_POINT=$((COMP_POINT-${#trigger}))
|
||||
unset 'rest[${#rest[@]}-2]'
|
||||
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
|
||||
[[ $? -ne 0 ]] &&
|
||||
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
|
||||
|
||||
selected=$(
|
||||
(if [[ ${#COMPREPLY[@]} -gt 0 ]]; then
|
||||
for h in "${COMPREPLY[@]}"; do
|
||||
echo "$h"
|
||||
done
|
||||
fi; cat) | command sort -u |
|
||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||
FZF_DEFAULT_OPTS_FILE='' \
|
||||
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
|
||||
|
@@ -120,8 +120,8 @@ Usage: fzf [options]
|
||||
--preview=COMMAND Command to preview highlighted line ({})
|
||||
--preview-window=OPT Preview window layout (default: right:50%)
|
||||
[up|down|left|right][,SIZE[%]]
|
||||
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
||||
[,border-BORDER_OPT]
|
||||
[,[no]wrap][,[no]cycle][,[no]follow][,[no]info]
|
||||
[,[no]hidden][,border-BORDER_OPT]
|
||||
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
||||
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
|
||||
--preview-label=LABEL
|
||||
@@ -271,6 +271,7 @@ type previewOpts struct {
|
||||
wrap bool
|
||||
cycle bool
|
||||
follow bool
|
||||
info bool
|
||||
border tui.BorderShape
|
||||
headerLines int
|
||||
threshold int
|
||||
@@ -386,7 +387,7 @@ func (a previewOpts) sameLayout(b previewOpts) bool {
|
||||
}
|
||||
|
||||
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
||||
return a.wrap == b.wrap && a.headerLines == b.headerLines
|
||||
return a.wrap == b.wrap && a.headerLines == b.headerLines && a.info == b.info
|
||||
}
|
||||
|
||||
func firstLine(s string) string {
|
||||
@@ -508,7 +509,7 @@ func filterNonEmpty(input []string) []string {
|
||||
}
|
||||
|
||||
func defaultPreviewOpts(command string) previewOpts {
|
||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.DefaultBorderShape, 0, 0, nil}
|
||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, true, tui.DefaultBorderShape, 0, 0, nil}
|
||||
}
|
||||
|
||||
func defaultOptions() *Options {
|
||||
@@ -1789,6 +1790,10 @@ func parsePreviewWindowImpl(opts *previewOpts, input string) error {
|
||||
opts.follow = true
|
||||
case "nofollow":
|
||||
opts.follow = false
|
||||
case "info":
|
||||
opts.info = true
|
||||
case "noinfo":
|
||||
opts.info = false
|
||||
default:
|
||||
if headerRegex.MatchString(token) {
|
||||
if opts.headerLines, err = atoi(token[1:]); err != nil {
|
||||
|
22
src/proxy.go
22
src/proxy.go
@@ -9,6 +9,7 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -32,7 +33,7 @@ func fifo(name string) (string, error) {
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts *Options, withExports bool) (int, error) {
|
||||
func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool) (*exec.Cmd, error), opts *Options, withExports bool) (int, error) {
|
||||
output, err := fifo("proxy-output")
|
||||
if err != nil {
|
||||
return ExitError, err
|
||||
@@ -92,17 +93,28 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts
|
||||
// To ensure that the options are processed by a POSIX-compliant shell,
|
||||
// we need to write the command to a temporary file and execute it with sh.
|
||||
var exports []string
|
||||
needBash := false
|
||||
if withExports {
|
||||
exports = os.Environ()
|
||||
for idx, pairStr := range exports {
|
||||
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||
for _, pairStr := range os.Environ() {
|
||||
pair := strings.SplitN(pairStr, "=", 2)
|
||||
exports[idx] = fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))
|
||||
if validIdentifier.MatchString(pair[0]) {
|
||||
exports = append(exports, fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1])))
|
||||
} else if strings.HasPrefix(pair[0], "BASH_FUNC_") && strings.HasSuffix(pair[0], "%%") {
|
||||
name := pair[0][10 : len(pair[0])-2]
|
||||
exports = append(exports, name+pair[1])
|
||||
exports = append(exports, "export -f "+name)
|
||||
needBash = true
|
||||
}
|
||||
}
|
||||
}
|
||||
temp := WriteTemporaryFile(append(exports, command), "\n")
|
||||
defer os.Remove(temp)
|
||||
|
||||
cmd := cmdBuilder(temp)
|
||||
cmd, err := cmdBuilder(temp, needBash)
|
||||
if err != nil {
|
||||
return ExitError, err
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
intChan := make(chan os.Signal, 1)
|
||||
defer close(intChan)
|
||||
|
@@ -9,7 +9,10 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func sh() (string, error) {
|
||||
func sh(bash bool) (string, error) {
|
||||
if bash {
|
||||
return "bash", nil
|
||||
}
|
||||
return "sh", nil
|
||||
}
|
||||
|
||||
|
@@ -13,12 +13,16 @@ import (
|
||||
|
||||
var shPath atomic.Value
|
||||
|
||||
func sh() (string, error) {
|
||||
func sh(bash bool) (string, error) {
|
||||
if cached := shPath.Load(); cached != nil {
|
||||
return cached.(string), nil
|
||||
}
|
||||
|
||||
cmd := exec.Command("cygpath", "-w", "/usr/bin/sh")
|
||||
name := "sh"
|
||||
if bash {
|
||||
name = "bash"
|
||||
}
|
||||
cmd := exec.Command("cygpath", "-w", "/usr/bin/"+name)
|
||||
bytes, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -31,7 +35,7 @@ func sh() (string, error) {
|
||||
|
||||
func mkfifo(path string, mode uint32) (string, error) {
|
||||
m := strconv.FormatUint(uint64(mode), 8)
|
||||
sh, err := sh()
|
||||
sh, err := sh(false)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
@@ -43,7 +47,7 @@ func mkfifo(path string, mode uint32) (string, error) {
|
||||
}
|
||||
|
||||
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
||||
sh, err := sh()
|
||||
sh, err := sh(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -62,7 +66,7 @@ func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
||||
}
|
||||
|
||||
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
||||
sh, err := sh()
|
||||
sh, err := sh(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -2507,7 +2507,7 @@ func (t *Terminal) renderPreviewSpinner() {
|
||||
spin := t.previewer.spinner
|
||||
if len(spin) > 0 || t.previewer.scrollable {
|
||||
maxWidth := t.pwindow.Width()
|
||||
if !t.previewer.scrollable {
|
||||
if !t.previewer.scrollable || !t.previewOpts.info {
|
||||
if maxWidth > 0 {
|
||||
t.pwindow.Move(0, maxWidth-1)
|
||||
t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
|
||||
|
@@ -49,9 +49,12 @@ func runTmux(args []string, opts *Options) (int, error) {
|
||||
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
|
||||
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
|
||||
|
||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
||||
sh, _ := sh()
|
||||
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||
sh, err := sh(needBash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmuxArgs = append(tmuxArgs, sh, temp)
|
||||
return exec.Command("tmux", tmuxArgs...)
|
||||
return exec.Command("tmux", tmuxArgs...), nil
|
||||
}, opts, true)
|
||||
}
|
||||
|
@@ -44,11 +44,6 @@ func needWinpty(opts *Options) bool {
|
||||
}
|
||||
|
||||
func runWinpty(args []string, opts *Options) (int, error) {
|
||||
sh, err := sh()
|
||||
if err != nil {
|
||||
return ExitError, err
|
||||
}
|
||||
|
||||
argStr := escapeSingleQuote(args[0])
|
||||
for _, arg := range args[1:] {
|
||||
argStr += " " + escapeSingleQuote(arg)
|
||||
@@ -56,20 +51,30 @@ func runWinpty(args []string, opts *Options) (int, error) {
|
||||
argStr += ` --no-winpty`
|
||||
|
||||
if isMintty345() {
|
||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
||||
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||
sh, err := sh(needBash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(sh, temp)
|
||||
cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd
|
||||
return cmd, nil
|
||||
}, opts, false)
|
||||
}
|
||||
|
||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
||||
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||
sh, err := sh(needBash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd
|
||||
return cmd, nil
|
||||
}, opts, false)
|
||||
}
|
||||
|
@@ -3378,6 +3378,20 @@ class TestGoFZF < TestBase
|
||||
assert_equal expected, result
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_window_noinfo
|
||||
# │ 1 ││
|
||||
tmux.send_keys %(#{FZF} --preview 'seq 1000' --preview-window top,noinfo --scrollbar --bind space:change-preview-window:info), :Enter
|
||||
tmux.until do |lines|
|
||||
assert lines[1]&.start_with?('│ 1')
|
||||
assert lines[1]&.end_with?(' ││')
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until do |lines|
|
||||
assert lines[1]&.start_with?('│ 1')
|
||||
assert lines[1]&.end_with?('1000││')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
|
Reference in New Issue
Block a user