mirror of
https://github.com/junegunn/fzf.git
synced 2025-12-28 06:56:54 +08:00
Compare commits
24 Commits
53f95f91cc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
029b241dbb | ||
|
|
d6ded42026 | ||
|
|
6eb4b41e34 | ||
|
|
14b5e1d88c | ||
|
|
603240122e | ||
|
|
8d688521fe | ||
|
|
775129367a | ||
|
|
b3b221854b | ||
|
|
c8cf0992c1 | ||
|
|
33d8d51c8a | ||
|
|
b473477c22 | ||
|
|
fcc4178bca | ||
|
|
cfc37caabc | ||
|
|
af2a81dc02 | ||
|
|
be5a687281 | ||
|
|
771e35b972 | ||
|
|
60a5be1e65 | ||
|
|
1d5e87f5e4 | ||
|
|
3db63f5e52 | ||
|
|
2ab923f3ae | ||
|
|
c3e6d9a8f9 | ||
|
|
2471edf3ff | ||
|
|
53a8aeeb72 | ||
|
|
60b35e748b |
@@ -22,6 +22,7 @@ builds:
|
||||
- loong64
|
||||
- ppc64le
|
||||
- s390x
|
||||
- riscv64
|
||||
goarm:
|
||||
- "5"
|
||||
- "6"
|
||||
@@ -39,6 +40,8 @@ builds:
|
||||
goarch: arm64
|
||||
- goos: openbsd
|
||||
goarch: arm64
|
||||
- goos: openbsd
|
||||
goarch: riscv64
|
||||
- goos: android
|
||||
goarch: amd64
|
||||
- goos: android
|
||||
@@ -82,12 +85,14 @@ notarize:
|
||||
|
||||
archives:
|
||||
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
builds:
|
||||
ids:
|
||||
- fzf
|
||||
format: tar.gz
|
||||
formats:
|
||||
- tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats:
|
||||
- zip
|
||||
files:
|
||||
- non-existent*
|
||||
|
||||
@@ -99,7 +104,7 @@ release:
|
||||
name_template: '{{ .Version }}'
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ .Version }}-devel"
|
||||
version_template: "{{ .Version }}-devel"
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
|
||||
9
Makefile
9
Makefile
@@ -1,4 +1,5 @@
|
||||
GO ?= go
|
||||
DOCKER ?= docker
|
||||
GOOS ?= $(shell $(GO) env GOOS)
|
||||
|
||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||
@@ -192,12 +193,12 @@ bin/fzf: target/$(BINARY) | bin
|
||||
cp -f target/$(BINARY) bin/fzf
|
||||
|
||||
docker:
|
||||
docker build -t fzf-ubuntu .
|
||||
docker run -it fzf-ubuntu tmux
|
||||
$(DOCKER) build -t fzf-ubuntu .
|
||||
$(DOCKER) run -it fzf-ubuntu tmux
|
||||
|
||||
docker-test:
|
||||
docker build -t fzf-ubuntu .
|
||||
docker run -it fzf-ubuntu
|
||||
$(DOCKER) build -t fzf-ubuntu .
|
||||
$(DOCKER) run -it fzf-ubuntu
|
||||
|
||||
update:
|
||||
$(GO) get -u
|
||||
|
||||
3
install
3
install
@@ -2,7 +2,7 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.66.1
|
||||
version=0.67.0
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
@@ -177,6 +177,7 @@ case "$archi" in
|
||||
Linux\ aarch64\ Android) download fzf-$version-android_arm64.tar.gz ;;
|
||||
Linux\ aarch64*) download fzf-$version-linux_arm64.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\ *64*) download fzf-$version-linux_amd64.tar.gz ;;
|
||||
Linux\ s390x*) download fzf-$version-linux_s390x.tar.gz ;;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
$version="0.66.1"
|
||||
$version="0.67.0"
|
||||
|
||||
$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"
|
||||
)
|
||||
|
||||
var version = "0.66"
|
||||
var version = "0.67"
|
||||
var revision = "devel"
|
||||
|
||||
//go:embed shell/key-bindings.bash
|
||||
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf\-tmux 1 "Oct 2025" "fzf 0.66.1" "fzf\-tmux - open fzf in tmux split pane"
|
||||
.TH fzf\-tmux 1 "Nov 2025" "fzf 0.67.0" "fzf\-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf\-tmux - open fzf in tmux split pane
|
||||
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Oct 2025" "fzf 0.66.1" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Nov 2025" "fzf 0.67.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -272,6 +272,7 @@ color mappings. Each entry is separated by a comma and/or whitespaces.
|
||||
\fBgutter \fRGutter on the left
|
||||
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
||||
\fBalt\-bg \fRAlternate background color to create striped lines
|
||||
\fBalt\-gutter \fRAlternate gutter color to create the striped pattern
|
||||
\fBquery (input\-fg) \fRQuery string
|
||||
\fBghost \fRGhost text (\fB\-\-ghost\fR, \fBdim\fR applied by default)
|
||||
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
||||
@@ -740,7 +741,7 @@ ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES.
|
||||
|
||||
e.g.
|
||||
\fB# Prepend the current cursor position in yellow
|
||||
fzf \-\-info\-command='echo \-e "\\x1b[33;1m$FZF_POS\\x1b[m/$FZF_INFO 💛"'\fR
|
||||
fzf \-\-info\-command='printf "\\x1b[33;1m$FZF_POS\\x1b[m/$FZF_INFO 💛"'\fR
|
||||
|
||||
.TP
|
||||
.B "\-\-no\-info"
|
||||
@@ -840,6 +841,9 @@ e.g.
|
||||
# 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
|
||||
|
||||
\fB# Use {+f} to get the selected items as a line-separated list
|
||||
seq 100 | fzf \-\-multi \-\-bind 'enter:become:cat {+f}'\fR
|
||||
|
||||
Also,
|
||||
|
||||
* \fB{q}\fR is replaced to the current query string
|
||||
@@ -2105,7 +2109,7 @@ payload of HTTP POST request to the \fB\-\-listen\fR server.
|
||||
|
||||
e.g.
|
||||
\fB# Disallow selecting an empty line
|
||||
echo \-e "1. Hello\\n2. Goodbye\\n\\n3. Exit" |
|
||||
printf "1. Hello\\n2. Goodbye\\n\\n3. Exit" |
|
||||
fzf \-\-height '~100%' \-\-reverse \-\-header 'Select one' \\
|
||||
\-\-bind 'enter:transform:[[ \-n {} ]] &&
|
||||
echo accept ||
|
||||
|
||||
@@ -128,25 +128,52 @@ fi
|
||||
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
fzf-history-widget() {
|
||||
local selected
|
||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
|
||||
local selected extracted_with_perl=0
|
||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases no_glob no_ksharrays extendedglob 2> /dev/null
|
||||
# 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
|
||||
# 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
|
||||
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; }' |
|
||||
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=$(__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_FILE='' $(__fzfcmd))"
|
||||
extracted_with_perl=1
|
||||
else
|
||||
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 ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
|
||||
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_FILE='' $(__fzfcmd))"
|
||||
fi
|
||||
local ret=$?
|
||||
local -a cmds
|
||||
# Avoid leaking auto assigned values when using backreferences '(#b)'
|
||||
local -a mbegin mend match
|
||||
if [ -n "$selected" ]; then
|
||||
if [[ $(__fzf_exec_awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
|
||||
zle vi-fetch-history -n $MATCH
|
||||
# Heuristic to check if the selected value is from history or a custom query
|
||||
if ((( extracted_with_perl )) && [[ $selected == <->$'\t'* ]]) ||
|
||||
((( ! 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
|
||||
LBUFFER="$selected"
|
||||
fi
|
||||
|
||||
@@ -445,7 +445,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
|
||||
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||
// we fall back to the greedy algorithm.
|
||||
if slab != nil && N*M > cap(slab.I16) {
|
||||
// Also, we should not allow a very long pattern to avoid 16-bit integer
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -501,7 +503,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
if pidx < M {
|
||||
F[pidx] = int32(off)
|
||||
pidx++
|
||||
pchar = pattern[util.Min(pidx, M-1)]
|
||||
pchar = pattern[min(pidx, M-1)]
|
||||
}
|
||||
lastIdx = off
|
||||
}
|
||||
@@ -519,9 +521,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
inGap = false
|
||||
} else {
|
||||
if inGap {
|
||||
H0[off] = util.Max16(prevH0+scoreGapExtension, 0)
|
||||
H0[off] = max(prevH0+scoreGapExtension, 0)
|
||||
} else {
|
||||
H0[off] = util.Max16(prevH0+scoreGapStart, 0)
|
||||
H0[off] = max(prevH0+scoreGapStart, 0)
|
||||
}
|
||||
C0[off] = 0
|
||||
inGap = true
|
||||
@@ -587,7 +589,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
if b >= bonusBoundary && b > fb {
|
||||
consecutive = 1
|
||||
} else {
|
||||
b = util.Max16(b, util.Max16(bonusConsecutive, fb))
|
||||
b = max(b, bonusConsecutive, fb)
|
||||
}
|
||||
}
|
||||
if s1+b < s2 {
|
||||
@@ -600,7 +602,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
Csub[off] = consecutive
|
||||
|
||||
inGap = s1 < s2
|
||||
score := util.Max16(util.Max16(s1, s2), 0)
|
||||
score := max(s1, s2, 0)
|
||||
if pidx == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
||||
maxScore, maxScorePos = score, col
|
||||
}
|
||||
@@ -684,7 +686,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
|
||||
if bonus >= bonusBoundary && bonus > firstBonus {
|
||||
firstBonus = bonus
|
||||
}
|
||||
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
|
||||
bonus = max(bonus, firstBonus, bonusConsecutive)
|
||||
}
|
||||
if pidx == 0 {
|
||||
score += int(bonus * bonusFirstCharMultiplier)
|
||||
|
||||
@@ -86,7 +86,7 @@ func TestFuzzyMatch(t *testing.T) {
|
||||
scoreGapStart*2+scoreGapExtension*2)
|
||||
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
|
||||
scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*2+
|
||||
util.Max(bonusCamel123, int(bonusBoundaryWhite)))
|
||||
max(bonusCamel123, int(bonusBoundaryWhite)))
|
||||
|
||||
// Consecutive bonus updated
|
||||
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,
|
||||
|
||||
34
src/core.go
34
src/core.go
@@ -38,6 +38,18 @@ func (r revision) compatible(other revision) bool {
|
||||
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
|
||||
func Run(opts *Options) (int, error) {
|
||||
if opts.Filter == nil {
|
||||
@@ -147,7 +159,7 @@ func Run(opts *Options) (int, error) {
|
||||
if item.colors != nil {
|
||||
for _, ansi := range *item.colors {
|
||||
if ansi.color.bg >= 0 {
|
||||
maxColorOffset = util.Max32(maxColorOffset, ansi.offset[1])
|
||||
maxColorOffset = max(maxColorOffset, ansi.offset[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,6 +255,8 @@ func Run(opts *Options) (int, error) {
|
||||
pattern := patternBuilder([]rune(*opts.Filter))
|
||||
matcher.sort = pattern.sortable
|
||||
|
||||
transformer := buildItemTransformer(opts)
|
||||
|
||||
found := false
|
||||
if streamingFilter {
|
||||
slab := util.MakeSlab(slab16Size, slab32Size)
|
||||
@@ -253,7 +267,7 @@ func Run(opts *Options) (int, error) {
|
||||
if chunkList.trans(&item, runes) {
|
||||
mutex.Lock()
|
||||
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
||||
opts.Printer(item.text.ToString())
|
||||
opts.Printer(transformer(&item))
|
||||
found = true
|
||||
}
|
||||
mutex.Unlock()
|
||||
@@ -271,7 +285,7 @@ func Run(opts *Options) (int, error) {
|
||||
chunks: snapshot,
|
||||
pattern: pattern})
|
||||
for i := 0; i < result.merger.Length(); i++ {
|
||||
opts.Printer(result.merger.Get(i).item.AsString(opts.Ansi))
|
||||
opts.Printer(transformer(result.merger.Get(i).item))
|
||||
found = true
|
||||
}
|
||||
}
|
||||
@@ -319,7 +333,7 @@ func Run(opts *Options) (int, error) {
|
||||
if total >= maxFit || final {
|
||||
deferred = false
|
||||
heightUnknown = false
|
||||
terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
|
||||
terminal.startChan <- fitpad{min(total, maxFit), padHeight}
|
||||
}
|
||||
} else if deferred {
|
||||
deferred = false
|
||||
@@ -493,15 +507,7 @@ func Run(opts *Options) (int, error) {
|
||||
if len(opts.Expect) > 0 {
|
||||
opts.Printer("")
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
transformer := buildItemTransformer(opts)
|
||||
for i := range count {
|
||||
opts.Printer(transformer(merger.Get(i).item))
|
||||
}
|
||||
@@ -524,7 +530,7 @@ func Run(opts *Options) (int, error) {
|
||||
break
|
||||
}
|
||||
if delay && reading {
|
||||
dur := util.DurWithin(
|
||||
dur := util.Constrain(
|
||||
time.Duration(ticks-startTick)*coordinatorDelayStep,
|
||||
0, coordinatorDelayMax)
|
||||
time.Sleep(dur)
|
||||
|
||||
@@ -55,7 +55,7 @@ const (
|
||||
// NewMatcher returns a new Matcher
|
||||
func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
|
||||
sort bool, tac bool, eventBox *util.EventBox, revision revision) *Matcher {
|
||||
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
||||
partitions := min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
||||
return &Matcher{
|
||||
cache: cache,
|
||||
patternBuilder: patternBuilder,
|
||||
|
||||
@@ -1225,7 +1225,12 @@ func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Eve
|
||||
chords[evt] = key
|
||||
list = append(list, evt)
|
||||
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
||||
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
|
||||
evt := 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-") {
|
||||
r := runes[4]
|
||||
switch r {
|
||||
@@ -1471,6 +1476,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, *tui
|
||||
mergeAttr(&theme.Nomatch)
|
||||
case "gutter":
|
||||
mergeAttr(&theme.Gutter)
|
||||
case "alt-gutter":
|
||||
mergeAttr(&theme.AltGutter)
|
||||
case "hl":
|
||||
mergeAttr(&theme.Match)
|
||||
case "current-hl", "hl+":
|
||||
|
||||
@@ -178,7 +178,7 @@ func (r *Reader) feed(src io.Reader) {
|
||||
var err error
|
||||
for {
|
||||
n := 0
|
||||
scope := slab[:util.Min(len(slab), readerBufferSize)]
|
||||
scope := slab[:min(len(slab), readerBufferSize)]
|
||||
for range 100 {
|
||||
n, err = src.Read(scope)
|
||||
if n > 0 || err != nil {
|
||||
|
||||
@@ -42,9 +42,9 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
||||
for _, offset := range offsets {
|
||||
b, e := int(offset[0]), int(offset[1])
|
||||
if b < e {
|
||||
minBegin = util.Min(b, minBegin)
|
||||
minEnd = util.Min(e, minEnd)
|
||||
maxEnd = util.Max(e, maxEnd)
|
||||
minBegin = min(b, minBegin)
|
||||
minEnd = min(e, minEnd)
|
||||
maxEnd = max(e, maxEnd)
|
||||
validOffsetFound = true
|
||||
}
|
||||
}
|
||||
|
||||
168
src/terminal.go
168
src/terminal.go
@@ -900,7 +900,7 @@ func evaluateHeight(opts *Options, termHeight int) int {
|
||||
if opts.Height.inverse {
|
||||
size = 100 - size
|
||||
}
|
||||
return util.Max(int(size*float64(termHeight)/100.0), opts.MinHeight)
|
||||
return max(int(size*float64(termHeight)/100.0), opts.MinHeight)
|
||||
}
|
||||
if opts.Height.inverse {
|
||||
size = float64(termHeight) - size
|
||||
@@ -956,7 +956,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
effectiveMinHeight--
|
||||
}
|
||||
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
|
||||
return min(termHeight, max(evaluateHeight(opts, termHeight), effectiveMinHeight))
|
||||
}
|
||||
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.pointerEmptyRaw = ""
|
||||
} else {
|
||||
t.pointerEmpty = gutterChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
|
||||
t.pointerEmptyRaw = gutterRawChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
|
||||
t.pointerEmpty = gutterChar + strings.Repeat(" ", max(0, t.pointerLen-1))
|
||||
t.pointerEmptyRaw = gutterRawChar + strings.Repeat(" ", max(0, t.pointerLen-1))
|
||||
}
|
||||
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_LINES=%d", t.areaLines))
|
||||
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
|
||||
env = append(env, fmt.Sprintf("FZF_POS=%d", util.Min(t.merger.Length(), t.cy+1)))
|
||||
env = append(env, fmt.Sprintf("FZF_POS=%d", 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_COLUMN=%d", t.clickHeaderColumn))
|
||||
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 {
|
||||
return 0, 0
|
||||
}
|
||||
barLength := util.Max(1, height*height/(total*perLine))
|
||||
barLength := max(1, height*height/(total*perLine))
|
||||
var barStart int
|
||||
if total == height {
|
||||
barStart = 0
|
||||
} else {
|
||||
barStart = util.Min(height-barLength, (height*perLine-barLength)*offset/(total*perLine-height))
|
||||
barStart = min(height-barLength, (height*perLine-barLength)*offset/(total*perLine-height))
|
||||
}
|
||||
return barLength, barStart
|
||||
}
|
||||
@@ -1607,7 +1607,7 @@ func (t *Terminal) wrapCols() int {
|
||||
if !t.wrap {
|
||||
return 0 // No wrap
|
||||
}
|
||||
return util.Max(t.window.Width()-(t.pointerLen+t.markerLen+t.barCol()), 1)
|
||||
return max(t.window.Width()-(t.pointerLen+t.markerLen+t.barCol()), 1)
|
||||
}
|
||||
|
||||
func (t *Terminal) clearNumLinesCache() {
|
||||
@@ -1662,7 +1662,7 @@ func (t *Terminal) avgNumLines() int {
|
||||
numLines := 0
|
||||
count := 0
|
||||
total := t.merger.Length()
|
||||
offset := util.Max(0, util.Min(t.offset, total-maxItems-1))
|
||||
offset := max(0, min(t.offset, total-maxItems-1))
|
||||
for idx := 0; idx < maxItems && idx+offset < total; idx++ {
|
||||
result := t.merger.Get(idx + offset)
|
||||
lines, _ := t.numItemLines(result.item, maxItems)
|
||||
@@ -1822,7 +1822,7 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
||||
t.offset = 0
|
||||
} else if t.cy > count {
|
||||
// Try to keep the vertical position when the list shrinks
|
||||
t.cy = count - util.Min(count, t.maxItems()) + pos
|
||||
t.cy = count - min(count, t.maxItems()) + pos
|
||||
}
|
||||
}
|
||||
needActivation := false
|
||||
@@ -1987,17 +1987,17 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
|
||||
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
|
||||
}
|
||||
|
||||
adjust := func(idx1 int, idx2 int, max int, min int) {
|
||||
if min > max {
|
||||
min = max
|
||||
adjust := func(idx1 int, idx2 int, maximum int, minimum int) {
|
||||
if minimum > maximum {
|
||||
minimum = maximum
|
||||
}
|
||||
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
|
||||
if max-margin < min {
|
||||
desired := max - min
|
||||
if maximum-margin < minimum {
|
||||
desired := maximum - minimum
|
||||
paddingInt[idx1] = desired * paddingInt[idx1] / margin
|
||||
paddingInt[idx2] = desired * paddingInt[idx2] / margin
|
||||
marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin)
|
||||
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
|
||||
marginInt[idx1] = max(extraMargin[idx1], desired*marginInt[idx1]/margin)
|
||||
marginInt[idx2] = 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 {
|
||||
case posUp, posDown:
|
||||
minAreaHeight += minPreviewHeight
|
||||
minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
|
||||
minAreaWidth = max(minPreviewWidth, minAreaWidth)
|
||||
case posLeft, posRight:
|
||||
minAreaWidth += minPreviewWidth
|
||||
minAreaHeight = util.Max(minPreviewHeight, minAreaHeight)
|
||||
minAreaHeight = max(minPreviewHeight, minAreaHeight)
|
||||
}
|
||||
}
|
||||
adjust(1, 3, screenWidth, minAreaWidth)
|
||||
@@ -2490,6 +2490,8 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
if shape.HasRight() {
|
||||
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)
|
||||
return t.tui.NewWindow(top, left, width, height, windowType, noBorder, true)
|
||||
}
|
||||
@@ -2653,11 +2655,11 @@ func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts label
|
||||
}
|
||||
var col int
|
||||
if opts.column == 0 {
|
||||
col = util.Max(0, (window.Width()-length)/2)
|
||||
col = max(0, (window.Width()-length)/2)
|
||||
} else if opts.column < 0 {
|
||||
col = util.Max(0, window.Width()+opts.column+1-length)
|
||||
col = max(0, window.Width()+opts.column+1-length)
|
||||
} else {
|
||||
col = util.Min(opts.column-1, window.Width()-length)
|
||||
col = min(opts.column-1, window.Width()-length)
|
||||
}
|
||||
row := 0
|
||||
if borderShape == tui.BorderBottom || opts.bottom {
|
||||
@@ -2712,7 +2714,7 @@ func (t *Terminal) truncateQuery() {
|
||||
// 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
|
||||
// number of runes.
|
||||
t.input = t.input[:util.Min(len(t.input), maxPatternLength)]
|
||||
t.input = t.input[:min(len(t.input), maxPatternLength)]
|
||||
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
||||
}
|
||||
|
||||
@@ -2721,11 +2723,11 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
|
||||
if t.inputWindow != nil {
|
||||
w = t.inputWindow
|
||||
}
|
||||
maxWidth := util.Max(1, w.Width()-t.promptLen-1)
|
||||
maxWidth := max(1, w.Width()-t.promptLen-1)
|
||||
|
||||
_, overflow := t.trimLeft(t.input[:t.cx], maxWidth, 0)
|
||||
minOffset := int(overflow)
|
||||
maxOffset := minOffset + (maxWidth-util.Max(0, maxWidth-t.cx))/2
|
||||
maxOffset := minOffset + (maxWidth-max(0, maxWidth-t.cx))/2
|
||||
t.xoffset = util.Constrain(t.xoffset, minOffset, maxOffset)
|
||||
before, _ := t.trimLeft(t.input[t.xoffset:t.cx], maxWidth, 0)
|
||||
beforeLen := t.displayWidth(before)
|
||||
@@ -2747,7 +2749,7 @@ func (t *Terminal) promptLine() int {
|
||||
if !t.noSeparatorLine() {
|
||||
max--
|
||||
}
|
||||
return util.Min(t.visibleHeaderLinesInList(), max)
|
||||
return min(t.visibleHeaderLinesInList(), max)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -2762,11 +2764,11 @@ func (t *Terminal) placeCursor() {
|
||||
if t.layout == layoutReverse {
|
||||
y = 0
|
||||
}
|
||||
x = util.Min(x, t.inputWindow.Width()-1)
|
||||
x = min(x, t.inputWindow.Width()-1)
|
||||
t.inputWindow.Move(y, x)
|
||||
return
|
||||
}
|
||||
x = util.Min(x, t.window.Width()-1)
|
||||
x = min(x, t.window.Width()-1)
|
||||
t.move(t.promptLine(), x, false)
|
||||
}
|
||||
|
||||
@@ -2785,7 +2787,7 @@ func (t *Terminal) printPrompt() {
|
||||
|
||||
before, after := t.updatePromptOffset()
|
||||
if len(before) == 0 && len(after) == 0 && len(t.ghost) > 0 {
|
||||
maxWidth := util.Max(1, w.Width()-t.promptLen-1)
|
||||
maxWidth := max(1, w.Width()-t.promptLen-1)
|
||||
runes, _ := t.trimRight([]rune(t.ghost), maxWidth)
|
||||
w.CPrint(tui.ColGhost, string(runes))
|
||||
return
|
||||
@@ -2875,7 +2877,7 @@ func (t *Terminal) printInfoImpl() {
|
||||
}
|
||||
|
||||
found := t.resultMerger.Length()
|
||||
total := util.Max(found, t.count)
|
||||
total := max(found, t.count)
|
||||
output := fmt.Sprintf("%d/%d", found, total)
|
||||
if t.toggleSort {
|
||||
if t.sort {
|
||||
@@ -2969,7 +2971,7 @@ func (t *Terminal) printInfoImpl() {
|
||||
if t.infoStyle == infoInlineRight {
|
||||
if len(t.infoPrefix) == 0 {
|
||||
move(line, pos, false)
|
||||
newPos := util.Max(pos, t.window.Width()-outputLen-3)
|
||||
newPos := max(pos, t.window.Width()-outputLen-3)
|
||||
t.window.Print(strings.Repeat(" ", newPos-pos))
|
||||
pos = newPos
|
||||
if pos < t.window.Width() {
|
||||
@@ -2981,7 +2983,7 @@ func (t *Terminal) printInfoImpl() {
|
||||
pos++
|
||||
}
|
||||
} else {
|
||||
pos = util.Max(pos, t.window.Width()-outputLen-util.StringWidth(t.infoPrefix)-1)
|
||||
pos = max(pos, t.window.Width()-outputLen-util.StringWidth(t.infoPrefix)-1)
|
||||
printInfoPrefix()
|
||||
}
|
||||
}
|
||||
@@ -3077,7 +3079,7 @@ func (t *Terminal) printFooter() {
|
||||
}
|
||||
indentSize := t.headerIndent(t.footerBorderShape)
|
||||
indent := strings.Repeat(" ", indentSize)
|
||||
max := util.Min(len(t.footer), t.footerWindow.Height())
|
||||
max := min(len(t.footer), t.footerWindow.Height())
|
||||
|
||||
// Wrapping is not supported for footer
|
||||
wrap := t.wrap
|
||||
@@ -3107,7 +3109,11 @@ func (t *Terminal) printFooter() {
|
||||
}
|
||||
|
||||
func (t *Terminal) headerIndent(borderShape tui.BorderShape) int {
|
||||
indentSize := t.pointerLen + t.markerLen
|
||||
return t.headerIndentImpl(t.pointerLen+t.markerLen, borderShape)
|
||||
}
|
||||
|
||||
func (t *Terminal) headerIndentImpl(base int, borderShape tui.BorderShape) int {
|
||||
indentSize := base
|
||||
if t.listBorderShape.HasLeft() {
|
||||
indentSize += 1 + t.borderWidth
|
||||
}
|
||||
@@ -3196,14 +3202,22 @@ func (t *Terminal) renderEmptyLine(line int, barRange [2]int) {
|
||||
t.renderBar(line, barRange)
|
||||
}
|
||||
|
||||
func (t *Terminal) gutter(current bool) {
|
||||
func (t *Terminal) gutter(current bool, alt bool) {
|
||||
var color tui.ColorPair
|
||||
if current {
|
||||
color = tui.ColCurrentCursorEmpty
|
||||
} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
|
||||
color = tui.ColCursorEmpty
|
||||
if alt {
|
||||
color = tui.ColAltCursorEmpty
|
||||
} else {
|
||||
color = tui.ColCursorEmpty
|
||||
}
|
||||
} else {
|
||||
color = tui.ColCursorEmptyChar
|
||||
if alt {
|
||||
color = tui.ColAltCursorEmptyChar
|
||||
} else {
|
||||
color = tui.ColCursorEmptyChar
|
||||
}
|
||||
}
|
||||
gutter := t.pointerEmpty
|
||||
if t.raw {
|
||||
@@ -3214,7 +3228,7 @@ func (t *Terminal) gutter(current bool) {
|
||||
|
||||
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
|
||||
t.move(line, 0, false)
|
||||
t.gutter(false)
|
||||
t.gutter(false, false)
|
||||
t.window.Print(t.markerEmpty)
|
||||
x := t.pointerLen + t.markerLen
|
||||
|
||||
@@ -3290,7 +3304,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
||||
} else {
|
||||
alt = index%2 == 1
|
||||
}
|
||||
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
|
||||
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", max(0, t.pointerLen-1))
|
||||
if t.pointerLen == 0 {
|
||||
extraWidth = 1
|
||||
}
|
||||
@@ -3388,7 +3402,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
||||
return indentSize
|
||||
}
|
||||
if len(label) == 0 {
|
||||
t.gutter(true)
|
||||
t.gutter(true, false)
|
||||
} else {
|
||||
t.window.CPrint(tui.ColCurrentCursor, label)
|
||||
}
|
||||
@@ -3410,7 +3424,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
||||
return indentSize
|
||||
}
|
||||
if len(label) == 0 {
|
||||
t.gutter(false)
|
||||
t.gutter(false, index%2 == 1)
|
||||
} else {
|
||||
t.window.CPrint(tui.ColCursor, label)
|
||||
}
|
||||
@@ -3460,7 +3474,7 @@ func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit in
|
||||
}
|
||||
|
||||
func (t *Terminal) trimLeft(runes []rune, width int, ellipsisWidth int) ([]rune, int32) {
|
||||
width = util.Max(0, width)
|
||||
width = max(0, width)
|
||||
var trimmed int32
|
||||
// Assume that each rune takes at least one column on screen
|
||||
if len(runes) > width {
|
||||
@@ -3551,12 +3565,12 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
// ------> <------
|
||||
if t.freezeLeft > 0 {
|
||||
if len(tokens) > 0 {
|
||||
token := tokens[util.Min(t.freezeLeft, len(tokens))-1]
|
||||
token := tokens[min(t.freezeLeft, len(tokens))-1]
|
||||
splitOffset1 = int(token.prefixLength) + token.text.Length() - token.text.TrailingWhitespaces()
|
||||
}
|
||||
}
|
||||
if t.freezeRight > 0 {
|
||||
index := util.Max(t.freezeLeft-1, len(tokens)-t.freezeRight-1)
|
||||
index := max(t.freezeLeft-1, len(tokens)-t.freezeRight-1)
|
||||
if index < 0 {
|
||||
splitOffset2 = 0
|
||||
} else if index >= t.freezeLeft {
|
||||
@@ -3564,7 +3578,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
delimiter := strings.TrimLeftFunc(GetLastDelimiter(token.text.ToString(), t.delimiter), unicode.IsSpace)
|
||||
splitOffset2 = int(token.prefixLength) + token.text.Length() - len([]rune(delimiter))
|
||||
}
|
||||
splitOffset2 = util.Max(splitOffset2, splitOffset1)
|
||||
splitOffset2 = max(splitOffset2, splitOffset1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3654,7 +3668,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
var maxEnd int
|
||||
for _, offset := range offsets {
|
||||
if offset.match {
|
||||
maxEnd = util.Max(maxEnd, int(offset.offset[1]))
|
||||
maxEnd = max(maxEnd, int(offset.offset[1]))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3753,19 +3767,19 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
}
|
||||
displayWidth = t.displayWidthWithLimit(runes, 0, adjustedMaxWidth)
|
||||
if !t.wrap && displayWidth > adjustedMaxWidth {
|
||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(runes))
|
||||
maxe = util.Constrain(maxe+min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(runes))
|
||||
transformOffsets := func(diff int32, rightTrim bool) {
|
||||
for idx, offset := range offs {
|
||||
b, e := offset.offset[0], offset.offset[1]
|
||||
el := int32(len(ellipsis))
|
||||
b += el - diff
|
||||
e += el - diff
|
||||
b = util.Max32(b, el)
|
||||
b = max(b, el)
|
||||
if rightTrim {
|
||||
e = util.Min32(e, int32(maxWidth-ellipsisWidth))
|
||||
e = min(e, int32(maxWidth-ellipsisWidth))
|
||||
}
|
||||
offs[idx].offset[0] = b
|
||||
offs[idx].offset[1] = util.Max32(b, e)
|
||||
offs[idx].offset[1] = max(b, e)
|
||||
}
|
||||
}
|
||||
if t.hscroll {
|
||||
@@ -3797,11 +3811,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
runes = append(runes, ellipsis...)
|
||||
|
||||
for idx, offset := range offs {
|
||||
offs[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-len(ellipsis)))
|
||||
offs[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
||||
offs[idx].offset[0] = min(offset.offset[0], int32(maxWidth-len(ellipsis)))
|
||||
offs[idx].offset[1] = min(offset.offset[1], int32(maxWidth))
|
||||
}
|
||||
}
|
||||
displayWidth = t.displayWidthWithLimit(runes, 0, displayWidth)
|
||||
displayWidth = t.displayWidthWithLimit(runes, 0, maxWidth)
|
||||
}
|
||||
displayWidthSum += displayWidth
|
||||
|
||||
@@ -3845,8 +3859,8 @@ func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []
|
||||
maxOffset := int32(len(text))
|
||||
var url *url
|
||||
for _, offset := range offsets {
|
||||
b := util.Constrain32(offset.offset[0], index, maxOffset)
|
||||
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
||||
b := util.Constrain(offset.offset[0], index, maxOffset)
|
||||
e := util.Constrain(offset.offset[1], index, maxOffset)
|
||||
if url != nil && offset.url != url {
|
||||
url = nil
|
||||
window.LinkEnd()
|
||||
@@ -3927,7 +3941,7 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
|
||||
body := t.previewer.lines
|
||||
headerLines := t.activePreviewOpts.headerLines
|
||||
// Do not enable preview header lines if it's value is too large
|
||||
if headerLines > 0 && headerLines < util.Min(len(body), height) {
|
||||
if headerLines > 0 && headerLines < min(len(body), height) {
|
||||
header := t.previewer.lines[0:headerLines]
|
||||
body = t.previewer.lines[headerLines:]
|
||||
// Always redraw header
|
||||
@@ -3945,7 +3959,7 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
|
||||
}
|
||||
|
||||
effectiveHeight := height - headerLines
|
||||
barLength, barStart := getScrollbar(1, len(body), effectiveHeight, util.Min(len(body)-effectiveHeight, t.previewer.offset-headerLines))
|
||||
barLength, barStart := getScrollbar(1, len(body), effectiveHeight, min(len(body)-effectiveHeight, t.previewer.offset-headerLines))
|
||||
t.renderPreviewScrollbar(headerLines, barLength, barStart)
|
||||
}
|
||||
|
||||
@@ -3960,7 +3974,7 @@ func (t *Terminal) makeImageBorder(width int, top bool) string {
|
||||
h = "-"
|
||||
v = "|"
|
||||
}
|
||||
repeat := util.Max(0, width-2)
|
||||
repeat := max(0, width-2)
|
||||
if top {
|
||||
return tl + strings.Repeat(h, repeat) + tr
|
||||
}
|
||||
@@ -4495,7 +4509,7 @@ func (t *Terminal) evaluateScrollOffset() int {
|
||||
}
|
||||
|
||||
base := -1
|
||||
height := util.Max(0, t.pwindow.Height()-t.activePreviewOpts.headerLines)
|
||||
height := max(0, t.pwindow.Height()-t.activePreviewOpts.headerLines)
|
||||
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
|
||||
if strings.HasPrefix(component, "-/") {
|
||||
component = component[1:]
|
||||
@@ -4509,7 +4523,7 @@ func (t *Terminal) evaluateScrollOffset() int {
|
||||
}
|
||||
base += atoi(component)
|
||||
}
|
||||
return util.Max(0, base)
|
||||
return max(0, base)
|
||||
}
|
||||
|
||||
func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
||||
@@ -5129,16 +5143,16 @@ func (t *Terminal) Loop() error {
|
||||
if t.activePreviewOpts.aboveOrBelow() {
|
||||
if t.activePreviewOpts.size.percent {
|
||||
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.activePreviewOpts.size.size))
|
||||
contentHeight = util.Max(contentHeight+1+borderLines(t.activePreviewOpts.Border()), newContentHeight)
|
||||
contentHeight = max(contentHeight+1+borderLines(t.activePreviewOpts.Border()), newContentHeight)
|
||||
} else {
|
||||
contentHeight += int(t.activePreviewOpts.size.size) + borderLines(t.activePreviewOpts.Border())
|
||||
}
|
||||
} else {
|
||||
// Minimum height if preview window can appear
|
||||
contentHeight = util.Max(contentHeight, 1+borderLines(t.activePreviewOpts.Border()))
|
||||
contentHeight = max(contentHeight, 1+borderLines(t.activePreviewOpts.Border()))
|
||||
}
|
||||
}
|
||||
return util.Min(termHeight, contentHeight+pad)
|
||||
return min(termHeight, contentHeight+pad)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5539,7 +5553,7 @@ func (t *Terminal) Loop() error {
|
||||
t.previewer.lines = result.lines
|
||||
t.previewer.spinner = result.spinner
|
||||
if t.hasPreviewWindow() && t.previewer.following.Enabled() {
|
||||
t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.activePreviewOpts.headerLines))
|
||||
t.previewer.offset = max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.activePreviewOpts.headerLines))
|
||||
} else if result.offset >= 0 {
|
||||
t.previewer.offset = util.Constrain(result.offset, t.activePreviewOpts.headerLines, len(t.previewer.lines)-1)
|
||||
}
|
||||
@@ -6282,7 +6296,7 @@ func (t *Terminal) Loop() error {
|
||||
|
||||
// Try to retain position
|
||||
if prevIndex != minItem.Index() {
|
||||
t.cy = util.Max(0, t.merger.FindIndex(prevIndex))
|
||||
t.cy = max(0, t.merger.FindIndex(prevIndex))
|
||||
t.offset = t.cy - prevPos
|
||||
}
|
||||
|
||||
@@ -6378,7 +6392,7 @@ func (t *Terminal) Loop() error {
|
||||
linesToMove = maxItems / 2
|
||||
}
|
||||
// Move at least one line even in a very short window
|
||||
linesToMove = util.Max(1, linesToMove)
|
||||
linesToMove = max(1, linesToMove)
|
||||
|
||||
// Determine the direction of the movement
|
||||
direction := -1
|
||||
@@ -6687,7 +6701,7 @@ func (t *Terminal) Loop() error {
|
||||
if pbarDragging {
|
||||
effectiveHeight := t.pwindow.Height() - headerLines
|
||||
numLines := len(t.previewer.lines) - headerLines
|
||||
barLength, _ := getScrollbar(1, numLines, effectiveHeight, util.Min(numLines-effectiveHeight, t.previewer.offset-headerLines))
|
||||
barLength, _ := getScrollbar(1, numLines, effectiveHeight, min(numLines-effectiveHeight, t.previewer.offset-headerLines))
|
||||
if barLength > 0 {
|
||||
y := my - t.pwindow.Top() - headerLines - barLength/2
|
||||
y = util.Constrain(y, 0, effectiveHeight-barLength)
|
||||
@@ -7207,13 +7221,13 @@ func (t *Terminal) constrain() {
|
||||
numItems = numItemsFound
|
||||
}
|
||||
|
||||
t.cy = util.Constrain(t.cy, 0, util.Max(0, count-1))
|
||||
minOffset := util.Max(t.cy-numItems+1, 0)
|
||||
maxOffset := util.Max(util.Min(count-numItems, t.cy), 0)
|
||||
t.cy = util.Constrain(t.cy, 0, max(0, count-1))
|
||||
minOffset := max(t.cy-numItems+1, 0)
|
||||
maxOffset := max(min(count-numItems, t.cy), 0)
|
||||
prevOffset := t.offset
|
||||
t.offset = util.Constrain(t.offset, minOffset, maxOffset)
|
||||
if t.scrollOff > 0 {
|
||||
scrollOff := util.Min(maxLines/2, t.scrollOff)
|
||||
scrollOff := min(maxLines/2, t.scrollOff)
|
||||
newOffset := t.offset
|
||||
// 2-phase adjustment to avoid infinite loop of alternating between moving up and down
|
||||
for phase := range 2 {
|
||||
@@ -7240,9 +7254,9 @@ func (t *Terminal) constrain() {
|
||||
}
|
||||
|
||||
if phase == 0 && linesBefore < scrollOff {
|
||||
newOffset = util.Max(minOffset, newOffset-1)
|
||||
newOffset = max(minOffset, newOffset-1)
|
||||
} else if phase == 1 && linesAfter < scrollOff {
|
||||
newOffset = util.Min(maxOffset, newOffset+1)
|
||||
newOffset = min(maxOffset, newOffset+1)
|
||||
}
|
||||
if newOffset == prevOffset {
|
||||
break
|
||||
@@ -7299,8 +7313,8 @@ func (t *Terminal) promptLines() int {
|
||||
|
||||
// Number of item lines in the list window
|
||||
func (t *Terminal) maxItems() int {
|
||||
max := t.window.Height() - t.visibleHeaderLinesInList() - t.promptLines()
|
||||
return util.Max(max, 0)
|
||||
maximum := t.window.Height() - t.visibleHeaderLinesInList() - t.promptLines()
|
||||
return max(maximum, 0)
|
||||
}
|
||||
|
||||
func (t *Terminal) dumpItem(i *Item) StatusItem {
|
||||
@@ -7337,12 +7351,12 @@ func (t *Terminal) dumpStatus(params getParams) string {
|
||||
defer t.mutex.Unlock()
|
||||
|
||||
selectedItems := t.sortSelected()
|
||||
selected := make([]StatusItem, util.Max(0, util.Min(params.limit, len(selectedItems)-params.offset)))
|
||||
selected := make([]StatusItem, max(0, min(params.limit, len(selectedItems)-params.offset)))
|
||||
for i := range selected {
|
||||
selected[i] = t.dumpItem(selectedItems[i+params.offset].item)
|
||||
}
|
||||
|
||||
matches := make([]StatusItem, util.Max(0, util.Min(params.limit, t.resultMerger.Length()-params.offset)))
|
||||
matches := make([]StatusItem, max(0, min(params.limit, t.resultMerger.Length()-params.offset)))
|
||||
for i := range matches {
|
||||
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
|
||||
}
|
||||
}
|
||||
minIdx = util.Max(0, begin-1)
|
||||
minIdx = max(0, begin-1)
|
||||
for idx := begin; idx <= end; idx++ {
|
||||
if idx >= 1 && idx <= numTokens {
|
||||
parts = append(parts, tokens[idx-1].text)
|
||||
|
||||
@@ -16,7 +16,7 @@ func _() {
|
||||
_ = x[CtrlE-5]
|
||||
_ = x[CtrlF-6]
|
||||
_ = x[CtrlG-7]
|
||||
_ = x[CtrlBackspace-8]
|
||||
_ = x[CtrlH-8]
|
||||
_ = x[Tab-9]
|
||||
_ = x[CtrlJ-10]
|
||||
_ = x[CtrlK-11]
|
||||
@@ -99,74 +99,75 @@ func _() {
|
||||
_ = x[CtrlRight-88]
|
||||
_ = x[CtrlHome-89]
|
||||
_ = x[CtrlEnd-90]
|
||||
_ = x[CtrlDelete-91]
|
||||
_ = x[CtrlPageUp-92]
|
||||
_ = x[CtrlPageDown-93]
|
||||
_ = x[Alt-94]
|
||||
_ = x[CtrlAlt-95]
|
||||
_ = x[CtrlAltUp-96]
|
||||
_ = x[CtrlAltDown-97]
|
||||
_ = x[CtrlAltLeft-98]
|
||||
_ = x[CtrlAltRight-99]
|
||||
_ = x[CtrlAltHome-100]
|
||||
_ = x[CtrlAltEnd-101]
|
||||
_ = x[CtrlAltBackspace-102]
|
||||
_ = x[CtrlAltDelete-103]
|
||||
_ = x[CtrlAltPageUp-104]
|
||||
_ = x[CtrlAltPageDown-105]
|
||||
_ = x[CtrlShiftUp-106]
|
||||
_ = x[CtrlShiftDown-107]
|
||||
_ = x[CtrlShiftLeft-108]
|
||||
_ = x[CtrlShiftRight-109]
|
||||
_ = x[CtrlShiftHome-110]
|
||||
_ = x[CtrlShiftEnd-111]
|
||||
_ = x[CtrlShiftDelete-112]
|
||||
_ = x[CtrlShiftPageUp-113]
|
||||
_ = x[CtrlShiftPageDown-114]
|
||||
_ = x[CtrlAltShiftUp-115]
|
||||
_ = x[CtrlAltShiftDown-116]
|
||||
_ = x[CtrlAltShiftLeft-117]
|
||||
_ = x[CtrlAltShiftRight-118]
|
||||
_ = x[CtrlAltShiftHome-119]
|
||||
_ = x[CtrlAltShiftEnd-120]
|
||||
_ = x[CtrlAltShiftDelete-121]
|
||||
_ = x[CtrlAltShiftPageUp-122]
|
||||
_ = x[CtrlAltShiftPageDown-123]
|
||||
_ = x[Invalid-124]
|
||||
_ = x[Fatal-125]
|
||||
_ = x[BracketedPasteBegin-126]
|
||||
_ = x[BracketedPasteEnd-127]
|
||||
_ = x[Mouse-128]
|
||||
_ = x[DoubleClick-129]
|
||||
_ = x[LeftClick-130]
|
||||
_ = x[RightClick-131]
|
||||
_ = x[SLeftClick-132]
|
||||
_ = x[SRightClick-133]
|
||||
_ = x[ScrollUp-134]
|
||||
_ = x[ScrollDown-135]
|
||||
_ = x[SScrollUp-136]
|
||||
_ = x[SScrollDown-137]
|
||||
_ = x[PreviewScrollUp-138]
|
||||
_ = x[PreviewScrollDown-139]
|
||||
_ = x[Resize-140]
|
||||
_ = x[Change-141]
|
||||
_ = x[BackwardEOF-142]
|
||||
_ = x[Start-143]
|
||||
_ = x[Load-144]
|
||||
_ = x[Focus-145]
|
||||
_ = x[One-146]
|
||||
_ = x[Zero-147]
|
||||
_ = x[Result-148]
|
||||
_ = x[Jump-149]
|
||||
_ = x[JumpCancel-150]
|
||||
_ = x[ClickHeader-151]
|
||||
_ = x[ClickFooter-152]
|
||||
_ = x[Multi-153]
|
||||
_ = x[CtrlBackspace-91]
|
||||
_ = x[CtrlDelete-92]
|
||||
_ = x[CtrlPageUp-93]
|
||||
_ = x[CtrlPageDown-94]
|
||||
_ = x[Alt-95]
|
||||
_ = x[CtrlAlt-96]
|
||||
_ = x[CtrlAltUp-97]
|
||||
_ = x[CtrlAltDown-98]
|
||||
_ = x[CtrlAltLeft-99]
|
||||
_ = x[CtrlAltRight-100]
|
||||
_ = x[CtrlAltHome-101]
|
||||
_ = x[CtrlAltEnd-102]
|
||||
_ = x[CtrlAltBackspace-103]
|
||||
_ = x[CtrlAltDelete-104]
|
||||
_ = x[CtrlAltPageUp-105]
|
||||
_ = x[CtrlAltPageDown-106]
|
||||
_ = x[CtrlShiftUp-107]
|
||||
_ = x[CtrlShiftDown-108]
|
||||
_ = x[CtrlShiftLeft-109]
|
||||
_ = x[CtrlShiftRight-110]
|
||||
_ = x[CtrlShiftHome-111]
|
||||
_ = x[CtrlShiftEnd-112]
|
||||
_ = x[CtrlShiftDelete-113]
|
||||
_ = x[CtrlShiftPageUp-114]
|
||||
_ = x[CtrlShiftPageDown-115]
|
||||
_ = x[CtrlAltShiftUp-116]
|
||||
_ = x[CtrlAltShiftDown-117]
|
||||
_ = x[CtrlAltShiftLeft-118]
|
||||
_ = x[CtrlAltShiftRight-119]
|
||||
_ = x[CtrlAltShiftHome-120]
|
||||
_ = x[CtrlAltShiftEnd-121]
|
||||
_ = x[CtrlAltShiftDelete-122]
|
||||
_ = x[CtrlAltShiftPageUp-123]
|
||||
_ = x[CtrlAltShiftPageDown-124]
|
||||
_ = x[Invalid-125]
|
||||
_ = x[Fatal-126]
|
||||
_ = x[BracketedPasteBegin-127]
|
||||
_ = x[BracketedPasteEnd-128]
|
||||
_ = x[Mouse-129]
|
||||
_ = x[DoubleClick-130]
|
||||
_ = x[LeftClick-131]
|
||||
_ = x[RightClick-132]
|
||||
_ = x[SLeftClick-133]
|
||||
_ = x[SRightClick-134]
|
||||
_ = x[ScrollUp-135]
|
||||
_ = x[ScrollDown-136]
|
||||
_ = x[SScrollUp-137]
|
||||
_ = x[SScrollDown-138]
|
||||
_ = x[PreviewScrollUp-139]
|
||||
_ = x[PreviewScrollDown-140]
|
||||
_ = x[Resize-141]
|
||||
_ = x[Change-142]
|
||||
_ = x[BackwardEOF-143]
|
||||
_ = x[Start-144]
|
||||
_ = x[Load-145]
|
||||
_ = x[Focus-146]
|
||||
_ = x[One-147]
|
||||
_ = x[Zero-148]
|
||||
_ = x[Result-149]
|
||||
_ = x[Jump-150]
|
||||
_ = x[JumpCancel-151]
|
||||
_ = x[ClickHeader-152]
|
||||
_ = x[ClickFooter-153]
|
||||
_ = x[Multi-154]
|
||||
}
|
||||
|
||||
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlBackspaceTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteShiftHomeShiftEndShiftPageUpShiftPageDownF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltDeleteAltHomeAltEndAltPageUpAltPageDownAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltShiftDeleteAltShiftHomeAltShiftEndAltShiftPageUpAltShiftPageDownCtrlUpCtrlDownCtrlLeftCtrlRightCtrlHomeCtrlEndCtrlDeleteCtrlPageUpCtrlPageDownAltCtrlAltCtrlAltUpCtrlAltDownCtrlAltLeftCtrlAltRightCtrlAltHomeCtrlAltEndCtrlAltBackspaceCtrlAltDeleteCtrlAltPageUpCtrlAltPageDownCtrlShiftUpCtrlShiftDownCtrlShiftLeftCtrlShiftRightCtrlShiftHomeCtrlShiftEndCtrlShiftDeleteCtrlShiftPageUpCtrlShiftPageDownCtrlAltShiftUpCtrlAltShiftDownCtrlAltShiftLeftCtrlAltShiftRightCtrlAltShiftHomeCtrlAltShiftEndCtrlAltShiftDeleteCtrlAltShiftPageUpCtrlAltShiftPageDownInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMulti"
|
||||
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteShiftHomeShiftEndShiftPageUpShiftPageDownF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltDeleteAltHomeAltEndAltPageUpAltPageDownAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltShiftDeleteAltShiftHomeAltShiftEndAltShiftPageUpAltShiftPageDownCtrlUpCtrlDownCtrlLeftCtrlRightCtrlHomeCtrlEndCtrlBackspaceCtrlDeleteCtrlPageUpCtrlPageDownAltCtrlAltCtrlAltUpCtrlAltDownCtrlAltLeftCtrlAltRightCtrlAltHomeCtrlAltEndCtrlAltBackspaceCtrlAltDeleteCtrlAltPageUpCtrlAltPageDownCtrlShiftUpCtrlShiftDownCtrlShiftLeftCtrlShiftRightCtrlShiftHomeCtrlShiftEndCtrlShiftDeleteCtrlShiftPageUpCtrlShiftPageDownCtrlAltShiftUpCtrlAltShiftDownCtrlAltShiftLeftCtrlAltShiftRightCtrlAltShiftHomeCtrlAltShiftEndCtrlAltShiftDeleteCtrlAltShiftPageUpCtrlAltShiftPageDownInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMulti"
|
||||
|
||||
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}
|
||||
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}
|
||||
|
||||
func (i EventType) String() string {
|
||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||
|
||||
@@ -1033,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 {
|
||||
width = util.Max(0, width)
|
||||
height = util.Max(0, height)
|
||||
width = max(0, width)
|
||||
height = max(0, height)
|
||||
w := &LightWindow{
|
||||
renderer: r,
|
||||
colored: r.theme.Colored,
|
||||
|
||||
@@ -729,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 {
|
||||
width = util.Max(0, width)
|
||||
height = util.Max(0, height)
|
||||
width = max(0, width)
|
||||
height = max(0, height)
|
||||
normal := ColBorder
|
||||
switch windowType {
|
||||
case WindowList:
|
||||
|
||||
@@ -110,21 +110,21 @@ func TestGetCharEventKey(t *testing.T) {
|
||||
{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 | tcell.ModAlt}, wantKey{CtrlAltBackspace, 0, nil}},
|
||||
{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.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||
{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.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+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.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, 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{CtrlBackspace, 0, nil}}, // actual "Ctrl+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{CtrlBackspace, 0, nil}}, // actual "Ctrl+Shift+H" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{AltBackspace, 0, nil}}, // fabricated "Ctrl+Shift+Alt+H" keystroke
|
||||
{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.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.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.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{Backspace, 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.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, 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 | tcell.ModAlt}, wantKey{CtrlAlt, 'h', 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.ModAlt | tcell.ModShift}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Shift+Alt+H" keystroke
|
||||
|
||||
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
|
||||
|
||||
@@ -43,7 +43,7 @@ const (
|
||||
CtrlE
|
||||
CtrlF
|
||||
CtrlG
|
||||
CtrlBackspace
|
||||
CtrlH
|
||||
Tab
|
||||
CtrlJ
|
||||
CtrlK
|
||||
@@ -137,6 +137,7 @@ const (
|
||||
CtrlRight
|
||||
CtrlHome
|
||||
CtrlEnd
|
||||
CtrlBackspace
|
||||
CtrlDelete
|
||||
CtrlPageUp
|
||||
CtrlPageDown
|
||||
@@ -455,6 +456,7 @@ type ColorTheme struct {
|
||||
PreviewBg ColorAttr
|
||||
DarkBg ColorAttr
|
||||
Gutter ColorAttr
|
||||
AltGutter ColorAttr
|
||||
Prompt ColorAttr
|
||||
InputBg ColorAttr
|
||||
InputBorder ColorAttr
|
||||
@@ -825,6 +827,8 @@ var (
|
||||
ColCursor ColorPair
|
||||
ColCursorEmpty ColorPair
|
||||
ColCursorEmptyChar ColorPair
|
||||
ColAltCursorEmpty ColorPair
|
||||
ColAltCursorEmptyChar ColorPair
|
||||
ColMarker ColorPair
|
||||
ColSelected ColorPair
|
||||
ColSelectedMatch ColorPair
|
||||
@@ -890,6 +894,7 @@ func init() {
|
||||
PreviewFg: defaultColor,
|
||||
PreviewBg: defaultColor,
|
||||
Gutter: undefined,
|
||||
AltGutter: undefined,
|
||||
PreviewBorder: defaultColor,
|
||||
PreviewScrollbar: defaultColor,
|
||||
PreviewLabel: defaultColor,
|
||||
@@ -942,6 +947,7 @@ func init() {
|
||||
PreviewFg: undefined,
|
||||
PreviewBg: undefined,
|
||||
Gutter: undefined,
|
||||
AltGutter: undefined,
|
||||
PreviewBorder: undefined,
|
||||
PreviewScrollbar: undefined,
|
||||
PreviewLabel: undefined,
|
||||
@@ -990,6 +996,7 @@ func init() {
|
||||
PreviewFg: undefined,
|
||||
PreviewBg: undefined,
|
||||
Gutter: undefined,
|
||||
AltGutter: undefined,
|
||||
PreviewBorder: undefined,
|
||||
PreviewScrollbar: undefined,
|
||||
PreviewLabel: undefined,
|
||||
@@ -1040,6 +1047,7 @@ func init() {
|
||||
PreviewFg: undefined,
|
||||
PreviewBg: undefined,
|
||||
Gutter: undefined,
|
||||
AltGutter: undefined,
|
||||
PreviewBorder: undefined,
|
||||
PreviewScrollbar: undefined,
|
||||
PreviewLabel: undefined,
|
||||
@@ -1090,6 +1098,7 @@ func init() {
|
||||
PreviewFg: undefined,
|
||||
PreviewBg: undefined,
|
||||
Gutter: undefined,
|
||||
AltGutter: undefined,
|
||||
PreviewBorder: undefined,
|
||||
PreviewScrollbar: undefined,
|
||||
PreviewLabel: undefined,
|
||||
@@ -1207,6 +1216,7 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, boldify bool, forceBlac
|
||||
gutter.Attr = Dim
|
||||
}
|
||||
theme.Gutter = o(theme.DarkBg, gutter)
|
||||
theme.AltGutter = o(theme.Gutter, theme.AltGutter)
|
||||
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
||||
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
|
||||
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
|
||||
@@ -1276,6 +1286,8 @@ func initPalette(theme *ColorTheme) {
|
||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||
ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)
|
||||
ColAltCursorEmpty = pair(blank, theme.AltGutter)
|
||||
ColAltCursorEmptyChar = pair(theme.AltGutter, theme.ListBg)
|
||||
if theme.SelectedBg.Color != theme.ListBg.Color {
|
||||
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
||||
} else {
|
||||
|
||||
@@ -187,7 +187,7 @@ func (chars *Chars) TrailingWhitespaces() int {
|
||||
func (chars *Chars) TrimTrailingWhitespaces(maxIndex int) {
|
||||
whitespaces := chars.TrailingWhitespaces()
|
||||
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) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/rivo/uniseg"
|
||||
@@ -55,54 +55,8 @@ func Truncate(input string, limit int) ([]rune, int) {
|
||||
return runes, width
|
||||
}
|
||||
|
||||
// Max returns the largest integer
|
||||
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 Constrain[T cmp.Ordered](val, minimum, maximum T) T {
|
||||
return max(min(val, maximum), minimum)
|
||||
}
|
||||
|
||||
func AsUint16(val int) uint16 {
|
||||
@@ -114,18 +68,6 @@ func AsUint16(val int) uint16 {
|
||||
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
|
||||
func IsTty(file *os.File) bool {
|
||||
fd := file.Fd()
|
||||
@@ -197,7 +139,7 @@ func CompareVersions(v1, v2 string) int {
|
||||
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
|
||||
if i < len(parts1) {
|
||||
p1 = atoi(parts1[i])
|
||||
|
||||
@@ -4,72 +4,8 @@ import (
|
||||
"math"
|
||||
"strings"
|
||||
"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) {
|
||||
if Constrain(-3, -1, 3) != -1 {
|
||||
t.Error("Expected", -1)
|
||||
@@ -83,22 +19,6 @@ 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) {
|
||||
if AsUint16(5) != 5 {
|
||||
t.Error("Expected", 5)
|
||||
@@ -120,18 +40,6 @@ 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) {
|
||||
o := Once(false)
|
||||
if o() {
|
||||
|
||||
@@ -1235,6 +1235,16 @@ class TestCore < TestInteractive
|
||||
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
|
||||
tmux.send_keys "echo foo | #{FZF} --bind 'backward-eof:reload(seq 100)'", :Enter
|
||||
tmux.until { |lines| lines.item_count == 1 && lines.match_count == 1 }
|
||||
|
||||
@@ -312,4 +312,18 @@ class TestFilter < TestBase
|
||||
assert_equal expected, result
|
||||
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
|
||||
|
||||
@@ -1215,6 +1215,15 @@ class TestLayout < TestInteractive
|
||||
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
|
||||
skip unless ENV['LONGTEST']
|
||||
|
||||
|
||||
@@ -462,6 +462,119 @@ class TestZsh < TestBase
|
||||
tmux.send_keys 'C-c'
|
||||
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
|
||||
|
||||
class TestFish < TestBase
|
||||
|
||||
Reference in New Issue
Block a user