Compare commits

..

1 Commits

Author SHA1 Message Date
Junegunn Choi
72f73ee56d Make ctrl-alt-h a synonym of ctrl-alt-backspace on non-Windows environment 2025-11-15 10:43:33 +09:00
35 changed files with 417 additions and 641 deletions

View File

@@ -22,7 +22,6 @@ builds:
- loong64 - loong64
- ppc64le - ppc64le
- s390x - s390x
- riscv64
goarm: goarm:
- "5" - "5"
- "6" - "6"
@@ -40,8 +39,6 @@ builds:
goarch: arm64 goarch: arm64
- goos: openbsd - goos: openbsd
goarch: arm64 goarch: arm64
- goos: openbsd
goarch: riscv64
- goos: android - goos: android
goarch: amd64 goarch: amd64
- goos: android - goos: android
@@ -85,14 +82,12 @@ notarize:
archives: archives:
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" - name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
ids: builds:
- fzf - fzf
formats: format: tar.gz
- tar.gz
format_overrides: format_overrides:
- goos: windows - goos: windows
formats: format: zip
- zip
files: files:
- non-existent* - non-existent*
@@ -104,7 +99,7 @@ release:
name_template: '{{ .Version }}' name_template: '{{ .Version }}'
snapshot: snapshot:
version_template: "{{ .Version }}-devel" name_template: "{{ .Version }}-devel"
changelog: changelog:
sort: asc sort: asc

View File

@@ -1,5 +1,4 @@
GO ?= go GO ?= go
DOCKER ?= docker
GOOS ?= $(shell $(GO) env GOOS) GOOS ?= $(shell $(GO) env GOOS)
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST))) MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
@@ -193,12 +192,12 @@ bin/fzf: target/$(BINARY) | bin
cp -f target/$(BINARY) bin/fzf cp -f target/$(BINARY) bin/fzf
docker: docker:
$(DOCKER) build -t fzf-ubuntu . docker build -t fzf-ubuntu .
$(DOCKER) run -it fzf-ubuntu tmux docker run -it fzf-ubuntu tmux
docker-test: docker-test:
$(DOCKER) build -t fzf-ubuntu . docker build -t fzf-ubuntu .
$(DOCKER) run -it fzf-ubuntu docker run -it fzf-ubuntu
update: update:
$(GO) get -u $(GO) get -u

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.67.0 version=0.66.1
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -177,7 +177,6 @@ case "$archi" in
Linux\ aarch64\ Android) download fzf-$version-android_arm64.tar.gz ;; Linux\ aarch64\ Android) download fzf-$version-android_arm64.tar.gz ;;
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;; Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ loongarch64*) download fzf-$version-linux_loong64.tar.gz ;; Linux\ loongarch64*) download fzf-$version-linux_loong64.tar.gz ;;
Linux\ riscv64*) download fzf-$version-linux_riscv64.tar.gz ;;
Linux\ ppc64le*) download fzf-$version-linux_ppc64le.tar.gz ;; Linux\ ppc64le*) download fzf-$version-linux_ppc64le.tar.gz ;;
Linux\ *64*) download fzf-$version-linux_amd64.tar.gz ;; Linux\ *64*) download fzf-$version-linux_amd64.tar.gz ;;
Linux\ s390x*) download fzf-$version-linux_s390x.tar.gz ;; Linux\ s390x*) download fzf-$version-linux_s390x.tar.gz ;;

View File

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

View File

@@ -11,7 +11,7 @@ import (
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
) )
var version = "0.67" var version = "0.66"
var revision = "devel" var revision = "devel"
//go:embed shell/key-bindings.bash //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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf\-tmux 1 "Nov 2025" "fzf 0.67.0" "fzf\-tmux - open fzf in tmux split pane" .TH fzf\-tmux 1 "Oct 2025" "fzf 0.66.1" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf\-tmux - open fzf in tmux split pane 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Nov 2025" "fzf 0.67.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Oct 2025" "fzf 0.66.1" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -272,7 +272,6 @@ color mappings. Each entry is separated by a comma and/or whitespaces.
\fBgutter \fRGutter on the left \fBgutter \fRGutter on the left
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line) \fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
\fBalt\-bg \fRAlternate background color to create striped lines \fBalt\-bg \fRAlternate background color to create striped lines
\fBalt\-gutter \fRAlternate gutter color to create the striped pattern
\fBquery (input\-fg) \fRQuery string \fBquery (input\-fg) \fRQuery string
\fBghost \fRGhost text (\fB\-\-ghost\fR, \fBdim\fR applied by default) \fBghost \fRGhost text (\fB\-\-ghost\fR, \fBdim\fR applied by default)
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR) \fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
@@ -741,7 +740,7 @@ ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES.
e.g. e.g.
\fB# Prepend the current cursor position in yellow \fB# Prepend the current cursor position in yellow
fzf \-\-info\-command='printf "\\x1b[33;1m$FZF_POS\\x1b[m/$FZF_INFO 💛"'\fR fzf \-\-info\-command='echo \-e "\\x1b[33;1m$FZF_POS\\x1b[m/$FZF_INFO 💛"'\fR
.TP .TP
.B "\-\-no\-info" .B "\-\-no\-info"
@@ -841,9 +840,6 @@ e.g.
# This won't work properly without 'f' flag due to ARG_MAX limit. # This won't work properly without 'f' flag due to ARG_MAX limit.
seq 100000 | fzf \-\-preview "awk '{sum+=\\$1} END {print sum}' {*f}"\fR seq 100000 | fzf \-\-preview "awk '{sum+=\\$1} END {print sum}' {*f}"\fR
\fB# Use {+f} to get the selected items as a line-separated list
seq 100 | fzf \-\-multi \-\-bind 'enter:become:cat {+f}'\fR
Also, Also,
* \fB{q}\fR is replaced to the current query string * \fB{q}\fR is replaced to the current query string
@@ -1499,7 +1495,7 @@ e.g.
.br .br
\fIctrl\-/\fR (\fIctrl\-_\fR) \fIctrl\-/\fR (\fIctrl\-_\fR)
.br .br
\fIctrl\-alt\-[a\-z]\fR (\fIctrl\-alt\-h\fR is \fIctrl\-alt\-backspace\fR on non-Windows) \fIctrl\-alt\-[a\-z]\fR
.br .br
\fIalt\-[*]\fR (Any case-sensitive single character is allowed) \fIalt\-[*]\fR (Any case-sensitive single character is allowed)
.br .br
@@ -1629,7 +1625,7 @@ e.g.
.br .br
\fIctrl\-alt\-end\fR \fIctrl\-alt\-end\fR
.br .br
\fIctrl\-alt\-backspace\fR (\fIctrl\-alt\-bspace\fR \fIctrl\-alt\-bs\fR) (\fIctrl\-alt\-h\fR (non-Windows)) \fIctrl\-alt\-backspace\fR (\fIctrl\-alt\-bspace\fR \fIctrl\-alt\-bs\fR)
.br .br
\fIctrl\-alt\-delete\fR \fIctrl\-alt\-delete\fR
.br .br
@@ -2109,7 +2105,7 @@ payload of HTTP POST request to the \fB\-\-listen\fR server.
e.g. e.g.
\fB# Disallow selecting an empty line \fB# Disallow selecting an empty line
printf "1. Hello\\n2. Goodbye\\n\\n3. Exit" | echo \-e "1. Hello\\n2. Goodbye\\n\\n3. Exit" |
fzf \-\-height '~100%' \-\-reverse \-\-header 'Select one' \\ fzf \-\-height '~100%' \-\-reverse \-\-header 'Select one' \\
\-\-bind 'enter:transform:[[ \-n {} ]] && \-\-bind 'enter:transform:[[ \-n {} ]] &&
echo accept || echo accept ||

View File

@@ -121,7 +121,7 @@ else # awk - fallback for POSIX systems
fi fi
# Required to refresh the prompt after fzf # Required to refresh the prompt after fzf
bind -m emacs-standard '"\C-\e(": redraw-current-line' bind -m emacs-standard '"\er": redraw-current-line'
bind -m vi-command '"\C-z": emacs-editing-mode' bind -m vi-command '"\C-z": emacs-editing-mode'
bind -m vi-insert '"\C-z": emacs-editing-mode' bind -m vi-insert '"\C-z": emacs-editing-mode'
@@ -130,7 +130,7 @@ bind -m emacs-standard '"\C-z": vi-editing-mode'
if ((BASH_VERSINFO[0] < 4)); then if ((BASH_VERSINFO[0] < 4)); then
# CTRL-T - Paste the selected file path into the command line # CTRL-T - Paste the selected file path into the command line
if [[ ${FZF_CTRL_T_COMMAND-x} != "" ]]; then if [[ ${FZF_CTRL_T_COMMAND-x} != "" ]]; then
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\C-\e(\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f\C-y\ey\C-_"' bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f\C-y\ey\C-_"'
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"' bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"' bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
fi fi
@@ -140,7 +140,7 @@ if ((BASH_VERSINFO[0] < 4)); then
if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2 echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
fi fi
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\C-\e("' bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"' bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"' bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
fi fi
@@ -165,7 +165,7 @@ fi
# ALT-C - cd into the selected directory # ALT-C - cd into the selected directory
if [[ ${FZF_ALT_C_COMMAND-x} != "" ]]; then if [[ ${FZF_ALT_C_COMMAND-x} != "" ]]; then
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\C-\e(\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d\C-y\ey\C-_"' bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d\C-y\ey\C-_"'
bind -m vi-command '"\ec": "\C-z\ec\C-z"' bind -m vi-command '"\ec": "\C-z\ec\C-z"'
bind -m vi-insert '"\ec": "\C-z\ec\C-z"' bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
fi fi

View File

@@ -128,52 +128,25 @@ fi
# CTRL-R - Paste the selected command from history into the command line # CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() { fzf-history-widget() {
local selected extracted_with_perl=0 local selected
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases no_glob no_ksharrays extendedglob 2> /dev/null setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
# Ensure the module is loaded if not already, and the required features, such # Ensure the module is loaded if not already, and the required features, such
# as the associative 'history' array, which maps event numbers to full history # as the associative 'history' array, which maps event numbers to full history
# lines, are set. Also, make sure Perl is installed for multi-line output. # lines, are set. Also, make sure Perl is installed for multi-line output.
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" | selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' | perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line --multi ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} --read0") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))" FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
extracted_with_perl=1
else else
selected="$(fc -rl 1 | __fzf_exec_awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' | selected="$(fc -rl 1 | __fzf_exec_awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line --multi ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER}") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))" FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
fi fi
local ret=$? local ret=$?
local -a cmds
# Avoid leaking auto assigned values when using backreferences '(#b)'
local -a mbegin mend match
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
# Heuristic to check if the selected value is from history or a custom query if [[ $(__fzf_exec_awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
if ((( extracted_with_perl )) && [[ $selected == <->$'\t'* ]]) || zle vi-fetch-history -n $MATCH
((( ! extracted_with_perl )) && [[ $selected == [[:blank:]]#<->( |\* )* ]]); then
# Split at newlines
for line in ${(ps:\n:)selected}; do
if (( extracted_with_perl )); then
if [[ $line == (#b)(<->)(#B)$'\t'* ]]; then
(( ${+history[${match[1]}]} )) && cmds+=("${history[${match[1]}]}")
fi
elif [[ $line == [[:blank:]]#(#b)(<->)(#B)( |\* )* ]]; then
# Avoid $history array: lags behind 'fc' on foreign commands (*)
# https://zsh.org/mla/users/2024/msg00692.html
# Push BUFFER onto stack; fetch and save history entry from BUFFER; restore
zle .push-line
zle vi-fetch-history -n ${match[1]}
(( ${#BUFFER} )) && cmds+=("${BUFFER}")
BUFFER=""
zle .get-line
fi
done
if (( ${#cmds[@]} )); then
# Join by newline after stripping trailing newlines from each command
BUFFER="${(pj:\n:)${(@)cmds%%$'\n'#}}"
CURSOR=${#BUFFER}
fi
else # selected is a custom query, not from history else # selected is a custom query, not from history
LBUFFER="$selected" LBUFFER="$selected"
fi fi

View File

@@ -445,9 +445,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
// Since O(nm) algorithm can be prohibitively expensive for large input, // Since O(nm) algorithm can be prohibitively expensive for large input,
// we fall back to the greedy algorithm. // we fall back to the greedy algorithm.
// Also, we should not allow a very long pattern to avoid 16-bit integer if slab != nil && N*M > cap(slab.I16) {
// overflow in the score matrix. 1000 is a safe limit.
if slab != nil && N*M > cap(slab.I16) || M > 1000 {
return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab) return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
} }
@@ -503,7 +501,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
if pidx < M { if pidx < M {
F[pidx] = int32(off) F[pidx] = int32(off)
pidx++ pidx++
pchar = pattern[min(pidx, M-1)] pchar = pattern[util.Min(pidx, M-1)]
} }
lastIdx = off lastIdx = off
} }
@@ -521,9 +519,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
inGap = false inGap = false
} else { } else {
if inGap { if inGap {
H0[off] = max(prevH0+scoreGapExtension, 0) H0[off] = util.Max16(prevH0+scoreGapExtension, 0)
} else { } else {
H0[off] = max(prevH0+scoreGapStart, 0) H0[off] = util.Max16(prevH0+scoreGapStart, 0)
} }
C0[off] = 0 C0[off] = 0
inGap = true inGap = true
@@ -589,7 +587,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
if b >= bonusBoundary && b > fb { if b >= bonusBoundary && b > fb {
consecutive = 1 consecutive = 1
} else { } else {
b = max(b, bonusConsecutive, fb) b = util.Max16(b, util.Max16(bonusConsecutive, fb))
} }
} }
if s1+b < s2 { if s1+b < s2 {
@@ -602,7 +600,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
Csub[off] = consecutive Csub[off] = consecutive
inGap = s1 < s2 inGap = s1 < s2
score := max(s1, s2, 0) score := util.Max16(util.Max16(s1, s2), 0)
if pidx == M-1 && (forward && score > maxScore || !forward && score >= maxScore) { if pidx == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {
maxScore, maxScorePos = score, col maxScore, maxScorePos = score, col
} }
@@ -686,7 +684,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
if bonus >= bonusBoundary && bonus > firstBonus { if bonus >= bonusBoundary && bonus > firstBonus {
firstBonus = bonus firstBonus = bonus
} }
bonus = max(bonus, firstBonus, bonusConsecutive) bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
} }
if pidx == 0 { if pidx == 0 {
score += int(bonus * bonusFirstCharMultiplier) score += int(bonus * bonusFirstCharMultiplier)

View File

@@ -86,7 +86,7 @@ func TestFuzzyMatch(t *testing.T) {
scoreGapStart*2+scoreGapExtension*2) scoreGapStart*2+scoreGapExtension*2)
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4, assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*2+ scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*2+
max(bonusCamel123, int(bonusBoundaryWhite))) util.Max(bonusCamel123, int(bonusBoundaryWhite)))
// Consecutive bonus updated // Consecutive bonus updated
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6, assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,

View File

@@ -38,18 +38,6 @@ func (r revision) compatible(other revision) bool {
return r.major == other.major return r.major == other.major
} }
func buildItemTransformer(opts *Options) func(*Item) string {
if opts.AcceptNth != nil {
fn := opts.AcceptNth(opts.Delimiter)
return func(item *Item) string {
return item.acceptNth(opts.Ansi, opts.Delimiter, fn)
}
}
return func(item *Item) string {
return item.AsString(opts.Ansi)
}
}
// Run starts fzf // Run starts fzf
func Run(opts *Options) (int, error) { func Run(opts *Options) (int, error) {
if opts.Filter == nil { if opts.Filter == nil {
@@ -159,7 +147,7 @@ func Run(opts *Options) (int, error) {
if item.colors != nil { if item.colors != nil {
for _, ansi := range *item.colors { for _, ansi := range *item.colors {
if ansi.color.bg >= 0 { if ansi.color.bg >= 0 {
maxColorOffset = max(maxColorOffset, ansi.offset[1]) maxColorOffset = util.Max32(maxColorOffset, ansi.offset[1])
} }
} }
} }
@@ -255,8 +243,6 @@ func Run(opts *Options) (int, error) {
pattern := patternBuilder([]rune(*opts.Filter)) pattern := patternBuilder([]rune(*opts.Filter))
matcher.sort = pattern.sortable matcher.sort = pattern.sortable
transformer := buildItemTransformer(opts)
found := false found := false
if streamingFilter { if streamingFilter {
slab := util.MakeSlab(slab16Size, slab32Size) slab := util.MakeSlab(slab16Size, slab32Size)
@@ -267,7 +253,7 @@ func Run(opts *Options) (int, error) {
if chunkList.trans(&item, runes) { if chunkList.trans(&item, runes) {
mutex.Lock() mutex.Lock()
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil { if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
opts.Printer(transformer(&item)) opts.Printer(item.text.ToString())
found = true found = true
} }
mutex.Unlock() mutex.Unlock()
@@ -285,7 +271,7 @@ func Run(opts *Options) (int, error) {
chunks: snapshot, chunks: snapshot,
pattern: pattern}) pattern: pattern})
for i := 0; i < result.merger.Length(); i++ { for i := 0; i < result.merger.Length(); i++ {
opts.Printer(transformer(result.merger.Get(i).item)) opts.Printer(result.merger.Get(i).item.AsString(opts.Ansi))
found = true found = true
} }
} }
@@ -333,7 +319,7 @@ func Run(opts *Options) (int, error) {
if total >= maxFit || final { if total >= maxFit || final {
deferred = false deferred = false
heightUnknown = false heightUnknown = false
terminal.startChan <- fitpad{min(total, maxFit), padHeight} terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
} }
} else if deferred { } else if deferred {
deferred = false deferred = false
@@ -507,7 +493,15 @@ func Run(opts *Options) (int, error) {
if len(opts.Expect) > 0 { if len(opts.Expect) > 0 {
opts.Printer("") opts.Printer("")
} }
transformer := buildItemTransformer(opts) transformer := func(item *Item) string {
return item.AsString(opts.Ansi)
}
if opts.AcceptNth != nil {
fn := opts.AcceptNth(opts.Delimiter)
transformer = func(item *Item) string {
return item.acceptNth(opts.Ansi, opts.Delimiter, fn)
}
}
for i := range count { for i := range count {
opts.Printer(transformer(merger.Get(i).item)) opts.Printer(transformer(merger.Get(i).item))
} }
@@ -530,7 +524,7 @@ func Run(opts *Options) (int, error) {
break break
} }
if delay && reading { if delay && reading {
dur := util.Constrain( dur := util.DurWithin(
time.Duration(ticks-startTick)*coordinatorDelayStep, time.Duration(ticks-startTick)*coordinatorDelayStep,
0, coordinatorDelayMax) 0, coordinatorDelayMax)
time.Sleep(dur) time.Sleep(dur)

View File

@@ -55,7 +55,7 @@ const (
// NewMatcher returns a new Matcher // NewMatcher returns a new Matcher
func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern, func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
sort bool, tac bool, eventBox *util.EventBox, revision revision) *Matcher { sort bool, tac bool, eventBox *util.EventBox, revision revision) *Matcher {
partitions := min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions) partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
return &Matcher{ return &Matcher{
cache: cache, cache: cache,
patternBuilder: patternBuilder, patternBuilder: patternBuilder,

View File

@@ -1217,7 +1217,7 @@ func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Eve
default: default:
runes := []rune(key) runes := []rune(key)
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) { if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
r := rune(lkey[9]) r := rune(key[9])
evt := tui.CtrlAltKey(r) evt := tui.CtrlAltKey(r)
if r == 'h' && !util.IsWindows() { if r == 'h' && !util.IsWindows() {
evt = tui.CtrlAltBackspace.AsEvent() evt = tui.CtrlAltBackspace.AsEvent()
@@ -1225,12 +1225,7 @@ func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Eve
chords[evt] = key chords[evt] = key
list = append(list, evt) list = append(list, evt)
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) { } else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
evt := tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a') add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
r := rune(lkey[5])
if r == 'h' && !util.IsWindows() {
evt = tui.CtrlBackspace
}
add(evt)
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") { } else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
r := runes[4] r := runes[4]
switch r { switch r {
@@ -1476,8 +1471,6 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, *tui
mergeAttr(&theme.Nomatch) mergeAttr(&theme.Nomatch)
case "gutter": case "gutter":
mergeAttr(&theme.Gutter) mergeAttr(&theme.Gutter)
case "alt-gutter":
mergeAttr(&theme.AltGutter)
case "hl": case "hl":
mergeAttr(&theme.Match) mergeAttr(&theme.Match)
case "current-hl", "hl+": case "current-hl", "hl+":

View File

@@ -178,7 +178,7 @@ func (r *Reader) feed(src io.Reader) {
var err error var err error
for { for {
n := 0 n := 0
scope := slab[:min(len(slab), readerBufferSize)] scope := slab[:util.Min(len(slab), readerBufferSize)]
for range 100 { for range 100 {
n, err = src.Read(scope) n, err = src.Read(scope)
if n > 0 || err != nil { if n > 0 || err != nil {

View File

@@ -42,9 +42,9 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
for _, offset := range offsets { for _, offset := range offsets {
b, e := int(offset[0]), int(offset[1]) b, e := int(offset[0]), int(offset[1])
if b < e { if b < e {
minBegin = min(b, minBegin) minBegin = util.Min(b, minBegin)
minEnd = min(e, minEnd) minEnd = util.Min(e, minEnd)
maxEnd = max(e, maxEnd) maxEnd = util.Max(e, maxEnd)
validOffsetFound = true validOffsetFound = true
} }
} }

View File

@@ -900,7 +900,7 @@ func evaluateHeight(opts *Options, termHeight int) int {
if opts.Height.inverse { if opts.Height.inverse {
size = 100 - size size = 100 - size
} }
return max(int(size*float64(termHeight)/100.0), opts.MinHeight) return util.Max(int(size*float64(termHeight)/100.0), opts.MinHeight)
} }
if opts.Height.inverse { if opts.Height.inverse {
size = float64(termHeight) - size size = float64(termHeight) - size
@@ -956,7 +956,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
effectiveMinHeight-- effectiveMinHeight--
} }
effectiveMinHeight += borderLines(opts.BorderShape) effectiveMinHeight += borderLines(opts.BorderShape)
return min(termHeight, max(evaluateHeight(opts, termHeight), effectiveMinHeight)) return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
} }
renderer, err = tui.NewLightRenderer(opts.TtyDefault, ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc) renderer, err = tui.NewLightRenderer(opts.TtyDefault, ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
} }
@@ -1153,8 +1153,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
t.pointerEmpty = "" t.pointerEmpty = ""
t.pointerEmptyRaw = "" t.pointerEmptyRaw = ""
} else { } else {
t.pointerEmpty = gutterChar + strings.Repeat(" ", max(0, t.pointerLen-1)) t.pointerEmpty = gutterChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
t.pointerEmptyRaw = gutterRawChar + strings.Repeat(" ", max(0, t.pointerLen-1)) t.pointerEmptyRaw = gutterRawChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
} }
t.markerEmpty = strings.Repeat(" ", t.markerLen) t.markerEmpty = strings.Repeat(" ", t.markerLen)
@@ -1349,7 +1349,7 @@ func (t *Terminal) environImpl(forPreview bool) []string {
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected))) env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
env = append(env, fmt.Sprintf("FZF_LINES=%d", t.areaLines)) env = append(env, fmt.Sprintf("FZF_LINES=%d", t.areaLines))
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns)) env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
env = append(env, fmt.Sprintf("FZF_POS=%d", min(t.merger.Length(), t.cy+1))) env = append(env, fmt.Sprintf("FZF_POS=%d", util.Min(t.merger.Length(), t.cy+1)))
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_LINE=%d", t.clickHeaderLine)) env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_LINE=%d", t.clickHeaderLine))
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn)) env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn))
env = append(env, fmt.Sprintf("FZF_CLICK_FOOTER_LINE=%d", t.clickFooterLine)) env = append(env, fmt.Sprintf("FZF_CLICK_FOOTER_LINE=%d", t.clickFooterLine))
@@ -1586,12 +1586,12 @@ func getScrollbar(perLine int, total int, height int, offset int) (int, int) {
if total == 0 || total*perLine <= height { if total == 0 || total*perLine <= height {
return 0, 0 return 0, 0
} }
barLength := max(1, height*height/(total*perLine)) barLength := util.Max(1, height*height/(total*perLine))
var barStart int var barStart int
if total == height { if total == height {
barStart = 0 barStart = 0
} else { } else {
barStart = min(height-barLength, (height*perLine-barLength)*offset/(total*perLine-height)) barStart = util.Min(height-barLength, (height*perLine-barLength)*offset/(total*perLine-height))
} }
return barLength, barStart return barLength, barStart
} }
@@ -1607,7 +1607,7 @@ func (t *Terminal) wrapCols() int {
if !t.wrap { if !t.wrap {
return 0 // No wrap return 0 // No wrap
} }
return max(t.window.Width()-(t.pointerLen+t.markerLen+t.barCol()), 1) return util.Max(t.window.Width()-(t.pointerLen+t.markerLen+t.barCol()), 1)
} }
func (t *Terminal) clearNumLinesCache() { func (t *Terminal) clearNumLinesCache() {
@@ -1662,7 +1662,7 @@ func (t *Terminal) avgNumLines() int {
numLines := 0 numLines := 0
count := 0 count := 0
total := t.merger.Length() total := t.merger.Length()
offset := max(0, min(t.offset, total-maxItems-1)) offset := util.Max(0, util.Min(t.offset, total-maxItems-1))
for idx := 0; idx < maxItems && idx+offset < total; idx++ { for idx := 0; idx < maxItems && idx+offset < total; idx++ {
result := t.merger.Get(idx + offset) result := t.merger.Get(idx + offset)
lines, _ := t.numItemLines(result.item, maxItems) lines, _ := t.numItemLines(result.item, maxItems)
@@ -1822,7 +1822,7 @@ func (t *Terminal) UpdateList(result MatchResult) {
t.offset = 0 t.offset = 0
} else if t.cy > count { } else if t.cy > count {
// Try to keep the vertical position when the list shrinks // Try to keep the vertical position when the list shrinks
t.cy = count - min(count, t.maxItems()) + pos t.cy = count - util.Min(count, t.maxItems()) + pos
} }
} }
needActivation := false needActivation := false
@@ -1987,17 +1987,17 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx] marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
} }
adjust := func(idx1 int, idx2 int, maximum int, minimum int) { adjust := func(idx1 int, idx2 int, max int, min int) {
if minimum > maximum { if min > max {
minimum = maximum min = max
} }
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2] margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
if maximum-margin < minimum { if max-margin < min {
desired := maximum - minimum desired := max - min
paddingInt[idx1] = desired * paddingInt[idx1] / margin paddingInt[idx1] = desired * paddingInt[idx1] / margin
paddingInt[idx2] = desired * paddingInt[idx2] / margin paddingInt[idx2] = desired * paddingInt[idx2] / margin
marginInt[idx1] = max(extraMargin[idx1], desired*marginInt[idx1]/margin) marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin)
marginInt[idx2] = max(extraMargin[idx2], desired*marginInt[idx2]/margin) marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
} }
} }
@@ -2014,10 +2014,10 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
switch t.activePreviewOpts.position { switch t.activePreviewOpts.position {
case posUp, posDown: case posUp, posDown:
minAreaHeight += minPreviewHeight minAreaHeight += minPreviewHeight
minAreaWidth = max(minPreviewWidth, minAreaWidth) minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
case posLeft, posRight: case posLeft, posRight:
minAreaWidth += minPreviewWidth minAreaWidth += minPreviewWidth
minAreaHeight = max(minPreviewHeight, minAreaHeight) minAreaHeight = util.Max(minPreviewHeight, minAreaHeight)
} }
} }
adjust(1, 3, screenWidth, minAreaWidth) adjust(1, 3, screenWidth, minAreaWidth)
@@ -2490,8 +2490,6 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
if shape.HasRight() { if shape.HasRight() {
width++ width++
} }
// Make sure that the width does not exceed the list width
width = min(t.window.Width()+t.headerIndentImpl(0, shape), width)
height := b.Height() - borderLines(shape) height := b.Height() - borderLines(shape)
return t.tui.NewWindow(top, left, width, height, windowType, noBorder, true) return t.tui.NewWindow(top, left, width, height, windowType, noBorder, true)
} }
@@ -2655,11 +2653,11 @@ func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts label
} }
var col int var col int
if opts.column == 0 { if opts.column == 0 {
col = max(0, (window.Width()-length)/2) col = util.Max(0, (window.Width()-length)/2)
} else if opts.column < 0 { } else if opts.column < 0 {
col = max(0, window.Width()+opts.column+1-length) col = util.Max(0, window.Width()+opts.column+1-length)
} else { } else {
col = min(opts.column-1, window.Width()-length) col = util.Min(opts.column-1, window.Width()-length)
} }
row := 0 row := 0
if borderShape == tui.BorderBottom || opts.bottom { if borderShape == tui.BorderBottom || opts.bottom {
@@ -2714,7 +2712,7 @@ func (t *Terminal) truncateQuery() {
// the user accidentally pastes a huge chunk of text. Therefore, we're not // the user accidentally pastes a huge chunk of text. Therefore, we're not
// interested in the exact display width of the query. We just limit the // interested in the exact display width of the query. We just limit the
// number of runes. // number of runes.
t.input = t.input[:min(len(t.input), maxPatternLength)] t.input = t.input[:util.Min(len(t.input), maxPatternLength)]
t.cx = util.Constrain(t.cx, 0, len(t.input)) t.cx = util.Constrain(t.cx, 0, len(t.input))
} }
@@ -2723,11 +2721,11 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
if t.inputWindow != nil { if t.inputWindow != nil {
w = t.inputWindow w = t.inputWindow
} }
maxWidth := max(1, w.Width()-t.promptLen-1) maxWidth := util.Max(1, w.Width()-t.promptLen-1)
_, overflow := t.trimLeft(t.input[:t.cx], maxWidth, 0) _, overflow := t.trimLeft(t.input[:t.cx], maxWidth, 0)
minOffset := int(overflow) minOffset := int(overflow)
maxOffset := minOffset + (maxWidth-max(0, maxWidth-t.cx))/2 maxOffset := minOffset + (maxWidth-util.Max(0, maxWidth-t.cx))/2
t.xoffset = util.Constrain(t.xoffset, minOffset, maxOffset) t.xoffset = util.Constrain(t.xoffset, minOffset, maxOffset)
before, _ := t.trimLeft(t.input[t.xoffset:t.cx], maxWidth, 0) before, _ := t.trimLeft(t.input[t.xoffset:t.cx], maxWidth, 0)
beforeLen := t.displayWidth(before) beforeLen := t.displayWidth(before)
@@ -2749,7 +2747,7 @@ func (t *Terminal) promptLine() int {
if !t.noSeparatorLine() { if !t.noSeparatorLine() {
max-- max--
} }
return min(t.visibleHeaderLinesInList(), max) return util.Min(t.visibleHeaderLinesInList(), max)
} }
return 0 return 0
} }
@@ -2764,11 +2762,11 @@ func (t *Terminal) placeCursor() {
if t.layout == layoutReverse { if t.layout == layoutReverse {
y = 0 y = 0
} }
x = min(x, t.inputWindow.Width()-1) x = util.Min(x, t.inputWindow.Width()-1)
t.inputWindow.Move(y, x) t.inputWindow.Move(y, x)
return return
} }
x = min(x, t.window.Width()-1) x = util.Min(x, t.window.Width()-1)
t.move(t.promptLine(), x, false) t.move(t.promptLine(), x, false)
} }
@@ -2787,7 +2785,7 @@ func (t *Terminal) printPrompt() {
before, after := t.updatePromptOffset() before, after := t.updatePromptOffset()
if len(before) == 0 && len(after) == 0 && len(t.ghost) > 0 { if len(before) == 0 && len(after) == 0 && len(t.ghost) > 0 {
maxWidth := max(1, w.Width()-t.promptLen-1) maxWidth := util.Max(1, w.Width()-t.promptLen-1)
runes, _ := t.trimRight([]rune(t.ghost), maxWidth) runes, _ := t.trimRight([]rune(t.ghost), maxWidth)
w.CPrint(tui.ColGhost, string(runes)) w.CPrint(tui.ColGhost, string(runes))
return return
@@ -2877,7 +2875,7 @@ func (t *Terminal) printInfoImpl() {
} }
found := t.resultMerger.Length() found := t.resultMerger.Length()
total := max(found, t.count) total := util.Max(found, t.count)
output := fmt.Sprintf("%d/%d", found, total) output := fmt.Sprintf("%d/%d", found, total)
if t.toggleSort { if t.toggleSort {
if t.sort { if t.sort {
@@ -2971,7 +2969,7 @@ func (t *Terminal) printInfoImpl() {
if t.infoStyle == infoInlineRight { if t.infoStyle == infoInlineRight {
if len(t.infoPrefix) == 0 { if len(t.infoPrefix) == 0 {
move(line, pos, false) move(line, pos, false)
newPos := max(pos, t.window.Width()-outputLen-3) newPos := util.Max(pos, t.window.Width()-outputLen-3)
t.window.Print(strings.Repeat(" ", newPos-pos)) t.window.Print(strings.Repeat(" ", newPos-pos))
pos = newPos pos = newPos
if pos < t.window.Width() { if pos < t.window.Width() {
@@ -2983,7 +2981,7 @@ func (t *Terminal) printInfoImpl() {
pos++ pos++
} }
} else { } else {
pos = max(pos, t.window.Width()-outputLen-util.StringWidth(t.infoPrefix)-1) pos = util.Max(pos, t.window.Width()-outputLen-util.StringWidth(t.infoPrefix)-1)
printInfoPrefix() printInfoPrefix()
} }
} }
@@ -3079,7 +3077,7 @@ func (t *Terminal) printFooter() {
} }
indentSize := t.headerIndent(t.footerBorderShape) indentSize := t.headerIndent(t.footerBorderShape)
indent := strings.Repeat(" ", indentSize) indent := strings.Repeat(" ", indentSize)
max := min(len(t.footer), t.footerWindow.Height()) max := util.Min(len(t.footer), t.footerWindow.Height())
// Wrapping is not supported for footer // Wrapping is not supported for footer
wrap := t.wrap wrap := t.wrap
@@ -3109,11 +3107,7 @@ func (t *Terminal) printFooter() {
} }
func (t *Terminal) headerIndent(borderShape tui.BorderShape) int { func (t *Terminal) headerIndent(borderShape tui.BorderShape) int {
return t.headerIndentImpl(t.pointerLen+t.markerLen, borderShape) indentSize := t.pointerLen + t.markerLen
}
func (t *Terminal) headerIndentImpl(base int, borderShape tui.BorderShape) int {
indentSize := base
if t.listBorderShape.HasLeft() { if t.listBorderShape.HasLeft() {
indentSize += 1 + t.borderWidth indentSize += 1 + t.borderWidth
} }
@@ -3202,22 +3196,14 @@ func (t *Terminal) renderEmptyLine(line int, barRange [2]int) {
t.renderBar(line, barRange) t.renderBar(line, barRange)
} }
func (t *Terminal) gutter(current bool, alt bool) { func (t *Terminal) gutter(current bool) {
var color tui.ColorPair var color tui.ColorPair
if current { if current {
color = tui.ColCurrentCursorEmpty color = tui.ColCurrentCursorEmpty
} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse { } else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
if alt { color = tui.ColCursorEmpty
color = tui.ColAltCursorEmpty
} else {
color = tui.ColCursorEmpty
}
} else { } else {
if alt { color = tui.ColCursorEmptyChar
color = tui.ColAltCursorEmptyChar
} else {
color = tui.ColCursorEmptyChar
}
} }
gutter := t.pointerEmpty gutter := t.pointerEmpty
if t.raw { if t.raw {
@@ -3228,7 +3214,7 @@ func (t *Terminal) gutter(current bool, alt bool) {
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) { func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
t.move(line, 0, false) t.move(line, 0, false)
t.gutter(false, false) t.gutter(false)
t.window.Print(t.markerEmpty) t.window.Print(t.markerEmpty)
x := t.pointerLen + t.markerLen x := t.pointerLen + t.markerLen
@@ -3304,7 +3290,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
} else { } else {
alt = index%2 == 1 alt = index%2 == 1
} }
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", max(0, t.pointerLen-1)) label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
if t.pointerLen == 0 { if t.pointerLen == 0 {
extraWidth = 1 extraWidth = 1
} }
@@ -3402,7 +3388,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
return indentSize return indentSize
} }
if len(label) == 0 { if len(label) == 0 {
t.gutter(true, false) t.gutter(true)
} else { } else {
t.window.CPrint(tui.ColCurrentCursor, label) t.window.CPrint(tui.ColCurrentCursor, label)
} }
@@ -3424,7 +3410,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
return indentSize return indentSize
} }
if len(label) == 0 { if len(label) == 0 {
t.gutter(false, index%2 == 1) t.gutter(false)
} else { } else {
t.window.CPrint(tui.ColCursor, label) t.window.CPrint(tui.ColCursor, label)
} }
@@ -3474,7 +3460,7 @@ func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit in
} }
func (t *Terminal) trimLeft(runes []rune, width int, ellipsisWidth int) ([]rune, int32) { func (t *Terminal) trimLeft(runes []rune, width int, ellipsisWidth int) ([]rune, int32) {
width = max(0, width) width = util.Max(0, width)
var trimmed int32 var trimmed int32
// Assume that each rune takes at least one column on screen // Assume that each rune takes at least one column on screen
if len(runes) > width { if len(runes) > width {
@@ -3565,12 +3551,12 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
// ------> <------ // ------> <------
if t.freezeLeft > 0 { if t.freezeLeft > 0 {
if len(tokens) > 0 { if len(tokens) > 0 {
token := tokens[min(t.freezeLeft, len(tokens))-1] token := tokens[util.Min(t.freezeLeft, len(tokens))-1]
splitOffset1 = int(token.prefixLength) + token.text.Length() - token.text.TrailingWhitespaces() splitOffset1 = int(token.prefixLength) + token.text.Length() - token.text.TrailingWhitespaces()
} }
} }
if t.freezeRight > 0 { if t.freezeRight > 0 {
index := max(t.freezeLeft-1, len(tokens)-t.freezeRight-1) index := util.Max(t.freezeLeft-1, len(tokens)-t.freezeRight-1)
if index < 0 { if index < 0 {
splitOffset2 = 0 splitOffset2 = 0
} else if index >= t.freezeLeft { } else if index >= t.freezeLeft {
@@ -3578,7 +3564,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
delimiter := strings.TrimLeftFunc(GetLastDelimiter(token.text.ToString(), t.delimiter), unicode.IsSpace) delimiter := strings.TrimLeftFunc(GetLastDelimiter(token.text.ToString(), t.delimiter), unicode.IsSpace)
splitOffset2 = int(token.prefixLength) + token.text.Length() - len([]rune(delimiter)) splitOffset2 = int(token.prefixLength) + token.text.Length() - len([]rune(delimiter))
} }
splitOffset2 = max(splitOffset2, splitOffset1) splitOffset2 = util.Max(splitOffset2, splitOffset1)
} }
} }
@@ -3668,7 +3654,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
var maxEnd int var maxEnd int
for _, offset := range offsets { for _, offset := range offsets {
if offset.match { if offset.match {
maxEnd = max(maxEnd, int(offset.offset[1])) maxEnd = util.Max(maxEnd, int(offset.offset[1]))
} }
} }
@@ -3767,19 +3753,19 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
} }
displayWidth = t.displayWidthWithLimit(runes, 0, adjustedMaxWidth) displayWidth = t.displayWidthWithLimit(runes, 0, adjustedMaxWidth)
if !t.wrap && displayWidth > adjustedMaxWidth { if !t.wrap && displayWidth > adjustedMaxWidth {
maxe = util.Constrain(maxe+min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(runes)) maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(runes))
transformOffsets := func(diff int32, rightTrim bool) { transformOffsets := func(diff int32, rightTrim bool) {
for idx, offset := range offs { for idx, offset := range offs {
b, e := offset.offset[0], offset.offset[1] b, e := offset.offset[0], offset.offset[1]
el := int32(len(ellipsis)) el := int32(len(ellipsis))
b += el - diff b += el - diff
e += el - diff e += el - diff
b = max(b, el) b = util.Max32(b, el)
if rightTrim { if rightTrim {
e = min(e, int32(maxWidth-ellipsisWidth)) e = util.Min32(e, int32(maxWidth-ellipsisWidth))
} }
offs[idx].offset[0] = b offs[idx].offset[0] = b
offs[idx].offset[1] = max(b, e) offs[idx].offset[1] = util.Max32(b, e)
} }
} }
if t.hscroll { if t.hscroll {
@@ -3811,11 +3797,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
runes = append(runes, ellipsis...) runes = append(runes, ellipsis...)
for idx, offset := range offs { for idx, offset := range offs {
offs[idx].offset[0] = min(offset.offset[0], int32(maxWidth-len(ellipsis))) offs[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-len(ellipsis)))
offs[idx].offset[1] = min(offset.offset[1], int32(maxWidth)) offs[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
} }
} }
displayWidth = t.displayWidthWithLimit(runes, 0, maxWidth) displayWidth = t.displayWidthWithLimit(runes, 0, displayWidth)
} }
displayWidthSum += displayWidth displayWidthSum += displayWidth
@@ -3859,8 +3845,8 @@ func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []
maxOffset := int32(len(text)) maxOffset := int32(len(text))
var url *url var url *url
for _, offset := range offsets { for _, offset := range offsets {
b := util.Constrain(offset.offset[0], index, maxOffset) b := util.Constrain32(offset.offset[0], index, maxOffset)
e := util.Constrain(offset.offset[1], index, maxOffset) e := util.Constrain32(offset.offset[1], index, maxOffset)
if url != nil && offset.url != url { if url != nil && offset.url != url {
url = nil url = nil
window.LinkEnd() window.LinkEnd()
@@ -3941,7 +3927,7 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
body := t.previewer.lines body := t.previewer.lines
headerLines := t.activePreviewOpts.headerLines headerLines := t.activePreviewOpts.headerLines
// Do not enable preview header lines if it's value is too large // Do not enable preview header lines if it's value is too large
if headerLines > 0 && headerLines < min(len(body), height) { if headerLines > 0 && headerLines < util.Min(len(body), height) {
header := t.previewer.lines[0:headerLines] header := t.previewer.lines[0:headerLines]
body = t.previewer.lines[headerLines:] body = t.previewer.lines[headerLines:]
// Always redraw header // Always redraw header
@@ -3959,7 +3945,7 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
} }
effectiveHeight := height - headerLines effectiveHeight := height - headerLines
barLength, barStart := getScrollbar(1, len(body), effectiveHeight, min(len(body)-effectiveHeight, t.previewer.offset-headerLines)) barLength, barStart := getScrollbar(1, len(body), effectiveHeight, util.Min(len(body)-effectiveHeight, t.previewer.offset-headerLines))
t.renderPreviewScrollbar(headerLines, barLength, barStart) t.renderPreviewScrollbar(headerLines, barLength, barStart)
} }
@@ -3974,7 +3960,7 @@ func (t *Terminal) makeImageBorder(width int, top bool) string {
h = "-" h = "-"
v = "|" v = "|"
} }
repeat := max(0, width-2) repeat := util.Max(0, width-2)
if top { if top {
return tl + strings.Repeat(h, repeat) + tr return tl + strings.Repeat(h, repeat) + tr
} }
@@ -4509,7 +4495,7 @@ func (t *Terminal) evaluateScrollOffset() int {
} }
base := -1 base := -1
height := max(0, t.pwindow.Height()-t.activePreviewOpts.headerLines) height := util.Max(0, t.pwindow.Height()-t.activePreviewOpts.headerLines)
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) { for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
if strings.HasPrefix(component, "-/") { if strings.HasPrefix(component, "-/") {
component = component[1:] component = component[1:]
@@ -4523,7 +4509,7 @@ func (t *Terminal) evaluateScrollOffset() int {
} }
base += atoi(component) base += atoi(component)
} }
return max(0, base) return util.Max(0, base)
} }
func replacePlaceholder(params replacePlaceholderParams) (string, []string) { func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
@@ -5143,16 +5129,16 @@ func (t *Terminal) Loop() error {
if t.activePreviewOpts.aboveOrBelow() { if t.activePreviewOpts.aboveOrBelow() {
if t.activePreviewOpts.size.percent { if t.activePreviewOpts.size.percent {
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.activePreviewOpts.size.size)) newContentHeight := int(float64(contentHeight) * 100. / (100. - t.activePreviewOpts.size.size))
contentHeight = max(contentHeight+1+borderLines(t.activePreviewOpts.Border()), newContentHeight) contentHeight = util.Max(contentHeight+1+borderLines(t.activePreviewOpts.Border()), newContentHeight)
} else { } else {
contentHeight += int(t.activePreviewOpts.size.size) + borderLines(t.activePreviewOpts.Border()) contentHeight += int(t.activePreviewOpts.size.size) + borderLines(t.activePreviewOpts.Border())
} }
} else { } else {
// Minimum height if preview window can appear // Minimum height if preview window can appear
contentHeight = max(contentHeight, 1+borderLines(t.activePreviewOpts.Border())) contentHeight = util.Max(contentHeight, 1+borderLines(t.activePreviewOpts.Border()))
} }
} }
return min(termHeight, contentHeight+pad) return util.Min(termHeight, contentHeight+pad)
}) })
} }
@@ -5553,7 +5539,7 @@ func (t *Terminal) Loop() error {
t.previewer.lines = result.lines t.previewer.lines = result.lines
t.previewer.spinner = result.spinner t.previewer.spinner = result.spinner
if t.hasPreviewWindow() && t.previewer.following.Enabled() { if t.hasPreviewWindow() && t.previewer.following.Enabled() {
t.previewer.offset = max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.activePreviewOpts.headerLines)) t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.activePreviewOpts.headerLines))
} else if result.offset >= 0 { } else if result.offset >= 0 {
t.previewer.offset = util.Constrain(result.offset, t.activePreviewOpts.headerLines, len(t.previewer.lines)-1) t.previewer.offset = util.Constrain(result.offset, t.activePreviewOpts.headerLines, len(t.previewer.lines)-1)
} }
@@ -5615,7 +5601,7 @@ func (t *Terminal) Loop() error {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case t.keyChan <- t.tui.GetChar(t.listenAddr != nil): case t.keyChan <- t.tui.GetChar():
} }
} }
}() }()
@@ -5702,13 +5688,6 @@ func (t *Terminal) Loop() error {
} }
} }
} }
for _, action := range actions {
if action.t == actExecute {
t.tui.CancelGetChar()
break
}
}
case callback := <-t.callbackChan: case callback := <-t.callbackChan:
event = tui.Invalid.AsEvent() event = tui.Invalid.AsEvent()
actions = append(actions, &action{t: actAsync}) actions = append(actions, &action{t: actAsync})
@@ -6303,7 +6282,7 @@ func (t *Terminal) Loop() error {
// Try to retain position // Try to retain position
if prevIndex != minItem.Index() { if prevIndex != minItem.Index() {
t.cy = max(0, t.merger.FindIndex(prevIndex)) t.cy = util.Max(0, t.merger.FindIndex(prevIndex))
t.offset = t.cy - prevPos t.offset = t.cy - prevPos
} }
@@ -6399,7 +6378,7 @@ func (t *Terminal) Loop() error {
linesToMove = maxItems / 2 linesToMove = maxItems / 2
} }
// Move at least one line even in a very short window // Move at least one line even in a very short window
linesToMove = max(1, linesToMove) linesToMove = util.Max(1, linesToMove)
// Determine the direction of the movement // Determine the direction of the movement
direction := -1 direction := -1
@@ -6708,7 +6687,7 @@ func (t *Terminal) Loop() error {
if pbarDragging { if pbarDragging {
effectiveHeight := t.pwindow.Height() - headerLines effectiveHeight := t.pwindow.Height() - headerLines
numLines := len(t.previewer.lines) - headerLines numLines := len(t.previewer.lines) - headerLines
barLength, _ := getScrollbar(1, numLines, effectiveHeight, min(numLines-effectiveHeight, t.previewer.offset-headerLines)) barLength, _ := getScrollbar(1, numLines, effectiveHeight, util.Min(numLines-effectiveHeight, t.previewer.offset-headerLines))
if barLength > 0 { if barLength > 0 {
y := my - t.pwindow.Top() - headerLines - barLength/2 y := my - t.pwindow.Top() - headerLines - barLength/2
y = util.Constrain(y, 0, effectiveHeight-barLength) y = util.Constrain(y, 0, effectiveHeight-barLength)
@@ -7228,13 +7207,13 @@ func (t *Terminal) constrain() {
numItems = numItemsFound numItems = numItemsFound
} }
t.cy = util.Constrain(t.cy, 0, max(0, count-1)) t.cy = util.Constrain(t.cy, 0, util.Max(0, count-1))
minOffset := max(t.cy-numItems+1, 0) minOffset := util.Max(t.cy-numItems+1, 0)
maxOffset := max(min(count-numItems, t.cy), 0) maxOffset := util.Max(util.Min(count-numItems, t.cy), 0)
prevOffset := t.offset prevOffset := t.offset
t.offset = util.Constrain(t.offset, minOffset, maxOffset) t.offset = util.Constrain(t.offset, minOffset, maxOffset)
if t.scrollOff > 0 { if t.scrollOff > 0 {
scrollOff := min(maxLines/2, t.scrollOff) scrollOff := util.Min(maxLines/2, t.scrollOff)
newOffset := t.offset newOffset := t.offset
// 2-phase adjustment to avoid infinite loop of alternating between moving up and down // 2-phase adjustment to avoid infinite loop of alternating between moving up and down
for phase := range 2 { for phase := range 2 {
@@ -7261,9 +7240,9 @@ func (t *Terminal) constrain() {
} }
if phase == 0 && linesBefore < scrollOff { if phase == 0 && linesBefore < scrollOff {
newOffset = max(minOffset, newOffset-1) newOffset = util.Max(minOffset, newOffset-1)
} else if phase == 1 && linesAfter < scrollOff { } else if phase == 1 && linesAfter < scrollOff {
newOffset = min(maxOffset, newOffset+1) newOffset = util.Min(maxOffset, newOffset+1)
} }
if newOffset == prevOffset { if newOffset == prevOffset {
break break
@@ -7320,8 +7299,8 @@ func (t *Terminal) promptLines() int {
// Number of item lines in the list window // Number of item lines in the list window
func (t *Terminal) maxItems() int { func (t *Terminal) maxItems() int {
maximum := t.window.Height() - t.visibleHeaderLinesInList() - t.promptLines() max := t.window.Height() - t.visibleHeaderLinesInList() - t.promptLines()
return max(maximum, 0) return util.Max(max, 0)
} }
func (t *Terminal) dumpItem(i *Item) StatusItem { func (t *Terminal) dumpItem(i *Item) StatusItem {
@@ -7358,12 +7337,12 @@ func (t *Terminal) dumpStatus(params getParams) string {
defer t.mutex.Unlock() defer t.mutex.Unlock()
selectedItems := t.sortSelected() selectedItems := t.sortSelected()
selected := make([]StatusItem, max(0, min(params.limit, len(selectedItems)-params.offset))) selected := make([]StatusItem, util.Max(0, util.Min(params.limit, len(selectedItems)-params.offset)))
for i := range selected { for i := range selected {
selected[i] = t.dumpItem(selectedItems[i+params.offset].item) selected[i] = t.dumpItem(selectedItems[i+params.offset].item)
} }
matches := make([]StatusItem, max(0, min(params.limit, t.resultMerger.Length()-params.offset))) matches := make([]StatusItem, util.Max(0, util.Min(params.limit, t.resultMerger.Length()-params.offset)))
for i := range matches { for i := range matches {
matches[i] = t.dumpItem(t.resultMerger.Get(i + params.offset).item) matches[i] = t.dumpItem(t.resultMerger.Get(i + params.offset).item)
} }

View File

@@ -302,7 +302,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
end += numTokens + 1 end += numTokens + 1
} }
} }
minIdx = max(0, begin-1) minIdx = util.Max(0, begin-1)
for idx := begin; idx <= end; idx++ { for idx := begin; idx <= end; idx++ {
if idx >= 1 && idx <= numTokens { if idx >= 1 && idx <= numTokens {
parts = append(parts, tokens[idx-1].text) parts = append(parts, tokens[idx-1].text)

View File

@@ -34,11 +34,11 @@ func (r *FullscreenRenderer) ShowCursor() {}
func (r *FullscreenRenderer) Refresh() {} func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {} func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} } func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
func (r *FullscreenRenderer) Top() int { return 0 }
func (r *FullscreenRenderer) MaxX() int { return 0 } func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) MaxY() int { return 0 } func (r *FullscreenRenderer) Top() int { return 0 }
func (r *FullscreenRenderer) GetChar(bool) Event { return Event{} } func (r *FullscreenRenderer) MaxX() int { return 0 }
func (r *FullscreenRenderer) CancelGetChar() {} func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {} func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}

View File

@@ -16,7 +16,7 @@ func _() {
_ = x[CtrlE-5] _ = x[CtrlE-5]
_ = x[CtrlF-6] _ = x[CtrlF-6]
_ = x[CtrlG-7] _ = x[CtrlG-7]
_ = x[CtrlH-8] _ = x[CtrlBackspace-8]
_ = x[Tab-9] _ = x[Tab-9]
_ = x[CtrlJ-10] _ = x[CtrlJ-10]
_ = x[CtrlK-11] _ = x[CtrlK-11]
@@ -99,75 +99,74 @@ func _() {
_ = x[CtrlRight-88] _ = x[CtrlRight-88]
_ = x[CtrlHome-89] _ = x[CtrlHome-89]
_ = x[CtrlEnd-90] _ = x[CtrlEnd-90]
_ = x[CtrlBackspace-91] _ = x[CtrlDelete-91]
_ = x[CtrlDelete-92] _ = x[CtrlPageUp-92]
_ = x[CtrlPageUp-93] _ = x[CtrlPageDown-93]
_ = x[CtrlPageDown-94] _ = x[Alt-94]
_ = x[Alt-95] _ = x[CtrlAlt-95]
_ = x[CtrlAlt-96] _ = x[CtrlAltUp-96]
_ = x[CtrlAltUp-97] _ = x[CtrlAltDown-97]
_ = x[CtrlAltDown-98] _ = x[CtrlAltLeft-98]
_ = x[CtrlAltLeft-99] _ = x[CtrlAltRight-99]
_ = x[CtrlAltRight-100] _ = x[CtrlAltHome-100]
_ = x[CtrlAltHome-101] _ = x[CtrlAltEnd-101]
_ = x[CtrlAltEnd-102] _ = x[CtrlAltBackspace-102]
_ = x[CtrlAltBackspace-103] _ = x[CtrlAltDelete-103]
_ = x[CtrlAltDelete-104] _ = x[CtrlAltPageUp-104]
_ = x[CtrlAltPageUp-105] _ = x[CtrlAltPageDown-105]
_ = x[CtrlAltPageDown-106] _ = x[CtrlShiftUp-106]
_ = x[CtrlShiftUp-107] _ = x[CtrlShiftDown-107]
_ = x[CtrlShiftDown-108] _ = x[CtrlShiftLeft-108]
_ = x[CtrlShiftLeft-109] _ = x[CtrlShiftRight-109]
_ = x[CtrlShiftRight-110] _ = x[CtrlShiftHome-110]
_ = x[CtrlShiftHome-111] _ = x[CtrlShiftEnd-111]
_ = x[CtrlShiftEnd-112] _ = x[CtrlShiftDelete-112]
_ = x[CtrlShiftDelete-113] _ = x[CtrlShiftPageUp-113]
_ = x[CtrlShiftPageUp-114] _ = x[CtrlShiftPageDown-114]
_ = x[CtrlShiftPageDown-115] _ = x[CtrlAltShiftUp-115]
_ = x[CtrlAltShiftUp-116] _ = x[CtrlAltShiftDown-116]
_ = x[CtrlAltShiftDown-117] _ = x[CtrlAltShiftLeft-117]
_ = x[CtrlAltShiftLeft-118] _ = x[CtrlAltShiftRight-118]
_ = x[CtrlAltShiftRight-119] _ = x[CtrlAltShiftHome-119]
_ = x[CtrlAltShiftHome-120] _ = x[CtrlAltShiftEnd-120]
_ = x[CtrlAltShiftEnd-121] _ = x[CtrlAltShiftDelete-121]
_ = x[CtrlAltShiftDelete-122] _ = x[CtrlAltShiftPageUp-122]
_ = x[CtrlAltShiftPageUp-123] _ = x[CtrlAltShiftPageDown-123]
_ = x[CtrlAltShiftPageDown-124] _ = x[Invalid-124]
_ = x[Invalid-125] _ = x[Fatal-125]
_ = x[Fatal-126] _ = x[BracketedPasteBegin-126]
_ = x[BracketedPasteBegin-127] _ = x[BracketedPasteEnd-127]
_ = x[BracketedPasteEnd-128] _ = x[Mouse-128]
_ = x[Mouse-129] _ = x[DoubleClick-129]
_ = x[DoubleClick-130] _ = x[LeftClick-130]
_ = x[LeftClick-131] _ = x[RightClick-131]
_ = x[RightClick-132] _ = x[SLeftClick-132]
_ = x[SLeftClick-133] _ = x[SRightClick-133]
_ = x[SRightClick-134] _ = x[ScrollUp-134]
_ = x[ScrollUp-135] _ = x[ScrollDown-135]
_ = x[ScrollDown-136] _ = x[SScrollUp-136]
_ = x[SScrollUp-137] _ = x[SScrollDown-137]
_ = x[SScrollDown-138] _ = x[PreviewScrollUp-138]
_ = x[PreviewScrollUp-139] _ = x[PreviewScrollDown-139]
_ = x[PreviewScrollDown-140] _ = x[Resize-140]
_ = x[Resize-141] _ = x[Change-141]
_ = x[Change-142] _ = x[BackwardEOF-142]
_ = x[BackwardEOF-143] _ = x[Start-143]
_ = x[Start-144] _ = x[Load-144]
_ = x[Load-145] _ = x[Focus-145]
_ = x[Focus-146] _ = x[One-146]
_ = x[One-147] _ = x[Zero-147]
_ = x[Zero-148] _ = x[Result-148]
_ = x[Result-149] _ = x[Jump-149]
_ = x[Jump-150] _ = x[JumpCancel-150]
_ = x[JumpCancel-151] _ = x[ClickHeader-151]
_ = x[ClickHeader-152] _ = x[ClickFooter-152]
_ = x[ClickFooter-153] _ = x[Multi-153]
_ = x[Multi-154]
} }
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteShiftHomeShiftEndShiftPageUpShiftPageDownF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltDeleteAltHomeAltEndAltPageUpAltPageDownAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltShiftDeleteAltShiftHomeAltShiftEndAltShiftPageUpAltShiftPageDownCtrlUpCtrlDownCtrlLeftCtrlRightCtrlHomeCtrlEndCtrlBackspaceCtrlDeleteCtrlPageUpCtrlPageDownAltCtrlAltCtrlAltUpCtrlAltDownCtrlAltLeftCtrlAltRightCtrlAltHomeCtrlAltEndCtrlAltBackspaceCtrlAltDeleteCtrlAltPageUpCtrlAltPageDownCtrlShiftUpCtrlShiftDownCtrlShiftLeftCtrlShiftRightCtrlShiftHomeCtrlShiftEndCtrlShiftDeleteCtrlShiftPageUpCtrlShiftPageDownCtrlAltShiftUpCtrlAltShiftDownCtrlAltShiftLeftCtrlAltShiftRightCtrlAltShiftHomeCtrlAltShiftEndCtrlAltShiftDeleteCtrlAltShiftPageUpCtrlAltShiftPageDownInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMulti" const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlBackspaceTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteShiftHomeShiftEndShiftPageUpShiftPageDownF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltDeleteAltHomeAltEndAltPageUpAltPageDownAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltShiftDeleteAltShiftHomeAltShiftEndAltShiftPageUpAltShiftPageDownCtrlUpCtrlDownCtrlLeftCtrlRightCtrlHomeCtrlEndCtrlDeleteCtrlPageUpCtrlPageDownAltCtrlAltCtrlAltUpCtrlAltDownCtrlAltLeftCtrlAltRightCtrlAltHomeCtrlAltEndCtrlAltBackspaceCtrlAltDeleteCtrlAltPageUpCtrlAltPageDownCtrlShiftUpCtrlShiftDownCtrlShiftLeftCtrlShiftRightCtrlShiftHomeCtrlShiftEndCtrlShiftDeleteCtrlShiftPageUpCtrlShiftPageDownCtrlAltShiftUpCtrlAltShiftDownCtrlAltShiftLeftCtrlAltShiftRightCtrlAltShiftHomeCtrlAltShiftEndCtrlAltShiftDeleteCtrlAltShiftPageUpCtrlAltShiftPageDownInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMulti"
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 157, 173, 182, 191, 199, 208, 214, 220, 228, 230, 234, 238, 243, 247, 250, 256, 263, 272, 281, 291, 302, 311, 319, 330, 343, 345, 347, 349, 351, 353, 355, 357, 359, 361, 364, 367, 370, 382, 387, 394, 401, 409, 418, 425, 431, 440, 451, 461, 473, 485, 498, 512, 524, 535, 549, 565, 571, 579, 587, 596, 604, 611, 624, 634, 644, 656, 659, 666, 675, 686, 697, 709, 720, 730, 746, 759, 772, 787, 798, 811, 824, 838, 851, 863, 878, 893, 910, 924, 940, 956, 973, 989, 1004, 1022, 1040, 1060, 1067, 1072, 1091, 1108, 1113, 1124, 1133, 1143, 1153, 1164, 1172, 1182, 1191, 1202, 1217, 1234, 1240, 1246, 1257, 1262, 1266, 1271, 1274, 1278, 1284, 1288, 1298, 1309, 1320, 1325} var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 52, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120, 125, 130, 135, 140, 143, 152, 165, 181, 190, 199, 207, 216, 222, 228, 236, 238, 242, 246, 251, 255, 258, 264, 271, 280, 289, 299, 310, 319, 327, 338, 351, 353, 355, 357, 359, 361, 363, 365, 367, 369, 372, 375, 378, 390, 395, 402, 409, 417, 426, 433, 439, 448, 459, 469, 481, 493, 506, 520, 532, 543, 557, 573, 579, 587, 595, 604, 612, 619, 629, 639, 651, 654, 661, 670, 681, 692, 704, 715, 725, 741, 754, 767, 782, 793, 806, 819, 833, 846, 858, 873, 888, 905, 919, 935, 951, 968, 984, 999, 1017, 1035, 1055, 1062, 1067, 1086, 1103, 1108, 1119, 1128, 1138, 1148, 1159, 1167, 1177, 1186, 1197, 1212, 1229, 1235, 1241, 1252, 1257, 1261, 1266, 1269, 1273, 1279, 1283, 1293, 1304, 1315, 1320}
func (i EventType) String() string { func (i EventType) String() string {
if i < 0 || i >= EventType(len(_EventType_index)-1) { if i < 0 || i >= EventType(len(_EventType_index)-1) {

View File

@@ -26,7 +26,6 @@ const (
escPollInterval = 5 escPollInterval = 5
offsetPollTries = 10 offsetPollTries = 10
maxInputBuffer = 1024 * 1024 maxInputBuffer = 1024 * 1024
maxSelectTries = 100
) )
const DefaultTtyDevice string = "/dev/tty" const DefaultTtyDevice string = "/dev/tty"
@@ -50,18 +49,6 @@ const DIM string = "\x1b[2m"
const CR string = DIM + "␍" const CR string = DIM + "␍"
const LF string = DIM + "␊" const LF string = DIM + "␊"
type getCharResult int
const (
getCharSuccess getCharResult = iota
getCharError
getCharCancelled
)
func (r getCharResult) ok() bool {
return r == getCharSuccess
}
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool, resetCode string) { func (r *LightRenderer) stderrInternal(str string, allowNLCR bool, resetCode string) {
bytes := []byte(str) bytes := []byte(str)
runes := []rune{} runes := []rune{}
@@ -117,7 +104,6 @@ type LightRenderer struct {
clicks [][2]int clicks [][2]int
ttyin *os.File ttyin *os.File
ttyout *os.File ttyout *os.File
cancel func()
buffer []byte buffer []byte
origState *term.State origState *term.State
width int width int
@@ -132,9 +118,9 @@ type LightRenderer struct {
x int x int
maxHeightFunc func(int) int maxHeightFunc func(int) int
showCursor bool showCursor bool
mutex sync.Mutex
// Windows only // Windows only
mutex sync.Mutex
ttyinChannel chan byte ttyinChannel chan byte
inHandle uintptr inHandle uintptr
outHandle uintptr outHandle uintptr
@@ -276,18 +262,16 @@ func getEnv(name string, defaultValue int) int {
return atoi(env, defaultValue) return atoi(env, defaultValue)
} }
func (r *LightRenderer) getBytes(cancellable bool) ([]byte, getCharResult, error) { func (r *LightRenderer) getBytes() ([]byte, error) {
return r.getBytesInternal(cancellable, r.buffer, false) bytes, err := r.getBytesInternal(r.buffer, false)
return bytes, err
} }
func (r *LightRenderer) getBytesInternal(cancellable bool, buffer []byte, nonblock bool) ([]byte, getCharResult, error) { func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte, error) {
c, result := r.getch(cancellable, nonblock) c, ok := r.getch(nonblock)
if result == getCharCancelled { if !nonblock && !ok {
return buffer, getCharCancelled, nil
}
if !nonblock && !result.ok() {
r.Close() r.Close()
return nil, getCharError, errors.New("failed to read " + DefaultTtyDevice) return nil, errors.New("failed to read " + DefaultTtyDevice)
} }
retries := 0 retries := 0
@@ -298,8 +282,8 @@ func (r *LightRenderer) getBytesInternal(cancellable bool, buffer []byte, nonblo
pc := c pc := c
for { for {
c, result = r.getch(false, true) c, ok = r.getch(true)
if !result.ok() { if !ok {
if retries > 0 { if retries > 0 {
retries-- retries--
time.Sleep(escPollInterval * time.Millisecond) time.Sleep(escPollInterval * time.Millisecond)
@@ -318,24 +302,20 @@ func (r *LightRenderer) getBytesInternal(cancellable bool, buffer []byte, nonblo
// so terminate fzf immediately. // so terminate fzf immediately.
if len(buffer) > maxInputBuffer { if len(buffer) > maxInputBuffer {
r.Close() r.Close()
return nil, getCharError, fmt.Errorf("input buffer overflow (%d): %v", len(buffer), buffer) return nil, fmt.Errorf("input buffer overflow (%d): %v", len(buffer), buffer)
} }
} }
return buffer, getCharSuccess, nil return buffer, nil
} }
func (r *LightRenderer) GetChar(cancellable bool) Event { func (r *LightRenderer) GetChar() Event {
var err error var err error
var result getCharResult
if len(r.buffer) == 0 { if len(r.buffer) == 0 {
r.buffer, result, err = r.getBytes(cancellable) r.buffer, err = r.getBytes()
if err != nil { if err != nil {
return Event{Fatal, 0, nil} return Event{Fatal, 0, nil}
} }
if result == getCharCancelled {
return Event{Invalid, 0, nil}
}
} }
if len(r.buffer) == 0 { if len(r.buffer) == 0 {
return Event{Fatal, 0, nil} return Event{Fatal, 0, nil}
@@ -371,14 +351,9 @@ func (r *LightRenderer) GetChar(cancellable bool) Event {
ev := r.escSequence(&sz) ev := r.escSequence(&sz)
// Second chance // Second chance
if ev.Type == Invalid { if ev.Type == Invalid {
r.buffer, result, err = r.getBytes(true) if r.buffer, err = r.getBytes(); err != nil {
if err != nil {
return Event{Fatal, 0, nil} return Event{Fatal, 0, nil}
} }
if result == getCharCancelled {
return Event{Invalid, 0, nil}
}
ev = r.escSequence(&sz) ev = r.escSequence(&sz)
} }
return ev return ev
@@ -396,21 +371,6 @@ func (r *LightRenderer) GetChar(cancellable bool) Event {
return Event{Rune, char, nil} return Event{Rune, char, nil}
} }
func (r *LightRenderer) CancelGetChar() {
r.mutex.Lock()
if r.cancel != nil {
r.cancel()
r.cancel = nil
}
r.mutex.Unlock()
}
func (r *LightRenderer) setCancel(f func()) {
r.mutex.Lock()
r.cancel = f
r.mutex.Unlock()
}
func (r *LightRenderer) escSequence(sz *int) Event { func (r *LightRenderer) escSequence(sz *int) Event {
if len(r.buffer) < 2 { if len(r.buffer) < 2 {
return Event{Esc, 0, nil} return Event{Esc, 0, nil}
@@ -1073,8 +1033,8 @@ func (r *LightRenderer) MaxY() int {
} }
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window { func (r *LightRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {
width = max(0, width) width = util.Max(0, width)
height = max(0, height) height = util.Max(0, height)
w := &LightWindow{ w := &LightWindow{
renderer: r, renderer: r,
colored: r.theme.Colored, colored: r.theme.Colored,

View File

@@ -15,27 +15,10 @@ func TestLightRenderer(t *testing.T) {
light_renderer := renderer.(*LightRenderer) light_renderer := renderer.(*LightRenderer)
go func() {
for {
light_renderer.mutex.Lock()
ready := light_renderer.cancel != nil
light_renderer.mutex.Unlock()
if ready {
light_renderer.CancelGetChar()
break
}
}
}()
event := light_renderer.GetChar(true)
if event.Type != Invalid {
t.Error("Not cancelled")
}
assertCharSequence := func(sequence string, name string) { assertCharSequence := func(sequence string, name string) {
bytes := []byte(sequence) bytes := []byte(sequence)
light_renderer.buffer = bytes light_renderer.buffer = bytes
event := light_renderer.GetChar(true) event := light_renderer.GetChar()
if event.KeyName() != name { if event.KeyName() != name {
t.Errorf( t.Errorf(
"sequence: %q | %v | '%s' (%s) != %s", "sequence: %q | %v | '%s' (%s) != %s",

View File

@@ -99,7 +99,7 @@ func (r *LightRenderer) findOffset() (row int, col int) {
var err error var err error
bytes := []byte{} bytes := []byte{}
for tries := range offsetPollTries { for tries := range offsetPollTries {
bytes, _, err = r.getBytesInternal(false, bytes, tries > 0) bytes, err = r.getBytesInternal(bytes, tries > 0)
if err != nil { if err != nil {
return -1, -1 return -1, -1
} }
@@ -114,62 +114,15 @@ func (r *LightRenderer) findOffset() (row int, col int) {
return -1, -1 return -1, -1
} }
func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResult) { func (r *LightRenderer) getch(nonblock bool) (int, bool) {
b := make([]byte, 1)
fd := r.fd() fd := r.fd()
getter := func() (int, getCharResult) { util.SetNonblock(r.ttyin, nonblock)
b := make([]byte, 1) _, err := util.Read(fd, b)
util.SetNonblock(r.ttyin, nonblock)
_, err := util.Read(fd, b)
if err != nil {
return 0, getCharError
}
return int(b[0]), getCharSuccess
}
if nonblock || !cancellable {
return getter()
}
rpipe, wpipe, err := os.Pipe()
if err != nil { if err != nil {
// Fallback to blocking read without cancellation return 0, false
return getter()
} }
r.setCancel(func() { return int(b[0]), true
wpipe.Write([]byte{0})
})
defer func() {
r.setCancel(nil)
rpipe.Close()
wpipe.Close()
}()
cancelFd := int(rpipe.Fd())
for range maxSelectTries {
var rfds unix.FdSet
limit := len(rfds.Bits) * unix.NFDBITS
if fd >= limit || cancelFd >= limit {
return getter()
}
rfds.Set(fd)
rfds.Set(cancelFd)
_, err := unix.Select(max(fd, cancelFd)+1, &rfds, nil, nil, nil)
if err != nil {
if err == syscall.EINTR {
continue
}
return 0, getCharError
}
if rfds.IsSet(cancelFd) {
return 0, getCharCancelled
}
if rfds.IsSet(fd) {
return getter()
}
}
return 0, getCharError
} }
func (r *LightRenderer) Size() TermSize { func (r *LightRenderer) Size() TermSize {

View File

@@ -151,33 +151,16 @@ func (r *LightRenderer) findOffset() (row int, col int) {
return int(bufferInfo.CursorPosition.Y), int(bufferInfo.CursorPosition.X) return int(bufferInfo.CursorPosition.Y), int(bufferInfo.CursorPosition.X)
} }
func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResult) { func (r *LightRenderer) getch(nonblock bool) (int, bool) {
if !nonblock && !cancellable {
bc := <-r.ttyinChannel
return int(bc), getCharSuccess
}
var timeout <-chan time.Time
if nonblock { if nonblock {
timeout = time.After(timeoutInterval * time.Millisecond) select {
} case bc := <-r.ttyinChannel:
return int(bc), true
var cancel chan struct{} case <-time.After(timeoutInterval * time.Millisecond):
if cancellable { return 0, false
cancel = make(chan struct{}) }
r.setCancel(func() { } else {
close(cancel) bc := <-r.ttyinChannel
}) return int(bc), true
defer r.setCancel(nil)
}
select {
case bc := <-r.ttyinChannel:
return int(bc), getCharSuccess
case <-cancel:
return 0, getCharCancelled
case <-timeout:
// NOTE: not really an error
return 0, getCharError
} }
} }

View File

@@ -246,7 +246,7 @@ func (r *FullscreenRenderer) Size() TermSize {
return TermSize{lines, cols, 0, 0} return TermSize{lines, cols, 0, 0}
} }
func (r *FullscreenRenderer) GetChar(cancellable bool) Event { func (r *FullscreenRenderer) GetChar() Event {
ev := _screen.PollEvent() ev := _screen.PollEvent()
switch ev := ev.(type) { switch ev := ev.(type) {
case *tcell.EventPaste: case *tcell.EventPaste:
@@ -703,10 +703,6 @@ func (r *FullscreenRenderer) GetChar(cancellable bool) Event {
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
} }
func (r *FullscreenRenderer) CancelGetChar() {
// TODO
}
func (r *FullscreenRenderer) Pause(clear bool) { func (r *FullscreenRenderer) Pause(clear bool) {
if clear { if clear {
_screen.Suspend() _screen.Suspend()
@@ -733,8 +729,8 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
} }
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window { func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {
width = max(0, width) width = util.Max(0, width)
height = max(0, height) height = util.Max(0, height)
normal := ColBorder normal := ColBorder
switch windowType { switch windowType {
case WindowList: case WindowList:

View File

@@ -110,21 +110,21 @@ func TestGetCharEventKey(t *testing.T) {
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{AltDelete, 0, nil}}, {giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{AltDelete, 0, nil}},
{giveKey{tcell.KeyBackspace, 0, tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}}, {giveKey{tcell.KeyBackspace, 0, tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}},
{giveKey{tcell.KeyBackspace, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltBackspace, 0, nil}}, {giveKey{tcell.KeyBackspace, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltBackspace, 0, nil}},
{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled {giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled {giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled {giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{Backspace, 0, nil}}, // actual "Backspace" keystroke {giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{CtrlBackspace, 0, nil}}, // actual "Backspace" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Alt+Backspace" keystroke {giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Alt+Backspace" keystroke
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+Backspace" keystroke {giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{Backspace, 0, nil}}, // actual "Shift+Backspace" keystroke {giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{CtrlBackspace, 0, nil}}, // actual "Shift+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltBackspace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke {giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltBackspace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke {giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke {giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAltBackspace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke {giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAltBackspace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+H" keystroke {giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+H" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Alt+H" keystroke {giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // fabricated "Ctrl+Alt+H" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+Shift+H" keystroke {giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+Shift+H" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Shift+Alt+H" keystroke {giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{AltBackspace, 0, nil}}, // fabricated "Ctrl+Shift+Alt+H" keystroke
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right) // section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}}, {giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},

View File

@@ -43,7 +43,7 @@ const (
CtrlE CtrlE
CtrlF CtrlF
CtrlG CtrlG
CtrlH CtrlBackspace
Tab Tab
CtrlJ CtrlJ
CtrlK CtrlK
@@ -137,7 +137,6 @@ const (
CtrlRight CtrlRight
CtrlHome CtrlHome
CtrlEnd CtrlEnd
CtrlBackspace
CtrlDelete CtrlDelete
CtrlPageUp CtrlPageUp
CtrlPageDown CtrlPageDown
@@ -456,7 +455,6 @@ type ColorTheme struct {
PreviewBg ColorAttr PreviewBg ColorAttr
DarkBg ColorAttr DarkBg ColorAttr
Gutter ColorAttr Gutter ColorAttr
AltGutter ColorAttr
Prompt ColorAttr Prompt ColorAttr
InputBg ColorAttr InputBg ColorAttr
InputBorder ColorAttr InputBorder ColorAttr
@@ -749,8 +747,7 @@ type Renderer interface {
HideCursor() HideCursor()
ShowCursor() ShowCursor()
GetChar(cancellable bool) Event GetChar() Event
CancelGetChar()
Top() int Top() int
MaxX() int MaxX() int
@@ -828,8 +825,6 @@ var (
ColCursor ColorPair ColCursor ColorPair
ColCursorEmpty ColorPair ColCursorEmpty ColorPair
ColCursorEmptyChar ColorPair ColCursorEmptyChar ColorPair
ColAltCursorEmpty ColorPair
ColAltCursorEmptyChar ColorPair
ColMarker ColorPair ColMarker ColorPair
ColSelected ColorPair ColSelected ColorPair
ColSelectedMatch ColorPair ColSelectedMatch ColorPair
@@ -895,7 +890,6 @@ func init() {
PreviewFg: defaultColor, PreviewFg: defaultColor,
PreviewBg: defaultColor, PreviewBg: defaultColor,
Gutter: undefined, Gutter: undefined,
AltGutter: undefined,
PreviewBorder: defaultColor, PreviewBorder: defaultColor,
PreviewScrollbar: defaultColor, PreviewScrollbar: defaultColor,
PreviewLabel: defaultColor, PreviewLabel: defaultColor,
@@ -948,7 +942,6 @@ func init() {
PreviewFg: undefined, PreviewFg: undefined,
PreviewBg: undefined, PreviewBg: undefined,
Gutter: undefined, Gutter: undefined,
AltGutter: undefined,
PreviewBorder: undefined, PreviewBorder: undefined,
PreviewScrollbar: undefined, PreviewScrollbar: undefined,
PreviewLabel: undefined, PreviewLabel: undefined,
@@ -997,7 +990,6 @@ func init() {
PreviewFg: undefined, PreviewFg: undefined,
PreviewBg: undefined, PreviewBg: undefined,
Gutter: undefined, Gutter: undefined,
AltGutter: undefined,
PreviewBorder: undefined, PreviewBorder: undefined,
PreviewScrollbar: undefined, PreviewScrollbar: undefined,
PreviewLabel: undefined, PreviewLabel: undefined,
@@ -1048,7 +1040,6 @@ func init() {
PreviewFg: undefined, PreviewFg: undefined,
PreviewBg: undefined, PreviewBg: undefined,
Gutter: undefined, Gutter: undefined,
AltGutter: undefined,
PreviewBorder: undefined, PreviewBorder: undefined,
PreviewScrollbar: undefined, PreviewScrollbar: undefined,
PreviewLabel: undefined, PreviewLabel: undefined,
@@ -1099,7 +1090,6 @@ func init() {
PreviewFg: undefined, PreviewFg: undefined,
PreviewBg: undefined, PreviewBg: undefined,
Gutter: undefined, Gutter: undefined,
AltGutter: undefined,
PreviewBorder: undefined, PreviewBorder: undefined,
PreviewScrollbar: undefined, PreviewScrollbar: undefined,
PreviewLabel: undefined, PreviewLabel: undefined,
@@ -1217,7 +1207,6 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, boldify bool, forceBlac
gutter.Attr = Dim gutter.Attr = Dim
} }
theme.Gutter = o(theme.DarkBg, gutter) theme.Gutter = o(theme.DarkBg, gutter)
theme.AltGutter = o(theme.Gutter, theme.AltGutter)
theme.PreviewFg = o(theme.Fg, theme.PreviewFg) theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
theme.PreviewBg = o(theme.Bg, theme.PreviewBg) theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel) theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
@@ -1287,8 +1276,6 @@ func initPalette(theme *ColorTheme) {
ColCursor = pair(theme.Cursor, theme.Gutter) ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter) ColCursorEmpty = pair(blank, theme.Gutter)
ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg) ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)
ColAltCursorEmpty = pair(blank, theme.AltGutter)
ColAltCursorEmptyChar = pair(theme.AltGutter, theme.ListBg)
if theme.SelectedBg.Color != theme.ListBg.Color { if theme.SelectedBg.Color != theme.ListBg.Color {
ColMarker = pair(theme.Marker, theme.SelectedBg) ColMarker = pair(theme.Marker, theme.SelectedBg)
} else { } else {

View File

@@ -187,7 +187,7 @@ func (chars *Chars) TrailingWhitespaces() int {
func (chars *Chars) TrimTrailingWhitespaces(maxIndex int) { func (chars *Chars) TrimTrailingWhitespaces(maxIndex int) {
whitespaces := chars.TrailingWhitespaces() whitespaces := chars.TrailingWhitespaces()
end := len(chars.slice) - whitespaces end := len(chars.slice) - whitespaces
chars.slice = chars.slice[0:max(end, maxIndex)] chars.slice = chars.slice[0:Max(end, maxIndex)]
} }
func (chars *Chars) TrimSuffix(runes []rune) { func (chars *Chars) TrimSuffix(runes []rune) {

View File

@@ -1,11 +1,11 @@
package util package util
import ( import (
"cmp"
"math" "math"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/rivo/uniseg" "github.com/rivo/uniseg"
@@ -55,8 +55,54 @@ func Truncate(input string, limit int) ([]rune, int) {
return runes, width return runes, width
} }
func Constrain[T cmp.Ordered](val, minimum, maximum T) T { // Max returns the largest integer
return max(min(val, maximum), minimum) func Max(first int, second int) int {
if first >= second {
return first
}
return second
}
// Max16 returns the largest integer
func Max16(first int16, second int16) int16 {
if first >= second {
return first
}
return second
}
// Max32 returns the largest 32-bit integer
func Max32(first int32, second int32) int32 {
if first > second {
return first
}
return second
}
// Min returns the smallest integer
func Min(first int, second int) int {
if first <= second {
return first
}
return second
}
// Min32 returns the smallest 32-bit integer
func Min32(first int32, second int32) int32 {
if first <= second {
return first
}
return second
}
// Constrain32 limits the given 32-bit integer with the upper and lower bounds
func Constrain32(val int32, min int32, max int32) int32 {
return Max32(Min32(val, max), min)
}
// Constrain limits the given integer with the upper and lower bounds
func Constrain(val int, min int, max int) int {
return Max(Min(val, max), min)
} }
func AsUint16(val int) uint16 { func AsUint16(val int) uint16 {
@@ -68,6 +114,18 @@ func AsUint16(val int) uint16 {
return uint16(val) return uint16(val)
} }
// DurWithin limits the given time.Duration with the upper and lower bounds
func DurWithin(
val time.Duration, min time.Duration, max time.Duration) time.Duration {
if val < min {
return min
}
if val > max {
return max
}
return val
}
// IsTty returns true if the file is a terminal // IsTty returns true if the file is a terminal
func IsTty(file *os.File) bool { func IsTty(file *os.File) bool {
fd := file.Fd() fd := file.Fd()
@@ -139,7 +197,7 @@ func CompareVersions(v1, v2 string) int {
return n return n
} }
for i := 0; i < max(len(parts1), len(parts2)); i++ { for i := 0; i < Max(len(parts1), len(parts2)); i++ {
var p1, p2 int var p1, p2 int
if i < len(parts1) { if i < len(parts1) {
p1 = atoi(parts1[i]) p1 = atoi(parts1[i])

View File

@@ -4,8 +4,72 @@ import (
"math" "math"
"strings" "strings"
"testing" "testing"
"time"
) )
func TestMax(t *testing.T) {
if Max(10, 1) != 10 {
t.Error("Expected", 10)
}
if Max(-2, 5) != 5 {
t.Error("Expected", 5)
}
}
func TestMax16(t *testing.T) {
if Max16(10, 1) != 10 {
t.Error("Expected", 10)
}
if Max16(-2, 5) != 5 {
t.Error("Expected", 5)
}
if Max16(math.MaxInt16, 0) != math.MaxInt16 {
t.Error("Expected", math.MaxInt16)
}
if Max16(0, math.MinInt16) != 0 {
t.Error("Expected", 0)
}
}
func TestMax32(t *testing.T) {
if Max32(10, 1) != 10 {
t.Error("Expected", 10)
}
if Max32(-2, 5) != 5 {
t.Error("Expected", 5)
}
if Max32(math.MaxInt32, 0) != math.MaxInt32 {
t.Error("Expected", math.MaxInt32)
}
if Max32(0, math.MinInt32) != 0 {
t.Error("Expected", 0)
}
}
func TestMin(t *testing.T) {
if Min(10, 1) != 1 {
t.Error("Expected", 1)
}
if Min(-2, 5) != -2 {
t.Error("Expected", -2)
}
}
func TestMin32(t *testing.T) {
if Min32(10, 1) != 1 {
t.Error("Expected", 1)
}
if Min32(-2, 5) != -2 {
t.Error("Expected", -2)
}
if Min32(math.MaxInt32, 0) != 0 {
t.Error("Expected", 0)
}
if Min32(0, math.MinInt32) != math.MinInt32 {
t.Error("Expected", math.MinInt32)
}
}
func TestConstrain(t *testing.T) { func TestConstrain(t *testing.T) {
if Constrain(-3, -1, 3) != -1 { if Constrain(-3, -1, 3) != -1 {
t.Error("Expected", -1) t.Error("Expected", -1)
@@ -19,6 +83,22 @@ func TestConstrain(t *testing.T) {
} }
} }
func TestConstrain32(t *testing.T) {
if Constrain32(-3, -1, 3) != -1 {
t.Error("Expected", -1)
}
if Constrain32(2, -1, 3) != 2 {
t.Error("Expected", 2)
}
if Constrain32(5, -1, 3) != 3 {
t.Error("Expected", 3)
}
if Constrain32(0, math.MinInt32, math.MaxInt32) != 0 {
t.Error("Expected", 0)
}
}
func TestAsUint16(t *testing.T) { func TestAsUint16(t *testing.T) {
if AsUint16(5) != 5 { if AsUint16(5) != 5 {
t.Error("Expected", 5) t.Error("Expected", 5)
@@ -40,6 +120,18 @@ func TestAsUint16(t *testing.T) {
} }
} }
func TestDurWithIn(t *testing.T) {
if DurWithin(time.Duration(5), time.Duration(1), time.Duration(8)) != time.Duration(5) {
t.Error("Expected", time.Duration(0))
}
if DurWithin(time.Duration(0)*time.Second, time.Second, time.Duration(3)*time.Second) != time.Second {
t.Error("Expected", time.Second)
}
if DurWithin(time.Duration(10)*time.Second, time.Duration(0), time.Second) != time.Second {
t.Error("Expected", time.Second)
}
}
func TestOnce(t *testing.T) { func TestOnce(t *testing.T) {
o := Once(false) o := Once(false)
if o() { if o() {

View File

@@ -1235,16 +1235,6 @@ class TestCore < TestInteractive
end end
end end
def test_freeze_right_with_ellipsis_and_scrolling
tmux.send_keys "{ seq 6; ruby -e 'print \"g\"*1000, \"\\n\"'; seq 8 100; } | #{FZF} --ellipsis='777' --freeze-right 1 --scroll-off 0 --bind a:offset-up", :Enter
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
tmux.send_keys(*Array.new(6) { :a })
tmux.until do |lines|
assert_match(/> 777g+$/, lines[-3])
assert_equal 1, lines.count { |l| l.end_with?('g') }
end
end
def test_backward_eof def test_backward_eof
tmux.send_keys "echo foo | #{FZF} --bind 'backward-eof:reload(seq 100)'", :Enter tmux.send_keys "echo foo | #{FZF} --bind 'backward-eof:reload(seq 100)'", :Enter
tmux.until { |lines| lines.item_count == 1 && lines.match_count == 1 } tmux.until { |lines| lines.item_count == 1 && lines.match_count == 1 }

View File

@@ -312,18 +312,4 @@ class TestFilter < TestBase
assert_equal expected, result assert_equal expected, result
end end
end end
def test_accept_nth
# Single field selection
assert_equal 'three', `echo 'one two three' | #{FZF} -d' ' --with-nth 1 --accept-nth -1 -f one`.chomp
# Multiple field selection
writelines(['ID001:John:Developer', 'ID002:Jane:Manager', 'ID003:Bob:Designer'])
assert_equal 'ID001', `#{FZF} -d: --with-nth 2 --accept-nth 1 -f John < #{tempname}`.chomp
assert_equal "ID002:Manager", `#{FZF} -d: --with-nth 2 --accept-nth 1,3 -f Jane < #{tempname}`.chomp
# Test with different delimiters
writelines(['emp001 Alice Engineering', 'emp002 Bob Marketing'])
assert_equal 'emp001', `#{FZF} -d' ' --with-nth 2 --accept-nth 1 -f Alice < #{tempname}`.chomp
end
end end

View File

@@ -1215,15 +1215,6 @@ class TestLayout < TestInteractive
end end
end end
def test_header_and_footer_should_not_be_wider_than_list
tmux.send_keys %(WIDE=$(printf 'x%.0s' {1..1000}); (echo $WIDE; echo $WIDE) | fzf --header-lines 1 --style full --header-border bottom --header-lines-border top --ellipsis XX --header "$WIDE" --footer "$WIDE" --no-footer-border), :Enter
tmux.until do |lines|
matches = lines.filter_map { |line| line[/x+XX/] }
assert_equal 4, matches.length
assert_equal 1, matches.uniq.length
end
end
def test_combinations def test_combinations
skip unless ENV['LONGTEST'] skip unless ENV['LONGTEST']

View File

@@ -462,119 +462,6 @@ class TestZsh < TestBase
tmux.send_keys 'C-c' tmux.send_keys 'C-c'
end end
end end
# Helper function to run test with Perl and again with Awk
def self.test_perl_and_awk(name, &block)
define_method("test_#{name}") do
instance_eval(&block)
end
define_method("test_#{name}_awk") do
tmux.send_keys "unset 'commands[perl]'", :Enter
tmux.prepare
# Verify perl is actually unset (0 = not found)
tmux.send_keys 'echo ${+commands[perl]}', :Enter
tmux.until { |lines| assert_equal '0', lines[-1] }
tmux.prepare
instance_eval(&block)
end
end
def prepare_ctrl_r_test
tmux.send_keys ':', :Enter
tmux.send_keys 'echo match-collision', :Enter
tmux.prepare
tmux.send_keys 'echo "line 1', :Enter, '2 line 2"', :Enter
tmux.prepare
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
tmux.prepare
tmux.send_keys 'echo "bar', :Enter, 'foo"', :Enter
tmux.prepare
tmux.send_keys 'echo "trailing_space "', :Enter
tmux.prepare
tmux.send_keys 'cat <<EOF | wc -c', :Enter, 'qux thud', :Enter, 'EOF', :Enter
tmux.prepare
tmux.send_keys 'C-l', 'C-r'
end
test_perl_and_awk 'ctrl_r_accept_or_print_query' do
set_var('FZF_CTRL_R_OPTS', '--bind enter:accept-or-print-query')
prepare_ctrl_r_test
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
tmux.send_keys '1 foobar'
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.send_keys :Enter
tmux.until { |lines| assert_equal '1 foobar', lines[-1] }
end
test_perl_and_awk 'ctrl_r_multiline_index_collision' do
# Leading number in multi-line history content is not confused with index
prepare_ctrl_r_test
tmux.send_keys "'line 1"
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Enter
tmux.until do |lines|
assert_equal ['echo "line 1', '2 line 2"'], lines[-2..]
end
end
test_perl_and_awk 'ctrl_r_multi_selection' do
prepare_ctrl_r_test
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| assert_includes lines[-2], '(3)' }
tmux.send_keys :Enter
tmux.until do |lines|
assert_equal ['cat <<EOF | wc -c', 'qux thud', 'EOF', 'echo "trailing_space "', 'echo "bar', 'foo"'], lines[-6..]
end
end
test_perl_and_awk 'ctrl_r_no_multi_selection' do
set_var('FZF_CTRL_R_OPTS', '--no-multi')
prepare_ctrl_r_test
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| refute_includes lines[-2], '(3)' }
tmux.send_keys :Enter
tmux.until do |lines|
assert_equal ['cat <<EOF | wc -c', 'qux thud', 'EOF'], lines[-3..]
end
end
# NOTE: 'Perl/$history' won't see foreign cmds immediately, unlike 'awk/fc'.
# Perl passes only because another cmd runs between mocking and triggering C-r
# https://github.com/junegunn/fzf/issues/4061
# https://zsh.org/mla/users/2024/msg00692.html
test_perl_and_awk 'ctrl_r_foreign_commands' do
histfile = "#{tempname}-foreign-hist"
tmux.send_keys "HISTFILE=#{histfile}", :Enter
tmux.prepare
# SHARE_HISTORY picks up foreign commands; marked with * in fc
tmux.send_keys 'setopt SHARE_HISTORY', :Enter
tmux.prepare
tmux.send_keys 'fzf_cmd_local', :Enter
tmux.prepare
# Mock foreign command (for testing only; don't edit your HISTFILE this way)
tmux.send_keys "echo ': 0:0;fzf_cmd_foreign' >> $HISTFILE", :Enter
tmux.prepare
# Verify fc shows foreign command with asterisk
tmux.send_keys 'fc -rl -1', :Enter
tmux.until { |lines| assert lines.any? { |l| l.match?(/^\s*\d+\* fzf_cmd_foreign/) } }
tmux.prepare
# Test ctrl-r correctly extracts the foreign command
tmux.send_keys 'C-r'
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
tmux.send_keys '^fzf_cmd_'
tmux.until { |lines| assert_equal 2, lines.match_count }
tmux.send_keys :BTab, :BTab
tmux.until { |lines| assert_includes lines[-2], '(2)' }
tmux.send_keys :Enter
tmux.until do |lines|
assert_equal ['fzf_cmd_foreign', 'fzf_cmd_local'], lines[-2..]
end
ensure
FileUtils.rm_f(histfile)
end
end end
class TestFish < TestBase class TestFish < TestBase