m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-16 07:13:48 -05:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Junegunn Choi
e8a39eeb0f Ignore invalid byte sequence in UTF-8 2024-07-29 20:24:35 +09:00
Junegunn Choi
c790ab2024 [bash] CTRL-R: Show timestamp and syntax highlighted command 2024-07-29 20:24:35 +09:00
60 changed files with 1788 additions and 4345 deletions

View File

@@ -36,7 +36,7 @@ jobs:
run: sudo apt-get install --yes zsh fish tmux run: sudo apt-get install --yes zsh fish tmux
- name: Install Ruby gems - name: Install Ruby gems
run: sudo gem install --no-document minitest:5.25.1 rubocop:1.65.0 rubocop-minitest:0.35.1 rubocop-performance:1.21.1 run: sudo gem install --no-document minitest:5.17.0 rubocop:1.43.0 rubocop-minitest:0.25.1 rubocop-performance:1.15.2
- name: Rubocop - name: Rubocop
run: rubocop --require rubocop-minitest --require rubocop-performance run: rubocop --require rubocop-minitest --require rubocop-performance

View File

@@ -7,4 +7,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: crate-ci/typos@v1.28.4 - uses: crate-ci/typos@v1.23.1

View File

@@ -6,7 +6,7 @@ Lint/ShadowingOuterLocalVariable:
Enabled: false Enabled: false
Style/MethodCallWithArgsParentheses: Style/MethodCallWithArgsParentheses:
Enabled: true Enabled: true
AllowedMethods: IgnoredMethods:
- assert - assert
- exit - exit
- paste - paste
@@ -15,7 +15,7 @@ Style/MethodCallWithArgsParentheses:
- refute - refute
- require - require
- send_keys - send_keys
AllowedPatterns: IgnoredPatterns:
- ^assert_ - ^assert_
- ^refute_ - ^refute_
Style/NumericPredicate: Style/NumericPredicate:

View File

@@ -128,7 +128,7 @@ fzf --height 70% --tmux 70%
You can also specify the position, width, and height of the popup window in You can also specify the position, width, and height of the popup window in
the following format: the following format:
* `[center|top|bottom|left|right][,SIZE[%]][,SIZE[%][,border-native]]` * `[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]`
```sh ```sh
# 100% width and 60% height # 100% width and 60% height

View File

@@ -1,204 +1,9 @@
CHANGELOG CHANGELOG
========= =========
0.58.0
------
_Release highlights: https://junegunn.github.io/fzf/releases/0.58.0/_
This version introduces three new border types, `--list-border`, `--input-border`, and `--header-border`, offering much greater flexibility for customizing the user interface.
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-4-borders.png" />
Also, fzf now offers "style presets" for quick customization, which can be activated using the `--style` option.
| Preset | Screenshot |
| :--- | :--- |
| `default` | <img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-style-default.png"/> |
| `full` | <img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-style-full.png"/> |
| `minimal` | <img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-style-minimal.png"/> |
- Style presets (#4160)
- `--style=full[:BORDER_STYLE]`
- `--style=default`
- `--style=minimal`
- Border and label for the list section (#4148)
- Options
- `--list-border[=STYLE]`
- `--list-label=LABEL`
- `--list-label-pos=COL[:bottom]`
- Colors
- `list-fg`
- `list-bg`
- `list-border`
- `list-label`
- Actions
- `change-list-label`
- `transform-list-label`
- Border and label for the input section (prompt line and info line) (#4154)
- Options
- `--input-border[=STYLE]`
- `--input-label=LABEL`
- `--input-label-pos=COL[:bottom]`
- Colors
- `input-fg` (`query`)
- `input-bg`
- `input-border`
- `input-label`
- Actions
- `change-input-label`
- `transform-input-label`
- Border and label for the header section (#4159)
- Options
- `--header-border[=STYLE]`
- `--header-label=LABEL`
- `--header-label-pos=COL[:bottom]`
- Colors
- `header-fg` (`header`)
- `header-bg`
- `header-border`
- `header-label`
- Actions
- `change-header-label`
- `transform-header-label`
- Added `--preview-border[=STYLE]` as short for `--preview-window=border[-STYLE]`
- Added new preview border style `line` which draws a single separator line between the preview window and the rest of the interface
- fzf will now render a dashed line (`┈┈`) in each `--gap` for better visual separation.
```sh
# All bash/zsh functions, highlighted
declare -f |
perl -0 -pe 's/^}\n/}\0/gm' |
bat --plain --language bash --color always |
fzf --read0 --ansi --layout reverse --multi --highlight-line --gap
```
* You can customize the line using `--gap-line[=STR]`.
- You can specify `border-native` to `--tmux` so that native tmux border is used instead of `--border`. This can be useful if you start a different program from inside the popup.
```sh
fzf --tmux border-native --bind 'enter:execute:less {}'
```
- Added `toggle-multi-line` action
- Added `toggle-hscroll` action
- Added `change-nth` action for dynamically changing the value of the `--nth` option
```sh
# Start with --nth 1, then 2, then 3, then back to the default, 1
echo 'foo foobar foobarbaz' | fzf --bind 'space:change-nth(2|3|)' --nth 1 -q foo
```
- `--nth` parts of each line can now be rendered in a different text style
```sh
# nth in a different style
ls -al | fzf --nth -1 --color nth:italic
ls -al | fzf --nth -1 --color nth:reverse
ls -al | fzf --nth -1 --color nth:reverse:bold
# Dim the other parts
ls -al | fzf --nth -1 --color nth:regular,fg:dim
# With 'change-nth'. The current nth option is exported as $FZF_NTH.
ps -ef | fzf --reverse --header-lines 1 --header-border bottom --input-border \
--color nth:regular,fg:dim \
--bind 'ctrl-n:change-nth(8..|1|2|3|4|5|6|7|)' \
--bind 'result:transform-prompt:echo "${FZF_NTH}> "'
```
- A single-character delimiter is now treated as a plain string delimiter rather than a regular expression delimiter, even if it's a regular expression meta-character.
- This means you can just write `--delimiter '|'` instead of escaping it as `--delimiter '\|'`
- Bug fixes
- Bug fixes and improvements in fish scripts (thanks to @bitraid)
0.57.0
------
- You can now resize the preview window by dragging the border
- Built-in walker improvements
- `--walker-root` can take multiple directory arguments. e.g. `--walker-root include src lib`
- `--walker-skip` can handle multi-component patterns. e.g. `--walker-skip target/build`
- Removed long processing delay when displaying images in the preview window
- `FZF_PREVIEW_*` environment variables are exported to all child processes (#4098)
- Bug fixes in fish scripts
0.56.3
------
- Bug fixes in zsh scripts
- fix(zsh): handle backtick trigger edge case (#4090)
- revert(zsh): remove 'fc -RI' call in the history widget (#4093)
- Thanks to @LangLangBart for the contributions
0.56.2
------
- Bug fixes
- Fixed abnormal scrolling behavior when `--wrap` is set (#4083)
- [zsh] Fixed warning message when `ksh_arrays` is set (#4084)
0.56.1
------
- Bug fixes and improvements
- Fixed a race condition which would cause fzf to present stale results after `reload` (#4070)
- `page-up` and `page-down` actions now work correctly with multi-line items (#4069)
- `{n}` is allowed in `SCROLL` expression in `--preview-window` (#4079)
- [zsh] Fixed regression in history loading with shared option (#4071)
- [zsh] Better command extraction in zsh completion (#4082)
- Thanks to @LangLangBart, @jaydee-coder, @alex-huff, and @vejkse for the contributions
0.56.0
------
- Added `--gap[=N]` option to display empty lines between items.
- This can be useful to visually separate adjacent multi-line items.
```sh
# All bash functions, highlighted
declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
bat --plain --language bash --color always |
fzf --read0 --ansi --reverse --multi --highlight-line --gap
```
- Or just to make the list easier to read. For single-line items, you probably want to set `--color gutter:-1` as well to hide the gutter.
```sh
fzf --info inline-right --gap --color gutter:-1
```
- Added `noinfo` option to `--preview-window` to hide the scroll indicator in the preview window
- Bug fixes
- Thanks to @LangLangBart, @akinomyoga, and @charlievieth for fixing the bugs
0.55.0
------
_Release highlights: https://junegunn.github.io/fzf/releases/0.55.0/_
- Added `exact-boundary-match` type to the search syntax. When a search term is single-quoted, fzf will search for the exact occurrences of the string with both ends at word boundaries.
```sh
fzf --query "'here'" << EOF
come here
not there
EOF
```
- [bash] Fuzzy path completion is enabled for all commands
- 1. If the default completion is not already set
- 2. And if the current bash supports `complete -D` option
- However, fuzzy completion for some commands can be "dynamically" disabled by the dynamic completion loader
- See the comment in `__fzf_default_completion` function for more information
- Comments are now allowed in `$FZF_DEFAULT_OPTS` and `$FZF_DEFAULT_OPTS_FILE`
```sh
export FZF_DEFAULT_OPTS='
# Layout options
--layout=reverse
--info=inline-right # Show info on the right side of the prompt line
# ...
'
```
- Hyperlinks (OSC 8) are now supported in the preview window and in the main window
```sh
printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>' | fzf --ansi
fzf --preview "printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>'"
```
- The default `--ellipsis` is now `··` instead of `..`.
- [vim] A spec can have `exit` callback that is called with the exit status of fzf
- This can be used to clean up temporary resources or restore the original state when fzf is closed without a selection
- Fixed `--tmux bottom` when the status line is not at the bottom
- Fixed extra scroll offset in multi-line mode (`--read0` or `--wrap`)
- Added fallback `ps` command for `kill` completion on Cygwin
0.54.3 0.54.3
------ ------
- Fixed incompatibility of adaptive height specification and 'start:reload' - Fixed incompatibility of adaptive height and 'start:reload'
```sh
# A regression in 0.54.0 would cause this to fail
fzf --height '~100%' --bind 'start:reload:seq 10'
```
- Environment variables are now available to `$FZF_DEFAULT_COMMAND` - Environment variables are now available to `$FZF_DEFAULT_COMMAND`
```sh ```sh
FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo

View File

@@ -8,5 +8,5 @@ RUN echo '. ~/.bashrc' >> ~/.bash_profile
RUN rm -f /etc/bash.bashrc RUN rm -f /etc/bash.bashrc
COPY . /fzf COPY . /fzf
RUN cd /fzf && make install && ./install --all RUN cd /fzf && make install && ./install --all
ENV LANG=C.UTF-8 ENV LANG C.UTF-8
CMD ["bash", "-ic", "tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]"] CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]

View File

@@ -1,3 +1,4 @@
SHELL := bash
GO ?= go GO ?= go
GOOS ?= $(shell $(GO) env GOOS) GOOS ?= $(shell $(GO) env GOOS)
@@ -13,7 +14,7 @@ endif
ifeq ($(VERSION),) ifeq ($(VERSION),)
$(error Not on git repository; cannot determine $$FZF_VERSION) $(error Not on git repository; cannot determine $$FZF_VERSION)
endif endif
VERSION_TRIM := $(shell echo $(VERSION) | sed "s/^v//; s/-.*//") VERSION_TRIM := $(shell sed "s/^v//; s/-.*//" <<< $(VERSION))
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM)) VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
ifdef FZF_REVISION ifdef FZF_REVISION
@@ -76,6 +77,7 @@ endif
all: target/$(BINARY) all: target/$(BINARY)
test: $(SOURCES) test: $(SOURCES)
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \ SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
github.com/junegunn/fzf/src \ github.com/junegunn/fzf/src \
github.com/junegunn/fzf/src/algo \ github.com/junegunn/fzf/src/algo \
@@ -85,10 +87,6 @@ test: $(SOURCES)
bench: bench:
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
lint: $(SOURCES) test/test_go.rb
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
rubocop --require rubocop-minitest --require rubocop-performance
install: bin/fzf install: bin/fzf
generate: generate:
@@ -186,4 +184,4 @@ update:
$(GO) get -u $(GO) get -u
$(GO) mod tidy $(GO) mod tidy
.PHONY: all generate build release test bench lint install clean docker docker-test update .PHONY: all generate build release test bench install clean docker docker-test update

View File

@@ -289,9 +289,8 @@ The following table summarizes the available options.
| `source` | string | External command to generate input to fzf (e.g. `find .`) | | `source` | string | External command to generate input to fzf (e.g. `find .`) |
| `source` | list | Vim list as input to fzf | | `source` | list | Vim list as input to fzf |
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) | | `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
| `sink` | funcref | Function to be called with each selected item | | `sink` | funcref | Reference to function to process each selected item |
| `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once | | `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once |
| `exit` | funcref | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130) |
| `options` | string/list | Options to fzf | | `options` | string/list | Options to fzf |
| `dir` | string | Working directory | | `dir` | string | Working directory |
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) | | `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |

115
README.md

File diff suppressed because one or more lines are too long

View File

@@ -9,24 +9,12 @@
# - https://iterm2.com/utilities/imgcat # - https://iterm2.com/utilities/imgcat
if [[ $# -ne 1 ]]; then if [[ $# -ne 1 ]]; then
>&2 echo "usage: $0 FILENAME[:LINENO][:IGNORED]" >&2 echo "usage: $0 FILENAME"
exit 1 exit 1
fi fi
file=${1/#\~\//$HOME/} file=${1/#\~\//$HOME/}
type=$(file --dereference --mime -- "$file")
center=0
if [[ ! -r $file ]]; then
if [[ $file =~ ^(.+):([0-9]+)\ *$ ]] && [[ -r ${BASH_REMATCH[1]} ]]; then
file=${BASH_REMATCH[1]}
center=${BASH_REMATCH[2]}
elif [[ $file =~ ^(.+):([0-9]+):[0-9]+\ *$ ]] && [[ -r ${BASH_REMATCH[1]} ]]; then
file=${BASH_REMATCH[1]}
center=${BASH_REMATCH[2]}
fi
fi
type=$(file --brief --dereference --mime -- "$file")
if [[ ! $type =~ image/ ]]; then if [[ ! $type =~ image/ ]]; then
if [[ $type =~ =binary ]]; then if [[ $type =~ =binary ]]; then
@@ -44,7 +32,7 @@ if [[ ! $type =~ image/ ]]; then
exit exit
fi fi
${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never --highlight-line="${center:-0}" -- "$file" ${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never -- "$file"
exit exit
fi fi

View File

@@ -19,9 +19,6 @@ term=""
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}") [[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}") [[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
tmux_32=$(awk '{print ($1 >= 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version >= 3.2")
help() { help() {
>&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS] >&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
@@ -97,19 +94,11 @@ while [[ $# -gt 0 ]]; do
opt="$opt ${arg:0:2}$size" opt="$opt ${arg:0:2}$size"
elif [[ "$size" =~ %$ ]]; then elif [[ "$size" =~ %$ ]]; then
size=${size:0:((${#size}-1))} size=${size:0:((${#size}-1))}
if [[ $tmux_32 = 1 ]]; then
if [[ -n "$swap" ]]; then if [[ -n "$swap" ]]; then
opt="$opt -l $(( 100 - size ))%" opt="$opt -l $(( 100 - size ))%"
else else
opt="$opt -l $size%" opt="$opt -l $size%"
fi fi
else
if [[ -n "$swap" ]]; then
opt="$opt -p $(( 100 - size ))"
else
opt="$opt -p $size"
fi
fi
else else
if [[ -n "$swap" ]]; then if [[ -n "$swap" ]]; then
if [[ "$arg" =~ ^.l ]]; then if [[ "$arg" =~ ^.l ]]; then
@@ -198,11 +187,12 @@ trap 'cleanup' EXIT
envs="export TERM=$TERM " envs="export TERM=$TERM "
if [[ "$opt" =~ "-E" ]]; then if [[ "$opt" =~ "-E" ]]; then
if [[ $tmux_version = 3.2 ]]; then tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS" if [[ $(awk '{print ($1 > 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version > 3.2") = 1 ]]; then
elif [[ $tmux_32 = 1 ]]; then
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS" FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
opt="-B $opt" opt="-B $opt"
elif [[ $tmux_version = 3.2 ]]; then
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
else else
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2 echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
exit 2 exit 2

View File

@@ -306,9 +306,8 @@ The following table summarizes the available options.
`source` | string | External command to generate input to fzf (e.g. `find .` ) `source` | string | External command to generate input to fzf (e.g. `find .` )
`source` | list | Vim list as input to fzf `source` | list | Vim list as input to fzf
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` ) `sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
`sink` | funcref | Function to be called with each selected item `sink` | funcref | Reference to function to process each selected item
`sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once `sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once
`exit` | funcref | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130)
`options` | string/list | Options to fzf `options` | string/list | Options to fzf
`dir` | string | Working directory `dir` | string | Working directory
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` ) `up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )

16
go.mod
View File

@@ -1,20 +1,20 @@
module github.com/junegunn/fzf module github.com/junegunn/fzf
require ( require (
github.com/charlievieth/fastwalk v1.0.9 github.com/charlievieth/fastwalk v1.0.8
github.com/gdamore/tcell/v2 v2.8.1 github.com/gdamore/tcell/v2 v2.7.4
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-shellwords v1.0.12
github.com/rivo/uniseg v0.4.7 github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.29.0 golang.org/x/sys v0.22.0
golang.org/x/term v0.28.0 golang.org/x/term v0.22.0
) )
require ( require (
github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.14.0 // indirect
) )
go 1.20 go 1.20

58
go.sum
View File

@@ -1,18 +1,17 @@
github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0= github.com/charlievieth/fastwalk v1.0.8 h1:uaoH6cAKSk73aK7aKXqs0+bL+J3Txzd3NGH8tRXgHko=
github.com/charlievieth/fastwalk v1.0.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI= github.com/charlievieth/fastwalk v1.0.8/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU= github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw= github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97 h1:rqzLixVo1c/GQW6px9j1xQmlvQIn+lf/V6M1UQ7IFzw=
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -20,29 +19,15 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -50,36 +35,23 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

35
install
View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.58.0 version=0.54.2
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -83,7 +83,7 @@ ask() {
check_binary() { check_binary() {
echo -n " - Checking fzf executable ... " echo -n " - Checking fzf executable ... "
local output local output
output=$(FZF_DEFAULT_OPTS= "$fzf_base"/bin/fzf --version 2>&1) output=$("$fzf_base"/bin/fzf --version 2>&1)
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Error: $output" echo "Error: $output"
binary_error="Invalid binary" binary_error="Invalid binary"
@@ -295,44 +295,35 @@ EOF
fi fi
append_line() { append_line() {
local update line file pat lines set -e
local update line file pat lno
update="$1" update="$1"
line="$2" line="$2"
file="$3" file="$3"
pat="${4:-}" pat="${4:-}"
lines="" lno=""
echo "Update $file:" echo "Update $file:"
echo " - $line" echo " - $line"
if [ -f "$file" ]; then if [ -f "$file" ]; then
if [ $# -lt 4 ]; then if [ $# -lt 4 ]; then
lines=$(\grep -nF "$line" "$file") lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
else else
lines=$(\grep -nF "$pat" "$file") lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
fi fi
fi fi
if [ -n "$lno" ]; then
if [ -n "$lines" ]; then echo " - Already exists: line #$lno"
echo " - Already exists:" else
sed 's/^/ Line /' <<< "$lines" if [ $update -eq 1 ]; then
update=0
if ! \grep -qv "^[0-9]*:[[:space:]]*#" <<< "$lines" ; then
echo " - But they all seem to be commented"
ask " - Continue modifying $file?"
update=$?
fi
fi
set -e
if [ "$update" -eq 1 ]; then
[ -f "$file" ] && echo >> "$file" [ -f "$file" ] && echo >> "$file"
echo "$line" >> "$file" echo "$line" >> "$file"
echo " + Added" echo " + Added"
else else
echo " ~ Skipped" echo " ~ Skipped"
fi fi
fi
echo echo
set +e set +e
} }

View File

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

View File

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

View File

@@ -21,13 +21,13 @@ 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 "Jan 2025" "fzf 0.58.0" "fzf\-tmux - open fzf in tmux split pane" .TH fzf\-tmux 1 "Jul 2024" "fzf 0.54.2" "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
.SH SYNOPSIS .SH SYNOPSIS
.B fzf\-tmux [\fILAYOUT OPTIONS\fR] [\-\-] [\fIFZF OPTIONS\fR] .B fzf\-tmux [LAYOUT OPTIONS] [\-\-] [FZF OPTIONS]
.SH DESCRIPTION .SH DESCRIPTION
fzf\-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in fzf\-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in

File diff suppressed because it is too large Load Diff

View File

@@ -198,7 +198,6 @@ function! s:compare_binary_versions(a, b)
return s:compare_versions(s:get_version(a:a), s:get_version(a:b)) return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
endfunction endfunction
let s:min_version = '0.53.0'
let s:checked = {} let s:checked = {}
function! fzf#exec(...) function! fzf#exec(...)
if !exists('s:exec') if !exists('s:exec')
@@ -226,11 +225,7 @@ function! fzf#exec(...)
let s:exec = binaries[-1] let s:exec = binaries[-1]
endif endif
let min_version = s:min_version if a:0 && !has_key(s:checked, a:1)
if a:0 && s:compare_versions(a:1, min_version) > 0
let min_version = a:1
endif
if !has_key(s:checked, min_version)
let fzf_version = s:get_version(s:exec) let fzf_version = s:get_version(s:exec)
if empty(fzf_version) if empty(fzf_version)
let message = printf('Failed to run "%s --version"', s:exec) let message = printf('Failed to run "%s --version"', s:exec)
@@ -238,17 +233,17 @@ function! fzf#exec(...)
throw message throw message
end end
if s:compare_versions(fzf_version, min_version) >= 0 if s:compare_versions(fzf_version, a:1) >= 0
let s:checked[min_version] = 1 let s:checked[a:1] = 1
return s:exec return s:exec
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', min_version, fzf_version)) =~? '^y' elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', a:1, fzf_version)) =~? '^y'
let s:versions = {} let s:versions = {}
unlet s:exec unlet s:exec
redraw redraw
call fzf#install() call fzf#install()
return fzf#exec(min_version, 1) return fzf#exec(a:1, 1)
else else
throw printf('You need to upgrade fzf (required: %s or above)', min_version) throw printf('You need to upgrade fzf (required: %s or above)', a:1)
endif endif
endif endif
@@ -670,17 +665,21 @@ else
let s:launcher = function('s:xterm_launcher') let s:launcher = function('s:xterm_launcher')
endif endif
function! s:exit_handler(dict, code, command, ...) function! s:exit_handler(code, command, ...)
if has_key(a:dict, 'exit') if a:code == 130
call a:dict.exit(a:code) return 0
endif elseif has('nvim') && a:code == 129
if a:code == 2 " When deleting the terminal buffer while fzf is still running,
" Nvim sends SIGHUP.
return 0
elseif a:code > 1
call s:error('Error running ' . a:command) call s:error('Error running ' . a:command)
if !empty(a:000) if !empty(a:000)
sleep sleep
endif endif
return 0
endif endif
return a:code return 1
endfunction endfunction
function! s:execute(dict, command, use_height, temps) abort function! s:execute(dict, command, use_height, temps) abort
@@ -732,7 +731,7 @@ function! s:execute(dict, command, use_height, temps) abort
let exit_status = v:shell_error let exit_status = v:shell_error
redraw! redraw!
let lines = s:collect(a:temps) let lines = s:collect(a:temps)
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : [] return s:exit_handler(exit_status, command) ? lines : []
endfunction endfunction
function! s:execute_tmux(dict, command, temps) abort function! s:execute_tmux(dict, command, temps) abort
@@ -747,7 +746,7 @@ function! s:execute_tmux(dict, command, temps) abort
let exit_status = v:shell_error let exit_status = v:shell_error
redraw! redraw!
let lines = s:collect(a:temps) let lines = s:collect(a:temps)
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : [] return s:exit_handler(exit_status, command) ? lines : []
endfunction endfunction
function! s:calc_size(max, val, dict) function! s:calc_size(max, val, dict)
@@ -913,7 +912,7 @@ function! s:execute_term(dict, command, temps) abort
endif endif
let lines = s:collect(self.temps) let lines = s:collect(self.temps)
if s:exit_handler(self.dict, a:code, self.command, 1) >= 2 if !s:exit_handler(a:code, self.command, 1)
return return
endif endif

View File

@@ -264,7 +264,6 @@ _fzf_handle_dynamic_completion() {
# _completion_loader may not have updated completion for the command # _completion_loader may not have updated completion for the command
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null) __fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
__fzf_orig_completion_get_orig_func "$cmd" || ret=1
# Update orig_complete by _fzf_orig_completion entry # Update orig_complete by _fzf_orig_completion entry
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] && [[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
@@ -290,7 +289,7 @@ __fzf_generic_path_completion() {
fi fi
COMPREPLY=() COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
[[ $COMP_CWORD -ge 0 ]] && cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
base=${cur:0:${#cur}-${#trigger}} base=${cur:0:${#cur}-${#trigger}}
eval "base=$base" 2> /dev/null || return eval "base=$base" 2> /dev/null || return
@@ -377,7 +376,7 @@ _fzf_complete() {
selected=$( selected=$(
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
FZF_DEFAULT_OPTS_FILE='' \ FZF_DEFAULT_OPTS_FILE='' \
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | eval "$post" | command tr '\n' ' ') __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace" selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [[ -n "$selected" ]]; then if [[ -n "$selected" ]]; then
COMPREPLY=("$selected") COMPREPLY=("$selected")
@@ -411,8 +410,7 @@ _fzf_complete_kill() {
_fzf_proc_completion() { _fzf_proc_completion() {
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <( _fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
command ps -eo user,pid,ppid,start,time,command 2> /dev/null || command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox command ps -eo user,pid,ppid,time,args # For BusyBox
command ps --everyone --full --windows # For cygwin
) )
} }
@@ -482,36 +480,10 @@ complete -o default -F _fzf_opts_completion fzf
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch. # fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
complete -o default -F _fzf_opts_completion fzf-tmux complete -o default -F _fzf_opts_completion fzf-tmux
# Default path completion
__fzf_default_completion() {
__fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
# Dynamic completion loader has updated the completion for the command
if [[ $? -eq 124 ]]; then
# We trigger _fzf_setup_completion so that fuzzy completion for the command
# still works. However, loader can update the completion for multiple
# commands at once, and fuzzy completion will no longer work for those
# other commands. e.g. pytest -> py.test, pytest-2, pytest-3, etc
_fzf_setup_completion path "$1"
return 124
fi
}
# Set fuzzy path completion as the default completion for all commands.
# We can't set up default completion,
# 1. if it's already set up by another script
# 2. or if the current version of bash doesn't support -D option
complete | command grep -q __fzf_default_completion ||
complete | command grep -- '-D$' | command grep -qv _comp_complete_load ||
complete -D -F __fzf_default_completion -o default -o bashdefault 2> /dev/null
d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}" d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}"
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are # NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
# undocumented and subject to change in the future. # undocumented and subject to change in the future.
#
# NOTE: Although we have default completion, we still need to set up completion
# for each command in case they already have completion set up by another script.
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-" a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
awk bat cat code diff diff3 awk bat cat code diff diff3
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java emacs emacsclient ex file ftp g++ gcc gvim head hg hx java

View File

@@ -120,18 +120,25 @@ __fzf_comprun() {
fi fi
} }
# Extract the name of the command. e.g. ls; foo=1 ssh **<tab> # Extract the name of the command. e.g. foo=1 bar baz**<tab>
__fzf_extract_command() { __fzf_extract_command() {
# Control completion with the "compstate" parameter, insert and list nothing local token tokens
compstate[insert]= tokens=(${(z)1})
compstate[list]= for token in $tokens; do
cmd_word="${(Q)words[1]}" token=${(Q)token}
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
echo "$token"
return
fi
done
echo "${tokens[1]}"
} }
__fzf_generic_path_completion() { __fzf_generic_path_completion() {
local base lbuf compgen fzf_opts suffix tail dir leftover matches local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches
base=$1 base=$1
lbuf=$2 lbuf=$2
cmd=$(__fzf_extract_command "$lbuf")
compgen=$3 compgen=$3
fzf_opts=$4 fzf_opts=$4
suffix=$5 suffix=$5
@@ -154,7 +161,7 @@ __fzf_generic_path_completion() {
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}") FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
if declare -f "$compgen" > /dev/null; then if declare -f "$compgen" > /dev/null; then
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
else else
if [[ $compgen =~ dir ]]; then if [[ $compgen =~ dir ]]; then
walker=dir,follow walker=dir,follow
@@ -163,7 +170,7 @@ __fzf_generic_path_completion() {
walker=file,dir,follow,hidden walker=file,dir,follow,hidden
rest=${FZF_COMPLETION_PATH_OPTS-} rest=${FZF_COMPLETION_PATH_OPTS-}
fi fi
__fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
fi | while read -r item; do fi | while read -r item; do
item="${item%$suffix}$suffix" item="${item%$suffix}$suffix"
echo -n -E "${(q)item} " echo -n -E "${(q)item} "
@@ -220,9 +227,10 @@ _fzf_complete() {
rest=("$@") rest=("$@")
fi fi
local fifo lbuf matches post local fifo lbuf cmd matches post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$" fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
lbuf=${rest[0]} lbuf=${rest[0]}
cmd=$(__fzf_extract_command "$lbuf")
post="${funcstack[1]}_post" post="${funcstack[1]}_post"
type $post > /dev/null 2>&1 || post=cat type $post > /dev/null 2>&1 || post=cat
@@ -230,7 +238,7 @@ _fzf_complete() {
matches=$( matches=$(
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
FZF_DEFAULT_OPTS_FILE='' \ FZF_DEFAULT_OPTS_FILE='' \
__fzf_comprun "$cmd_word" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ') __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi
@@ -292,8 +300,7 @@ _fzf_complete_unalias() {
_fzf_complete_kill() { _fzf_complete_kill() {
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <( _fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
command ps -eo user,pid,ppid,start,time,command 2> /dev/null || command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox command ps -eo user,pid,ppid,time,args # For BusyBox
command ps --everyone --full --windows # For cygwin
) )
} }
@@ -302,7 +309,7 @@ _fzf_complete_kill_post() {
} }
fzf-completion() { fzf-completion() {
local tokens prefix trigger tail matches lbuf d_cmds cursor_pos cmd_word local tokens cmd prefix trigger tail matches lbuf d_cmds
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
# http://zsh.sourceforge.net/FAQ/zshfaq03.html # http://zsh.sourceforge.net/FAQ/zshfaq03.html
@@ -313,9 +320,11 @@ fzf-completion() {
return return
fi fi
cmd=$(__fzf_extract_command "$LBUFFER")
# Explicitly allow for empty trigger. # Explicitly allow for empty trigger.
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
[[ -z $trigger && ${LBUFFER[-1]} == ' ' ]] && tokens+=("") [ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
# When the trigger starts with ';', it becomes a separate token # When the trigger starts with ';', it becomes a separate token
if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
@@ -330,37 +339,16 @@ fzf-completion() {
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}) d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})
{
cursor_pos=$CURSOR
# Move the cursor before the trigger to preserve word array elements when
# trigger chars like ';' or '`' would otherwise reset the 'words' array.
CURSOR=$((cursor_pos - ${#trigger} - 1))
# Check if at least one completion system (old or new) is active.
# If at least one user-defined completion widget is detected, nothing will
# be completed if neither the old nor the new completion system is enabled.
# In such cases, the 'zsh/compctl' module is loaded as a fallback.
if ! zmodload -F zsh/parameter p:functions 2>/dev/null || ! (( ${+functions[compdef]} )); then
zmodload -F zsh/compctl 2>/dev/null
fi
# Create a completion widget to access the 'words' array (man zshcompwid)
zle -C __fzf_extract_command .complete-word __fzf_extract_command
zle __fzf_extract_command
} always {
CURSOR=$cursor_pos
# Delete the completion widget
zle -D __fzf_extract_command 2>/dev/null
}
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}} [ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
return return
fi fi
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}} [ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
if eval "noglob type _fzf_complete_${cmd_word} >/dev/null"; then if eval "type _fzf_complete_${cmd} > /dev/null"; then
prefix="$prefix" eval _fzf_complete_${cmd_word} ${(q)lbuf} prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
zle reset-prompt zle reset-prompt
elif [ ${d_cmds[(i)$cmd_word]} -le ${#d_cmds} ]; then elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
_fzf_dir_completion "$prefix" "$lbuf" _fzf_dir_completion "$prefix" "$lbuf"
else else
_fzf_path_completion "$prefix" "$lbuf" _fzf_path_completion "$prefix" "$lbuf"
@@ -377,7 +365,6 @@ fzf-completion() {
unset binding unset binding
} }
# Normal widget
zle -N fzf-completion zle -N fzf-completion
bindkey '^I' fzf-completion bindkey '^I' fzf-completion
fi fi

View File

@@ -54,7 +54,45 @@ __fzf_cd__() {
) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)" ) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)"
} }
if command -v perl > /dev/null; then if command -v ruby > /dev/null; then
__fzf_history__() {
local n output
builtin history -w /tmp/fzf-bash-history
output=$(
ruby -e '
fmt = begin
require "rouge"
formatter = Rouge::Formatters::Terminal256.new(Rouge::Themes::Monokai.new)
lexer = Rouge::Lexers::Shell.new
lambda { |c| formatter.format(lexer.lex(c)) }
rescue LoadError
lambda { |c| c }
end
h = {}
i = 0
File.read("/tmp/fzf-bash-history").encode!("UTF-8", "UTF-8", :invalid => :replace).scan(/^#([0-9]+)$\n(.*?)\n(?=^#[0-9]+$|\z)/m) do |t, c|
next if c.empty?
h.delete(c)
h[c] = [i += 1, t]
end
h.to_a.reverse.each do |c, it|
i, t = it
print "\x1b[33m#{i}\t\x1b[32m#{Time.at(t.to_i).strftime(%[%F %T])}\x1b[m "
print fmt[c.gsub("\n", "\n\t")]
print "\0"
end
' | FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n1,4.. --ansi --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"$'\t'"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE" --bind 'enter:become:echo {4..}'
) || return
READLINE_LINE=$(command perl -pe 's/^\d*\t//' <<< "$output")
if [[ -z "$READLINE_POINT" ]]; then
echo "$READLINE_LINE"
else
READLINE_POINT=0x7fffffff
fi
}
elif command -v perl > /dev/null; then
__fzf_history__() { __fzf_history__() {
local output script local output script
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; s/\n/\n\t/gm; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++' script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; s/\n/\n\t/gm; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'

View File

@@ -11,6 +11,8 @@
# - $FZF_ALT_C_COMMAND # - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS # - $FZF_ALT_C_OPTS
status is-interactive; or exit 0
# Key bindings # Key bindings
# ------------ # ------------
@@ -21,7 +23,7 @@ function fzf_key_bindings
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS # $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1] echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1]
test -r "$FZF_DEFAULT_OPTS_FILE"; and string collect -N -- <$FZF_DEFAULT_OPTS_FILE command cat "$FZF_DEFAULT_OPTS_FILE" 2> /dev/null
echo $FZF_DEFAULT_OPTS $argv[2] echo $FZF_DEFAULT_OPTS $argv[2]
end end
@@ -31,16 +33,15 @@ function fzf_key_bindings
set -lx dir $commandline[1] set -lx dir $commandline[1]
set -l fzf_query $commandline[2] set -l fzf_query $commandline[2]
set -l prefix $commandline[3] set -l prefix $commandline[3]
set -l result
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_CTRL_T_OPTS") set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_CTRL_T_OPTS")
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND" set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
set -lx FZF_DEFAULT_OPTS_FILE '' set -lx FZF_DEFAULT_OPTS_FILE ''
set result (eval (__fzfcmd) -m --query=$fzf_query) eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
end end
if test -z "$result" if [ -z "$result" ]
commandline -f repaint commandline -f repaint
return return
else else
@@ -49,7 +50,7 @@ function fzf_key_bindings
end end
for i in $result for i in $result
commandline -it -- $prefix commandline -it -- $prefix
commandline -it -- (string escape -- $i) commandline -it -- (string escape $i)
commandline -it -- ' ' commandline -it -- ' '
end end
commandline -f repaint commandline -f repaint
@@ -58,25 +59,33 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history" function fzf-history-widget -d "Show command history"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
# merge history from other sessions before searching set -l FISH_MAJOR (echo $version | cut -f1 -d.)
test -z "$fish_private_mode"; and builtin history merge set -l FISH_MINOR (echo $version | cut -f2 -d.)
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line +m $FZF_CTRL_R_OPTS") # merge history from other sessions before searching
set -lx FZF_DEFAULT_OPTS_FILE '' if test -z "$fish_private_mode"
set -lx FZF_DEFAULT_COMMAND builtin history merge
string match -q -r -- '/fish$' $SHELL; or set -lx SHELL (type -p fish)
if type -q perl
set -a FZF_DEFAULT_OPTS '--tac'
set FZF_DEFAULT_COMMAND 'builtin history -z --reverse | command perl -0 -pe \'s/^/$.\t/g; s/\n/\n\t/gm\''
else
set FZF_DEFAULT_COMMAND \
'set -l h (builtin history -z --reverse | string split0);' \
'for i in (seq (count $h) -1 1);' \
'string join0 -- $i\t(string replace -a -- \n \n\t $h[$i] | string collect);' \
'end'
end end
set -l result (eval "$FZF_DEFAULT_COMMAND | $(__fzfcmd) --read0 --print0 -q (commandline) --bind='enter:become:string replace -a -- \n\t \n {2..} | string collect'")
# history's -z flag is needed for multi-line support.
# history's -z flag was added in fish 2.4.0, so don't use it for versions
# before 2.4.0.
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
if type -P perl > /dev/null 2>&1
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
set -lx FZF_DEFAULT_OPTS_FILE ''
builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | command perl -pe 's/^\d*\t//' | read -lz result
and commandline -- $result and commandline -- $result
else
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
set -lx FZF_DEFAULT_OPTS_FILE ''
builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
and commandline -- $result
end
else
builtin history | eval (__fzfcmd) -q '(commandline)' | read -l result
and commandline -- $result
end
end end
commandline -f repaint commandline -f repaint
end end
@@ -89,12 +98,12 @@ function fzf_key_bindings
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_ALT_C_OPTS") set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_ALT_C_OPTS")
set -lx FZF_DEFAULT_OPTS_FILE '' set -lx FZF_DEFAULT_OPTS_FILE ''
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND" set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
set -l result (eval (__fzfcmd) +m --query=$fzf_query) eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if test -n "$result" if [ -n "$result" ]
cd -- $result cd -- $result
# Remove last token from commandline. # Remove last token from commandline.
@@ -109,9 +118,9 @@ function fzf_key_bindings
function __fzfcmd function __fzfcmd
test -n "$FZF_TMUX"; or set FZF_TMUX 0 test -n "$FZF_TMUX"; or set FZF_TMUX 0
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
if test -n "$FZF_TMUX_OPTS" if [ -n "$FZF_TMUX_OPTS" ]
echo "fzf-tmux $FZF_TMUX_OPTS -- " echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if test "$FZF_TMUX" = "1" else if [ $FZF_TMUX -eq 1 ]
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- " echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else else
echo "fzf" echo "fzf"
@@ -126,6 +135,7 @@ function fzf_key_bindings
bind \ec fzf-cd-widget bind \ec fzf-cd-widget
end end
if bind -M insert > /dev/null 2>&1
bind -M insert \cr fzf-history-widget bind -M insert \cr fzf-history-widget
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND" if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind -M insert \ct fzf-file-widget bind -M insert \ct fzf-file-widget
@@ -133,6 +143,7 @@ function fzf_key_bindings
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND" if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
bind -M insert \ec fzf-cd-widget bind -M insert \ec fzf-cd-widget
end end
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix' function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
set -l commandline (commandline -t) set -l commandline (commandline -t)
@@ -141,53 +152,40 @@ function fzf_key_bindings
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline) set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
set commandline (string replace -- "$prefix" '' $commandline) set commandline (string replace -- "$prefix" '' $commandline)
# Enable home directory expansion of leading ~/
set commandline (string replace -r -- '^~/' '\$HOME/' $commandline)
# escape special characters, except for the $ sign of valid variable names,
# so that after eval, the original string is returned, but with the
# variable names replaced by their values.
set commandline (string escape -n -- $commandline)
set commandline (string replace -r -a -- '\x5c\$(?=[\w])' '\$' $commandline)
# eval is used to do shell expansion on paths # eval is used to do shell expansion on paths
eval set commandline $commandline eval set commandline $commandline
# Combine multiple consecutive slashes into one if [ -z $commandline ]
set commandline (string replace -r -a -- '/+' '/' $commandline)
if test -z "$commandline"
# Default to current directory with no --query # Default to current directory with no --query
set dir '.' set dir '.'
set fzf_query '' set fzf_query ''
else else
set dir (__fzf_get_dir $commandline) set dir (__fzf_get_dir $commandline)
# BUG: on combined expressions, if a left argument is a single `!`, the if [ "$dir" = "." -a (string sub -l 1 -- $commandline) != '.' ]
# builtin test command of fish will treat it as the ! operator. To
# overcome this, have the variable parts on the right.
if test "." = "$dir" -a "./" != (string sub -l 2 -- $commandline)
# if $dir is "." but commandline is not a relative path, this means no file path found # if $dir is "." but commandline is not a relative path, this means no file path found
set fzf_query $commandline set fzf_query $commandline
else else
# Also remove trailing slash after dir, to "split" input properly # Also remove trailing slash after dir, to "split" input properly
set fzf_query (string replace -r -- "^$dir/?" '' $commandline) set fzf_query (string replace -r "^$dir/?" -- '' "$commandline")
end end
end end
echo (string escape -- $dir) echo $dir
echo (string escape -- $fzf_query) echo $fzf_query
echo $prefix echo $prefix
end end
function __fzf_get_dir -d 'Find the longest existing filepath from input string' function __fzf_get_dir -d 'Find the longest existing filepath from input string'
set dir $argv set dir $argv
# Strip trailing slash, unless $dir is root dir (/) # Strip all trailing slashes. Ignore if $dir is root dir (/)
set dir (string replace -r -- '(?<!^)/$' '' $dir) if [ (string length -- $dir) -gt 1 ]
set dir (string replace -r '/*$' -- '' $dir)
end
# Iteratively check if dir exists and strip tail end of path # Iteratively check if dir exists and strip tail end of path
while test ! -d "$dir" while [ ! -d "$dir" ]
# If path is absolute, this can keep going until ends up at / # If path is absolute, this can keep going until ends up at /
# If path is relative, this can keep going until entire input is consumed, dirname returns "." # If path is relative, this can keep going until entire input is consumed, dirname returns "."
set dir (dirname -- "$dir") set dir (dirname -- "$dir")

View File

@@ -108,10 +108,9 @@ fi
fzf-history-widget() { fzf-history-widget() {
local selected local selected
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
# Ensure the module is loaded if not already, and the required features, such # Ensure the associative history array, which maps event numbers to the full
# as the associative 'history' array, which maps event numbers to full history # history lines, is loaded, and that 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: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 --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 --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \

View File

@@ -25,116 +25,107 @@ func _() {
_ = x[actBackwardWord-14] _ = x[actBackwardWord-14]
_ = x[actCancel-15] _ = x[actCancel-15]
_ = x[actChangeBorderLabel-16] _ = x[actChangeBorderLabel-16]
_ = x[actChangeListLabel-17] _ = x[actChangeHeader-17]
_ = x[actChangeInputLabel-18] _ = x[actChangeMulti-18]
_ = x[actChangeHeader-19] _ = x[actChangePreviewLabel-19]
_ = x[actChangeHeaderLabel-20] _ = x[actChangePrompt-20]
_ = x[actChangeMulti-21] _ = x[actChangeQuery-21]
_ = x[actChangePreviewLabel-22] _ = x[actClearScreen-22]
_ = x[actChangePrompt-23] _ = x[actClearQuery-23]
_ = x[actChangeQuery-24] _ = x[actClearSelection-24]
_ = x[actChangeNth-25] _ = x[actClose-25]
_ = x[actClearScreen-26] _ = x[actDeleteChar-26]
_ = x[actClearQuery-27] _ = x[actDeleteCharEof-27]
_ = x[actClearSelection-28] _ = x[actEndOfLine-28]
_ = x[actClose-29] _ = x[actFatal-29]
_ = x[actDeleteChar-30] _ = x[actForwardChar-30]
_ = x[actDeleteCharEof-31] _ = x[actForwardWord-31]
_ = x[actEndOfLine-32] _ = x[actKillLine-32]
_ = x[actFatal-33] _ = x[actKillWord-33]
_ = x[actForwardChar-34] _ = x[actUnixLineDiscard-34]
_ = x[actForwardWord-35] _ = x[actUnixWordRubout-35]
_ = x[actKillLine-36] _ = x[actYank-36]
_ = x[actKillWord-37] _ = x[actBackwardKillWord-37]
_ = x[actUnixLineDiscard-38] _ = x[actSelectAll-38]
_ = x[actUnixWordRubout-39] _ = x[actDeselectAll-39]
_ = x[actYank-40] _ = x[actToggle-40]
_ = x[actBackwardKillWord-41] _ = x[actToggleSearch-41]
_ = x[actSelectAll-42] _ = x[actToggleAll-42]
_ = x[actDeselectAll-43] _ = x[actToggleDown-43]
_ = x[actToggle-44] _ = x[actToggleUp-44]
_ = x[actToggleSearch-45] _ = x[actToggleIn-45]
_ = x[actToggleAll-46] _ = x[actToggleOut-46]
_ = x[actToggleDown-47] _ = x[actToggleTrack-47]
_ = x[actToggleUp-48] _ = x[actToggleTrackCurrent-48]
_ = x[actToggleIn-49] _ = x[actToggleHeader-49]
_ = x[actToggleOut-50] _ = x[actToggleWrap-50]
_ = x[actToggleTrack-51] _ = x[actTrackCurrent-51]
_ = x[actToggleTrackCurrent-52] _ = x[actUntrackCurrent-52]
_ = x[actToggleHeader-53] _ = x[actDown-53]
_ = x[actToggleWrap-54] _ = x[actUp-54]
_ = x[actToggleMultiLine-55] _ = x[actPageUp-55]
_ = x[actToggleHscroll-56] _ = x[actPageDown-56]
_ = x[actTrackCurrent-57] _ = x[actPosition-57]
_ = x[actUntrackCurrent-58] _ = x[actHalfPageUp-58]
_ = x[actDown-59] _ = x[actHalfPageDown-59]
_ = x[actUp-60] _ = x[actOffsetUp-60]
_ = x[actPageUp-61] _ = x[actOffsetDown-61]
_ = x[actPageDown-62] _ = x[actOffsetMiddle-62]
_ = x[actPosition-63] _ = x[actJump-63]
_ = x[actHalfPageUp-64] _ = x[actJumpAccept-64]
_ = x[actHalfPageDown-65] _ = x[actPrintQuery-65]
_ = x[actOffsetUp-66] _ = x[actRefreshPreview-66]
_ = x[actOffsetDown-67] _ = x[actReplaceQuery-67]
_ = x[actOffsetMiddle-68] _ = x[actToggleSort-68]
_ = x[actJump-69] _ = x[actShowPreview-69]
_ = x[actJumpAccept-70] _ = x[actHidePreview-70]
_ = x[actPrintQuery-71] _ = x[actTogglePreview-71]
_ = x[actRefreshPreview-72] _ = x[actTogglePreviewWrap-72]
_ = x[actReplaceQuery-73] _ = x[actTransform-73]
_ = x[actToggleSort-74] _ = x[actTransformBorderLabel-74]
_ = x[actShowPreview-75] _ = x[actTransformHeader-75]
_ = x[actHidePreview-76] _ = x[actTransformPreviewLabel-76]
_ = x[actTogglePreview-77] _ = x[actTransformPrompt-77]
_ = x[actTogglePreviewWrap-78] _ = x[actTransformQuery-78]
_ = x[actTransform-79] _ = x[actPreview-79]
_ = x[actTransformBorderLabel-80] _ = x[actChangePreview-80]
_ = x[actTransformListLabel-81] _ = x[actChangePreviewWindow-81]
_ = x[actTransformInputLabel-82] _ = x[actPreviewTop-82]
_ = x[actTransformHeader-83] _ = x[actPreviewBottom-83]
_ = x[actTransformHeaderLabel-84] _ = x[actPreviewUp-84]
_ = x[actTransformPreviewLabel-85] _ = x[actPreviewDown-85]
_ = x[actTransformPrompt-86] _ = x[actPreviewPageUp-86]
_ = x[actTransformQuery-87] _ = x[actPreviewPageDown-87]
_ = x[actPreview-88] _ = x[actPreviewHalfPageUp-88]
_ = x[actChangePreview-89] _ = x[actPreviewHalfPageDown-89]
_ = x[actChangePreviewWindow-90] _ = x[actPrevHistory-90]
_ = x[actPreviewTop-91] _ = x[actPrevSelected-91]
_ = x[actPreviewBottom-92] _ = x[actPrint-92]
_ = x[actPreviewUp-93] _ = x[actPut-93]
_ = x[actPreviewDown-94] _ = x[actNextHistory-94]
_ = x[actPreviewPageUp-95] _ = x[actNextSelected-95]
_ = x[actPreviewPageDown-96] _ = x[actExecute-96]
_ = x[actPreviewHalfPageUp-97] _ = x[actExecuteSilent-97]
_ = x[actPreviewHalfPageDown-98] _ = x[actExecuteMulti-98]
_ = x[actPrevHistory-99] _ = x[actSigStop-99]
_ = x[actPrevSelected-100] _ = x[actFirst-100]
_ = x[actPrint-101] _ = x[actLast-101]
_ = x[actPut-102] _ = x[actReload-102]
_ = x[actNextHistory-103] _ = x[actReloadSync-103]
_ = x[actNextSelected-104] _ = x[actDisableSearch-104]
_ = x[actExecute-105] _ = x[actEnableSearch-105]
_ = x[actExecuteSilent-106] _ = x[actSelect-106]
_ = x[actExecuteMulti-107] _ = x[actDeselect-107]
_ = x[actSigStop-108] _ = x[actUnbind-108]
_ = x[actFirst-109] _ = x[actRebind-109]
_ = x[actLast-110] _ = x[actBecome-110]
_ = x[actReload-111] _ = x[actShowHeader-111]
_ = x[actReloadSync-112] _ = x[actHideHeader-112]
_ = x[actDisableSearch-113]
_ = x[actEnableSearch-114]
_ = x[actSelect-115]
_ = x[actDeselect-116]
_ = x[actUnbind-117]
_ = x[actRebind-118]
_ = x[actBecome-119]
_ = x[actShowHeader-120]
_ = x[actHideHeader-121]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader" const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 825, 832, 837, 846, 857, 868, 881, 896, 907, 920, 935, 942, 955, 968, 985, 1000, 1013, 1027, 1041, 1057, 1077, 1089, 1112, 1133, 1155, 1173, 1196, 1220, 1238, 1255, 1265, 1281, 1303, 1316, 1332, 1344, 1358, 1374, 1392, 1412, 1434, 1448, 1463, 1471, 1477, 1491, 1506, 1516, 1532, 1547, 1557, 1565, 1572, 1581, 1594, 1610, 1625, 1634, 1645, 1654, 1663, 1672, 1685, 1698} var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 690, 705, 722, 729, 734, 743, 754, 765, 778, 793, 804, 817, 832, 839, 852, 865, 882, 897, 910, 924, 938, 954, 974, 986, 1009, 1027, 1051, 1069, 1086, 1096, 1112, 1134, 1147, 1163, 1175, 1189, 1205, 1223, 1243, 1265, 1279, 1294, 1302, 1308, 1322, 1337, 1347, 1363, 1378, 1388, 1396, 1403, 1412, 1425, 1441, 1456, 1465, 1476, 1485, 1494, 1503, 1516, 1529}
func (i actionType) String() string { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -401,7 +401,7 @@ func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []in
if i == 0 { if i == 0 {
fmt.Print(" ") fmt.Print(" ")
for j := int(f); j <= lastIdx; j++ { for j := int(f); j <= lastIdx; j++ {
fmt.Print(" " + string(T[j]) + " ") fmt.Printf(" " + string(T[j]) + " ")
} }
fmt.Println() fmt.Println()
} }
@@ -798,14 +798,6 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
// The solution is much cheaper since there is only one possible alignment of // The solution is much cheaper since there is only one possible alignment of
// the pattern. // the pattern.
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) { func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
return exactMatchNaive(caseSensitive, normalize, forward, false, text, pattern, withPos, slab)
}
func ExactMatchBoundary(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
return exactMatchNaive(caseSensitive, normalize, forward, true, text, pattern, withPos, slab)
}
func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryCheck bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
if len(pattern) == 0 { if len(pattern) == 0 {
return Result{0, 0, 0}, nil return Result{0, 0, 0}, nil
} }
@@ -840,22 +832,10 @@ func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryC
} }
pidx_ := indexAt(pidx, lenPattern, forward) pidx_ := indexAt(pidx, lenPattern, forward)
pchar := pattern[pidx_] pchar := pattern[pidx_]
ok := pchar == char if pchar == char {
if ok {
if pidx_ == 0 { if pidx_ == 0 {
bonus = bonusAt(text, index_) bonus = bonusAt(text, index_)
} }
if boundaryCheck {
ok = bonus >= bonusBoundary
if ok && pidx_ == 0 {
ok = index_ == 0 || charClassOf(text.Get(index_-1)) <= charDelimiter
}
if ok && pidx_ == len(pattern)-1 {
ok = index_ == lenRunes-1 || charClassOf(text.Get(index_+1)) <= charDelimiter
}
}
}
if ok {
pidx++ pidx++
if pidx == lenPattern { if pidx == lenPattern {
if bonus > bestBonus { if bonus > bestBonus {
@@ -881,23 +861,7 @@ func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryC
sidx = lenRunes - (bestPos + 1) sidx = lenRunes - (bestPos + 1)
eidx = lenRunes - (bestPos - lenPattern + 1) eidx = lenRunes - (bestPos - lenPattern + 1)
} }
var score int score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
if boundaryCheck {
// Underscore boundaries should be ranked lower than the other types of boundaries
score = int(bonus)
deduct := int(bonus-bonusBoundary) + 1
if sidx > 0 && text.Get(sidx-1) == '_' {
score -= deduct + 1
deduct = 1
}
if eidx < lenRunes && text.Get(eidx) == '_' {
score -= deduct
}
// Add base score so that this can compete with other match types e.g. 'foo' | bar
score += scoreMatch*lenPattern + int(bonusBoundaryWhite)*(lenPattern+1)
} else {
score, _ = calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
}
return Result{sidx, eidx, score}, nil return Result{sidx, eidx, score}, nil
} }
return Result{-1, -1, 0}, nil return Result{-1, -1, 0}, nil

View File

@@ -1,7 +1,6 @@
package fzf package fzf
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
@@ -14,28 +13,22 @@ type ansiOffset struct {
color ansiState color ansiState
} }
type url struct {
uri string
params string
}
type ansiState struct { type ansiState struct {
fg tui.Color fg tui.Color
bg tui.Color bg tui.Color
attr tui.Attr attr tui.Attr
lbg tui.Color lbg tui.Color
url *url
} }
func (s *ansiState) colored() bool { func (s *ansiState) colored() bool {
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0 || s.url != nil return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0
} }
func (s *ansiState) equals(t *ansiState) bool { func (s *ansiState) equals(t *ansiState) bool {
if t == nil { if t == nil {
return !s.colored() return !s.colored()
} }
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg && s.url == t.url return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg
} }
func (s *ansiState) ToString() string { func (s *ansiState) ToString() string {
@@ -44,7 +37,7 @@ func (s *ansiState) ToString() string {
} }
ret := "" ret := ""
if s.attr&tui.Bold > 0 || s.attr&tui.BoldForce > 0 { if s.attr&tui.Bold > 0 {
ret += "1;" ret += "1;"
} }
if s.attr&tui.Dim > 0 { if s.attr&tui.Dim > 0 {
@@ -67,11 +60,7 @@ func (s *ansiState) ToString() string {
} }
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40) ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
ret = "\x1b[" + strings.TrimSuffix(ret, ";") + "m" return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
if s.url != nil {
ret = fmt.Sprintf("\x1b]8;%s;%s\x1b\\%s\x1b]8;;\x1b", s.url.params, s.url.uri, ret)
}
return ret
} }
func toAnsiString(color tui.Color, offset int) string { func toAnsiString(color tui.Color, offset int) string {
@@ -98,30 +87,21 @@ func isPrint(c uint8) bool {
return '\x20' <= c && c <= '\x7e' return '\x20' <= c && c <= '\x7e'
} }
func matchOperatingSystemCommand(s string, start int) int { func matchOperatingSystemCommand(s string) int {
// `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)` // `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
// ^ match starting here after the first printable character // ^ match starting here
// //
i := start // prefix matched in nextAnsiEscapeSequence() i := 5 // prefix matched in nextAnsiEscapeSequence()
for ; i < len(s) && isPrint(s[i]); i++ { for ; i < len(s) && isPrint(s[i]); i++ {
} }
if i < len(s) { if i < len(s) {
if s[i] == '\x07' { if s[i] == '\x07' {
return i + 1 return i + 1
} }
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
// ------
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' { if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
return i + 2 return i + 2
} }
} }
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
// ------------
if i < len(s) && s[:i+1] == "\x1b]8;;\x1b" {
return i + 1
}
return -1 return -1
} }
@@ -156,7 +136,7 @@ func isCtrlSeqStart(c uint8) bool {
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to // nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
// calling FindStringIndex() on the below regex (which was originally used): // calling FindStringIndex() on the below regex (which was originally used):
// //
// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)" // "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
func nextAnsiEscapeSequence(s string) (int, int) { func nextAnsiEscapeSequence(s string) (int, int) {
// fast check for ANSI escape sequences // fast check for ANSI escape sequences
i := 0 i := 0
@@ -191,20 +171,12 @@ Loop:
} }
} }
// match: `\x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)` // match: `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
if i+5 < len(s) && s[i+1] == ']' { if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
j := 2 (s[i+3] == ';' || s[i+3] == ':') && isPrint(s[i+4]) {
// \x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)
// ------
for ; i+j < len(s) && isNumeric(s[i+j]); j++ {
}
// \x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07) if j := matchOperatingSystemCommand(s[i:]); j != -1 {
// --------------- return i, i + j
if j > 2 && i+j+1 < len(s) && (s[i+j] == ';' || s[i+j] == ':') && isPrint(s[i+j+1]) {
if k := matchOperatingSystemCommand(s[i:], j+2); k != -1 {
return i, i + k
}
} }
} }
@@ -318,15 +290,20 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
return trimmed, nil, state return trimmed, nil, state
} }
func parseAnsiCode(s string) (int, string) { func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
var remaining string var remaining string
var i int var i int
if delimiter == 0 {
// Faster than strings.IndexAny(";:") // Faster than strings.IndexAny(";:")
i = strings.IndexByte(s, ';') i = strings.IndexByte(s, ';')
if i < 0 { if i < 0 {
i = strings.IndexByte(s, ':') i = strings.IndexByte(s, ':')
} }
} else {
i = strings.IndexByte(s, delimiter)
}
if i >= 0 { if i >= 0 {
delimiter = s[i]
remaining = s[i+1:] remaining = s[i+1:]
s = s[:i] s = s[:i]
} }
@@ -338,34 +315,26 @@ func parseAnsiCode(s string) (int, string) {
for _, ch := range stringBytes(s) { for _, ch := range stringBytes(s) {
ch -= '0' ch -= '0'
if ch > 9 { if ch > 9 {
return -1, remaining return -1, delimiter, remaining
} }
code = code*10 + int(ch) code = code*10 + int(ch)
} }
return code, remaining return code, delimiter, remaining
} }
return -1, remaining return -1, delimiter, remaining
} }
func interpretCode(ansiCode string, prevState *ansiState) ansiState { func interpretCode(ansiCode string, prevState *ansiState) ansiState {
var state ansiState var state ansiState
if prevState == nil { if prevState == nil {
state = ansiState{-1, -1, 0, -1, nil} state = ansiState{-1, -1, 0, -1}
} else { } else {
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg, prevState.url} state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
} }
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' { if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
if prevState != nil && strings.HasSuffix(ansiCode, "0K") { if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
state.lbg = prevState.bg state.lbg = prevState.bg
} else if ansiCode == "\x1b]8;;\x1b\\" { // End of a hyperlink
state.url = nil
} else if strings.HasPrefix(ansiCode, "\x1b]8;") && strings.HasSuffix(ansiCode, "\x1b\\") {
if paramsEnd := strings.IndexRune(ansiCode[4:], ';'); paramsEnd >= 0 {
params := ansiCode[4 : 4+paramsEnd]
uri := ansiCode[5+paramsEnd : len(ansiCode)-2]
state.url = &url{uri: uri, params: params}
}
} }
return state return state
} }
@@ -381,10 +350,11 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
state256 := 0 state256 := 0
ptr := &state.fg ptr := &state.fg
var delimiter byte
count := 0 count := 0
for len(ansiCode) != 0 { for len(ansiCode) != 0 {
var num int var num int
if num, ansiCode = parseAnsiCode(ansiCode); num != -1 { if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 {
count++ count++
switch state256 { switch state256 {
case 0: case 0:

View File

@@ -335,28 +335,6 @@ func TestExtractColor(t *testing.T) {
assert((*offsets)[0], 0, 6, 2, -1, true) assert((*offsets)[0], 0, 6, 2, -1, true)
assert((*offsets)[1], 6, 11, 200, 100, false) assert((*offsets)[1], 6, 11, 200, 100, false)
}) })
state = nil
var color24 tui.Color = (1 << 24) + (180 << 16) + (190 << 8) + 254
src = "\x1b[1mhello \x1b[22;1;38:2:180:190:254mworld"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 2 {
t.Fail()
}
if state.fg != color24 || state.attr != 1 {
t.Fail()
}
assert((*offsets)[0], 0, 6, -1, -1, true)
assert((*offsets)[1], 6, 11, color24, -1, true)
})
src = "\x1b]133;A\x1b\\hello \x1b]133;C\x1b\\world"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 1 {
t.Fail()
}
assert((*offsets)[0], 0, 11, color24, -1, true)
})
} }
func TestAnsiCodeStringConversion(t *testing.T) { func TestAnsiCodeStringConversion(t *testing.T) {
@@ -403,7 +381,7 @@ func TestParseAnsiCode(t *testing.T) {
{"-2", "", -1}, {"-2", "", -1},
} }
for _, x := range tests { for _, x := range tests {
n, s := parseAnsiCode(x.In) n, _, s := parseAnsiCode(x.In, 0)
if n != x.N || s != x.Exp { if n != x.N || s != x.Exp {
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp) t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
} }

View File

@@ -172,9 +172,7 @@ func Run(opts *Options) (int, error) {
return chunkList.Push(data) return chunkList.Push(data)
}, eventBox, executor, opts.ReadZero, opts.Filter == nil) }, eventBox, executor, opts.ReadZero, opts.Filter == nil)
readyChan := make(chan bool) go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, readyChan)
<-readyChan
} }
// Matcher // Matcher
@@ -190,14 +188,11 @@ func Run(opts *Options) (int, error) {
forward = true forward = true
} }
} }
nth := opts.Nth
nthRevision := 0
patternCache := make(map[string]*Pattern) patternCache := make(map[string]*Pattern)
patternBuilder := func(runes []rune) *Pattern { patternBuilder := func(runes []rune) *Pattern {
return BuildPattern(cache, patternCache, return BuildPattern(cache, patternCache,
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos, opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
opts.Filter == nil, nth, opts.Delimiter, nthRevision, runes) opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
} }
inputRevision := revision{} inputRevision := revision{}
snapshotRevision := revision{} snapshotRevision := revision{}
@@ -229,7 +224,7 @@ func Run(opts *Options) (int, error) {
} }
return false return false
}, eventBox, executor, opts.ReadZero, false) }, eventBox, executor, opts.ReadZero, false)
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, nil) reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
} else { } else {
eventBox.Unwatch(EvtReadNew) eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin) eventBox.WaitFor(EvtReadFin)
@@ -304,9 +299,7 @@ func Run(opts *Options) (int, error) {
itemIndex = 0 itemIndex = 0
inputRevision.bumpMajor() inputRevision.bumpMajor()
header = make([]string, 0, opts.HeaderLines) header = make([]string, 0, opts.HeaderLines)
readyChan := make(chan bool) go reader.restart(command, environ)
go reader.restart(command, environ, readyChan)
<-readyChan
} }
exitCode := ExitOk exitCode := ExitOk
@@ -376,14 +369,6 @@ func Run(opts *Options) (int, error) {
command = val.command command = val.command
environ = val.environ environ = val.environ
changed = val.changed changed = val.changed
if val.nth != nil {
// Change nth and clear caches
nth = *val.nth
nthRevision++
patternCache = make(map[string]*Pattern)
cache.Clear()
inputRevision.bumpMinor()
}
if command != nil { if command != nil {
useSnapshot = val.sync useSnapshot = val.sync
} }

View File

@@ -6,17 +6,10 @@ import (
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
type transformed struct {
// Because nth can be changed dynamically by change-nth action, we need to
// keep the revision number at the time of transformation.
revision int
tokens []Token
}
// Item represents each input line. 56 bytes. // Item represents each input line. 56 bytes.
type Item struct { type Item struct {
text util.Chars // 32 = 24 + 1 + 1 + 2 + 4 text util.Chars // 32 = 24 + 1 + 1 + 2 + 4
transformed *transformed // 8 transformed *[]Token // 8
origText *[]byte // 8 origText *[]byte // 8
colors *[]ansiOffset // 8 colors *[]ansiOffset // 8
} }

View File

@@ -102,7 +102,7 @@ func (m *Matcher) Loop() {
if !cacheCleared { if !cacheCleared {
if count == prevCount { if count == prevCount {
// Look up mergerCache // Look up mergerCache
if cached, found := m.mergerCache[patternString]; found && cached.final == request.final { if cached, found := m.mergerCache[patternString]; found {
merger = cached merger = cached
} }
} else { } else {

File diff suppressed because it is too large Load Diff

View File

@@ -9,13 +9,9 @@ import (
) )
func TestDelimiterRegex(t *testing.T) { func TestDelimiterRegex(t *testing.T) {
// Valid regex, but a single character -> string // Valid regex
delim := delimiterRegexp(".") delim := delimiterRegexp(".")
if delim.regex != nil || *delim.str != "." { if delim.regex == nil || delim.str != nil {
t.Error(delim)
}
delim = delimiterRegexp("|")
if delim.regex != nil || *delim.str != "|" {
t.Error(delim) t.Error(delim)
} }
// Broken regex -> string // Broken regex -> string

View File

@@ -23,7 +23,6 @@ type termType int
const ( const (
termFuzzy termType = iota termFuzzy termType = iota
termExact termExact
termExactBoundary
termPrefix termPrefix
termSuffix termSuffix
termEqual termEqual
@@ -60,7 +59,6 @@ type Pattern struct {
cacheKey string cacheKey string
delimiter Delimiter delimiter Delimiter
nth []Range nth []Range
revision int
procFun map[termType]algo.Algo procFun map[termType]algo.Algo
cache *ChunkCache cache *ChunkCache
} }
@@ -73,7 +71,7 @@ func init() {
// BuildPattern builds Pattern object from the given arguments // BuildPattern builds Pattern object from the given arguments
func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool, func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, revision int, runes []rune) *Pattern { withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
var asString string var asString string
if extended { if extended {
@@ -141,7 +139,6 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
sortable: sortable, sortable: sortable,
cacheable: cacheable, cacheable: cacheable,
nth: nth, nth: nth,
revision: revision,
delimiter: delimiter, delimiter: delimiter,
cache: cache, cache: cache,
procFun: make(map[termType]algo.Algo)} procFun: make(map[termType]algo.Algo)}
@@ -150,7 +147,6 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
ptr.procFun[termFuzzy] = fuzzyAlgo ptr.procFun[termFuzzy] = fuzzyAlgo
ptr.procFun[termEqual] = algo.EqualMatch ptr.procFun[termEqual] = algo.EqualMatch
ptr.procFun[termExact] = algo.ExactMatchNaive ptr.procFun[termExact] = algo.ExactMatchNaive
ptr.procFun[termExactBoundary] = algo.ExactMatchBoundary
ptr.procFun[termPrefix] = algo.PrefixMatch ptr.procFun[termPrefix] = algo.PrefixMatch
ptr.procFun[termSuffix] = algo.SuffixMatch ptr.procFun[termSuffix] = algo.SuffixMatch
@@ -197,10 +193,7 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
text = text[:len(text)-1] text = text[:len(text)-1]
} }
if len(text) > 2 && strings.HasPrefix(text, "'") && strings.HasSuffix(text, "'") { if strings.HasPrefix(text, "'") {
typ = termExactBoundary
text = text[1 : len(text)-1]
} else if strings.HasPrefix(text, "'") {
// Flip exactness // Flip exactness
if fuzzy && !inv { if fuzzy && !inv {
typ = termExact typ = termExact
@@ -395,15 +388,12 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
func (p *Pattern) transformInput(item *Item) []Token { func (p *Pattern) transformInput(item *Item) []Token {
if item.transformed != nil { if item.transformed != nil {
transformed := *item.transformed return *item.transformed
if transformed.revision == p.revision {
return transformed.tokens
}
} }
tokens := Tokenize(item.text.ToString(), p.delimiter) tokens := Tokenize(item.text.ToString(), p.delimiter)
ret := Transform(tokens, p.nth) ret := Transform(tokens, p.nth)
item.transformed = &transformed{p.revision, ret} item.transformed = &ret
return ret return ret
} }

View File

@@ -68,7 +68,7 @@ func buildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern { withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
return BuildPattern(NewChunkCache(), make(map[string]*Pattern), return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward, fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
withPos, cacheable, nth, delimiter, 0, runes) withPos, cacheable, nth, delimiter, runes)
} }
func TestExact(t *testing.T) { func TestExact(t *testing.T) {
@@ -135,12 +135,12 @@ func TestOrigTextAndTransformed(t *testing.T) {
chunk.items[0] = Item{ chunk.items[0] = Item{
text: util.ToChars([]byte("junegunn")), text: util.ToChars([]byte("junegunn")),
origText: &origBytes, origText: &origBytes,
transformed: &transformed{pattern.revision, trans}} transformed: &trans}
pattern.extended = extended pattern.extended = extended
matches := pattern.matchChunk(&chunk, nil, slab) // No cache matches := pattern.matchChunk(&chunk, nil, slab) // No cache
if !(matches[0].item.text.ToString() == "junegunn" && if !(matches[0].item.text.ToString() == "junegunn" &&
string(*matches[0].item.origText) == "junegunn.choi" && string(*matches[0].item.origText) == "junegunn.choi" &&
reflect.DeepEqual((*matches[0].item.transformed).tokens, trans)) { reflect.DeepEqual(*matches[0].item.transformed, trans)) {
t.Error("Invalid match result", matches) t.Error("Invalid match result", matches)
} }
@@ -148,7 +148,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
if !(match.item.text.ToString() == "junegunn" && if !(match.item.text.ToString() == "junegunn" &&
string(*match.item.origText) == "junegunn.choi" && string(*match.item.origText) == "junegunn.choi" &&
offsets[0][0] == 0 && offsets[0][1] == 5 && offsets[0][0] == 0 && offsets[0][1] == 5 &&
reflect.DeepEqual((*match.item.transformed).tokens, trans)) { reflect.DeepEqual(*match.item.transformed, trans)) {
t.Error("Invalid match result", match, offsets, extended) t.Error("Invalid match result", match, offsets, extended)
} }
if !((*pos)[0] == 4 && (*pos)[1] == 0) { if !((*pos)[0] == 4 && (*pos)[1] == 0) {

View File

@@ -9,7 +9,6 @@ import (
"os/exec" "os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"time" "time"
@@ -33,7 +32,7 @@ func fifo(name string) (string, error) {
return output, nil return output, nil
} }
func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool) (*exec.Cmd, error), opts *Options, withExports bool) (int, error) { func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts *Options, withExports bool) (int, error) {
output, err := fifo("proxy-output") output, err := fifo("proxy-output")
if err != nil { if err != nil {
return ExitError, err return ExitError, err
@@ -93,28 +92,17 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
// To ensure that the options are processed by a POSIX-compliant shell, // To ensure that the options are processed by a POSIX-compliant shell,
// we need to write the command to a temporary file and execute it with sh. // we need to write the command to a temporary file and execute it with sh.
var exports []string var exports []string
needBash := false
if withExports { if withExports {
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`) exports = os.Environ()
for _, pairStr := range os.Environ() { for idx, pairStr := range exports {
pair := strings.SplitN(pairStr, "=", 2) pair := strings.SplitN(pairStr, "=", 2)
if validIdentifier.MatchString(pair[0]) { exports[idx] = fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))
exports = append(exports, fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1])))
} else if strings.HasPrefix(pair[0], "BASH_FUNC_") && strings.HasSuffix(pair[0], "%%") {
name := pair[0][10 : len(pair[0])-2]
exports = append(exports, name+pair[1])
exports = append(exports, "export -f "+name)
needBash = true
}
} }
} }
temp := WriteTemporaryFile(append(exports, command), "\n") temp := WriteTemporaryFile(append(exports, command), "\n")
defer os.Remove(temp) defer os.Remove(temp)
cmd, err := cmdBuilder(temp, needBash) cmd := cmdBuilder(temp)
if err != nil {
return ExitError, err
}
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
intChan := make(chan os.Signal, 1) intChan := make(chan os.Signal, 1)
defer close(intChan) defer close(intChan)

View File

@@ -9,10 +9,7 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func sh(bash bool) (string, error) { func sh() (string, error) {
if bash {
return "bash", nil
}
return "sh", nil return "sh", nil
} }

View File

@@ -13,16 +13,12 @@ import (
var shPath atomic.Value var shPath atomic.Value
func sh(bash bool) (string, error) { func sh() (string, error) {
if cached := shPath.Load(); cached != nil { if cached := shPath.Load(); cached != nil {
return cached.(string), nil return cached.(string), nil
} }
name := "sh" cmd := exec.Command("cygpath", "-w", "/usr/bin/sh")
if bash {
name = "bash"
}
cmd := exec.Command("cygpath", "-w", "/usr/bin/"+name)
bytes, err := cmd.Output() bytes, err := cmd.Output()
if err != nil { if err != nil {
return "", err return "", err
@@ -35,7 +31,7 @@ func sh(bash bool) (string, error) {
func mkfifo(path string, mode uint32) (string, error) { func mkfifo(path string, mode uint32) (string, error) {
m := strconv.FormatUint(uint64(mode), 8) m := strconv.FormatUint(uint64(mode), 8)
sh, err := sh(false) sh, err := sh()
if err != nil { if err != nil {
return path, err return path, err
} }
@@ -47,7 +43,7 @@ func mkfifo(path string, mode uint32) (string, error) {
} }
func withOutputPipe(output string, task func(io.ReadCloser)) error { func withOutputPipe(output string, task func(io.ReadCloser)) error {
sh, err := sh(false) sh, err := sh()
if err != nil { if err != nil {
return err return err
} }
@@ -66,7 +62,7 @@ func withOutputPipe(output string, task func(io.ReadCloser)) error {
} }
func withInputPipe(input string, task func(io.WriteCloser)) error { func withInputPipe(input string, task func(io.WriteCloser)) error {
sh, err := sh(false) sh, err := sh()
if err != nil { if err != nil {
return err return err
} }

View File

@@ -6,8 +6,8 @@ import (
"io" "io"
"io/fs" "io/fs"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -25,26 +25,16 @@ type Reader struct {
event int32 event int32
finChan chan bool finChan chan bool
mutex sync.Mutex mutex sync.Mutex
killed bool exec *exec.Cmd
termFunc func() execOut io.ReadCloser
command *string command *string
killed bool
wait bool wait bool
} }
// NewReader returns new Reader object // NewReader returns new Reader object
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader { func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
return &Reader{ return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, nil, false, wait}
pusher,
executor,
eventBox,
delimNil,
int32(EvtReady),
make(chan bool, 1),
sync.Mutex{},
false,
func() { os.Stdin.Close() },
nil,
wait}
} }
func (r *Reader) startEventPoller() { func (r *Reader) startEventPoller() {
@@ -90,19 +80,19 @@ func (r *Reader) fin(success bool) {
func (r *Reader) terminate() { func (r *Reader) terminate() {
r.mutex.Lock() r.mutex.Lock()
r.killed = true r.killed = true
if r.termFunc != nil { if r.exec != nil && r.exec.Process != nil {
r.termFunc() r.execOut.Close()
r.termFunc = nil util.KillCommand(r.exec)
} else {
os.Stdin.Close()
} }
r.mutex.Unlock() r.mutex.Unlock()
} }
func (r *Reader) restart(command commandSpec, environ []string, readyChan chan bool) { func (r *Reader) restart(command commandSpec, environ []string) {
r.event = int32(EvtReady) r.event = int32(EvtReady)
r.startEventPoller() r.startEventPoller()
success := r.readFromCommand(command.command, environ, func() { success := r.readFromCommand(command.command, environ)
readyChan <- true
})
r.fin(success) r.fin(success)
removeFiles(command.tempFiles) removeFiles(command.tempFiles)
} }
@@ -121,29 +111,21 @@ func (r *Reader) readChannel(inputChan chan string) bool {
} }
// ReadSource reads data from the default command or from standard input // ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource(inputChan chan string, roots []string, opts walkerOpts, ignores []string, initCmd string, initEnv []string, readyChan chan bool) { func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string, initCmd string, initEnv []string) {
r.startEventPoller() r.startEventPoller()
var success bool var success bool
signalReady := func() {
if readyChan != nil {
readyChan <- true
}
}
if inputChan != nil { if inputChan != nil {
signalReady()
success = r.readChannel(inputChan) success = r.readChannel(inputChan)
} else if len(initCmd) > 0 { } else if len(initCmd) > 0 {
success = r.readFromCommand(initCmd, initEnv, signalReady) success = r.readFromCommand(initCmd, initEnv)
} else if util.IsTty(os.Stdin) { } else if util.IsTty(os.Stdin) {
cmd := os.Getenv("FZF_DEFAULT_COMMAND") cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 { if len(cmd) == 0 {
signalReady() success = r.readFiles(root, opts, ignores)
success = r.readFiles(roots, opts, ignores)
} else { } else {
success = r.readFromCommand(cmd, initEnv, signalReady) success = r.readFromCommand(cmd, initEnv)
} }
} else { } else {
signalReady()
success = r.readFromStdin() success = r.readFromStdin()
} }
r.fin(success) r.fin(success)
@@ -266,33 +248,14 @@ func trimPath(path string) string {
return byteString(bytes) return byteString(bytes)
} }
func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bool { func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
r.killed = false
conf := fastwalk.Config{ conf := fastwalk.Config{
Follow: opts.follow, Follow: opts.follow,
// Use forward slashes when running a Windows binary under WSL or MSYS // Use forward slashes when running a Windows binary under WSL or MSYS
ToSlash: fastwalk.DefaultToSlash(), ToSlash: fastwalk.DefaultToSlash(),
Sort: fastwalk.SortFilesFirst, Sort: fastwalk.SortFilesFirst,
} }
ignoresBase := []string{}
ignoresFull := []string{}
ignoresSuffix := []string{}
sep := string(os.PathSeparator)
for _, ignore := range ignores {
if strings.ContainsRune(ignore, os.PathSeparator) {
if strings.HasPrefix(ignore, sep) {
ignoresSuffix = append(ignoresSuffix, ignore)
} else {
// 'foo/bar' should match match
// * 'foo/bar'
// * 'baz/foo/bar'
// * but NOT 'bazfoo/bar'
ignoresFull = append(ignoresFull, ignore)
ignoresSuffix = append(ignoresSuffix, sep+ignore)
}
} else {
ignoresBase = append(ignoresBase, ignore)
}
}
fn := func(path string, de os.DirEntry, err error) error { fn := func(path string, de os.DirEntry, err error) error {
if err != nil { if err != nil {
return nil return nil
@@ -302,24 +265,14 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
isDir := de.IsDir() isDir := de.IsDir()
if isDir || opts.follow && isSymlinkToDir(path, de) { if isDir || opts.follow && isSymlinkToDir(path, de) {
base := filepath.Base(path) base := filepath.Base(path)
if !opts.hidden && base[0] == '.' && base != ".." { if !opts.hidden && base[0] == '.' {
return filepath.SkipDir return filepath.SkipDir
} }
for _, ignore := range ignoresBase { for _, ignore := range ignores {
if ignore == base { if ignore == base {
return filepath.SkipDir return filepath.SkipDir
} }
} }
for _, ignore := range ignoresFull {
if ignore == path {
return filepath.SkipDir
}
}
for _, ignore := range ignoresSuffix {
if strings.HasSuffix(path, ignore) {
return filepath.SkipDir
}
}
} }
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) { if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
atomic.StoreInt32(&r.event, int32(EvtReadNew)) atomic.StoreInt32(&r.event, int32(EvtReadNew))
@@ -332,39 +285,34 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
} }
return nil return nil
} }
noerr := true return fastwalk.Walk(&conf, root, fn) == nil
for _, root := range roots {
noerr = noerr && (fastwalk.Walk(&conf, root, fn) == nil)
}
return noerr
} }
func (r *Reader) readFromCommand(command string, environ []string, signalReady func()) bool { func (r *Reader) readFromCommand(command string, environ []string) bool {
r.mutex.Lock() r.mutex.Lock()
r.killed = false r.killed = false
r.termFunc = nil
r.command = &command r.command = &command
exec := r.executor.ExecCommand(command, true) r.exec = r.executor.ExecCommand(command, true)
if environ != nil { if environ != nil {
exec.Env = environ r.exec.Env = environ
} }
execOut, err := exec.StdoutPipe()
if err != nil || exec.Start() != nil { var err error
signalReady() r.execOut, err = r.exec.StdoutPipe()
if err != nil {
r.exec = nil
r.mutex.Unlock() r.mutex.Unlock()
return false return false
} }
// Function to call to terminate the running command err = r.exec.Start()
r.termFunc = func() { if err != nil {
execOut.Close() r.exec = nil
util.KillCommand(exec) r.mutex.Unlock()
return false
} }
signalReady()
r.mutex.Unlock() r.mutex.Unlock()
r.feed(r.execOut)
r.feed(execOut) return r.exec.Wait() == nil
return exec.Wait() == nil
} }

View File

@@ -23,12 +23,8 @@ func TestReadFromCommand(t *testing.T) {
} }
// Normal command // Normal command
counter := 0 reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil))
ready := func() { if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
counter++
}
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil, ready))
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" || counter != 1 {
t.Errorf("%s", strs) t.Errorf("%s", strs)
} }
@@ -52,9 +48,9 @@ func TestReadFromCommand(t *testing.T) {
reader.startEventPoller() reader.startEventPoller()
// Failing command // Failing command
reader.fin(reader.readFromCommand(`no-such-command`, nil, ready)) reader.fin(reader.readFromCommand(`no-such-command`, nil))
strs = []string{} strs = []string{}
if len(strs) > 0 || counter != 2 { if len(strs) > 0 {
t.Errorf("%s", strs) t.Errorf("%s", strs)
} }

View File

@@ -16,7 +16,6 @@ type colorOffset struct {
offset [2]int32 offset [2]int32
color tui.ColorPair color tui.ColorPair
match bool match bool
url *url
} }
type Result struct { type Result struct {
@@ -104,11 +103,11 @@ func minRank() Result {
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}} return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
} }
func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr, current bool) []colorOffset { func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, current bool) []colorOffset {
itemColors := result.item.Colors() itemColors := result.item.Colors()
// No ANSI codes // No ANSI codes
if len(itemColors) == 0 && len(nthOffsets) == 0 { if len(itemColors) == 0 {
var offsets []colorOffset var offsets []colorOffset
for _, off := range matchOffsets { for _, off := range matchOffsets {
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch, match: true}) offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch, match: true})
@@ -118,7 +117,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
// Find max column // Find max column
var maxCol int32 var maxCol int32
for _, off := range append(matchOffsets, nthOffsets...) { for _, off := range matchOffsets {
if off[1] > maxCol { if off[1] > maxCol {
maxCol = off[1] maxCol = off[1]
} }
@@ -129,30 +128,21 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
} }
} }
type cellInfo struct { cols := make([]int, maxCol)
index int
color bool
match bool
nth bool
}
cols := make([]cellInfo, maxCol)
for colorIndex, ansi := range itemColors { for colorIndex, ansi := range itemColors {
for i := ansi.offset[0]; i < ansi.offset[1]; i++ { for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
cols[i] = cellInfo{colorIndex, true, false, false} cols[i] = colorIndex + 1 // 1-based index of itemColors
} }
} }
for _, off := range matchOffsets { for _, off := range matchOffsets {
for i := off[0]; i < off[1]; i++ { for i := off[0]; i < off[1]; i++ {
cols[i].match = true // Negative of 1-based index of itemColors
// - The extra -1 means highlighted
if cols[i] >= 0 {
cols[i] = cols[i]*-1 - 1
} }
} }
for _, off := range nthOffsets {
for i := off[0]; i < off[1]; i++ {
cols[i].nth = true
}
} }
// sort.Sort(ByOrder(offsets)) // sort.Sort(ByOrder(offsets))
@@ -161,7 +151,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
// ------------ ---- -- ---- // ------------ ---- -- ----
// ++++++++ ++++++++++ // ++++++++ ++++++++++
// --++++++++-- --++++++++++--- // --++++++++-- --++++++++++---
var curr cellInfo = cellInfo{0, false, false, false} curr := 0
start := 0 start := 0
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair { ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
fg := ansi.color.fg fg := ansi.color.fg
@@ -184,19 +174,11 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
} }
var colors []colorOffset var colors []colorOffset
add := func(idx int) { add := func(idx int) {
if (curr.color || curr.nth || curr.match) && idx > start { if curr != 0 && idx > start {
if curr.match { if curr < 0 {
var color tui.ColorPair color := colMatch
if curr.nth { if curr < -1 && theme.Colored {
color = colBase.WithAttr(attrNth).Merge(colMatch) origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
} else {
color = colBase.Merge(colMatch)
}
var url *url
if curr.color && theme.Colored {
ansi := itemColors[curr.index]
url = ansi.color.url
origColor := ansiToColorPair(ansi, colMatch)
// hl or hl+ only sets the foreground color, so colMatch is the // hl or hl+ only sets the foreground color, so colMatch is the
// combination of either [hl and bg] or [hl+ and bg+]. // combination of either [hl and bg] or [hl+ and bg+].
// //
@@ -207,32 +189,18 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
// echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline // echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
if color.Fg().IsDefault() && origColor.HasBg() { if color.Fg().IsDefault() && origColor.HasBg() {
color = origColor color = origColor
if curr.nth {
color = color.WithAttr(attrNth)
}
} else { } else {
color = origColor.MergeNonDefault(color) color = origColor.MergeNonDefault(color)
} }
} }
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url}) offset: [2]int32{int32(start), int32(idx)}, color: color, match: true})
} else if curr.color {
ansi := itemColors[curr.index]
color := ansiToColorPair(ansi, colBase)
if curr.nth {
color = color.WithAttr(attrNth)
}
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)},
color: color,
match: false,
url: ansi.color.url})
} else { } else {
ansi := itemColors[curr-1]
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, offset: [2]int32{int32(start), int32(idx)},
color: colBase.WithAttr(attrNth), color: ansiToColorPair(ansi, colBase),
match: false, match: false})
url: nil})
} }
} }
} }

View File

@@ -124,14 +124,14 @@ func TestColorOffset(t *testing.T) {
item := Result{ item := Result{
item: &Item{ item: &Item{
colors: &[]ansiOffset{ colors: &[]ansiOffset{
{[2]int32{0, 20}, ansiState{1, 5, 0, -1, nil}}, {[2]int32{0, 20}, ansiState{1, 5, 0, -1}},
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1, nil}}, {[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
{[2]int32{30, 32}, ansiState{3, 7, 0, -1, nil}}, {[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1, nil}}}}} {[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined) colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined) colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined, true) colors := item.colorOffsets(offsets, tui.Dark256, colBase, colMatch, true)
assert := func(idx int, b int32, e int32, c tui.ColorPair) { assert := func(idx int, b int32, e int32, c tui.ColorPair) {
o := colors[idx] o := colors[idx]
if o.offset[0] != b || o.offset[1] != e || o.color != c { if o.offset[0] != b || o.offset[1] != e || o.color != c {
@@ -155,15 +155,12 @@ func TestColorOffset(t *testing.T) {
colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined) colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined)
colUnderline := tui.NewColorPair(-1, -1, tui.Underline) colUnderline := tui.NewColorPair(-1, -1, tui.Underline)
colors = item.colorOffsets(offsets, tui.Dark256, colRegular, colUnderline, true)
nthOffsets := []Offset{{37, 39}, {42, 45}}
for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} {
colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr, true)
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}} // [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}} // {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
// {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}} // {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}}
// {[35 37] {4 8 1}} {[37 39] {4 8 x|1}} {[39 40] {4 8 x|1}}] // {[35 40] {4 8 1}}]
assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined)) assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline)) assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))
assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined)) assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
@@ -173,12 +170,5 @@ func TestColorOffset(t *testing.T) {
assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline)) assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))
assert(7, 32, 33, colUnderline) assert(7, 32, 33, colUnderline)
assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline)) assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))
assert(9, 35, 37, tui.NewColorPair(4, 8, tui.Bold)) assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
expected := tui.Bold | attr
if attr == tui.AttrRegular {
expected = tui.AttrRegular
}
assert(10, 37, 39, tui.NewColorPair(4, 8, expected))
assert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -507,34 +507,6 @@ func TestParsePlaceholder(t *testing.T) {
} }
} }
func TestExtractPassthroughs(t *testing.T) {
for _, middle := range []string{
"\x1bPtmux;\x1b\x1bbar\x1b\\",
"\x1bPtmux;\x1b\x1bbar\x1bbar\x1b\\",
"\x1b]1337;bar\x1b\\",
"\x1b]1337;bar\x1bbar\x1b\\",
"\x1b]1337;bar\a",
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1b\\",
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1b\\\r",
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1bbar\x1b\\\r",
"\x1b_Gm=1;AAAAAAAAA=\x1b\\",
"\x1b_Gm=1;AAAAAAAAA=\x1b\\\r",
"\x1b_Gm=1;\x1bAAAAAAAAA=\x1b\\\r",
} {
line := "foo" + middle + "baz"
loc := findPassThrough(line)
if loc == nil || line[0:loc[0]] != "foo" || line[loc[1]:] != "baz" {
t.Error("failed to find passthrough")
}
garbage := "\x1bPtmux;\x1b]1337;\x1b_Ga=\x1b]1337;bar\x1b."
line = strings.Repeat("foo"+middle+middle+"baz", 3) + garbage
passthroughs, result := extractPassThroughs(line)
if result != "foobazfoobazfoobaz"+garbage || len(passthroughs) != 6 {
t.Error("failed to extract passthroughs")
}
}
}
/* utilities section */ /* utilities section */
// Item represents one line in fzf UI. Usually it is relative path to files and folders. // Item represents one line in fzf UI. Usually it is relative path to files and folders.

View File

@@ -20,5 +20,9 @@ func notifyStop(p *os.Process) {
if err == nil { if err == nil {
pid = pgid * -1 pid = pgid * -1
} }
unix.Kill(pid, syscall.SIGTSTP) unix.Kill(pid, syscall.SIGSTOP)
}
func notifyOnCont(resizeChan chan<- os.Signal) {
signal.Notify(resizeChan, syscall.SIGCONT)
} }

View File

@@ -13,3 +13,7 @@ func notifyOnResize(resizeChan chan<- os.Signal) {
func notifyStop(p *os.Process) { func notifyStop(p *os.Process) {
// NOOP // NOOP
} }
func notifyOnCont(resizeChan chan<- os.Signal) {
// NOOP
}

View File

@@ -9,18 +9,13 @@ import (
func runTmux(args []string, opts *Options) (int, error) { func runTmux(args []string, opts *Options) (int, error) {
// Prepare arguments // Prepare arguments
fzf, rest := args[0], args[1:] fzf := args[0]
args = []string{"--bind=ctrl-z:ignore"} args = append([]string{"--bind=ctrl-z:ignore"}, args[1:]...)
if !opts.Tmux.border && opts.BorderShape == tui.BorderUndefined { if opts.BorderShape == tui.BorderUndefined {
// We append --border option at the end, because `--style=full:STYLE` args = append(args, "--border")
// may have changed the default border style.
rest = append(rest, "--border")
}
if opts.Tmux.border && opts.Margin == defaultMargin() {
args = append(args, "--margin=0,1")
} }
argStr := escapeSingleQuote(fzf) argStr := escapeSingleQuote(fzf)
for _, arg := range append(args, rest...) { for _, arg := range args {
argStr += " " + escapeSingleQuote(arg) argStr += " " + escapeSingleQuote(arg)
} }
argStr += ` --no-tmux --no-height` argStr += ` --no-tmux --no-height`
@@ -38,15 +33,12 @@ func runTmux(args []string, opts *Options) (int, error) {
// M Both The mouse position // M Both The mouse position
// W Both The window position on the status line // W Both The window position on the status line
// S -y The line above or below the status line // S -y The line above or below the status line
tmuxArgs := []string{"display-popup", "-E", "-d", dir} tmuxArgs := []string{"display-popup", "-E", "-B", "-d", dir}
if !opts.Tmux.border {
tmuxArgs = append(tmuxArgs, "-B")
}
switch opts.Tmux.position { switch opts.Tmux.position {
case posUp: case posUp:
tmuxArgs = append(tmuxArgs, "-xC", "-y0") tmuxArgs = append(tmuxArgs, "-xC", "-y0")
case posDown: case posDown:
tmuxArgs = append(tmuxArgs, "-xC", "-y9999") tmuxArgs = append(tmuxArgs, "-xC", "-yS")
case posLeft: case posLeft:
tmuxArgs = append(tmuxArgs, "-x0", "-yC") tmuxArgs = append(tmuxArgs, "-x0", "-yC")
case posRight: case posRight:
@@ -57,12 +49,9 @@ func runTmux(args []string, opts *Options) (int, error) {
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String()) tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String()) tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) { return runProxy(argStr, func(temp string) *exec.Cmd {
sh, err := sh(needBash) sh, _ := sh()
if err != nil {
return nil, err
}
tmuxArgs = append(tmuxArgs, sh, temp) tmuxArgs = append(tmuxArgs, sh, temp)
return exec.Command("tmux", tmuxArgs...), nil return exec.Command("tmux", tmuxArgs...)
}, opts, true) }, opts, true)
} }

View File

@@ -18,36 +18,6 @@ type Range struct {
end int end int
} }
func (r Range) IsFull() bool {
return r.begin == rangeEllipsis && r.end == rangeEllipsis
}
func RangesToString(ranges []Range) string {
strs := []string{}
for _, r := range ranges {
s := ""
if r.begin == rangeEllipsis && r.end == rangeEllipsis {
s = ".."
} else if r.begin == r.end {
s = strconv.Itoa(r.begin)
} else {
if r.begin != rangeEllipsis {
s += strconv.Itoa(r.begin)
}
if r.begin != -1 {
s += ".."
if r.end != rangeEllipsis {
s += strconv.Itoa(r.end)
}
}
}
strs = append(strs, s)
}
return strings.Join(strs, ",")
}
// Token contains the tokenized part of the strings and its prefix length // Token contains the tokenized part of the strings and its prefix length
type Token struct { type Token struct {
text *util.Chars text *util.Chars
@@ -71,7 +41,7 @@ func (d Delimiter) String() string {
} }
func newRange(begin int, end int) Range { func newRange(begin int, end int) Range {
if begin == 1 && end != 1 { if begin == 1 {
begin = rangeEllipsis begin = rangeEllipsis
} }
if end == -1 { if end == -1 {
@@ -103,7 +73,7 @@ func ParseRange(str *string) (Range, bool) {
} }
begin, err1 := strconv.Atoi(ns[0]) begin, err1 := strconv.Atoi(ns[0])
end, err2 := strconv.Atoi(ns[1]) end, err2 := strconv.Atoi(ns[1])
if err1 != nil || err2 != nil || begin == 0 || end == 0 || begin < 0 && end > 0 { if err1 != nil || err2 != nil || begin == 0 || end == 0 {
return Range{}, false return Range{}, false
} }
return newRange(begin, end), true return newRange(begin, end), true

View File

@@ -40,18 +40,6 @@ func TestParseRange(t *testing.T) {
t.Errorf("%v", r) t.Errorf("%v", r)
} }
} }
{
i := "1..3..5"
if r, ok := ParseRange(&i); ok {
t.Errorf("%v", r)
}
}
{
i := "-3..3"
if r, ok := ParseRange(&i); ok {
t.Errorf("%v", r)
}
}
} }
func TestTokenize(t *testing.T) { func TestTokenize(t *testing.T) {

View File

@@ -11,11 +11,6 @@ func HasFullscreenRenderer() bool {
var DefaultBorderShape = BorderRounded var DefaultBorderShape = BorderRounded
func (a Attr) Merge(b Attr) Attr { func (a Attr) Merge(b Attr) Attr {
if b&AttrRegular > 0 {
// Only keep bold attribute set by the system
return b | (a & BoldForce)
}
return a | b return a | b
} }
@@ -23,7 +18,6 @@ const (
AttrUndefined = Attr(0) AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 8) AttrRegular = Attr(1 << 8)
AttrClear = Attr(1 << 9) AttrClear = Attr(1 << 9)
BoldForce = Attr(1 << 10)
Bold = Attr(1) Bold = Attr(1)
Dim = Attr(1 << 1) Dim = Attr(1 << 1)
@@ -36,7 +30,6 @@ const (
) )
func (r *FullscreenRenderer) Init() error { return nil } func (r *FullscreenRenderer) Init() error { return nil }
func (r *FullscreenRenderer) DefaultTheme() *ColorTheme { return nil }
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {} func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) Pause(bool) {} func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {} func (r *FullscreenRenderer) Resume(bool, bool) {}
@@ -55,6 +48,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {} func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window { func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
return nil return nil
} }

View File

@@ -73,15 +73,11 @@ func (r *LightRenderer) csi(code string) string {
func (r *LightRenderer) flush() { func (r *LightRenderer) flush() {
if r.queued.Len() > 0 { if r.queued.Len() > 0 {
r.flushRaw("\x1b[?7l\x1b[?25l" + r.queued.String() + "\x1b[?25h\x1b[?7h") fmt.Fprint(r.ttyout, "\x1b[?7l\x1b[?25l"+r.queued.String()+"\x1b[?25h\x1b[?7h")
r.queued.Reset() r.queued.Reset()
} }
} }
func (r *LightRenderer) flushRaw(sequence string) {
fmt.Fprint(r.ttyout, sequence)
}
// Light renderer // Light renderer
type LightRenderer struct { type LightRenderer struct {
closed *util.AtomicBool closed *util.AtomicBool
@@ -118,7 +114,7 @@ type LightRenderer struct {
type LightWindow struct { type LightWindow struct {
renderer *LightRenderer renderer *LightRenderer
colored bool colored bool
windowType WindowType preview bool
border BorderStyle border BorderStyle
top int top int
left int left int
@@ -174,6 +170,7 @@ func (r *LightRenderer) Init() error {
return err return err
} }
r.updateTerminalSize() r.updateTerminalSize()
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
if r.fullscreen { if r.fullscreen {
r.smcup() r.smcup()
@@ -658,13 +655,11 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
} }
func (r *LightRenderer) smcup() { func (r *LightRenderer) smcup() {
r.flush() r.csi("?1049h")
r.flushRaw("\x1b[?1049h")
} }
func (r *LightRenderer) rmcup() { func (r *LightRenderer) rmcup() {
r.flush() r.csi("?1049l")
r.flushRaw("\x1b[?1049l")
} }
func (r *LightRenderer) Pause(clear bool) { func (r *LightRenderer) Pause(clear bool) {
@@ -779,13 +774,11 @@ func (r *LightRenderer) MaxY() int {
return r.height return r.height
} }
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window { func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
width = util.Max(0, width)
height = util.Max(0, height)
w := &LightWindow{ w := &LightWindow{
renderer: r, renderer: r,
colored: r.theme.Colored, colored: r.theme.Colored,
windowType: windowType, preview: preview,
border: borderStyle, border: borderStyle,
top: top, top: top,
left: left, left: left,
@@ -794,25 +787,14 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, wind
tabstop: r.tabstop, tabstop: r.tabstop,
fg: colDefault, fg: colDefault,
bg: colDefault} bg: colDefault}
switch windowType { if preview {
case WindowBase:
w.fg = r.theme.Fg.Color
w.bg = r.theme.Bg.Color
case WindowList:
w.fg = r.theme.ListFg.Color
w.bg = r.theme.ListBg.Color
case WindowInput:
w.fg = r.theme.Input.Color
w.bg = r.theme.InputBg.Color
case WindowHeader:
w.fg = r.theme.Header.Color
w.bg = r.theme.HeaderBg.Color
case WindowPreview:
w.fg = r.theme.PreviewFg.Color w.fg = r.theme.PreviewFg.Color
w.bg = r.theme.PreviewBg.Color w.bg = r.theme.PreviewBg.Color
} else {
w.fg = r.theme.Fg.Color
w.bg = r.theme.Bg.Color
} }
if erase && !w.bg.IsDefault() && w.border.shape != BorderNone { if !w.bg.IsDefault() && w.border.shape != BorderNone {
// fzf --color bg:blue --border --padding 1,2
w.Erase() w.Erase()
} }
w.drawBorder(false) w.drawBorder(false)
@@ -828,9 +810,6 @@ func (w *LightWindow) DrawHBorder() {
} }
func (w *LightWindow) drawBorder(onlyHorizontal bool) { func (w *LightWindow) drawBorder(onlyHorizontal bool) {
if w.height == 0 {
return
}
switch w.border.shape { switch w.border.shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble: case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
w.drawBorderAround(onlyHorizontal) w.drawBorderAround(onlyHorizontal)
@@ -860,14 +839,7 @@ func (w *LightWindow) drawBorder(onlyHorizontal bool) {
func (w *LightWindow) drawBorderHorizontal(top, bottom bool) { func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
color := ColBorder color := ColBorder
switch w.windowType { if w.preview {
case WindowList:
color = ColListBorder
case WindowInput:
color = ColInputBorder
case WindowHeader:
color = ColHeaderBorder
case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
hw := runeWidth(w.border.top) hw := runeWidth(w.border.top)
@@ -885,14 +857,7 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
func (w *LightWindow) drawBorderVertical(left, right bool) { func (w *LightWindow) drawBorderVertical(left, right bool) {
vw := runeWidth(w.border.left) vw := runeWidth(w.border.left)
color := ColBorder color := ColBorder
switch w.windowType { if w.preview {
case WindowList:
color = ColListBorder
case WindowInput:
color = ColInputBorder
case WindowHeader:
color = ColHeaderBorder
case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
for y := 0; y < w.height; y++ { for y := 0; y < w.height; y++ {
@@ -912,14 +877,7 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
func (w *LightWindow) drawBorderAround(onlyHorizontal bool) { func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
w.Move(0, 0) w.Move(0, 0)
color := ColBorder color := ColBorder
switch w.windowType { if w.preview {
case WindowList:
color = ColListBorder
case WindowInput:
color = ColInputBorder
case WindowHeader:
color = ColHeaderBorder
case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
hw := runeWidth(w.border.top) hw := runeWidth(w.border.top)
@@ -971,6 +929,9 @@ func (w *LightWindow) Height() int {
func (w *LightWindow) Refresh() { func (w *LightWindow) Refresh() {
} }
func (w *LightWindow) Close() {
}
func (w *LightWindow) X() int { func (w *LightWindow) X() int {
return w.posx return w.posx
} }
@@ -979,16 +940,9 @@ func (w *LightWindow) Y() int {
return w.posy return w.posy
} }
func (w *LightWindow) EncloseX(x int) bool {
return x >= w.left && x < (w.left+w.width)
}
func (w *LightWindow) EncloseY(y int) bool {
return y >= w.top && y < (w.top+w.height)
}
func (w *LightWindow) Enclose(y int, x int) bool { func (w *LightWindow) Enclose(y int, x int) bool {
return w.EncloseX(x) && w.EncloseY(y) return x >= w.left && x < (w.left+w.width) &&
y >= w.top && y < (w.top+w.height)
} }
func (w *LightWindow) Move(y int, x int) { func (w *LightWindow) Move(y int, x int) {
@@ -1011,7 +965,7 @@ func attrCodes(attr Attr) []string {
if (attr & AttrClear) > 0 { if (attr & AttrClear) > 0 {
return codes return codes
} }
if (attr&Bold) > 0 || (attr&BoldForce) > 0 { if (attr & Bold) > 0 {
codes = append(codes, "1") codes = append(codes, "1")
} }
if (attr & Dim) > 0 { if (attr & Dim) > 0 {
@@ -1076,13 +1030,13 @@ func cleanse(str string) string {
func (w *LightWindow) CPrint(pair ColorPair, text string) { func (w *LightWindow) CPrint(pair ColorPair, text string) {
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr()) _, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
w.stderrInternal(cleanse(text), false, code) w.stderrInternal(cleanse(text), false, code)
w.csi("0m") w.csi("m")
} }
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) { func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
hasColors, code := w.csiColor(fg, bg, attr) hasColors, code := w.csiColor(fg, bg, attr)
if hasColors { if hasColors {
defer w.csi("0m") defer w.csi("m")
} }
w.stderrInternal(cleanse(text), false, code) w.stderrInternal(cleanse(text), false, code)
} }
@@ -1143,7 +1097,7 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
} }
} }
} }
if w.posx >= w.Width() { if w.posx+1 >= w.Width() {
if w.posy+1 >= w.height { if w.posy+1 >= w.height {
return FillSuspend return FillSuspend
} }
@@ -1164,14 +1118,6 @@ func (w *LightWindow) setBg() string {
return "\x1b[m" return "\x1b[m"
} }
func (w *LightWindow) LinkBegin(uri string, params string) {
w.renderer.queued.WriteString("\x1b]8;" + params + ";" + uri + "\x1b\\")
}
func (w *LightWindow) LinkEnd() {
w.renderer.queued.WriteString("\x1b]8;;\x1b\\")
}
func (w *LightWindow) Fill(text string) FillReturn { func (w *LightWindow) Fill(text string) FillReturn {
w.Move(w.posy, w.posx) w.Move(w.posy, w.posx)
code := w.setBg() code := w.setBg()
@@ -1187,7 +1133,7 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
bg = w.bg bg = w.bg
} }
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors { if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
defer w.csi("0m") defer w.csi("m")
return w.fill(text, resetCode) return w.fill(text, resetCode)
} }
return w.fill(text, w.setBg()) return w.fill(text, w.setBg())

View File

@@ -18,7 +18,7 @@ func IsLightRendererSupported() bool {
return true return true
} }
func (r *LightRenderer) DefaultTheme() *ColorTheme { func (r *LightRenderer) defaultTheme() *ColorTheme {
if strings.Contains(os.Getenv("TERM"), "256") { if strings.Contains(os.Getenv("TERM"), "256") {
return Dark256 return Dark256
} }

View File

@@ -39,7 +39,7 @@ func IsLightRendererSupported() bool {
return canSetVt100 return canSetVt100
} }
func (r *LightRenderer) DefaultTheme() *ColorTheme { func (r *LightRenderer) defaultTheme() *ColorTheme {
// the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178: // the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178:
if !IsLightRendererSupported() || os.Getenv("ConEmuPID") != "" || os.Getenv("TCELL_TRUECOLOR") == "disable" { if !IsLightRendererSupported() || os.Getenv("ConEmuPID") != "" || os.Getenv("TCELL_TRUECOLOR") == "disable" {
return Default16 return Default16

View File

@@ -4,7 +4,6 @@ package tui
import ( import (
"os" "os"
"regexp"
"time" "time"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
@@ -40,7 +39,7 @@ type Attr int32
type TcellWindow struct { type TcellWindow struct {
color bool color bool
windowType WindowType preview bool
top int top int
left int left int
width int width int
@@ -50,8 +49,6 @@ type TcellWindow struct {
lastY int lastY int
moveCursor bool moveCursor bool
borderStyle BorderStyle borderStyle BorderStyle
uri *string
params *string
} }
func (w *TcellWindow) Top() int { func (w *TcellWindow) Top() int {
@@ -97,7 +94,6 @@ const (
AttrUndefined = Attr(0) AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7) AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8) AttrClear = Attr(1 << 8)
BoldForce = Attr(1 << 10)
) )
func (r *FullscreenRenderer) PassThrough(str string) { func (r *FullscreenRenderer) PassThrough(str string) {
@@ -107,12 +103,8 @@ func (r *FullscreenRenderer) PassThrough(str string) {
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {} func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) DefaultTheme() *ColorTheme { func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
s, e := r.getScreen() if _screen.Colors() >= 256 {
if e != nil {
return Default16
}
if s.Colors() >= 256 {
return Dark256 return Dark256
} }
return Default16 return Default16
@@ -142,11 +134,6 @@ func (c Color) Style() tcell.Color {
} }
func (a Attr) Merge(b Attr) Attr { func (a Attr) Merge(b Attr) Attr {
if b&AttrRegular > 0 {
// Only keep bold attribute set by the system
return b | (a & BoldForce)
}
return a | b return a | b
} }
@@ -158,19 +145,8 @@ var (
_initialResize bool = true _initialResize bool = true
) )
func (r *FullscreenRenderer) getScreen() (tcell.Screen, error) {
if _screen == nil {
s, e := tcell.NewScreen()
if e != nil {
return nil, e
}
_screen = s
}
return _screen, nil
}
func (r *FullscreenRenderer) initScreen() error { func (r *FullscreenRenderer) initScreen() error {
s, e := r.getScreen() s, e := tcell.NewScreen()
if e != nil { if e != nil {
return e return e
} }
@@ -182,6 +158,7 @@ func (r *FullscreenRenderer) initScreen() error {
} else { } else {
s.DisableMouse() s.DisableMouse()
} }
_screen = s
return nil return nil
} }
@@ -194,6 +171,7 @@ func (r *FullscreenRenderer) Init() error {
if err := r.initScreen(); err != nil { if err := r.initScreen(); err != nil {
return err return err
} }
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
return nil return nil
} }
@@ -556,23 +534,14 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
_screen.Show() _screen.Show()
} }
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window { func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
width = util.Max(0, width) normal := ColNormal
height = util.Max(0, height) if preview {
normal := ColBorder
switch windowType {
case WindowList:
normal = ColNormal
case WindowHeader:
normal = ColHeader
case WindowInput:
normal = ColInput
case WindowPreview:
normal = ColPreview normal = ColPreview
} }
w := &TcellWindow{ w := &TcellWindow{
color: r.theme.Colored, color: r.theme.Colored,
windowType: windowType, preview: preview,
top: top, top: top,
left: left, left: left,
width: width, width: width,
@@ -583,6 +552,10 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
return w return w
} }
func (w *TcellWindow) Close() {
// TODO
}
func fill(x, y, w, h int, n ColorPair, r rune) { func fill(x, y, w, h int, n ColorPair, r rune) {
for ly := 0; ly <= h; ly++ { for ly := 0; ly <= h; ly++ {
for lx := 0; lx <= w; lx++ { for lx := 0; lx <= w; lx++ {
@@ -592,7 +565,11 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
} }
func (w *TcellWindow) Erase() { func (w *TcellWindow) Erase() {
if w.borderStyle.shape.HasLeft() {
fill(w.left-1, w.top, w.width, w.height-1, w.normal, ' ')
} else {
fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ') fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ')
}
w.drawBorder(false) w.drawBorder(false)
} }
@@ -601,16 +578,9 @@ func (w *TcellWindow) EraseMaybe() bool {
return true return true
} }
func (w *TcellWindow) EncloseX(x int) bool {
return x >= w.left && x < (w.left+w.width)
}
func (w *TcellWindow) EncloseY(y int) bool {
return y >= w.top && y < (w.top+w.height)
}
func (w *TcellWindow) Enclose(y int, x int) bool { func (w *TcellWindow) Enclose(y int, x int) bool {
return w.EncloseX(x) && w.EncloseY(y) return x >= w.left && x < (w.left+w.width) &&
y >= w.top && y < (w.top+w.height)
} }
func (w *TcellWindow) Move(y int, x int) { func (w *TcellWindow) Move(y int, x int) {
@@ -631,16 +601,6 @@ func (w *TcellWindow) Print(text string) {
w.printString(text, w.normal) w.printString(text, w.normal)
} }
func (w *TcellWindow) withUrl(style tcell.Style) tcell.Style {
if w.uri != nil {
style = style.Url(*w.uri)
if md := regexp.MustCompile(`id=([^:]+)`).FindStringSubmatch(*w.params); len(md) > 1 {
style = style.UrlId(md[1])
}
}
return style
}
func (w *TcellWindow) printString(text string, pair ColorPair) { func (w *TcellWindow) printString(text string, pair ColorPair) {
lx := 0 lx := 0
a := pair.Attr() a := pair.Attr()
@@ -655,7 +615,6 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
Blink(a&Attr(tcell.AttrBlink) != 0). Blink(a&Attr(tcell.AttrBlink) != 0).
Dim(a&Attr(tcell.AttrDim) != 0) Dim(a&Attr(tcell.AttrDim) != 0)
} }
style = w.withUrl(style)
gr := uniseg.NewGraphemes(text) gr := uniseg.NewGraphemes(text)
for gr.Next() { for gr.Next() {
@@ -700,13 +659,12 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
} }
style = style. style = style.
Blink(a&Attr(tcell.AttrBlink) != 0). Blink(a&Attr(tcell.AttrBlink) != 0).
Bold(a&Attr(tcell.AttrBold) != 0 || a&BoldForce != 0). Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0). Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0). Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0). Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0). StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0) Italic(a&Attr(tcell.AttrItalic) != 0)
style = w.withUrl(style)
gr := uniseg.NewGraphemes(text) gr := uniseg.NewGraphemes(text)
Loop: Loop:
@@ -758,16 +716,6 @@ func (w *TcellWindow) Fill(str string) FillReturn {
return w.fillString(str, w.normal) return w.fillString(str, w.normal)
} }
func (w *TcellWindow) LinkBegin(uri string, params string) {
w.uri = &uri
w.params = &params
}
func (w *TcellWindow) LinkEnd() {
w.uri = nil
w.params = nil
}
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn { func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
if fg == colDefault { if fg == colDefault {
fg = w.normal.Fg() fg = w.normal.Fg()
@@ -787,9 +735,6 @@ func (w *TcellWindow) DrawHBorder() {
} }
func (w *TcellWindow) drawBorder(onlyHorizontal bool) { func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
if w.height == 0 {
return
}
shape := w.borderStyle.shape shape := w.borderStyle.shape
if shape == BorderNone { if shape == BorderNone {
return return
@@ -802,17 +747,10 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
var style tcell.Style var style tcell.Style
if w.color { if w.color {
switch w.windowType { if w.preview {
case WindowBase:
style = ColBorder.style()
case WindowList:
style = ColListBorder.style()
case WindowHeader:
style = ColHeaderBorder.style()
case WindowInput:
style = ColInputBorder.style()
case WindowPreview:
style = ColPreviewBorder.style() style = ColPreviewBorder.style()
} else {
style = ColBorder.style()
} }
} else { } else {
style = w.normal.style() style = w.normal.style()

View File

@@ -15,7 +15,7 @@ func TtyIn() (*os.File, error) {
return os.Stdin, nil return os.Stdin, nil
} }
// TtyOut on Windows returns nil // TtyIn on Windows returns nil
func TtyOut() (*os.File, error) { func TtyOut() (*os.File, error) {
return nil, nil return nil, nil
} }

View File

@@ -205,24 +205,10 @@ type ColorAttr struct {
Attr Attr Attr Attr
} }
func (a ColorAttr) IsColorDefined() bool {
return a.Color != colUndefined
}
func NewColorAttr() ColorAttr { func NewColorAttr() ColorAttr {
return ColorAttr{Color: colUndefined, Attr: AttrUndefined} return ColorAttr{Color: colUndefined, Attr: AttrUndefined}
} }
func (a ColorAttr) Merge(other ColorAttr) ColorAttr {
if other.Color != colUndefined {
a.Color = other.Color
}
if other.Attr != AttrUndefined {
a.Attr = a.Attr.Merge(other.Attr)
}
return a
}
const ( const (
colUndefined Color = -2 colUndefined Color = -2
colDefault Color = -1 colDefault Color = -1
@@ -317,9 +303,6 @@ type ColorTheme struct {
Disabled ColorAttr Disabled ColorAttr
Fg ColorAttr Fg ColorAttr
Bg ColorAttr Bg ColorAttr
ListFg ColorAttr
ListBg ColorAttr
Nth ColorAttr
SelectedFg ColorAttr SelectedFg ColorAttr
SelectedBg ColorAttr SelectedBg ColorAttr
SelectedMatch ColorAttr SelectedMatch ColorAttr
@@ -328,9 +311,6 @@ type ColorTheme struct {
DarkBg ColorAttr DarkBg ColorAttr
Gutter ColorAttr Gutter ColorAttr
Prompt ColorAttr Prompt ColorAttr
InputBg ColorAttr
InputBorder ColorAttr
InputLabel ColorAttr
Match ColorAttr Match ColorAttr
Current ColorAttr Current ColorAttr
CurrentMatch ColorAttr CurrentMatch ColorAttr
@@ -339,19 +319,13 @@ type ColorTheme struct {
Cursor ColorAttr Cursor ColorAttr
Marker ColorAttr Marker ColorAttr
Header ColorAttr Header ColorAttr
HeaderBg ColorAttr
HeaderBorder ColorAttr
HeaderLabel ColorAttr
Separator ColorAttr Separator ColorAttr
Scrollbar ColorAttr Scrollbar ColorAttr
Border ColorAttr Border ColorAttr
PreviewBorder ColorAttr PreviewBorder ColorAttr
PreviewLabel ColorAttr
PreviewScrollbar ColorAttr PreviewScrollbar ColorAttr
BorderLabel ColorAttr BorderLabel ColorAttr
ListLabel ColorAttr PreviewLabel ColorAttr
ListBorder ColorAttr
GapLine ColorAttr
} }
type Event struct { type Event struct {
@@ -374,7 +348,6 @@ type BorderShape int
const ( const (
BorderUndefined BorderShape = iota BorderUndefined BorderShape = iota
BorderLine
BorderNone BorderNone
BorderRounded BorderRounded
BorderSharp BorderSharp
@@ -392,7 +365,7 @@ const (
func (s BorderShape) HasLeft() bool { func (s BorderShape) HasLeft() bool {
switch s { switch s {
case BorderNone, BorderLine, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left case BorderNone, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
return false return false
} }
return true return true
@@ -400,7 +373,7 @@ func (s BorderShape) HasLeft() bool {
func (s BorderShape) HasRight() bool { func (s BorderShape) HasRight() bool {
switch s { switch s {
case BorderNone, BorderLine, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
return false return false
} }
return true return true
@@ -408,24 +381,12 @@ func (s BorderShape) HasRight() bool {
func (s BorderShape) HasTop() bool { func (s BorderShape) HasTop() bool {
switch s { switch s {
case BorderNone, BorderLine, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top case BorderNone, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
return false return false
} }
return true return true
} }
func (s BorderShape) HasBottom() bool {
switch s {
case BorderNone, BorderLine, BorderLeft, BorderRight, BorderTop, BorderVertical: // No bottom
return false
}
return true
}
func (s BorderShape) Visible() bool {
return s != BorderNone
}
type BorderStyle struct { type BorderStyle struct {
shape BorderShape shape BorderShape
top rune top rune
@@ -441,18 +402,6 @@ type BorderStyle struct {
type BorderCharacter int type BorderCharacter int
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle { func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if shape == BorderNone {
return BorderStyle{
shape: shape,
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
topLeft: ' ',
topRight: ' ',
bottomLeft: ' ',
bottomRight: ' '}
}
if !unicode { if !unicode {
return BorderStyle{ return BorderStyle{
shape: shape, shape: shape,
@@ -549,6 +498,19 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
} }
} }
func MakeTransparentBorder() BorderStyle {
return BorderStyle{
shape: BorderRounded,
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
topLeft: ' ',
topRight: ' ',
bottomLeft: ' ',
bottomRight: ' '}
}
type TermSize struct { type TermSize struct {
Lines int Lines int
Columns int Columns int
@@ -556,18 +518,7 @@ type TermSize struct {
PxHeight int PxHeight int
} }
type WindowType int
const (
WindowBase WindowType = iota
WindowList
WindowPreview
WindowInput
WindowHeader
)
type Renderer interface { type Renderer interface {
DefaultTheme() *ColorTheme
Init() error Init() error
Resize(maxHeightFunc func(int) int) Resize(maxHeightFunc func(int) int)
Pause(clear bool) Pause(clear bool)
@@ -588,7 +539,7 @@ type Renderer interface {
Size() TermSize Size() TermSize
NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
} }
type Window interface { type Window interface {
@@ -601,11 +552,10 @@ type Window interface {
DrawHBorder() DrawHBorder()
Refresh() Refresh()
FinishFill() FinishFill()
Close()
X() int X() int
Y() int Y() int
EncloseX(x int) bool
EncloseY(y int) bool
Enclose(y int, x int) bool Enclose(y int, x int) bool
Move(y int, x int) Move(y int, x int)
@@ -614,8 +564,6 @@ type Window interface {
CPrint(color ColorPair, text string) CPrint(color ColorPair, text string)
Fill(text string) FillReturn Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn CFill(fg Color, bg Color, attr Attr, text string) FillReturn
LinkBegin(uri string, params string)
LinkEnd()
Erase() Erase()
EraseMaybe() bool EraseMaybe() bool
} }
@@ -662,11 +610,8 @@ var (
ColSpinner ColorPair ColSpinner ColorPair
ColInfo ColorPair ColInfo ColorPair
ColHeader ColorPair ColHeader ColorPair
ColHeaderBorder ColorPair
ColHeaderLabel ColorPair
ColSeparator ColorPair ColSeparator ColorPair
ColScrollbar ColorPair ColScrollbar ColorPair
ColGapLine ColorPair
ColBorder ColorPair ColBorder ColorPair
ColPreview ColorPair ColPreview ColorPair
ColPreviewBorder ColorPair ColPreviewBorder ColorPair
@@ -674,10 +619,6 @@ var (
ColPreviewLabel ColorPair ColPreviewLabel ColorPair
ColPreviewScrollbar ColorPair ColPreviewScrollbar ColorPair
ColPreviewSpinner ColorPair ColPreviewSpinner ColorPair
ColListBorder ColorPair
ColListLabel ColorPair
ColInputBorder ColorPair
ColInputLabel ColorPair
) )
func EmptyTheme() *ColorTheme { func EmptyTheme() *ColorTheme {
@@ -686,8 +627,6 @@ func EmptyTheme() *ColorTheme {
Input: ColorAttr{colUndefined, AttrUndefined}, Input: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined}, Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined}, Bg: ColorAttr{colUndefined, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -703,8 +642,6 @@ func EmptyTheme() *ColorTheme {
Header: ColorAttr{colUndefined, AttrUndefined}, Header: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined}, Border: ColorAttr{colUndefined, AttrUndefined},
BorderLabel: ColorAttr{colUndefined, AttrUndefined}, BorderLabel: ColorAttr{colUndefined, AttrUndefined},
ListLabel: ColorAttr{colUndefined, AttrUndefined},
ListBorder: ColorAttr{colUndefined, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined}, Disabled: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined}, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined}, PreviewBg: ColorAttr{colUndefined, AttrUndefined},
@@ -714,14 +651,6 @@ func EmptyTheme() *ColorTheme {
PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
} }
} }
@@ -731,8 +660,6 @@ func NoColorTheme() *ColorTheme {
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colDefault, AttrUndefined},
ListBg: ColorAttr{colDefault, AttrUndefined},
SelectedFg: ColorAttr{colDefault, AttrUndefined}, SelectedFg: ColorAttr{colDefault, AttrUndefined},
SelectedBg: ColorAttr{colDefault, AttrUndefined}, SelectedBg: ColorAttr{colDefault, AttrUndefined},
SelectedMatch: ColorAttr{colDefault, AttrUndefined}, SelectedMatch: ColorAttr{colDefault, AttrUndefined},
@@ -755,18 +682,8 @@ func NoColorTheme() *ColorTheme {
PreviewBorder: ColorAttr{colDefault, AttrUndefined}, PreviewBorder: ColorAttr{colDefault, AttrUndefined},
PreviewScrollbar: ColorAttr{colDefault, AttrUndefined}, PreviewScrollbar: ColorAttr{colDefault, AttrUndefined},
PreviewLabel: ColorAttr{colDefault, AttrUndefined}, PreviewLabel: ColorAttr{colDefault, AttrUndefined},
ListLabel: ColorAttr{colDefault, AttrUndefined},
ListBorder: ColorAttr{colDefault, AttrUndefined},
Separator: ColorAttr{colDefault, AttrUndefined}, Separator: ColorAttr{colDefault, AttrUndefined},
Scrollbar: ColorAttr{colDefault, AttrUndefined}, Scrollbar: ColorAttr{colDefault, AttrUndefined},
InputBg: ColorAttr{colDefault, AttrUndefined},
InputBorder: ColorAttr{colDefault, AttrUndefined},
InputLabel: ColorAttr{colDefault, AttrUndefined},
HeaderBg: ColorAttr{colDefault, AttrUndefined},
HeaderBorder: ColorAttr{colDefault, AttrUndefined},
HeaderLabel: ColorAttr{colDefault, AttrUndefined},
GapLine: ColorAttr{colDefault, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
} }
} }
@@ -776,8 +693,6 @@ func init() {
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -800,23 +715,14 @@ func init() {
PreviewBorder: ColorAttr{colUndefined, AttrUndefined}, PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined}, PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
ListLabel: ColorAttr{colUndefined, AttrUndefined},
ListBorder: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
} }
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -839,23 +745,14 @@ func init() {
PreviewBorder: ColorAttr{colUndefined, AttrUndefined}, PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined}, PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
ListLabel: ColorAttr{colUndefined, AttrUndefined},
ListBorder: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
} }
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -878,22 +775,12 @@ func init() {
PreviewBorder: ColorAttr{colUndefined, AttrUndefined}, PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined}, PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
ListLabel: ColorAttr{colUndefined, AttrUndefined},
ListBorder: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
} }
} }
func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInputWindow bool, hasHeaderWindow bool) { func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
if forceBlack { if forceBlack {
theme.Bg = ColorAttr{colBlack, AttrUndefined} theme.Bg = ColorAttr{colBlack, AttrUndefined}
} }
@@ -914,9 +801,7 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg) theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Prompt = o(baseTheme.Prompt, theme.Prompt) theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
theme.Match = o(baseTheme.Match, theme.Match) theme.Match = o(baseTheme.Match, theme.Match)
// Inherit from 'fg', so that we don't have to write 'current-fg:dim' theme.Current = o(baseTheme.Current, theme.Current)
// e.g. fzf --delimiter / --nth -1 --color fg:dim,nth:regular
theme.Current = theme.Fg.Merge(o(baseTheme.Current, theme.Current))
theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch) theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch)
theme.Spinner = o(baseTheme.Spinner, theme.Spinner) theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
theme.Info = o(baseTheme.Info, theme.Info) theme.Info = o(baseTheme.Info, theme.Info)
@@ -926,15 +811,9 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
theme.Border = o(baseTheme.Border, theme.Border) theme.Border = o(baseTheme.Border, theme.Border)
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel) theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
undefined := NewColorAttr()
scrollbarDefined := theme.Scrollbar != undefined
previewBorderDefined := theme.PreviewBorder != undefined
// These colors are not defined in the base themes // These colors are not defined in the base themes
theme.ListFg = o(theme.Fg, theme.ListFg) theme.SelectedFg = o(theme.Fg, theme.SelectedFg)
theme.ListBg = o(theme.Bg, theme.ListBg) theme.SelectedBg = o(theme.Bg, theme.SelectedBg)
theme.SelectedFg = o(theme.ListFg, theme.SelectedFg)
theme.SelectedBg = o(theme.ListBg, theme.SelectedBg)
theme.SelectedMatch = o(theme.Match, theme.SelectedMatch) theme.SelectedMatch = o(theme.Match, theme.SelectedMatch)
theme.Disabled = o(theme.Input, theme.Disabled) theme.Disabled = o(theme.Input, theme.Disabled)
theme.Gutter = o(theme.DarkBg, theme.Gutter) theme.Gutter = o(theme.DarkBg, theme.Gutter)
@@ -942,38 +821,9 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
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)
theme.PreviewBorder = o(theme.Border, theme.PreviewBorder) theme.PreviewBorder = o(theme.Border, theme.PreviewBorder)
theme.ListLabel = o(theme.BorderLabel, theme.ListLabel) theme.Separator = o(theme.Border, theme.Separator)
theme.ListBorder = o(theme.Border, theme.ListBorder) theme.Scrollbar = o(theme.Border, theme.Scrollbar)
theme.Separator = o(theme.ListBorder, theme.Separator)
theme.Scrollbar = o(theme.ListBorder, theme.Scrollbar)
theme.GapLine = o(theme.ListBorder, theme.GapLine)
/*
--color list-border:green
--color scrollbar:red
--color scrollbar:red,list-border:green
--color scrollbar:red,preview-border:green
*/
if scrollbarDefined && !previewBorderDefined {
theme.PreviewScrollbar = o(theme.Scrollbar, theme.PreviewScrollbar)
} else {
theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar) theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar)
}
if hasInputWindow {
theme.InputBg = o(theme.Bg, theme.InputBg)
} else {
// We shouldn't use input-bg if there's no separate input window
// e.g. fzf --color 'list-bg:green,input-bg:red' --no-input-border
theme.InputBg = o(theme.Bg, theme.ListBg)
}
theme.InputBorder = o(theme.Border, theme.InputBorder)
theme.InputLabel = o(theme.BorderLabel, theme.InputLabel)
if hasHeaderWindow {
theme.HeaderBg = o(theme.Bg, theme.HeaderBg)
} else {
theme.HeaderBg = o(theme.Bg, theme.ListBg)
}
theme.HeaderBorder = o(theme.Border, theme.HeaderBorder)
theme.HeaderLabel = o(theme.BorderLabel, theme.HeaderLabel)
initPalette(theme) initPalette(theme)
} }
@@ -985,19 +835,19 @@ func initPalette(theme *ColorTheme) {
} }
return ColorPair{fg.Color, bg.Color, fg.Attr} return ColorPair{fg.Color, bg.Color, fg.Attr}
} }
blank := theme.ListFg blank := theme.Fg
blank.Attr = AttrRegular blank.Attr = AttrRegular
ColPrompt = pair(theme.Prompt, theme.InputBg) ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.ListFg, theme.ListBg) ColNormal = pair(theme.Fg, theme.Bg)
ColSelected = pair(theme.SelectedFg, theme.SelectedBg) ColSelected = pair(theme.SelectedFg, theme.SelectedBg)
ColInput = pair(theme.Input, theme.InputBg) ColInput = pair(theme.Input, theme.Bg)
ColDisabled = pair(theme.Disabled, theme.ListBg) ColDisabled = pair(theme.Disabled, theme.Bg)
ColMatch = pair(theme.Match, theme.ListBg) ColMatch = pair(theme.Match, theme.Bg)
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg) ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
ColCursor = pair(theme.Cursor, theme.Gutter) ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter) ColCursorEmpty = pair(blank, theme.Gutter)
if theme.SelectedBg.Color != theme.ListBg.Color { if theme.SelectedBg.Color != theme.Bg.Color {
ColMarker = pair(theme.Marker, theme.SelectedBg) ColMarker = pair(theme.Marker, theme.SelectedBg)
} else { } else {
ColMarker = pair(theme.Marker, theme.Gutter) ColMarker = pair(theme.Marker, theme.Gutter)
@@ -1008,11 +858,11 @@ func initPalette(theme *ColorTheme) {
ColCurrentCursorEmpty = pair(blank, theme.DarkBg) ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
ColCurrentMarker = pair(theme.Marker, theme.DarkBg) ColCurrentMarker = pair(theme.Marker, theme.DarkBg)
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg) ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.InputBg) ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.InputBg) ColInfo = pair(theme.Info, theme.Bg)
ColSeparator = pair(theme.Separator, theme.InputBg) ColHeader = pair(theme.Header, theme.Bg)
ColScrollbar = pair(theme.Scrollbar, theme.ListBg) ColSeparator = pair(theme.Separator, theme.Bg)
ColGapLine = pair(theme.GapLine, theme.ListBg) ColScrollbar = pair(theme.Scrollbar, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg) ColBorder = pair(theme.Border, theme.Bg)
ColBorderLabel = pair(theme.BorderLabel, theme.Bg) ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
ColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg) ColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg)
@@ -1020,13 +870,6 @@ func initPalette(theme *ColorTheme) {
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg) ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg) ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg) ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
ColListLabel = pair(theme.ListLabel, theme.ListBg)
ColListBorder = pair(theme.ListBorder, theme.ListBg)
ColInputBorder = pair(theme.InputBorder, theme.InputBg)
ColInputLabel = pair(theme.InputLabel, theme.InputBg)
ColHeader = pair(theme.Header, theme.HeaderBg)
ColHeaderBorder = pair(theme.HeaderBorder, theme.HeaderBg)
ColHeaderLabel = pair(theme.HeaderLabel, theme.HeaderBg)
} }
func runeWidth(r rune) int { func runeWidth(r rune) int {

View File

@@ -306,5 +306,5 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
} }
} }
return wrapped, overflow return wrapped, false
} }

View File

@@ -44,6 +44,11 @@ func needWinpty(opts *Options) bool {
} }
func runWinpty(args []string, opts *Options) (int, error) { func runWinpty(args []string, opts *Options) (int, error) {
sh, err := sh()
if err != nil {
return ExitError, err
}
argStr := escapeSingleQuote(args[0]) argStr := escapeSingleQuote(args[0])
for _, arg := range args[1:] { for _, arg := range args[1:] {
argStr += " " + escapeSingleQuote(arg) argStr += " " + escapeSingleQuote(arg)
@@ -51,30 +56,20 @@ func runWinpty(args []string, opts *Options) (int, error) {
argStr += ` --no-winpty` argStr += ` --no-winpty`
if isMintty345() { if isMintty345() {
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) { return runProxy(argStr, func(temp string) *exec.Cmd {
sh, err := sh(needBash)
if err != nil {
return nil, err
}
cmd := exec.Command(sh, temp) cmd := exec.Command(sh, temp)
cmd.Env = append(os.Environ(), "MSYS=enable_pcon") cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
return cmd, nil return cmd
}, opts, false) }, opts, false)
} }
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) { return runProxy(argStr, func(temp string) *exec.Cmd {
sh, err := sh(needBash)
if err != nil {
return nil, err
}
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp)) cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
return cmd, nil return cmd
}, opts, false) }, opts, false)
} }

View File

@@ -1442,14 +1442,10 @@ class TestGoFZF < TestBase
writelines(['=' * 10_000 + '0123456789']) writelines(['=' * 10_000 + '0123456789'])
[0, 3, 6].each do |off| [0, 3, 6].each do |off|
tmux.prepare tmux.prepare
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 --bind space:toggle-hscroll < #{tempname}", :Enter tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '··') } tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '..') }
tmux.send_keys '9' tmux.send_keys '9'
tmux.until { |lines| assert lines[-3]&.end_with?('789') } tmux.until { |lines| assert lines[-3]&.end_with?('789') }
tmux.send_keys :Space
tmux.until { |lines| assert lines[-3]&.end_with?('=··') }
tmux.send_keys :Space
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
tmux.send_keys :Enter tmux.send_keys :Enter
end end
end end
@@ -2137,11 +2133,7 @@ class TestGoFZF < TestBase
end end
def test_keep_right def test_keep_right
tmux.send_keys "seq 10000 | #{FZF} --read0 --keep-right --no-multi-line --bind space:toggle-multi-line", :Enter tmux.send_keys "seq 10000 | #{FZF} --read0 --keep-right --no-multi-line", :Enter
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('> 1') }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('9999␊10000') } tmux.until { |lines| assert lines.any_include?('9999␊10000') }
end end
@@ -2646,7 +2638,7 @@ class TestGoFZF < TestBase
end end
def test_change_preview_window def test_change_preview_window
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --no-preview-border --bind '" \ tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --preview-window border-none --bind '" \
'a:change-preview(echo __{}__),' \ 'a:change-preview(echo __{}__),' \
'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \ 'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \
'c:change-preview(),d:change-preview-window(hidden),' \ 'c:change-preview(),d:change-preview-window(hidden),' \
@@ -3081,21 +3073,6 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines, '/1/1/' } tmux.until { |lines| assert_includes lines, '/1/1/' }
end end
def test_alternative_preview_window_opts
tmux.send_keys "seq 10 | #{FZF} --preview-window '~5,2,+0,<100000(~0,+100,wrap,noinfo)' --preview 'seq 1000'", :Enter
tmux.until { |lines| assert_equal 10, lines.item_count }
tmux.until do |lines|
assert_equal ['╭────╮', '│ 10 │', '│ 0 │', '│ 10 │', '│ 1 │'], lines.take(5).map(&:strip)
end
end
def test_preview_window_width_exception
tmux.send_keys "seq 10 | #{FZF} --scrollbar --preview-window border-left --border --preview 'seq 1000'", :Enter
tmux.until do |lines|
assert lines[1]&.end_with?(' 1/1000││')
end
end
def test_become def test_become
tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count } tmux.until { |lines| assert_equal 100, lines.item_count }
@@ -3389,397 +3366,6 @@ class TestGoFZF < TestBase
tmux.send_keys :Space tmux.send_keys :Space
tmux.until { |lines| assert_includes lines[-3], 'bar' } tmux.until { |lines| assert_includes lines[-3], 'bar' }
end end
def test_boundary_match
# Underscore boundaries should be ranked lower
{
default: [' x '] + %w[/x/ [x] -x- -x_ _x- _x_],
path: ['/x/', ' x '] + %w[[x] -x- -x_ _x- _x_],
history: ['[x]', '-x-', ' x '] + %w[/x/ -x_ _x- _x_]
}.each do |scheme, expected|
result = `printf -- 'xxx\n-xx\nxx-\n_x_\n_x-\n-x_\n[x]\n-x-\n x \n/x/\n' | #{FZF} -f"'x'" --scheme=#{scheme}`.lines(chomp: true)
assert_equal expected, result
end
end
def test_preview_window_noinfo
# │ 1 ││
tmux.send_keys %(#{FZF} --preview 'seq 1000' --preview-window top,noinfo --scrollbar --bind space:change-preview-window:info), :Enter
tmux.until do |lines|
assert lines[1]&.start_with?('│ 1')
assert lines[1]&.end_with?(' ││')
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines[1]&.start_with?('│ 1')
assert lines[1]&.end_with?('1000││')
end
end
def test_gap
tmux.send_keys %(seq 100 | #{FZF} --gap --border --reverse), :Enter
block = <<~BLOCK
>
100/100
> 1
2
3
4
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_gap_2
tmux.send_keys %(seq 100 | #{FZF} --gap=2 --gap-line xyz --border --reverse), :Enter
block = <<~BLOCK
>
100/100
> 1
xyzxyzxyzxyzxy
2
xyzxyzxyzxyzxy
3
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_list_border_and_label
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --query 1 --padding 1,2), :Enter
block = <<~BLOCK
11
> 10
3
2
1
19/97
> 1
list
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_input_border_and_label
tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2), :Enter
block = <<~BLOCK
11
> 10
3
2
1
input
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_input_border_and_label_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 --header-first), :Enter
block = <<~BLOCK
11
> 10
input
19/97
> 1
3
2
1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_list_input_border_and_label
tmux.send_keys %(
seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \
--bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \
--bind 'space:change-input-label( input )+change-list-label( list )'
).strip, :Enter
block = <<~BLOCK
11
> 10
LIST
3
2
1
INPUT
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
tmux.send_keys :Space
block = <<~BLOCK
11
> 10
list
3
2
1
input
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_list_input_border_and_label_header_first
tmux.send_keys %(
seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \
--bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \
--bind 'space:change-input-label( input )+change-list-label( list )' --header-first
).strip, :Enter
block = <<~BLOCK
11
> 10
LIST
INPUT
19/97
> 1
3
2
1
BLOCK
tmux.until { assert_block(block, _1) }
tmux.send_keys :Space
block = <<~BLOCK
11
> 10
list
input
19/97
> 1
3
2
1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label
tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter
block = <<~BLOCK
12
11
> 10
3
2
1
header
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter
block = <<~BLOCK
12
11
> 10
19/97
> 1
3
2
1
header
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label_with_list_border
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter
block = <<~BLOCK
12
11
> 10
list
3
2
1
header
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label_with_list_border_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter
block = <<~BLOCK
12
11
> 10
list
19/97
> 1
3
2
1
header
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_all_borders
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom), :Enter
block = <<~BLOCK
12
11
> 10
list
3
2
1
header
19/97
> 1
input
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_all_borders_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom --header-first), :Enter
block = <<~BLOCK
12
11
> 10
list
19/97
> 1
input
3
2
1
header
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_style_full_adaptive_height
tmux.send_keys %(seq 1| #{FZF} --style=full --height=~100% --header-lines=1 --info=default), :Enter
block = <<~BLOCK
1
0/0
>
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_style_full_adaptive_height_double
tmux.send_keys %(seq 1| #{FZF} --style=full:double --border --height=~100% --header-lines=1 --info=default), :Enter
block = <<~BLOCK
1
0/0
>
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_change_nth
input = [
*[''] * 1000,
'foo bar bar bar bar',
'foo foo bar bar bar',
'foo foo foo bar bar',
'foo foo foo foo bar',
*[''] * 1000
]
writelines(input)
nths = '1,2..4,-1,-3..,..2'
tmux.send_keys %(#{FZF} -qfoo -n#{nths} --bind 'space:change-nth(2|3|4|5|),result:transform-prompt:echo "[$FZF_NTH] "' < #{tempname}), :Enter
tmux.until do |lines|
assert lines.any_include?("[#{nths}] foo")
assert_equal 4, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines.any_include?('[2] foo')
assert_equal 3, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines.any_include?('[3] foo')
assert_equal 2, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines.any_include?('[4] foo')
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines.any_include?('[5] foo')
assert_equal 0, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines.any_include?("[#{nths}] foo")
assert_equal 4, lines.match_count
end
end
end end
module TestShell module TestShell
@@ -4091,23 +3677,6 @@ module CompletionTest
tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] } tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] }
end end
def test_completion_in_command_sequence
tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter
tmux.prepare
triggers = ['**', '~~', '++', 'ff', '/']
triggers.concat(['&', '[', ';', '`']) if instance_of?(TestZsh)
triggers.each do |trigger|
set_var('FZF_COMPLETION_TRIGGER', trigger)
command = "echo foo; QUX=THUD unset FZFFOOBR#{trigger}"
tmux.send_keys command.sub(/(;|`)$/, '\\\\\1'), :Tab
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Enter
tmux.until { |lines| assert_equal 'echo foo; QUX=THUD unset FZFFOOBAR', lines[-1] }
end
end
def test_file_completion_unicode def test_file_completion_unicode
FileUtils.mkdir_p('/tmp/fzf-test') FileUtils.mkdir_p('/tmp/fzf-test')
tmux.paste "cd /tmp/fzf-test; echo test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'" tmux.paste "cd /tmp/fzf-test; echo test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'"