mirror of
https://github.com/junegunn/fzf.git
synced 2026-01-09 13:02:35 +08:00
Compare commits
1 Commits
master
...
72f73ee56d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72f73ee56d |
@@ -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
|
||||||
|
|||||||
9
Makefile
9
Makefile
@@ -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
|
||||||
|
|||||||
3
install
3
install
@@ -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 ;;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ||
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
34
src/core.go
34
src/core.go
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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+":
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
177
src/terminal.go
177
src/terminal.go
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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) {}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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}},
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user