Compare commits

..

1 Commits

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

View File

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

View File

@@ -1,5 +1,4 @@
GO ?= go
DOCKER ?= docker
GOOS ?= $(shell $(GO) env GOOS)
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
@@ -193,12 +192,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

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
set -u
version=0.67.0
version=0.66.1
auto_completion=
key_bindings=
update_config=2
@@ -177,7 +177,6 @@ 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 ;;

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf\-tmux 1 "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
fzf\-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "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
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
\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)
@@ -741,7 +740,7 @@ ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES.
e.g.
\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
.B "\-\-no\-info"
@@ -841,9 +840,6 @@ 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
@@ -1499,7 +1495,7 @@ e.g.
.br
\fIctrl\-/\fR (\fIctrl\-_\fR)
.br
\fIctrl\-alt\-[a\-z]\fR (\fIctrl\-alt\-h\fR is \fIctrl\-alt\-backspace\fR on non-Windows)
\fIctrl\-alt\-[a\-z]\fR
.br
\fIalt\-[*]\fR (Any case-sensitive single character is allowed)
.br
@@ -1629,7 +1625,7 @@ e.g.
.br
\fIctrl\-alt\-end\fR
.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
\fIctrl\-alt\-delete\fR
.br
@@ -2109,7 +2105,7 @@ payload of HTTP POST request to the \fB\-\-listen\fR server.
e.g.
\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' \\
\-\-bind 'enter:transform:[[ \-n {} ]] &&
echo accept ||

View File

@@ -128,52 +128,25 @@ fi
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
local selected extracted_with_perl=0
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases no_glob no_ksharrays extendedglob 2> /dev/null
local selected
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
# 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 --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))"
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 --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))"
fi
local ret=$?
local -a cmds
# Avoid leaking auto assigned values when using backreferences '(#b)'
local -a mbegin mend match
if [ -n "$selected" ]; then
# 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
if [[ $(__fzf_exec_awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
zle vi-fetch-history -n $MATCH
else # selected is a custom query, not from history
LBUFFER="$selected"
fi

View File

@@ -445,9 +445,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
// Since O(nm) algorithm can be prohibitively expensive for large input,
// we fall back to the greedy algorithm.
// 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 {
if slab != nil && N*M > cap(slab.I16) {
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 {
F[pidx] = int32(off)
pidx++
pchar = pattern[min(pidx, M-1)]
pchar = pattern[util.Min(pidx, M-1)]
}
lastIdx = off
}
@@ -521,9 +519,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
inGap = false
} else {
if inGap {
H0[off] = max(prevH0+scoreGapExtension, 0)
H0[off] = util.Max16(prevH0+scoreGapExtension, 0)
} else {
H0[off] = max(prevH0+scoreGapStart, 0)
H0[off] = util.Max16(prevH0+scoreGapStart, 0)
}
C0[off] = 0
inGap = true
@@ -589,7 +587,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
if b >= bonusBoundary && b > fb {
consecutive = 1
} else {
b = max(b, bonusConsecutive, fb)
b = util.Max16(b, util.Max16(bonusConsecutive, fb))
}
}
if s1+b < s2 {
@@ -602,7 +600,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
Csub[off] = consecutive
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) {
maxScore, maxScorePos = score, col
}
@@ -686,7 +684,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
if bonus >= bonusBoundary && bonus > firstBonus {
firstBonus = bonus
}
bonus = max(bonus, firstBonus, bonusConsecutive)
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
}
if pidx == 0 {
score += int(bonus * bonusFirstCharMultiplier)

View File

@@ -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+
max(bonusCamel123, int(bonusBoundaryWhite)))
util.Max(bonusCamel123, int(bonusBoundaryWhite)))
// Consecutive bonus updated
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,

View File

@@ -38,18 +38,6 @@ func (r revision) compatible(other revision) bool {
return r.major == other.major
}
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 {
@@ -159,7 +147,7 @@ func Run(opts *Options) (int, error) {
if item.colors != nil {
for _, ansi := range *item.colors {
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))
matcher.sort = pattern.sortable
transformer := buildItemTransformer(opts)
found := false
if streamingFilter {
slab := util.MakeSlab(slab16Size, slab32Size)
@@ -267,7 +253,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(transformer(&item))
opts.Printer(item.text.ToString())
found = true
}
mutex.Unlock()
@@ -285,7 +271,7 @@ func Run(opts *Options) (int, error) {
chunks: snapshot,
pattern: pattern})
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
}
}
@@ -333,7 +319,7 @@ func Run(opts *Options) (int, error) {
if total >= maxFit || final {
deferred = false
heightUnknown = false
terminal.startChan <- fitpad{min(total, maxFit), padHeight}
terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
}
} else if deferred {
deferred = false
@@ -507,7 +493,15 @@ func Run(opts *Options) (int, error) {
if len(opts.Expect) > 0 {
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 {
opts.Printer(transformer(merger.Get(i).item))
}
@@ -530,7 +524,7 @@ func Run(opts *Options) (int, error) {
break
}
if delay && reading {
dur := util.Constrain(
dur := util.DurWithin(
time.Duration(ticks-startTick)*coordinatorDelayStep,
0, coordinatorDelayMax)
time.Sleep(dur)

View File

@@ -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 := min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
return &Matcher{
cache: cache,
patternBuilder: patternBuilder,

View File

@@ -1217,7 +1217,7 @@ func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Eve
default:
runes := []rune(key)
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
r := rune(lkey[9])
r := rune(key[9])
evt := tui.CtrlAltKey(r)
if r == 'h' && !util.IsWindows() {
evt = tui.CtrlAltBackspace.AsEvent()
@@ -1225,12 +1225,7 @@ 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]) {
evt := tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a')
r := rune(lkey[5])
if r == 'h' && !util.IsWindows() {
evt = tui.CtrlBackspace
}
add(evt)
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
r := runes[4]
switch r {
@@ -1476,8 +1471,6 @@ 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+":

View File

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

View File

@@ -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 = min(b, minBegin)
minEnd = min(e, minEnd)
maxEnd = max(e, maxEnd)
minBegin = util.Min(b, minBegin)
minEnd = util.Min(e, minEnd)
maxEnd = util.Max(e, maxEnd)
validOffsetFound = true
}
}

View File

@@ -900,7 +900,7 @@ func evaluateHeight(opts *Options, termHeight int) int {
if opts.Height.inverse {
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 {
size = float64(termHeight) - size
@@ -956,7 +956,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
effectiveMinHeight--
}
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)
}
@@ -1153,8 +1153,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
t.pointerEmpty = ""
t.pointerEmptyRaw = ""
} else {
t.pointerEmpty = gutterChar + strings.Repeat(" ", max(0, t.pointerLen-1))
t.pointerEmptyRaw = gutterRawChar + strings.Repeat(" ", max(0, t.pointerLen-1))
t.pointerEmpty = gutterChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
t.pointerEmptyRaw = gutterRawChar + strings.Repeat(" ", util.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", 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_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 := max(1, height*height/(total*perLine))
barLength := util.Max(1, height*height/(total*perLine))
var barStart int
if total == height {
barStart = 0
} 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
}
@@ -1607,7 +1607,7 @@ func (t *Terminal) wrapCols() int {
if !t.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() {
@@ -1662,7 +1662,7 @@ func (t *Terminal) avgNumLines() int {
numLines := 0
count := 0
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++ {
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 - min(count, t.maxItems()) + pos
t.cy = count - util.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, maximum int, minimum int) {
if minimum > maximum {
minimum = maximum
adjust := func(idx1 int, idx2 int, max int, min int) {
if min > max {
min = max
}
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
if maximum-margin < minimum {
desired := maximum - minimum
if max-margin < min {
desired := max - min
paddingInt[idx1] = desired * paddingInt[idx1] / margin
paddingInt[idx2] = desired * paddingInt[idx2] / margin
marginInt[idx1] = max(extraMargin[idx1], desired*marginInt[idx1]/margin)
marginInt[idx2] = max(extraMargin[idx2], desired*marginInt[idx2]/margin)
marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/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 {
case posUp, posDown:
minAreaHeight += minPreviewHeight
minAreaWidth = max(minPreviewWidth, minAreaWidth)
minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
case posLeft, posRight:
minAreaWidth += minPreviewWidth
minAreaHeight = max(minPreviewHeight, minAreaHeight)
minAreaHeight = util.Max(minPreviewHeight, minAreaHeight)
}
}
adjust(1, 3, screenWidth, minAreaWidth)
@@ -2490,8 +2490,6 @@ 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)
}
@@ -2655,11 +2653,11 @@ func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts label
}
var col int
if opts.column == 0 {
col = max(0, (window.Width()-length)/2)
col = util.Max(0, (window.Width()-length)/2)
} 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 {
col = min(opts.column-1, window.Width()-length)
col = util.Min(opts.column-1, window.Width()-length)
}
row := 0
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
// interested in the exact display width of the query. We just limit the
// 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))
}
@@ -2723,11 +2721,11 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
if t.inputWindow != nil {
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)
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)
before, _ := t.trimLeft(t.input[t.xoffset:t.cx], maxWidth, 0)
beforeLen := t.displayWidth(before)
@@ -2749,7 +2747,7 @@ func (t *Terminal) promptLine() int {
if !t.noSeparatorLine() {
max--
}
return min(t.visibleHeaderLinesInList(), max)
return util.Min(t.visibleHeaderLinesInList(), max)
}
return 0
}
@@ -2764,11 +2762,11 @@ func (t *Terminal) placeCursor() {
if t.layout == layoutReverse {
y = 0
}
x = min(x, t.inputWindow.Width()-1)
x = util.Min(x, t.inputWindow.Width()-1)
t.inputWindow.Move(y, x)
return
}
x = min(x, t.window.Width()-1)
x = util.Min(x, t.window.Width()-1)
t.move(t.promptLine(), x, false)
}
@@ -2787,7 +2785,7 @@ func (t *Terminal) printPrompt() {
before, after := t.updatePromptOffset()
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)
w.CPrint(tui.ColGhost, string(runes))
return
@@ -2877,7 +2875,7 @@ func (t *Terminal) printInfoImpl() {
}
found := t.resultMerger.Length()
total := max(found, t.count)
total := util.Max(found, t.count)
output := fmt.Sprintf("%d/%d", found, total)
if t.toggleSort {
if t.sort {
@@ -2971,7 +2969,7 @@ func (t *Terminal) printInfoImpl() {
if t.infoStyle == infoInlineRight {
if len(t.infoPrefix) == 0 {
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))
pos = newPos
if pos < t.window.Width() {
@@ -2983,7 +2981,7 @@ func (t *Terminal) printInfoImpl() {
pos++
}
} 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()
}
}
@@ -3079,7 +3077,7 @@ func (t *Terminal) printFooter() {
}
indentSize := t.headerIndent(t.footerBorderShape)
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
wrap := t.wrap
@@ -3109,11 +3107,7 @@ func (t *Terminal) printFooter() {
}
func (t *Terminal) headerIndent(borderShape tui.BorderShape) int {
return t.headerIndentImpl(t.pointerLen+t.markerLen, borderShape)
}
func (t *Terminal) headerIndentImpl(base int, borderShape tui.BorderShape) int {
indentSize := base
indentSize := t.pointerLen + t.markerLen
if t.listBorderShape.HasLeft() {
indentSize += 1 + t.borderWidth
}
@@ -3202,22 +3196,14 @@ func (t *Terminal) renderEmptyLine(line int, barRange [2]int) {
t.renderBar(line, barRange)
}
func (t *Terminal) gutter(current bool, alt bool) {
func (t *Terminal) gutter(current bool) {
var color tui.ColorPair
if current {
color = tui.ColCurrentCursorEmpty
} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
if alt {
color = tui.ColAltCursorEmpty
} else {
color = tui.ColCursorEmpty
}
color = tui.ColCursorEmpty
} else {
if alt {
color = tui.ColAltCursorEmptyChar
} else {
color = tui.ColCursorEmptyChar
}
color = tui.ColCursorEmptyChar
}
gutter := t.pointerEmpty
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) {
t.move(line, 0, false)
t.gutter(false, false)
t.gutter(false)
t.window.Print(t.markerEmpty)
x := t.pointerLen + t.markerLen
@@ -3304,7 +3290,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(" ", max(0, t.pointerLen-1))
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
if t.pointerLen == 0 {
extraWidth = 1
}
@@ -3402,7 +3388,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
return indentSize
}
if len(label) == 0 {
t.gutter(true, false)
t.gutter(true)
} else {
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
}
if len(label) == 0 {
t.gutter(false, index%2 == 1)
t.gutter(false)
} else {
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) {
width = max(0, width)
width = util.Max(0, width)
var trimmed int32
// Assume that each rune takes at least one column on screen
if len(runes) > width {
@@ -3565,12 +3551,12 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
// ------> <------
if t.freezeLeft > 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()
}
}
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 {
splitOffset2 = 0
} 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)
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
for _, offset := range offsets {
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)
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) {
for idx, offset := range offs {
b, e := offset.offset[0], offset.offset[1]
el := int32(len(ellipsis))
b += el - diff
e += el - diff
b = max(b, el)
b = util.Max32(b, el)
if rightTrim {
e = min(e, int32(maxWidth-ellipsisWidth))
e = util.Min32(e, int32(maxWidth-ellipsisWidth))
}
offs[idx].offset[0] = b
offs[idx].offset[1] = max(b, e)
offs[idx].offset[1] = util.Max32(b, e)
}
}
if t.hscroll {
@@ -3811,11 +3797,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
runes = append(runes, ellipsis...)
for idx, offset := range offs {
offs[idx].offset[0] = min(offset.offset[0], int32(maxWidth-len(ellipsis)))
offs[idx].offset[1] = min(offset.offset[1], int32(maxWidth))
offs[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-len(ellipsis)))
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
@@ -3859,8 +3845,8 @@ func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []
maxOffset := int32(len(text))
var url *url
for _, offset := range offsets {
b := util.Constrain(offset.offset[0], index, maxOffset)
e := util.Constrain(offset.offset[1], index, maxOffset)
b := util.Constrain32(offset.offset[0], index, maxOffset)
e := util.Constrain32(offset.offset[1], index, maxOffset)
if url != nil && offset.url != url {
url = nil
window.LinkEnd()
@@ -3941,7 +3927,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 < min(len(body), height) {
if headerLines > 0 && headerLines < util.Min(len(body), height) {
header := t.previewer.lines[0:headerLines]
body = t.previewer.lines[headerLines:]
// Always redraw header
@@ -3959,7 +3945,7 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
}
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)
}
@@ -3974,7 +3960,7 @@ func (t *Terminal) makeImageBorder(width int, top bool) string {
h = "-"
v = "|"
}
repeat := max(0, width-2)
repeat := util.Max(0, width-2)
if top {
return tl + strings.Repeat(h, repeat) + tr
}
@@ -4509,7 +4495,7 @@ func (t *Terminal) evaluateScrollOffset() int {
}
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) {
if strings.HasPrefix(component, "-/") {
component = component[1:]
@@ -4523,7 +4509,7 @@ func (t *Terminal) evaluateScrollOffset() int {
}
base += atoi(component)
}
return max(0, base)
return util.Max(0, base)
}
func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
@@ -5143,16 +5129,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 = max(contentHeight+1+borderLines(t.activePreviewOpts.Border()), newContentHeight)
contentHeight = util.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 = 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.spinner = result.spinner
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 {
t.previewer.offset = util.Constrain(result.offset, t.activePreviewOpts.headerLines, len(t.previewer.lines)-1)
}
@@ -6296,7 +6282,7 @@ func (t *Terminal) Loop() error {
// Try to retain position
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
}
@@ -6392,7 +6378,7 @@ func (t *Terminal) Loop() error {
linesToMove = maxItems / 2
}
// 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
direction := -1
@@ -6701,7 +6687,7 @@ func (t *Terminal) Loop() error {
if pbarDragging {
effectiveHeight := t.pwindow.Height() - 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 {
y := my - t.pwindow.Top() - headerLines - barLength/2
y = util.Constrain(y, 0, effectiveHeight-barLength)
@@ -7221,13 +7207,13 @@ func (t *Terminal) constrain() {
numItems = numItemsFound
}
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)
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)
prevOffset := t.offset
t.offset = util.Constrain(t.offset, minOffset, maxOffset)
if t.scrollOff > 0 {
scrollOff := min(maxLines/2, t.scrollOff)
scrollOff := util.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 {
@@ -7254,9 +7240,9 @@ func (t *Terminal) constrain() {
}
if phase == 0 && linesBefore < scrollOff {
newOffset = max(minOffset, newOffset-1)
newOffset = util.Max(minOffset, newOffset-1)
} else if phase == 1 && linesAfter < scrollOff {
newOffset = min(maxOffset, newOffset+1)
newOffset = util.Min(maxOffset, newOffset+1)
}
if newOffset == prevOffset {
break
@@ -7313,8 +7299,8 @@ func (t *Terminal) promptLines() int {
// Number of item lines in the list window
func (t *Terminal) maxItems() int {
maximum := t.window.Height() - t.visibleHeaderLinesInList() - t.promptLines()
return max(maximum, 0)
max := t.window.Height() - t.visibleHeaderLinesInList() - t.promptLines()
return util.Max(max, 0)
}
func (t *Terminal) dumpItem(i *Item) StatusItem {
@@ -7351,12 +7337,12 @@ func (t *Terminal) dumpStatus(params getParams) string {
defer t.mutex.Unlock()
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 {
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 {
matches[i] = t.dumpItem(t.resultMerger.Get(i + params.offset).item)
}

View File

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

View File

@@ -16,7 +16,7 @@ func _() {
_ = x[CtrlE-5]
_ = x[CtrlF-6]
_ = x[CtrlG-7]
_ = x[CtrlH-8]
_ = x[CtrlBackspace-8]
_ = x[Tab-9]
_ = x[CtrlJ-10]
_ = x[CtrlK-11]
@@ -99,75 +99,74 @@ func _() {
_ = x[CtrlRight-88]
_ = x[CtrlHome-89]
_ = x[CtrlEnd-90]
_ = 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]
_ = 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]
}
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 {
if i < 0 || i >= EventType(len(_EventType_index)-1) {

View File

@@ -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 = max(0, width)
height = max(0, height)
width = util.Max(0, width)
height = util.Max(0, height)
w := &LightWindow{
renderer: r,
colored: r.theme.Colored,

View File

@@ -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 = max(0, width)
height = max(0, height)
width = util.Max(0, width)
height = util.Max(0, height)
normal := ColBorder
switch windowType {
case WindowList:

View File

@@ -110,21 +110,21 @@ func TestGetCharEventKey(t *testing.T) {
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{AltDelete, 0, nil}},
{giveKey{tcell.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{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
{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
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},

View File

@@ -43,7 +43,7 @@ const (
CtrlE
CtrlF
CtrlG
CtrlH
CtrlBackspace
Tab
CtrlJ
CtrlK
@@ -137,7 +137,6 @@ const (
CtrlRight
CtrlHome
CtrlEnd
CtrlBackspace
CtrlDelete
CtrlPageUp
CtrlPageDown
@@ -456,7 +455,6 @@ type ColorTheme struct {
PreviewBg ColorAttr
DarkBg ColorAttr
Gutter ColorAttr
AltGutter ColorAttr
Prompt ColorAttr
InputBg ColorAttr
InputBorder ColorAttr
@@ -827,8 +825,6 @@ var (
ColCursor ColorPair
ColCursorEmpty ColorPair
ColCursorEmptyChar ColorPair
ColAltCursorEmpty ColorPair
ColAltCursorEmptyChar ColorPair
ColMarker ColorPair
ColSelected ColorPair
ColSelectedMatch ColorPair
@@ -894,7 +890,6 @@ func init() {
PreviewFg: defaultColor,
PreviewBg: defaultColor,
Gutter: undefined,
AltGutter: undefined,
PreviewBorder: defaultColor,
PreviewScrollbar: defaultColor,
PreviewLabel: defaultColor,
@@ -947,7 +942,6 @@ func init() {
PreviewFg: undefined,
PreviewBg: undefined,
Gutter: undefined,
AltGutter: undefined,
PreviewBorder: undefined,
PreviewScrollbar: undefined,
PreviewLabel: undefined,
@@ -996,7 +990,6 @@ func init() {
PreviewFg: undefined,
PreviewBg: undefined,
Gutter: undefined,
AltGutter: undefined,
PreviewBorder: undefined,
PreviewScrollbar: undefined,
PreviewLabel: undefined,
@@ -1047,7 +1040,6 @@ func init() {
PreviewFg: undefined,
PreviewBg: undefined,
Gutter: undefined,
AltGutter: undefined,
PreviewBorder: undefined,
PreviewScrollbar: undefined,
PreviewLabel: undefined,
@@ -1098,7 +1090,6 @@ func init() {
PreviewFg: undefined,
PreviewBg: undefined,
Gutter: undefined,
AltGutter: undefined,
PreviewBorder: undefined,
PreviewScrollbar: undefined,
PreviewLabel: undefined,
@@ -1216,7 +1207,6 @@ 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)
@@ -1286,8 +1276,6 @@ 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 {

View File

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

View File

@@ -1,11 +1,11 @@
package util
import (
"cmp"
"math"
"os"
"strconv"
"strings"
"time"
"github.com/mattn/go-isatty"
"github.com/rivo/uniseg"
@@ -55,8 +55,54 @@ func Truncate(input string, limit int) ([]rune, int) {
return runes, width
}
func Constrain[T cmp.Ordered](val, minimum, maximum T) T {
return max(min(val, maximum), minimum)
// 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 AsUint16(val int) uint16 {
@@ -68,6 +114,18 @@ 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()
@@ -139,7 +197,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])

View File

@@ -4,8 +4,72 @@ 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)
@@ -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) {
if AsUint16(5) != 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) {
o := Once(false)
if o() {

View File

@@ -1235,16 +1235,6 @@ 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 }

View File

@@ -312,18 +312,4 @@ 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

View File

@@ -1215,15 +1215,6 @@ 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']

View File

@@ -462,119 +462,6 @@ 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