Merge remote-tracking branch 'upstream/master'

pull/2131/head
MenkeTechnologies 2 months ago
commit c9418579a1

@ -15,6 +15,8 @@ body:
required: true
- label: I have searched through the existing issues
required: true
- label: For bug reports, I have checked if the bug is reproducible in the latest version of fzf
required: false
- type: input
attributes:

@ -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

@ -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

@ -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)"
}

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

@ -3,6 +3,16 @@ 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`
@ -18,7 +28,7 @@ CHANGELOG
- `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
- Bug fixes and minor improvements
0.48.1
------

File diff suppressed because one or more lines are too long

@ -1,13 +1,13 @@
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
github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.18.0
golang.org/x/term v0.18.0
golang.org/x/sys v0.19.0
golang.org/x/term v0.19.0
)
require (
@ -17,4 +17,4 @@ require (
golang.org/x/text v0.14.0 // indirect
)
go 1.17
go 1.20

@ -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=
@ -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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.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.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
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=

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

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

@ -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

@ -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

@ -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,20 +372,21 @@ 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 right " On the right end of the horizontal separator"
.br
.BR right " Display on the right end of the next line to the prompt"
.BR hidden " Do not display finder info"
.br
.BR inline " Display on the same line with the default separator ' < '"
.BR inline " After the prompt with the default prefix ' < '"
.br
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
.BR inline:PREFIX " After the prompt with a non-default prefix"
.br
.BR inline-right " Display on the right end of the same line
.BR inline-right " On the right end of the prompt line"
.br
.BR hidden " Do not display finder info"
.BR inline-right:PREFIX " On the right end of the prompt line with a custom prefix"
.br
.TP
@ -1159,6 +1160,7 @@ e.g.
\fB# Move cursor to the last item and select all items
seq 1000 | fzf --multi --sync --bind start:last+select-all\fR
.RE
\fIload\fR
.RS
Triggered when the input stream is complete and the initial processing of the
@ -1168,6 +1170,7 @@ e.g.
\fB# Change the prompt to "loaded" when the input stream is complete
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR
.RE
\fIresize\fR
.RS
Triggered when the terminal size is changed.
@ -1175,6 +1178,7 @@ Triggered when the terminal size is changed.
e.g.
\fBfzf --bind 'resize:transform-header:echo Resized: ${FZF_COLUMNS}x${FZF_LINES}'\fR
.RE
\fIresult\fR
.RS
Triggered when the filtering for the current query is complete and the result list is ready.
@ -1208,6 +1212,7 @@ e.g.
# Beware not to introduce an infinite loop
seq 10 | fzf --bind 'focus:up' --cycle\fR
.RE
\fIone\fR
.RS
Triggered when there's only one match. \fBone:accept\fR binding is comparable
@ -1219,6 +1224,7 @@ e.g.
\fB# Automatically select the only match
seq 10 | fzf --bind one:accept\fR
.RE
\fIzero\fR
.RS
Triggered when there's no match. \fBzero:abort\fR binding is comparable to
@ -1240,6 +1246,22 @@ e.g.
\fBfzf --bind backward-eof:abort\fR
.RE
\fIjump\fR
.RS
Triggered when successfully jumped to the target item in \fB--jump\fR mode.
e.g.
\fBfzf --bind space:jump,jump:accept\fR
.RE
\fIjump-cancel\fR
.RS
Triggered when \fB--jump\fR mode is cancelled.
e.g.
\fBfzf --bind space:jump,jump:accept,jump-cancel:abort\fR
.RE
.SS AVAILABLE ACTIONS:
A key or an event can be bound to one or more of the following actions.

@ -9,7 +9,7 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
[[ $- =~ i ]] || return 0
if [[ $- =~ i ]]; then
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
@ -548,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
@ -581,3 +581,5 @@ _fzf_setup_completion 'var' export unset printenv
_fzf_setup_completion 'alias' unalias
_fzf_setup_completion 'host' telnet
_fzf_setup_completion 'proc' kill
fi

@ -9,7 +9,7 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
[[ -o interactive ]] || return 0
if [[ -o interactive ]]; then
# Both branches of the following `if` do the same thing -- define
@ -357,3 +357,5 @@ bindkey '^I' fzf-completion
eval $__fzf_completion_options
'unset' '__fzf_completion_options'
}
fi

@ -11,7 +11,7 @@
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
[[ $- =~ i ]] || return 0
if [[ $- =~ i ]]; then
# Key bindings
@ -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
@ -132,3 +132,5 @@ if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
fi
fi

@ -21,7 +21,7 @@ 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]
@ -70,7 +70,7 @@ 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]

@ -11,7 +11,7 @@
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
[[ -o interactive ]] || return 0
if [[ -o interactive ]]; then
# Key bindings
@ -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
@ -119,3 +119,5 @@ bindkey -M viins '^R' fzf-history-widget
eval $__fzf_key_bindings_options
'unset' '__fzf_key_bindings_options'
}
fi

@ -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

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

@ -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

@ -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
@ -702,6 +698,10 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
add(tui.One)
case "zero":
add(tui.Zero)
case "jump":
add(tui.Jump)
case "jump-cancel":
add(tui.JumpCancel)
case "alt-enter", "alt-return":
chords[tui.CtrlAltKey('m')] = key
case "alt-space":
@ -1506,17 +1506,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
}
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[:SEPARATOR]|inline-right)")
errorExit("invalid info style (expected: default|right|hidden|inline[-right][:PREFIX])")
}
return infoDefault, ""
}
@ -1807,13 +1814,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":
@ -2015,7 +2022,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 {

@ -1,7 +1,7 @@
package fzf
import (
"bufio"
"bytes"
"context"
"io"
"os"
@ -111,32 +111,84 @@ 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]
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 {
bytea = bytea[:byteaLen-1]
slice = slice[:len(slice)-1]
}
}
if r.pusher(bytea) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
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 != nil {
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))
}
}

@ -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
@ -677,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)
@ -699,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,
@ -882,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
@ -988,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) {
@ -1241,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() {
@ -1522,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 {
@ -1562,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)
@ -1607,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 {
@ -1635,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
}
@ -1669,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 {
@ -1700,19 +1716,35 @@ func (t *Terminal) printInfo() {
}
if t.infoStyle == infoInlineRight {
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
if pos >= t.window.Width() {
return
if len(t.infoPrefix) == 0 {
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
if pos < t.window.Width() {
t.move(line, pos, false)
printSpinner()
pos++
}
if pos < t.window.Width()-1 {
t.window.Print(" ")
pos++
}
} else {
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-util.StringWidth(t.infoPrefix)-1)
printInfoPrefix()
}
t.move(line, pos, false)
printSpinner()
t.window.Print(" ")
pos += 2
}
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, " ")
@ -1727,7 +1759,7 @@ func (t *Terminal) printHeader() {
max := t.window.Height()
if t.headerFirst {
max--
if !t.noInfoLine() {
if !t.noSeparatorLine() {
max--
}
}
@ -1744,7 +1776,7 @@ func (t *Terminal) printHeader() {
}
if !t.headerFirst {
line++
if !t.noInfoLine() {
if !t.noSeparatorLine() {
line++
}
}
@ -1776,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 {
@ -3102,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:
@ -3904,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()
@ -4076,6 +4108,9 @@ func (t *Terminal) Loop() {
// Break out of jump mode if any action is submitted to the server
if t.jumping != jumpDisabled {
t.jumping = jumpDisabled
if acts, prs := t.keymap[tui.JumpCancel.AsEvent()]; prs && !doActions(acts) {
continue
}
req(reqList)
}
if len(actions) == 0 {
@ -4089,19 +4124,17 @@ func (t *Terminal) Loop() {
t.truncateQuery()
queryChanged = string(previousInput) != string(t.input)
changed = changed || queryChanged
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs {
if !doActions(onChanges) {
continue
}
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) {
continue
}
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
if !doActions(onEOFs) {
continue
}
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs && !doActions(onEOFs) {
continue
}
} else {
jumpEvent := tui.JumpCancel
if event.Type == tui.Rune {
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
jumpEvent = tui.Jump
t.cy = idx + t.offset
if t.jumping == jumpAcceptEnabled {
req(reqClose)
@ -4109,6 +4142,9 @@ func (t *Terminal) Loop() {
}
}
t.jumping = jumpDisabled
if acts, prs := t.keymap[jumpEvent.AsEvent()]; prs && !doActions(acts) {
continue
}
req(reqList)
}
@ -4196,7 +4232,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)

@ -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 {

@ -71,7 +71,7 @@ func (r *LightRenderer) csi(code string) string {
func (r *LightRenderer) flush() {
if r.queued.Len() > 0 {
fmt.Fprint(os.Stderr, "\x1b[?25l"+r.queued.String()+"\x1b[?25h")
fmt.Fprint(os.Stderr, "\x1b[?7l\x1b[?25l"+r.queued.String()+"\x1b[?25h\x1b[?7h")
r.queued.Reset()
}
}

@ -108,6 +108,8 @@ const (
One
Zero
Result
Jump
JumpCancel
AltBS

@ -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 {

@ -1468,6 +1468,19 @@ class TestGoFZF < TestBase
assert_equal '3', readonce.chomp
end
def test_jump_events
tmux.send_keys "seq 1000 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump,jump:preview(echo jumped to {}),jump-cancel:preview(echo jump cancelled at {})'")}", :Enter
tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
tmux.send_keys 'C-j'
tmux.until { |lines| assert_includes lines[-7], '5 5' }
tmux.send_keys '3'
tmux.until { |lines| assert(lines.any? { _1.include?('jumped to 3') }) }
tmux.send_keys 'C-j'
tmux.until { |lines| assert_includes lines[-7], '5 5' }
tmux.send_keys 'C-c'
tmux.until { |lines| assert(lines.any? { _1.include?('jump cancelled at 3') }) }
end
def test_pointer
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
# Assert that specified pointer is displayed
@ -1719,7 +1732,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

Loading…
Cancel
Save