Compare commits

..

26 Commits

Author SHA1 Message Date
Junegunn Choi
62963dcefd 0.49.0 2024-04-05 00:20:26 +09:00
Junegunn Choi
68a35e4735 Do not trim CR on Windows when --read0 is set 2024-04-04 23:39:29 +09:00
Charlie Vieth
9b9ad77e1c mod: update changes/fastwalk to v1.0.3 (#3709)
Update charlievieth/fastwalk to resolve issue #3706.
2024-04-04 13:29:32 +09:00
Junegunn Choi
118b4d4a01 [bash] Add -o nospace to dir completion options (#1987) 2024-04-04 13:20:31 +09:00
Junegunn Choi
da14ab6f16 [bash] Remove -o default from dir completion options (#1987) 2024-04-04 12:58:52 +09:00
Junegunn Choi
09a4ca6ab5 [bash] Fix variable completion of directory-related commands
Fix #1987
2024-04-04 12:43:35 +09:00
Junegunn Choi
8a2df79711 Do not hide separator by default on --info=inline-right|hidden 2024-04-04 00:05:55 +09:00
Junegunn Choi
c30e486b64 Further performance improvements by removing unnecessary copies 2024-04-02 08:43:08 +09:00
Junegunn Choi
a575c0c54b GitHub Actions: Use Go "1.20" 2024-04-02 01:47:24 +09:00
Junegunn Choi
77fe96ac0d GitHub Actions: Use Go 1.20 2024-04-02 01:44:18 +09:00
Junegunn Choi
5234c3759a Improve ingestion performance (by around 40%)
Summary
    fzf --sync --bind load:accept < 27M-lines ran
      1.16 ± 0.01 times faster than fzf-41b3511 --sync --bind load:accept < 27M-lines
      1.44 ± 0.01 times faster than fzf-0.48.1 --sync --bind load:accept < 27M-lines
2024-04-02 01:38:12 +09:00
Junegunn Choi
41b3511ad9 Improve ingestion performance (by around 20%) 2024-04-01 23:38:46 +09:00
Junegunn Choi
128e4a2e8d [fish] Fix $dir in FZF_{CTRL_T,ALT_C}_COMMAND not evaluated
Fix #3705
2024-03-31 20:37:20 +09:00
junegunn
07ac90d798 Deploying to master from @ junegunn/fzf@7de87a9b2c 🚀 2024-03-31 00:01:48 +00:00
Emilio Vesprini
7de87a9b2c [shell] Make ALT-C use the absolute path to the selected directory (#3688)
Rationale: this way the resulting cd command that ends up in the shell
history can be reused to get to the same location regardless of
the current working directory.

Co-authored-by: LangLangBart <92653266+LangLangBart@users.noreply.github.com>
2024-03-31 01:13:15 +09:00
Junegunn Choi
dff865239a [bash-completion] Make dynamic loader return 124 to retry completion
Close #3702
2024-03-29 16:21:43 +09:00
Junegunn Choi
07f8f70c5b Fix flaky test case 2024-03-29 16:15:53 +09:00
Matthieu Cneude
f625c5aabe Add environment variables: FZF_{BORDER,PREVIEW}_LABEL (#3693)
The environment variable get the value of the preview label, even if it
has been updated with an action. It can be useful to track the label of
the preview and be able to switch between previews using only one
binding.

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-03-29 16:14:08 +09:00
Junegunn Choi
8a74976c1f Add track-current, untrack-current, and toggle-track-current (#3699)
Close #3691
2024-03-28 20:42:01 +09:00
Junegunn Choi
b6bfd4a5cb Fix typo in comment 2024-03-27 17:36:08 +09:00
Junegunn Choi
008fb9d258 Fix reload and reload-sync behaviors
https://github.com/junegunn/fzf/discussions/3696#discussioncomment-8915593
2024-03-27 17:25:56 +09:00
Junegunn Choi
db6db49ed6 Increase the buffer size for POST requests
Close #3685
2024-03-21 19:18:43 +09:00
Junegunn Choi
05453881c3 Set a 2-second timeout for POST requests
Close #3685
2024-03-21 19:18:38 +09:00
Junegunn Choi
5e47ab9431 README: Mention that you can source individual script files 2024-03-21 12:35:52 +09:00
LangLangBart
ec70acd0b9 chore: transition from markdown to YAML for issue template (#3687) 2024-03-21 08:33:28 +09:00
zeertzjq
25e61056b6 [fish] Fix Ctrl-T and Alt-C not using last token as search root (#3684) 2024-03-19 14:44:42 +09:00
30 changed files with 530 additions and 236 deletions

View File

@@ -1,22 +0,0 @@
<!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED -->
<!-- Check all that apply [x] -->
- [ ] I have read through the manual page (`man fzf`)
- [ ] I have the latest version of fzf
- [ ] I have searched through the existing issues
## Info
- OS
- [ ] Linux
- [ ] Mac OS X
- [ ] Windows
- [ ] Etc.
- Shell
- [ ] bash
- [ ] zsh
- [ ] fish
## Problem / Steps to reproduce

View File

@@ -0,0 +1,47 @@
---
name: Issue Template
description: Report a problem or bug related to fzf to help us improve
body:
- type: markdown
attributes:
value: ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED
- type: checkboxes
attributes:
label: Checklist
options:
- label: I have read through the manual page (`man fzf`)
required: true
- label: I have searched through the existing issues
required: true
- type: input
attributes:
label: Output of `fzf --version`
placeholder: e.g. 0.48.1 (d579e33)
validations:
required: true
- type: checkboxes
attributes:
label: OS
options:
- label: Linux
- label: macOS
- label: Windows
- label: Etc.
- type: checkboxes
attributes:
label: Shell
options:
- label: bash
- label: zsh
- label: fish
- type: textarea
attributes:
label: Problem / Steps to reproduce
validations:
required: true

View File

@@ -25,7 +25,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.19
go-version: "1.20"
- name: Setup Ruby
uses: ruby/setup-ruby@v1

View File

@@ -22,7 +22,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.18
go-version: "1.20"
- name: Setup Ruby
uses: ruby/setup-ruby@v1

View File

@@ -20,10 +20,6 @@ builds:
cat > /tmp/fzf-gon-amd64.hcl << EOF
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
}
@@ -48,10 +44,6 @@ builds:
cat > /tmp/fzf-gon-arm64.hcl << EOF
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
}

View File

@@ -6,7 +6,7 @@ Build instructions
### Prerequisites
- Go 1.18 or above
- Go 1.20 or above
### Using Makefile

View File

@@ -1,6 +1,35 @@
CHANGELOG
=========
0.49.0
------
- Ingestion performance improved by around 40% (more or less depending on options)
- `--info=hidden` and `--info=inline-right` will no longer hide the horizontal separator by default. This gives you more flexibility in customizing the layout.
```sh
fzf --border --info=inline-right
fzf --border --info=inline-right --separator ═
fzf --border --info=inline-right --no-separator
fzf --border --info=hidden
fzf --border --info=hidden --separator ━
fzf --border --info=hidden --no-separator
```
- Added two environment variables exported to the child processes
- `FZF_PREVIEW_LABEL`
- `FZF_BORDER_LABEL`
```sh
# Use the current value of $FZF_PREVIEW_LABEL to determine which actions to perform
git ls-files |
fzf --header 'Press CTRL-P to change preview mode' \
--bind='ctrl-p:transform:[[ $FZF_PREVIEW_LABEL =~ cat ]] \
&& echo "change-preview(git log --color=always \{})+change-preview-label([[ log ]])" \
|| echo "change-preview(bat --color=always \{})+change-preview-label([[ cat ]])"'
```
- Renamed `track` action to `track-current` to highlight the difference between the global tracking state set by `--track` and a one-off tracking action
- `track` is still available as an alias
- Added `untrack-current` and `toggle-track-current` actions
- `*-current` actions are no-op when the global tracking state is set
- Bug fixes and minor improvements
0.48.1
------
- CTRL-T and ALT-C bindings can be disabled by setting `FZF_CTRL_T_COMMAND` and `FZF_ALT_C_COMMAND` to empty strings respectively when sourcing the script

File diff suppressed because one or more lines are too long

4
go.mod
View File

@@ -1,7 +1,7 @@
module github.com/junegunn/fzf
require (
github.com/charlievieth/fastwalk v1.0.2
github.com/charlievieth/fastwalk v1.0.3
github.com/gdamore/tcell/v2 v2.7.4
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-shellwords v1.0.12
@@ -17,4 +17,4 @@ require (
golang.org/x/text v0.14.0 // indirect
)
go 1.17
go 1.20

4
go.sum
View File

@@ -1,5 +1,5 @@
github.com/charlievieth/fastwalk v1.0.2 h1:KYWo7xszmoldOGrwdNIeznSzhj9mhgk+6DwHunG99bc=
github.com/charlievieth/fastwalk v1.0.2/go.mod h1:JSfglY/gmL/rqsUS1NCsJTocB5n6sSl9ApAqif4CUbs=
github.com/charlievieth/fastwalk v1.0.3 h1:eNWFaNPe5srPqQ5yyDbhAf11paeZaHWcihRhpuYFfSg=
github.com/charlievieth/fastwalk v1.0.3/go.mod h1:JSfglY/gmL/rqsUS1NCsJTocB5n6sSl9ApAqif4CUbs=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=

View File

@@ -2,7 +2,7 @@
set -u
version=0.48.1
version=0.49.0
auto_completion=
key_bindings=
update_config=2

View File

@@ -1,4 +1,4 @@
$version="0.48.1"
$version="0.49.0"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition

View File

@@ -9,7 +9,7 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version string = "0.48"
var version string = "0.49"
var revision string = "devel"
//go:embed shell/key-bindings.bash

View File

@@ -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-tmux 1 "Mar 2024" "fzf 0.48.1" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Apr 2024" "fzf 0.49.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane

View File

@@ -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 "Mar 2024" "fzf 0.48.1" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Apr 2024" "fzf 0.49.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -372,21 +372,22 @@ e.g.
.TP
.BI "--info=" "STYLE"
Determines the display style of finder info (match counters).
Determines the display style of the finder info. (e.g. match counter, loading indicator, etc.)
.BR default " On the left end of the horizontal separator"
.br
.BR default " Display on the next line to the prompt"
.br
.BR right " Display on the right end of the next line to the prompt"
.br
.BR inline " Display on the same line with the default separator ' < '"
.br
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
.br
.BR inline-right " Display on the right end of the same line
.BR right " On the right end of the horizontal separator"
.br
.BR hidden " Do not display finder info"
.br
.BR inline " After the prompt with the default prefix ' < '"
.br
.BR inline:PREFIX " After the prompt with a non-default prefix"
.br
.BR inline-right " On the right end of the prompt line"
.br
.BR inline-right:PREFIX " On the right end of the prompt line with a custom prefix"
.br
.TP
.B "--no-info"
@@ -975,6 +976,10 @@ fzf exports the following environment variables to its child processes.
.br
.BR FZF_PROMPT " Prompt string"
.br
.BR FZF_PREVIEW_LABEL " Preview label string"
.br
.BR FZF_BORDER_LABEL " Border label string"
.br
.BR FZF_ACTION " The name of the last action performed"
.br
.BR FZF_PORT " Port number when --listen option is used"
@@ -1326,9 +1331,10 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle-preview-wrap\fR
\fBtoggle-search\fR (toggle search functionality)
\fBtoggle-sort\fR
\fBtoggle-track\fR
\fBtoggle-track\fR (toggle global tracking option (\fB--track\fR))
\fBtoggle-track-current\fR (toggle tracking of the current item)
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBtrack\fR (track the current item; automatically disabled if focus changes)
\fBtrack-current\fR (track the current item; automatically disabled if focus changes)
\fBtransform(...)\fR (transform states using the output of an external command)
\fBtransform-border-label(...)\fR (transform border label using an external command)
\fBtransform-header(...)\fR (transform header using an external command)
@@ -1338,6 +1344,7 @@ A key or an event can be bound to one or more of the following actions.
\fBunbind(...)\fR (unbind bindings)
\fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR
\fBuntrack-current\fR (stop tracking the current item; no-op if global tracking is enabled)
\fBup\fR \fIctrl-k ctrl-p up\fR
\fByank\fR \fIctrl-y\fR

View File

@@ -308,6 +308,7 @@ _fzf_handle_dynamic_completion() {
eval "$orig_complete"
fi
fi
[[ $ret -eq 0 ]] && return 124
return $ret
fi
}
@@ -547,7 +548,7 @@ done
# Directory
for cmd in $d_cmds; do
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
__fzf_defc "$cmd" _fzf_dir_completion "-o bashdefault -o nospace -o dirnames"
done
# ssh

View File

@@ -41,7 +41,7 @@ __fzf_cd__() {
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --walker=dir,follow,hidden --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
dir=$(
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)
) && printf 'builtin cd -- %q' "$dir"
) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)"
}
if command -v perl > /dev/null; then

View File

@@ -21,13 +21,13 @@ function fzf_key_bindings
# Store current token in $dir as root for the 'find' command
function fzf-file-widget -d "List files and folders"
set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1]
set -lx dir $commandline[1]
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=file,dir,follow,hidden --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=file,dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
end
@@ -70,13 +70,13 @@ function fzf_key_bindings
function fzf-cd-widget -d "Change directory"
set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1]
set -lx dir $commandline[1]
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=dir,follow,hidden --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result

View File

@@ -78,7 +78,7 @@ fzf-cd-widget() {
return 0
fi
zle push-line # Clear buffer. Auto-restored on next prompt.
BUFFER="builtin cd -- ${(q)dir}"
BUFFER="builtin cd -- ${(q)dir:a}"
zle accept-line
local ret=$?
unset dir # ensure this doesn't end up appearing in prompt expansion

View File

@@ -54,72 +54,74 @@ func _() {
_ = x[actToggleIn-43]
_ = x[actToggleOut-44]
_ = x[actToggleTrack-45]
_ = x[actToggleHeader-46]
_ = x[actTrack-47]
_ = x[actDown-48]
_ = x[actUp-49]
_ = x[actPageUp-50]
_ = x[actPageDown-51]
_ = x[actPosition-52]
_ = x[actHalfPageUp-53]
_ = x[actHalfPageDown-54]
_ = x[actOffsetUp-55]
_ = x[actOffsetDown-56]
_ = x[actJump-57]
_ = x[actJumpAccept-58]
_ = x[actPrintQuery-59]
_ = x[actRefreshPreview-60]
_ = x[actReplaceQuery-61]
_ = x[actToggleSort-62]
_ = x[actShowPreview-63]
_ = x[actHidePreview-64]
_ = x[actTogglePreview-65]
_ = x[actTogglePreviewWrap-66]
_ = x[actTransform-67]
_ = x[actTransformBorderLabel-68]
_ = x[actTransformHeader-69]
_ = x[actTransformPreviewLabel-70]
_ = x[actTransformPrompt-71]
_ = x[actTransformQuery-72]
_ = x[actPreview-73]
_ = x[actChangePreview-74]
_ = x[actChangePreviewWindow-75]
_ = x[actPreviewTop-76]
_ = x[actPreviewBottom-77]
_ = x[actPreviewUp-78]
_ = x[actPreviewDown-79]
_ = x[actPreviewPageUp-80]
_ = x[actPreviewPageDown-81]
_ = x[actPreviewHalfPageUp-82]
_ = x[actPreviewHalfPageDown-83]
_ = x[actPrevHistory-84]
_ = x[actPrevSelected-85]
_ = x[actPut-86]
_ = x[actNextHistory-87]
_ = x[actNextSelected-88]
_ = x[actExecute-89]
_ = x[actExecuteSilent-90]
_ = x[actExecuteMulti-91]
_ = x[actSigStop-92]
_ = x[actFirst-93]
_ = x[actLast-94]
_ = x[actReload-95]
_ = x[actReloadSync-96]
_ = x[actDisableSearch-97]
_ = x[actEnableSearch-98]
_ = x[actSelect-99]
_ = x[actDeselect-100]
_ = x[actUnbind-101]
_ = x[actRebind-102]
_ = x[actBecome-103]
_ = x[actResponse-104]
_ = x[actShowHeader-105]
_ = x[actHideHeader-106]
_ = x[actToggleTrackCurrent-46]
_ = x[actToggleHeader-47]
_ = x[actTrackCurrent-48]
_ = x[actUntrackCurrent-49]
_ = x[actDown-50]
_ = x[actUp-51]
_ = x[actPageUp-52]
_ = x[actPageDown-53]
_ = x[actPosition-54]
_ = x[actHalfPageUp-55]
_ = x[actHalfPageDown-56]
_ = x[actOffsetUp-57]
_ = x[actOffsetDown-58]
_ = x[actJump-59]
_ = x[actJumpAccept-60]
_ = x[actPrintQuery-61]
_ = x[actRefreshPreview-62]
_ = x[actReplaceQuery-63]
_ = x[actToggleSort-64]
_ = x[actShowPreview-65]
_ = x[actHidePreview-66]
_ = x[actTogglePreview-67]
_ = x[actTogglePreviewWrap-68]
_ = x[actTransform-69]
_ = x[actTransformBorderLabel-70]
_ = x[actTransformHeader-71]
_ = x[actTransformPreviewLabel-72]
_ = x[actTransformPrompt-73]
_ = x[actTransformQuery-74]
_ = x[actPreview-75]
_ = x[actChangePreview-76]
_ = x[actChangePreviewWindow-77]
_ = x[actPreviewTop-78]
_ = x[actPreviewBottom-79]
_ = x[actPreviewUp-80]
_ = x[actPreviewDown-81]
_ = x[actPreviewPageUp-82]
_ = x[actPreviewPageDown-83]
_ = x[actPreviewHalfPageUp-84]
_ = x[actPreviewHalfPageDown-85]
_ = x[actPrevHistory-86]
_ = x[actPrevSelected-87]
_ = x[actPut-88]
_ = x[actNextHistory-89]
_ = x[actNextSelected-90]
_ = x[actExecute-91]
_ = x[actExecuteSilent-92]
_ = x[actExecuteMulti-93]
_ = x[actSigStop-94]
_ = x[actFirst-95]
_ = x[actLast-96]
_ = x[actReload-97]
_ = x[actReloadSync-98]
_ = x[actDisableSearch-99]
_ = x[actEnableSearch-100]
_ = x[actSelect-101]
_ = x[actDeselect-102]
_ = x[actUnbind-103]
_ = x[actRebind-104]
_ = x[actBecome-105]
_ = x[actResponse-106]
_ = x[actShowHeader-107]
_ = x[actHideHeader-108]
}
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleHeaderactTrackactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 634, 642, 649, 654, 663, 674, 685, 698, 713, 724, 737, 744, 757, 770, 787, 802, 815, 829, 843, 859, 879, 891, 914, 932, 956, 974, 991, 1001, 1017, 1039, 1052, 1068, 1080, 1094, 1110, 1128, 1148, 1170, 1184, 1199, 1205, 1219, 1234, 1244, 1260, 1275, 1285, 1293, 1300, 1309, 1322, 1338, 1353, 1362, 1373, 1382, 1391, 1400, 1411, 1424, 1437}
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 640, 655, 670, 687, 694, 699, 708, 719, 730, 743, 758, 769, 782, 789, 802, 815, 832, 847, 860, 874, 888, 904, 924, 936, 959, 977, 1001, 1019, 1036, 1046, 1062, 1084, 1097, 1113, 1125, 1139, 1155, 1173, 1193, 1215, 1229, 1244, 1250, 1264, 1279, 1289, 1305, 1320, 1330, 1338, 1345, 1354, 1367, 1383, 1398, 1407, 1418, 1427, 1436, 1445, 1456, 1469, 1482}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -312,7 +312,7 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
// Inlined version of strconv.Atoi() that only handles positive
// integers and does not allocate on error.
code := 0
for _, ch := range []byte(s) {
for _, ch := range sbytes(s) {
ch -= '0'
if ch > 9 {
return -1, delimiter, remaining

View File

@@ -14,6 +14,7 @@ const (
// Reader
readerBufferSize = 64 * 1024
readerSlabSize = 128 * 1024
readerPollIntervalMin = 10 * time.Millisecond
readerPollIntervalStep = 5 * time.Millisecond
readerPollIntervalMax = 50 * time.Millisecond

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"time"
"unsafe"
"github.com/junegunn/fzf/src/util"
)
@@ -18,6 +19,14 @@ Matcher -> EvtSearchFin -> Terminal (update list)
Matcher -> EvtHeader -> Terminal (update header)
*/
func ustring(data []byte) string {
return unsafe.String(unsafe.SliceData(data), len(data))
}
func sbytes(data string) []byte {
return unsafe.Slice(unsafe.StringData(data), len(data))
}
// Run starts fzf
func Run(opts *Options, version string, revision string) {
sort := opts.Sort > 0
@@ -45,16 +54,16 @@ func Run(opts *Options, version string, revision string) {
if opts.Theme.Colored {
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
prevLineAnsiState = lineAnsiState
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
trimmed, offsets, newState := extractColor(ustring(data), lineAnsiState, nil)
lineAnsiState = newState
return util.ToChars([]byte(trimmed)), offsets
return util.ToChars(sbytes(trimmed)), offsets
}
} else {
// When color is disabled but ansi option is given,
// we simply strip out ANSI codes from the input
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
trimmed, _, _ := extractColor(string(data), nil, nil)
return util.ToChars([]byte(trimmed)), nil
trimmed, _, _ := extractColor(ustring(data), nil, nil)
return util.ToChars(sbytes(trimmed)), nil
}
}
}
@@ -66,7 +75,7 @@ func Run(opts *Options, version string, revision string) {
if len(opts.WithNth) == 0 {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
if len(header) < opts.HeaderLines {
header = append(header, string(data))
header = append(header, ustring(data))
eventBox.Set(EvtHeader, header)
return false
}
@@ -77,7 +86,7 @@ func Run(opts *Options, version string, revision string) {
})
} else {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(string(data), opts.Delimiter)
tokens := Tokenize(ustring(data), opts.Delimiter)
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState
if prevLineAnsiState != nil {
@@ -101,7 +110,7 @@ func Run(opts *Options, version string, revision string) {
eventBox.Set(EvtHeader, header)
return false
}
item.text, item.colors = ansiProcessor([]byte(transformed))
item.text, item.colors = ansiProcessor(sbytes(transformed))
item.text.TrimTrailingWhitespaces()
item.text.Index = itemIndex
item.origText = &data
@@ -245,11 +254,8 @@ func Run(opts *Options, version string, revision string) {
delay := true
ticks++
input := func() []rune {
reloaded := snapshotRevision != inputRevision
paused, input := terminal.Input()
if reloaded && paused {
query = []rune{}
} else if !paused {
if !paused {
query = input
}
return query
@@ -278,6 +284,9 @@ func Run(opts *Options, version string, revision string) {
useSnapshot = false
}
if !useSnapshot {
if snapshotRevision != inputRevision {
query = []rune{}
}
snapshot, count = chunkList.Snapshot()
snapshotRevision = inputRevision
}
@@ -319,10 +328,13 @@ func Run(opts *Options, version string, revision string) {
break
}
if !useSnapshot {
newSnapshot, _ := chunkList.Snapshot()
newSnapshot, newCount := chunkList.Snapshot()
// We want to avoid showing empty list when reload is triggered
// and the query string is changed at the same time i.e. command != nil && changed
if command == nil || len(newSnapshot) > 0 {
if command == nil || newCount > 0 {
if snapshotRevision != inputRevision {
query = []rune{}
}
snapshot = newSnapshot
snapshotRevision = inputRevision
}

View File

@@ -75,7 +75,7 @@ const usage = `usage: fzf [options]
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
--info=STYLE Finder info style
[default|right|hidden|inline[:SEPARATOR]|inline-right]
[default|right|hidden|inline[-right][:PREFIX]]
--separator=STR String to form horizontal separator on info line
--no-separator Hide info line separator
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
@@ -143,7 +143,7 @@ const usage = `usage: fzf [options]
`
const defaultInfoSep = " < "
const defaultInfoPrefix = " < "
// Case denotes case-sensitivity of search
type Case int
@@ -217,10 +217,6 @@ const (
infoHidden
)
func (s infoStyle) noExtraLine() bool {
return s == infoInline || s == infoInlineRight || s == infoHidden
}
type labelOpts struct {
label string
column int
@@ -327,7 +323,7 @@ type Options struct {
ScrollOff int
FileWord bool
InfoStyle infoStyle
InfoSep string
InfoPrefix string
Separator *string
JumpLabels string
Prompt string
@@ -1211,14 +1207,18 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggleSearch)
case "toggle-track":
appendAction(actToggleTrack)
case "toggle-track-current":
appendAction(actToggleTrackCurrent)
case "toggle-header":
appendAction(actToggleHeader)
case "show-header":
appendAction(actShowHeader)
case "hide-header":
appendAction(actHideHeader)
case "track":
appendAction(actTrack)
case "track", "track-current":
appendAction(actTrackCurrent)
case "untrack-current":
appendAction(actUntrackCurrent)
case "select":
appendAction(actSelect)
case "select-all":
@@ -1502,17 +1502,24 @@ func parseInfoStyle(str string) (infoStyle, string) {
case "right":
return infoRight, ""
case "inline":
return infoInline, defaultInfoSep
return infoInline, defaultInfoPrefix
case "inline-right":
return infoInlineRight, ""
case "hidden":
return infoHidden, ""
default:
prefix := "inline:"
if strings.HasPrefix(str, prefix) {
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
type infoSpec struct {
name string
style infoStyle
}
errorExit("invalid info style (expected: default|right|hidden|inline[:SEPARATOR]|inline-right)")
for _, spec := range []infoSpec{
{"inline", infoInline},
{"inline-right", infoInlineRight}} {
if strings.HasPrefix(str, spec.name+":") {
return spec.style, strings.ReplaceAll(str[len(spec.name)+1:], "\n", " ")
}
}
errorExit("invalid info style (expected: default|right|hidden|inline[-right][:PREFIX])")
}
return infoDefault, ""
}
@@ -1803,13 +1810,13 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-filepath-word":
opts.FileWord = false
case "--info":
opts.InfoStyle, opts.InfoSep = parseInfoStyle(
opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(
nextString(allArgs, &i, "info style required"))
case "--no-info":
opts.InfoStyle = infoHidden
case "--inline-info":
opts.InfoStyle = infoInline
opts.InfoSep = defaultInfoSep
opts.InfoPrefix = defaultInfoPrefix
case "--no-inline-info":
opts.InfoStyle = infoDefault
case "--separator":
@@ -2011,7 +2018,7 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--layout="); match {
opts.Layout = parseLayout(value)
} else if match, value := optString(arg, "--info="); match {
opts.InfoStyle, opts.InfoSep = parseInfoStyle(value)
opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(value)
} else if match, value := optString(arg, "--separator="); match {
opts.Separator = &value
} else if match, value := optString(arg, "--scrollbar="); match {

View File

@@ -1,7 +1,7 @@
package fzf
import (
"bufio"
"bytes"
"context"
"io"
"os"
@@ -111,33 +111,85 @@ func (r *Reader) ReadSource(root string, opts walkerOpts, ignores []string) {
}
func (r *Reader) feed(src io.Reader) {
/*
readerSlabSize, ae := strconv.Atoi(os.Getenv("SLAB_KB"))
if ae != nil {
readerSlabSize = 128 * 1024
} else {
readerSlabSize *= 1024
}
readerBufferSize, be := strconv.Atoi(os.Getenv("BUF_KB"))
if be != nil {
readerBufferSize = 64 * 1024
} else {
readerBufferSize *= 1024
}
*/
delim := byte('\n')
trimCR := util.IsWindows()
if r.delimNil {
delim = '\000'
trimCR = false
}
reader := bufio.NewReaderSize(src, readerBufferSize)
slab := make([]byte, readerSlabSize)
leftover := []byte{}
var err error
for {
// ReadBytes returns err != nil if and only if the returned data does not
// end in delim.
bytea, err := reader.ReadBytes(delim)
byteaLen := len(bytea)
if byteaLen > 0 {
if err == nil {
// get rid of carriage return if under Windows:
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
bytea = bytea[:byteaLen-2]
} else {
bytea = bytea[:byteaLen-1]
}
}
if r.pusher(bytea) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
}
}
if err != nil {
n := 0
scope := slab[:util.Min(len(slab), readerBufferSize)]
for i := 0; i < 100; i++ {
n, err = src.Read(scope)
if n > 0 || err != nil {
break
}
}
// We're not making any progress after 100 tries. Stop.
if n == 0 && err == nil {
break
}
buf := slab[:n]
slab = slab[n:]
for len(buf) > 0 {
if i := bytes.IndexByte(buf, delim); i >= 0 {
// Found the delimiter
slice := buf[:i+1]
buf = buf[i+1:]
if trimCR && len(slice) >= 2 && slice[len(slice)-2] == byte('\r') {
slice = slice[:len(slice)-2]
} else {
slice = slice[:len(slice)-1]
}
if len(leftover) > 0 {
slice = append(leftover, slice...)
leftover = []byte{}
}
if (err == nil || len(slice) > 0) && r.pusher(slice) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
}
} else {
// Could not find the delimiter in the buffer
leftover = append(leftover, buf...)
break
}
}
if err == io.EOF {
leftover = append(leftover, buf...)
break
}
if len(slab) == 0 {
slab = make([]byte, readerSlabSize)
}
}
if len(leftover) > 0 && r.pusher(leftover) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
}
}
func (r *Reader) readFromStdin() bool {

View File

@@ -32,6 +32,7 @@ const (
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
httpReadTimeout = 10 * time.Second
channelTimeout = 2 * time.Second
jsonContentType = "Content-Type: application/json" + crlf
maxContentLength = 1024 * 1024
)
@@ -170,7 +171,7 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
select {
case response := <-server.responseChannel:
return good(response)
case <-time.After(2 * time.Second):
case <-time.After(channelTimeout):
go func() {
// Drain the channel
<-server.responseChannel
@@ -227,7 +228,11 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
return bad("no action specified")
}
server.actionChannel <- actions
select {
case server.actionChannel <- actions:
case <-time.After(channelTimeout):
return httpUnavailable + crlf
}
return httpOk + crlf
}

View File

@@ -181,7 +181,7 @@ type Status struct {
type Terminal struct {
initDelay time.Duration
infoStyle infoStyle
infoSep string
infoPrefix string
separator labelPrinter
separatorLen int
spinner []string
@@ -394,8 +394,10 @@ const (
actToggleIn
actToggleOut
actToggleTrack
actToggleTrackCurrent
actToggleHeader
actTrack
actTrackCurrent
actUntrackCurrent
actDown
actUp
actPageUp
@@ -675,7 +677,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
if previewBox != nil && opts.Preview.aboveOrBelow() {
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
}
if opts.InfoStyle.noExtraLine() {
if noSeparatorLine(opts.InfoStyle, opts.Separator == nil || uniseg.StringWidth(*opts.Separator) > 0) {
effectiveMinHeight--
}
effectiveMinHeight += borderLines(opts.BorderShape)
@@ -697,7 +699,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
t := Terminal{
initDelay: delay,
infoStyle: opts.InfoStyle,
infoSep: opts.InfoSep,
infoPrefix: opts.InfoPrefix,
separator: nil,
spinner: makeSpinner(opts.Unicode),
promptString: opts.Prompt,
@@ -776,7 +778,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
theme: opts.Theme,
startChan: make(chan fitpad, 1),
killChan: make(chan int),
serverInputChan: make(chan []*action, 10),
serverInputChan: make(chan []*action, 100),
serverOutputChan: make(chan string),
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
tui: renderer,
@@ -850,6 +852,8 @@ func (t *Terminal) environ() []string {
env = append(env, "FZF_QUERY="+string(t.input))
env = append(env, "FZF_ACTION="+t.lastAction.Name())
env = append(env, "FZF_PROMPT="+string(t.promptString))
env = append(env, "FZF_PREVIEW_LABEL="+t.previewLabelOpts.label)
env = append(env, "FZF_BORDER_LABEL="+t.borderLabelOpts.label)
env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count))
env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.merger.Length()))
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
@@ -878,7 +882,7 @@ func (t *Terminal) visibleHeaderLines() int {
// Extra number of lines needed to display fzf
func (t *Terminal) extraLines() int {
extra := t.visibleHeaderLines() + 1
if !t.noInfoLine() {
if !t.noSeparatorLine() {
extra++
}
return extra
@@ -984,8 +988,18 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
return output, promptLen
}
func (t *Terminal) noInfoLine() bool {
return t.infoStyle.noExtraLine()
func noSeparatorLine(style infoStyle, separator bool) bool {
switch style {
case infoInline:
return true
case infoHidden, infoInlineRight:
return !separator
}
return false
}
func (t *Terminal) noSeparatorLine() bool {
return noSeparatorLine(t.infoStyle, t.separatorLen > 0)
}
func getScrollbar(total int, height int, offset int) (int, int) {
@@ -1237,7 +1251,7 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
minAreaWidth := minWidth
minAreaHeight := minHeight
if t.noInfoLine() {
if t.noSeparatorLine() {
minAreaHeight -= 1
}
if t.needPreviewWindow() {
@@ -1518,7 +1532,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
y = h - y - 1
case layoutReverseList:
n := 2 + t.visibleHeaderLines()
if t.noInfoLine() {
if t.noSeparatorLine() {
n--
}
if y < n {
@@ -1558,7 +1572,7 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
func (t *Terminal) promptLine() int {
if t.headerFirst {
max := t.window.Height() - 1
if !t.noInfoLine() {
if !t.noSeparatorLine() {
max--
}
return util.Min(t.visibleHeaderLines(), max)
@@ -1603,20 +1617,8 @@ func (t *Terminal) printInfo() {
t.window.Print(" ") // Clear spinner
}
}
switch t.infoStyle {
case infoDefault:
t.move(line+1, 0, t.separatorLen == 0)
printSpinner()
t.move(line+1, 2, false)
pos = 2
case infoRight:
t.move(line+1, 0, false)
case infoInlineRight:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
t.move(line, pos, true)
case infoInline:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
str := t.infoSep
printInfoPrefix := func() {
str := t.infoPrefix
maxWidth := t.window.Width() - pos
width := util.StringWidth(str)
if width > maxWidth {
@@ -1631,7 +1633,34 @@ func (t *Terminal) printInfo() {
t.window.CPrint(tui.ColPrompt, str)
}
pos += width
}
printSeparator := func(fillLength int, pad bool) {
// --------_
if t.separatorLen > 0 {
t.separator(t.window, fillLength)
t.window.Print(" ")
} else if pad {
t.window.Print(strings.Repeat(" ", fillLength+1))
}
}
switch t.infoStyle {
case infoDefault:
t.move(line+1, 0, t.separatorLen == 0)
printSpinner()
t.move(line+1, 2, false)
pos = 2
case infoRight:
t.move(line+1, 0, false)
case infoInlineRight:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
case infoInline:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
printInfoPrefix()
case infoHidden:
if t.separatorLen > 0 {
t.move(line+1, 0, false)
printSeparator(t.window.Width()-1, false)
}
return
}
@@ -1645,8 +1674,11 @@ func (t *Terminal) printInfo() {
output += " -S"
}
}
if t.track != trackDisabled {
switch t.track {
case trackEnabled:
output += " +T"
case trackCurrent:
output += " +t"
}
if t.multi > 0 {
if t.multi == maxMulti {
@@ -1662,15 +1694,6 @@ func (t *Terminal) printInfo() {
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
}
printSeparator := func(fillLength int, pad bool) {
// --------_
if t.separatorLen > 0 {
t.separator(t.window, fillLength)
t.window.Print(" ")
} else if pad {
t.window.Print(strings.Repeat(" ", fillLength+1))
}
}
if t.infoStyle == infoRight {
maxWidth := t.window.Width()
if t.reading {
@@ -1693,19 +1716,35 @@ func (t *Terminal) printInfo() {
}
if t.infoStyle == infoInlineRight {
if len(t.infoPrefix) == 0 {
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
if pos >= t.window.Width() {
return
}
if pos < t.window.Width() {
t.move(line, pos, false)
printSpinner()
pos++
}
if pos < t.window.Width()-1 {
t.window.Print(" ")
pos += 2
pos++
}
} else {
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-util.StringWidth(t.infoPrefix)-1)
printInfoPrefix()
}
}
maxWidth := t.window.Width() - pos
output = t.trimMessage(output, maxWidth)
t.window.CPrint(tui.ColInfo, output)
if t.infoStyle == infoInlineRight {
if t.separatorLen > 0 {
t.move(line+1, 0, false)
printSeparator(t.window.Width()-1, false)
}
return
}
fillLength := maxWidth - len(output) - 2
if fillLength > 0 {
t.window.CPrint(tui.ColSeparator, " ")
@@ -1720,7 +1759,7 @@ func (t *Terminal) printHeader() {
max := t.window.Height()
if t.headerFirst {
max--
if !t.noInfoLine() {
if !t.noSeparatorLine() {
max--
}
}
@@ -1737,7 +1776,7 @@ func (t *Terminal) printHeader() {
}
if !t.headerFirst {
line++
if !t.noInfoLine() {
if !t.noSeparatorLine() {
line++
}
}
@@ -1769,7 +1808,7 @@ func (t *Terminal) printList() {
i = maxy - 1 - j
}
line := i + 2 + t.visibleHeaderLines()
if t.noInfoLine() {
if t.noSeparatorLine() {
line--
}
if i < count {
@@ -3095,7 +3134,7 @@ func (t *Terminal) Loop() {
switch req {
case reqPrompt:
t.printPrompt()
if t.noInfoLine() {
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
t.printInfo()
}
case reqInfo:
@@ -3471,11 +3510,13 @@ func (t *Terminal) Loop() {
req(reqHeader)
}
case actChangeBorderLabel:
t.borderLabelOpts.label = a.a
if t.border != nil {
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
req(reqRedrawBorderLabel)
}
case actChangePreviewLabel:
t.previewLabelOpts.label = a.a
if t.pborder != nil {
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
req(reqRedrawPreviewLabel)
@@ -3485,14 +3526,16 @@ func (t *Terminal) Loop() {
actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {})
return doActions(actions)
case actTransformBorderLabel:
if t.border != nil {
label := t.executeCommand(a.a, false, true, true, true)
t.borderLabelOpts.label = label
if t.border != nil {
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
req(reqRedrawBorderLabel)
}
case actTransformPreviewLabel:
if t.pborder != nil {
label := t.executeCommand(a.a, false, true, true, true)
t.previewLabelOpts.label = label
if t.pborder != nil {
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
req(reqRedrawPreviewLabel)
}
@@ -3778,6 +3821,14 @@ func (t *Terminal) Loop() {
t.track = trackEnabled
}
req(reqInfo)
case actToggleTrackCurrent:
switch t.track {
case trackCurrent:
t.track = trackDisabled
case trackDisabled:
t.track = trackCurrent
}
req(reqInfo)
case actShowHeader:
t.headerVisible = true
req(reqList, reqInfo, reqPrompt, reqHeader)
@@ -3787,11 +3838,16 @@ func (t *Terminal) Loop() {
case actToggleHeader:
t.headerVisible = !t.headerVisible
req(reqList, reqInfo, reqPrompt, reqHeader)
case actTrack:
case actTrackCurrent:
if t.track == trackDisabled {
t.track = trackCurrent
}
req(reqInfo)
case actUntrackCurrent:
if t.track == trackCurrent {
t.track = trackDisabled
}
req(reqInfo)
case actEnableSearch:
t.paused = false
changed = true
@@ -3880,7 +3936,7 @@ func (t *Terminal) Loop() {
mx -= t.window.Left()
my -= t.window.Top()
min := 2 + t.visibleHeaderLines()
if t.noInfoLine() {
if t.noSeparatorLine() {
min--
}
h := t.window.Height()
@@ -4172,7 +4228,7 @@ func (t *Terminal) vset(o int) bool {
func (t *Terminal) maxItems() int {
max := t.window.Height() - 2 - t.visibleHeaderLines()
if t.noInfoLine() {
if t.noSeparatorLine() {
max++
}
return util.Max(max, 0)

View File

@@ -91,7 +91,7 @@ func withPrefixLengths(tokens []string, begin int) []Token {
prefixLength := begin
for idx := range tokens {
chars := util.ToChars([]byte(tokens[idx]))
chars := util.ToChars(sbytes(tokens[idx]))
ret[idx] = Token{&chars, int32(prefixLength)}
prefixLength += chars.Length()
}
@@ -187,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
if r.begin == r.end {
idx := r.begin
if idx == rangeEllipsis {
chars := util.ToChars([]byte(joinTokens(tokens)))
chars := util.ToChars(sbytes(joinTokens(tokens)))
parts = append(parts, &chars)
} else {
if idx < 0 {

View File

@@ -163,7 +163,7 @@ func (chars *Chars) ToString() string {
if runes := chars.optionalRunes(); runes != nil {
return string(runes)
}
return string(chars.slice)
return unsafe.String(unsafe.SliceData(chars.slice), len(chars.slice))
}
func (chars *Chars) ToRunes() []rune {

View File

@@ -1719,7 +1719,7 @@ class TestGoFZF < TestBase
end
def test_info_hidden
tmux.send_keys 'seq 10 | fzf --info=hidden', :Enter
tmux.send_keys 'seq 10 | fzf --info=hidden --no-separator', :Enter
tmux.until { |lines| assert_equal '> 1', lines[-2] }
end
@@ -2463,6 +2463,84 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal 10, lines.match_count }
end
def test_reload_disabled_case1
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)'", :Enter
tmux.until do |lines|
assert_equal 100, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.until { |lines| assert_equal 1000, lines.match_count }
end
def test_reload_disabled_case2
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)'", :Enter
tmux.until do |lines|
assert_equal 100, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_equal 1000, lines.match_count }
end
def test_reload_disabled_case3
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)+backward-delete-char'", :Enter
tmux.until do |lines|
assert_equal 100, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.until { |lines| assert_equal 1000, lines.match_count }
end
def test_reload_disabled_case4
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)+backward-delete-char'", :Enter
tmux.until do |lines|
assert_equal 100, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_equal 1000, lines.match_count }
end
def test_reload_disabled_case5
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(echo xx; sleep 2; seq 1000)'", :Enter
tmux.until do |lines|
assert_equal 100, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert_equal 1, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 1001, lines.match_count }
end
def test_reload_disabled_case6
tmux.send_keys "seq 1000 | #{FZF} --disabled --bind 'change:reload:sleep 0.5; seq {q}'", :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys '9'
tmux.until { |lines| assert_equal 9, lines.match_count }
tmux.send_keys '9'
tmux.until { |lines| assert_equal 99, lines.match_count }
# TODO: How do we verify if an intermediate empty list is not shown?
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 }
@@ -2799,6 +2877,27 @@ class TestGoFZF < TestBase
end
end
def test_labels_variables
tmux.send_keys ': | fzf --border --border-label foobar --preview "echo \$FZF_BORDER_LABEL // \$FZF_PREVIEW_LABEL" --preview-label barfoo --bind "space:change-border-label(barbaz)+change-preview-label(bazbar)+refresh-preview,enter:transform-border-label(echo 123)+transform-preview-label(echo 456)+refresh-preview"', :Enter
tmux.until do
assert_includes(_1[0], '─foobar─')
assert_includes(_1[1], '─barfoo─')
assert_includes(_1[2], ' foobar // barfoo ')
end
tmux.send_keys :Space
tmux.until do
assert_includes(_1[0], '─barbaz─')
assert_includes(_1[1], '─bazbar─')
assert_includes(_1[2], ' barbaz // bazbar ')
end
tmux.send_keys :Enter
tmux.until do
assert_includes(_1[0], '─123─')
assert_includes(_1[1], '─456─')
assert_includes(_1[2], ' 123 // 456 ')
end
end
def test_info_separator_unicode
tmux.send_keys 'seq 100 | fzf -q55', :Enter
tmux.until { assert_includes(_1[-2], ' 1/100 ─') }
@@ -3002,7 +3101,7 @@ class TestGoFZF < TestBase
end
tmux.send_keys :t
tmux.until do |lines|
assert_includes lines[-2], '+T'
assert_includes lines[-2], '+t'
end
tmux.send_keys :BSpace
tmux.until do |lines|
@@ -3014,7 +3113,7 @@ class TestGoFZF < TestBase
tmux.send_keys '4'
tmux.until do |lines|
assert_equal 28, lines.match_count
refute_includes lines[-2], '+T'
refute_includes lines[-2], '+t'
end
tmux.send_keys :BSpace
tmux.until do |lines|
@@ -3023,11 +3122,11 @@ class TestGoFZF < TestBase
end
tmux.send_keys :t
tmux.until do |lines|
assert_includes lines[-2], '+T'
assert_includes lines[-2], '+t'
end
tmux.send_keys :Up
tmux.until do |lines|
refute_includes lines[-2], '+T'
refute_includes lines[-2], '+t'
end
end
@@ -3307,7 +3406,10 @@ module CompletionTest
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
tmux.send_keys 'foobar$'
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until do |lines|
assert_equal 1, lines.match_count
assert lines.any_include?('> /tmp/fzf test/foobar')
end
tmux.send_keys :Enter
tmux.until(true) { |lines| assert_equal 'cat /tmp/fzf\ test/foobar', lines[-1] }