mirror of
https://github.com/junegunn/fzf.git
synced 2025-12-07 05:14:25 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b473477c22 | ||
|
|
fcc4178bca | ||
|
|
cfc37caabc | ||
|
|
af2a81dc02 | ||
|
|
be5a687281 | ||
|
|
771e35b972 | ||
|
|
60a5be1e65 | ||
|
|
1d5e87f5e4 | ||
|
|
3db63f5e52 | ||
|
|
2ab923f3ae | ||
|
|
c3e6d9a8f9 | ||
|
|
2471edf3ff | ||
|
|
53a8aeeb72 | ||
|
|
60b35e748b | ||
|
|
3f499f055e | ||
|
|
1df99db0b2 | ||
|
|
535b610a6b | ||
|
|
91fab3b3c2 | ||
|
|
b9f2bf64ff | ||
|
|
07d53cb7e4 | ||
|
|
ead534a1be | ||
|
|
8a05083503 | ||
|
|
e659b46ff5 | ||
|
|
991c36453c | ||
|
|
4d563c6dfa | ||
|
|
5cb695744f |
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -33,12 +33,12 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@v4
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v4
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ builds:
|
|||||||
- loong64
|
- loong64
|
||||||
- ppc64le
|
- ppc64le
|
||||||
- s390x
|
- s390x
|
||||||
|
- riscv64
|
||||||
goarm:
|
goarm:
|
||||||
- "5"
|
- "5"
|
||||||
- "6"
|
- "6"
|
||||||
@@ -39,6 +40,8 @@ builds:
|
|||||||
goarch: arm64
|
goarch: arm64
|
||||||
- goos: openbsd
|
- goos: openbsd
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
|
- goos: openbsd
|
||||||
|
goarch: riscv64
|
||||||
- goos: android
|
- goos: android
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
- goos: android
|
- goos: android
|
||||||
|
|||||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,6 +1,32 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.67.0
|
||||||
|
------
|
||||||
|
- Added `--freeze-left=N` option to keep the leftmost N columns always visible.
|
||||||
|
```sh
|
||||||
|
# Keep the file name column fixed and always visible
|
||||||
|
git grep --line-number --color=always -- '' |
|
||||||
|
fzf --ansi --delimiter : --freeze-left 1
|
||||||
|
|
||||||
|
# Can be used with --keep-right
|
||||||
|
git grep --line-number --color=always -- '' |
|
||||||
|
fzf --ansi --delimiter : --freeze-left 1 --keep-right
|
||||||
|
```
|
||||||
|
- Also added `--freeze-right=N` option to keep the rightmost N columns always visible.
|
||||||
|
```sh
|
||||||
|
# Stronger version of --keep-right that always keeps the right-end visible
|
||||||
|
fd | fzf --freeze-right 1
|
||||||
|
|
||||||
|
# Keep the base name always visible
|
||||||
|
fd | fzf --freeze-right 1 --delimiter /
|
||||||
|
|
||||||
|
# Keep both leftmost and rightmost components visible
|
||||||
|
fd | fzf --freeze-left 1 --freeze-right 1 --delimiter /
|
||||||
|
```
|
||||||
|
- Updated `--info=inline` to print the spinner (load indicator).
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
0.66.1
|
0.66.1
|
||||||
------
|
------
|
||||||
- Bug fixes
|
- Bug fixes
|
||||||
|
|||||||
3
install
3
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.66.1
|
version=0.67.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -177,6 +177,7 @@ case "$archi" in
|
|||||||
Linux\ aarch64\ Android) download fzf-$version-android_arm64.tar.gz ;;
|
Linux\ aarch64\ Android) download fzf-$version-android_arm64.tar.gz ;;
|
||||||
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
|
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
|
||||||
Linux\ loongarch64*) download fzf-$version-linux_loong64.tar.gz ;;
|
Linux\ loongarch64*) download fzf-$version-linux_loong64.tar.gz ;;
|
||||||
|
Linux\ riscv64*) download fzf-$version-linux_riscv64.tar.gz ;;
|
||||||
Linux\ ppc64le*) download fzf-$version-linux_ppc64le.tar.gz ;;
|
Linux\ ppc64le*) download fzf-$version-linux_ppc64le.tar.gz ;;
|
||||||
Linux\ *64*) download fzf-$version-linux_amd64.tar.gz ;;
|
Linux\ *64*) download fzf-$version-linux_amd64.tar.gz ;;
|
||||||
Linux\ s390x*) download fzf-$version-linux_s390x.tar.gz ;;
|
Linux\ s390x*) download fzf-$version-linux_s390x.tar.gz ;;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.66.1"
|
$version="0.67.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "0.66"
|
var version = "0.67"
|
||||||
var revision = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf\-tmux 1 "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
|
.SH NAME
|
||||||
fzf\-tmux - open fzf in tmux split pane
|
fzf\-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "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
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
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
|
\fBgutter \fRGutter on the left
|
||||||
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
||||||
\fBalt\-bg \fRAlternate background color to create striped lines
|
\fBalt\-bg \fRAlternate background color to create striped lines
|
||||||
|
\fBalt\-gutter \fRAlternate gutter color to create the striped pattern
|
||||||
\fBquery (input\-fg) \fRQuery string
|
\fBquery (input\-fg) \fRQuery string
|
||||||
\fBghost \fRGhost text (\fB\-\-ghost\fR, \fBdim\fR applied by default)
|
\fBghost \fRGhost text (\fB\-\-ghost\fR, \fBdim\fR applied by default)
|
||||||
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
||||||
@@ -629,9 +630,16 @@ Render empty lines between each item
|
|||||||
The given string will be repeated to draw a horizontal line on each gap
|
The given string will be repeated to draw a horizontal line on each gap
|
||||||
(default: '┈' or '\-' depending on \fB\-\-no\-unicode\fR).
|
(default: '┈' or '\-' depending on \fB\-\-no\-unicode\fR).
|
||||||
.TP
|
.TP
|
||||||
|
.BI "\-\-freeze\-left=" "N"
|
||||||
|
Number of fields to freeze on the left.
|
||||||
|
.TP
|
||||||
|
.BI "\-\-freeze\-right=" "N"
|
||||||
|
Number of fields to freeze on the right.
|
||||||
|
.TP
|
||||||
.B "\-\-keep\-right"
|
.B "\-\-keep\-right"
|
||||||
Keep the right end of the line visible when it's too long. Effective only when
|
Keep the right end of the line visible when it's too long. Effective only when
|
||||||
the query string is empty.
|
the query string is empty. Use \fB\-\-freeze\-right=1\fR instead if you want
|
||||||
|
the last field to be always visible even with a non-empty query.
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-scroll\-off=" "LINES"
|
.BI "\-\-scroll\-off=" "LINES"
|
||||||
Number of screen lines to keep above or below when scrolling to the top or to
|
Number of screen lines to keep above or below when scrolling to the top or to
|
||||||
@@ -651,6 +659,9 @@ Label characters for \fBjump\fR mode.
|
|||||||
.BI "\-\-gutter=" "CHAR"
|
.BI "\-\-gutter=" "CHAR"
|
||||||
Character used for the gutter column (default: '▌' unless \fB\-\-no\-unicode\fR is given)
|
Character used for the gutter column (default: '▌' unless \fB\-\-no\-unicode\fR is given)
|
||||||
.TP
|
.TP
|
||||||
|
.BI "\-\-gutter\-raw=" "CHAR"
|
||||||
|
Character used for the gutter column in raw mode (default: '▖' unless \fB\-\-no\-unicode\fR is given)
|
||||||
|
.TP
|
||||||
.BI "\-\-pointer=" "STR"
|
.BI "\-\-pointer=" "STR"
|
||||||
Pointer to the current line (default: '▌' or '>' depending on \fB\-\-no\-unicode\fR)
|
Pointer to the current line (default: '▌' or '>' depending on \fB\-\-no\-unicode\fR)
|
||||||
.TP
|
.TP
|
||||||
@@ -730,7 +741,7 @@ ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES.
|
|||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Prepend the current cursor position in yellow
|
\fB# Prepend the current cursor position in yellow
|
||||||
fzf \-\-info\-command='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
|
.TP
|
||||||
.B "\-\-no\-info"
|
.B "\-\-no\-info"
|
||||||
@@ -830,6 +841,9 @@ e.g.
|
|||||||
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
||||||
seq 100000 | fzf \-\-preview "awk '{sum+=\\$1} END {print sum}' {*f}"\fR
|
seq 100000 | fzf \-\-preview "awk '{sum+=\\$1} END {print sum}' {*f}"\fR
|
||||||
|
|
||||||
|
\fB# Use {+f} to get the selected items as a line-separated list
|
||||||
|
seq 100 | fzf \-\-multi \-\-bind 'enter:become:cat {+f}'\fR
|
||||||
|
|
||||||
Also,
|
Also,
|
||||||
|
|
||||||
* \fB{q}\fR is replaced to the current query string
|
* \fB{q}\fR is replaced to the current query string
|
||||||
@@ -1485,7 +1499,7 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIctrl\-/\fR (\fIctrl\-_\fR)
|
\fIctrl\-/\fR (\fIctrl\-_\fR)
|
||||||
.br
|
.br
|
||||||
\fIctrl\-alt\-[a\-z]\fR
|
\fIctrl\-alt\-[a\-z]\fR (\fIctrl\-alt\-h\fR is \fIctrl\-alt\-backspace\fR on non-Windows)
|
||||||
.br
|
.br
|
||||||
\fIalt\-[*]\fR (Any case-sensitive single character is allowed)
|
\fIalt\-[*]\fR (Any case-sensitive single character is allowed)
|
||||||
.br
|
.br
|
||||||
@@ -1615,7 +1629,7 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIctrl\-alt\-end\fR
|
\fIctrl\-alt\-end\fR
|
||||||
.br
|
.br
|
||||||
\fIctrl\-alt\-backspace\fR (\fIctrl\-alt\-bspace\fR \fIctrl\-alt\-bs\fR)
|
\fIctrl\-alt\-backspace\fR (\fIctrl\-alt\-bspace\fR \fIctrl\-alt\-bs\fR) (\fIctrl\-alt\-h\fR (non-Windows))
|
||||||
.br
|
.br
|
||||||
\fIctrl\-alt\-delete\fR
|
\fIctrl\-alt\-delete\fR
|
||||||
.br
|
.br
|
||||||
@@ -2095,7 +2109,7 @@ payload of HTTP POST request to the \fB\-\-listen\fR server.
|
|||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Disallow selecting an empty line
|
\fB# Disallow selecting an empty line
|
||||||
echo \-e "1. Hello\\n2. Goodbye\\n\\n3. Exit" |
|
printf "1. Hello\\n2. Goodbye\\n\\n3. Exit" |
|
||||||
fzf \-\-height '~100%' \-\-reverse \-\-header 'Select one' \\
|
fzf \-\-height '~100%' \-\-reverse \-\-header 'Select one' \\
|
||||||
\-\-bind 'enter:transform:[[ \-n {} ]] &&
|
\-\-bind 'enter:transform:[[ \-n {} ]] &&
|
||||||
echo accept ||
|
echo accept ||
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ __fzf_exec_awk() {
|
|||||||
# version >= 1.3.4
|
# version >= 1.3.4
|
||||||
local n x y z d
|
local n x y z d
|
||||||
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
|
[[ $n == mawk ]] &&
|
||||||
|
(((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&
|
||||||
|
((d >= 20230302)) 2> /dev/null &&
|
||||||
|
__fzf_awk=mawk
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# Note: macOS awk has a quirk that it stops processing at all when it sees
|
# Note: macOS awk has a quirk that it stops processing at all when it sees
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ __fzf_exec_awk() {
|
|||||||
elif command -v mawk > /dev/null 2>&1; then
|
elif command -v mawk > /dev/null 2>&1; then
|
||||||
local n x y z d
|
local n x y z d
|
||||||
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
|
[[ $n == mawk ]] &&
|
||||||
|
(((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&
|
||||||
|
((d >= 20230302)) 2> /dev/null &&
|
||||||
|
__fzf_awk=mawk
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
LC_ALL=C exec "$__fzf_awk" "$@"
|
LC_ALL=C exec "$__fzf_awk" "$@"
|
||||||
|
|||||||
@@ -115,7 +115,10 @@ __fzf_exec_awk() {
|
|||||||
elif command -v mawk > /dev/null 2>&1; then
|
elif command -v mawk > /dev/null 2>&1; then
|
||||||
local n x y z d
|
local n x y z d
|
||||||
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
|
[[ $n == mawk ]] &&
|
||||||
|
(((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&
|
||||||
|
((d >= 20230302)) 2> /dev/null &&
|
||||||
|
__fzf_awk=mawk
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
LC_ALL=C exec "$__fzf_awk" "$@"
|
LC_ALL=C exec "$__fzf_awk" "$@"
|
||||||
|
|||||||
@@ -38,7 +38,10 @@ __fzf_exec_awk() {
|
|||||||
elif command -v mawk > /dev/null 2>&1; then
|
elif command -v mawk > /dev/null 2>&1; then
|
||||||
local n x y z d
|
local n x y z d
|
||||||
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
|
[[ $n == mawk ]] &&
|
||||||
|
(((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&
|
||||||
|
((d >= 20230302)) 2> /dev/null &&
|
||||||
|
__fzf_awk=mawk
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
LC_ALL=C exec "$__fzf_awk" "$@"
|
LC_ALL=C exec "$__fzf_awk" "$@"
|
||||||
|
|||||||
@@ -58,7 +58,10 @@ __fzf_exec_awk() {
|
|||||||
elif command -v mawk > /dev/null 2>&1; then
|
elif command -v mawk > /dev/null 2>&1; then
|
||||||
local n x y z d
|
local n x y z d
|
||||||
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
|
[[ $n == mawk ]] &&
|
||||||
|
(((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&
|
||||||
|
((d >= 20230302)) 2> /dev/null &&
|
||||||
|
__fzf_awk=mawk
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
LC_ALL=C exec "$__fzf_awk" "$@"
|
LC_ALL=C exec "$__fzf_awk" "$@"
|
||||||
@@ -125,25 +128,52 @@ fi
|
|||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected
|
local selected extracted_with_perl=0
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
|
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
|
# Ensure the module is loaded if not already, and the required features, such
|
||||||
# as the associative 'history' array, which maps event numbers to full history
|
# as the associative 'history' array, which maps event numbers to full history
|
||||||
# lines, are set. Also, make sure Perl is installed for multi-line output.
|
# lines, are set. Also, make sure Perl is installed for multi-line output.
|
||||||
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
|
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
|
||||||
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
|
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
|
||||||
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
|
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line ${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))"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
|
extracted_with_perl=1
|
||||||
else
|
else
|
||||||
selected="$(fc -rl 1 | __fzf_exec_awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
selected="$(fc -rl 1 | __fzf_exec_awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line ${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))"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
fi
|
fi
|
||||||
local ret=$?
|
local ret=$?
|
||||||
|
local -a cmds
|
||||||
|
# Avoid leaking auto assigned values when using backreferences '(#b)'
|
||||||
|
local -a mbegin mend match
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
if [[ $(__fzf_exec_awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
|
# Heuristic to check if the selected value is from history or a custom query
|
||||||
zle vi-fetch-history -n $MATCH
|
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
|
else # selected is a custom query, not from history
|
||||||
LBUFFER="$selected"
|
LBUFFER="$selected"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) (int
|
|||||||
|
|
||||||
firstIdx, idx, lastIdx := 0, 0, 0
|
firstIdx, idx, lastIdx := 0, 0, 0
|
||||||
var b byte
|
var b byte
|
||||||
for pidx := 0; pidx < len(pattern); pidx++ {
|
for pidx := range pattern {
|
||||||
b = byte(pattern[pidx])
|
b = byte(pattern[pidx])
|
||||||
idx = trySkip(input, caseSensitive, b, idx)
|
idx = trySkip(input, caseSensitive, b, idx)
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
@@ -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,
|
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||||
// we fall back to the greedy algorithm.
|
// we fall back to the greedy algorithm.
|
||||||
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)
|
return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,7 +728,7 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
|
|||||||
lenRunes := text.Length()
|
lenRunes := text.Length()
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
|
|
||||||
for index := 0; index < lenRunes; index++ {
|
for index := range lenRunes {
|
||||||
char := text.Get(indexAt(index, lenRunes, forward))
|
char := text.Get(indexAt(index, lenRunes, forward))
|
||||||
// This is considerably faster than blindly applying strings.ToLower to the
|
// This is considerably faster than blindly applying strings.ToLower to the
|
||||||
// whole string
|
// whole string
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func testParserReference(t testing.TB, str string) {
|
|||||||
|
|
||||||
equal := len(got) == len(exp)
|
equal := len(got) == len(exp)
|
||||||
if equal {
|
if equal {
|
||||||
for i := 0; i < len(got); i++ {
|
for i := range got {
|
||||||
if got[i] != exp[i] {
|
if got[i] != exp[i] {
|
||||||
equal = false
|
equal = false
|
||||||
break
|
break
|
||||||
@@ -167,9 +167,9 @@ func TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {
|
|||||||
randomString := func(rr *rand.Rand) string {
|
randomString := func(rr *rand.Rand) string {
|
||||||
numChars := rand.Intn(50)
|
numChars := rand.Intn(50)
|
||||||
codePoints := make([]rune, numChars)
|
codePoints := make([]rune, numChars)
|
||||||
for i := 0; i < len(codePoints); i++ {
|
for i := range codePoints {
|
||||||
var r rune
|
var r rune
|
||||||
for n := 0; n < 1000; n++ {
|
for range 1000 {
|
||||||
r = rune(rr.Intn(utf8.MaxRune))
|
r = rune(rr.Intn(utf8.MaxRune))
|
||||||
// Allow 10% of runes to be invalid
|
// Allow 10% of runes to be invalid
|
||||||
if utf8.ValidRune(r) || rr.Float64() < 0.10 {
|
if utf8.ValidRune(r) || rr.Float64() < 0.10 {
|
||||||
@@ -182,7 +182,7 @@ func TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rr := rand.New(rand.NewSource(1))
|
rr := rand.New(rand.NewSource(1))
|
||||||
for i := 0; i < 100_000; i++ {
|
for range 100_000 {
|
||||||
testParserReference(t, randomString(rr))
|
testParserReference(t, randomString(rr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func TestChunkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add more data
|
// Add more data
|
||||||
for i := 0; i < chunkSize*2; i++ {
|
for i := range chunkSize * 2 {
|
||||||
cl.Push(fmt.Appendf(nil, "item %d", i))
|
cl.Push(fmt.Appendf(nil, "item %d", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ func TestChunkListTail(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
total := chunkSize*2 + chunkSize/2
|
total := chunkSize*2 + chunkSize/2
|
||||||
for i := 0; i < total; i++ {
|
for i := range total {
|
||||||
cl.Push(fmt.Appendf(nil, "item %d", i))
|
cl.Push(fmt.Appendf(nil, "item %d", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -502,7 +502,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
return item.acceptNth(opts.Ansi, opts.Delimiter, fn)
|
return item.acceptNth(opts.Ansi, opts.Delimiter, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i < count; i++ {
|
for i := range count {
|
||||||
opts.Printer(transformer(merger.Get(i).item))
|
opts.Printer(transformer(merger.Get(i).item))
|
||||||
}
|
}
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func TestHistory(t *testing.T) {
|
|||||||
if len(h.lines) != maxHistory+1 {
|
if len(h.lines) != maxHistory+1 {
|
||||||
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
|
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
|
||||||
}
|
}
|
||||||
for i := 0; i < maxHistory; i++ {
|
for i := range maxHistory {
|
||||||
if h.lines[i] != "foobar" {
|
if h.lines[i] != "foobar" {
|
||||||
t.Error("Expected: foobar, actual: " + h.lines[i])
|
t.Error("Expected: foobar, actual: " + h.lines[i])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
|||||||
numLists := 4
|
numLists := 4
|
||||||
lists := make([][]Result, numLists)
|
lists := make([][]Result, numLists)
|
||||||
cnt := 0
|
cnt := 0
|
||||||
for i := 0; i < numLists; i++ {
|
for i := range numLists {
|
||||||
numResults := rand.Int() % 20
|
numResults := rand.Int() % 20
|
||||||
cnt += numResults
|
cnt += numResults
|
||||||
lists[i] = make([]Result, numResults)
|
lists[i] = make([]Result, numResults)
|
||||||
for j := 0; j < numResults; j++ {
|
for j := range numResults {
|
||||||
item := randResult()
|
item := randResult()
|
||||||
lists[i][j] = item
|
lists[i][j] = item
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ func TestMergerUnsorted(t *testing.T) {
|
|||||||
// Not sorted: same order
|
// Not sorted: same order
|
||||||
mg := NewMerger(nil, lists, false, false, revision{}, 0, 0)
|
mg := NewMerger(nil, lists, false, false, revision{}, 0, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
for i := 0; i < cnt; i++ {
|
for i := range cnt {
|
||||||
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
mg := NewMerger(nil, lists, true, false, revision{}, 0, 0)
|
mg := NewMerger(nil, lists, true, false, revision{}, 0, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
for i := 0; i < cnt; i++ {
|
for i := range cnt {
|
||||||
if items[i] != mg.Get(i) {
|
if items[i] != mg.Get(i) {
|
||||||
t.Error("Not sorted", items[i], mg.Get(i))
|
t.Error("Not sorted", items[i], mg.Get(i))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ Usage: fzf [options]
|
|||||||
--gap[=N] Render empty lines between each item
|
--gap[=N] Render empty lines between each item
|
||||||
--gap-line[=STR] Draw horizontal line on each gap using the string
|
--gap-line[=STR] Draw horizontal line on each gap using the string
|
||||||
(default: '┈' or '-')
|
(default: '┈' or '-')
|
||||||
|
--freeze-left=N Number of fields to freeze on the left
|
||||||
|
--freeze-right=N Number of fields to freeze on the right
|
||||||
--keep-right Keep the right end of the line visible on overflow
|
--keep-right Keep the right end of the line visible on overflow
|
||||||
--scroll-off=LINES Number of screen lines to keep above or below when
|
--scroll-off=LINES Number of screen lines to keep above or below when
|
||||||
scrolling to the top or to the bottom (default: 0)
|
scrolling to the top or to the bottom (default: 0)
|
||||||
@@ -562,6 +564,8 @@ type Options struct {
|
|||||||
Case Case
|
Case Case
|
||||||
Normalize bool
|
Normalize bool
|
||||||
Nth []Range
|
Nth []Range
|
||||||
|
FreezeLeft int
|
||||||
|
FreezeRight int
|
||||||
WithNth func(Delimiter) func([]Token, int32) string
|
WithNth func(Delimiter) func([]Token, int32) string
|
||||||
AcceptNth func(Delimiter) func([]Token, int32) string
|
AcceptNth func(Delimiter) func([]Token, int32) string
|
||||||
Delimiter Delimiter
|
Delimiter Delimiter
|
||||||
@@ -1213,11 +1217,20 @@ func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Eve
|
|||||||
default:
|
default:
|
||||||
runes := []rune(key)
|
runes := []rune(key)
|
||||||
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
|
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
|
||||||
evt := tui.CtrlAltKey(rune(key[9]))
|
r := rune(lkey[9])
|
||||||
|
evt := tui.CtrlAltKey(r)
|
||||||
|
if r == 'h' && !util.IsWindows() {
|
||||||
|
evt = tui.CtrlAltBackspace.AsEvent()
|
||||||
|
}
|
||||||
chords[evt] = key
|
chords[evt] = key
|
||||||
list = append(list, evt)
|
list = append(list, evt)
|
||||||
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
||||||
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-") {
|
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
|
||||||
r := runes[4]
|
r := runes[4]
|
||||||
switch r {
|
switch r {
|
||||||
@@ -1463,6 +1476,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, *tui
|
|||||||
mergeAttr(&theme.Nomatch)
|
mergeAttr(&theme.Nomatch)
|
||||||
case "gutter":
|
case "gutter":
|
||||||
mergeAttr(&theme.Gutter)
|
mergeAttr(&theme.Gutter)
|
||||||
|
case "alt-gutter":
|
||||||
|
mergeAttr(&theme.AltGutter)
|
||||||
case "hl":
|
case "hl":
|
||||||
mergeAttr(&theme.Match)
|
mergeAttr(&theme.Match)
|
||||||
case "current-hl", "hl+":
|
case "current-hl", "hl+":
|
||||||
@@ -2695,6 +2710,14 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if opts.Nth, err = splitNth(str); err != nil {
|
if opts.Nth, err = splitNth(str); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "--freeze-left":
|
||||||
|
if opts.FreezeLeft, err = nextInt("number of fields required"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "--freeze-right":
|
||||||
|
if opts.FreezeRight, err = nextInt("number of fields required"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case "--with-nth":
|
case "--with-nth":
|
||||||
str, err := nextString("nth expression required")
|
str, err := nextString("nth expression required")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3338,6 +3361,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
return errors.New("empty jump labels")
|
return errors.New("empty jump labels")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.FreezeLeft < 0 || opts.FreezeRight < 0 {
|
||||||
|
return errors.New("number of fields to freeze must be a non-negative integer")
|
||||||
|
}
|
||||||
|
|
||||||
if validateJumpLabels {
|
if validateJumpLabels {
|
||||||
for _, r := range opts.JumpLabels {
|
for _, r := range opts.JumpLabels {
|
||||||
if r < 32 || r > 126 {
|
if r < 32 || r > 126 {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -178,7 +179,7 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
for {
|
for {
|
||||||
n := 0
|
n := 0
|
||||||
scope := slab[:util.Min(len(slab), readerBufferSize)]
|
scope := slab[:util.Min(len(slab), readerBufferSize)]
|
||||||
for i := 0; i < 100; i++ {
|
for range 100 {
|
||||||
n, err = src.Read(scope)
|
n, err = src.Read(scope)
|
||||||
if n > 0 || err != nil {
|
if n > 0 || err != nil {
|
||||||
break
|
break
|
||||||
@@ -308,15 +309,11 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
|
|||||||
if !opts.hidden && base[0] == '.' && base != ".." {
|
if !opts.hidden && base[0] == '.' && base != ".." {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
for _, ignore := range ignoresBase {
|
if slices.Contains(ignoresBase, base) {
|
||||||
if ignore == base {
|
return filepath.SkipDir
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for _, ignore := range ignoresFull {
|
if slices.Contains(ignoresFull, path) {
|
||||||
if ignore == path {
|
return filepath.SkipDir
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for _, ignore := range ignoresSuffix {
|
for _, ignore := range ignoresSuffix {
|
||||||
if strings.HasSuffix(path, ignore) {
|
if strings.HasSuffix(path, ignore) {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
case byBegin, byEnd:
|
case byBegin, byEnd:
|
||||||
if validOffsetFound {
|
if validOffsetFound {
|
||||||
whitePrefixLen := 0
|
whitePrefixLen := 0
|
||||||
for idx := 0; idx < numChars; idx++ {
|
for idx := range numChars {
|
||||||
r := item.text.Get(idx)
|
r := item.text.Get(idx)
|
||||||
whitePrefixLen = idx
|
whitePrefixLen = idx
|
||||||
if idx == minBegin || !unicode.IsSpace(r) {
|
if idx == minBegin || !unicode.IsSpace(r) {
|
||||||
|
|||||||
256
src/terminal.go
256
src/terminal.go
@@ -331,6 +331,8 @@ type Terminal struct {
|
|||||||
scrollbar string
|
scrollbar string
|
||||||
previewScrollbar string
|
previewScrollbar string
|
||||||
ansi bool
|
ansi bool
|
||||||
|
freezeLeft int
|
||||||
|
freezeRight int
|
||||||
nthAttr tui.Attr
|
nthAttr tui.Attr
|
||||||
nth []Range
|
nth []Range
|
||||||
nthCurrent []Range
|
nthCurrent []Range
|
||||||
@@ -496,6 +498,14 @@ const (
|
|||||||
reqFatal
|
reqFatal
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isTerminalEvent(et util.EventType) bool {
|
||||||
|
switch et {
|
||||||
|
case reqClose, reqPrintQuery, reqBecome, reqQuit, reqFatal:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type action struct {
|
type action struct {
|
||||||
t actionType
|
t actionType
|
||||||
a string
|
a string
|
||||||
@@ -1050,6 +1060,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
footer: opts.Footer,
|
footer: opts.Footer,
|
||||||
header0: opts.Header,
|
header0: opts.Header,
|
||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
|
freezeLeft: opts.FreezeLeft,
|
||||||
|
freezeRight: opts.FreezeRight,
|
||||||
nthAttr: opts.Theme.Nth.Attr,
|
nthAttr: opts.Theme.Nth.Attr,
|
||||||
nth: opts.Nth,
|
nth: opts.Nth,
|
||||||
nthCurrent: opts.Nth,
|
nthCurrent: opts.Nth,
|
||||||
@@ -2478,6 +2490,8 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
|||||||
if shape.HasRight() {
|
if shape.HasRight() {
|
||||||
width++
|
width++
|
||||||
}
|
}
|
||||||
|
// Make sure that the width does not exceed the list width
|
||||||
|
width = util.Min(t.window.Width()+t.headerIndentImpl(0, shape), width)
|
||||||
height := b.Height() - borderLines(shape)
|
height := b.Height() - borderLines(shape)
|
||||||
return t.tui.NewWindow(top, left, width, height, windowType, noBorder, true)
|
return t.tui.NewWindow(top, left, width, height, windowType, noBorder, true)
|
||||||
}
|
}
|
||||||
@@ -2981,6 +2995,11 @@ func (t *Terminal) printInfoImpl() {
|
|||||||
} else {
|
} else {
|
||||||
outputPrinter(t.window, maxWidth)
|
outputPrinter(t.window, maxWidth)
|
||||||
}
|
}
|
||||||
|
if t.infoStyle == infoInline && outputLen < maxWidth-1 && t.reading {
|
||||||
|
t.window.Print(" ")
|
||||||
|
printSpinner()
|
||||||
|
outputLen += 2
|
||||||
|
}
|
||||||
|
|
||||||
if t.infoStyle == infoInlineRight {
|
if t.infoStyle == infoInlineRight {
|
||||||
if t.separatorLen > 0 {
|
if t.separatorLen > 0 {
|
||||||
@@ -3090,7 +3109,11 @@ func (t *Terminal) printFooter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) headerIndent(borderShape tui.BorderShape) int {
|
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() {
|
if t.listBorderShape.HasLeft() {
|
||||||
indentSize += 1 + t.borderWidth
|
indentSize += 1 + t.borderWidth
|
||||||
}
|
}
|
||||||
@@ -3179,14 +3202,22 @@ func (t *Terminal) renderEmptyLine(line int, barRange [2]int) {
|
|||||||
t.renderBar(line, barRange)
|
t.renderBar(line, barRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) gutter(current bool) {
|
func (t *Terminal) gutter(current bool, alt bool) {
|
||||||
var color tui.ColorPair
|
var color tui.ColorPair
|
||||||
if current {
|
if current {
|
||||||
color = tui.ColCurrentCursorEmpty
|
color = tui.ColCurrentCursorEmpty
|
||||||
} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
|
} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
|
||||||
color = tui.ColCursorEmpty
|
if alt {
|
||||||
|
color = tui.ColAltCursorEmpty
|
||||||
|
} else {
|
||||||
|
color = tui.ColCursorEmpty
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
color = tui.ColCursorEmptyChar
|
if alt {
|
||||||
|
color = tui.ColAltCursorEmptyChar
|
||||||
|
} else {
|
||||||
|
color = tui.ColCursorEmptyChar
|
||||||
|
}
|
||||||
}
|
}
|
||||||
gutter := t.pointerEmpty
|
gutter := t.pointerEmpty
|
||||||
if t.raw {
|
if t.raw {
|
||||||
@@ -3197,7 +3228,7 @@ func (t *Terminal) gutter(current bool) {
|
|||||||
|
|
||||||
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
|
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
|
||||||
t.move(line, 0, false)
|
t.move(line, 0, false)
|
||||||
t.gutter(false)
|
t.gutter(false, false)
|
||||||
t.window.Print(t.markerEmpty)
|
t.window.Print(t.markerEmpty)
|
||||||
x := t.pointerLen + t.markerLen
|
x := t.pointerLen + t.markerLen
|
||||||
|
|
||||||
@@ -3371,7 +3402,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
return indentSize
|
return indentSize
|
||||||
}
|
}
|
||||||
if len(label) == 0 {
|
if len(label) == 0 {
|
||||||
t.gutter(true)
|
t.gutter(true, false)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCurrentCursor, label)
|
t.window.CPrint(tui.ColCurrentCursor, label)
|
||||||
}
|
}
|
||||||
@@ -3393,7 +3424,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
return indentSize
|
return indentSize
|
||||||
}
|
}
|
||||||
if len(label) == 0 {
|
if len(label) == 0 {
|
||||||
t.gutter(false)
|
t.gutter(false, index%2 == 1)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCursor, label)
|
t.window.CPrint(tui.ColCursor, label)
|
||||||
}
|
}
|
||||||
@@ -3509,17 +3540,48 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
} else {
|
} else {
|
||||||
tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent)
|
tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent)
|
||||||
}
|
}
|
||||||
for _, token := range tokens {
|
nthOffsets = make([]Offset, len(tokens))
|
||||||
|
for i, token := range tokens {
|
||||||
start := token.prefixLength
|
start := token.prefixLength
|
||||||
length := token.text.Length() - token.text.TrailingWhitespaces()
|
length := token.text.Length() - token.text.TrailingWhitespaces()
|
||||||
end := start + int32(length)
|
end := start + int32(length)
|
||||||
nthOffsets = append(nthOffsets, Offset{int32(start), int32(end)})
|
nthOffsets[i] = Offset{int32(start), int32(end)}
|
||||||
}
|
}
|
||||||
sort.Sort(ByOrder(nthOffsets))
|
sort.Sort(ByOrder(nthOffsets))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr, hidden)
|
allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr, hidden)
|
||||||
|
|
||||||
|
// Determine split offset for horizontal scrolling with freeze
|
||||||
|
splitOffset1 := -1
|
||||||
|
splitOffset2 := -1
|
||||||
|
if t.hscroll && !t.wrap {
|
||||||
|
var tokens []Token
|
||||||
|
if t.freezeLeft > 0 || t.freezeRight > 0 {
|
||||||
|
tokens = Tokenize(item.text.ToString(), t.delimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 | 1 | 2 | 3 | 4 | 5
|
||||||
|
// ------> <------
|
||||||
|
if t.freezeLeft > 0 {
|
||||||
|
if len(tokens) > 0 {
|
||||||
|
token := tokens[util.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)
|
||||||
|
if index < 0 {
|
||||||
|
splitOffset2 = 0
|
||||||
|
} else if index >= t.freezeLeft {
|
||||||
|
token := tokens[index]
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
maxLines := 1
|
maxLines := 1
|
||||||
if t.canSpanMultiLines() {
|
if t.canSpanMultiLines() {
|
||||||
maxLines = maxLineNum - lineNum + 1
|
maxLines = maxLineNum - lineNum + 1
|
||||||
@@ -3589,16 +3651,24 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
splitOffsetLeft := 0
|
||||||
|
if splitOffset1 >= 0 && splitOffset1 > from && splitOffset1 < from+len(line) {
|
||||||
|
splitOffsetLeft = splitOffset1 - from
|
||||||
|
}
|
||||||
|
splitOffsetRight := -1
|
||||||
|
if splitOffset2 >= 0 && splitOffset2 >= from && splitOffset2 < from+len(line) {
|
||||||
|
splitOffsetRight = splitOffset2 - from
|
||||||
|
}
|
||||||
from += len(line)
|
from += len(line)
|
||||||
if lineOffset < skipLines {
|
if lineOffset < skipLines {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
actualLineOffset := lineOffset - skipLines
|
actualLineOffset := lineOffset - skipLines
|
||||||
|
|
||||||
var maxe int
|
var maxEnd int
|
||||||
for _, offset := range offsets {
|
for _, offset := range offsets {
|
||||||
if offset.match {
|
if offset.match {
|
||||||
maxe = util.Max(maxe, int(offset.offset[1]))
|
maxEnd = util.Max(maxEnd, int(offset.offset[1]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3662,69 +3732,117 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
wrapped = true
|
wrapped = true
|
||||||
}
|
}
|
||||||
|
|
||||||
displayWidth = t.displayWidthWithLimit(line, 0, maxWidth)
|
frozenLeft := line[:splitOffsetLeft]
|
||||||
if !t.wrap && displayWidth > maxWidth {
|
middle := line[splitOffsetLeft:]
|
||||||
ellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth/2)
|
frozenRight := []rune{}
|
||||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(line))
|
if splitOffsetRight >= splitOffsetLeft {
|
||||||
transformOffsets := func(diff int32, rightTrim bool) {
|
middle = line[splitOffsetLeft:splitOffsetRight]
|
||||||
for idx, offset := range offsets {
|
frozenRight = line[splitOffsetRight:]
|
||||||
b, e := offset.offset[0], offset.offset[1]
|
}
|
||||||
el := int32(len(ellipsis))
|
displayWidthSum := 0
|
||||||
b += el - diff
|
todo := [3]func(){}
|
||||||
e += el - diff
|
for fidx, runes := range [][]rune{frozenLeft, frozenRight, middle} {
|
||||||
b = util.Max32(b, el)
|
if len(runes) == 0 {
|
||||||
if rightTrim {
|
continue
|
||||||
e = util.Min32(e, int32(maxWidth-ellipsisWidth))
|
|
||||||
}
|
|
||||||
offsets[idx].offset[0] = b
|
|
||||||
offsets[idx].offset[1] = util.Max32(b, e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if t.hscroll {
|
shift := 0
|
||||||
if t.keepRight && pos == nil {
|
maxe := maxEnd
|
||||||
trimmed, diff := t.trimLeft(line, maxWidth, ellipsisWidth)
|
offs := make([]colorOffset, len(offsets))
|
||||||
transformOffsets(diff, false)
|
for idx := range offsets {
|
||||||
line = append(ellipsis, trimmed...)
|
offs[idx] = offsets[idx]
|
||||||
} else if !t.overflow(line[:maxe], maxWidth-ellipsisWidth) {
|
if fidx == 1 && splitOffsetRight > 0 {
|
||||||
// Stri..
|
shift = splitOffsetRight
|
||||||
line, _ = t.trimRight(line, maxWidth-ellipsisWidth)
|
} else if fidx == 2 && splitOffsetLeft > 0 {
|
||||||
line = append(line, ellipsis...)
|
shift = splitOffsetLeft
|
||||||
} else {
|
}
|
||||||
// Stri..
|
offs[idx].offset[0] -= int32(shift)
|
||||||
rightTrim := false
|
offs[idx].offset[1] -= int32(shift)
|
||||||
if t.overflow(line[maxe:], ellipsisWidth) {
|
}
|
||||||
line = append(line[:maxe], ellipsis...)
|
maxe -= shift
|
||||||
rightTrim = true
|
ellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth)
|
||||||
|
adjustedMaxWidth := maxWidth
|
||||||
|
if fidx < 2 {
|
||||||
|
// For frozen parts, reserve space for the ellipsis in the middle part
|
||||||
|
adjustedMaxWidth -= ellipsisWidth
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
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)
|
||||||
|
if rightTrim {
|
||||||
|
e = util.Min32(e, int32(maxWidth-ellipsisWidth))
|
||||||
|
}
|
||||||
|
offs[idx].offset[0] = b
|
||||||
|
offs[idx].offset[1] = util.Max32(b, e)
|
||||||
}
|
}
|
||||||
// ..ri..
|
}
|
||||||
var diff int32
|
if t.hscroll {
|
||||||
line, diff = t.trimLeft(line, maxWidth, ellipsisWidth)
|
if fidx == 1 || fidx == 2 && t.keepRight && pos == nil {
|
||||||
|
trimmed, diff := t.trimLeft(runes, maxWidth, ellipsisWidth)
|
||||||
|
transformOffsets(diff, false)
|
||||||
|
runes = append(ellipsis, trimmed...)
|
||||||
|
} else if fidx == 0 || !t.overflow(runes[:maxe], maxWidth-ellipsisWidth) {
|
||||||
|
// Stri..
|
||||||
|
runes, _ = t.trimRight(runes, maxWidth-ellipsisWidth)
|
||||||
|
runes = append(runes, ellipsis...)
|
||||||
|
} else {
|
||||||
|
// Stri..
|
||||||
|
rightTrim := false
|
||||||
|
if t.overflow(runes[maxe:], ellipsisWidth) {
|
||||||
|
runes = append(runes[:maxe], ellipsis...)
|
||||||
|
rightTrim = true
|
||||||
|
}
|
||||||
|
// ..ri..
|
||||||
|
var diff int32
|
||||||
|
runes, diff = t.trimLeft(runes, maxWidth, ellipsisWidth)
|
||||||
|
|
||||||
// Transform offsets
|
// Transform offsets
|
||||||
transformOffsets(diff, rightTrim)
|
transformOffsets(diff, rightTrim)
|
||||||
line = append(ellipsis, line...)
|
runes = append(ellipsis, runes...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
runes, _ = t.trimRight(runes, maxWidth-ellipsisWidth)
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
displayWidth = t.displayWidthWithLimit(runes, 0, maxWidth)
|
||||||
|
}
|
||||||
|
displayWidthSum += displayWidth
|
||||||
|
|
||||||
|
if maxWidth > 0 {
|
||||||
|
color := colBase
|
||||||
|
if hidden {
|
||||||
|
color = color.WithFg(t.theme.Nomatch)
|
||||||
|
}
|
||||||
|
todo[fidx] = func() {
|
||||||
|
t.printColoredString(t.window, runes, offs, color)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
line, _ = t.trimRight(line, maxWidth-ellipsisWidth)
|
break
|
||||||
line = append(line, ellipsis...)
|
|
||||||
|
|
||||||
for idx, offset := range offsets {
|
|
||||||
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-len(ellipsis)))
|
|
||||||
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
displayWidth = t.displayWidthWithLimit(line, 0, displayWidth)
|
maxWidth -= displayWidth
|
||||||
}
|
}
|
||||||
|
if todo[0] != nil {
|
||||||
if maxWidth > 0 {
|
todo[0]()
|
||||||
color := colBase
|
}
|
||||||
if hidden {
|
if todo[2] != nil {
|
||||||
color = color.WithFg(t.theme.Nomatch)
|
todo[2]()
|
||||||
}
|
}
|
||||||
t.printColoredString(t.window, line, offsets, color)
|
if todo[1] != nil {
|
||||||
|
todo[1]()
|
||||||
}
|
}
|
||||||
if postTask != nil {
|
if postTask != nil {
|
||||||
postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw, lbg)
|
postTask(actualLineNum, displayWidthSum, wasWrapped, forceRedraw, lbg)
|
||||||
} else {
|
} else {
|
||||||
t.markOtherLine(actualLineNum)
|
t.markOtherLine(actualLineNum)
|
||||||
}
|
}
|
||||||
@@ -4806,7 +4924,7 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, [3][]*I
|
|||||||
if asterisk {
|
if asterisk {
|
||||||
cnt := t.merger.Length()
|
cnt := t.merger.Length()
|
||||||
all = make([]*Item, cnt)
|
all = make([]*Item, cnt)
|
||||||
for i := 0; i < cnt; i++ {
|
for i := range cnt {
|
||||||
all[i] = t.merger.Get(i).item
|
all[i] = t.merger.Get(i).item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5523,7 +5641,7 @@ func (t *Terminal) Loop() error {
|
|||||||
req := func(evts ...util.EventType) {
|
req := func(evts ...util.EventType) {
|
||||||
for _, event := range evts {
|
for _, event := range evts {
|
||||||
events = append(events, event)
|
events = append(events, event)
|
||||||
if event == reqClose || event == reqQuit {
|
if isTerminalEvent(event) {
|
||||||
looping = false
|
looping = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7058,7 +7176,7 @@ func (t *Terminal) constrain() {
|
|||||||
|
|
||||||
// May need to try again after adjusting the offset
|
// May need to try again after adjusting the offset
|
||||||
t.offset = util.Constrain(t.offset, 0, count)
|
t.offset = util.Constrain(t.offset, 0, count)
|
||||||
for tries := 0; tries < maxLines; tries++ {
|
for range maxLines {
|
||||||
numItems := maxLines
|
numItems := maxLines
|
||||||
// How many items can be fit on screen including the current item?
|
// How many items can be fit on screen including the current item?
|
||||||
if t.canSpanMultiLines() && t.merger.Length() > 0 {
|
if t.canSpanMultiLines() && t.merger.Length() > 0 {
|
||||||
@@ -7112,7 +7230,7 @@ func (t *Terminal) constrain() {
|
|||||||
scrollOff := util.Min(maxLines/2, t.scrollOff)
|
scrollOff := util.Min(maxLines/2, t.scrollOff)
|
||||||
newOffset := t.offset
|
newOffset := t.offset
|
||||||
// 2-phase adjustment to avoid infinite loop of alternating between moving up and down
|
// 2-phase adjustment to avoid infinite loop of alternating between moving up and down
|
||||||
for phase := 0; phase < 2; phase++ {
|
for phase := range 2 {
|
||||||
for {
|
for {
|
||||||
prevOffset := newOffset
|
prevOffset := newOffset
|
||||||
numItems := t.merger.Length()
|
numItems := t.merger.Length()
|
||||||
|
|||||||
@@ -206,8 +206,9 @@ func Tokenize(text string, delimiter Delimiter) []Token {
|
|||||||
if delimiter.regex != nil {
|
if delimiter.regex != nil {
|
||||||
locs := delimiter.regex.FindAllStringIndex(text, -1)
|
locs := delimiter.regex.FindAllStringIndex(text, -1)
|
||||||
begin := 0
|
begin := 0
|
||||||
for _, loc := range locs {
|
tokens = make([]string, len(locs))
|
||||||
tokens = append(tokens, text[begin:loc[1]])
|
for i, loc := range locs {
|
||||||
|
tokens[i] = text[begin:loc[1]]
|
||||||
begin = loc[1]
|
begin = loc[1]
|
||||||
}
|
}
|
||||||
if begin < len(text) {
|
if begin < len(text) {
|
||||||
@@ -233,6 +234,23 @@ func StripLastDelimiter(str string, delimiter Delimiter) string {
|
|||||||
return strings.TrimRightFunc(str, unicode.IsSpace)
|
return strings.TrimRightFunc(str, unicode.IsSpace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetLastDelimiter(str string, delimiter Delimiter) string {
|
||||||
|
if delimiter.str != nil {
|
||||||
|
if strings.HasSuffix(str, *delimiter.str) {
|
||||||
|
return *delimiter.str
|
||||||
|
}
|
||||||
|
} else if delimiter.regex != nil {
|
||||||
|
locs := delimiter.regex.FindAllStringIndex(str, -1)
|
||||||
|
if len(locs) > 0 {
|
||||||
|
lastLoc := locs[len(locs)-1]
|
||||||
|
if lastLoc[1] == len(str) {
|
||||||
|
return str[lastLoc[0]:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// JoinTokens concatenates the tokens into a single string
|
// JoinTokens concatenates the tokens into a single string
|
||||||
func JoinTokens(tokens []Token) string {
|
func JoinTokens(tokens []Token) string {
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func _() {
|
|||||||
_ = x[CtrlE-5]
|
_ = x[CtrlE-5]
|
||||||
_ = x[CtrlF-6]
|
_ = x[CtrlF-6]
|
||||||
_ = x[CtrlG-7]
|
_ = x[CtrlG-7]
|
||||||
_ = x[CtrlBackspace-8]
|
_ = x[CtrlH-8]
|
||||||
_ = x[Tab-9]
|
_ = x[Tab-9]
|
||||||
_ = x[CtrlJ-10]
|
_ = x[CtrlJ-10]
|
||||||
_ = x[CtrlK-11]
|
_ = x[CtrlK-11]
|
||||||
@@ -99,74 +99,75 @@ func _() {
|
|||||||
_ = x[CtrlRight-88]
|
_ = x[CtrlRight-88]
|
||||||
_ = x[CtrlHome-89]
|
_ = x[CtrlHome-89]
|
||||||
_ = x[CtrlEnd-90]
|
_ = x[CtrlEnd-90]
|
||||||
_ = x[CtrlDelete-91]
|
_ = x[CtrlBackspace-91]
|
||||||
_ = x[CtrlPageUp-92]
|
_ = x[CtrlDelete-92]
|
||||||
_ = x[CtrlPageDown-93]
|
_ = x[CtrlPageUp-93]
|
||||||
_ = x[Alt-94]
|
_ = x[CtrlPageDown-94]
|
||||||
_ = x[CtrlAlt-95]
|
_ = x[Alt-95]
|
||||||
_ = x[CtrlAltUp-96]
|
_ = x[CtrlAlt-96]
|
||||||
_ = x[CtrlAltDown-97]
|
_ = x[CtrlAltUp-97]
|
||||||
_ = x[CtrlAltLeft-98]
|
_ = x[CtrlAltDown-98]
|
||||||
_ = x[CtrlAltRight-99]
|
_ = x[CtrlAltLeft-99]
|
||||||
_ = x[CtrlAltHome-100]
|
_ = x[CtrlAltRight-100]
|
||||||
_ = x[CtrlAltEnd-101]
|
_ = x[CtrlAltHome-101]
|
||||||
_ = x[CtrlAltBackspace-102]
|
_ = x[CtrlAltEnd-102]
|
||||||
_ = x[CtrlAltDelete-103]
|
_ = x[CtrlAltBackspace-103]
|
||||||
_ = x[CtrlAltPageUp-104]
|
_ = x[CtrlAltDelete-104]
|
||||||
_ = x[CtrlAltPageDown-105]
|
_ = x[CtrlAltPageUp-105]
|
||||||
_ = x[CtrlShiftUp-106]
|
_ = x[CtrlAltPageDown-106]
|
||||||
_ = x[CtrlShiftDown-107]
|
_ = x[CtrlShiftUp-107]
|
||||||
_ = x[CtrlShiftLeft-108]
|
_ = x[CtrlShiftDown-108]
|
||||||
_ = x[CtrlShiftRight-109]
|
_ = x[CtrlShiftLeft-109]
|
||||||
_ = x[CtrlShiftHome-110]
|
_ = x[CtrlShiftRight-110]
|
||||||
_ = x[CtrlShiftEnd-111]
|
_ = x[CtrlShiftHome-111]
|
||||||
_ = x[CtrlShiftDelete-112]
|
_ = x[CtrlShiftEnd-112]
|
||||||
_ = x[CtrlShiftPageUp-113]
|
_ = x[CtrlShiftDelete-113]
|
||||||
_ = x[CtrlShiftPageDown-114]
|
_ = x[CtrlShiftPageUp-114]
|
||||||
_ = x[CtrlAltShiftUp-115]
|
_ = x[CtrlShiftPageDown-115]
|
||||||
_ = x[CtrlAltShiftDown-116]
|
_ = x[CtrlAltShiftUp-116]
|
||||||
_ = x[CtrlAltShiftLeft-117]
|
_ = x[CtrlAltShiftDown-117]
|
||||||
_ = x[CtrlAltShiftRight-118]
|
_ = x[CtrlAltShiftLeft-118]
|
||||||
_ = x[CtrlAltShiftHome-119]
|
_ = x[CtrlAltShiftRight-119]
|
||||||
_ = x[CtrlAltShiftEnd-120]
|
_ = x[CtrlAltShiftHome-120]
|
||||||
_ = x[CtrlAltShiftDelete-121]
|
_ = x[CtrlAltShiftEnd-121]
|
||||||
_ = x[CtrlAltShiftPageUp-122]
|
_ = x[CtrlAltShiftDelete-122]
|
||||||
_ = x[CtrlAltShiftPageDown-123]
|
_ = x[CtrlAltShiftPageUp-123]
|
||||||
_ = x[Invalid-124]
|
_ = x[CtrlAltShiftPageDown-124]
|
||||||
_ = x[Fatal-125]
|
_ = x[Invalid-125]
|
||||||
_ = x[BracketedPasteBegin-126]
|
_ = x[Fatal-126]
|
||||||
_ = x[BracketedPasteEnd-127]
|
_ = x[BracketedPasteBegin-127]
|
||||||
_ = x[Mouse-128]
|
_ = x[BracketedPasteEnd-128]
|
||||||
_ = x[DoubleClick-129]
|
_ = x[Mouse-129]
|
||||||
_ = x[LeftClick-130]
|
_ = x[DoubleClick-130]
|
||||||
_ = x[RightClick-131]
|
_ = x[LeftClick-131]
|
||||||
_ = x[SLeftClick-132]
|
_ = x[RightClick-132]
|
||||||
_ = x[SRightClick-133]
|
_ = x[SLeftClick-133]
|
||||||
_ = x[ScrollUp-134]
|
_ = x[SRightClick-134]
|
||||||
_ = x[ScrollDown-135]
|
_ = x[ScrollUp-135]
|
||||||
_ = x[SScrollUp-136]
|
_ = x[ScrollDown-136]
|
||||||
_ = x[SScrollDown-137]
|
_ = x[SScrollUp-137]
|
||||||
_ = x[PreviewScrollUp-138]
|
_ = x[SScrollDown-138]
|
||||||
_ = x[PreviewScrollDown-139]
|
_ = x[PreviewScrollUp-139]
|
||||||
_ = x[Resize-140]
|
_ = x[PreviewScrollDown-140]
|
||||||
_ = x[Change-141]
|
_ = x[Resize-141]
|
||||||
_ = x[BackwardEOF-142]
|
_ = x[Change-142]
|
||||||
_ = x[Start-143]
|
_ = x[BackwardEOF-143]
|
||||||
_ = x[Load-144]
|
_ = x[Start-144]
|
||||||
_ = x[Focus-145]
|
_ = x[Load-145]
|
||||||
_ = x[One-146]
|
_ = x[Focus-146]
|
||||||
_ = x[Zero-147]
|
_ = x[One-147]
|
||||||
_ = x[Result-148]
|
_ = x[Zero-148]
|
||||||
_ = x[Jump-149]
|
_ = x[Result-149]
|
||||||
_ = x[JumpCancel-150]
|
_ = x[Jump-150]
|
||||||
_ = x[ClickHeader-151]
|
_ = x[JumpCancel-151]
|
||||||
_ = x[ClickFooter-152]
|
_ = x[ClickHeader-152]
|
||||||
_ = x[Multi-153]
|
_ = 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 {
|
func (i EventType) String() string {
|
||||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func (r *LightRenderer) findOffset() (row int, col int) {
|
|||||||
r.flush()
|
r.flush()
|
||||||
var err error
|
var err error
|
||||||
bytes := []byte{}
|
bytes := []byte{}
|
||||||
for tries := 0; tries < offsetPollTries; tries++ {
|
for tries := range offsetPollTries {
|
||||||
bytes, err = r.getBytesInternal(bytes, tries > 0)
|
bytes, err = r.getBytesInternal(bytes, tries > 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
|
|||||||
@@ -371,10 +371,12 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
case rune(tcell.KeyCtrlH):
|
case rune(tcell.KeyCtrlH):
|
||||||
switch {
|
switch {
|
||||||
|
case ctrl:
|
||||||
|
return keyfn('h')
|
||||||
case alt:
|
case alt:
|
||||||
return Event{AltBackspace, 0, nil}
|
return Event{AltBackspace, 0, nil}
|
||||||
case ctrl, none, shift:
|
case none, shift:
|
||||||
return keyfn('h')
|
return Event{Backspace, 0, nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case tcell.KeyCtrlI:
|
case tcell.KeyCtrlI:
|
||||||
|
|||||||
@@ -110,21 +110,21 @@ func TestGetCharEventKey(t *testing.T) {
|
|||||||
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{AltDelete, 0, nil}},
|
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{AltDelete, 0, nil}},
|
||||||
{giveKey{tcell.KeyBackspace, 0, tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}},
|
{giveKey{tcell.KeyBackspace, 0, tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}},
|
||||||
{giveKey{tcell.KeyBackspace, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltBackspace, 0, nil}},
|
{giveKey{tcell.KeyBackspace, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltBackspace, 0, nil}},
|
||||||
{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{CtrlBackspace, 0, nil}}, // actual "Backspace" keystroke
|
{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.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
|
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{CtrlBackspace, 0, nil}}, // actual "Shift+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.ModAlt}, wantKey{CtrlAltBackspace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAltBackspace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAltBackspace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}}, // actual "Ctrl+H" 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{AltBackspace, 0, nil}}, // fabricated "Ctrl+Alt+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{CtrlBackspace, 0, nil}}, // actual "Ctrl+Shift+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{AltBackspace, 0, nil}}, // fabricated "Ctrl+Shift+Alt+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)
|
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||||
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
|
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const (
|
|||||||
CtrlE
|
CtrlE
|
||||||
CtrlF
|
CtrlF
|
||||||
CtrlG
|
CtrlG
|
||||||
CtrlBackspace
|
CtrlH
|
||||||
Tab
|
Tab
|
||||||
CtrlJ
|
CtrlJ
|
||||||
CtrlK
|
CtrlK
|
||||||
@@ -137,6 +137,7 @@ const (
|
|||||||
CtrlRight
|
CtrlRight
|
||||||
CtrlHome
|
CtrlHome
|
||||||
CtrlEnd
|
CtrlEnd
|
||||||
|
CtrlBackspace
|
||||||
CtrlDelete
|
CtrlDelete
|
||||||
CtrlPageUp
|
CtrlPageUp
|
||||||
CtrlPageDown
|
CtrlPageDown
|
||||||
@@ -455,6 +456,7 @@ type ColorTheme struct {
|
|||||||
PreviewBg ColorAttr
|
PreviewBg ColorAttr
|
||||||
DarkBg ColorAttr
|
DarkBg ColorAttr
|
||||||
Gutter ColorAttr
|
Gutter ColorAttr
|
||||||
|
AltGutter ColorAttr
|
||||||
Prompt ColorAttr
|
Prompt ColorAttr
|
||||||
InputBg ColorAttr
|
InputBg ColorAttr
|
||||||
InputBorder ColorAttr
|
InputBorder ColorAttr
|
||||||
@@ -825,6 +827,8 @@ var (
|
|||||||
ColCursor ColorPair
|
ColCursor ColorPair
|
||||||
ColCursorEmpty ColorPair
|
ColCursorEmpty ColorPair
|
||||||
ColCursorEmptyChar ColorPair
|
ColCursorEmptyChar ColorPair
|
||||||
|
ColAltCursorEmpty ColorPair
|
||||||
|
ColAltCursorEmptyChar ColorPair
|
||||||
ColMarker ColorPair
|
ColMarker ColorPair
|
||||||
ColSelected ColorPair
|
ColSelected ColorPair
|
||||||
ColSelectedMatch ColorPair
|
ColSelectedMatch ColorPair
|
||||||
@@ -890,6 +894,7 @@ func init() {
|
|||||||
PreviewFg: defaultColor,
|
PreviewFg: defaultColor,
|
||||||
PreviewBg: defaultColor,
|
PreviewBg: defaultColor,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
|
AltGutter: undefined,
|
||||||
PreviewBorder: defaultColor,
|
PreviewBorder: defaultColor,
|
||||||
PreviewScrollbar: defaultColor,
|
PreviewScrollbar: defaultColor,
|
||||||
PreviewLabel: defaultColor,
|
PreviewLabel: defaultColor,
|
||||||
@@ -942,6 +947,7 @@ func init() {
|
|||||||
PreviewFg: undefined,
|
PreviewFg: undefined,
|
||||||
PreviewBg: undefined,
|
PreviewBg: undefined,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
|
AltGutter: undefined,
|
||||||
PreviewBorder: undefined,
|
PreviewBorder: undefined,
|
||||||
PreviewScrollbar: undefined,
|
PreviewScrollbar: undefined,
|
||||||
PreviewLabel: undefined,
|
PreviewLabel: undefined,
|
||||||
@@ -990,6 +996,7 @@ func init() {
|
|||||||
PreviewFg: undefined,
|
PreviewFg: undefined,
|
||||||
PreviewBg: undefined,
|
PreviewBg: undefined,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
|
AltGutter: undefined,
|
||||||
PreviewBorder: undefined,
|
PreviewBorder: undefined,
|
||||||
PreviewScrollbar: undefined,
|
PreviewScrollbar: undefined,
|
||||||
PreviewLabel: undefined,
|
PreviewLabel: undefined,
|
||||||
@@ -1040,6 +1047,7 @@ func init() {
|
|||||||
PreviewFg: undefined,
|
PreviewFg: undefined,
|
||||||
PreviewBg: undefined,
|
PreviewBg: undefined,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
|
AltGutter: undefined,
|
||||||
PreviewBorder: undefined,
|
PreviewBorder: undefined,
|
||||||
PreviewScrollbar: undefined,
|
PreviewScrollbar: undefined,
|
||||||
PreviewLabel: undefined,
|
PreviewLabel: undefined,
|
||||||
@@ -1090,6 +1098,7 @@ func init() {
|
|||||||
PreviewFg: undefined,
|
PreviewFg: undefined,
|
||||||
PreviewBg: undefined,
|
PreviewBg: undefined,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
|
AltGutter: undefined,
|
||||||
PreviewBorder: undefined,
|
PreviewBorder: undefined,
|
||||||
PreviewScrollbar: undefined,
|
PreviewScrollbar: undefined,
|
||||||
PreviewLabel: undefined,
|
PreviewLabel: undefined,
|
||||||
@@ -1207,6 +1216,7 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, boldify bool, forceBlac
|
|||||||
gutter.Attr = Dim
|
gutter.Attr = Dim
|
||||||
}
|
}
|
||||||
theme.Gutter = o(theme.DarkBg, gutter)
|
theme.Gutter = o(theme.DarkBg, gutter)
|
||||||
|
theme.AltGutter = o(theme.Gutter, theme.AltGutter)
|
||||||
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
||||||
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
|
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
|
||||||
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
|
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
|
||||||
@@ -1276,6 +1286,8 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||||
ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)
|
ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)
|
||||||
|
ColAltCursorEmpty = pair(blank, theme.AltGutter)
|
||||||
|
ColAltCursorEmptyChar = pair(theme.AltGutter, theme.ListBg)
|
||||||
if theme.SelectedBg.Color != theme.ListBg.Color {
|
if theme.SelectedBg.Color != theme.ListBg.Color {
|
||||||
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
func TestAtExit(t *testing.T) {
|
func TestAtExit(t *testing.T) {
|
||||||
want := []int{3, 2, 1, 0}
|
want := []int{3, 2, 1, 0}
|
||||||
var called []int
|
var called []int
|
||||||
for i := 0; i < 4; i++ {
|
for i := range 4 {
|
||||||
n := i
|
n := i
|
||||||
AtExit(func() { called = append(called, n) })
|
AtExit(func() { called = append(called, n) })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func ToChars(bytes []byte) Chars {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runes := make([]rune, bytesUntil, len(bytes))
|
runes := make([]rune, bytesUntil, len(bytes))
|
||||||
for i := 0; i < bytesUntil; i++ {
|
for i := range bytesUntil {
|
||||||
runes[i] = rune(bytes[i])
|
runes[i] = rune(bytes[i])
|
||||||
}
|
}
|
||||||
for i := bytesUntil; i < len(bytes); {
|
for i := bytesUntil; i < len(bytes); {
|
||||||
@@ -259,7 +259,7 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
|
|||||||
lines = append(lines, text)
|
lines = append(lines, text)
|
||||||
} else {
|
} else {
|
||||||
from := 0
|
from := 0
|
||||||
for off := 0; off < len(text); off++ {
|
for off := range text {
|
||||||
if text[off] == '\n' {
|
if text[off] == '\n' {
|
||||||
lines = append(lines, text[from:off+1]) // Include '\n'
|
lines = append(lines, text[from:off+1]) // Include '\n'
|
||||||
from = off + 1
|
from = off + 1
|
||||||
|
|||||||
@@ -1190,6 +1190,61 @@ class TestCore < TestInteractive
|
|||||||
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
|
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_freeze_left_keep_right
|
||||||
|
tmux.send_keys %[seq 10000 | #{FZF} --read0 --delimiter "\n" --freeze-left 3 --keep-right --ellipsis XX --no-multi-line --bind space:toggle-multi-line], :Enter
|
||||||
|
tmux.until { |lines| assert_match(/^> 1␊2␊3XX.*10000␊$/, lines[-3]) }
|
||||||
|
tmux.send_keys '5'
|
||||||
|
tmux.until { |lines| assert_match(/^> 1␊2␊3␊4␊5␊.*XX$/, lines[-3]) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert lines.any_include?('> 1') }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert lines.any_include?('1␊2␊3␊4␊5␊') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_freeze_left_and_right
|
||||||
|
tmux.send_keys %[seq 10000 | tr "\n" ' ' | #{FZF} --freeze-left 3 --freeze-right 3 --ellipsis XX], :Enter
|
||||||
|
tmux.until { |lines| assert_match(/XX9998 9999 10000$/, lines[-3]) }
|
||||||
|
tmux.send_keys "'1000"
|
||||||
|
tmux.until { |lines| assert_match(/^> 1 2 3XX.*XX9998 9999 10000$/,lines[-3]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_freeze_left_and_right_delimiter
|
||||||
|
tmux.send_keys %[seq 10000 | tr "\n" ' ' | sed 's/ / , /g' | #{FZF} --freeze-left 3 --freeze-right 3 --ellipsis XX --delimiter ' , '], :Enter
|
||||||
|
tmux.until { |lines| assert_match(/XX, 9999 , 10000 ,$/, lines[-3]) }
|
||||||
|
tmux.send_keys "'1000"
|
||||||
|
tmux.until { |lines| assert_match(/^> 1 , 2 , 3 ,XX.*XX, 9999 , 10000 ,$/,lines[-3]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_freeze_right_exceed_range
|
||||||
|
tmux.send_keys %[seq 10000 | tr "\n" ' ' | #{FZF} --freeze-right 100000 --ellipsis XX], :Enter
|
||||||
|
['', "'1000"].each do |query|
|
||||||
|
tmux.send_keys query
|
||||||
|
tmux.until { |lines| assert lines.any_include?("> #{query}".strip) }
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_match(/ 9998 9999 10000$/, lines[-3])
|
||||||
|
assert_equal(1, lines[-3].scan('XX').size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_freeze_right_exceed_range_with_freeze_left
|
||||||
|
tmux.send_keys %[seq 10000 | tr "\n" ' ' | #{FZF} --freeze-left 3 --freeze-right 100000 --ellipsis XX], :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_match(/^> 1 2 3XX.*9998 9999 10000$/, lines[-3])
|
||||||
|
assert_equal(1, lines[-3].scan('XX').size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_freeze_right_with_ellipsis_and_scrolling
|
||||||
|
tmux.send_keys "{ seq 6; ruby -e 'print \"g\"*1000, \"\\n\"'; seq 8 100; } | #{FZF} --ellipsis='777' --freeze-right 1 --scroll-off 0 --bind a:offset-up", :Enter
|
||||||
|
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
||||||
|
tmux.send_keys(*Array.new(6) { :a })
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_match(/> 777g+$/, lines[-3])
|
||||||
|
assert_equal 1, lines.count { |l| l.end_with?('g') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_backward_eof
|
def test_backward_eof
|
||||||
tmux.send_keys "echo foo | #{FZF} --bind 'backward-eof:reload(seq 100)'", :Enter
|
tmux.send_keys "echo foo | #{FZF} --bind 'backward-eof:reload(seq 100)'", :Enter
|
||||||
tmux.until { |lines| lines.item_count == 1 && lines.match_count == 1 }
|
tmux.until { |lines| lines.item_count == 1 && lines.match_count == 1 }
|
||||||
|
|||||||
@@ -1215,6 +1215,15 @@ class TestLayout < TestInteractive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_header_and_footer_should_not_be_wider_than_list
|
||||||
|
tmux.send_keys %(WIDE=$(printf 'x%.0s' {1..1000}); (echo $WIDE; echo $WIDE) | fzf --header-lines 1 --style full --header-border bottom --header-lines-border top --ellipsis XX --header "$WIDE" --footer "$WIDE" --no-footer-border), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
matches = lines.filter_map { |line| line[/x+XX/] }
|
||||||
|
assert_equal 4, matches.length
|
||||||
|
assert_equal 1, matches.uniq.length
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_combinations
|
def test_combinations
|
||||||
skip unless ENV['LONGTEST']
|
skip unless ENV['LONGTEST']
|
||||||
|
|
||||||
|
|||||||
@@ -462,6 +462,84 @@ class TestZsh < TestBase
|
|||||||
tmux.send_keys 'C-c'
|
tmux.send_keys 'C-c'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Helper function to run test with Perl and again with Awk
|
||||||
|
def self.test_perl_and_awk(name, &block)
|
||||||
|
define_method("test_#{name}") do
|
||||||
|
instance_eval(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
define_method("test_#{name}_awk") do
|
||||||
|
tmux.send_keys "unset 'commands[perl]'", :Enter
|
||||||
|
tmux.prepare
|
||||||
|
# Verify perl is actually unset (0 = not found)
|
||||||
|
tmux.send_keys 'echo ${+commands[perl]}', :Enter
|
||||||
|
tmux.until { |lines| assert_equal '0', lines[-1] }
|
||||||
|
tmux.prepare
|
||||||
|
instance_eval(&block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare_ctrl_r_test
|
||||||
|
tmux.send_keys ':', :Enter
|
||||||
|
tmux.send_keys 'echo match-collision', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "line 1', :Enter, '2 line 2"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "bar', :Enter, 'foo"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "trailing_space "', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'cat <<EOF | wc -c', :Enter, 'qux thud', :Enter, 'EOF', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-l', 'C-r'
|
||||||
|
end
|
||||||
|
|
||||||
|
test_perl_and_awk 'ctrl_r_accept_or_print_query' do
|
||||||
|
set_var('FZF_CTRL_R_OPTS', '--bind enter:accept-or-print-query')
|
||||||
|
prepare_ctrl_r_test
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys '1 foobar'
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal '1 foobar', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
test_perl_and_awk 'ctrl_r_multiline_index_collision' do
|
||||||
|
# Leading number in multi-line history content is not confused with index
|
||||||
|
prepare_ctrl_r_test
|
||||||
|
tmux.send_keys "'line 1"
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal ['echo "line 1', '2 line 2"'], lines[-2..]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test_perl_and_awk 'ctrl_r_multi_selection' do
|
||||||
|
prepare_ctrl_r_test
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], '(3)' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal ['cat <<EOF | wc -c', 'qux thud', 'EOF', 'echo "trailing_space "', 'echo "bar', 'foo"'], lines[-6..]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test_perl_and_awk 'ctrl_r_no_multi_selection' do
|
||||||
|
set_var('FZF_CTRL_R_OPTS', '--no-multi')
|
||||||
|
prepare_ctrl_r_test
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
|
tmux.until { |lines| refute_includes lines[-2], '(3)' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal ['cat <<EOF | wc -c', 'qux thud', 'EOF'], lines[-3..]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestFish < TestBase
|
class TestFish < TestBase
|
||||||
|
|||||||
Reference in New Issue
Block a user