mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-16 12:43:50 -07:00
Compare commits
99 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
04d0b0223f | ||
|
78ad6d2d88 | ||
|
22cbd9fa58 | ||
|
984049586a | ||
|
cdfc2b92e3 | ||
|
4530abe8df | ||
|
586020b8b6 | ||
|
3a8626fd04 | ||
|
a6e483a434 | ||
|
6a942e56b1 | ||
|
87c91550ad | ||
|
731daf0f37 | ||
|
f931e53890 | ||
|
b5efc68737 | ||
|
b9e6e7926c | ||
|
845034c81c | ||
|
54d42e3f40 | ||
|
e03ac3136e | ||
|
6fb41a202a | ||
|
4bef330ce1 | ||
|
8a5f719964 | ||
|
209d5e8e90 | ||
|
9d041aa582 | ||
|
6532b3e655 | ||
|
c1c355160d | ||
|
83515d5610 | ||
|
aa10dccf90 | ||
|
f4fd53211a | ||
|
4993d19466 | ||
|
19f9bbca0d | ||
|
779d8e1627 | ||
|
bb07410448 | ||
|
d826f9e72f | ||
|
6a6130615d | ||
|
a8e7021be2 | ||
|
38259d0382 | ||
|
f7e7259910 | ||
|
f0bfeba733 | ||
|
c3a7a24eea | ||
|
bbbcd780c9 | ||
|
475469a2e7 | ||
|
3a7447dcb6 | ||
|
e5d8cbd383 | ||
|
3c08dca7e7 | ||
|
d083f01d22 | ||
|
68cf393644 | ||
|
18f7230662 | ||
|
728f735281 | ||
|
ecc418ba77 | ||
|
3af5b7f2ac | ||
|
7a7cfcacbe | ||
|
52594355bf | ||
|
0d06c28b19 | ||
|
ccc4677252 | ||
|
821fc9feed | ||
|
82b46726fc | ||
|
8df872a482 | ||
|
c79c306adb | ||
|
51fdaad002 | ||
|
885cd8ff04 | ||
|
2707af403a | ||
|
2d227e5222 | ||
|
70529878e2 | ||
|
3b7a962dc6 | ||
|
6dcf5c3d7d | ||
|
b089bb5e7b | ||
|
a91a67668e | ||
|
220a908118 | ||
|
54841248e7 | ||
|
a0b42e6538 | ||
|
3312cf525d | ||
|
2093667548 | ||
|
3c868d7961 | ||
|
707f4f5816 | ||
|
b3ab6311c5 | ||
|
d56f605b63 | ||
|
f8b713f425 | ||
|
5209e95bc7 | ||
|
ef67a45702 | ||
|
b88eb72ac2 | ||
|
32847f7254 | ||
|
71df93b534 | ||
|
bb028191f8 | ||
|
19af8fc7d8 | ||
|
a06671b47f | ||
|
5f385d88e0 | ||
|
9cb7a364a3 | ||
|
f68cbc577d | ||
|
dc975e8974 | ||
|
4311ade535 | ||
|
cd23401411 | ||
|
176ee6910f | ||
|
13c8f3d3aa | ||
|
ce9af687bc | ||
|
43f0d0cacd | ||
|
20b4e6953e | ||
|
7da287e3aa | ||
|
205f885d69 | ||
|
3715cd349d |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1 +1 @@
|
|||||||
custom: ["https://paypal.me/junegunn", "https://www.buymeacoffee.com/junegunn"]
|
github: junegunn
|
||||||
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,3 +4,7 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
15
.github/workflows/codeql-analysis.yml
vendored
15
.github/workflows/codeql-analysis.yml
vendored
@@ -8,8 +8,15 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
|
permissions:
|
||||||
|
actions: read # for github/codeql-action/init to get workflow details
|
||||||
|
contents: read # for actions/checkout to fetch code
|
||||||
|
security-events: write # for github/codeql-action/autobuild to send a status report
|
||||||
name: Analyze
|
name: Analyze
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -20,18 +27,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@86f3159a697a097a813ad9bfa0002412d97690a4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@86f3159a697a097a813ad9bfa0002412d97690a4
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@86f3159a697a097a813ad9bfa0002412d97690a4
|
||||||
|
14
.github/workflows/linux.yml
vendored
14
.github/workflows/linux.yml
vendored
@@ -8,24 +8,24 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go: [1.14, 1.16]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f # v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: 1.19
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1.62.0
|
uses: ruby/setup-ruby@3068fa83f9cbd7ae106cac45483635a2f3a195c9
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
14
.github/workflows/macos.yml
vendored
14
.github/workflows/macos.yml
vendored
@@ -8,24 +8,24 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go: [1.14, 1.16]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f # v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: 1.18
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1.62.0
|
uses: ruby/setup-ruby@3068fa83f9cbd7ae106cac45483635a2f3a195c9
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@ builds:
|
|||||||
post: |
|
post: |
|
||||||
sh -c '
|
sh -c '
|
||||||
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
||||||
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
|
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
||||||
bundle_id = "kr.junegunn.fzf"
|
bundle_id = "kr.junegunn.fzf"
|
||||||
apple_id {
|
apple_id {
|
||||||
username = "junegunn.c@gmail.com"
|
username = "junegunn.c@gmail.com"
|
||||||
|
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@@ -0,0 +1 @@
|
|||||||
|
golang 1.19
|
92
ADVANCED.md
92
ADVANCED.md
@@ -1,7 +1,7 @@
|
|||||||
Advanced fzf examples
|
Advanced fzf examples
|
||||||
======================
|
======================
|
||||||
|
|
||||||
*(Last update: 2021/05/22)*
|
*(Last update: 2022/08/25)*
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ Advanced fzf examples
|
|||||||
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
||||||
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
|
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
|
||||||
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
|
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
|
||||||
|
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
|
||||||
* [Log tailing](#log-tailing)
|
* [Log tailing](#log-tailing)
|
||||||
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
||||||
* [Files listed in `git status`](#files-listed-in-git-status)
|
* [Files listed in `git status`](#files-listed-in-git-status)
|
||||||
@@ -405,6 +406,40 @@ IFS=: read -ra selected < <(
|
|||||||
- We reverted `--color` option for customizing how the matching chunks are
|
- We reverted `--color` option for customizing how the matching chunks are
|
||||||
displayed in the second phase
|
displayed in the second phase
|
||||||
|
|
||||||
|
### Switching between Ripgrep mode and fzf mode
|
||||||
|
|
||||||
|
*(Requires fzf 0.30.0 or above)*
|
||||||
|
|
||||||
|
fzf 0.30.0 added `rebind` action so we can "rebind" the bindings that were
|
||||||
|
previously "unbound" via `unbind`.
|
||||||
|
|
||||||
|
This is an improved version of the previous example that allows us to switch
|
||||||
|
between Ripgrep launcher mode and fzf-only filtering mode via CTRL-R and
|
||||||
|
CTRL-F.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Switch between Ripgrep launcher mode (CTRL-R) and fzf filtering mode (CTRL-F)
|
||||||
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
|
INITIAL_QUERY="${*:-}"
|
||||||
|
IFS=: read -ra selected < <(
|
||||||
|
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
||||||
|
fzf --ansi \
|
||||||
|
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||||
|
--disabled --query "$INITIAL_QUERY" \
|
||||||
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
|
--bind "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+clear-query+rebind(ctrl-r)" \
|
||||||
|
--bind "ctrl-r:unbind(ctrl-r)+change-prompt(1. ripgrep> )+disable-search+reload($RG_PREFIX {q} || true)+rebind(change,ctrl-f)" \
|
||||||
|
--prompt '1. Ripgrep> ' \
|
||||||
|
--delimiter : \
|
||||||
|
--header '╱ CTRL-R (Ripgrep mode) ╱ CTRL-F (fzf mode) ╱' \
|
||||||
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
||||||
|
)
|
||||||
|
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
||||||
|
```
|
||||||
|
|
||||||
Log tailing
|
Log tailing
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -429,37 +464,49 @@ Admittedly, that was a silly example. Here's a practical one for browsing
|
|||||||
Kubernetes pods.
|
Kubernetes pods.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/usr/bin/env bash
|
pods() {
|
||||||
|
FZF_DEFAULT_COMMAND="kubectl get pods --all-namespaces" \
|
||||||
read -ra tokens < <(
|
fzf --info=inline --layout=reverse --header-lines=1 \
|
||||||
kubectl get pods --all-namespaces |
|
|
||||||
fzf --info=inline --layout=reverse --header-lines=1 --border \
|
|
||||||
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
|
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
|
||||||
--header $'Press CTRL-O to open log in editor\n\n' \
|
--header $'╱ Enter (kubectl exec) ╱ CTRL-O (open log in editor) ╱ CTRL-R (reload) ╱\n\n' \
|
||||||
--bind ctrl-/:toggle-preview \
|
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
||||||
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --namespace {1} {2}) > /dev/tty' \
|
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash > /dev/tty' \
|
||||||
--preview-window up,follow \
|
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2}) > /dev/tty' \
|
||||||
--preview 'kubectl logs --follow --tail=100000 --namespace {1} {2}' "$@"
|
--bind 'ctrl-r:reload:$FZF_DEFAULT_COMMAND' \
|
||||||
)
|
--preview-window up:follow \
|
||||||
[ ${#tokens} -gt 1 ] &&
|
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
|
||||||
kubectl exec -it --namespace "${tokens[0]}" "${tokens[1]}" -- bash
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- The preview window will *"log tail"* the pod
|
- The preview window will *"log tail"* the pod
|
||||||
- Holding on to a large amount of log will consume a lot of memory. So we
|
- Holding on to a large amount of log will consume a lot of memory. So we
|
||||||
limited the initial log amount with `--tail=100000`.
|
limited the initial log amount with `--tail=10000`.
|
||||||
- With `execute` binding, you can press CTRL-O to open the log in your editor
|
- `execute` bindings allow you to run any command without leaving fzf
|
||||||
without leaving fzf
|
- Press enter key on a pod to `kubectl exec` into it
|
||||||
- Select a pod (with an enter key) to `kubectl exec` into it
|
- Press CTRL-O to open the log in your editor
|
||||||
|
- Press CTRL-R to reload the pod list
|
||||||
|
- Press CTRL-/ repeatedly to to rotate through a different sets of preview
|
||||||
|
window options
|
||||||
|
1. `80%,border-bottom`
|
||||||
|
1. `hidden`
|
||||||
|
1. Empty string after `|` translates to the default options from `--preview-window`
|
||||||
|
|
||||||
Key bindings for git objects
|
Key bindings for git objects
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
I have [blogged](https://junegunn.kr/2016/07/fzf-git) about my fzf+git key
|
Oftentimes, you want to put the identifiers of various Git object to the
|
||||||
bindings a few years ago. I'm going to show them here again, because they are
|
command-line. For example, it is common to write commands like these:
|
||||||
seriously useful.
|
|
||||||
|
```sh
|
||||||
|
git checkout [SOME_COMMIT_HASH or BRANCH or TAG]
|
||||||
|
git diff [SOME_COMMIT_HASH or BRANCH or TAG] [SOME_COMMIT_HASH or BRANCH or TAG]
|
||||||
|
```
|
||||||
|
|
||||||
|
[fzf-git.sh](https://github.com/junegunn/fzf-git.sh) project defines a set of
|
||||||
|
fzf-based key bindings for Git objects. I strongly recommend that you check
|
||||||
|
them out because they are seriously useful.
|
||||||
|
|
||||||
### Files listed in `git status`
|
### Files listed in `git status`
|
||||||
|
|
||||||
@@ -479,9 +526,6 @@ seriously useful.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
The full source code can be found [here](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236).
|
|
||||||
|
|
||||||
Color themes
|
Color themes
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
2
BUILD.md
2
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Go 1.13 or above
|
- Go 1.17 or above
|
||||||
|
|
||||||
### Using Makefile
|
### Using Makefile
|
||||||
|
|
||||||
|
152
CHANGELOG.md
152
CHANGELOG.md
@@ -1,6 +1,158 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.34.0
|
||||||
|
------
|
||||||
|
- Added support for adaptive `--height`. If the `--height` value is prefixed
|
||||||
|
with `~`, fzf will automatically determine the height in the range according
|
||||||
|
to the input size.
|
||||||
|
```sh
|
||||||
|
seq 1 | fzf --height ~70% --border --padding 1 --margin 1
|
||||||
|
seq 10 | fzf --height ~70% --border --padding 1 --margin 1
|
||||||
|
seq 100 | fzf --height ~70% --border --padding 1 --margin 1
|
||||||
|
```
|
||||||
|
- There are a few limitations
|
||||||
|
- Not compatible with percent top/bottom margin/padding
|
||||||
|
```sh
|
||||||
|
# This is not allowed (top/bottom margin in percent value)
|
||||||
|
fzf --height ~50% --border --margin 5%,10%
|
||||||
|
|
||||||
|
# This is allowed (top/bottom margin in fixed value)
|
||||||
|
fzf --height ~50% --border --margin 2,10%
|
||||||
|
```
|
||||||
|
- fzf will not start until it can determine the right height for the input
|
||||||
|
```sh
|
||||||
|
# fzf will open immediately
|
||||||
|
(sleep 2; seq 10) | fzf --height 50%
|
||||||
|
|
||||||
|
# fzf will open after 2 seconds
|
||||||
|
(sleep 2; seq 10) | fzf --height ~50%
|
||||||
|
(sleep 2; seq 1000) | fzf --height ~50%
|
||||||
|
```
|
||||||
|
- Fixed tcell renderer used to render full-screen fzf on Windows
|
||||||
|
- `--no-clear` is deprecated. Use `reload` action instead.
|
||||||
|
|
||||||
|
0.33.0
|
||||||
|
------
|
||||||
|
- Added `--scheme=[default|path|history]` option to choose scoring scheme
|
||||||
|
- (Experimental)
|
||||||
|
- We updated the scoring algorithm in 0.32.0, however we have learned that
|
||||||
|
this new scheme (`default`) is not always giving the optimal result
|
||||||
|
- `path`: Additional bonus point is only given to the characters after
|
||||||
|
path separator. You might want to choose this scheme if you have many
|
||||||
|
files with spaces in their paths.
|
||||||
|
- `history`: No additional bonus points are given so that we give more
|
||||||
|
weight to the chronological ordering. This is equivalent to the scoring
|
||||||
|
scheme before 0.32.0. This also sets `--tiebreak=index`.
|
||||||
|
- ANSI color sequences with colon delimiters are now supported.
|
||||||
|
```sh
|
||||||
|
printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi
|
||||||
|
printf "\e[38:5:208mOption 1\e[m\nOption 2" | fzf --ansi
|
||||||
|
```
|
||||||
|
- Support `border-{up,down}` as the synonyms for `border-{top,bottom}` in
|
||||||
|
`--preview-window`
|
||||||
|
- Added support for ANSI `strikethrough`
|
||||||
|
```sh
|
||||||
|
printf "\e[9mdeleted" | fzf --ansi
|
||||||
|
fzf --color fg+:strikethrough
|
||||||
|
```
|
||||||
|
|
||||||
|
0.32.1
|
||||||
|
------
|
||||||
|
- Fixed incorrect ordering of `--tiebreak=chunk`
|
||||||
|
- fzf-tmux will show fzf border instead of tmux popup border (requires tmux 3.3)
|
||||||
|
```sh
|
||||||
|
fzf-tmux -p70%
|
||||||
|
fzf-tmux -p70% --color=border:bright-red
|
||||||
|
fzf-tmux -p100%,60% --color=border:bright-yellow --border=horizontal --padding 1,5 --margin 1,0
|
||||||
|
fzf-tmux -p70%,100% --color=border:bright-green --border=vertical
|
||||||
|
|
||||||
|
# Key bindings (CTRL-T, CTRL-R, ALT-C) will use these options
|
||||||
|
export FZF_TMUX_OPTS='-p100%,60% --color=border:green --border=horizontal --padding 1,5 --margin 1,0'
|
||||||
|
```
|
||||||
|
|
||||||
|
0.32.0
|
||||||
|
------
|
||||||
|
- Updated the scoring algorithm
|
||||||
|
- Different bonus points to different categories of word boundaries
|
||||||
|
(listed higher to lower bonus point)
|
||||||
|
- Word after whitespace characters or beginning of the string
|
||||||
|
- Word after common delimiter characters (`/,:;|`)
|
||||||
|
- Word after other non-word characters
|
||||||
|
```sh
|
||||||
|
# foo/bar.sh` is preferred over `foo-bar.sh` on `bar`
|
||||||
|
fzf --query=bar --height=4 << EOF
|
||||||
|
foo-bar.sh
|
||||||
|
foo/bar.sh
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
- Added a new tiebreak `chunk`
|
||||||
|
- Favors the line with shorter matched chunk. A chunk is a set of
|
||||||
|
consecutive non-whitespace characters.
|
||||||
|
- Unlike the default `length`, this scheme works well with tabular input
|
||||||
|
```sh
|
||||||
|
# length prefers item #1, because the whole line is shorter,
|
||||||
|
# chunk prefers item #2, because the matched chunk ("foo") is shorter
|
||||||
|
fzf --height=6 --header-lines=2 --tiebreak=chunk --reverse --query=fo << "EOF"
|
||||||
|
N | Field1 | Field2 | Field3
|
||||||
|
- | ------ | ------ | ------
|
||||||
|
1 | hello | foobar | baz
|
||||||
|
2 | world | foo | bazbaz
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
- If the input does not contain any spaces, `chunk` is equivalent to
|
||||||
|
`length`. But we're not going to set it as the default because it is
|
||||||
|
computationally more expensive.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.31.0
|
||||||
|
------
|
||||||
|
- Added support for an alternative preview window layout that is activated
|
||||||
|
when the size of the preview window is smaller than a certain threshold.
|
||||||
|
```sh
|
||||||
|
# If the width of the preview window is smaller than 50 columns,
|
||||||
|
# it will be displayed above the search window.
|
||||||
|
fzf --preview 'cat {}' --preview-window 'right,50%,border-left,<50(up,30%,border-bottom)'
|
||||||
|
|
||||||
|
# Or you can just hide it like so
|
||||||
|
fzf --preview 'cat {}' --preview-window '<50(hidden)'
|
||||||
|
```
|
||||||
|
- fzf now uses SGR mouse mode to properly support mouse on larger terminals
|
||||||
|
- You can now use characters that do not satisfy `unicode.IsGraphic` constraint
|
||||||
|
for `--marker`, `--pointer`, and `--ellipsis`. Allows Nerd Fonts and stuff.
|
||||||
|
Use at your own risk.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Shell extension
|
||||||
|
- `kill` completion now requires trigger sequence (`**`) for consistency
|
||||||
|
|
||||||
|
0.30.0
|
||||||
|
------
|
||||||
|
- Fixed cursor flickering over the screen by hiding it during rendering
|
||||||
|
- Added `--ellipsis` option. You can take advantage of it to make fzf
|
||||||
|
effectively search non-visible parts of the item.
|
||||||
|
```sh
|
||||||
|
# Search against hidden line numbers on the far right
|
||||||
|
nl /usr/share/dict/words |
|
||||||
|
awk '{printf "%s%1000s\n", $2, $1}' |
|
||||||
|
fzf --nth=-1 --no-hscroll --ellipsis='' |
|
||||||
|
awk '{print $2}'
|
||||||
|
```
|
||||||
|
- Added `rebind` action for restoring bindings after `unbind`
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.29.0
|
||||||
|
------
|
||||||
|
- Added `change-preview(...)` action to change the `--preview` command
|
||||||
|
- cf. `preview(...)` is a one-off action that doesn't change the default
|
||||||
|
preview command
|
||||||
|
- Added `change-preview-window(...)` action
|
||||||
|
- You can rotate through the different options separated by `|`
|
||||||
|
```sh
|
||||||
|
fzf --preview 'cat {}' --preview-window right:40% \
|
||||||
|
--bind 'ctrl-/:change-preview-window(right,70%|down,40%,border-top|hidden|)'
|
||||||
|
```
|
||||||
|
- Fixed rendering of the prompt line when overflow occurs with `--info=inline`
|
||||||
|
|
||||||
0.28.0
|
0.28.0
|
||||||
------
|
------
|
||||||
- Added `--header-first` option to print header before the prompt line
|
- Added `--header-first` option to print header before the prompt line
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM archlinux/base:latest
|
FROM archlinux
|
||||||
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
|
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
|
||||||
RUN gem install --no-document -v 5.14.2 minitest
|
RUN gem install --no-document -v 5.14.2 minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
Copyright (c) 2013-2022 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
7
Makefile
7
Makefile
@@ -35,6 +35,7 @@ BINARYARM7 := fzf-$(GOOS)_arm7
|
|||||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||||
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
||||||
BINARYRISCV64 := fzf-$(GOOS)_riscv64
|
BINARYRISCV64 := fzf-$(GOOS)_riscv64
|
||||||
|
BINARYLOONG64 := fzf-$(GOOS)_loong64
|
||||||
|
|
||||||
# https://en.wikipedia.org/wiki/Uname
|
# https://en.wikipedia.org/wiki/Uname
|
||||||
UNAME_M := $(shell uname -m)
|
UNAME_M := $(shell uname -m)
|
||||||
@@ -62,6 +63,8 @@ else ifeq ($(UNAME_M),ppc64le)
|
|||||||
BINARY := $(BINARYPPC64LE)
|
BINARY := $(BINARYPPC64LE)
|
||||||
else ifeq ($(UNAME_M),riscv64)
|
else ifeq ($(UNAME_M),riscv64)
|
||||||
BINARY := $(BINARYRISCV64)
|
BINARY := $(BINARYRISCV64)
|
||||||
|
else ifeq ($(UNAME_M),loongarch64)
|
||||||
|
BINARY := $(BINARYLOONG64)
|
||||||
else
|
else
|
||||||
$(error Build on $(UNAME_M) is not supported, yet.)
|
$(error Build on $(UNAME_M) is not supported, yet.)
|
||||||
endif
|
endif
|
||||||
@@ -148,7 +151,11 @@ target/$(BINARYPPC64LE): $(SOURCES)
|
|||||||
target/$(BINARYRISCV64): $(SOURCES)
|
target/$(BINARYRISCV64): $(SOURCES)
|
||||||
GOARCH=riscv64 $(GO) build $(BUILD_FLAGS) -o $@
|
GOARCH=riscv64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARYLOONG64): $(SOURCES)
|
||||||
|
GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
bin/fzf: target/$(BINARY) | bin
|
bin/fzf: target/$(BINARY) | bin
|
||||||
|
-rm -f bin/fzf
|
||||||
cp -f target/$(BINARY) bin/fzf
|
cp -f target/$(BINARY) bin/fzf
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
|
27
README.md
27
README.md
@@ -86,7 +86,7 @@ stuff.
|
|||||||
|
|
||||||
### Using Homebrew
|
### Using Homebrew
|
||||||
|
|
||||||
You can use [Homebrew](http://brew.sh/) (on macOS or Linux)
|
You can use [Homebrew](https://brew.sh/) (on macOS or Linux)
|
||||||
to install fzf.
|
to install fzf.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -131,6 +131,8 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
>
|
>
|
||||||
> Refer to the package documentation for more information. (e.g. `apt-cache show fzf`)
|
> Refer to the package documentation for more information. (e.g. `apt-cache show fzf`)
|
||||||
|
|
||||||
|
[](https://repology.org/project/fzf/versions)
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||||
@@ -380,12 +382,11 @@ cd ~/github/fzf**<TAB>
|
|||||||
|
|
||||||
#### Process IDs
|
#### Process IDs
|
||||||
|
|
||||||
Fuzzy completion for PIDs is provided for kill command. In this case,
|
Fuzzy completion for PIDs is provided for kill command.
|
||||||
there is no trigger sequence; just press the tab key after the kill command.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Can select multiple processes with <TAB> or <Shift-TAB> keys
|
# Can select multiple processes with <TAB> or <Shift-TAB> keys
|
||||||
kill -9 <TAB>
|
kill -9 **<TAB>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Host names
|
#### Host names
|
||||||
@@ -556,7 +557,7 @@ more details.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
FZF_DEFAULT_COMMAND='ps -ef' \
|
FZF_DEFAULT_COMMAND='ps -ef' \
|
||||||
fzf --bind 'ctrl-r:reload($FZF_DEFAULT_COMMAND)' \
|
fzf --bind 'ctrl-r:reload(eval "$FZF_DEFAULT_COMMAND")' \
|
||||||
--header 'Press CTRL-R to reload' --header-lines=1 \
|
--header 'Press CTRL-R to reload' --header-lines=1 \
|
||||||
--height=50% --layout=reverse
|
--height=50% --layout=reverse
|
||||||
```
|
```
|
||||||
@@ -565,7 +566,7 @@ FZF_DEFAULT_COMMAND='ps -ef' \
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
FZF_DEFAULT_COMMAND='find . -type f' \
|
FZF_DEFAULT_COMMAND='find . -type f' \
|
||||||
fzf --bind 'ctrl-d:reload(find . -type d),ctrl-f:reload($FZF_DEFAULT_COMMAND)' \
|
fzf --bind 'ctrl-d:reload(find . -type d),ctrl-f:reload(eval "$FZF_DEFAULT_COMMAND")' \
|
||||||
--height=50% --layout=reverse
|
--height=50% --layout=reverse
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -590,6 +591,9 @@ If ripgrep doesn't find any matches, it will exit with a non-zero exit status,
|
|||||||
and fzf will warn you about it. To suppress the warning message, we added
|
and fzf will warn you about it. To suppress the warning message, we added
|
||||||
`|| true` to the command, so that it always exits with 0.
|
`|| true` to the command, so that it always exits with 0.
|
||||||
|
|
||||||
|
See ["Using fzf as interative Ripgrep launcher"](https://github.com/junegunn/fzf/blob/master/ADVANCED.md#using-fzf-as-interative-ripgrep-launcher)
|
||||||
|
for a fuller example with preview window options.
|
||||||
|
|
||||||
### Preview window
|
### Preview window
|
||||||
|
|
||||||
When the `--preview` option is set, fzf automatically starts an external process
|
When the `--preview` option is set, fzf automatically starts an external process
|
||||||
@@ -623,10 +627,7 @@ fzf --height 40% --layout reverse --info inline --border \
|
|||||||
|
|
||||||
See the man page (`man fzf`) for the full list of options.
|
See the man page (`man fzf`) for the full list of options.
|
||||||
|
|
||||||
For more advanced examples, see [Key bindings for git with fzf][fzf-git]
|
More advanced examples can be found [here](https://github.com/junegunn/fzf/blob/master/ADVANCED.md).
|
||||||
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
|
|
||||||
|
|
||||||
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
@@ -658,10 +659,10 @@ default find command to traverse the file system while respecting
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Feed the output of fd into fzf
|
# Feed the output of fd into fzf
|
||||||
fd --type f | fzf
|
fd --type f --strip-cwd-prefix | fzf
|
||||||
|
|
||||||
# Setting fd as the default source for fzf
|
# Setting fd as the default source for fzf
|
||||||
export FZF_DEFAULT_COMMAND='fd --type f'
|
export FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix'
|
||||||
|
|
||||||
# Now fzf (w/o pipe) will use fd instead of find
|
# Now fzf (w/o pipe) will use fd instead of find
|
||||||
fzf
|
fzf
|
||||||
@@ -674,7 +675,7 @@ If you want the command to follow symbolic links and don't want it to exclude
|
|||||||
hidden files, use the following command:
|
hidden files, use the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
|
export FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix --hidden --follow --exclude .git'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Fish shell
|
#### Fish shell
|
||||||
|
30
bin/fzf-tmux
30
bin/fzf-tmux
@@ -10,7 +10,6 @@ fail() {
|
|||||||
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
||||||
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
||||||
|
|
||||||
tmux_args=()
|
|
||||||
args=()
|
args=()
|
||||||
opt=""
|
opt=""
|
||||||
skip=""
|
skip=""
|
||||||
@@ -58,7 +57,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
;;
|
;;
|
||||||
-p*|-w*|-h*|-x*|-y*|-d*|-u*|-r*|-l*)
|
-p*|-w*|-h*|-x*|-y*|-d*|-u*|-r*|-l*)
|
||||||
if [[ "$arg" =~ ^-[pwhxy] ]]; then
|
if [[ "$arg" =~ ^-[pwhxy] ]]; then
|
||||||
[[ "$opt" =~ "-K -E" ]] || opt="-K -E"
|
[[ "$opt" =~ "-E" ]] || opt="-E"
|
||||||
elif [[ "$arg" =~ ^.[lr] ]]; then
|
elif [[ "$arg" =~ ^.[lr] ]]; then
|
||||||
opt="-h"
|
opt="-h"
|
||||||
if [[ "$arg" =~ ^.l ]]; then
|
if [[ "$arg" =~ ^.l ]]; then
|
||||||
@@ -119,8 +118,6 @@ while [[ $# -gt 0 ]]; do
|
|||||||
# "--" can be used to separate fzf-tmux options from fzf options to
|
# "--" can be used to separate fzf-tmux options from fzf options to
|
||||||
# avoid conflicts
|
# avoid conflicts
|
||||||
skip=1
|
skip=1
|
||||||
tmux_args=("${args[@]}")
|
|
||||||
args=()
|
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -139,7 +136,7 @@ fi
|
|||||||
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
|
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
|
||||||
|
|
||||||
# Handle zoomed tmux pane without popup options by moving it to a temp window
|
# Handle zoomed tmux pane without popup options by moving it to a temp window
|
||||||
if [[ ! "$opt" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
if [[ ! "$opt" =~ "-E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
||||||
zoomed_without_popup=1
|
zoomed_without_popup=1
|
||||||
original_window=$(tmux display-message -p "#{window_id}")
|
original_window=$(tmux display-message -p "#{window_id}")
|
||||||
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
|
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
|
||||||
@@ -181,7 +178,14 @@ trap 'cleanup 1' SIGUSR1
|
|||||||
trap 'cleanup' EXIT
|
trap 'cleanup' EXIT
|
||||||
|
|
||||||
envs="export TERM=$TERM "
|
envs="export TERM=$TERM "
|
||||||
[[ "$opt" =~ "-K -E" ]] && FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
if [[ "$opt" =~ "-E" ]]; then
|
||||||
|
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
||||||
|
tmux_verson=$(tmux -V)
|
||||||
|
if [[ ! $tmux_verson =~ 3\.2 ]]; then
|
||||||
|
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
||||||
|
opt="-B $opt"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||||
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||||
echo "$envs;" > "$argsf"
|
echo "$envs;" > "$argsf"
|
||||||
@@ -195,7 +199,7 @@ close="; trap - EXIT SIGINT SIGTERM $close"
|
|||||||
|
|
||||||
export TMUX=$(cut -d , -f 1,2 <<< "$TMUX")
|
export TMUX=$(cut -d , -f 1,2 <<< "$TMUX")
|
||||||
mkfifo -m o+w $fifo2
|
mkfifo -m o+w $fifo2
|
||||||
if [[ "$opt" =~ "-K -E" ]]; then
|
if [[ "$opt" =~ "-E" ]]; then
|
||||||
cat $fifo2 &
|
cat $fifo2 &
|
||||||
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||||
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
||||||
@@ -205,15 +209,7 @@ if [[ "$opt" =~ "-K -E" ]]; then
|
|||||||
cat <&0 > $fifo1 &
|
cat <&0 > $fifo1 &
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# tmux dropped the support for `-K`, `-R` to popup command
|
tmux popup -d "$PWD" $opt "bash $argsf" > /dev/null 2>&1
|
||||||
# TODO: We can remove this once tmux 3.2 is released
|
|
||||||
if [[ ! "$(tmux popup --help 2>&1)" =~ '-R shell-command' ]]; then
|
|
||||||
opt="${opt/-K/}"
|
|
||||||
else
|
|
||||||
opt="${opt} -R"
|
|
||||||
fi
|
|
||||||
|
|
||||||
tmux popup -d "$PWD" "${tmux_args[@]}" $opt "bash $argsf" > /dev/null 2>&1
|
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -227,7 +223,7 @@ else
|
|||||||
fi
|
fi
|
||||||
tmux set-window-option synchronize-panes off \;\
|
tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
split-window -c "$PWD" $opt "${tmux_args[@]}" "bash -c 'exec -a fzf bash $argsf'" $swap \
|
split-window -c "$PWD" $opt "bash -c 'exec -a fzf bash $argsf'" $swap \
|
||||||
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
|
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
|
||||||
cat $fifo2
|
cat $fifo2
|
||||||
exit "$(cat $fifo3)"
|
exit "$(cat $fifo3)"
|
||||||
|
26
go.mod
26
go.mod
@@ -1,17 +1,21 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/tcell v1.4.0
|
github.com/gdamore/tcell/v2 v2.5.3
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/mattn/go-isatty v0.0.16
|
||||||
github.com/mattn/go-isatty v0.0.14
|
github.com/mattn/go-runewidth v0.0.14
|
||||||
github.com/mattn/go-runewidth v0.0.13
|
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.2.0
|
github.com/rivo/uniseg v0.4.2
|
||||||
github.com/saracen/walker v0.1.2
|
github.com/saracen/walker v0.1.3
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
|
|
||||||
golang.org/x/text v0.3.6 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
require (
|
||||||
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
41
go.sum
41
go.sum
@@ -1,31 +1,32 @@
|
|||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
|
github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
|
||||||
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
|
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
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.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
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/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|
||||||
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/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo=
|
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||||
github.com/saracen/walker v0.1.2/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
|
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
github.com/saracen/walker v0.1.3 h1:YtcKKmpRPy6XJTHJ75J2QYXXZYWnZNQxPCVqZSHVV/g=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
github.com/saracen/walker v0.1.3/go.mod h1:FU+7qU8DeQQgSZDmmThMJi93kPkLFgy0oVAcLxurjIk=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
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-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
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.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
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=
|
||||||
|
9
install
9
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.28.0
|
version=0.34.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -256,7 +256,7 @@ for shell in $shells; do
|
|||||||
# Setup fzf
|
# Setup fzf
|
||||||
# ---------
|
# ---------
|
||||||
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
||||||
export PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Auto-completion
|
# Auto-completion
|
||||||
@@ -280,11 +280,6 @@ EOF
|
|||||||
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
||||||
|
|
||||||
mkdir -p "${fish_dir}/functions"
|
mkdir -p "${fish_dir}/functions"
|
||||||
if [ -e "${fish_dir}/functions/fzf.fish" ]; then
|
|
||||||
echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
|
|
||||||
rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
||||||
if [ $key_bindings -ne 0 ]; then
|
if [ $key_bindings -ne 0 ]; then
|
||||||
echo -n "Symlink $fish_binding ... "
|
echo -n "Symlink $fish_binding ... "
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.28.0"
|
$version="0.34.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.28"
|
var version string = "0.34"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "Nov 2021" "fzf 0.28.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Sep 2022" "fzf 0.34.0" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Nov 2021" "fzf 0.28.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Sep 2022" "fzf 0.34.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -51,6 +51,18 @@ Case-sensitive match
|
|||||||
.B "--literal"
|
.B "--literal"
|
||||||
Do not normalize latin script letters for matching.
|
Do not normalize latin script letters for matching.
|
||||||
.TP
|
.TP
|
||||||
|
.BI "--scheme=" SCHEME
|
||||||
|
Choose scoring scheme tailored for different types of input.
|
||||||
|
|
||||||
|
.br
|
||||||
|
.BR default " Generic scoring scheme designed to work well with any type of input"
|
||||||
|
.br
|
||||||
|
.BR path " Scoring scheme for paths (additional bonus point only after path separator)
|
||||||
|
.br
|
||||||
|
.BR history " Scoring scheme for command history (no additional bonus points).
|
||||||
|
Sets \fB--tiebreak=index\fR as well.
|
||||||
|
.br
|
||||||
|
.TP
|
||||||
.BI "--algo=" TYPE
|
.BI "--algo=" TYPE
|
||||||
Fuzzy matching algorithm (default: v2)
|
Fuzzy matching algorithm (default: v2)
|
||||||
|
|
||||||
@@ -95,6 +107,8 @@ Comma-separated list of sort criteria to apply when the scores are tied.
|
|||||||
.br
|
.br
|
||||||
.BR length " Prefers line with shorter length"
|
.BR length " Prefers line with shorter length"
|
||||||
.br
|
.br
|
||||||
|
.BR chunk " Prefers line with shorter matched chunk (delimited by whitespaces)"
|
||||||
|
.br
|
||||||
.BR begin " Prefers line with matched substring closer to the beginning"
|
.BR begin " Prefers line with matched substring closer to the beginning"
|
||||||
.br
|
.br
|
||||||
.BR end " Prefers line with matched substring closer to the end"
|
.BR end " Prefers line with matched substring closer to the end"
|
||||||
@@ -163,9 +177,11 @@ actions are affected:
|
|||||||
Label characters for \fBjump\fR and \fBjump-accept\fR
|
Label characters for \fBjump\fR and \fBjump-accept\fR
|
||||||
.SS Layout
|
.SS Layout
|
||||||
.TP
|
.TP
|
||||||
.BI "--height=" "HEIGHT[%]"
|
.BI "--height=" "[~]HEIGHT[%]"
|
||||||
Display fzf window below the cursor with the given height instead of using
|
Display fzf window below the cursor with the given height instead of using
|
||||||
the full screen.
|
the full screen. When prefixed with \fB~\fR, fzf will automatically determine
|
||||||
|
the height in the range according to the input size. Note that adaptive height
|
||||||
|
is not compatible with top/bottom margin and padding given in percent size.
|
||||||
.TP
|
.TP
|
||||||
.BI "--min-height=" "HEIGHT"
|
.BI "--min-height=" "HEIGHT"
|
||||||
Minimum height when \fB--height\fR is given in percent (default: 10).
|
Minimum height when \fB--height\fR is given in percent (default: 10).
|
||||||
@@ -187,7 +203,7 @@ Choose the layout (default: default)
|
|||||||
A synonym for \fB--layout=reverse\fB
|
A synonym for \fB--layout=reverse\fB
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--border" [=STYLE]
|
.BI "--border" [=BORDER_OPT]
|
||||||
Draw border around the finder
|
Draw border around the finder
|
||||||
|
|
||||||
.br
|
.br
|
||||||
@@ -199,9 +215,9 @@ Draw border around the finder
|
|||||||
.br
|
.br
|
||||||
.BR vertical " Vertical lines on each side of the finder"
|
.BR vertical " Vertical lines on each side of the finder"
|
||||||
.br
|
.br
|
||||||
.BR top
|
.BR top " (up)"
|
||||||
.br
|
.br
|
||||||
.BR bottom
|
.BR bottom " (down)"
|
||||||
.br
|
.br
|
||||||
.BR left
|
.BR left
|
||||||
.br
|
.br
|
||||||
@@ -302,6 +318,9 @@ lines that follow.
|
|||||||
.TP
|
.TP
|
||||||
.B "--header-first"
|
.B "--header-first"
|
||||||
Print header before the prompt line
|
Print header before the prompt line
|
||||||
|
.TP
|
||||||
|
.BI "--ellipsis=" "STR"
|
||||||
|
Ellipsis to show when line is truncated (default: '..')
|
||||||
.SS Display
|
.SS Display
|
||||||
.TP
|
.TP
|
||||||
.B "--ansi"
|
.B "--ansi"
|
||||||
@@ -373,6 +392,7 @@ color mappings.
|
|||||||
\fBreverse\fR
|
\fBreverse\fR
|
||||||
\fBdim\fR
|
\fBdim\fR
|
||||||
\fBitalic\fR
|
\fBitalic\fR
|
||||||
|
\fBstrikethrough\fR
|
||||||
|
|
||||||
.B EXAMPLES:
|
.B EXAMPLES:
|
||||||
|
|
||||||
@@ -447,7 +467,7 @@ e.g.
|
|||||||
\fB# Press CTRL-A to select 100K items and see the sum of all the numbers.
|
\fB# Press CTRL-A to select 100K items and see the sum of all the numbers.
|
||||||
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
||||||
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
||||||
--preview "awk '{sum+=\$1} END {print sum}' {+f}"\fR
|
--preview "awk '{sum+=\\$1} END {print sum}' {+f}"\fR
|
||||||
|
|
||||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||||
|
|
||||||
@@ -467,7 +487,7 @@ e.g.
|
|||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"
|
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B POSITION: (default: right)
|
.B POSITION: (default: right)
|
||||||
@@ -485,9 +505,9 @@ default until \fBtoggle-preview\fR action is triggered.
|
|||||||
execute the command in the background.
|
execute the command in the background.
|
||||||
|
|
||||||
* Long lines are truncated by default. Line wrap can be enabled with
|
* Long lines are truncated by default. Line wrap can be enabled with
|
||||||
\fB:wrap\fR flag.
|
\fBwrap\fR flag.
|
||||||
|
|
||||||
* Preview window will automatically scroll to the bottom when \fB:follow\fR
|
* Preview window will automatically scroll to the bottom when \fBfollow\fR
|
||||||
flag is set, similarly to how \fBtail -f\fR works.
|
flag is set, similarly to how \fBtail -f\fR works.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
@@ -499,7 +519,7 @@ e.g.
|
|||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
|
* Cyclic scrolling is enabled with \fBcycle\fR flag.
|
||||||
|
|
||||||
* To change the style of the border of the preview window, specify one of
|
* To change the style of the border of the preview window, specify one of
|
||||||
the options for \fB--border\fR with \fBborder-\fR prefix.
|
the options for \fB--border\fR with \fBborder-\fR prefix.
|
||||||
@@ -549,6 +569,15 @@ e.g.
|
|||||||
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
* You can specify an alternative set of options that are used only when the size
|
||||||
|
of the preview window is below a certain threshold. Note that only one
|
||||||
|
alternative layout is allowed.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
e.g.
|
||||||
|
\fBfzf --preview 'cat {}' --preview-window 'right,border-left,<30(up,30%,border-bottom)'\fR
|
||||||
|
.RE
|
||||||
|
|
||||||
.SS Scripting
|
.SS Scripting
|
||||||
.TP
|
.TP
|
||||||
.BI "-q, --query=" "STR"
|
.BI "-q, --query=" "STR"
|
||||||
@@ -589,12 +618,6 @@ Read input delimited by ASCII NUL characters instead of newline characters
|
|||||||
.B "--print0"
|
.B "--print0"
|
||||||
Print output delimited by ASCII NUL characters instead of newline characters
|
Print output delimited by ASCII NUL characters instead of newline characters
|
||||||
.TP
|
.TP
|
||||||
.B "--no-clear"
|
|
||||||
Do not clear finder interface on exit. If fzf was started in full screen mode,
|
|
||||||
it will not switch back to the original screen, so you'll have to manually run
|
|
||||||
\fBtput rmcup\fR to return. This option can be used to avoid flickering of the
|
|
||||||
screen when your application needs to start fzf multiple times in order.
|
|
||||||
.TP
|
|
||||||
.B "--sync"
|
.B "--sync"
|
||||||
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
||||||
ncurses finder only after the input stream is complete.
|
ncurses finder only after the input stream is complete.
|
||||||
@@ -820,6 +843,8 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
|
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
||||||
|
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
||||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||||
\fBclear-screen\fR \fIctrl-l\fR
|
\fBclear-screen\fR \fIctrl-l\fR
|
||||||
\fBclear-selection\fR (clear multi-selection)
|
\fBclear-selection\fR (clear multi-selection)
|
||||||
@@ -862,6 +887,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBprint-query\fR (print query and exit)
|
\fBprint-query\fR (print query and exit)
|
||||||
\fBput\fR (put the character to the prompt)
|
\fBput\fR (put the character to the prompt)
|
||||||
\fBrefresh-preview\fR
|
\fBrefresh-preview\fR
|
||||||
|
\fBrebind(...)\fR (rebind bindings after \fBunbind\fR)
|
||||||
\fBreload(...)\fR (see below for the details)
|
\fBreload(...)\fR (see below for the details)
|
||||||
\fBreplace-query\fR (replace query string with the current selection)
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBselect\fR
|
\fBselect\fR
|
||||||
@@ -970,7 +996,6 @@ commands in addition to the default preview command given by \fB--preview\fR
|
|||||||
option.
|
option.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
|
|
||||||
# Default preview command with an extra preview binding
|
# Default preview command with an extra preview binding
|
||||||
fzf --preview 'file {}' --bind '?:preview:cat {}'
|
fzf --preview 'file {}' --bind '?:preview:cat {}'
|
||||||
|
|
||||||
@@ -981,6 +1006,22 @@ e.g.
|
|||||||
# Preview window hidden by default, it appears when you first hit '?'
|
# Preview window hidden by default, it appears when you first hit '?'
|
||||||
fzf --bind '?:preview:cat {}' --preview-window hidden
|
fzf --bind '?:preview:cat {}' --preview-window hidden
|
||||||
|
|
||||||
|
.SS CHANGE PREVIEW WINDOW ATTRIBUTES
|
||||||
|
|
||||||
|
\fBchange-preview-window\fR action can be used to change the properties of the
|
||||||
|
preview window. Unlike the \fB--preview-window\fR option, you can specify
|
||||||
|
multiple sets of options separated by '|' characters.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
# Rotate through the options using CTRL-/
|
||||||
|
fzf --preview 'cat {}' --bind 'ctrl-/:change-preview-window(right,70%|down,40%,border-horizontal|hidden|right)'
|
||||||
|
|
||||||
|
# The default properties given by `--preview-window` are inherited, so an empty string in the list is interpreted as the default
|
||||||
|
fzf --preview 'cat {}' --preview-window 'right,40%,border-left' --bind 'ctrl-/:change-preview-window(70%|down,border-top|hidden|)'
|
||||||
|
|
||||||
|
# This is equivalent to toggle-preview action
|
||||||
|
fzf --preview 'cat {}' --bind 'ctrl-/:change-preview-window(hidden|)'
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||||
|
|
||||||
|
@@ -96,7 +96,12 @@ function! fzf#shellescape(arg, ...)
|
|||||||
if shell =~# 'cmd.exe$'
|
if shell =~# 'cmd.exe$'
|
||||||
return s:shellesc_cmd(a:arg)
|
return s:shellesc_cmd(a:arg)
|
||||||
endif
|
endif
|
||||||
|
try
|
||||||
|
let [shell, &shell] = [&shell, shell]
|
||||||
return s:fzf_call('shellescape', a:arg)
|
return s:fzf_call('shellescape', a:arg)
|
||||||
|
finally
|
||||||
|
let [shell, &shell] = [&shell, shell]
|
||||||
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:fzf_getcwd()
|
function! s:fzf_getcwd()
|
||||||
@@ -159,7 +164,7 @@ function s:get_version(bin)
|
|||||||
if has_key(s:versions, a:bin)
|
if has_key(s:versions, a:bin)
|
||||||
return s:versions[a:bin]
|
return s:versions[a:bin]
|
||||||
end
|
end
|
||||||
let command = a:bin . ' --version --no-height'
|
let command = (&shell =~ 'powershell' ? '&' : '') . shellescape(a:bin) . ' --version --no-height'
|
||||||
let output = systemlist(command)
|
let output = systemlist(command)
|
||||||
if v:shell_error || empty(output)
|
if v:shell_error || empty(output)
|
||||||
return ''
|
return ''
|
||||||
@@ -337,7 +342,8 @@ function! s:common_sink(action, lines) abort
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:get_color(attr, ...)
|
function! s:get_color(attr, ...)
|
||||||
let gui = !s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors
|
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)
|
||||||
|
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors)
|
||||||
let fam = gui ? 'gui' : 'cterm'
|
let fam = gui ? 'gui' : 'cterm'
|
||||||
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
||||||
for group in a:000
|
for group in a:000
|
||||||
@@ -444,6 +450,12 @@ function! s:use_sh()
|
|||||||
return [shell, shellslash, shellcmdflag, shellxquote]
|
return [shell, shellslash, shellcmdflag, shellxquote]
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:writefile(...)
|
||||||
|
if call('writefile', a:000) == -1
|
||||||
|
throw 'Failed to write temporary file. Check if you can write to the path tempname() returns.'
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! fzf#run(...) abort
|
||||||
try
|
try
|
||||||
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||||
@@ -471,7 +483,7 @@ try
|
|||||||
let source_command = source
|
let source_command = source
|
||||||
elseif type == 3
|
elseif type == 3
|
||||||
let temps.input = s:fzf_tempname()
|
let temps.input = s:fzf_tempname()
|
||||||
call writefile(source, temps.input)
|
call s:writefile(source, temps.input)
|
||||||
let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
|
let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
|
||||||
else
|
else
|
||||||
throw 'Invalid source type'
|
throw 'Invalid source type'
|
||||||
@@ -515,7 +527,7 @@ try
|
|||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
if len(source_command)
|
if exists('source_command') && len(source_command)
|
||||||
if len(prev_default_command)
|
if len(prev_default_command)
|
||||||
let $FZF_DEFAULT_COMMAND = prev_default_command
|
let $FZF_DEFAULT_COMMAND = prev_default_command
|
||||||
else
|
else
|
||||||
@@ -660,7 +672,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
endif
|
endif
|
||||||
if s:is_win
|
if s:is_win
|
||||||
let batchfile = s:fzf_tempname().'.bat'
|
let batchfile = s:fzf_tempname().'.bat'
|
||||||
call writefile(s:wrap_cmds(command), batchfile)
|
call s:writefile(s:wrap_cmds(command), batchfile)
|
||||||
let command = batchfile
|
let command = batchfile
|
||||||
let a:temps.batchfile = batchfile
|
let a:temps.batchfile = batchfile
|
||||||
if has('nvim')
|
if has('nvim')
|
||||||
@@ -678,7 +690,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
endif
|
endif
|
||||||
elseif has('win32unix') && $TERM !=# 'cygwin'
|
elseif has('win32unix') && $TERM !=# 'cygwin'
|
||||||
let shellscript = s:fzf_tempname()
|
let shellscript = s:fzf_tempname()
|
||||||
call writefile([command], shellscript)
|
call s:writefile([command], shellscript)
|
||||||
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
|
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
|
||||||
let a:temps.shellscript = shellscript
|
let a:temps.shellscript = shellscript
|
||||||
endif
|
endif
|
||||||
@@ -877,7 +889,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
call s:pushd(a:dict)
|
call s:pushd(a:dict)
|
||||||
if s:is_win
|
if s:is_win
|
||||||
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
|
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
|
||||||
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
|
call s:writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
|
||||||
let command = fzf.temps.batchfile
|
let command = fzf.temps.batchfile
|
||||||
else
|
else
|
||||||
let command = a:command
|
let command = a:command
|
||||||
|
@@ -161,7 +161,11 @@ _fzf_handle_dynamic_completion() {
|
|||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local cur base dir leftover matches trigger cmd
|
local cur base dir leftover matches trigger cmd
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]}"
|
||||||
|
if [[ $cmd == \\* ]]; then
|
||||||
|
cmd="${cmd:1}"
|
||||||
|
fi
|
||||||
|
cmd="${cmd//[^A-Za-z0-9_=]/_}"
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
@@ -177,7 +181,7 @@ __fzf_generic_path_completion() {
|
|||||||
[[ -z "$dir" ]] && dir='.'
|
[[ -z "$dir" ]] && dir='.'
|
||||||
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||||
printf "%q$3 " "$item"
|
printf "%q " "${item%$3}$3"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
||||||
@@ -260,14 +264,6 @@ _fzf_dir_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_kill() {
|
_fzf_complete_kill() {
|
||||||
local trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
|
||||||
local cur="${COMP_WORDS[COMP_CWORD]}"
|
|
||||||
if [[ -z "$cur" ]]; then
|
|
||||||
COMP_WORDS[$COMP_CWORD]=$trigger
|
|
||||||
elif [[ "$cur" != *"$trigger" ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
_fzf_proc_completion "$@"
|
_fzf_proc_completion "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +279,7 @@ _fzf_proc_completion_post() {
|
|||||||
|
|
||||||
_fzf_host_completion() {
|
_fzf_host_completion() {
|
||||||
_fzf_complete +m -- "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
@@ -292,18 +288,22 @@ _fzf_host_completion() {
|
|||||||
|
|
||||||
_fzf_var_completion() {
|
_fzf_var_completion() {
|
||||||
_fzf_complete -m -- "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
declare -xp | sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_alias_completion() {
|
_fzf_alias_completion() {
|
||||||
_fzf_complete -m -- "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
alias | sed 's/=.*//' | sed 's/.* //'
|
alias | sed -En 's|^alias ([^=]+).*|\1|p'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
# fzf options
|
# fzf options
|
||||||
complete -o default -F _fzf_opts_completion fzf
|
complete -o default -F _fzf_opts_completion fzf
|
||||||
|
# fzf-tmux is a thin fzf wrapper that has only a few more options than fzf
|
||||||
|
# itself. As a quick improvement we take fzf's completion. Adding the few extra
|
||||||
|
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
||||||
|
complete -o default -F _fzf_opts_completion fzf-tmux
|
||||||
|
|
||||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
||||||
a_cmds="
|
a_cmds="
|
||||||
@@ -348,9 +348,6 @@ for cmd in $d_cmds; do
|
|||||||
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Kill completion (supports empty completion trigger)
|
|
||||||
complete -F _fzf_complete_kill -o default -o bashdefault kill
|
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds
|
unset cmd d_cmds a_cmds
|
||||||
|
|
||||||
_fzf_setup_completion() {
|
_fzf_setup_completion() {
|
||||||
@@ -373,9 +370,10 @@ _fzf_setup_completion() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Environment variables / Aliases / Hosts
|
# Environment variables / Aliases / Hosts / Process
|
||||||
_fzf_setup_completion 'var' export unset
|
_fzf_setup_completion 'var' export unset
|
||||||
_fzf_setup_completion 'alias' unalias
|
_fzf_setup_completion 'alias' unalias
|
||||||
_fzf_setup_completion 'host' ssh telnet
|
_fzf_setup_completion 'host' ssh telnet
|
||||||
|
_fzf_setup_completion 'proc' kill
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
@@ -146,7 +146,8 @@ __fzf_generic_path_completion() {
|
|||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||||
echo -n "${(q)item}$suffix "
|
item="${item%$suffix}$suffix"
|
||||||
|
echo -n "${(q)item} "
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
@@ -224,7 +225,7 @@ _fzf_complete_telnet() {
|
|||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete +m -- "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
@@ -285,12 +286,6 @@ fzf-completion() {
|
|||||||
|
|
||||||
lbuf=$LBUFFER
|
lbuf=$LBUFFER
|
||||||
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||||
# Kill completion (do not require trigger sequence)
|
|
||||||
if [ "$cmd" = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
|
||||||
tail=$trigger
|
|
||||||
tokens+=$trigger
|
|
||||||
lbuf="$lbuf$trigger"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Trigger sequence given
|
# Trigger sequence given
|
||||||
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||||
|
@@ -14,14 +14,17 @@
|
|||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd opts
|
||||||
|
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS -m"
|
||||||
printf '%q ' "$item"
|
eval "$cmd" |
|
||||||
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
||||||
|
while read -r item; do
|
||||||
|
printf '%q ' "$item" # escape special chars
|
||||||
done
|
done
|
||||||
echo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
if [[ $- =~ i ]]; then
|
||||||
@@ -32,24 +35,27 @@ __fzfcmd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
local selected="$(__fzf_select__)"
|
local selected="$(__fzf_select__ "$@")"
|
||||||
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
|
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
|
||||||
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
|
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_cd__() {
|
__fzf_cd__() {
|
||||||
local cmd dir
|
local cmd opts dir
|
||||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS +m"
|
||||||
|
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output
|
local output opts script
|
||||||
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0"
|
||||||
|
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
output=$(
|
output=$(
|
||||||
builtin fc -lnr -2147483648 |
|
builtin fc -lnr -2147483648 |
|
||||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e "$script" |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
|
@@ -53,7 +53,7 @@ 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
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
|
||||||
|
|
||||||
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
||||||
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
||||||
@@ -87,7 +87,7 @@ function fzf_key_bindings
|
|||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
if [ -n "$result" ]
|
if [ -n "$result" ]
|
||||||
cd $result
|
cd -- $result
|
||||||
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
commandline -t ""
|
commandline -t ""
|
||||||
|
@@ -66,7 +66,9 @@ fzf-file-widget() {
|
|||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-file-widget
|
zle -N fzf-file-widget
|
||||||
bindkey '^T' fzf-file-widget
|
bindkey -M emacs '^T' fzf-file-widget
|
||||||
|
bindkey -M vicmd '^T' fzf-file-widget
|
||||||
|
bindkey -M viins '^T' fzf-file-widget
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
@@ -79,7 +81,7 @@ fzf-cd-widget() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
zle push-line # Clear buffer. Auto-restored on next prompt.
|
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||||
BUFFER="cd ${(q)dir}"
|
BUFFER="builtin cd -- ${(q)dir}"
|
||||||
zle accept-line
|
zle accept-line
|
||||||
local ret=$?
|
local ret=$?
|
||||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||||
@@ -87,14 +89,16 @@ fzf-cd-widget() {
|
|||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-cd-widget
|
zle -N fzf-cd-widget
|
||||||
bindkey '\ec' fzf-cd-widget
|
bindkey -M emacs '\ec' fzf-cd-widget
|
||||||
|
bindkey -M vicmd '\ec' fzf-cd-widget
|
||||||
|
bindkey -M viins '\ec' fzf-cd-widget
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||||
selected=( $(fc -rl 1 | perl -ne 'print if !$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++' |
|
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$selected[1]
|
||||||
@@ -106,7 +110,9 @@ fzf-history-widget() {
|
|||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-history-widget
|
zle -N fzf-history-widget
|
||||||
bindkey '^R' fzf-history-widget
|
bindkey -M emacs '^R' fzf-history-widget
|
||||||
|
bindkey -M vicmd '^R' fzf-history-widget
|
||||||
|
bindkey -M viins '^R' fzf-history-widget
|
||||||
|
|
||||||
} always {
|
} always {
|
||||||
eval $__fzf_key_bindings_options
|
eval $__fzf_key_bindings_options
|
||||||
|
@@ -80,6 +80,7 @@ Scoring criteria
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -89,6 +90,10 @@ import (
|
|||||||
|
|
||||||
var DEBUG bool
|
var DEBUG bool
|
||||||
|
|
||||||
|
var delimiterChars = "/,:;|"
|
||||||
|
|
||||||
|
const whiteChars = " \t\n\v\f\r\x85\xA0"
|
||||||
|
|
||||||
func indexAt(index int, max int, forward bool) int {
|
func indexAt(index int, max int, forward bool) int {
|
||||||
if forward {
|
if forward {
|
||||||
return index
|
return index
|
||||||
@@ -140,16 +145,51 @@ const (
|
|||||||
bonusFirstCharMultiplier = 2
|
bonusFirstCharMultiplier = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Extra bonus for word boundary after whitespace character or beginning of the string
|
||||||
|
bonusBoundaryWhite int16 = bonusBoundary + 2
|
||||||
|
|
||||||
|
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
|
||||||
|
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
||||||
|
|
||||||
|
initialCharClass charClass = charWhite
|
||||||
|
)
|
||||||
|
|
||||||
type charClass int
|
type charClass int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
charNonWord charClass = iota
|
charWhite charClass = iota
|
||||||
|
charNonWord
|
||||||
|
charDelimiter
|
||||||
charLower
|
charLower
|
||||||
charUpper
|
charUpper
|
||||||
charLetter
|
charLetter
|
||||||
charNumber
|
charNumber
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Init(scheme string) bool {
|
||||||
|
switch scheme {
|
||||||
|
case "default":
|
||||||
|
bonusBoundaryWhite = bonusBoundary + 2
|
||||||
|
bonusBoundaryDelimiter = bonusBoundary + 1
|
||||||
|
case "path":
|
||||||
|
bonusBoundaryWhite = bonusBoundary
|
||||||
|
bonusBoundaryDelimiter = bonusBoundary + 1
|
||||||
|
if os.PathSeparator == '/' {
|
||||||
|
delimiterChars = "/"
|
||||||
|
} else {
|
||||||
|
delimiterChars = string([]rune{os.PathSeparator, '/'})
|
||||||
|
}
|
||||||
|
initialCharClass = charDelimiter
|
||||||
|
case "history":
|
||||||
|
bonusBoundaryWhite = bonusBoundary
|
||||||
|
bonusBoundaryDelimiter = bonusBoundary
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func posArray(withPos bool, len int) *[]int {
|
func posArray(withPos bool, len int) *[]int {
|
||||||
if withPos {
|
if withPos {
|
||||||
pos := make([]int, 0, len)
|
pos := make([]int, 0, len)
|
||||||
@@ -181,6 +221,10 @@ func charClassOfAscii(char rune) charClass {
|
|||||||
return charUpper
|
return charUpper
|
||||||
} else if char >= '0' && char <= '9' {
|
} else if char >= '0' && char <= '9' {
|
||||||
return charNumber
|
return charNumber
|
||||||
|
} else if strings.IndexRune(whiteChars, char) >= 0 {
|
||||||
|
return charWhite
|
||||||
|
} else if strings.IndexRune(delimiterChars, char) >= 0 {
|
||||||
|
return charDelimiter
|
||||||
}
|
}
|
||||||
return charNonWord
|
return charNonWord
|
||||||
}
|
}
|
||||||
@@ -194,6 +238,10 @@ func charClassOfNonAscii(char rune) charClass {
|
|||||||
return charNumber
|
return charNumber
|
||||||
} else if unicode.IsLetter(char) {
|
} else if unicode.IsLetter(char) {
|
||||||
return charLetter
|
return charLetter
|
||||||
|
} else if unicode.IsSpace(char) {
|
||||||
|
return charWhite
|
||||||
|
} else if strings.IndexRune(delimiterChars, char) >= 0 {
|
||||||
|
return charDelimiter
|
||||||
}
|
}
|
||||||
return charNonWord
|
return charNonWord
|
||||||
}
|
}
|
||||||
@@ -206,22 +254,33 @@ func charClassOf(char rune) charClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bonusFor(prevClass charClass, class charClass) int16 {
|
func bonusFor(prevClass charClass, class charClass) int16 {
|
||||||
if prevClass == charNonWord && class != charNonWord {
|
if class > charNonWord {
|
||||||
|
if prevClass == charWhite {
|
||||||
|
// Word boundary after whitespace
|
||||||
|
return bonusBoundaryWhite
|
||||||
|
} else if prevClass == charDelimiter {
|
||||||
|
// Word boundary after a delimiter character
|
||||||
|
return bonusBoundaryDelimiter
|
||||||
|
} else if prevClass == charNonWord {
|
||||||
// Word boundary
|
// Word boundary
|
||||||
return bonusBoundary
|
return bonusBoundary
|
||||||
} else if prevClass == charLower && class == charUpper ||
|
}
|
||||||
|
}
|
||||||
|
if prevClass == charLower && class == charUpper ||
|
||||||
prevClass != charNumber && class == charNumber {
|
prevClass != charNumber && class == charNumber {
|
||||||
// camelCase letter123
|
// camelCase letter123
|
||||||
return bonusCamel123
|
return bonusCamel123
|
||||||
} else if class == charNonWord {
|
} else if class == charNonWord {
|
||||||
return bonusNonWord
|
return bonusNonWord
|
||||||
|
} else if class == charWhite {
|
||||||
|
return bonusBoundaryWhite
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func bonusAt(input *util.Chars, idx int) int16 {
|
func bonusAt(input *util.Chars, idx int) int16 {
|
||||||
if idx == 0 {
|
if idx == 0 {
|
||||||
return bonusBoundary
|
return bonusBoundaryWhite
|
||||||
}
|
}
|
||||||
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
|
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
|
||||||
}
|
}
|
||||||
@@ -377,7 +436,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
// Phase 2. Calculate bonus for each point
|
// Phase 2. Calculate bonus for each point
|
||||||
maxScore, maxScorePos := int16(0), 0
|
maxScore, maxScorePos := int16(0), 0
|
||||||
pidx, lastIdx := 0, 0
|
pidx, lastIdx := 0, 0
|
||||||
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charNonWord, false
|
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
|
||||||
Tsub := T[idx:]
|
Tsub := T[idx:]
|
||||||
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
|
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
|
||||||
for off, char := range Tsub {
|
for off, char := range Tsub {
|
||||||
@@ -417,7 +476,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
C0sub[off] = 1
|
C0sub[off] = 1
|
||||||
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
||||||
maxScore, maxScorePos = score, idx+off
|
maxScore, maxScorePos = score, idx+off
|
||||||
if forward && bonus == bonusBoundary {
|
if forward && bonus >= bonusBoundary {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -486,11 +545,14 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
s1 = Hdiag[off] + scoreMatch
|
s1 = Hdiag[off] + scoreMatch
|
||||||
b := Bsub[off]
|
b := Bsub[off]
|
||||||
consecutive = Cdiag[off] + 1
|
consecutive = Cdiag[off] + 1
|
||||||
|
if consecutive > 1 {
|
||||||
|
fb := B[col-int(consecutive)+1]
|
||||||
// Break consecutive chunk
|
// Break consecutive chunk
|
||||||
if b == bonusBoundary {
|
if b >= bonusBoundary && b > fb {
|
||||||
consecutive = 1
|
consecutive = 1
|
||||||
} else if consecutive > 1 {
|
} else {
|
||||||
b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
|
b = util.Max16(b, util.Max16(bonusConsecutive, fb))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if s1+b < s2 {
|
if s1+b < s2 {
|
||||||
s1 += Bsub[off]
|
s1 += Bsub[off]
|
||||||
@@ -555,7 +617,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
|
func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
|
||||||
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
|
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
|
||||||
pos := posArray(withPos, len(pattern))
|
pos := posArray(withPos, len(pattern))
|
||||||
prevClass := charNonWord
|
prevClass := charWhite
|
||||||
if sidx > 0 {
|
if sidx > 0 {
|
||||||
prevClass = charClassOf(text.Get(sidx - 1))
|
prevClass = charClassOf(text.Get(sidx - 1))
|
||||||
}
|
}
|
||||||
@@ -583,7 +645,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
|
|||||||
firstBonus = bonus
|
firstBonus = bonus
|
||||||
} else {
|
} else {
|
||||||
// Break consecutive chunk
|
// Break consecutive chunk
|
||||||
if bonus == bonusBoundary {
|
if bonus >= bonusBoundary && bonus > firstBonus {
|
||||||
firstBonus = bonus
|
firstBonus = bonus
|
||||||
}
|
}
|
||||||
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
|
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
|
||||||
@@ -741,7 +803,7 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
|||||||
if bonus > bestBonus {
|
if bonus > bestBonus {
|
||||||
bestPos, bestBonus = index, bonus
|
bestPos, bestBonus = index, bonus
|
||||||
}
|
}
|
||||||
if bonus == bonusBoundary {
|
if bonus >= bonusBoundary {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
index -= pidx - 1
|
index -= pidx - 1
|
||||||
@@ -877,8 +939,8 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
|
|||||||
match = runesStr == string(pattern)
|
match = runesStr == string(pattern)
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
|
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+int(bonusBoundaryWhite))*lenPattern +
|
||||||
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
|
(bonusFirstCharMultiplier-1)*int(bonusBoundaryWhite)}, nil
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
@@ -45,29 +45,29 @@ func TestFuzzyMatch(t *testing.T) {
|
|||||||
assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
|
assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
|
||||||
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
|
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
|
||||||
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
|
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
|
||||||
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
|
||||||
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
|
int(bonusBoundaryWhite)*2+2*scoreGapStart+4*scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
|
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
|
||||||
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
|
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
|
||||||
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
|
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
|
||||||
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
|
scoreMatch*4+int(bonusBoundaryDelimiter)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*3)
|
||||||
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
|
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
|
||||||
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3+scoreGapStart)
|
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+int(bonusBoundaryDelimiter))
|
||||||
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
|
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
|
||||||
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
|
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
|
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
|
||||||
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
|
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
|
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
|
||||||
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
|
||||||
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
|
int(bonusBoundaryDelimiter)*2+2*scoreGapStart+4*scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
|
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
|
||||||
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
|
||||||
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
|
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
|
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
|
||||||
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)+
|
||||||
scoreGapStart*2+scoreGapExtension*3)
|
scoreGapStart*2+scoreGapExtension*3)
|
||||||
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
|
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
|
||||||
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
|
scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*3)
|
||||||
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
|
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
|
||||||
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
|
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
|
||||||
bonusNonWord+bonusBoundary)
|
bonusNonWord+bonusBoundary)
|
||||||
@@ -75,14 +75,14 @@ func TestFuzzyMatch(t *testing.T) {
|
|||||||
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
|
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
|
||||||
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
|
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
|
||||||
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
|
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
|
||||||
scoreMatch*3+bonusBoundary*(bonusFirstCharMultiplier+2)+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*2+
|
||||||
scoreGapStart*2+scoreGapExtension*4)
|
scoreGapStart*2+scoreGapExtension*4)
|
||||||
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
|
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
|
||||||
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusCamel123*2+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+bonusCamel123*2+
|
||||||
scoreGapStart*2+scoreGapExtension*2)
|
scoreGapStart*2+scoreGapExtension*2)
|
||||||
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
|
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
|
||||||
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+
|
scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*2+
|
||||||
util.Max(bonusCamel123, bonusBoundary))
|
util.Max(bonusCamel123, int(bonusBoundaryWhite)))
|
||||||
|
|
||||||
// Consecutive bonus updated
|
// Consecutive bonus updated
|
||||||
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,
|
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,
|
||||||
@@ -98,10 +98,10 @@ func TestFuzzyMatch(t *testing.T) {
|
|||||||
|
|
||||||
func TestFuzzyMatchBackward(t *testing.T) {
|
func TestFuzzyMatchBackward(t *testing.T) {
|
||||||
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
|
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
|
||||||
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
|
||||||
scoreGapStart+scoreGapExtension)
|
scoreGapStart+scoreGapExtension)
|
||||||
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
|
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
|
||||||
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary)
|
scoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExactMatchNaive(t *testing.T) {
|
func TestExactMatchNaive(t *testing.T) {
|
||||||
@@ -114,9 +114,9 @@ func TestExactMatchNaive(t *testing.T) {
|
|||||||
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13,
|
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13,
|
||||||
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
|
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
|
||||||
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10,
|
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10,
|
||||||
scoreMatch*4+bonusBoundary*(bonusFirstCharMultiplier+3))
|
scoreMatch*4+int(bonusBoundaryDelimiter)*(bonusFirstCharMultiplier+3))
|
||||||
assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13,
|
assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13,
|
||||||
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+4))
|
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+int(bonusBoundaryDelimiter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ func TestExactMatchNaiveBackward(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixMatch(t *testing.T) {
|
func TestPrefixMatch(t *testing.T) {
|
||||||
score := (scoreMatch+bonusBoundary)*3 + bonusBoundary*(bonusFirstCharMultiplier-1)
|
score := scoreMatch*3 + int(bonusBoundaryWhite)*bonusFirstCharMultiplier + int(bonusBoundaryWhite)*2
|
||||||
|
|
||||||
for _, dir := range []bool{true, false} {
|
for _, dir := range []bool{true, false} {
|
||||||
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
|
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
|
||||||
@@ -156,9 +156,10 @@ func TestSuffixMatch(t *testing.T) {
|
|||||||
// Strip trailing white space from the string
|
// Strip trailing white space from the string
|
||||||
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9,
|
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9,
|
||||||
scoreMatch*3+bonusConsecutive*2)
|
scoreMatch*3+bonusConsecutive*2)
|
||||||
|
|
||||||
// Only when the pattern doesn't end with a space
|
// Only when the pattern doesn't end with a space
|
||||||
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
|
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
|
||||||
scoreMatch*4+bonusConsecutive*2+bonusNonWord)
|
scoreMatch*4+bonusConsecutive*2+int(bonusBoundaryWhite))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +183,9 @@ func TestNormalize(t *testing.T) {
|
|||||||
input, pattern, sidx, eidx, score)
|
input, pattern, sidx, eidx, score)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
test("Só Danço Samba", "So", 0, 2, 56, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
|
test("Só Danço Samba", "So", 0, 2, 62, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
|
||||||
test("Só Danço Samba", "sodc", 0, 7, 89, FuzzyMatchV1, FuzzyMatchV2)
|
test("Só Danço Samba", "sodc", 0, 7, 97, FuzzyMatchV1, FuzzyMatchV2)
|
||||||
test("Danço", "danco", 0, 5, 128, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
|
test("Danço", "danco", 0, 5, 140, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLongString(t *testing.T) {
|
func TestLongString(t *testing.T) {
|
||||||
|
58
src/ansi.go
58
src/ansi.go
@@ -55,6 +55,9 @@ func (s *ansiState) ToString() string {
|
|||||||
if s.attr&tui.Reverse > 0 {
|
if s.attr&tui.Reverse > 0 {
|
||||||
ret += "7;"
|
ret += "7;"
|
||||||
}
|
}
|
||||||
|
if s.attr&tui.StrikeThrough > 0 {
|
||||||
|
ret += "9;"
|
||||||
|
}
|
||||||
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
||||||
|
|
||||||
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
||||||
@@ -85,7 +88,7 @@ func isPrint(c uint8) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func matchOperatingSystemCommand(s string) int {
|
func matchOperatingSystemCommand(s string) int {
|
||||||
// `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
|
// `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
|
||||||
// ^ match starting here
|
// ^ match starting here
|
||||||
//
|
//
|
||||||
i := 5 // prefix matched in nextAnsiEscapeSequence()
|
i := 5 // prefix matched in nextAnsiEscapeSequence()
|
||||||
@@ -103,30 +106,37 @@ func matchOperatingSystemCommand(s string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func matchControlSequence(s string) int {
|
func matchControlSequence(s string) int {
|
||||||
// `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
|
// `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
|
||||||
// ^ match starting here
|
// ^ match starting here
|
||||||
//
|
//
|
||||||
i := 2 // prefix matched in nextAnsiEscapeSequence()
|
i := 2 // prefix matched in nextAnsiEscapeSequence()
|
||||||
for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';' || s[i] == '?'); i++ {
|
for ; i < len(s); i++ {
|
||||||
}
|
|
||||||
if i < len(s) {
|
|
||||||
c := s[i]
|
c := s[i]
|
||||||
|
switch c {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';', ':', '?':
|
||||||
|
// ok
|
||||||
|
default:
|
||||||
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
|
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
|
||||||
return i + 1
|
return i + 1
|
||||||
}
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func isCtrlSeqStart(c uint8) bool {
|
func isCtrlSeqStart(c uint8) bool {
|
||||||
return c == '\\' || c == '[' || c == '(' || c == ')'
|
switch c {
|
||||||
|
case '\\', '[', '(', ')':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -154,16 +164,16 @@ Loop:
|
|||||||
return i - n, i + 1
|
return i - n, i + 1
|
||||||
}
|
}
|
||||||
case '\x1b':
|
case '\x1b':
|
||||||
// match: `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
|
// match: `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
|
||||||
if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
|
if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
|
||||||
if j := matchControlSequence(s[i:]); j != -1 {
|
if j := matchControlSequence(s[i:]); j != -1 {
|
||||||
return i, i + j
|
return i, i + j
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// match: `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
|
// match: `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
|
||||||
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
|
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
|
||||||
s[i+3] == ';' && isPrint(s[i+4]) {
|
(s[i+3] == ';' || s[i+3] == ':') && isPrint(s[i+4]) {
|
||||||
|
|
||||||
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
|
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
|
||||||
return i, i + j
|
return i, i + j
|
||||||
@@ -280,9 +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
|
||||||
if i := strings.IndexByte(s, ';'); i >= 0 {
|
i := -1
|
||||||
|
if delimiter == 0 {
|
||||||
|
// Faster than strings.IndexAny(";:")
|
||||||
|
i = strings.IndexByte(s, ';')
|
||||||
|
if i < 0 {
|
||||||
|
i = strings.IndexByte(s, ':')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i = strings.IndexByte(s, delimiter)
|
||||||
|
}
|
||||||
|
if i >= 0 {
|
||||||
|
delimiter = s[i]
|
||||||
remaining = s[i+1:]
|
remaining = s[i+1:]
|
||||||
s = s[:i]
|
s = s[:i]
|
||||||
}
|
}
|
||||||
@@ -294,14 +315,14 @@ func parseAnsiCode(s string) (int, string) {
|
|||||||
for _, ch := range []byte(s) {
|
for _, ch := range []byte(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 {
|
||||||
@@ -329,9 +350,10 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state256 := 0
|
state256 := 0
|
||||||
ptr := &state.fg
|
ptr := &state.fg
|
||||||
|
|
||||||
|
var delimiter byte = 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 {
|
||||||
switch state256 {
|
switch state256 {
|
||||||
case 0:
|
case 0:
|
||||||
switch num {
|
switch num {
|
||||||
@@ -357,6 +379,8 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state.attr = state.attr | tui.Blink
|
state.attr = state.attr | tui.Blink
|
||||||
case 7:
|
case 7:
|
||||||
state.attr = state.attr | tui.Reverse
|
state.attr = state.attr | tui.Reverse
|
||||||
|
case 9:
|
||||||
|
state.attr = state.attr | tui.StrikeThrough
|
||||||
case 23: // tput rmso
|
case 23: // tput rmso
|
||||||
state.attr = state.attr &^ tui.Italic
|
state.attr = state.attr &^ tui.Italic
|
||||||
case 24: // tput rmul
|
case 24: // tput rmul
|
||||||
|
@@ -22,7 +22,7 @@ import (
|
|||||||
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
||||||
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||||
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;:]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
||||||
|
|
||||||
func testParserReference(t testing.TB, str string) {
|
func testParserReference(t testing.TB, str string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -358,6 +358,7 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
|||||||
assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
|
assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
|
||||||
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m")
|
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m")
|
||||||
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
|
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
|
||||||
|
assert("\x1b[38:5:100:48:5:200m", nil, "\x1b[38;5;100;48;5;200m")
|
||||||
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
|
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
|
||||||
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
|
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
|
||||||
assert("\x1b[48;5;100;38;2;10;20;30;7m",
|
assert("\x1b[48;5;100;38;2;10;20;30;7m",
|
||||||
@@ -377,7 +378,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)
|
||||||
}
|
}
|
||||||
@@ -385,9 +386,9 @@ func TestParseAnsiCode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// kernel/bpf/preload/iterators/README
|
// kernel/bpf/preload/iterators/README
|
||||||
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" +
|
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38:5:81mbpf/" +
|
||||||
"\x1b[0m\x1b[38;5;81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
|
"\x1b[0m\x1b[38:5:81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
|
||||||
"\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m"
|
"\x1b[0m\x1b[38:5:149mMakefile\x1b[m\x1b[K\x1b[0m"
|
||||||
|
|
||||||
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
|
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
|
||||||
b.SetBytes(int64(len(ansiBenchmarkString)))
|
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||||
|
89
src/core.go
89
src/core.go
@@ -1,28 +1,4 @@
|
|||||||
/*
|
// Package fzf implements fzf, a command-line fuzzy finder.
|
||||||
Package fzf implements fzf, a command-line fuzzy finder.
|
|
||||||
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -146,18 +122,20 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
forward := true
|
forward := true
|
||||||
for _, cri := range opts.Criteria[1:] {
|
withPos := false
|
||||||
if cri == byEnd {
|
for idx := len(opts.Criteria) - 1; idx > 0; idx-- {
|
||||||
|
switch opts.Criteria[idx] {
|
||||||
|
case byChunk:
|
||||||
|
withPos = true
|
||||||
|
case byEnd:
|
||||||
forward = false
|
forward = false
|
||||||
break
|
case byBegin:
|
||||||
}
|
forward = true
|
||||||
if cri == byBegin {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
return BuildPattern(
|
return BuildPattern(
|
||||||
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward,
|
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
||||||
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
||||||
}
|
}
|
||||||
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
||||||
@@ -216,10 +194,17 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
|
|
||||||
// Terminal I/O
|
// Terminal I/O
|
||||||
terminal := NewTerminal(opts, eventBox)
|
terminal := NewTerminal(opts, eventBox)
|
||||||
|
maxFit := 0 // Maximum number of items that can fit on screen
|
||||||
|
padHeight := 0
|
||||||
|
heightUnknown := opts.Height.auto
|
||||||
|
if heightUnknown {
|
||||||
|
maxFit, padHeight = terminal.MaxFitAndPad(opts)
|
||||||
|
}
|
||||||
deferred := opts.Select1 || opts.Exit0
|
deferred := opts.Select1 || opts.Exit0
|
||||||
go terminal.Loop()
|
go terminal.Loop()
|
||||||
if !deferred {
|
if !deferred && !heightUnknown {
|
||||||
terminal.startChan <- true
|
// Start right away
|
||||||
|
terminal.startChan <- fitpad{-1, -1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event coordination
|
// Event coordination
|
||||||
@@ -238,13 +223,27 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
go reader.restart(command)
|
go reader.restart(command)
|
||||||
}
|
}
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
|
total := 0
|
||||||
query := []rune{}
|
query := []rune{}
|
||||||
|
determine := func(final bool) {
|
||||||
|
if heightUnknown {
|
||||||
|
if total >= maxFit || final {
|
||||||
|
heightUnknown = false
|
||||||
|
terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
|
||||||
|
}
|
||||||
|
} else if deferred {
|
||||||
|
deferred = false
|
||||||
|
terminal.startChan <- fitpad{-1, -1}
|
||||||
|
}
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
input := func() []rune {
|
input := func(reloaded bool) []rune {
|
||||||
paused, input := terminal.Input()
|
paused, input := terminal.Input()
|
||||||
if !paused {
|
if reloaded && paused {
|
||||||
|
query = []rune{}
|
||||||
|
} else if !paused {
|
||||||
query = input
|
query = input
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
@@ -269,12 +268,17 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
reading = reading && evt == EvtReadNew
|
reading = reading && evt == EvtReadNew
|
||||||
}
|
}
|
||||||
snapshot, count := chunkList.Snapshot()
|
snapshot, count := chunkList.Snapshot()
|
||||||
terminal.UpdateCount(count, !reading, value.(*string))
|
total = count
|
||||||
|
terminal.UpdateCount(total, !reading, value.(*string))
|
||||||
if opts.Sync {
|
if opts.Sync {
|
||||||
opts.Sync = false
|
opts.Sync = false
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
||||||
}
|
}
|
||||||
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
|
if heightUnknown && !deferred {
|
||||||
|
determine(!reading)
|
||||||
|
}
|
||||||
|
reset := clearCache()
|
||||||
|
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
var command *string
|
var command *string
|
||||||
@@ -293,7 +297,8 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
snapshot, _ := chunkList.Snapshot()
|
snapshot, _ := chunkList.Snapshot()
|
||||||
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
|
reset := clearCache()
|
||||||
|
matcher.Reset(snapshot, input(reset), true, !reading, sort, reset)
|
||||||
delay = false
|
delay = false
|
||||||
|
|
||||||
case EvtSearchProgress:
|
case EvtSearchProgress:
|
||||||
@@ -313,8 +318,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if deferred {
|
if deferred {
|
||||||
count := val.Length()
|
count := val.Length()
|
||||||
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
|
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
|
||||||
deferred = false
|
determine(val.final)
|
||||||
terminal.startChan <- true
|
|
||||||
} else if val.final {
|
} else if val.final {
|
||||||
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
|
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
|
||||||
if opts.PrintQuery {
|
if opts.PrintQuery {
|
||||||
@@ -331,8 +335,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
os.Exit(exitNoMatch)
|
os.Exit(exitNoMatch)
|
||||||
}
|
}
|
||||||
deferred = false
|
determine(val.final)
|
||||||
terminal.startChan <- true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.UpdateList(val, clearSelection())
|
terminal.UpdateList(val, clearSelection())
|
||||||
|
218
src/options.go
218
src/options.go
@@ -21,9 +21,9 @@ const usage = `usage: fzf [options]
|
|||||||
-x, --extended Extended-search mode
|
-x, --extended Extended-search mode
|
||||||
(enabled by default; +x or --no-extended to disable)
|
(enabled by default; +x or --no-extended to disable)
|
||||||
-e, --exact Enable Exact-match
|
-e, --exact Enable Exact-match
|
||||||
--algo=TYPE Fuzzy matching algorithm: [v1|v2] (default: v2)
|
|
||||||
-i Case-insensitive match (default: smart-case match)
|
-i Case-insensitive match (default: smart-case match)
|
||||||
+i Case-sensitive match
|
+i Case-sensitive match
|
||||||
|
--scheme=SCHEME Scoring scheme [default|path|history]
|
||||||
--literal Do not normalize latin script letters before matching
|
--literal Do not normalize latin script letters before matching
|
||||||
-n, --nth=N[,..] Comma-separated list of field index expressions
|
-n, --nth=N[,..] Comma-separated list of field index expressions
|
||||||
for limiting search scope. Each can be a non-zero
|
for limiting search scope. Each can be a non-zero
|
||||||
@@ -35,7 +35,7 @@ const usage = `usage: fzf [options]
|
|||||||
--tac Reverse the order of the input
|
--tac Reverse the order of the input
|
||||||
--disabled Do not perform search
|
--disabled Do not perform search
|
||||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||||
when the scores are tied [length|begin|end|index]
|
when the scores are tied [length|chunk|begin|end|index]
|
||||||
(default: length)
|
(default: length)
|
||||||
|
|
||||||
Interface
|
Interface
|
||||||
@@ -53,8 +53,10 @@ const usage = `usage: fzf [options]
|
|||||||
--jump-labels=CHARS Label characters for jump and jump-accept
|
--jump-labels=CHARS Label characters for jump and jump-accept
|
||||||
|
|
||||||
Layout
|
Layout
|
||||||
--height=HEIGHT[%] Display fzf window below the cursor with the given
|
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
||||||
height instead of using fullscreen
|
height instead of using fullscreen.
|
||||||
|
If prefixed with '~', fzf will determine the height
|
||||||
|
according to the input size.
|
||||||
--min-height=HEIGHT Minimum height when --height is given in percent
|
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||||
(default: 10)
|
(default: 10)
|
||||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
@@ -70,6 +72,7 @@ const usage = `usage: fzf [options]
|
|||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
--header-first Print header before the prompt line
|
--header-first Print header before the prompt line
|
||||||
|
--ellipsis=STR Ellipsis to show when line is truncated (default: '..')
|
||||||
|
|
||||||
Display
|
Display
|
||||||
--ansi Enable processing of ANSI color codes
|
--ansi Enable processing of ANSI color codes
|
||||||
@@ -88,7 +91,7 @@ const usage = `usage: fzf [options]
|
|||||||
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
||||||
[,border-BORDER_OPT]
|
[,border-BORDER_OPT]
|
||||||
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
||||||
[,default]
|
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
|
||||||
|
|
||||||
Scripting
|
Scripting
|
||||||
-q, --query=STR Start the finder with the given query
|
-q, --query=STR Start the finder with the given query
|
||||||
@@ -124,11 +127,18 @@ type criterion int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
byScore criterion = iota
|
byScore criterion = iota
|
||||||
|
byChunk
|
||||||
byLength
|
byLength
|
||||||
byBegin
|
byBegin
|
||||||
byEnd
|
byEnd
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type heightSpec struct {
|
||||||
|
size float64
|
||||||
|
percent bool
|
||||||
|
auto bool
|
||||||
|
}
|
||||||
|
|
||||||
type sizeSpec struct {
|
type sizeSpec struct {
|
||||||
size float64
|
size float64
|
||||||
percent bool
|
percent bool
|
||||||
@@ -174,12 +184,29 @@ type previewOpts struct {
|
|||||||
follow bool
|
follow bool
|
||||||
border tui.BorderShape
|
border tui.BorderShape
|
||||||
headerLines int
|
headerLines int
|
||||||
|
threshold int
|
||||||
|
alternative *previewOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a previewOpts) aboveOrBelow() bool {
|
||||||
|
return a.size.size > 0 && (a.position == posUp || a.position == posDown)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a previewOpts) sameLayout(b previewOpts) bool {
|
||||||
|
return a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden && a.threshold == b.threshold &&
|
||||||
|
(a.alternative != nil && b.alternative != nil && a.alternative.sameLayout(*b.alternative) ||
|
||||||
|
a.alternative == nil && b.alternative == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
||||||
|
return a.wrap == b.wrap && a.headerLines == b.headerLines
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options stores the values of command-line options
|
// Options stores the values of command-line options
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Fuzzy bool
|
Fuzzy bool
|
||||||
FuzzyAlgo algo.Algo
|
FuzzyAlgo algo.Algo
|
||||||
|
Scheme string
|
||||||
Extended bool
|
Extended bool
|
||||||
Phony bool
|
Phony bool
|
||||||
Case Case
|
Case Case
|
||||||
@@ -196,7 +223,7 @@ type Options struct {
|
|||||||
Theme *tui.ColorTheme
|
Theme *tui.ColorTheme
|
||||||
Black bool
|
Black bool
|
||||||
Bold bool
|
Bold bool
|
||||||
Height sizeSpec
|
Height heightSpec
|
||||||
MinHeight int
|
MinHeight int
|
||||||
Layout layoutType
|
Layout layoutType
|
||||||
Cycle bool
|
Cycle bool
|
||||||
@@ -216,7 +243,7 @@ type Options struct {
|
|||||||
Filter *string
|
Filter *string
|
||||||
ToggleSort bool
|
ToggleSort bool
|
||||||
Expect map[tui.Event]string
|
Expect map[tui.Event]string
|
||||||
Keymap map[tui.Event][]action
|
Keymap map[tui.Event][]*action
|
||||||
Preview previewOpts
|
Preview previewOpts
|
||||||
PrintQuery bool
|
PrintQuery bool
|
||||||
ReadZero bool
|
ReadZero bool
|
||||||
@@ -227,6 +254,7 @@ type Options struct {
|
|||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
HeaderFirst bool
|
HeaderFirst bool
|
||||||
|
Ellipsis string
|
||||||
Margin [4]sizeSpec
|
Margin [4]sizeSpec
|
||||||
Padding [4]sizeSpec
|
Padding [4]sizeSpec
|
||||||
BorderShape tui.BorderShape
|
BorderShape tui.BorderShape
|
||||||
@@ -237,13 +265,14 @@ type Options struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultPreviewOpts(command string) previewOpts {
|
func defaultPreviewOpts(command string) previewOpts {
|
||||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0}
|
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultOptions() *Options {
|
func defaultOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
Fuzzy: true,
|
Fuzzy: true,
|
||||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||||
|
Scheme: "default",
|
||||||
Extended: true,
|
Extended: true,
|
||||||
Phony: false,
|
Phony: false,
|
||||||
Case: CaseSmart,
|
Case: CaseSmart,
|
||||||
@@ -279,7 +308,7 @@ func defaultOptions() *Options {
|
|||||||
Filter: nil,
|
Filter: nil,
|
||||||
ToggleSort: false,
|
ToggleSort: false,
|
||||||
Expect: make(map[tui.Event]string),
|
Expect: make(map[tui.Event]string),
|
||||||
Keymap: make(map[tui.Event][]action),
|
Keymap: make(map[tui.Event][]*action),
|
||||||
Preview: defaultPreviewOpts(""),
|
Preview: defaultPreviewOpts(""),
|
||||||
PrintQuery: false,
|
PrintQuery: false,
|
||||||
ReadZero: false,
|
ReadZero: false,
|
||||||
@@ -290,6 +319,7 @@ func defaultOptions() *Options {
|
|||||||
Header: make([]string, 0),
|
Header: make([]string, 0),
|
||||||
HeaderLines: 0,
|
HeaderLines: 0,
|
||||||
HeaderFirst: false,
|
HeaderFirst: false,
|
||||||
|
Ellipsis: "..",
|
||||||
Margin: defaultMargin(),
|
Margin: defaultMargin(),
|
||||||
Padding: defaultMargin(),
|
Padding: defaultMargin(),
|
||||||
Unicode: true,
|
Unicode: true,
|
||||||
@@ -425,6 +455,15 @@ func parseAlgo(str string) algo.Algo {
|
|||||||
return algo.FuzzyMatchV2
|
return algo.FuzzyMatchV2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func processScheme(opts *Options) {
|
||||||
|
if !algo.Init(opts.Scheme) {
|
||||||
|
errorExit("invalid scoring scheme (expected: default|path|history)")
|
||||||
|
}
|
||||||
|
if opts.Scheme == "history" {
|
||||||
|
opts.Criteria = []criterion{byScore}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func parseBorder(str string, optional bool) tui.BorderShape {
|
func parseBorder(str string, optional bool) tui.BorderShape {
|
||||||
switch str {
|
switch str {
|
||||||
case "rounded":
|
case "rounded":
|
||||||
@@ -596,6 +635,7 @@ func parseKeyChords(str string, message string) map[tui.Event]string {
|
|||||||
func parseTiebreak(str string) []criterion {
|
func parseTiebreak(str string) []criterion {
|
||||||
criteria := []criterion{byScore}
|
criteria := []criterion{byScore}
|
||||||
hasIndex := false
|
hasIndex := false
|
||||||
|
hasChunk := false
|
||||||
hasLength := false
|
hasLength := false
|
||||||
hasBegin := false
|
hasBegin := false
|
||||||
hasEnd := false
|
hasEnd := false
|
||||||
@@ -612,6 +652,9 @@ func parseTiebreak(str string) []criterion {
|
|||||||
switch str {
|
switch str {
|
||||||
case "index":
|
case "index":
|
||||||
check(&hasIndex, "index")
|
check(&hasIndex, "index")
|
||||||
|
case "chunk":
|
||||||
|
check(&hasChunk, "chunk")
|
||||||
|
criteria = append(criteria, byChunk)
|
||||||
case "length":
|
case "length":
|
||||||
check(&hasLength, "length")
|
check(&hasLength, "length")
|
||||||
criteria = append(criteria, byLength)
|
criteria = append(criteria, byLength)
|
||||||
@@ -625,6 +668,9 @@ func parseTiebreak(str string) []criterion {
|
|||||||
errorExit("invalid sort criterion: " + str)
|
errorExit("invalid sort criterion: " + str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(criteria) > 4 {
|
||||||
|
errorExit("at most 3 tiebreaks are allowed: " + str)
|
||||||
|
}
|
||||||
return criteria
|
return criteria
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -677,6 +723,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
cattr.Attr |= tui.Blink
|
cattr.Attr |= tui.Blink
|
||||||
case "reverse":
|
case "reverse":
|
||||||
cattr.Attr |= tui.Reverse
|
cattr.Attr |= tui.Reverse
|
||||||
|
case "strikethrough":
|
||||||
|
cattr.Attr |= tui.StrikeThrough
|
||||||
case "black":
|
case "black":
|
||||||
cattr.Color = tui.Color(0)
|
cattr.Color = tui.Color(0)
|
||||||
case "red":
|
case "red":
|
||||||
@@ -787,10 +835,10 @@ func init() {
|
|||||||
// Backreferences are not supported.
|
// Backreferences are not supported.
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[tui.Event][]action, str string) {
|
func parseKeymap(keymap map[tui.Event][]*action, str string) {
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
symbol := ":"
|
symbol := ":"
|
||||||
if strings.HasPrefix(src, "+") {
|
if strings.HasPrefix(src, "+") {
|
||||||
@@ -799,10 +847,16 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
prefix := symbol + "execute"
|
prefix := symbol + "execute"
|
||||||
if strings.HasPrefix(src[1:], "reload") {
|
if strings.HasPrefix(src[1:], "reload") {
|
||||||
prefix = symbol + "reload"
|
prefix = symbol + "reload"
|
||||||
|
} else if strings.HasPrefix(src[1:], "change-preview-window") {
|
||||||
|
prefix = symbol + "change-preview-window"
|
||||||
|
} else if strings.HasPrefix(src[1:], "change-preview") {
|
||||||
|
prefix = symbol + "change-preview"
|
||||||
} else if strings.HasPrefix(src[1:], "preview") {
|
} else if strings.HasPrefix(src[1:], "preview") {
|
||||||
prefix = symbol + "preview"
|
prefix = symbol + "preview"
|
||||||
} else if strings.HasPrefix(src[1:], "unbind") {
|
} else if strings.HasPrefix(src[1:], "unbind") {
|
||||||
prefix = symbol + "unbind"
|
prefix = symbol + "unbind"
|
||||||
|
} else if strings.HasPrefix(src[1:], "rebind") {
|
||||||
|
prefix = symbol + "rebind"
|
||||||
} else if strings.HasPrefix(src[1:], "change-prompt") {
|
} else if strings.HasPrefix(src[1:], "change-prompt") {
|
||||||
prefix = symbol + "change-prompt"
|
prefix = symbol + "change-prompt"
|
||||||
} else if src[len(prefix)] == '-' {
|
} else if src[len(prefix)] == '-' {
|
||||||
@@ -842,7 +896,7 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
|
|
||||||
idx2 := len(pair[0]) + 1
|
idx2 := len(pair[0]) + 1
|
||||||
specs := strings.Split(pair[1], "+")
|
specs := strings.Split(pair[1], "+")
|
||||||
actions := make([]action, 0, len(specs))
|
actions := make([]*action, 0, len(specs))
|
||||||
appendAction := func(types ...actionType) {
|
appendAction := func(types ...actionType) {
|
||||||
actions = append(actions, toActions(types...)...)
|
actions = append(actions, toActions(types...)...)
|
||||||
}
|
}
|
||||||
@@ -1002,10 +1056,16 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
offset = len("reload")
|
offset = len("reload")
|
||||||
case actPreview:
|
case actPreview:
|
||||||
offset = len("preview")
|
offset = len("preview")
|
||||||
|
case actChangePreviewWindow:
|
||||||
|
offset = len("change-preview-window")
|
||||||
|
case actChangePreview:
|
||||||
|
offset = len("change-preview")
|
||||||
case actChangePrompt:
|
case actChangePrompt:
|
||||||
offset = len("change-prompt")
|
offset = len("change-prompt")
|
||||||
case actUnbind:
|
case actUnbind:
|
||||||
offset = len("unbind")
|
offset = len("unbind")
|
||||||
|
case actRebind:
|
||||||
|
offset = len("rebind")
|
||||||
case actExecuteSilent:
|
case actExecuteSilent:
|
||||||
offset = len("execute-silent")
|
offset = len("execute-silent")
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
@@ -1017,17 +1077,17 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
if spec[offset] == ':' {
|
if spec[offset] == ':' {
|
||||||
if specIndex == len(specs)-1 {
|
if specIndex == len(specs)-1 {
|
||||||
actionArg = spec[offset+1:]
|
actionArg = spec[offset+1:]
|
||||||
actions = append(actions, action{t: t, a: actionArg})
|
actions = append(actions, &action{t: t, a: actionArg})
|
||||||
} else {
|
} else {
|
||||||
prevSpec = spec + "+"
|
prevSpec = spec + "+"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
actionArg = spec[offset+1 : len(spec)-1]
|
actionArg = spec[offset+1 : len(spec)-1]
|
||||||
actions = append(actions, action{t: t, a: actionArg})
|
actions = append(actions, &action{t: t, a: actionArg})
|
||||||
}
|
}
|
||||||
if t == actUnbind {
|
if t == actUnbind || t == actRebind {
|
||||||
parseKeyChords(actionArg, "unbind target required")
|
parseKeyChords(actionArg, spec[0:offset]+" target required")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1051,8 +1111,14 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actReload
|
return actReload
|
||||||
case "unbind":
|
case "unbind":
|
||||||
return actUnbind
|
return actUnbind
|
||||||
|
case "rebind":
|
||||||
|
return actRebind
|
||||||
case "preview":
|
case "preview":
|
||||||
return actPreview
|
return actPreview
|
||||||
|
case "change-preview-window":
|
||||||
|
return actChangePreviewWindow
|
||||||
|
case "change-preview":
|
||||||
|
return actChangePreview
|
||||||
case "change-prompt":
|
case "change-prompt":
|
||||||
return actChangePrompt
|
return actChangePrompt
|
||||||
case "execute":
|
case "execute":
|
||||||
@@ -1065,7 +1131,7 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseToggleSort(keymap map[tui.Event][]action, str string) {
|
func parseToggleSort(keymap map[tui.Event][]*action, str string) {
|
||||||
keys := parseKeyChords(str, "key name required")
|
keys := parseKeyChords(str, "key name required")
|
||||||
if len(keys) != 1 {
|
if len(keys) != 1 {
|
||||||
errorExit("multiple keys specified")
|
errorExit("multiple keys specified")
|
||||||
@@ -1101,9 +1167,17 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
|
|||||||
return sizeSpec{val, percent}
|
return sizeSpec{val, percent}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHeight(str string) sizeSpec {
|
func parseHeight(str string) heightSpec {
|
||||||
|
heightSpec := heightSpec{}
|
||||||
|
if strings.HasPrefix(str, "~") {
|
||||||
|
heightSpec.auto = true
|
||||||
|
str = str[1:]
|
||||||
|
}
|
||||||
|
|
||||||
size := parseSize(str, 100, "height")
|
size := parseSize(str, 100, "height")
|
||||||
return size
|
heightSpec.size = size.size
|
||||||
|
heightSpec.percent = size.percent
|
||||||
|
return heightSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLayout(str string) layoutType {
|
func parseLayout(str string) layoutType {
|
||||||
@@ -1135,12 +1209,19 @@ func parseInfoStyle(str string) infoStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
delimRegex := regexp.MustCompile("[:,]") // : for backward compatibility
|
tokenRegex := regexp.MustCompile(`[:,]*(<([1-9][0-9]*)\(([^)<]+)\)|[^,:]+)`)
|
||||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||||
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
|
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
|
||||||
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
|
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
|
||||||
tokens := delimRegex.Split(input, -1)
|
tokens := tokenRegex.FindAllStringSubmatch(input, -1)
|
||||||
for _, token := range tokens {
|
var alternative string
|
||||||
|
for _, match := range tokens {
|
||||||
|
if len(match[2]) > 0 {
|
||||||
|
opts.threshold = atoi(match[2])
|
||||||
|
alternative = match[3]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
token := match[1]
|
||||||
switch token {
|
switch token {
|
||||||
case "":
|
case "":
|
||||||
case "default":
|
case "default":
|
||||||
@@ -1175,9 +1256,9 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
opts.border = tui.BorderHorizontal
|
opts.border = tui.BorderHorizontal
|
||||||
case "border-vertical":
|
case "border-vertical":
|
||||||
opts.border = tui.BorderVertical
|
opts.border = tui.BorderVertical
|
||||||
case "border-top":
|
case "border-up", "border-top":
|
||||||
opts.border = tui.BorderTop
|
opts.border = tui.BorderTop
|
||||||
case "border-bottom":
|
case "border-down", "border-bottom":
|
||||||
opts.border = tui.BorderBottom
|
opts.border = tui.BorderBottom
|
||||||
case "border-left":
|
case "border-left":
|
||||||
opts.border = tui.BorderLeft
|
opts.border = tui.BorderLeft
|
||||||
@@ -1199,6 +1280,13 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(alternative) > 0 {
|
||||||
|
alternativeOpts := *opts
|
||||||
|
opts.alternative = &alternativeOpts
|
||||||
|
opts.alternative.hidden = false
|
||||||
|
opts.alternative.alternative = nil
|
||||||
|
parsePreviewWindow(opts.alternative, alternative)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMargin(opt string, margin string) [4]sizeSpec {
|
func parseMargin(opt string, margin string) [4]sizeSpec {
|
||||||
@@ -1283,6 +1371,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Normalize = true
|
opts.Normalize = true
|
||||||
case "--algo":
|
case "--algo":
|
||||||
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
||||||
|
case "--scheme":
|
||||||
|
opts.Scheme = strings.ToLower(nextString(allArgs, &i, "scoring scheme required (default|path|history)"))
|
||||||
case "--expect":
|
case "--expect":
|
||||||
for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
|
for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
|
||||||
opts.Expect[k] = v
|
opts.Expect[k] = v
|
||||||
@@ -1440,6 +1530,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.HeaderFirst = true
|
opts.HeaderFirst = true
|
||||||
case "--no-header-first":
|
case "--no-header-first":
|
||||||
opts.HeaderFirst = false
|
opts.HeaderFirst = false
|
||||||
|
case "--ellipsis":
|
||||||
|
opts.Ellipsis = nextString(allArgs, &i, "ellipsis string required")
|
||||||
case "--preview":
|
case "--preview":
|
||||||
opts.Preview.command = nextString(allArgs, &i, "preview command required")
|
opts.Preview.command = nextString(allArgs, &i, "preview command required")
|
||||||
case "--no-preview":
|
case "--no-preview":
|
||||||
@@ -1448,11 +1540,11 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
parsePreviewWindow(&opts.Preview,
|
parsePreviewWindow(&opts.Preview,
|
||||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"))
|
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"))
|
||||||
case "--height":
|
case "--height":
|
||||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
opts.Height = parseHeight(nextString(allArgs, &i, "height required: [~]HEIGHT[%]"))
|
||||||
case "--min-height":
|
case "--min-height":
|
||||||
opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
|
opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
|
||||||
case "--no-height":
|
case "--no-height":
|
||||||
opts.Height = sizeSpec{}
|
opts.Height = heightSpec{}
|
||||||
case "--no-margin":
|
case "--no-margin":
|
||||||
opts.Margin = defaultMargin()
|
opts.Margin = defaultMargin()
|
||||||
case "--no-padding":
|
case "--no-padding":
|
||||||
@@ -1482,9 +1574,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.ClearOnExit = false
|
opts.ClearOnExit = false
|
||||||
case "--version":
|
case "--version":
|
||||||
opts.Version = true
|
opts.Version = true
|
||||||
|
case "--":
|
||||||
|
// Ignored
|
||||||
default:
|
default:
|
||||||
if match, value := optString(arg, "--algo="); match {
|
if match, value := optString(arg, "--algo="); match {
|
||||||
opts.FuzzyAlgo = parseAlgo(value)
|
opts.FuzzyAlgo = parseAlgo(value)
|
||||||
|
} else if match, value := optString(arg, "--scheme="); match {
|
||||||
|
opts.Scheme = strings.ToLower(value)
|
||||||
} else if match, value := optString(arg, "-q", "--query="); match {
|
} else if match, value := optString(arg, "-q", "--query="); match {
|
||||||
opts.Query = value
|
opts.Query = value
|
||||||
} else if match, value := optString(arg, "-f", "--filter="); match {
|
} else if match, value := optString(arg, "-f", "--filter="); match {
|
||||||
@@ -1537,6 +1633,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Header = strLines(value)
|
opts.Header = strLines(value)
|
||||||
} else if match, value := optString(arg, "--header-lines="); match {
|
} else if match, value := optString(arg, "--header-lines="); match {
|
||||||
opts.HeaderLines = atoi(value)
|
opts.HeaderLines = atoi(value)
|
||||||
|
} else if match, value := optString(arg, "--ellipsis="); match {
|
||||||
|
opts.Ellipsis = value
|
||||||
} else if match, value := optString(arg, "--preview="); match {
|
} else if match, value := optString(arg, "--preview="); match {
|
||||||
opts.Preview.command = value
|
opts.Preview.command = value
|
||||||
} else if match, value := optString(arg, "--preview-window="); match {
|
} else if match, value := optString(arg, "--preview-window="); match {
|
||||||
@@ -1605,11 +1703,6 @@ func validateSign(sign string, signOptName string) error {
|
|||||||
if sign == "" {
|
if sign == "" {
|
||||||
return fmt.Errorf("%v cannot be empty", signOptName)
|
return fmt.Errorf("%v cannot be empty", signOptName)
|
||||||
}
|
}
|
||||||
for _, r := range sign {
|
|
||||||
if !unicode.IsGraphic(r) {
|
|
||||||
return fmt.Errorf("invalid character in %v", signOptName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if runewidth.StringWidth(sign) > 2 {
|
if runewidth.StringWidth(sign) > 2 {
|
||||||
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
||||||
}
|
}
|
||||||
@@ -1631,17 +1724,59 @@ func postProcessOptions(opts *Options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extend the default key map
|
// Extend the default key map
|
||||||
|
previewEnabled := len(opts.Preview.command) > 0 || hasPreviewAction(opts)
|
||||||
keymap := defaultKeymap()
|
keymap := defaultKeymap()
|
||||||
for key, actions := range opts.Keymap {
|
for key, actions := range opts.Keymap {
|
||||||
|
var lastChangePreviewWindow *action
|
||||||
for _, act := range actions {
|
for _, act := range actions {
|
||||||
if act.t == actToggleSort {
|
switch act.t {
|
||||||
|
case actToggleSort:
|
||||||
|
// To display "+S"/"-S" on info line
|
||||||
opts.ToggleSort = true
|
opts.ToggleSort = true
|
||||||
|
case actChangePreviewWindow:
|
||||||
|
lastChangePreviewWindow = act
|
||||||
|
if !previewEnabled {
|
||||||
|
// Doesn't matter
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
opts := previewOpts{}
|
||||||
|
for _, arg := range strings.Split(act.a, "|") {
|
||||||
|
// Make sure that each expression is valid
|
||||||
|
parsePreviewWindow(&opts, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-organize actions so that we only keep the last change-preview-window
|
||||||
|
// and it comes first in the list.
|
||||||
|
// * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20)
|
||||||
|
// -> change-preview-window(up,+20)+preview(sleep 3; cat {})
|
||||||
|
if lastChangePreviewWindow != nil {
|
||||||
|
reordered := []*action{lastChangePreviewWindow}
|
||||||
|
for _, act := range actions {
|
||||||
|
if act.t != actChangePreviewWindow {
|
||||||
|
reordered = append(reordered, act)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions = reordered
|
||||||
}
|
}
|
||||||
keymap[key] = actions
|
keymap[key] = actions
|
||||||
}
|
}
|
||||||
opts.Keymap = keymap
|
opts.Keymap = keymap
|
||||||
|
|
||||||
|
if opts.Height.auto {
|
||||||
|
for _, s := range []sizeSpec{opts.Margin[0], opts.Margin[2]} {
|
||||||
|
if s.percent {
|
||||||
|
errorExit("adaptive height is not compatible with top/bottom percent margin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range []sizeSpec{opts.Padding[0], opts.Padding[2]} {
|
||||||
|
if s.percent {
|
||||||
|
errorExit("adaptive height is not compatible with top/bottom percent padding")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we're not using extended search mode, --nth option becomes irrelevant
|
// If we're not using extended search mode, --nth option becomes irrelevant
|
||||||
// if it contains the whole range
|
// if it contains the whole range
|
||||||
if !opts.Extended || len(opts.Nth) == 1 {
|
if !opts.Extended || len(opts.Nth) == 1 {
|
||||||
@@ -1671,12 +1806,31 @@ func postProcessOptions(opts *Options) {
|
|||||||
theme.Cursor = boldify(theme.Cursor)
|
theme.Cursor = boldify(theme.Cursor)
|
||||||
theme.Spinner = boldify(theme.Spinner)
|
theme.Spinner = boldify(theme.Spinner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.Scheme != "default" {
|
||||||
|
processScheme(opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectsArbitraryString(opt string) bool {
|
||||||
|
switch opt {
|
||||||
|
case "-q", "--query", "-f", "--filter", "--header", "--prompt":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseOptions parses command-line options
|
// ParseOptions parses command-line options
|
||||||
func ParseOptions() *Options {
|
func ParseOptions() *Options {
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
|
|
||||||
|
for idx, arg := range os.Args[1:] {
|
||||||
|
if arg == "--version" && (idx == 0 || idx > 0 && !expectsArbitraryString(os.Args[idx])) {
|
||||||
|
opts.Version = true
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Options from Env var
|
// Options from Env var
|
||||||
words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
|
words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
|
||||||
if len(words) > 0 {
|
if len(words) > 0 {
|
||||||
|
@@ -65,6 +65,19 @@ func TestDelimiterRegexRegex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDelimiterRegexRegexCaret(t *testing.T) {
|
||||||
|
delim := delimiterRegexp(`(^\s*|\s+)`)
|
||||||
|
tokens := Tokenize("foo bar baz", delim)
|
||||||
|
if delim.str != nil ||
|
||||||
|
len(tokens) != 4 ||
|
||||||
|
tokens[0].text.ToString() != "" ||
|
||||||
|
tokens[1].text.ToString() != "foo " ||
|
||||||
|
tokens[2].text.ToString() != "bar " ||
|
||||||
|
tokens[3].text.ToString() != "baz" {
|
||||||
|
t.Errorf("%s %d", tokens, len(tokens))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSplitNth(t *testing.T) {
|
func TestSplitNth(t *testing.T) {
|
||||||
{
|
{
|
||||||
ranges := splitNth("..")
|
ranges := splitNth("..")
|
||||||
@@ -440,8 +453,6 @@ func TestValidateSign(t *testing.T) {
|
|||||||
{"😀", true},
|
{"😀", true},
|
||||||
{"", false},
|
{"", false},
|
||||||
{">>>", false},
|
{">>>", false},
|
||||||
{"\n", false},
|
|
||||||
{"\t", false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
@@ -51,6 +51,7 @@ type Pattern struct {
|
|||||||
caseSensitive bool
|
caseSensitive bool
|
||||||
normalize bool
|
normalize bool
|
||||||
forward bool
|
forward bool
|
||||||
|
withPos bool
|
||||||
text []rune
|
text []rune
|
||||||
termSets []termSet
|
termSets []termSet
|
||||||
sortable bool
|
sortable bool
|
||||||
@@ -85,7 +86,7 @@ func clearChunkCache() {
|
|||||||
|
|
||||||
// BuildPattern builds Pattern object from the given arguments
|
// BuildPattern builds Pattern object from the given arguments
|
||||||
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
|
|
||||||
var asString string
|
var asString string
|
||||||
if extended {
|
if extended {
|
||||||
@@ -145,6 +146,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
caseSensitive: caseSensitive,
|
caseSensitive: caseSensitive,
|
||||||
normalize: normalize,
|
normalize: normalize,
|
||||||
forward: forward,
|
forward: forward,
|
||||||
|
withPos: withPos,
|
||||||
text: []rune(asString),
|
text: []rune(asString),
|
||||||
termSets: termSets,
|
termSets: termSets,
|
||||||
sortable: sortable,
|
sortable: sortable,
|
||||||
@@ -302,13 +304,13 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
|
|||||||
|
|
||||||
if space == nil {
|
if space == nil {
|
||||||
for idx := 0; idx < chunk.count; idx++ {
|
for idx := 0; idx < chunk.count; idx++ {
|
||||||
if match, _, _ := p.MatchItem(&chunk.items[idx], false, slab); match != nil {
|
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
|
||||||
matches = append(matches, *match)
|
matches = append(matches, *match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, result := range space {
|
for _, result := range space {
|
||||||
if match, _, _ := p.MatchItem(result.item, false, slab); match != nil {
|
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match != nil {
|
||||||
matches = append(matches, *match)
|
matches = append(matches, *match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -67,7 +67,7 @@ func TestParseTermsEmpty(t *testing.T) {
|
|||||||
func TestExact(t *testing.T) {
|
func TestExact(t *testing.T) {
|
||||||
defer clearPatternCache()
|
defer clearPatternCache()
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true,
|
||||||
[]Range{}, Delimiter{}, []rune("'abc"))
|
[]Range{}, Delimiter{}, []rune("'abc"))
|
||||||
chars := util.ToChars([]byte("aabbcc abc"))
|
chars := util.ToChars([]byte("aabbcc abc"))
|
||||||
res, pos := algo.ExactMatchNaive(
|
res, pos := algo.ExactMatchNaive(
|
||||||
@@ -83,7 +83,7 @@ func TestExact(t *testing.T) {
|
|||||||
func TestEqual(t *testing.T) {
|
func TestEqual(t *testing.T) {
|
||||||
defer clearPatternCache()
|
defer clearPatternCache()
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
||||||
|
|
||||||
match := func(str string, sidxExpected int, eidxExpected int) {
|
match := func(str string, sidxExpected int, eidxExpected int) {
|
||||||
chars := util.ToChars([]byte(str))
|
chars := util.ToChars([]byte(str))
|
||||||
@@ -106,17 +106,17 @@ func TestEqual(t *testing.T) {
|
|||||||
func TestCaseSensitivity(t *testing.T) {
|
func TestCaseSensitivity(t *testing.T) {
|
||||||
defer clearPatternCache()
|
defer clearPatternCache()
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
|
|
||||||
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
|
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
|
||||||
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
|
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
|
||||||
@@ -129,7 +129,7 @@ func TestCaseSensitivity(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOrigTextAndTransformed(t *testing.T) {
|
func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||||
tokens := Tokenize("junegunn", Delimiter{})
|
tokens := Tokenize("junegunn", Delimiter{})
|
||||||
trans := Transform(tokens, []Range{{1, 1}})
|
trans := Transform(tokens, []Range{{1, 1}})
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
func TestCacheKey(t *testing.T) {
|
func TestCacheKey(t *testing.T) {
|
||||||
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune(patStr))
|
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(patStr))
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ func TestCacheKey(t *testing.T) {
|
|||||||
func TestCacheable(t *testing.T) {
|
func TestCacheable(t *testing.T) {
|
||||||
test := func(fuzzy bool, str string, expected string, cacheable bool) {
|
test := func(fuzzy bool, str string, expected string, cacheable bool) {
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, true, []Range{}, Delimiter{}, []rune(str))
|
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, true, []Range{}, Delimiter{}, []rune(str))
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !openbsd
|
//go:build !openbsd
|
||||||
|
|
||||||
package protector
|
package protector
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build openbsd
|
//go:build openbsd
|
||||||
|
|
||||||
package protector
|
package protector
|
||||||
|
|
||||||
|
@@ -49,6 +49,21 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
case byScore:
|
case byScore:
|
||||||
// Higher is better
|
// Higher is better
|
||||||
val = math.MaxUint16 - util.AsUint16(score)
|
val = math.MaxUint16 - util.AsUint16(score)
|
||||||
|
case byChunk:
|
||||||
|
b := minBegin
|
||||||
|
e := maxEnd
|
||||||
|
l := item.text.Length()
|
||||||
|
for ; b >= 1; b-- {
|
||||||
|
if unicode.IsSpace(item.text.Get(b - 1)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ; e < l; e++ {
|
||||||
|
if unicode.IsSpace(item.text.Get(e)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val = util.AsUint16(e - b)
|
||||||
case byLength:
|
case byLength:
|
||||||
val = item.TrimLength()
|
val = item.TrimLength()
|
||||||
case byBegin, byEnd:
|
case byBegin, byEnd:
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !386,!amd64
|
//go:build !386 && !amd64
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
// +build !tcell
|
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -56,9 +54,9 @@ func TestResultRank(t *testing.T) {
|
|||||||
// FIXME global
|
// FIXME global
|
||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
str := []rune("foo")
|
||||||
item1 := buildResult(
|
item1 := buildResult(
|
||||||
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
|
withIndex(&Item{text: util.RunesToChars(str)}, 1), []Offset{}, 2)
|
||||||
if item1.points[3] != math.MaxUint16-2 || // Bonus
|
if item1.points[3] != math.MaxUint16-2 || // Bonus
|
||||||
item1.points[2] != 3 || // Length
|
item1.points[2] != 3 || // Length
|
||||||
item1.points[1] != 0 || // Unused
|
item1.points[1] != 0 || // Unused
|
||||||
@@ -67,7 +65,7 @@ func TestResultRank(t *testing.T) {
|
|||||||
t.Error(item1)
|
t.Error(item1)
|
||||||
}
|
}
|
||||||
// Only differ in index
|
// Only differ in index
|
||||||
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
|
item2 := buildResult(&Item{text: util.RunesToChars(str)}, []Offset{}, 2)
|
||||||
|
|
||||||
items := []Result{item1, item2}
|
items := []Result{item1, item2}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
@@ -100,6 +98,23 @@ func TestResultRank(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChunkTiebreak(t *testing.T) {
|
||||||
|
// FIXME global
|
||||||
|
sortCriteria = []criterion{byScore, byChunk}
|
||||||
|
|
||||||
|
score := 100
|
||||||
|
test := func(input string, offset Offset, chunk string) {
|
||||||
|
item := buildResult(withIndex(&Item{text: util.RunesToChars([]rune(input))}, 1), []Offset{offset}, score)
|
||||||
|
if !(item.points[3] == math.MaxUint16-uint16(score) && item.points[2] == uint16(len(chunk))) {
|
||||||
|
t.Error(item.points)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test("hello foobar goodbye", Offset{8, 9}, "foobar")
|
||||||
|
test("hello foobar goodbye", Offset{7, 18}, "foobar goodbye")
|
||||||
|
test("hello foobar goodbye", Offset{0, 1}, "hello")
|
||||||
|
test("hello foobar goodbye", Offset{5, 7}, "hello foobar") // TBD
|
||||||
|
}
|
||||||
|
|
||||||
func TestColorOffset(t *testing.T) {
|
func TestColorOffset(t *testing.T) {
|
||||||
// ------------ 20 ---- -- ----
|
// ------------ 20 ---- -- ----
|
||||||
// ++++++++ ++++++++++
|
// ++++++++ ++++++++++
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build 386 amd64
|
//go:build 386 || amd64
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
384
src/terminal.go
384
src/terminal.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -49,7 +50,6 @@ var offsetComponentRegex *regexp.Regexp
|
|||||||
var offsetTrimCharsRegex *regexp.Regexp
|
var offsetTrimCharsRegex *regexp.Regexp
|
||||||
var activeTempFiles []string
|
var activeTempFiles []string
|
||||||
|
|
||||||
const ellipsis string = ".."
|
|
||||||
const clearCode string = "\x1b[2J"
|
const clearCode string = "\x1b[2J"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -100,6 +100,11 @@ type itemLine struct {
|
|||||||
result Result
|
result Result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fitpad struct {
|
||||||
|
fit int
|
||||||
|
pad int
|
||||||
|
}
|
||||||
|
|
||||||
var emptyLine = itemLine{}
|
var emptyLine = itemLine{}
|
||||||
|
|
||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
@@ -135,7 +140,8 @@ type Terminal struct {
|
|||||||
toggleSort bool
|
toggleSort bool
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
expect map[tui.Event]string
|
expect map[tui.Event]string
|
||||||
keymap map[tui.Event][]action
|
keymap map[tui.Event][]*action
|
||||||
|
keymapOrg map[tui.Event][]*action
|
||||||
pressed string
|
pressed string
|
||||||
printQuery bool
|
printQuery bool
|
||||||
history *History
|
history *History
|
||||||
@@ -144,6 +150,7 @@ type Terminal struct {
|
|||||||
headerLines int
|
headerLines int
|
||||||
header []string
|
header []string
|
||||||
header0 []string
|
header0 []string
|
||||||
|
ellipsis string
|
||||||
ansi bool
|
ansi bool
|
||||||
tabstop int
|
tabstop int
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
@@ -170,6 +177,7 @@ type Terminal struct {
|
|||||||
selected map[int32]selectedItem
|
selected map[int32]selectedItem
|
||||||
version int64
|
version int64
|
||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
|
initialPreviewOpts previewOpts
|
||||||
previewOpts previewOpts
|
previewOpts previewOpts
|
||||||
previewer previewer
|
previewer previewer
|
||||||
previewed previewed
|
previewed previewed
|
||||||
@@ -180,7 +188,7 @@ type Terminal struct {
|
|||||||
prevLines []itemLine
|
prevLines []itemLine
|
||||||
suppress bool
|
suppress bool
|
||||||
sigstop bool
|
sigstop bool
|
||||||
startChan chan bool
|
startChan chan fitpad
|
||||||
killChan chan int
|
killChan chan int
|
||||||
slab *util.Slab
|
slab *util.Slab
|
||||||
theme *tui.ColorTheme
|
theme *tui.ColorTheme
|
||||||
@@ -216,6 +224,7 @@ const (
|
|||||||
reqRefresh
|
reqRefresh
|
||||||
reqReinit
|
reqReinit
|
||||||
reqRedraw
|
reqRedraw
|
||||||
|
reqFullRedraw
|
||||||
reqClose
|
reqClose
|
||||||
reqPrintQuery
|
reqPrintQuery
|
||||||
reqPreviewEnqueue
|
reqPreviewEnqueue
|
||||||
@@ -286,6 +295,8 @@ const (
|
|||||||
actTogglePreview
|
actTogglePreview
|
||||||
actTogglePreviewWrap
|
actTogglePreviewWrap
|
||||||
actPreview
|
actPreview
|
||||||
|
actChangePreview
|
||||||
|
actChangePreviewWindow
|
||||||
actPreviewTop
|
actPreviewTop
|
||||||
actPreviewBottom
|
actPreviewBottom
|
||||||
actPreviewUp
|
actPreviewUp
|
||||||
@@ -308,6 +319,7 @@ const (
|
|||||||
actSelect
|
actSelect
|
||||||
actDeselect
|
actDeselect
|
||||||
actUnbind
|
actUnbind
|
||||||
|
actRebind
|
||||||
)
|
)
|
||||||
|
|
||||||
type placeholderFlags struct {
|
type placeholderFlags struct {
|
||||||
@@ -326,6 +338,7 @@ type searchRequest struct {
|
|||||||
type previewRequest struct {
|
type previewRequest struct {
|
||||||
template string
|
template string
|
||||||
pwindow tui.Window
|
pwindow tui.Window
|
||||||
|
scrollOffset int
|
||||||
list []*Item
|
list []*Item
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,16 +349,16 @@ type previewResult struct {
|
|||||||
spinner string
|
spinner string
|
||||||
}
|
}
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []*action {
|
||||||
actions := make([]action, len(types))
|
actions := make([]*action, len(types))
|
||||||
for idx, t := range types {
|
for idx, t := range types {
|
||||||
actions[idx] = action{t: t, a: ""}
|
actions[idx] = &action{t: t, a: ""}
|
||||||
}
|
}
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultKeymap() map[tui.Event][]action {
|
func defaultKeymap() map[tui.Event][]*action {
|
||||||
keymap := make(map[tui.Event][]action)
|
keymap := make(map[tui.Event][]*action)
|
||||||
add := func(e tui.EventType, a actionType) {
|
add := func(e tui.EventType, a actionType) {
|
||||||
keymap[e.AsEvent()] = toActions(a)
|
keymap[e.AsEvent()] = toActions(a)
|
||||||
}
|
}
|
||||||
@@ -416,7 +429,7 @@ func trimQuery(query string) []rune {
|
|||||||
func hasPreviewAction(opts *Options) bool {
|
func hasPreviewAction(opts *Options) bool {
|
||||||
for _, actions := range opts.Keymap {
|
for _, actions := range opts.Keymap {
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
if action.t == actPreview {
|
if action.t == actPreview || action.t == actChangePreview {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,6 +444,13 @@ func makeSpinner(unicode bool) []string {
|
|||||||
return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func evaluateHeight(opts *Options, termHeight int) int {
|
||||||
|
if opts.Height.percent {
|
||||||
|
return util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
|
||||||
|
}
|
||||||
|
return int(opts.Height.size)
|
||||||
|
}
|
||||||
|
|
||||||
// NewTerminal returns new Terminal object
|
// NewTerminal returns new Terminal object
|
||||||
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
input := trimQuery(opts.Query)
|
input := trimQuery(opts.Query)
|
||||||
@@ -457,7 +477,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
strongAttr = tui.AttrRegular
|
strongAttr = tui.AttrRegular
|
||||||
}
|
}
|
||||||
var renderer tui.Renderer
|
var renderer tui.Renderer
|
||||||
fullscreen := opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100
|
fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100)
|
||||||
if fullscreen {
|
if fullscreen {
|
||||||
if tui.HasFullscreenRenderer() {
|
if tui.HasFullscreenRenderer() {
|
||||||
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
||||||
@@ -467,24 +487,16 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
maxHeightFunc := func(termHeight int) int {
|
maxHeightFunc := func(termHeight int) int {
|
||||||
var maxHeight int
|
// Minimum height required to render fzf excluding margin and padding
|
||||||
if opts.Height.percent {
|
|
||||||
maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
|
|
||||||
} else {
|
|
||||||
maxHeight = int(opts.Height.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
effectiveMinHeight := minHeight
|
effectiveMinHeight := minHeight
|
||||||
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
|
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
||||||
effectiveMinHeight *= 2
|
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
||||||
}
|
}
|
||||||
if opts.InfoStyle != infoDefault {
|
if opts.InfoStyle != infoDefault {
|
||||||
effectiveMinHeight--
|
effectiveMinHeight--
|
||||||
}
|
}
|
||||||
if opts.BorderShape != tui.BorderNone {
|
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||||
effectiveMinHeight += 2
|
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
|
||||||
}
|
|
||||||
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
|
|
||||||
}
|
}
|
||||||
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
||||||
}
|
}
|
||||||
@@ -495,6 +507,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
||||||
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
||||||
}
|
}
|
||||||
|
keymapCopy := make(map[tui.Event][]*action)
|
||||||
|
for key, action := range opts.Keymap {
|
||||||
|
keymapCopy[key] = action
|
||||||
|
}
|
||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
infoStyle: opts.InfoStyle,
|
infoStyle: opts.InfoStyle,
|
||||||
@@ -520,6 +536,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
delimiter: opts.Delimiter,
|
delimiter: opts.Delimiter,
|
||||||
expect: opts.Expect,
|
expect: opts.Expect,
|
||||||
keymap: opts.Keymap,
|
keymap: opts.Keymap,
|
||||||
|
keymapOrg: keymapCopy,
|
||||||
pressed: "",
|
pressed: "",
|
||||||
printQuery: opts.PrintQuery,
|
printQuery: opts.PrintQuery,
|
||||||
history: opts.History,
|
history: opts.History,
|
||||||
@@ -535,6 +552,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
headerLines: opts.HeaderLines,
|
headerLines: opts.HeaderLines,
|
||||||
header: header,
|
header: header,
|
||||||
header0: header,
|
header0: header,
|
||||||
|
ellipsis: opts.Ellipsis,
|
||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
reading: true,
|
reading: true,
|
||||||
@@ -547,6 +565,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
merger: EmptyMerger,
|
merger: EmptyMerger,
|
||||||
selected: make(map[int32]selectedItem),
|
selected: make(map[int32]selectedItem),
|
||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
|
initialPreviewOpts: opts.Preview,
|
||||||
previewOpts: opts.Preview,
|
previewOpts: opts.Preview,
|
||||||
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
|
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
|
||||||
previewed: previewed{0, 0, 0, false},
|
previewed: previewed{0, 0, 0, false},
|
||||||
@@ -557,7 +576,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
sigstop: false,
|
sigstop: false,
|
||||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||||
theme: opts.Theme,
|
theme: opts.Theme,
|
||||||
startChan: make(chan bool, 1),
|
startChan: make(chan fitpad, 1),
|
||||||
killChan: make(chan int),
|
killChan: make(chan int),
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
initFunc: func() { renderer.Init() },
|
initFunc: func() { renderer.Init() },
|
||||||
@@ -572,6 +591,32 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func borderLines(shape tui.BorderShape) int {
|
||||||
|
switch shape {
|
||||||
|
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp:
|
||||||
|
return 2
|
||||||
|
case tui.BorderTop, tui.BorderBottom:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra number of lines needed to display fzf
|
||||||
|
func (t *Terminal) extraLines() int {
|
||||||
|
extra := len(t.header0) + t.headerLines + 1
|
||||||
|
if !t.noInfoLine() {
|
||||||
|
extra++
|
||||||
|
}
|
||||||
|
return extra
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) MaxFitAndPad(opts *Options) (int, int) {
|
||||||
|
_, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
|
||||||
|
padHeight := marginInt[0] + marginInt[2] + paddingInt[0] + paddingInt[2]
|
||||||
|
fit := screenHeight - padHeight - t.extraLines()
|
||||||
|
return fit, padHeight
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
trimmed, colors, _ := extractColor(prompt, state, nil)
|
trimmed, colors, _ := extractColor(prompt, state, nil)
|
||||||
@@ -665,6 +710,7 @@ func (t *Terminal) UpdateList(merger *Merger, reset bool) {
|
|||||||
t.merger = merger
|
t.merger = merger
|
||||||
if reset {
|
if reset {
|
||||||
t.selected = make(map[int32]selectedItem)
|
t.selected = make(map[int32]selectedItem)
|
||||||
|
t.version++
|
||||||
}
|
}
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
t.reqBox.Set(reqInfo, nil)
|
t.reqBox.Set(reqInfo, nil)
|
||||||
@@ -703,28 +749,29 @@ func (t *Terminal) sortSelected() []selectedItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) displayWidth(runes []rune) int {
|
func (t *Terminal) displayWidth(runes []rune) int {
|
||||||
width, _ := util.RunesWidth(runes, 0, t.tabstop, 0)
|
width, _ := util.RunesWidth(runes, 0, t.tabstop, math.MaxInt32)
|
||||||
return width
|
return width
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minWidth = 4
|
minWidth = 4
|
||||||
minHeight = 4
|
minHeight = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
|
func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
|
||||||
max := base - occupied
|
max := base - occupied
|
||||||
|
if max < minSize {
|
||||||
|
max = minSize
|
||||||
|
}
|
||||||
if size.percent {
|
if size.percent {
|
||||||
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
|
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
|
||||||
}
|
}
|
||||||
return util.Constrain(int(size.size)+pad, minSize, max)
|
return util.Constrain(int(size.size)+pad, minSize, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) resizeWindows() {
|
func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
|
||||||
screenWidth := t.tui.MaxX()
|
screenWidth := t.tui.MaxX()
|
||||||
screenHeight := t.tui.MaxY()
|
screenHeight := t.tui.MaxY()
|
||||||
t.prevLines = make([]itemLine, screenHeight)
|
|
||||||
|
|
||||||
marginInt := [4]int{} // TRBL
|
marginInt := [4]int{} // TRBL
|
||||||
paddingInt := [4]int{} // TRBL
|
paddingInt := [4]int{} // TRBL
|
||||||
sizeSpecToInt := func(index int, spec sizeSpec) int {
|
sizeSpecToInt := func(index int, spec sizeSpec) int {
|
||||||
@@ -773,7 +820,9 @@ func (t *Terminal) resizeWindows() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
adjust := func(idx1 int, idx2 int, max int, min int) {
|
adjust := func(idx1 int, idx2 int, max int, min int) {
|
||||||
if max >= min {
|
if min > max {
|
||||||
|
min = max
|
||||||
|
}
|
||||||
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
|
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
|
||||||
if max-margin < min {
|
if max-margin < min {
|
||||||
desired := max - min
|
desired := max - min
|
||||||
@@ -783,38 +832,54 @@ func (t *Terminal) resizeWindows() {
|
|||||||
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
|
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
previewVisible := t.isPreviewEnabled() && t.previewOpts.size.size > 0
|
|
||||||
minAreaWidth := minWidth
|
minAreaWidth := minWidth
|
||||||
minAreaHeight := minHeight
|
minAreaHeight := minHeight
|
||||||
if previewVisible {
|
if t.noInfoLine() {
|
||||||
|
minAreaHeight -= 1
|
||||||
|
}
|
||||||
|
if t.isPreviewVisible() {
|
||||||
|
minPreviewHeight := 1 + borderLines(t.previewOpts.border)
|
||||||
|
minPreviewWidth := 5
|
||||||
switch t.previewOpts.position {
|
switch t.previewOpts.position {
|
||||||
case posUp, posDown:
|
case posUp, posDown:
|
||||||
minAreaHeight *= 2
|
minAreaHeight += minPreviewHeight
|
||||||
|
minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
|
||||||
case posLeft, posRight:
|
case posLeft, posRight:
|
||||||
minAreaWidth *= 2
|
minAreaWidth += minPreviewWidth
|
||||||
|
minAreaHeight = util.Max(minPreviewHeight, minAreaHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adjust(1, 3, screenWidth, minAreaWidth)
|
adjust(1, 3, screenWidth, minAreaWidth)
|
||||||
adjust(0, 2, screenHeight, minAreaHeight)
|
adjust(0, 2, screenHeight, minAreaHeight)
|
||||||
|
|
||||||
|
return screenWidth, screenHeight, marginInt, paddingInt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) resizeWindows() {
|
||||||
|
screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
|
||||||
|
width := screenWidth - marginInt[1] - marginInt[3]
|
||||||
|
height := screenHeight - marginInt[0] - marginInt[2]
|
||||||
|
|
||||||
|
t.prevLines = make([]itemLine, screenHeight)
|
||||||
if t.border != nil {
|
if t.border != nil {
|
||||||
t.border.Close()
|
t.border.Close()
|
||||||
}
|
}
|
||||||
if t.window != nil {
|
if t.window != nil {
|
||||||
t.window.Close()
|
t.window.Close()
|
||||||
|
t.window = nil
|
||||||
}
|
}
|
||||||
if t.pborder != nil {
|
if t.pborder != nil {
|
||||||
t.pborder.Close()
|
t.pborder.Close()
|
||||||
|
t.pborder = nil
|
||||||
}
|
}
|
||||||
if t.pwindow != nil {
|
if t.pwindow != nil {
|
||||||
t.pwindow.Close()
|
t.pwindow.Close()
|
||||||
|
t.pwindow = nil
|
||||||
}
|
}
|
||||||
// Reset preview version so that full redraw occurs
|
// Reset preview version so that full redraw occurs
|
||||||
t.previewed.version = 0
|
t.previewed.version = 0
|
||||||
|
|
||||||
width := screenWidth - marginInt[1] - marginInt[3]
|
|
||||||
height := screenHeight - marginInt[0] - marginInt[2]
|
|
||||||
switch t.borderShape {
|
switch t.borderShape {
|
||||||
case tui.BorderHorizontal:
|
case tui.BorderHorizontal:
|
||||||
t.border = t.tui.NewWindow(
|
t.border = t.tui.NewWindow(
|
||||||
@@ -846,26 +911,30 @@ func (t *Terminal) resizeWindows() {
|
|||||||
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add padding
|
// Add padding to margin
|
||||||
for idx, val := range paddingInt {
|
for idx, val := range paddingInt {
|
||||||
marginInt[idx] += val
|
marginInt[idx] += val
|
||||||
}
|
}
|
||||||
width = screenWidth - marginInt[1] - marginInt[3]
|
width -= paddingInt[1] + paddingInt[3]
|
||||||
height = screenHeight - marginInt[0] - marginInt[2]
|
height -= paddingInt[0] + paddingInt[2]
|
||||||
|
|
||||||
|
// Set up preview window
|
||||||
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
||||||
if previewVisible {
|
if t.isPreviewVisible() {
|
||||||
|
var resizePreviewWindows func(previewOpts previewOpts)
|
||||||
|
resizePreviewWindows = func(previewOpts previewOpts) {
|
||||||
|
hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil
|
||||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||||
pwidth := w
|
pwidth := w
|
||||||
pheight := h
|
pheight := h
|
||||||
var previewBorder tui.BorderStyle
|
var previewBorder tui.BorderStyle
|
||||||
if t.previewOpts.border == tui.BorderNone {
|
if previewOpts.border == tui.BorderNone {
|
||||||
previewBorder = tui.MakeTransparentBorder()
|
previewBorder = tui.MakeTransparentBorder()
|
||||||
} else {
|
} else {
|
||||||
previewBorder = tui.MakeBorderStyle(t.previewOpts.border, t.unicode)
|
previewBorder = tui.MakeBorderStyle(previewOpts.border, t.unicode)
|
||||||
}
|
}
|
||||||
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
||||||
switch t.previewOpts.border {
|
switch previewOpts.border {
|
||||||
case tui.BorderSharp, tui.BorderRounded:
|
case tui.BorderSharp, tui.BorderRounded:
|
||||||
pwidth -= 4
|
pwidth -= 4
|
||||||
pheight -= 2
|
pheight -= 2
|
||||||
@@ -892,7 +961,7 @@ func (t *Terminal) resizeWindows() {
|
|||||||
}
|
}
|
||||||
verticalPad := 2
|
verticalPad := 2
|
||||||
minPreviewHeight := 3
|
minPreviewHeight := 3
|
||||||
switch t.previewOpts.border {
|
switch previewOpts.border {
|
||||||
case tui.BorderNone, tui.BorderVertical, tui.BorderLeft, tui.BorderRight:
|
case tui.BorderNone, tui.BorderVertical, tui.BorderLeft, tui.BorderRight:
|
||||||
verticalPad = 0
|
verticalPad = 0
|
||||||
minPreviewHeight = 1
|
minPreviewHeight = 1
|
||||||
@@ -900,29 +969,46 @@ func (t *Terminal) resizeWindows() {
|
|||||||
verticalPad = 1
|
verticalPad = 1
|
||||||
minPreviewHeight = 2
|
minPreviewHeight = 2
|
||||||
}
|
}
|
||||||
switch t.previewOpts.position {
|
switch previewOpts.position {
|
||||||
case posUp:
|
case posUp, posDown:
|
||||||
pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
|
pheight := calculateSize(height, previewOpts.size, minHeight, minPreviewHeight, verticalPad)
|
||||||
|
if hasThreshold && pheight < previewOpts.threshold {
|
||||||
|
if !previewOpts.alternative.hidden {
|
||||||
|
resizePreviewWindows(*previewOpts.alternative)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if previewOpts.position == posUp {
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
||||||
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
||||||
case posDown:
|
} else {
|
||||||
pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
|
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
|
marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
|
||||||
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
||||||
case posLeft:
|
}
|
||||||
pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
|
case posLeft, posRight:
|
||||||
|
pwidth := calculateSize(width, previewOpts.size, minWidth, 5, 4)
|
||||||
|
if hasThreshold && pwidth < previewOpts.threshold {
|
||||||
|
if !previewOpts.alternative.hidden {
|
||||||
|
resizePreviewWindows(*previewOpts.alternative)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if previewOpts.position == posLeft {
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
|
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
|
||||||
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
||||||
case posRight:
|
} else {
|
||||||
pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
|
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
|
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
|
||||||
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
resizePreviewWindows(t.previewOpts)
|
||||||
|
}
|
||||||
|
if t.window == nil {
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0],
|
marginInt[0],
|
||||||
marginInt[3],
|
marginInt[3],
|
||||||
@@ -1191,7 +1277,7 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
|||||||
|
|
||||||
func (t *Terminal) trimRight(runes []rune, width int) ([]rune, bool) {
|
func (t *Terminal) trimRight(runes []rune, width int) ([]rune, bool) {
|
||||||
// We start from the beginning to handle tab characters
|
// We start from the beginning to handle tab characters
|
||||||
width, overflowIdx := util.RunesWidth(runes, 0, t.tabstop, width)
|
_, overflowIdx := util.RunesWidth(runes, 0, t.tabstop, width)
|
||||||
if overflowIdx >= 0 {
|
if overflowIdx >= 0 {
|
||||||
return runes[:overflowIdx], true
|
return runes[:overflowIdx], true
|
||||||
}
|
}
|
||||||
@@ -1254,47 +1340,54 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
|
|
||||||
offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
|
offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
|
||||||
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
||||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
ellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth/2)
|
||||||
|
maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(text))
|
||||||
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
||||||
if displayWidth > maxWidth {
|
if displayWidth > maxWidth {
|
||||||
transformOffsets := func(diff int32) {
|
transformOffsets := func(diff int32, rightTrim bool) {
|
||||||
for idx, offset := range offsets {
|
for idx, offset := range offsets {
|
||||||
b, e := offset.offset[0], offset.offset[1]
|
b, e := offset.offset[0], offset.offset[1]
|
||||||
b += 2 - diff
|
el := int32(len(ellipsis))
|
||||||
e += 2 - diff
|
b += el - diff
|
||||||
b = util.Max32(b, 2)
|
e += el - diff
|
||||||
|
b = util.Max32(b, el)
|
||||||
|
if rightTrim {
|
||||||
|
e = util.Min32(e, int32(maxWidth-ellipsisWidth))
|
||||||
|
}
|
||||||
offsets[idx].offset[0] = b
|
offsets[idx].offset[0] = b
|
||||||
offsets[idx].offset[1] = util.Max32(b, e)
|
offsets[idx].offset[1] = util.Max32(b, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.hscroll {
|
if t.hscroll {
|
||||||
if t.keepRight && pos == nil {
|
if t.keepRight && pos == nil {
|
||||||
trimmed, diff := t.trimLeft(text, maxWidth-2)
|
trimmed, diff := t.trimLeft(text, maxWidth-ellipsisWidth)
|
||||||
transformOffsets(diff)
|
transformOffsets(diff, false)
|
||||||
text = append([]rune(ellipsis), trimmed...)
|
text = append(ellipsis, trimmed...)
|
||||||
} else if !t.overflow(text[:maxe], maxWidth-2) {
|
} else if !t.overflow(text[:maxe], maxWidth-ellipsisWidth) {
|
||||||
// Stri..
|
// Stri..
|
||||||
text, _ = t.trimRight(text, maxWidth-2)
|
text, _ = t.trimRight(text, maxWidth-ellipsisWidth)
|
||||||
text = append(text, []rune(ellipsis)...)
|
text = append(text, ellipsis...)
|
||||||
} else {
|
} else {
|
||||||
// Stri..
|
// Stri..
|
||||||
if t.overflow(text[maxe:], 2) {
|
rightTrim := false
|
||||||
text = append(text[:maxe], []rune(ellipsis)...)
|
if t.overflow(text[maxe:], ellipsisWidth) {
|
||||||
|
text = append(text[:maxe], ellipsis...)
|
||||||
|
rightTrim = true
|
||||||
}
|
}
|
||||||
// ..ri..
|
// ..ri..
|
||||||
var diff int32
|
var diff int32
|
||||||
text, diff = t.trimLeft(text, maxWidth-2)
|
text, diff = t.trimLeft(text, maxWidth-ellipsisWidth)
|
||||||
|
|
||||||
// Transform offsets
|
// Transform offsets
|
||||||
transformOffsets(diff)
|
transformOffsets(diff, rightTrim)
|
||||||
text = append([]rune(ellipsis), text...)
|
text = append(ellipsis, text...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
text, _ = t.trimRight(text, maxWidth-2)
|
text, _ = t.trimRight(text, maxWidth-ellipsisWidth)
|
||||||
text = append(text, []rune(ellipsis)...)
|
text = append(text, ellipsis...)
|
||||||
|
|
||||||
for idx, offset := range offsets {
|
for idx, offset := range offsets {
|
||||||
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
|
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-len(ellipsis)))
|
||||||
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1394,6 +1487,7 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
|
|||||||
line = strings.TrimSuffix(line, "\n")
|
line = strings.TrimSuffix(line, "\n")
|
||||||
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
||||||
t.previewed.filled = true
|
t.previewed.filled = true
|
||||||
|
t.previewer.scrollable = true
|
||||||
break
|
break
|
||||||
} else if lineNo >= 0 {
|
} else if lineNo >= 0 {
|
||||||
var fillRet tui.FillReturn
|
var fillRet tui.FillReturn
|
||||||
@@ -1642,9 +1736,14 @@ func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input str
|
|||||||
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
func (t *Terminal) evaluateScrollOffset() int {
|
||||||
|
if t.pwindow == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only need the current item to calculate the scroll offset
|
||||||
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
|
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
|
||||||
t.replacePlaceholder(t.previewOpts.scroll, false, "", list), "")
|
t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil}), "")
|
||||||
|
|
||||||
atoi := func(s string) int {
|
atoi := func(s string) int {
|
||||||
n, e := strconv.Atoi(s)
|
n, e := strconv.Atoi(s)
|
||||||
@@ -1655,20 +1754,21 @@ func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
base := -1
|
base := -1
|
||||||
|
height := util.Max(0, t.pwindow.Height()-t.previewOpts.headerLines)
|
||||||
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
|
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
|
||||||
if strings.HasPrefix(component, "-/") {
|
if strings.HasPrefix(component, "-/") {
|
||||||
component = component[1:]
|
component = component[1:]
|
||||||
}
|
}
|
||||||
if component[0] == '/' {
|
if component[0] == '/' {
|
||||||
denom := atoi(component[1:])
|
denom := atoi(component[1:])
|
||||||
if denom == 0 {
|
if denom != 0 {
|
||||||
return base
|
base -= height / denom
|
||||||
}
|
}
|
||||||
return base - height/denom
|
break
|
||||||
}
|
}
|
||||||
base += atoi(component)
|
base += atoi(component)
|
||||||
}
|
}
|
||||||
return base
|
return util.Max(0, base)
|
||||||
}
|
}
|
||||||
|
|
||||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||||
@@ -1767,8 +1867,10 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, pr
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) redraw() {
|
func (t *Terminal) redraw(clear bool) {
|
||||||
|
if clear {
|
||||||
t.tui.Clear()
|
t.tui.Clear()
|
||||||
|
}
|
||||||
t.tui.Refresh()
|
t.tui.Refresh()
|
||||||
t.printAll()
|
t.printAll()
|
||||||
}
|
}
|
||||||
@@ -1788,7 +1890,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
t.tui.Pause(true)
|
t.tui.Pause(true)
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
t.tui.Resume(true, false)
|
t.tui.Resume(true, false)
|
||||||
t.redraw()
|
t.redraw(true)
|
||||||
t.refresh()
|
t.refresh()
|
||||||
} else {
|
} else {
|
||||||
t.tui.Pause(false)
|
t.tui.Pause(false)
|
||||||
@@ -1807,6 +1909,10 @@ func (t *Terminal) isPreviewEnabled() bool {
|
|||||||
return t.hasPreviewer() && t.previewer.enabled
|
return t.hasPreviewer() && t.previewer.enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) isPreviewVisible() bool {
|
||||||
|
return t.isPreviewEnabled() && t.previewOpts.size.size > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) hasPreviewWindow() bool {
|
func (t *Terminal) hasPreviewWindow() bool {
|
||||||
return t.pwindow != nil && t.isPreviewEnabled()
|
return t.pwindow != nil && t.isPreviewEnabled()
|
||||||
}
|
}
|
||||||
@@ -1906,7 +2012,28 @@ func (t *Terminal) cancelPreview() {
|
|||||||
// Loop is called to start Terminal I/O
|
// Loop is called to start Terminal I/O
|
||||||
func (t *Terminal) Loop() {
|
func (t *Terminal) Loop() {
|
||||||
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
||||||
<-t.startChan
|
fitpad := <-t.startChan
|
||||||
|
fit := fitpad.fit
|
||||||
|
if fit >= 0 {
|
||||||
|
pad := fitpad.pad
|
||||||
|
t.tui.Resize(func(termHeight int) int {
|
||||||
|
contentHeight := fit + t.extraLines()
|
||||||
|
if t.hasPreviewer() {
|
||||||
|
if t.previewOpts.aboveOrBelow() {
|
||||||
|
if t.previewOpts.size.percent {
|
||||||
|
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.previewOpts.size.size))
|
||||||
|
contentHeight = util.Max(contentHeight+1+borderLines(t.previewOpts.border), newContentHeight)
|
||||||
|
} else {
|
||||||
|
contentHeight += int(t.previewOpts.size.size) + borderLines(t.previewOpts.border)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Minimum height if preview window can appear
|
||||||
|
contentHeight = util.Max(contentHeight, 1+borderLines(t.previewOpts.border))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.Min(termHeight, contentHeight+pad)
|
||||||
|
})
|
||||||
|
}
|
||||||
{ // Late initialization
|
{ // Late initialization
|
||||||
intChan := make(chan os.Signal, 1)
|
intChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
||||||
@@ -1933,7 +2060,7 @@ func (t *Terminal) Loop() {
|
|||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
<-resizeChan
|
<-resizeChan
|
||||||
t.reqBox.Set(reqRedraw, nil)
|
t.reqBox.Set(reqFullRedraw, nil)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -1972,12 +2099,14 @@ func (t *Terminal) Loop() {
|
|||||||
var items []*Item
|
var items []*Item
|
||||||
var commandTemplate string
|
var commandTemplate string
|
||||||
var pwindow tui.Window
|
var pwindow tui.Window
|
||||||
|
initialOffset := 0
|
||||||
t.previewBox.Wait(func(events *util.Events) {
|
t.previewBox.Wait(func(events *util.Events) {
|
||||||
for req, value := range *events {
|
for req, value := range *events {
|
||||||
switch req {
|
switch req {
|
||||||
case reqPreviewEnqueue:
|
case reqPreviewEnqueue:
|
||||||
request := value.(previewRequest)
|
request := value.(previewRequest)
|
||||||
commandTemplate = request.template
|
commandTemplate = request.template
|
||||||
|
initialOffset = request.scrollOffset
|
||||||
items = request.list
|
items = request.list
|
||||||
pwindow = request.pwindow
|
pwindow = request.pwindow
|
||||||
}
|
}
|
||||||
@@ -1989,11 +2118,9 @@ func (t *Terminal) Loop() {
|
|||||||
if items[0] != nil {
|
if items[0] != nil {
|
||||||
_, query := t.Input()
|
_, query := t.Input()
|
||||||
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
||||||
initialOffset := 0
|
|
||||||
cmd := util.ExecCommand(command, true)
|
cmd := util.ExecCommand(command, true)
|
||||||
if pwindow != nil {
|
if pwindow != nil {
|
||||||
height := pwindow.Height()
|
height := pwindow.Height()
|
||||||
initialOffset = util.Max(0, t.evaluateScrollOffset(items, util.Max(0, height-t.previewOpts.headerLines)))
|
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
lines := fmt.Sprintf("LINES=%d", height)
|
lines := fmt.Sprintf("LINES=%d", height)
|
||||||
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
||||||
@@ -2128,7 +2255,7 @@ func (t *Terminal) Loop() {
|
|||||||
if len(command) > 0 && t.isPreviewEnabled() {
|
if len(command) > 0 && t.isPreviewEnabled() {
|
||||||
_, list := t.buildPlusList(command, false)
|
_, list := t.buildPlusList(command, false)
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, list})
|
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.evaluateScrollOffset(), list})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2183,9 +2310,11 @@ func (t *Terminal) Loop() {
|
|||||||
t.suppress = false
|
t.suppress = false
|
||||||
case reqReinit:
|
case reqReinit:
|
||||||
t.tui.Resume(t.fullscreen, t.sigstop)
|
t.tui.Resume(t.fullscreen, t.sigstop)
|
||||||
t.redraw()
|
t.redraw(true)
|
||||||
case reqRedraw:
|
case reqRedraw:
|
||||||
t.redraw()
|
t.redraw(false)
|
||||||
|
case reqFullRedraw:
|
||||||
|
t.redraw(true)
|
||||||
case reqClose:
|
case reqClose:
|
||||||
exit(func() int {
|
exit(func() int {
|
||||||
if t.output() {
|
if t.output() {
|
||||||
@@ -2253,13 +2382,15 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
togglePreview := func(enabled bool) {
|
togglePreview := func(enabled bool) bool {
|
||||||
if t.previewer.enabled != enabled {
|
if t.previewer.enabled != enabled {
|
||||||
t.previewer.enabled = enabled
|
t.previewer.enabled = enabled
|
||||||
t.tui.Clear()
|
// We need to immediately update t.pwindow so we don't use reqRedraw
|
||||||
t.resizeWindows()
|
t.resizeWindows()
|
||||||
req(reqPrompt, reqList, reqInfo, reqHeader)
|
req(reqPrompt, reqList, reqInfo, reqHeader)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
toggle := func() bool {
|
toggle := func() bool {
|
||||||
current := t.currentItem()
|
current := t.currentItem()
|
||||||
@@ -2296,12 +2427,12 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsFor := func(eventType tui.EventType) []action {
|
actionsFor := func(eventType tui.EventType) []*action {
|
||||||
return t.keymap[eventType.AsEvent()]
|
return t.keymap[eventType.AsEvent()]
|
||||||
}
|
}
|
||||||
|
|
||||||
var doAction func(action) bool
|
var doAction func(*action) bool
|
||||||
doActions := func(actions []action) bool {
|
doActions := func(actions []*action) bool {
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
if !doAction(action) {
|
if !doAction(action) {
|
||||||
return false
|
return false
|
||||||
@@ -2309,7 +2440,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
doAction = func(a action) bool {
|
doAction = func(a *action) bool {
|
||||||
switch a.t {
|
switch a.t {
|
||||||
case actIgnore:
|
case actIgnore:
|
||||||
case actExecute, actExecuteSilent:
|
case actExecute, actExecuteSilent:
|
||||||
@@ -2327,7 +2458,7 @@ func (t *Terminal) Loop() {
|
|||||||
if valid {
|
if valid {
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue,
|
t.previewBox.Set(reqPreviewEnqueue,
|
||||||
previewRequest{t.previewOpts.command, t.pwindow, list})
|
previewRequest{t.previewOpts.command, t.pwindow, t.evaluateScrollOffset(), list})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2489,14 +2620,14 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actToggleIn:
|
case actToggleIn:
|
||||||
if t.layout != layoutDefault {
|
if t.layout != layoutDefault {
|
||||||
return doAction(action{t: actToggleUp})
|
return doAction(&action{t: actToggleUp})
|
||||||
}
|
}
|
||||||
return doAction(action{t: actToggleDown})
|
return doAction(&action{t: actToggleDown})
|
||||||
case actToggleOut:
|
case actToggleOut:
|
||||||
if t.layout != layoutDefault {
|
if t.layout != layoutDefault {
|
||||||
return doAction(action{t: actToggleDown})
|
return doAction(&action{t: actToggleDown})
|
||||||
}
|
}
|
||||||
return doAction(action{t: actToggleUp})
|
return doAction(&action{t: actToggleUp})
|
||||||
case actToggleDown:
|
case actToggleDown:
|
||||||
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
||||||
t.vmove(-1, true)
|
t.vmove(-1, true)
|
||||||
@@ -2520,7 +2651,7 @@ func (t *Terminal) Loop() {
|
|||||||
req(reqClose)
|
req(reqClose)
|
||||||
}
|
}
|
||||||
case actClearScreen:
|
case actClearScreen:
|
||||||
req(reqRedraw)
|
req(reqFullRedraw)
|
||||||
case actClearQuery:
|
case actClearQuery:
|
||||||
t.input = []rune{}
|
t.input = []rune{}
|
||||||
t.cx = 0
|
t.cx = 0
|
||||||
@@ -2700,13 +2831,58 @@ func (t *Terminal) Loop() {
|
|||||||
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||||
newCommand = &command
|
newCommand = &command
|
||||||
t.reading = true
|
t.reading = true
|
||||||
t.version++
|
|
||||||
}
|
}
|
||||||
case actUnbind:
|
case actUnbind:
|
||||||
keys := parseKeyChords(a.a, "PANIC")
|
keys := parseKeyChords(a.a, "PANIC")
|
||||||
for key := range keys {
|
for key := range keys {
|
||||||
delete(t.keymap, key)
|
delete(t.keymap, key)
|
||||||
}
|
}
|
||||||
|
case actRebind:
|
||||||
|
keys := parseKeyChords(a.a, "PANIC")
|
||||||
|
for key := range keys {
|
||||||
|
if originalAction, found := t.keymapOrg[key]; found {
|
||||||
|
t.keymap[key] = originalAction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case actChangePreview:
|
||||||
|
if t.previewOpts.command != a.a {
|
||||||
|
togglePreview(len(a.a) > 0)
|
||||||
|
t.previewOpts.command = a.a
|
||||||
|
refreshPreview(t.previewOpts.command)
|
||||||
|
}
|
||||||
|
case actChangePreviewWindow:
|
||||||
|
currentPreviewOpts := t.previewOpts
|
||||||
|
|
||||||
|
// Reset preview options and apply the additional options
|
||||||
|
t.previewOpts = t.initialPreviewOpts
|
||||||
|
|
||||||
|
// Split window options
|
||||||
|
tokens := strings.Split(a.a, "|")
|
||||||
|
parsePreviewWindow(&t.previewOpts, tokens[0])
|
||||||
|
if len(tokens) > 1 {
|
||||||
|
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.previewOpts.hidden {
|
||||||
|
togglePreview(false)
|
||||||
|
} else {
|
||||||
|
// Full redraw
|
||||||
|
if !currentPreviewOpts.sameLayout(t.previewOpts) {
|
||||||
|
if togglePreview(true) {
|
||||||
|
refreshPreview(t.previewOpts.command)
|
||||||
|
} else {
|
||||||
|
req(reqRedraw)
|
||||||
|
}
|
||||||
|
} else if !currentPreviewOpts.sameContentLayout(t.previewOpts) {
|
||||||
|
t.previewed.version = 0
|
||||||
|
req(reqPreviewRefresh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust scroll offset
|
||||||
|
if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.previewOpts.scroll {
|
||||||
|
scrollPreviewTo(t.evaluateScrollOffset())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -2714,7 +2890,7 @@ func (t *Terminal) Loop() {
|
|||||||
if t.jumping == jumpDisabled {
|
if t.jumping == jumpDisabled {
|
||||||
actions := t.keymap[event.Comparable()]
|
actions := t.keymap[event.Comparable()]
|
||||||
if len(actions) == 0 && event.Type == tui.Rune {
|
if len(actions) == 0 && event.Type == tui.Rune {
|
||||||
doAction(action{t: actRune})
|
doAction(&action{t: actRune})
|
||||||
} else if !doActions(actions) {
|
} else if !doActions(actions) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build windows
|
//go:build windows
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
@@ -156,14 +156,14 @@ func Tokenize(text string, delimiter Delimiter) []Token {
|
|||||||
// FIXME performance
|
// FIXME performance
|
||||||
var tokens []string
|
var tokens []string
|
||||||
if delimiter.regex != nil {
|
if delimiter.regex != nil {
|
||||||
for len(text) > 0 {
|
locs := delimiter.regex.FindAllStringIndex(text, -1)
|
||||||
loc := delimiter.regex.FindStringIndex(text)
|
begin := 0
|
||||||
if len(loc) < 2 {
|
for _, loc := range locs {
|
||||||
loc = []int{0, len(text)}
|
tokens = append(tokens, text[begin:loc[1]])
|
||||||
|
begin = loc[1]
|
||||||
}
|
}
|
||||||
last := util.Max(loc[1], 1)
|
if begin < len(text) {
|
||||||
tokens = append(tokens, text[:last])
|
tokens = append(tokens, text[begin:])
|
||||||
text = text[last:]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return withPrefixLengths(tokens, 0)
|
return withPrefixLengths(tokens, 0)
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
// +build !ncurses
|
//go:build !tcell && !windows
|
||||||
// +build !tcell
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
@@ -16,8 +14,8 @@ func (a Attr) Merge(b Attr) Attr {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
AttrUndefined = Attr(0)
|
AttrUndefined = Attr(0)
|
||||||
AttrRegular = Attr(1 << 7)
|
AttrRegular = Attr(1 << 8)
|
||||||
AttrClear = Attr(1 << 8)
|
AttrClear = Attr(1 << 9)
|
||||||
|
|
||||||
Bold = Attr(1)
|
Bold = Attr(1)
|
||||||
Dim = Attr(1 << 1)
|
Dim = Attr(1 << 1)
|
||||||
@@ -26,9 +24,11 @@ const (
|
|||||||
Blink = Attr(1 << 4)
|
Blink = Attr(1 << 4)
|
||||||
Blink2 = Attr(1 << 5)
|
Blink2 = Attr(1 << 5)
|
||||||
Reverse = Attr(1 << 6)
|
Reverse = Attr(1 << 6)
|
||||||
|
StrikeThrough = Attr(1 << 7)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {}
|
func (r *FullscreenRenderer) Init() {}
|
||||||
|
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) {}
|
||||||
func (r *FullscreenRenderer) Clear() {}
|
func (r *FullscreenRenderer) Clear() {}
|
||||||
|
@@ -23,7 +23,7 @@ const (
|
|||||||
defaultEscDelay = 100
|
defaultEscDelay = 100
|
||||||
escPollInterval = 5
|
escPollInterval = 5
|
||||||
offsetPollTries = 10
|
offsetPollTries = 10
|
||||||
maxInputBuffer = 10 * 1024
|
maxInputBuffer = 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const consoleDevice string = "/dev/tty"
|
||||||
@@ -60,7 +60,7 @@ func (r *LightRenderer) csi(code string) {
|
|||||||
|
|
||||||
func (r *LightRenderer) flush() {
|
func (r *LightRenderer) flush() {
|
||||||
if r.queued.Len() > 0 {
|
if r.queued.Len() > 0 {
|
||||||
fmt.Fprint(os.Stderr, r.queued.String())
|
fmt.Fprint(os.Stderr, "\x1b[?25l"+r.queued.String()+"\x1b[?25h")
|
||||||
r.queued.Reset()
|
r.queued.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,6 +176,7 @@ func (r *LightRenderer) Init() {
|
|||||||
|
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
r.csi("?1000h")
|
r.csi("?1000h")
|
||||||
|
r.csi("?1006h")
|
||||||
}
|
}
|
||||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
@@ -188,6 +189,10 @@ func (r *LightRenderer) Init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
|
||||||
|
r.maxHeightFunc = maxHeightFunc
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) makeSpace() {
|
func (r *LightRenderer) makeSpace() {
|
||||||
r.stderr("\n")
|
r.stderr("\n")
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
@@ -378,7 +383,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case 'F':
|
case 'F':
|
||||||
return Event{End, 0, nil}
|
return Event{End, 0, nil}
|
||||||
case 'M':
|
case '<':
|
||||||
return r.mouseSequence(sz)
|
return r.mouseSequence(sz)
|
||||||
case 'P':
|
case 'P':
|
||||||
return Event{F1, 0, nil}
|
return Event{F1, 0, nil}
|
||||||
@@ -519,19 +524,55 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
func (r *LightRenderer) mouseSequence(sz *int) Event {
|
func (r *LightRenderer) mouseSequence(sz *int) Event {
|
||||||
if len(r.buffer) < 6 || !r.mouse {
|
// "\e[<0;0;0M"
|
||||||
|
if len(r.buffer) < 9 || !r.mouse {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
*sz = 6
|
|
||||||
switch r.buffer[3] {
|
rest := r.buffer[*sz:]
|
||||||
case 32, 34, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
end := bytes.IndexAny(rest, "mM")
|
||||||
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
if end == -1 {
|
||||||
mod := r.buffer[3] >= 36
|
return Event{Invalid, 0, nil}
|
||||||
left := r.buffer[3] == 32
|
}
|
||||||
down := r.buffer[3]%2 == 0
|
|
||||||
x := int(r.buffer[4] - 33)
|
elems := strings.SplitN(string(rest[:end]), ";", 3)
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
if len(elems) != 3 {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := atoi(elems[0], -1)
|
||||||
|
x := atoi(elems[1], -1) - 1
|
||||||
|
y := atoi(elems[2], -1) - 1 - r.yoffset
|
||||||
|
if t < 0 || x < 0 || y < 0 {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
*sz += end + 1
|
||||||
|
|
||||||
|
down := rest[end] == 'M'
|
||||||
|
|
||||||
|
scroll := 0
|
||||||
|
if t >= 64 {
|
||||||
|
t -= 64
|
||||||
|
if t&0b1 == 1 {
|
||||||
|
scroll = -1
|
||||||
|
} else {
|
||||||
|
scroll = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// middle := t & 0b1
|
||||||
|
left := t&0b11 == 0
|
||||||
|
|
||||||
|
// shift := t & 0b100
|
||||||
|
// ctrl := t & 0b1000
|
||||||
|
mod := t&0b1100 > 0
|
||||||
|
|
||||||
|
if scroll != 0 {
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, scroll, false, false, false, mod}}
|
||||||
|
}
|
||||||
|
|
||||||
double := false
|
double := false
|
||||||
if down {
|
if down {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@@ -549,17 +590,7 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
double = true
|
double = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
||||||
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
|
||||||
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
|
||||||
mod := r.buffer[3] >= 100
|
|
||||||
s := 1 - int(r.buffer[3]%2)*2
|
|
||||||
x := int(r.buffer[4] - 33)
|
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, false, mod}}
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) smcup() {
|
func (r *LightRenderer) smcup() {
|
||||||
@@ -597,6 +628,7 @@ func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
|||||||
// It's highly likely that the offset we obtained at the beginning is
|
// It's highly likely that the offset we obtained at the beginning is
|
||||||
// no longer correct, so we simply disable mouse input.
|
// no longer correct, so we simply disable mouse input.
|
||||||
r.csi("?1000l")
|
r.csi("?1000l")
|
||||||
|
r.csi("?1006l")
|
||||||
r.mouse = false
|
r.mouse = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -636,6 +668,7 @@ func (r *LightRenderer) Close() {
|
|||||||
}
|
}
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
r.csi("?1000l")
|
r.csi("?1000l")
|
||||||
|
r.csi("?1006l")
|
||||||
}
|
}
|
||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
r.closePlatform()
|
||||||
@@ -647,6 +680,9 @@ func (r *LightRenderer) MaxX() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) MaxY() int {
|
func (r *LightRenderer) MaxY() int {
|
||||||
|
if r.height == 0 {
|
||||||
|
r.updateTerminalSize()
|
||||||
|
}
|
||||||
return r.height
|
return r.height
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,6 +863,9 @@ func attrCodes(attr Attr) []string {
|
|||||||
if (attr & Reverse) > 0 {
|
if (attr & Reverse) > 0 {
|
||||||
codes = append(codes, "7")
|
codes = append(codes, "7")
|
||||||
}
|
}
|
||||||
|
if (attr & StrikeThrough) > 0 {
|
||||||
|
codes = append(codes, "9")
|
||||||
|
}
|
||||||
return codes
|
return codes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
//+build windows
|
//go:build windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build tcell windows
|
//go:build tcell || windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/gdamore/tcell/encoding"
|
"github.com/gdamore/tcell/v2/encoding"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
@@ -19,12 +19,24 @@ func HasFullscreenRenderer() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ColorPair) style() tcell.Style {
|
func asTcellColor(color Color) tcell.Color {
|
||||||
style := tcell.StyleDefault
|
if color == colDefault {
|
||||||
return style.Foreground(tcell.Color(p.Fg())).Background(tcell.Color(p.Bg()))
|
return tcell.ColorDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attr tcell.Style
|
value := uint64(tcell.ColorValid) + uint64(color)
|
||||||
|
if color.is24() {
|
||||||
|
value = value | uint64(tcell.ColorIsRGB)
|
||||||
|
}
|
||||||
|
return tcell.Color(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) style() tcell.Style {
|
||||||
|
style := tcell.StyleDefault
|
||||||
|
return style.Foreground(asTcellColor(p.Fg())).Background(asTcellColor(p.Bg()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attr int32
|
||||||
|
|
||||||
type TcellWindow struct {
|
type TcellWindow struct {
|
||||||
color bool
|
color bool
|
||||||
@@ -77,6 +89,7 @@ const (
|
|||||||
Blink = Attr(tcell.AttrBlink)
|
Blink = Attr(tcell.AttrBlink)
|
||||||
Reverse = Attr(tcell.AttrReverse)
|
Reverse = Attr(tcell.AttrReverse)
|
||||||
Underline = Attr(tcell.AttrUnderline)
|
Underline = Attr(tcell.AttrUnderline)
|
||||||
|
StrikeThrough = Attr(tcell.AttrStrikeThrough)
|
||||||
Italic = Attr(tcell.AttrItalic)
|
Italic = Attr(tcell.AttrItalic)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,6 +99,8 @@ const (
|
|||||||
AttrClear = Attr(1 << 8)
|
AttrClear = Attr(1 << 8)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
||||||
if _screen.Colors() >= 256 {
|
if _screen.Colors() >= 256 {
|
||||||
return Dark256
|
return Dark256
|
||||||
@@ -561,6 +576,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
|
|||||||
style = style.
|
style = style.
|
||||||
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).
|
||||||
Italic(a&Attr(tcell.AttrItalic) != 0).
|
Italic(a&Attr(tcell.AttrItalic) != 0).
|
||||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
Dim(a&Attr(tcell.AttrDim) != 0)
|
Dim(a&Attr(tcell.AttrDim) != 0)
|
||||||
@@ -612,6 +628,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
|
|||||||
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).
|
||||||
Italic(a&Attr(tcell.AttrItalic) != 0)
|
Italic(a&Attr(tcell.AttrItalic) != 0)
|
||||||
|
|
||||||
gr := uniseg.NewGraphemes(text)
|
gr := uniseg.NewGraphemes(text)
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
// +build tcell windows
|
//go:build tcell || windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build windows
|
//go:build windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
@@ -358,6 +358,7 @@ func MakeTransparentBorder() BorderStyle {
|
|||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Init()
|
Init()
|
||||||
|
Resize(maxHeightFunc func(int) int)
|
||||||
Pause(clear bool)
|
Pause(clear bool)
|
||||||
Resume(clear bool, sigcont bool)
|
Resume(clear bool, sigcont bool)
|
||||||
Clear()
|
Clear()
|
||||||
|
@@ -26,7 +26,7 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
|
|||||||
w = runewidth.StringWidth(s) + strings.Count(s, "\n")
|
w = runewidth.StringWidth(s) + strings.Count(s, "\n")
|
||||||
}
|
}
|
||||||
width += w
|
width += w
|
||||||
if limit > 0 && width > limit {
|
if width > limit {
|
||||||
return width, idx
|
return width, idx
|
||||||
}
|
}
|
||||||
idx += len(rs)
|
idx += len(rs)
|
||||||
@@ -34,6 +34,23 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
|
|||||||
return width, -1
|
return width, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Truncate returns the truncated runes and its width
|
||||||
|
func Truncate(input string, limit int) ([]rune, int) {
|
||||||
|
runes := []rune{}
|
||||||
|
width := 0
|
||||||
|
gr := uniseg.NewGraphemes(input)
|
||||||
|
for gr.Next() {
|
||||||
|
rs := gr.Runes()
|
||||||
|
w := runewidth.StringWidth(string(rs))
|
||||||
|
if width+w > limit {
|
||||||
|
return runes, width
|
||||||
|
}
|
||||||
|
width += w
|
||||||
|
runes = append(runes, rs...)
|
||||||
|
}
|
||||||
|
return runes, width
|
||||||
|
}
|
||||||
|
|
||||||
// Max returns the largest integer
|
// Max returns the largest integer
|
||||||
func Max(first int, second int) int {
|
func Max(first int, second int) int {
|
||||||
if first >= second {
|
if first >= second {
|
||||||
|
@@ -38,3 +38,29 @@ func TestOnce(t *testing.T) {
|
|||||||
t.Error("Expected: false")
|
t.Error("Expected: false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunesWidth(t *testing.T) {
|
||||||
|
for _, args := range [][]int{
|
||||||
|
{100, 5, -1},
|
||||||
|
{3, 4, 3},
|
||||||
|
{0, 1, 0},
|
||||||
|
} {
|
||||||
|
width, overflowIdx := RunesWidth([]rune("hello"), 0, 0, args[0])
|
||||||
|
if width != args[1] {
|
||||||
|
t.Errorf("Expected width: %d, actual: %d", args[1], width)
|
||||||
|
}
|
||||||
|
if overflowIdx != args[2] {
|
||||||
|
t.Errorf("Expected overflow index: %d, actual: %d", args[2], overflowIdx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTruncate(t *testing.T) {
|
||||||
|
truncated, width := Truncate("가나다라마", 7)
|
||||||
|
if string(truncated) != "가나다" {
|
||||||
|
t.Errorf("Expected: 가나다, actual: %s", string(truncated))
|
||||||
|
}
|
||||||
|
if width != 6 {
|
||||||
|
t.Errorf("Expected: 6, actual: %d", width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build windows
|
//go:build windows
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
|
196
test/test_go.rb
196
test/test_go.rb
@@ -754,6 +754,26 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.lines(chomp: true)
|
assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.lines(chomp: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_tiebreak_chunk
|
||||||
|
writelines(tempname, [
|
||||||
|
'1 foobarbaz ba',
|
||||||
|
'2 foobar baz',
|
||||||
|
'3 foo barbaz'
|
||||||
|
])
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
'3 foo barbaz',
|
||||||
|
'2 foobar baz',
|
||||||
|
'1 foobarbaz ba'
|
||||||
|
], `#{FZF} -fo --tiebreak=chunk < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
'1 foobarbaz ba',
|
||||||
|
'2 foobar baz',
|
||||||
|
'3 foo barbaz'
|
||||||
|
], `#{FZF} -fba --tiebreak=chunk < #{tempname}`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
def test_invalid_cache
|
def test_invalid_cache
|
||||||
tmux.send_keys "(echo d; echo D; echo x) | #{fzf('-q d')}", :Enter
|
tmux.send_keys "(echo d; echo D; echo x) | #{fzf('-q d')}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 2/3', lines[-2] }
|
tmux.until { |lines| assert_equal ' 2/3', lines[-2] }
|
||||||
@@ -2043,8 +2063,8 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unbind
|
def test_unbind_rebind
|
||||||
tmux.send_keys "seq 100 | #{FZF} --bind 'c:clear-query,d:unbind(c,d)'", :Enter
|
tmux.send_keys "seq 100 | #{FZF} --bind 'c:clear-query,d:unbind(c,d),e:rebind(c,d)'", :Enter
|
||||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
tmux.send_keys 'ab'
|
tmux.send_keys 'ab'
|
||||||
tmux.until { |lines| assert_equal '> ab', lines[-1] }
|
tmux.until { |lines| assert_equal '> ab', lines[-1] }
|
||||||
@@ -2052,6 +2072,8 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal '>', lines[-1] }
|
tmux.until { |lines| assert_equal '>', lines[-1] }
|
||||||
tmux.send_keys 'dabcd'
|
tmux.send_keys 'dabcd'
|
||||||
tmux.until { |lines| assert_equal '> abcd', lines[-1] }
|
tmux.until { |lines| assert_equal '> abcd', lines[-1] }
|
||||||
|
tmux.send_keys 'ecabddc'
|
||||||
|
tmux.until { |lines| assert_equal '> abdc', lines[-1] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_item_index_reset_on_reload
|
def test_item_index_reset_on_reload
|
||||||
@@ -2077,6 +2099,15 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_includes lines[1], '4' }
|
tmux.until { |lines| assert_includes lines[1], '4' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_reload_and_change_preview_should_update_preview
|
||||||
|
tmux.send_keys "seq 3 | #{FZF} --bind 'ctrl-t:reload(echo 4)+change-preview(echo {})'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '1' }
|
||||||
|
tmux.send_keys 'C-t'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '4' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_scroll_off
|
def test_scroll_off
|
||||||
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
|
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
|
||||||
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
||||||
@@ -2142,6 +2173,165 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal expected.chomp, lines.take(6).join("\n")
|
assert_equal expected.chomp, lines.take(6).join("\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_change_preview_window
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --preview-window border-none --bind '" \
|
||||||
|
'a:change-preview(echo __{}__),' \
|
||||||
|
'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \
|
||||||
|
'c:change-preview(),d:change-preview-window(hidden),' \
|
||||||
|
"e:preview(printf ::%${FZF_PREVIEW_COLUMNS}s{})+change-preview-window(up),f:change-preview-window(up,wrap)'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[0], '[[1]]' }
|
||||||
|
|
||||||
|
# change-preview action permanently changes the preview command set by --preview
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], '__1__' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[0], '__2__' }
|
||||||
|
|
||||||
|
# When multiple change-preview-window actions are bound to a single key,
|
||||||
|
# the last one wins and the updated options are immediately applied to the new preview
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert_equal '==2==', lines[0] }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal '==3==', lines[0] }
|
||||||
|
|
||||||
|
# change-preview with an empty preview command closes the preview window
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.until { |lines| refute_includes lines[0], '==' }
|
||||||
|
|
||||||
|
# change-preview again to re-open the preview window
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_equal '__3__', lines[0] }
|
||||||
|
|
||||||
|
# Hide the preview window with hidden flag
|
||||||
|
tmux.send_keys 'd'
|
||||||
|
tmux.until { |lines| refute_includes lines[0], '__3__' }
|
||||||
|
|
||||||
|
# One-off preview
|
||||||
|
tmux.send_keys 'e'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal '::', lines[0]
|
||||||
|
refute_includes lines[1], '3'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wrapped
|
||||||
|
tmux.send_keys 'f'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal '::', lines[0]
|
||||||
|
assert_equal ' 3', lines[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_preview_window_rotate
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \
|
||||||
|
"a:change-preview-window(right|down|up|hidden|)'", :Enter
|
||||||
|
3.times do
|
||||||
|
tmux.until { |lines| lines[0].start_with?('hello') }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| lines[0].end_with?('hello') }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| lines[-1].start_with?('hello') }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_equal 'hello', lines[0] }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| refute_includes lines[0], 'hello' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ellipsis
|
||||||
|
tmux.send_keys 'seq 1000 | tr "\n" , | fzf --ellipsis=SNIPSNIP -e -q500', :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_block(expected, lines)
|
||||||
|
cols = expected.lines.map(&:chomp).map(&:length).max
|
||||||
|
actual = lines.reverse.take(expected.lines.length).reverse.map { _1[0, cols].rstrip + "\n" }.join
|
||||||
|
assert_equal expected, actual
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit
|
||||||
|
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
╭──────────
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
│ > < 3/3
|
||||||
|
╰──────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit_preview_above
|
||||||
|
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border --preview "seq {}" --preview-window up,60%', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
╭──────────
|
||||||
|
│ ╭────────
|
||||||
|
│ │ 1
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ ╰────────
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
│ > < 3/3
|
||||||
|
╰──────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit_preview_above_alternative
|
||||||
|
tmux.send_keys 'seq 3 | fzf --height ~100% --border=sharp --preview "seq {}" --preview-window up,40%,border-bottom --padding 1 --exit-0 --header hello --header-lines=2', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
┌─────────
|
||||||
|
│
|
||||||
|
│ 1
|
||||||
|
│ 2
|
||||||
|
│ 3
|
||||||
|
│ ───────
|
||||||
|
│ > 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ hello
|
||||||
|
│ 1/1
|
||||||
|
│ >
|
||||||
|
│
|
||||||
|
└─────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit_preview_left
|
||||||
|
tmux.send_keys "seq 3 | fzf --height ~100% --border=vertical --preview 'seq {}' --preview-window left,5,border-right --padding 1 --exit-0 --header $'hello\\nworld' --header-lines=2", :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
│
|
||||||
|
│ 1 │> 3
|
||||||
|
│ 2 │ 2
|
||||||
|
│ 3 │ 1
|
||||||
|
│ │ hello
|
||||||
|
│ │ world
|
||||||
|
│ │ 1/1
|
||||||
|
│ │>
|
||||||
|
│
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_overflow
|
||||||
|
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
╭──────────────
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
│ > < 100/100
|
||||||
|
╰──────────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -2395,7 +2585,7 @@ module CompletionTest
|
|||||||
pid = lines[-1]&.split&.last
|
pid = lines[-1]&.split&.last
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
tmux.send_keys 'kill ', :Tab
|
tmux.send_keys 'kill **', :Tab
|
||||||
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
tmux.send_keys 'sleep12345'
|
tmux.send_keys 'sleep12345'
|
||||||
tmux.until { |lines| assert lines.any_include?('sleep 12345') }
|
tmux.until { |lines| assert lines.any_include?('sleep 12345') }
|
||||||
|
10
uninstall
10
uninstall
@@ -51,13 +51,8 @@ remove() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove_line() {
|
remove_line() {
|
||||||
src=$(readlink "$1")
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "Remove from $1 ($src):"
|
|
||||||
else
|
|
||||||
src=$1
|
src=$1
|
||||||
echo "Remove from $1:"
|
echo "Remove from $1:"
|
||||||
fi
|
|
||||||
|
|
||||||
shift
|
shift
|
||||||
line_no=1
|
line_no=1
|
||||||
@@ -75,8 +70,9 @@ remove_line() {
|
|||||||
echo " - Line #$line_no: $content"
|
echo " - Line #$line_no: $content"
|
||||||
[ "$content" = "$1" ] || ask " - Remove?"
|
[ "$content" = "$1" ] || ask " - Remove?"
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
|
temp=$(mktemp)
|
||||||
mv "$src.bak" "$src" || break
|
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$temp" &&
|
||||||
|
cat "$temp" > "$src" && rm -f "$temp" || break
|
||||||
echo " - Removed"
|
echo " - Removed"
|
||||||
else
|
else
|
||||||
echo " - Skipped"
|
echo " - Skipped"
|
||||||
|
Reference in New Issue
Block a user