m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-15 06:43:47 -05:00

Compare commits

..

48 Commits

Author SHA1 Message Date
Junegunn Choi
8c533e34ea 0.25.0 2021-01-03 00:56:11 +09:00
Junegunn Choi
37708ad9cd Revert "[zsh] Use shell redirection (#2281)"
This reverts commit e9bc7331bd.

The change is no longer necessary since 090dee8.
2021-01-03 00:48:40 +09:00
Junegunn Choi
090dee857f Do not disable mouse on SIGCONT before SIGSTOP
Fix #2161
2021-01-03 00:43:56 +09:00
Junegunn Choi
d779ff7e6d Make search toggleable
- `--phony` renamed to `--disabled` for consistency
    - `--no-phony` is now `--enabled`
- Added `enable-search`, `disable-search`, and `toggle-search` actions
  for `--bind`
- Added `--color` options: `query` and `disabled`

Close #2303
2021-01-03 00:15:00 +09:00
Junegunn Choi
fd8858f8c9 [fzf-tmux] Disable CTRL-Z 2021-01-01 22:43:36 +09:00
Junegunn Choi
b234647a63 [shell] Disable CTRL-Z
Fix #2289
2021-01-01 22:36:45 +09:00
Junegunn Choi
6e93eefc82 Update vimdoc 2020-12-31 19:12:57 +09:00
yhu266
38fca30125 Add tag links for "doc/fzf.txt" 2020-12-31 19:12:57 +09:00
Junegunn Choi
012ee9ca85 [Makefile] Make sure to use bash 2020-12-31 12:57:57 +09:00
Junegunn Choi
151252e33a Add preview-top and preview-bottom actions 2020-12-31 12:57:57 +09:00
Junegunn Choi
7136cfc68b Fix alt-, for --expect 2020-12-31 03:38:46 +09:00
Junegunn Choi
408c04f25f Update test case for 'first' and 'last' 2020-12-30 18:43:16 +09:00
Junegunn Choi
7f8e0dbc40 Extend support for alt key chords
"alt-" with any case-sensitive character is allowed
2020-12-30 18:39:17 +09:00
Junegunn Choi
0de7ab18f6 Add "last" action to move the cursor to the last match
This is the opposite of "first" (previously known as "top").
2020-12-30 18:39:17 +09:00
林千里
e9bc7331bd [zsh] Use shell redirection (#2281)
zsh sends SIGCONT when running fzf in a pipe in certain cases,
causing mouse mode to become disabled

Fix #2101
2020-12-23 14:38:37 +09:00
Loic Nageleisen
797dd7c449 [Makefile] Support building on machines with uname -m == "arm64" (#2291) 2020-12-23 14:27:46 +09:00
Junegunn Choi
f37ccaa64f Prevent index out of range error
Fix #2293
2020-12-23 10:34:31 +09:00
Junegunn Choi
ab3937ee5a [vim] Allow closing Vim running fzf without confirmation
Close #2287
2020-12-16 21:44:28 +09:00
Junegunn Choi
00f4551a7b Revert "[zsh] Reload shared history before searching (#2251)"
This reverts commit b62a74b315.

https://github.com/junegunn/fzf/pull/2251#issuecomment-740551383
2020-12-09 00:02:35 +09:00
Junegunn Choi
257ddd028d Update CHANGELOG 2020-12-07 19:35:48 +09:00
Junegunn Choi
e0a22e76f8 Make --color attributes mergeable
So you can override the colors and still have the text attributes

    # Default colors and attributes
    fzf

    export FZF_DEFAULT_OPTS='--color hl👎underline,hl+👎underline:reverse'

    # Default colors with underline+reverse attributes
    fzf

    # Different colors with underline+reverse attributes
    fzf --color hl:176,hl+:177

Related: https://github.com/junegunn/fzf.vim/issues/1197#issuecomment-739804363
2020-12-07 19:11:00 +09:00
Junegunn Choi
00a3610331 0.24.4 2020-12-05 23:24:55 +09:00
Junegunn Choi
f502725120 Fix slice bound error on extremely narrow screen 2020-12-05 22:00:42 +09:00
Martin Polden
b62a74b315 [zsh] Reload shared history before searching (#2251) 2020-12-05 21:48:54 +09:00
Junegunn Choi
2ec382ae0e Add --preview-window follow option 2020-12-05 21:16:35 +09:00
Junegunn Choi
cbfee31593 Fix typo in test case 2020-12-04 20:39:52 +09:00
Junegunn Choi
6d647e13ff Add change-prompt action
Close #2270
2020-12-04 20:34:41 +09:00
Junegunn Choi
d2af3ff98d Change how hl:-1 or hl+:-1 is applied to text with background color 2020-12-04 19:27:43 +09:00
Junegunn Choi
052d17e66a Fix Travis OSX build 2020-12-03 10:33:45 +09:00
Junegunn Choi
a9bc954e17 Do not update Homebrew on Travis OSX build
https://docs.travis-ci.com/user/installing-dependencies/#installing-packages-on-macos

> By default, the Homebrew addon will not run brew update before
> installing packages. brew update can take a long time and slow down your
> builds.
2020-12-03 10:24:06 +09:00
Junegunn Choi
2983426771 Fix unit tests 2020-11-25 13:08:28 +09:00
ratijas
c61eb94b3f [zsh] Declare variable as local before assignment (#2266) 2020-11-25 11:21:30 +09:00
Junegunn Choi
3829eab1cf Support ANSI code for clearing the rest of the line (ESC[0K)
Some programs use it to set the background color for the whole line.

  fzf --preview "printf 'normal \x1b[42mgreen\x1b[0K \x1b[43myellow\x1b[m\nnormal again'"

  fzf --preview 'delta <(echo foo) <(echo bar) < /dev/tty'

Fix #2249
2020-11-25 01:49:48 +09:00
Junegunn Choi
3fe8eeedc5 Fix handling of arrow keys with alt and/or shift modifier
Fix #2254

- Properly handle extra chars in the buffer. Patch suggested by @mckelly2833.
- Support alt-arrow sequences in \e[1;3A format
- Support shift-alt-arrow sequences in \e[1;10A format
2020-11-24 19:51:19 +09:00
Junegunn Choi
1efef88b6e Improve trim function to handle longer strings
Fix #2258
2020-11-24 19:03:59 +09:00
Junegunn Choi
7acdaf0b43 [vim] &termwinkey may not be available
/cc @Caid11
2020-11-19 09:55:57 +09:00
Michal Domonkos
1ed25d76ba [vim] Clean up temp files on interrupt (#2252)
The clean-up is done in s:collect(), so let's make sure it's run before
we may terminate due to CTRL-C or ESC (or some other error code) in
s:exit_handler().
2020-11-17 14:37:14 +09:00
Junegunn Choi
474c1f5e32 [vim] Map CTRL-Z to <nop> 2020-11-15 21:28:07 +09:00
Junegunn Choi
8b71fea5dc [vim] Set termwinkey to allow CTRL-W
Fix https://github.com/junegunn/fzf.vim/issues/1176
2020-11-15 21:27:57 +09:00
Tomas Janousek
7bd99a22ee [bash-completion] Fix endless loop when completion.bash sourced twice
I forgot to add the "not _fzf" check into __fzf_orig_completion, so
invoking it twice would rewrite the _fzf_orig_completion_xxx variables
and then cause an endless loop when completion is requested.

Fixes: ef2c29d5d4 ("[bash-completion] Optimize __fzf_orig_completion_filter")
2020-11-14 10:29:05 +09:00
Tomas Janousek
75b8cca3b3 [bash-completion] Unexport _fzf_orig_completion_* variables 2020-11-13 02:16:54 +09:00
Tomas Janousek
ef2c29d5d4 [bash-completion] Optimize __fzf_orig_completion_filter
Commit d4ad4a25 slowed loading of completion.bash significantly (on my
laptop from 10 ms to 30 ms), then 54891d11 improved that (to 20 ms) but
it still stands out as the heavy part of my .bashrc.

Rewriting __fzf_orig_completion_filter to pure bash without forking to
sed/awk brings this back under 10 ms.

before:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):      21.2 ms ±   0.3 ms    [User: 24.9 ms, System: 6.4 ms]
      Range (min … max):    20.7 ms …  23.3 ms    132 runs

after:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):       9.6 ms ±   0.3 ms    [User: 8.0 ms, System: 2.2 ms]
      Range (min … max):     9.3 ms …  11.4 ms    298 runs

Fixes: d4ad4a25db ("[bash-completion] Fix default alias/variable completion")
Fixes: 54891d11e0 ("[bash-completion] Minor optimization")
2020-11-13 02:16:54 +09:00
Tomas Janousek
218b3c8274 [bash-completion] Move -F/_fzf filter to __fzf_orig_completion_filter
This prevents mistakes like the one fixed by the previous commit, and
also speeds bash startup a tiny bit:

before:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):      22.4 ms ±   0.6 ms    [User: 28.7 ms, System: 7.8 ms]
      Range (min … max):    21.7 ms …  25.2 ms    123 runs

after:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):      21.2 ms ±   0.3 ms    [User: 24.9 ms, System: 6.4 ms]
      Range (min … max):    20.7 ms …  23.3 ms    132 runs
2020-11-13 02:16:54 +09:00
Tomas Janousek
db9cb2ddda [bash-completion] Avoid empty _a, _v completions
This doesn't look right:

    $ complete | grep ' _.$'
    complete _a
    complete _v

The __fzf_orig_completion_filter invocation in _fzf_setup_completion
needs the /-F/ filter, just like all the other invocations.

Fixes: d4ad4a25db ("[bash-completion] Fix default alias/variable completion")
2020-11-13 02:16:54 +09:00
Junegunn Choi
722d66e85a 0.24.3 2020-11-09 20:41:59 +09:00
Junegunn Choi
f6269f0193 Add --padding option
Close #2241
2020-11-09 20:37:17 +09:00
Junegunn Choi
520eae817a Remove print statement for debugging 2020-11-09 19:17:33 +09:00
Junegunn Choi
d099941360 [vim] Fix double path separator issue on Windows
Fix https://github.com/junegunn/fzf.vim/issues/1141
2020-11-05 18:14:45 +09:00
32 changed files with 1025 additions and 584 deletions

View File

@@ -6,6 +6,7 @@ os:
- linux - linux
- osx - osx
dist: bionic dist: bionic
osx_image: xcode12.2
addons: addons:
apt: apt:
packages: packages:
@@ -17,7 +18,6 @@ addons:
packages: packages:
- fish - fish
- tmux - tmux
update: true
install: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1 install: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
script: script:
- make test - make test

View File

@@ -1,6 +1,69 @@
CHANGELOG CHANGELOG
========= =========
0.25.0
------
- Text attributes set in `--color` are not reset when fzf sees another
`--color` option for the same element. This allows you to put custom text
attributes in your `$FZF_DEFAULT_OPTS` and still have those attributes
even when you override the colors.
```sh
# Default colors and attributes
fzf
# Apply custom text attributes
export FZF_DEFAULT_OPTS='--color fg+:italic,hl:-1:underline,hl+:-1:reverse:underline'
fzf
# Different colors but you still have the attributes
fzf --color hl:176,hl+:177
# Write "regular" if you want to clear the attributes
fzf --color hl:176:regular,hl+:177:regular
```
- Renamed `--phony` to `--disabled`
- You can dynamically enable and disable the search functionality using the
new `enable-search`, `disable-search`, and `toggle-search` actions
- You can assign a different color to the query string for when search is disabled
```sh
fzf --color query:#ffffff,disabled:#999999 --bind space:toggle-search
```
- Added `last` action to move the cursor to the last match
- The opposite action `top` is renamed to `first`, but `top` is still
recognized as a synonym for backward compatibility
- Added `preview-top` and `preview-bottom` actions
- Extended support for alt key chords: alt with any case-sensitive single character
```sh
fzf --bind alt-,:first,alt-.:last
```
0.24.4
------
- Added `--preview-window` option `follow`
```sh
# Preview window will automatically scroll to the bottom
fzf --preview-window follow --preview 'for i in $(seq 100000); do
echo "$i"
sleep 0.01
(( i % 300 == 0 )) && printf "\033[2J"
done'
```
- Added `change-prompt` action
```sh
fzf --prompt 'foo> ' --bind $'a:change-prompt:\x1b[31mbar> '
```
- Bug fixes and improvements
0.24.3
------
- Added `--padding` option
```sh
fzf --margin 5% --padding 5% --border --preview 'cat {}' \
--color bg:#222222,preview-bg:#333333
```
0.24.2 0.24.2
------ ------
- Bug fixes and improvements - Bug fixes and improvements

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi Copyright (c) 2013-2021 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

View File

@@ -1,3 +1,4 @@
SHELL := bash
GO ?= go GO ?= go
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version)))) GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
@@ -47,6 +48,8 @@ else ifeq ($(UNAME_M),armv7l)
BINARY := $(BINARYARM7) BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),armv8l) else ifeq ($(UNAME_M),armv8l)
BINARY := $(BINARYARM8) BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),arm64)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),aarch64) else ifeq ($(UNAME_M),aarch64)
BINARY := $(BINARYARM8) BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),ppc64le) else ifeq ($(UNAME_M),ppc64le)

View File

@@ -171,19 +171,23 @@ list:
- `element` is an fzf element to apply a color to: - `element` is an fzf element to apply a color to:
| Element | Description | | Element | Description |
| --- | --- | | --- | --- |
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) | | `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) | | `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
| `hl` / `hl+` | Highlighted substrings (normal / current) | | `preview-fg` / `preview-bg` | Preview window text and background |
| `gutter` | Background of the gutter on the left | | `hl` / `hl+` | Highlighted substrings (normal / current) |
| `pointer` | Pointer to the current line (`>`) | | `gutter` | Background of the gutter on the left |
| `marker` | Multi-select marker (`>`) | | `pointer` | Pointer to the current line (`>`) |
| `border` | Border around the window (`--border` and `--preview`) | | `marker` | Multi-select marker (`>`) |
| `header` | Header (`--header` or `--header-lines`) | | `border` | Border around the window (`--border` and `--preview`) |
| `info` | Info line (match counters) | | `header` | Header (`--header` or `--header-lines`) |
| `spinner` | Streaming input indicator | | `info` | Info line (match counters) |
| `prompt` | Prompt before query (`> `) | | `spinner` | Streaming input indicator |
| `query` | Query string |
| `disabled` | Query string when search is disabled |
| `prompt` | Prompt before query (`> `) |
| `pointer` | Pointer to the current line (`>`) |
- `component` specifies the component (`fg` / `bg`) from which to extract the - `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups color when considering each of the following highlight groups
@@ -406,7 +410,6 @@ The latest versions of Vim and Neovim include builtin terminal emulator
" Optional: " Optional:
" - xoffset [float default 0.5 range [0 ~ 1]] " - xoffset [float default 0.5 range [0 ~ 1]]
" - yoffset [float default 0.5 range [0 ~ 1]] " - yoffset [float default 0.5 range [0 ~ 1]]
" - highlight [string default 'Comment']: Highlight group for border
" - border [string default 'rounded']: Border style " - border [string default 'rounded']: Border style
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right' " - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
@@ -446,4 +449,4 @@ endif
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi Copyright (c) 2013-2021 Junegunn Choi

View File

@@ -698,4 +698,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi Copyright (c) 2013-2021 Junegunn Choi

View File

@@ -135,8 +135,8 @@ if [[ -z "$TMUX" ]]; then
exit $? exit $?
fi fi
# --height option is not allowed # --height option is not allowed. CTRL-Z is also disabled.
args=("${args[@]}" "--no-height") 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" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then

View File

@@ -1,22 +1,22 @@
fzf.txt fzf Last change: October 18 2020 fzf.txt fzf Last change: January 3 2021
FZF - TABLE OF CONTENTS *fzf* *fzf-toc* FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
============================================================================== ==============================================================================
FZF Vim integration FZF Vim integration |fzf-vim-integration|
Installation Installation |fzf-installation|
Summary Summary |fzf-summary|
:FZF[!] :FZF[!] |:FZF|
Configuration Configuration |fzf-configuration|
Examples Examples |fzf-examples|
Explanation of g:fzf_colors Explanation of g:fzf_colors |fzf-explanation-of-gfzfcolors|
fzf#run fzf#run |fzf#run|
fzf#wrap fzf#wrap |fzf#wrap|
Global options supported by fzf#wrap Global options supported by fzf#wrap |fzf-global-options-supported-by-fzf#wrap|
Tips Tips |fzf-tips|
fzf inside terminal buffer fzf inside terminal buffer |fzf-inside-terminal-buffer|
Starting fzf in a popup window Starting fzf in a popup window |fzf-starting-fzf-in-a-popup-window|
Hide statusline Hide statusline |fzf-hide-statusline|
License License |fzf-license|
FZF VIM INTEGRATION *fzf-vim-integration* FZF VIM INTEGRATION *fzf-vim-integration*
============================================================================== ==============================================================================
@@ -200,21 +200,25 @@ list:
< <
- `element` is an fzf element to apply a color to: - `element` is an fzf element to apply a color to:
----------------------+------------------------------------------------------ ----------------------------+------------------------------------------------------
Element | Description ~ Element | Description ~
----------------------+------------------------------------------------------ ----------------------------+------------------------------------------------------
`fg` / `bg` / `hl` | Item (foreground / background / highlight) `fg` / `bg` / `hl` | Item (foreground / background / highlight)
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
`hl` / `hl+` | Highlighted substrings (normal / current) `preview-fg` / `preview-bg` | Preview window text and background
`gutter` | Background of the gutter on the left `hl` / `hl+` | Highlighted substrings (normal / current)
`pointer` | Pointer to the current line ( `>` ) `gutter` | Background of the gutter on the left
`marker` | Multi-select marker ( `>` ) `pointer` | Pointer to the current line ( `>` )
`border` | Border around the window ( `--border` and `--preview` ) `marker` | Multi-select marker ( `>` )
`header` | Header ( `--header` or `--header-lines` ) `border` | Border around the window ( `--border` and `--preview` )
`info` | Info line (match counters) `header` | Header ( `--header` or `--header-lines` )
`spinner` | Streaming input indicator `info` | Info line (match counters)
`prompt` | Prompt before query ( `>` ) `spinner` | Streaming input indicator
----------------------+------------------------------------------------------ `query` | Query string
`disabled` | Query string when search is disabled
`prompt` | Prompt before query ( `>` )
`pointer` | Pointer to the current line ( `>` )
----------------------------+------------------------------------------------------
- `component` specifies the component (`fg` / `bg`) from which to extract the - `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups color when considering each of the following highlight groups
- `group1[,group2,...]` is a list of highlight groups that are searched (in - `group1[,group2,...]` is a list of highlight groups that are searched (in
@@ -314,9 +318,8 @@ following options are allowed:
- Optional: - Optional:
- `yoffset` [float default 0.5 range [0 ~ 1]] - `yoffset` [float default 0.5 range [0 ~ 1]]
- `xoffset` [float default 0.5 range [0 ~ 1]] - `xoffset` [float default 0.5 range [0 ~ 1]]
- `highlight` [string default `'Comment'`]: Highlight group for border
- `border` [string default `rounded`]: Border style - `border` [string default `rounded`]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` - `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
FZF#WRAP FZF#WRAP
@@ -420,7 +423,6 @@ Starting fzf in a popup window~
" Optional: " Optional:
" - xoffset [float default 0.5 range [0 ~ 1]] " - xoffset [float default 0.5 range [0 ~ 1]]
" - yoffset [float default 0.5 range [0 ~ 1]] " - yoffset [float default 0.5 range [0 ~ 1]]
" - highlight [string default 'Comment']: Highlight group for border
" - border [string default 'rounded']: Border style " - border [string default 'rounded']: Border style
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right' " - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
@@ -458,7 +460,7 @@ LICENSE *fzf-license*
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi Copyright (c) 2013-2021 Junegunn Choi
============================================================================== ==============================================================================
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap: vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.24.2 version=0.25.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2

View File

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

View File

@@ -1,7 +1,7 @@
.ig .ig
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi Copyright (c) 2013-2021 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
@@ -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 2020" "fzf 0.24.2" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Jan 2021" "fzf 0.25.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

View File

@@ -1,7 +1,7 @@
.ig .ig
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi Copyright (c) 2013-2021 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
@@ -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 2020" "fzf 0.24.2" "fzf - a command-line fuzzy finder" .TH fzf 1 "Jan 2021" "fzf 0.25.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -71,9 +71,10 @@ Transform the presentation of each line using field index expressions
.BI "-d, --delimiter=" "STR" .BI "-d, --delimiter=" "STR"
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style) Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
.TP .TP
.BI "--phony" .BI "--disabled"
Do not perform search. With this option, fzf becomes a simple selector Do not perform search. With this option, fzf becomes a simple selector
interface rather than a "fuzzy finder". interface rather than a "fuzzy finder". You can later enable the search using
\fBenable-search\fR or `\fBtoggle-search\R action.
.SS Search result .SS Search result
.TP .TP
.B "+s, --no-sort" .B "+s, --no-sort"
@@ -233,6 +234,29 @@ e.g.
\fBfzf --margin 10% \fBfzf --margin 10%
fzf --margin 1,5%\fR fzf --margin 1,5%\fR
.RE .RE
.TP
.BI "--padding=" PADDING
Comma-separated expression for padding inside the border. Padding is
distinguishable from margin only when \fB--border\fR option is used.
.br
.br
e.g.
\fBfzf --margin 5% --padding 5% --border --preview 'cat {}' \\
--color bg:#222222,preview-bg:#333333\fR
.br
.RS
.BR TRBL " Same padding for top, right, bottom, and left"
.br
.BR TB,RL " Vertical, horizontal padding"
.br
.BR T,RL,B " Top, horizontal, bottom padding"
.br
.BR T,R,B,L " Top, right, bottom, left padding"
.br
.RE
.TP .TP
.BI "--info=" "STYLE" .BI "--info=" "STYLE"
Determines the display style of finder info. Determines the display style of finder info.
@@ -300,6 +324,8 @@ color mappings.
\fBbg+ \fRBackground (current line) \fBbg+ \fRBackground (current line)
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR) \fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
\fBhl+ \fRHighlighted substrings (current line) \fBhl+ \fRHighlighted substrings (current line)
\fBquery \fRQuery string
\fBdisabled \fRQuery string when search is disabled
\fBinfo \fRInfo line (match counters) \fBinfo \fRInfo line (match counters)
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR) \fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBprompt \fRPrompt \fBprompt \fRPrompt
@@ -416,7 +442,7 @@ e.g.
done'\fR done'\fR
.RE .RE
.TP .TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]" .BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
.RS .RS
.B POSITION: (default: right) .B POSITION: (default: right)
@@ -425,27 +451,43 @@ e.g.
\fBleft \fBleft
\fBright \fBright
\fRDetermines the layout of the preview window. If the argument contains \fRDetermines the layout of the preview window.
\fB:hidden\fR, the preview window will be hidden by default until
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
Line wrap can be enabled with \fB:wrap\fR flag. Cyclic scrolling is enabled
with \fB:cycle\fR flag.
If size is given as 0, preview window will not be visible, but fzf will still * If the argument contains \fB:hidden\fR, the preview window will be hidden by
default until \fBtoggle-preview\fR action is triggered.
* If size is given as 0, preview window will not be visible, but fzf will still
execute the command in the background. execute the command in the background.
To change the style of the border of the preview window, specify one of * Long lines are truncated by default. Line wrap can be enabled with
\fB:wrap\fR flag.
* Preview window will automatically scroll to the bottom when \fB:follow\fR
flag is set, similarly to how \fBtail -f\fR works.
.RS
e.g.
\fBfzf --preview-window follow --preview 'for i in $(seq 100000); do
echo "$i"
sleep 0.01
(( i % 300 == 0 )) && printf "\\033[2J"
done'\fR
.RE
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
* To change the style of the border of the preview window, specify one of
\fBrounded\fR (border with rounded edges, default), \fBsharp\fR (border with \fBrounded\fR (border with rounded edges, default), \fBsharp\fR (border with
sharp edges), or \fBnoborder\fR (no border). sharp edges), or \fBnoborder\fR (no border).
\fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview * \fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview
window. \fBSCROLL\fR can be either a numeric integer or a single-field index window. \fBSCROLL\fR can be either a numeric integer or a single-field index
expression that refers to a numeric integer. The optional \fB-OFFSET\fR part is expression that refers to a numeric integer. The optional \fB-OFFSET\fR part is
for adjusting the base offset so that you can see the text above it. It should for adjusting the base offset so that you can see the text above it. It should
be given as a numeric integer (\fB-INTEGER\fR), or as a denominator form be given as a numeric integer (\fB-INTEGER\fR), or as a denominator form
(\fB-/INTEGER\fR) for specifying a fraction of the preview window height. (\fB-/INTEGER\fR) for specifying a fraction of the preview window height.
\fBdefault\fR resets all options previously set to the default. * \fBdefault\fR resets all options previously set to the default.
.RS .RS
e.g. e.g.
@@ -627,9 +669,7 @@ e.g.
.br .br
\fIctrl-alt-[a-z]\fR \fIctrl-alt-[a-z]\fR
.br .br
\fIalt-[a-z]\fR \fIalt-[*]\fR (Any case-sensitive single character is allowed)
.br
\fIalt-[0-9]\fR
.br .br
\fIf[1-12]\fR \fIf[1-12]\fR
.br .br
@@ -653,8 +693,6 @@ e.g.
.br .br
\fIalt-bspace\fR (\fIalt-bs\fR) \fIalt-bspace\fR (\fIalt-bs\fR)
.br .br
\fIalt-/\fR
.br
\fItab\fR \fItab\fR
.br .br
\fIbtab\fR (\fIshift-tab\fR) \fIbtab\fR (\fIshift-tab\fR)
@@ -689,6 +727,14 @@ e.g.
.br .br
\fIshift-right\fR \fIshift-right\fR
.br .br
\fIalt-shift-up\fR
.br
\fIalt-shift-down\fR
.br
\fIalt-shift-left\fR
.br
\fIalt-shift-right\fR
.br
\fIleft-click\fR \fIleft-click\fR
.br .br
\fIright-click\fR \fIright-click\fR
@@ -703,8 +749,8 @@ or any single character
Triggered whenever the query string is changed Triggered whenever the query string is changed
e.g. e.g.
\fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed \fB# Move cursor to the first entry whenever the query is changed
fzf --bind change:top\fR fzf --bind change:first\fR
.RE .RE
\fIbackward-eof\fR \fIbackward-eof\fR
@@ -730,16 +776,20 @@ 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-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)
\fBclear-query\fR (clear query string) \fBclear-query\fR (clear query string)
\fBdelete-char\fR \fIdel\fR \fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty) \fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
\fBdeselect-all\fR (deselect all matches) \fBdeselect-all\fR (deselect all matches)
\fBdisable-search\fR (disable search functionality)
\fBdown\fR \fIctrl-j ctrl-n down\fR \fBdown\fR \fIctrl-j ctrl-n down\fR
\fBenable-search\fR (enable search functionality)
\fBend-of-line\fR \fIctrl-e end\fR \fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details) \fBexecute(...)\fR (see below for the details)
\fBexecute-silent(...)\fR (see below for the details) \fBexecute-silent(...)\fR (see below for the details)
\fBfirst\fR (move to the first match)
\fBforward-char\fR \fIctrl-f right\fR \fBforward-char\fR \fIctrl-f right\fR
\fBforward-word\fR \fIalt-f shift-right\fR \fBforward-word\fR \fIalt-f shift-right\fR
\fBignore\fR \fBignore\fR
@@ -747,6 +797,7 @@ A key or an event can be bound to one or more of the following actions.
\fBjump-accept\fR (jump and accept) \fBjump-accept\fR (jump and accept)
\fBkill-line\fR \fBkill-line\fR
\fBkill-word\fR \fIalt-d\fR \fBkill-word\fR \fIalt-d\fR
\fBlast\fR (move to the last match)
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR) \fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
\fBpage-down\fR \fIpgdn\fR \fBpage-down\fR \fIpgdn\fR
\fBpage-up\fR \fIpgup\fR \fBpage-up\fR \fIpgup\fR
@@ -759,6 +810,8 @@ A key or an event can be bound to one or more of the following actions.
\fBpreview-page-up\fR \fBpreview-page-up\fR
\fBpreview-half-page-down\fR \fBpreview-half-page-down\fR
\fBpreview-half-page-up\fR \fBpreview-half-page-up\fR
\fBpreview-bottom\fR
\fBpreview-top\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR) \fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprint-query\fR (print query and exit) \fBprint-query\fR (print query and exit)
\fBrefresh-preview\fR \fBrefresh-preview\fR
@@ -772,9 +825,9 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR) \fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR \fBtoggle-preview\fR
\fBtoggle-preview-wrap\fR \fBtoggle-preview-wrap\fR
\fBtoggle-search\fR (toggle search functionality)
\fBtoggle-sort\fR \fBtoggle-sort\fR
\fBtoggle+up\fR \fIbtab (shift-tab)\fR \fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBtop\fR (move to the top result)
\fBunix-line-discard\fR \fIctrl-u\fR \fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR \fBunix-word-rubout\fR \fIctrl-w\fR
\fBup\fR \fIctrl-k ctrl-p up\fR \fBup\fR \fIctrl-k ctrl-p up\fR
@@ -785,7 +838,40 @@ A key or an event can be bound to one or more of the following actions.
Multiple actions can be chained using \fB+\fR separator. Multiple actions can be chained using \fB+\fR separator.
e.g. e.g.
\fBfzf --bind 'ctrl-a:select-all+accept'\fR \fBfzf --multi --bind 'ctrl-a:select-all+accept'\fR
\fBfzf --multi --bind 'ctrl-a:select-all' --bind 'ctrl-a:+accept'\fR
.SS ACTION ARGUMENT
An action denoted with \fB(...)\fR suffix takes an argument.
e.g.
\fBfzf --bind 'ctrl-a:change-prompt(NewPrompt> )'\fR
\fBfzf --bind 'ctrl-v:preview(cat {})' --preview-window hidden\fR
If the argument contains parentheses, fzf may fail to parse the expression. In
that case, you can use any of the following alternative notations to avoid
parse errors.
\fBaction-name[...]\fR
\fBaction-name~...~\fR
\fBaction-name!...!\fR
\fBaction-name@...@\fR
\fBaction-name#...#\fR
\fBaction-name$...$\fR
\fBaction-name%...%\fR
\fBaction-name^...^\fR
\fBaction-name&...&\fR
\fBaction-name*...*\fR
\fBaction-name;...;\fR
\fBaction-name/.../\fR
\fBaction-name|...|\fR
\fBaction-name:...\fR
.RS
The last one is the special form that frees you from parse errors as it does
not expect the closing character. The catch is that it should be the last one
in the comma-separated list of key-action pairs.
.RE
.SS COMMAND EXECUTION .SS COMMAND EXECUTION
@@ -797,30 +883,6 @@ binding \fBenter\fR key to \fBless\fR command like follows.
You can use the same placeholder expressions as in \fB--preview\fR. You can use the same placeholder expressions as in \fB--preview\fR.
If the command contains parentheses, fzf may fail to parse the expression. In
that case, you can use any of the following alternative notations to avoid
parse errors.
\fBexecute[...]\fR
\fBexecute~...~\fR
\fBexecute!...!\fR
\fBexecute@...@\fR
\fBexecute#...#\fR
\fBexecute$...$\fR
\fBexecute%...%\fR
\fBexecute^...^\fR
\fBexecute&...&\fR
\fBexecute*...*\fR
\fBexecute;...;\fR
\fBexecute/.../\fR
\fBexecute|...|\fR
\fBexecute:...\fR
.RS
The last one is the special form that frees you from parse errors as it does
not expect the closing character. The catch is that it should be the last one
in the comma-separated list of key-action pairs.
.RE
fzf switches to the alternate screen when executing a command. However, if the fzf switches to the alternate screen when executing a command. However, if the
command is expected to complete quickly, and you are not interested in its command is expected to complete quickly, and you are not interested in its
output, you might want to use \fBexecute-silent\fR instead, which silently output, you might want to use \fBexecute-silent\fR instead, which silently
@@ -846,7 +908,7 @@ e.g.
INITIAL_QUERY="foobar" INITIAL_QUERY="foobar"
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\ FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\ fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
--ansi --phony --query "$INITIAL_QUERY"\fR --ansi --disabled --query "$INITIAL_QUERY"\fR
.SS PREVIEW BINDING .SS PREVIEW BINDING

View File

@@ -283,7 +283,8 @@ function! s:common_sink(action, lines) abort
let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h') let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h')
for item in a:lines for item in a:lines
if item[0] != '~' && item !~ (s:is_win ? '^[A-Z]:\' : '^/') if item[0] != '~' && item !~ (s:is_win ? '^[A-Z]:\' : '^/')
let item = join([cwd, item], (s:is_win ? '\' : '/')) let sep = s:is_win ? '\' : '/'
let item = join([cwd, item], cwd[len(cwd)-1] == sep ? '' : sep)
endif endif
if empty if empty
execute 'e' s:escape(item) execute 'e' s:escape(item)
@@ -644,7 +645,8 @@ function! s:execute(dict, command, use_height, temps) abort
endif endif
let exit_status = v:shell_error let exit_status = v:shell_error
redraw! redraw!
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : [] let lines = s:collect(a:temps)
return s:exit_handler(exit_status, command) ? lines : []
endfunction endfunction
function! s:execute_tmux(dict, command, temps) abort function! s:execute_tmux(dict, command, temps) abort
@@ -658,7 +660,8 @@ function! s:execute_tmux(dict, command, temps) abort
call system(command) call system(command)
let exit_status = v:shell_error let exit_status = v:shell_error
redraw! redraw!
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : [] let lines = s:collect(a:temps)
return s:exit_handler(exit_status, command) ? lines : []
endfunction endfunction
function! s:calc_size(max, val, dict) function! s:calc_size(max, val, dict)
@@ -805,12 +808,12 @@ function! s:execute_term(dict, command, temps) abort
execute self.winrest execute self.winrest
endif endif
let lines = s:collect(self.temps)
if !s:exit_handler(a:code, self.command, 1) if !s:exit_handler(a:code, self.command, 1)
return return
endif endif
call s:pushd(self.dict) call s:pushd(self.dict)
let lines = s:collect(self.temps)
call s:callback(self.dict, lines) call s:callback(self.dict, lines)
call self.switch_back(s:getpos() == self.ppos) call self.switch_back(s:getpos() == self.ppos)
endfunction endfunction
@@ -828,13 +831,16 @@ function! s:execute_term(dict, command, temps) abort
if has('nvim') if has('nvim')
call termopen(command, fzf) call termopen(command, fzf)
else else
let term_opts = {'exit_cb': function(fzf.on_exit)} let term_opts = {'exit_cb': function(fzf.on_exit), 'term_kill': 'term'}
if is_popup if is_popup
let term_opts.hidden = 1 let term_opts.hidden = 1
else else
let term_opts.curwin = 1 let term_opts.curwin = 1
endif endif
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts) let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
if exists('&termwinkey')
call setbufvar(fzf.buf, '&termwinkey', '<c-z>')
endif
if is_popup && exists('#TerminalWinOpen') if is_popup && exists('#TerminalWinOpen')
doautocmd <nomodeline> TerminalWinOpen doautocmd <nomodeline> TerminalWinOpen
endif endif
@@ -842,6 +848,7 @@ function! s:execute_term(dict, command, temps) abort
call term_wait(fzf.buf, 20) call term_wait(fzf.buf, 20)
endif endif
endif endif
tnoremap <buffer> <c-z> <nop>
finally finally
call s:dopopd() call s:dopopd()
endtry endtry

View File

@@ -46,9 +46,20 @@ __fzf_comprun() {
fi fi
} }
__fzf_orig_completion_filter() { __fzf_orig_completion() {
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2"; [[ "\1" = *" -o nospace "* ]] \&\& [[ ! "$__fzf_nospace_commands" = *" \3 "* ]] \&\& __fzf_nospace_commands="$__fzf_nospace_commands \3 ";/' | local l comp f cmd
awk -F= '{OFS = FS} {gsub(/[^A-Za-z0-9_= ;]/, "_", $1);}1' while read -r l; do
if [[ "$l" =~ ^(.*\ -F)\ *([^ ]*).*\ ([^ ]*)$ ]]; then
comp="${BASH_REMATCH[1]}"
f="${BASH_REMATCH[2]}"
cmd="${BASH_REMATCH[3]}"
[[ "$f" = _fzf_* ]] && continue
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
if [[ "$l" = *" -o nospace "* ]] && [[ ! "$__fzf_nospace_commands" = *" $cmd "* ]]; then
__fzf_nospace_commands="$__fzf_nospace_commands $cmd "
fi
fi
done
} }
_fzf_opts_completion() { _fzf_opts_completion() {
@@ -137,7 +148,7 @@ _fzf_handle_dynamic_completion() {
ret=$? ret=$?
# _completion_loader may not have updated completion for the command # _completion_loader may not have updated completion for the command
if [ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]; then if [ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]; then
eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)" __fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
eval "${orig_complete/ -F / -o nospace -F }" eval "${orig_complete/ -F / -o nospace -F }"
else else
@@ -165,7 +176,7 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/} leftover=${leftover/#\/}
[ -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 $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$3 " "$item"
done) done)
matches=${matches% } matches=${matches% }
@@ -221,7 +232,7 @@ _fzf_complete() {
if [[ "$cur" == *"$trigger" ]]; then if [[ "$cur" == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}} cur=${cur:0:${#cur}-${#trigger}}
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ') selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace" selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
COMPREPLY=("$selected") COMPREPLY=("$selected")
@@ -306,9 +317,7 @@ a_cmds="
svn tar unzip zip" svn tar unzip zip"
# Preserve existing completion # Preserve existing completion
eval "$(complete | __fzf_orig_completion < <(complete -p $d_cmds $a_cmds 2> /dev/null)
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
__fzf_orig_completion_filter)"
if type _completion_loader > /dev/null 2>&1; then if type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=1 _fzf_completion_loader=1
@@ -353,7 +362,7 @@ _fzf_setup_completion() {
return 1 return 1
fi fi
shift shift
eval "$(complete -p "$@" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)" __fzf_orig_completion < <(complete -p "$@" 2> /dev/null)
for cmd in "$@"; do for cmd in "$@"; do
case "$kind" in case "$kind" in
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;; dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;

View File

@@ -145,7 +145,7 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/} leftover=${leftover/#\/}
[ -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 $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 " echo -n "${(q)item}$suffix "
done) done)
matches=${matches% } matches=${matches% }
@@ -207,7 +207,7 @@ _fzf_complete() {
type $post > /dev/null 2>&1 || post=cat type $post > /dev/null 2>&1 || post=cat
_fzf_feed_fifo "$fifo" _fzf_feed_fifo "$fifo"
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ') matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi

View File

@@ -18,7 +18,7 @@ __fzf_select__() {
-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 $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do 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
printf '%q ' "$item" printf '%q ' "$item"
done done
echo echo
@@ -41,7 +41,7 @@ __fzf_cd__() {
local cmd dir local cmd 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 $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir" 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"
} }
__fzf_history__() { __fzf_history__() {
@@ -49,7 +49,7 @@ __fzf_history__() {
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 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE" 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"
) || return ) || return
READLINE_LINE=${output#*$'\t'} READLINE_LINE=${output#*$'\t'}
if [ -z "$READLINE_POINT" ]; then if [ -z "$READLINE_POINT" ]; then

View File

@@ -31,7 +31,7 @@ function fzf_key_bindings
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
end end
if [ -z "$result" ] if [ -z "$result" ]
@@ -51,7 +51,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 $FZF_CTRL_R_OPTS +m" 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 -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.)
@@ -80,7 +80,7 @@ function fzf_key_bindings
-o -type d -print 2> /dev/null | sed 's@^\./@@'" -o -type d -print 2> /dev/null | sed 's@^\./@@'"
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 --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
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" ]

View File

@@ -45,7 +45,8 @@ __fsel() {
-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-"}"
setopt localoptions pipefail no_aliases 2> /dev/null setopt localoptions pipefail no_aliases 2> /dev/null
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do local item
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 item; do
echo -n "${(q)item} " echo -n "${(q)item} "
done done
local ret=$? local ret=$?
@@ -82,7 +83,7 @@ fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \ local 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-"}"
setopt localoptions pipefail no_aliases 2> /dev/null setopt localoptions pipefail no_aliases 2> /dev/null
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)" local 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)"
if [[ -z "$dir" ]]; then if [[ -z "$dir" ]]; then
zle redisplay zle redisplay
return 0 return 0
@@ -107,7 +108,7 @@ 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 | perl -ne 'print if !$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) ) 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)) )
local ret=$? local ret=$?
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
num=$selected[1] num=$selected[1]

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi Copyright (c) 2013-2021 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

View File

@@ -19,17 +19,18 @@ type ansiState struct {
fg tui.Color fg tui.Color
bg tui.Color bg tui.Color
attr tui.Attr attr tui.Attr
lbg tui.Color
} }
func (s *ansiState) colored() bool { func (s *ansiState) colored() bool {
return s.fg != -1 || s.bg != -1 || s.attr > 0 return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0
} }
func (s *ansiState) equals(t *ansiState) bool { func (s *ansiState) equals(t *ansiState) bool {
if t == nil { if t == nil {
return !s.colored() return !s.colored()
} }
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg
} }
func (s *ansiState) ToString() string { func (s *ansiState) ToString() string {
@@ -195,11 +196,14 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
// State // State
var state *ansiState var state *ansiState
if prevState == nil { if prevState == nil {
state = &ansiState{-1, -1, 0} state = &ansiState{-1, -1, 0, -1}
} else { } else {
state = &ansiState{prevState.fg, prevState.bg, prevState.attr} state = &ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
} }
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' { if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
if strings.HasSuffix(ansiCode, "0K") {
state.lbg = prevState.bg
}
return state return state
} }

View File

@@ -168,7 +168,7 @@ func TestAnsiCodeStringConversion(t *testing.T) {
} }
} }
assert("\x1b[m", nil, "") assert("\x1b[m", nil, "")
assert("\x1b[m", &ansiState{attr: tui.Blink}, "") assert("\x1b[m", &ansiState{attr: tui.Blink, lbg: -1}, "")
assert("\x1b[31m", nil, "\x1b[31;49m") assert("\x1b[31m", nil, "\x1b[31;49m")
assert("\x1b[41m", nil, "\x1b[39;41m") assert("\x1b[41m", nil, "\x1b[39;41m")
@@ -176,8 +176,8 @@ func TestAnsiCodeStringConversion(t *testing.T) {
assert("\x1b[92m", nil, "\x1b[92;49m") assert("\x1b[92m", nil, "\x1b[92;49m")
assert("\x1b[102m", nil, "\x1b[39;102m") assert("\x1b[102m", nil, "\x1b[39;102m")
assert("\x1b[31m", &ansiState{fg: 4, bg: 4}, "\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}, "\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[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")

View File

@@ -3,7 +3,7 @@ Package fzf implements fzf, a command-line fuzzy finder.
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi Copyright (c) 2013-2021 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
@@ -237,14 +237,16 @@ func Run(opts *Options, version string, revision string) {
go reader.restart(command) go reader.restart(command)
} }
eventBox.Watch(EvtReadNew) eventBox.Watch(EvtReadNew)
query := []rune{}
for { for {
delay := true delay := true
ticks++ ticks++
input := func() []rune { input := func() []rune {
if opts.Phony { paused, input := terminal.Input()
return []rune{} if !paused {
query = input
} }
return []rune(terminal.Input()) return query
} }
eventBox.Wait(func(events *util.Events) { eventBox.Wait(func(events *util.Events) {
if _, fin := (*events)[EvtReadFin]; fin { if _, fin := (*events)[EvtReadFin]; fin {

View File

@@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8"
"github.com/junegunn/fzf/src/algo" "github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
@@ -34,7 +33,7 @@ const usage = `usage: fzf [options]
-d, --delimiter=STR Field delimiter regex (default: AWK-style) -d, --delimiter=STR Field delimiter regex (default: AWK-style)
+s, --no-sort Do not sort the result +s, --no-sort Do not sort the result
--tac Reverse the order of the input --tac Reverse the order of the input
--phony 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|begin|end|index]
(default: length) (default: length)
@@ -60,7 +59,8 @@ const usage = `usage: fzf [options]
--border[=STYLE] Draw border around the finder --border[=STYLE] Draw border around the finder
[rounded|sharp|horizontal|vertical| [rounded|sharp|horizontal|vertical|
top|bottom|left|right] (default: rounded) top|bottom|left|right] (default: rounded)
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L) --margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
--info=STYLE Finder info style [default|inline|hidden] --info=STYLE Finder info style [default|inline|hidden]
--prompt=STR Input prompt (default: '> ') --prompt=STR Input prompt (default: '> ')
--pointer=STR Pointer to the current line (default: '>') --pointer=STR Pointer to the current line (default: '>')
@@ -82,7 +82,7 @@ const usage = `usage: fzf [options]
--preview=COMMAND Command to preview highlighted line ({}) --preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%) --preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][:SIZE[%]] [up|down|left|right][:SIZE[%]]
[:[no]wrap][:[no]cycle][:[no]hidden] [:[no]wrap][:[no]cycle][:[no]follow][:[no]hidden]
[:rounded|sharp|noborder] [:rounded|sharp|noborder]
[:+SCROLL[-OFFSET]] [:+SCROLL[-OFFSET]]
[:default] [:default]
@@ -168,6 +168,7 @@ type previewOpts struct {
hidden bool hidden bool
wrap bool wrap bool
cycle bool cycle bool
follow bool
border tui.BorderShape border tui.BorderShape
} }
@@ -209,8 +210,8 @@ type Options struct {
Exit0 bool Exit0 bool
Filter *string Filter *string
ToggleSort bool ToggleSort bool
Expect map[int]string Expect map[tui.Event]string
Keymap map[int][]action Keymap map[tui.Event][]action
Preview previewOpts Preview previewOpts
PrintQuery bool PrintQuery bool
ReadZero bool ReadZero bool
@@ -221,6 +222,7 @@ type Options struct {
Header []string Header []string
HeaderLines int HeaderLines int
Margin [4]sizeSpec Margin [4]sizeSpec
Padding [4]sizeSpec
BorderShape tui.BorderShape BorderShape tui.BorderShape
Unicode bool Unicode bool
Tabstop int Tabstop int
@@ -229,7 +231,7 @@ type Options struct {
} }
func defaultPreviewOpts(command string) previewOpts { func defaultPreviewOpts(command string) previewOpts {
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, tui.BorderRounded} return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded}
} }
func defaultOptions() *Options { func defaultOptions() *Options {
@@ -269,8 +271,8 @@ func defaultOptions() *Options {
Exit0: false, Exit0: false,
Filter: nil, Filter: nil,
ToggleSort: false, ToggleSort: false,
Expect: make(map[int]string), Expect: make(map[tui.Event]string),
Keymap: make(map[int][]action), Keymap: make(map[tui.Event][]action),
Preview: defaultPreviewOpts(""), Preview: defaultPreviewOpts(""),
PrintQuery: false, PrintQuery: false,
ReadZero: false, ReadZero: false,
@@ -281,6 +283,7 @@ func defaultOptions() *Options {
Header: make([]string, 0), Header: make([]string, 0),
HeaderLines: 0, HeaderLines: 0,
Margin: defaultMargin(), Margin: defaultMargin(),
Padding: defaultMargin(),
Unicode: true, Unicode: true,
Tabstop: 8, Tabstop: 8,
ClearOnExit: true, ClearOnExit: true,
@@ -441,126 +444,141 @@ func parseBorder(str string, optional bool) tui.BorderShape {
return tui.BorderNone return tui.BorderNone
} }
func parseKeyChords(str string, message string) map[int]string { func parseKeyChords(str string, message string) map[tui.Event]string {
if len(str) == 0 { if len(str) == 0 {
errorExit(message) errorExit(message)
} }
str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string(escapedComma))
tokens := strings.Split(str, ",") tokens := strings.Split(str, ",")
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") { if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
tokens = append(tokens, ",") tokens = append(tokens, ",")
} }
chords := make(map[int]string) chords := make(map[tui.Event]string)
for _, key := range tokens { for _, key := range tokens {
if len(key) == 0 { if len(key) == 0 {
continue // ignore continue // ignore
} }
key = strings.ReplaceAll(key, string(escapedComma), ",")
lkey := strings.ToLower(key) lkey := strings.ToLower(key)
chord := 0 add := func(e tui.EventType) {
chords[e.AsEvent()] = key
}
switch lkey { switch lkey {
case "up": case "up":
chord = tui.Up add(tui.Up)
case "down": case "down":
chord = tui.Down add(tui.Down)
case "left": case "left":
chord = tui.Left add(tui.Left)
case "right": case "right":
chord = tui.Right add(tui.Right)
case "enter", "return": case "enter", "return":
chord = tui.CtrlM add(tui.CtrlM)
case "space": case "space":
chord = tui.AltZ + int(' ') chords[tui.Key(' ')] = key
case "bspace", "bs": case "bspace", "bs":
chord = tui.BSpace add(tui.BSpace)
case "ctrl-space": case "ctrl-space":
chord = tui.CtrlSpace add(tui.CtrlSpace)
case "ctrl-^", "ctrl-6": case "ctrl-^", "ctrl-6":
chord = tui.CtrlCaret add(tui.CtrlCaret)
case "ctrl-/", "ctrl-_": case "ctrl-/", "ctrl-_":
chord = tui.CtrlSlash add(tui.CtrlSlash)
case "ctrl-\\": case "ctrl-\\":
chord = tui.CtrlBackSlash add(tui.CtrlBackSlash)
case "ctrl-]": case "ctrl-]":
chord = tui.CtrlRightBracket add(tui.CtrlRightBracket)
case "change": case "change":
chord = tui.Change add(tui.Change)
case "backward-eof": case "backward-eof":
chord = tui.BackwardEOF add(tui.BackwardEOF)
case "alt-enter", "alt-return": case "alt-enter", "alt-return":
chord = tui.CtrlAltM chords[tui.CtrlAltKey('m')] = key
case "alt-space": case "alt-space":
chord = tui.AltSpace chords[tui.AltKey(' ')] = key
case "alt-/":
chord = tui.AltSlash
case "alt-bs", "alt-bspace": case "alt-bs", "alt-bspace":
chord = tui.AltBS add(tui.AltBS)
case "alt-up": case "alt-up":
chord = tui.AltUp add(tui.AltUp)
case "alt-down": case "alt-down":
chord = tui.AltDown add(tui.AltDown)
case "alt-left": case "alt-left":
chord = tui.AltLeft add(tui.AltLeft)
case "alt-right": case "alt-right":
chord = tui.AltRight add(tui.AltRight)
case "tab": case "tab":
chord = tui.Tab add(tui.Tab)
case "btab", "shift-tab": case "btab", "shift-tab":
chord = tui.BTab add(tui.BTab)
case "esc": case "esc":
chord = tui.ESC add(tui.ESC)
case "del": case "del":
chord = tui.Del add(tui.Del)
case "home": case "home":
chord = tui.Home add(tui.Home)
case "end": case "end":
chord = tui.End add(tui.End)
case "insert": case "insert":
chord = tui.Insert add(tui.Insert)
case "pgup", "page-up": case "pgup", "page-up":
chord = tui.PgUp add(tui.PgUp)
case "pgdn", "page-down": case "pgdn", "page-down":
chord = tui.PgDn add(tui.PgDn)
case "alt-shift-up", "shift-alt-up":
add(tui.AltSUp)
case "alt-shift-down", "shift-alt-down":
add(tui.AltSDown)
case "alt-shift-left", "shift-alt-left":
add(tui.AltSLeft)
case "alt-shift-right", "shift-alt-right":
add(tui.AltSRight)
case "shift-up": case "shift-up":
chord = tui.SUp add(tui.SUp)
case "shift-down": case "shift-down":
chord = tui.SDown add(tui.SDown)
case "shift-left": case "shift-left":
chord = tui.SLeft add(tui.SLeft)
case "shift-right": case "shift-right":
chord = tui.SRight add(tui.SRight)
case "left-click": case "left-click":
chord = tui.LeftClick add(tui.LeftClick)
case "right-click": case "right-click":
chord = tui.RightClick add(tui.RightClick)
case "double-click": case "double-click":
chord = tui.DoubleClick add(tui.DoubleClick)
case "f10": case "f10":
chord = tui.F10 add(tui.F10)
case "f11": case "f11":
chord = tui.F11 add(tui.F11)
case "f12": case "f12":
chord = tui.F12 add(tui.F12)
default: default:
runes := []rune(key)
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) { if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
chord = tui.CtrlAltA + int(lkey[9]) - 'a' chords[tui.CtrlAltKey(rune(key[9]))] = key
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) { } else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
chord = tui.CtrlA + int(lkey[5]) - 'a' add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) { } else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
chord = tui.AltA + int(lkey[4]) - 'a' r := runes[4]
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isNumeric(lkey[4]) { switch r {
chord = tui.Alt0 + int(lkey[4]) - '0' case escapedColon:
r = ':'
case escapedComma:
r = ','
case escapedPlus:
r = '+'
}
chords[tui.AltKey(r)] = key
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' { } else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
chord = tui.F1 + int(key[1]) - '1' add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
} else if utf8.RuneCountInString(key) == 1 { } else if len(runes) == 1 {
chord = tui.AltZ + int([]rune(key)[0]) chords[tui.Key(runes[0])] = key
} else { } else {
errorExit("unsupported key: " + key) errorExit("unsupported key: " + key)
} }
} }
if chord > 0 {
chords[chord] = key
}
} }
return chords return chords
} }
@@ -632,71 +650,74 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
fail() fail()
} }
cattr := tui.NewColorAttr() mergeAttr := func(cattr *tui.ColorAttr) {
for _, component := range components[1:] { for _, component := range components[1:] {
switch component { switch component {
case "regular": case "regular":
cattr.Attr = tui.AttrRegular cattr.Attr = tui.AttrRegular
case "bold", "strong": case "bold", "strong":
cattr.Attr |= tui.Bold cattr.Attr |= tui.Bold
case "dim": case "dim":
cattr.Attr |= tui.Dim cattr.Attr |= tui.Dim
case "italic": case "italic":
cattr.Attr |= tui.Italic cattr.Attr |= tui.Italic
case "underline": case "underline":
cattr.Attr |= tui.Underline cattr.Attr |= tui.Underline
case "blink": case "blink":
cattr.Attr |= tui.Blink cattr.Attr |= tui.Blink
case "reverse": case "reverse":
cattr.Attr |= tui.Reverse cattr.Attr |= tui.Reverse
case "": case "":
default: default:
if rrggbb.MatchString(component) { if rrggbb.MatchString(component) {
cattr.Color = tui.HexToColor(component) cattr.Color = tui.HexToColor(component)
} else { } else {
ansi32, err := strconv.Atoi(component) ansi32, err := strconv.Atoi(component)
if err != nil || ansi32 < -1 || ansi32 > 255 { if err != nil || ansi32 < -1 || ansi32 > 255 {
fail() fail()
}
cattr.Color = tui.Color(ansi32)
} }
cattr.Color = tui.Color(ansi32)
} }
} }
} }
switch components[0] { switch components[0] {
case "input": case "query", "input":
theme.Input = cattr mergeAttr(&theme.Input)
case "disabled":
mergeAttr(&theme.Disabled)
case "fg": case "fg":
theme.Fg = cattr mergeAttr(&theme.Fg)
case "bg": case "bg":
theme.Bg = cattr mergeAttr(&theme.Bg)
case "preview-fg": case "preview-fg":
theme.PreviewFg = cattr mergeAttr(&theme.PreviewFg)
case "preview-bg": case "preview-bg":
theme.PreviewBg = cattr mergeAttr(&theme.PreviewBg)
case "fg+": case "fg+":
theme.Current = cattr mergeAttr(&theme.Current)
case "bg+": case "bg+":
theme.DarkBg = cattr mergeAttr(&theme.DarkBg)
case "gutter": case "gutter":
theme.Gutter = cattr mergeAttr(&theme.Gutter)
case "hl": case "hl":
theme.Match = cattr mergeAttr(&theme.Match)
case "hl+": case "hl+":
theme.CurrentMatch = cattr mergeAttr(&theme.CurrentMatch)
case "border": case "border":
theme.Border = cattr mergeAttr(&theme.Border)
case "prompt": case "prompt":
theme.Prompt = cattr mergeAttr(&theme.Prompt)
case "spinner": case "spinner":
theme.Spinner = cattr mergeAttr(&theme.Spinner)
case "info": case "info":
theme.Info = cattr mergeAttr(&theme.Info)
case "pointer": case "pointer":
theme.Cursor = cattr mergeAttr(&theme.Cursor)
case "marker": case "marker":
theme.Selected = cattr mergeAttr(&theme.Selected)
case "header": case "header":
theme.Header = cattr mergeAttr(&theme.Header)
default: default:
fail() fail()
} }
@@ -707,11 +728,11 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
var executeRegexp *regexp.Regexp var executeRegexp *regexp.Regexp
func firstKey(keymap map[int]string) int { func firstKey(keymap map[tui.Event]string) tui.Event {
for k := range keymap { for k := range keymap {
return k return k
} }
return 0 return tui.EventType(0).AsEvent()
} }
const ( const (
@@ -724,10 +745,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):.+|[:+](execute(?:-multi|-silent)?|reload|preview)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`) `(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
} }
func parseKeymap(keymap map[int][]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, "+") {
@@ -738,6 +759,8 @@ func parseKeymap(keymap map[int][]action, str string) {
prefix = symbol + "reload" prefix = symbol + "reload"
} 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:], "change-prompt") {
prefix = symbol + "change-prompt"
} else if src[len(prefix)] == '-' { } else if src[len(prefix)] == '-' {
c := src[len(prefix)+1] c := src[len(prefix)+1]
if c == 's' || c == 'S' { if c == 's' || c == 'S' {
@@ -761,13 +784,13 @@ func parseKeymap(keymap map[int][]action, str string) {
if len(pair) < 2 { if len(pair) < 2 {
errorExit("bind action not specified: " + origPairStr) errorExit("bind action not specified: " + origPairStr)
} }
var key int var key tui.Event
if len(pair[0]) == 1 && pair[0][0] == escapedColon { if len(pair[0]) == 1 && pair[0][0] == escapedColon {
key = ':' + tui.AltZ key = tui.Key(':')
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma { } else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
key = ',' + tui.AltZ key = tui.Key(',')
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus { } else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
key = '+' + tui.AltZ key = tui.Key('+')
} else { } else {
keys := parseKeyChords(pair[0], "key name required") keys := parseKeyChords(pair[0], "key name required")
key = firstKey(keys) key = firstKey(keys)
@@ -854,6 +877,8 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actToggleOut) appendAction(actToggleOut)
case "toggle-all": case "toggle-all":
appendAction(actToggleAll) appendAction(actToggleAll)
case "toggle-search":
appendAction(actToggleSearch)
case "select-all": case "select-all":
appendAction(actSelectAll) appendAction(actSelectAll)
case "deselect-all": case "deselect-all":
@@ -864,8 +889,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actDown) appendAction(actDown)
case "up": case "up":
appendAction(actUp) appendAction(actUp)
case "top": case "first", "top":
appendAction(actTop) appendAction(actFirst)
case "last":
appendAction(actLast)
case "page-up": case "page-up":
appendAction(actPageUp) appendAction(actPageUp)
case "page-down": case "page-down":
@@ -884,6 +911,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actTogglePreviewWrap) appendAction(actTogglePreviewWrap)
case "toggle-sort": case "toggle-sort":
appendAction(actToggleSort) appendAction(actToggleSort)
case "preview-top":
appendAction(actPreviewTop)
case "preview-bottom":
appendAction(actPreviewBottom)
case "preview-up": case "preview-up":
appendAction(actPreviewUp) appendAction(actPreviewUp)
case "preview-down": case "preview-down":
@@ -896,6 +927,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actPreviewHalfPageUp) appendAction(actPreviewHalfPageUp)
case "preview-half-page-down": case "preview-half-page-down":
appendAction(actPreviewHalfPageDown) appendAction(actPreviewHalfPageDown)
case "enable-search":
appendAction(actEnableSearch)
case "disable-search":
appendAction(actDisableSearch)
default: default:
t := isExecuteAction(specLower) t := isExecuteAction(specLower)
if t == actIgnore { if t == actIgnore {
@@ -911,6 +946,8 @@ func parseKeymap(keymap map[int][]action, str string) {
offset = len("reload") offset = len("reload")
case actPreview: case actPreview:
offset = len("preview") offset = len("preview")
case actChangePrompt:
offset = len("change-prompt")
case actExecuteSilent: case actExecuteSilent:
offset = len("execute-silent") offset = len("execute-silent")
case actExecuteMulti: case actExecuteMulti:
@@ -950,6 +987,8 @@ func isExecuteAction(str string) actionType {
return actReload return actReload
case "preview": case "preview":
return actPreview return actPreview
case "change-prompt":
return actChangePrompt
case "execute": case "execute":
return actExecute return actExecute
case "execute-silent": case "execute-silent":
@@ -960,7 +999,7 @@ func isExecuteAction(str string) actionType {
return actIgnore return actIgnore
} }
func parseToggleSort(keymap map[int][]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")
@@ -1064,6 +1103,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
opts.border = tui.BorderSharp opts.border = tui.BorderSharp
case "noborder": case "noborder":
opts.border = tui.BorderNone opts.border = tui.BorderNone
case "follow":
opts.follow = true
case "nofollow":
opts.follow = false
default: default:
if sizeRegex.MatchString(token) { if sizeRegex.MatchString(token) {
opts.size = parseSize(token, 99, "window size") opts.size = parseSize(token, 99, "window size")
@@ -1076,10 +1119,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
} }
} }
func parseMargin(margin string) [4]sizeSpec { func parseMargin(opt string, margin string) [4]sizeSpec {
margins := strings.Split(margin, ",") margins := strings.Split(margin, ",")
checked := func(str string) sizeSpec { checked := func(str string) sizeSpec {
return parseSize(str, 49, "margin") return parseSize(str, 49, opt)
} }
switch len(margins) { switch len(margins) {
case 1: case 1:
@@ -1099,7 +1142,7 @@ func parseMargin(margin string) [4]sizeSpec {
checked(margins[0]), checked(margins[1]), checked(margins[0]), checked(margins[1]),
checked(margins[2]), checked(margins[3])} checked(margins[2]), checked(margins[3])}
default: default:
errorExit("invalid margin: " + margin) errorExit("invalid " + opt + ": " + margin)
} }
return defaultMargin() return defaultMargin()
} }
@@ -1163,10 +1206,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Expect[k] = v opts.Expect[k] = v
} }
case "--no-expect": case "--no-expect":
opts.Expect = make(map[int]string) opts.Expect = make(map[tui.Event]string)
case "--no-phony": case "--enabled", "--no-phony":
opts.Phony = false opts.Phony = false
case "--phony": case "--disabled", "--phony":
opts.Phony = true opts.Phony = true
case "--tiebreak": case "--tiebreak":
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required")) opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
@@ -1324,6 +1367,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Height = sizeSpec{} opts.Height = sizeSpec{}
case "--no-margin": case "--no-margin":
opts.Margin = defaultMargin() opts.Margin = defaultMargin()
case "--no-padding":
opts.Padding = defaultMargin()
case "--no-border": case "--no-border":
opts.BorderShape = tui.BorderNone opts.BorderShape = tui.BorderNone
case "--border": case "--border":
@@ -1335,7 +1380,12 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Unicode = true opts.Unicode = true
case "--margin": case "--margin":
opts.Margin = parseMargin( opts.Margin = parseMargin(
"margin",
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)")) nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
case "--padding":
opts.Padding = parseMargin(
"padding",
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
case "--tabstop": case "--tabstop":
opts.Tabstop = nextInt(allArgs, &i, "tab stop required") opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
case "--clear": case "--clear":
@@ -1404,7 +1454,9 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--preview-window="); match { } else if match, value := optString(arg, "--preview-window="); match {
parsePreviewWindow(&opts.Preview, value) parsePreviewWindow(&opts.Preview, value)
} else if match, value := optString(arg, "--margin="); match { } else if match, value := optString(arg, "--margin="); match {
opts.Margin = parseMargin(value) opts.Margin = parseMargin("margin", value)
} else if match, value := optString(arg, "--padding="); match {
opts.Padding = parseMargin("padding", value)
} else if match, value := optString(arg, "--tabstop="); match { } else if match, value := optString(arg, "--tabstop="); match {
opts.Tabstop = atoi(value) opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--hscroll-off="); match { } else if match, value := optString(arg, "--hscroll-off="); match {
@@ -1478,11 +1530,11 @@ func postProcessOptions(opts *Options) {
} }
// Default actions for CTRL-N / CTRL-P when --history is set // Default actions for CTRL-N / CTRL-P when --history is set
if opts.History != nil { if opts.History != nil {
if _, prs := opts.Keymap[tui.CtrlP]; !prs { if _, prs := opts.Keymap[tui.CtrlP.AsEvent()]; !prs {
opts.Keymap[tui.CtrlP] = toActions(actPreviousHistory) opts.Keymap[tui.CtrlP.AsEvent()] = toActions(actPreviousHistory)
} }
if _, prs := opts.Keymap[tui.CtrlN]; !prs { if _, prs := opts.Keymap[tui.CtrlN.AsEvent()]; !prs {
opts.Keymap[tui.CtrlN] = toActions(actNextHistory) opts.Keymap[tui.CtrlN.AsEvent()] = toActions(actNextHistory)
} }
} }

View File

@@ -125,26 +125,29 @@ func TestIrrelevantNth(t *testing.T) {
func TestParseKeys(t *testing.T) { func TestParseKeys(t *testing.T) {
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "") pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
check := func(i int, s string) { checkEvent := func(e tui.Event, s string) {
if pairs[i] != s { if pairs[e] != s {
t.Errorf("%s != %s", pairs[i], s) t.Errorf("%s != %s", pairs[e], s)
} }
} }
check := func(et tui.EventType, s string) {
checkEvent(et.AsEvent(), s)
}
if len(pairs) != 12 { if len(pairs) != 12 {
t.Error(12) t.Error(12)
} }
check(tui.CtrlZ, "ctrl-z") check(tui.CtrlZ, "ctrl-z")
check(tui.AltZ, "alt-z")
check(tui.F2, "f2") check(tui.F2, "f2")
check(tui.AltZ+'@', "@") check(tui.CtrlG, "ctrl-G")
check(tui.AltA, "Alt-a") checkEvent(tui.AltKey('z'), "alt-z")
check(tui.AltZ+'!', "!") checkEvent(tui.Key('@'), "@")
check(tui.CtrlA+'g'-'a', "ctrl-G") checkEvent(tui.AltKey('a'), "Alt-a")
check(tui.AltZ+'J', "J") checkEvent(tui.Key('!'), "!")
check(tui.AltZ+'g', "g") checkEvent(tui.Key('J'), "J")
check(tui.CtrlAltA, "ctrl-alt-a") checkEvent(tui.Key('g'), "g")
check(tui.CtrlAltM, "ALT-enter") checkEvent(tui.CtrlAltKey('a'), "ctrl-alt-a")
check(tui.AltSpace, "alt-SPACE") checkEvent(tui.CtrlAltKey('m'), "ALT-enter")
checkEvent(tui.AltKey(' '), "alt-SPACE")
// Synonyms // Synonyms
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "") pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
@@ -152,7 +155,7 @@ func TestParseKeys(t *testing.T) {
t.Error(9) t.Error(9)
} }
check(tui.CtrlM, "Return") check(tui.CtrlM, "Return")
check(tui.AltZ+' ', "space") checkEvent(tui.Key(' '), "space")
check(tui.Tab, "tab") check(tui.Tab, "tab")
check(tui.BTab, "btab") check(tui.BTab, "btab")
check(tui.ESC, "esc") check(tui.ESC, "esc")
@@ -184,93 +187,98 @@ func TestParseKeysWithComma(t *testing.T) {
t.Errorf("%d != %d", a, b) t.Errorf("%d != %d", a, b)
} }
} }
check := func(pairs map[int]string, i int, s string) { check := func(pairs map[tui.Event]string, e tui.Event, s string) {
if pairs[i] != s { if pairs[e] != s {
t.Errorf("%s != %s", pairs[i], s) t.Errorf("%s != %s", pairs[e], s)
} }
} }
pairs := parseKeyChords(",", "") pairs := parseKeyChords(",", "")
checkN(len(pairs), 1) checkN(len(pairs), 1)
check(pairs, tui.AltZ+',', ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",,a,b", "") pairs = parseKeyChords(",,a,b", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.AltZ+'b', "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.AltZ+',', ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,b,,", "") pairs = parseKeyChords("a,b,,", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.AltZ+'b', "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.AltZ+',', ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,,,b", "") pairs = parseKeyChords("a,,,b", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.AltZ+'b', "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.AltZ+',', ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,,,b,c", "") pairs = parseKeyChords("a,,,b,c", "")
checkN(len(pairs), 4) checkN(len(pairs), 4)
check(pairs, tui.AltZ+'a', "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.AltZ+'b', "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.AltZ+'c', "c") check(pairs, tui.Key('c'), "c")
check(pairs, tui.AltZ+',', ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",,,", "") pairs = parseKeyChords(",,,", "")
checkN(len(pairs), 1) checkN(len(pairs), 1)
check(pairs, tui.AltZ+',', ",") check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",ALT-,,", "")
checkN(len(pairs), 1)
check(pairs, tui.AltKey(','), "ALT-,")
} }
func TestBind(t *testing.T) { func TestBind(t *testing.T) {
keymap := defaultKeymap() keymap := defaultKeymap()
check := func(keyName int, arg1 string, types ...actionType) { check := func(event tui.Event, arg1 string, types ...actionType) {
if len(keymap[keyName]) != len(types) { if len(keymap[event]) != len(types) {
t.Errorf("invalid number of actions (%d != %d)", len(types), len(keymap[keyName])) t.Errorf("invalid number of actions for %v (%d != %d)",
event, len(types), len(keymap[event]))
return return
} }
for idx, action := range keymap[keyName] { for idx, action := range keymap[event] {
if types[idx] != action.t { if types[idx] != action.t {
t.Errorf("invalid action type (%d != %d)", types[idx], action.t) t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
} }
} }
if len(arg1) > 0 && keymap[keyName][0].a != arg1 { if len(arg1) > 0 && keymap[event][0].a != arg1 {
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[keyName][0].a) t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[event][0].a)
} }
} }
check(tui.CtrlA, "", actBeginningOfLine) check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
parseKeymap(keymap, parseKeymap(keymap,
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+ "ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+ "f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+ "alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
"x:Execute(foo+bar),X:execute/bar+baz/"+ "x:Execute(foo+bar),X:execute/bar+baz/"+
",f1:+top,f1:+top"+ ",f1:+first,f1:+top"+
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up") ",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
check(tui.CtrlA, "", actKillLine) check(tui.CtrlA.AsEvent(), "", actKillLine)
check(tui.CtrlB, "", actToggleSort, actUp, actDown) check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
check(tui.AltZ+'c', "", actPageUp) check(tui.Key('c'), "", actPageUp)
check(tui.AltZ+',', "", actAbort) check(tui.Key(','), "", actAbort)
check(tui.AltZ+':', "", actAccept) check(tui.Key(':'), "", actAccept)
check(tui.AltZ, "", actPageDown) check(tui.AltKey('z'), "", actPageDown)
check(tui.F1, "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actTop, actTop) check(tui.F1.AsEvent(), "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actFirst, actFirst)
check(tui.F2, "echo {}, {}, {}", actExecute) check(tui.F2.AsEvent(), "echo {}, {}, {}", actExecute)
check(tui.F3, "echo '({})'", actExecute) check(tui.F3.AsEvent(), "echo '({})'", actExecute)
check(tui.F4, "less {}", actExecute) check(tui.F4.AsEvent(), "less {}", actExecute)
check(tui.AltZ+'x', "foo+bar", actExecute) check(tui.Key('x'), "foo+bar", actExecute)
check(tui.AltZ+'X', "bar+baz", actExecute) check(tui.Key('X'), "bar+baz", actExecute)
check(tui.AltA, "echo (,),[,],/,:,;,%,{}", actExecuteMulti) check(tui.AltKey('a'), "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
check(tui.AltB, "echo (,),[,],/,:,@,%,{}", actExecute) check(tui.AltKey('b'), "echo (,),[,],/,:,@,%,{}", actExecute)
check(tui.AltZ+'+', "++\nfoobar,Y:execute(baz)+up", actExecute) check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} { for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char)) parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
check(tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute) check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
} }
parseKeymap(keymap, "f1:abort") parseKeymap(keymap, "f1:abort")
check(tui.F1, "", actAbort) check(tui.F1.AsEvent(), "", actAbort)
} }
func TestColorSpec(t *testing.T) { func TestColorSpec(t *testing.T) {
@@ -314,11 +322,12 @@ func TestColorSpec(t *testing.T) {
} }
func TestDefaultCtrlNP(t *testing.T) { func TestDefaultCtrlNP(t *testing.T) {
check := func(words []string, key int, expected actionType) { check := func(words []string, et tui.EventType, expected actionType) {
e := et.AsEvent()
opts := defaultOptions() opts := defaultOptions()
parseOptions(opts, words) parseOptions(opts, words)
postProcessOptions(opts) postProcessOptions(opts)
if opts.Keymap[key][0].t != expected { if opts.Keymap[e][0].t != expected {
t.Error() t.Error()
} }
} }

View File

@@ -160,7 +160,19 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
color := colMatch color := colMatch
if curr < -1 && theme.Colored { if curr < -1 && theme.Colored {
origColor := ansiToColorPair(itemColors[-curr-2], colMatch) origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
color = origColor.MergeNonDefault(color) // hl or hl+ only sets the foreground color, so colMatch is the
// combination of either [hl and bg] or [hl+ and bg+].
//
// If the original text already has background color, and the
// forground color of colMatch is -1, we shouldn't only apply the
// background color of colMatch.
// e.g. echo -e "\x1b[32;7mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
// echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
if color.Fg().IsDefault() && origColor.HasBg() {
color = origColor
} else {
color = origColor.MergeNonDefault(color)
}
} }
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color}) offset: [2]int32{int32(start), int32(idx)}, color: color})

View File

@@ -109,10 +109,10 @@ func TestColorOffset(t *testing.T) {
item := Result{ item := Result{
item: &Item{ item: &Item{
colors: &[]ansiOffset{ colors: &[]ansiOffset{
{[2]int32{0, 20}, ansiState{1, 5, 0}}, {[2]int32{0, 20}, ansiState{1, 5, 0, -1}},
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}}, {[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
{[2]int32{30, 32}, ansiState{3, 7, 0}}, {[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}} {[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined) colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined) colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)

View File

@@ -51,6 +51,7 @@ type previewer struct {
enabled bool enabled bool
scrollable bool scrollable bool
final bool final bool
following bool
spinner string spinner string
} }
@@ -108,8 +109,8 @@ type Terminal struct {
sort bool sort bool
toggleSort bool toggleSort bool
delimiter Delimiter delimiter Delimiter
expect map[int]string expect map[tui.Event]string
keymap map[int][]action keymap map[tui.Event][]action
pressed string pressed string
printQuery bool printQuery bool
history *History history *History
@@ -119,10 +120,12 @@ type Terminal struct {
ansi bool ansi bool
tabstop int tabstop int
margin [4]sizeSpec margin [4]sizeSpec
padding [4]sizeSpec
strong tui.Attr strong tui.Attr
unicode bool unicode bool
borderShape tui.BorderShape borderShape tui.BorderShape
cleanExit bool cleanExit bool
paused bool
border tui.Window border tui.Window
window tui.Window window tui.Window
pborder tui.Window pborder tui.Window
@@ -139,7 +142,7 @@ type Terminal struct {
selected map[int32]selectedItem selected map[int32]selectedItem
version int64 version int64
reqBox *util.EventBox reqBox *util.EventBox
preview previewOpts previewOpts previewOpts
previewer previewer previewer previewer
previewed previewed previewed previewed
previewBox *util.EventBox previewBox *util.EventBox
@@ -148,6 +151,7 @@ type Terminal struct {
initFunc func() initFunc func()
prevLines []itemLine prevLines []itemLine
suppress bool suppress bool
sigstop bool
startChan chan bool startChan chan bool
killChan chan int killChan chan int
slab *util.Slab slab *util.Slab
@@ -213,6 +217,7 @@ const (
actBackwardDeleteCharEOF actBackwardDeleteCharEOF
actBackwardWord actBackwardWord
actCancel actCancel
actChangePrompt
actClearScreen actClearScreen
actClearQuery actClearQuery
actClearSelection actClearSelection
@@ -230,6 +235,7 @@ const (
actSelectAll actSelectAll
actDeselectAll actDeselectAll
actToggle actToggle
actToggleSearch
actToggleAll actToggleAll
actToggleDown actToggleDown
actToggleUp actToggleUp
@@ -250,6 +256,8 @@ const (
actTogglePreview actTogglePreview
actTogglePreviewWrap actTogglePreviewWrap
actPreview actPreview
actPreviewTop
actPreviewBottom
actPreviewUp actPreviewUp
actPreviewDown actPreviewDown
actPreviewPageUp actPreviewPageUp
@@ -262,8 +270,11 @@ const (
actExecuteSilent actExecuteSilent
actExecuteMulti // Deprecated actExecuteMulti // Deprecated
actSigStop actSigStop
actTop actFirst
actLast
actReload actReload
actDisableSearch
actEnableSearch
) )
type placeholderFlags struct { type placeholderFlags struct {
@@ -300,62 +311,68 @@ func toActions(types ...actionType) []action {
return actions return actions
} }
func defaultKeymap() map[int][]action { func defaultKeymap() map[tui.Event][]action {
keymap := make(map[int][]action) keymap := make(map[tui.Event][]action)
keymap[tui.Invalid] = toActions(actInvalid) add := func(e tui.EventType, a actionType) {
keymap[tui.Resize] = toActions(actClearScreen) keymap[e.AsEvent()] = toActions(a)
keymap[tui.CtrlA] = toActions(actBeginningOfLine) }
keymap[tui.CtrlB] = toActions(actBackwardChar) addEvent := func(e tui.Event, a actionType) {
keymap[tui.CtrlC] = toActions(actAbort) keymap[e] = toActions(a)
keymap[tui.CtrlG] = toActions(actAbort)
keymap[tui.CtrlQ] = toActions(actAbort)
keymap[tui.ESC] = toActions(actAbort)
keymap[tui.CtrlD] = toActions(actDeleteCharEOF)
keymap[tui.CtrlE] = toActions(actEndOfLine)
keymap[tui.CtrlF] = toActions(actForwardChar)
keymap[tui.CtrlH] = toActions(actBackwardDeleteChar)
keymap[tui.BSpace] = toActions(actBackwardDeleteChar)
keymap[tui.Tab] = toActions(actToggleDown)
keymap[tui.BTab] = toActions(actToggleUp)
keymap[tui.CtrlJ] = toActions(actDown)
keymap[tui.CtrlK] = toActions(actUp)
keymap[tui.CtrlL] = toActions(actClearScreen)
keymap[tui.CtrlM] = toActions(actAccept)
keymap[tui.CtrlN] = toActions(actDown)
keymap[tui.CtrlP] = toActions(actUp)
keymap[tui.CtrlU] = toActions(actUnixLineDiscard)
keymap[tui.CtrlW] = toActions(actUnixWordRubout)
keymap[tui.CtrlY] = toActions(actYank)
if !util.IsWindows() {
keymap[tui.CtrlZ] = toActions(actSigStop)
} }
keymap[tui.AltB] = toActions(actBackwardWord) add(tui.Invalid, actInvalid)
keymap[tui.SLeft] = toActions(actBackwardWord) add(tui.Resize, actClearScreen)
keymap[tui.AltF] = toActions(actForwardWord) add(tui.CtrlA, actBeginningOfLine)
keymap[tui.SRight] = toActions(actForwardWord) add(tui.CtrlB, actBackwardChar)
keymap[tui.AltD] = toActions(actKillWord) add(tui.CtrlC, actAbort)
keymap[tui.AltBS] = toActions(actBackwardKillWord) add(tui.CtrlG, actAbort)
add(tui.CtrlQ, actAbort)
add(tui.ESC, actAbort)
add(tui.CtrlD, actDeleteCharEOF)
add(tui.CtrlE, actEndOfLine)
add(tui.CtrlF, actForwardChar)
add(tui.CtrlH, actBackwardDeleteChar)
add(tui.BSpace, actBackwardDeleteChar)
add(tui.Tab, actToggleDown)
add(tui.BTab, actToggleUp)
add(tui.CtrlJ, actDown)
add(tui.CtrlK, actUp)
add(tui.CtrlL, actClearScreen)
add(tui.CtrlM, actAccept)
add(tui.CtrlN, actDown)
add(tui.CtrlP, actUp)
add(tui.CtrlU, actUnixLineDiscard)
add(tui.CtrlW, actUnixWordRubout)
add(tui.CtrlY, actYank)
if !util.IsWindows() {
add(tui.CtrlZ, actSigStop)
}
keymap[tui.Up] = toActions(actUp) addEvent(tui.AltKey('b'), actBackwardWord)
keymap[tui.Down] = toActions(actDown) add(tui.SLeft, actBackwardWord)
keymap[tui.Left] = toActions(actBackwardChar) addEvent(tui.AltKey('f'), actForwardWord)
keymap[tui.Right] = toActions(actForwardChar) add(tui.SRight, actForwardWord)
addEvent(tui.AltKey('d'), actKillWord)
add(tui.AltBS, actBackwardKillWord)
keymap[tui.Home] = toActions(actBeginningOfLine) add(tui.Up, actUp)
keymap[tui.End] = toActions(actEndOfLine) add(tui.Down, actDown)
keymap[tui.Del] = toActions(actDeleteChar) add(tui.Left, actBackwardChar)
keymap[tui.PgUp] = toActions(actPageUp) add(tui.Right, actForwardChar)
keymap[tui.PgDn] = toActions(actPageDown)
keymap[tui.SUp] = toActions(actPreviewUp) add(tui.Home, actBeginningOfLine)
keymap[tui.SDown] = toActions(actPreviewDown) add(tui.End, actEndOfLine)
add(tui.Del, actDeleteChar)
add(tui.PgUp, actPageUp)
add(tui.PgDn, actPageDown)
keymap[tui.Rune] = toActions(actRune) add(tui.SUp, actPreviewUp)
keymap[tui.Mouse] = toActions(actMouse) add(tui.SDown, actPreviewDown)
keymap[tui.DoubleClick] = toActions(actAccept)
keymap[tui.LeftClick] = toActions(actIgnore) add(tui.Mouse, actMouse)
keymap[tui.RightClick] = toActions(actToggle) add(tui.DoubleClick, actAccept)
add(tui.LeftClick, actIgnore)
add(tui.RightClick, actToggle)
return keymap return keymap
} }
@@ -472,9 +489,11 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
printQuery: opts.PrintQuery, printQuery: opts.PrintQuery,
history: opts.History, history: opts.History,
margin: opts.Margin, margin: opts.Margin,
padding: opts.Padding,
unicode: opts.Unicode, unicode: opts.Unicode,
borderShape: opts.BorderShape, borderShape: opts.BorderShape,
cleanExit: opts.ClearOnExit, cleanExit: opts.ClearOnExit,
paused: opts.Phony,
strong: strongAttr, strong: strongAttr,
cycle: opts.Cycle, cycle: opts.Cycle,
header: header, header: header,
@@ -490,13 +509,14 @@ 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(),
preview: opts.Preview, previewOpts: opts.Preview,
previewer: previewer{0, []string{}, 0, previewBox != nil && !opts.Preview.hidden, false, true, ""}, previewer: previewer{0, []string{}, 0, previewBox != nil && !opts.Preview.hidden, false, true, false, ""},
previewed: previewed{0, 0, 0, false}, previewed: previewed{0, 0, 0, false},
previewBox: previewBox, previewBox: previewBox,
eventBox: eventBox, eventBox: eventBox,
mutex: sync.Mutex{}, mutex: sync.Mutex{},
suppress: true, suppress: true,
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 bool, 1),
@@ -523,10 +543,9 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
// // unless the part has a non-default ANSI state // // unless the part has a non-default ANSI state
loc := whiteSuffix.FindStringIndex(trimmed) loc := whiteSuffix.FindStringIndex(trimmed)
if loc != nil { if loc != nil {
blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear}} blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear, -1}}
if item.colors != nil { if item.colors != nil {
lastColor := (*item.colors)[len(*item.colors)-1] lastColor := (*item.colors)[len(*item.colors)-1]
fmt.Println(lastColor.offset[1], int32(loc[1]))
if lastColor.offset[1] < int32(loc[1]) { if lastColor.offset[1] < int32(loc[1]) {
blankState.offset[0] = lastColor.offset[1] blankState.offset[0] = lastColor.offset[1]
colors := append(*item.colors, blankState) colors := append(*item.colors, blankState)
@@ -551,10 +570,10 @@ func (t *Terminal) noInfoLine() bool {
} }
// Input returns current query string // Input returns current query string
func (t *Terminal) Input() []rune { func (t *Terminal) Input() (bool, []rune) {
t.mutex.Lock() t.mutex.Lock()
defer t.mutex.Unlock() defer t.mutex.Unlock()
return copySlice(t.input) return t.paused, copySlice(t.input)
} }
// UpdateCount updates the count information // UpdateCount updates the count information
@@ -655,8 +674,6 @@ func (t *Terminal) displayWidth(runes []rune) int {
const ( const (
minWidth = 4 minWidth = 4
minHeight = 4 minHeight = 4
maxDisplayWidthCalc = 1024
) )
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 {
@@ -670,61 +687,73 @@ func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int)
func (t *Terminal) resizeWindows() { func (t *Terminal) resizeWindows() {
screenWidth := t.tui.MaxX() screenWidth := t.tui.MaxX()
screenHeight := t.tui.MaxY() screenHeight := t.tui.MaxY()
marginInt := [4]int{} // TRBL
t.prevLines = make([]itemLine, screenHeight) t.prevLines = make([]itemLine, screenHeight)
for idx, sizeSpec := range t.margin {
if sizeSpec.percent { marginInt := [4]int{} // TRBL
paddingInt := [4]int{} // TRBL
sizeSpecToInt := func(index int, spec sizeSpec) int {
if spec.percent {
var max float64 var max float64
if idx%2 == 0 { if index%2 == 0 {
max = float64(screenHeight) max = float64(screenHeight)
} else { } else {
max = float64(screenWidth) max = float64(screenWidth)
} }
marginInt[idx] = int(max * sizeSpec.size * 0.01) return int(max * spec.size * 0.01)
} else {
marginInt[idx] = int(sizeSpec.size)
} }
return int(spec.size)
}
for idx, sizeSpec := range t.padding {
paddingInt[idx] = sizeSpecToInt(idx, sizeSpec)
}
extraMargin := [4]int{} // TRBL
for idx, sizeSpec := range t.margin {
switch t.borderShape { switch t.borderShape {
case tui.BorderHorizontal: case tui.BorderHorizontal:
marginInt[idx] += 1 - idx%2 extraMargin[idx] += 1 - idx%2
case tui.BorderVertical: case tui.BorderVertical:
marginInt[idx] += 2 * (idx % 2) extraMargin[idx] += 2 * (idx % 2)
case tui.BorderTop: case tui.BorderTop:
if idx == 0 { if idx == 0 {
marginInt[idx]++ extraMargin[idx]++
} }
case tui.BorderRight: case tui.BorderRight:
if idx == 1 { if idx == 1 {
marginInt[idx] += 2 extraMargin[idx] += 2
} }
case tui.BorderBottom: case tui.BorderBottom:
if idx == 2 { if idx == 2 {
marginInt[idx]++ extraMargin[idx]++
} }
case tui.BorderLeft: case tui.BorderLeft:
if idx == 3 { if idx == 3 {
marginInt[idx] += 2 extraMargin[idx] += 2
} }
case tui.BorderRounded, tui.BorderSharp: case tui.BorderRounded, tui.BorderSharp:
marginInt[idx] += 1 + idx%2 extraMargin[idx] += 1 + idx%2
} }
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
} }
adjust := func(idx1 int, idx2 int, max int, min int) { adjust := func(idx1 int, idx2 int, max int, min int) {
if max >= min { if max >= min {
margin := marginInt[idx1] + marginInt[idx2] margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
if max-margin < min { if max-margin < min {
desired := max - min desired := max - min
marginInt[idx1] = desired * marginInt[idx1] / margin paddingInt[idx1] = desired * paddingInt[idx1] / margin
marginInt[idx2] = desired * marginInt[idx2] / margin paddingInt[idx2] = desired * paddingInt[idx2] / margin
marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin)
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
} }
} }
} }
previewVisible := t.isPreviewEnabled() && t.preview.size.size > 0 previewVisible := t.isPreviewEnabled() && t.previewOpts.size.size > 0
minAreaWidth := minWidth minAreaWidth := minWidth
minAreaHeight := minHeight minAreaHeight := minHeight
if previewVisible { if previewVisible {
switch t.preview.position { switch t.previewOpts.position {
case posUp, posDown: case posUp, posDown:
minAreaHeight *= 2 minAreaHeight *= 2
case posLeft, posRight: case posLeft, posRight:
@@ -780,13 +809,21 @@ func (t *Terminal) resizeWindows() {
marginInt[0]-1, marginInt[3]-2, width+4, height+2, marginInt[0]-1, marginInt[3]-2, width+4, height+2,
false, tui.MakeBorderStyle(t.borderShape, t.unicode)) false, tui.MakeBorderStyle(t.borderShape, t.unicode))
} }
// Add padding
for idx, val := range paddingInt {
marginInt[idx] += val
}
width = screenWidth - marginInt[1] - marginInt[3]
height = screenHeight - marginInt[0] - marginInt[2]
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if previewVisible { if previewVisible {
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
if t.preview.border != tui.BorderNone { if t.previewOpts.border != tui.BorderNone {
previewBorder := tui.MakeBorderStyle(t.preview.border, t.unicode) previewBorder := tui.MakeBorderStyle(t.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)
pwidth -= 4 pwidth -= 4
pheight -= 2 pheight -= 2
@@ -802,28 +839,28 @@ func (t *Terminal) resizeWindows() {
} }
verticalPad := 2 verticalPad := 2
minPreviewHeight := 3 minPreviewHeight := 3
if t.preview.border == tui.BorderNone { if t.previewOpts.border == tui.BorderNone {
verticalPad = 0 verticalPad = 0
minPreviewHeight = 1 minPreviewHeight = 1
} }
switch t.preview.position { switch t.previewOpts.position {
case posUp: case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, minPreviewHeight, verticalPad) pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
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: case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, minPreviewHeight, verticalPad) 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: case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5, 4) pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
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: case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5, 4) 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)
@@ -895,8 +932,12 @@ func (t *Terminal) printPrompt() {
t.prompt() t.prompt()
before, after := t.updatePromptOffset() before, after := t.updatePromptOffset()
t.window.CPrint(tui.ColInput, string(before)) color := tui.ColInput
t.window.CPrint(tui.ColInput, string(after)) if t.paused {
color = tui.ColDisabled
}
t.window.CPrint(color, string(before))
t.window.CPrint(color, string(after))
} }
func (t *Terminal) trimMessage(message string, maxWidth int) string { func (t *Terminal) trimMessage(message string, maxWidth int) string {
@@ -1095,13 +1136,16 @@ func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit in
} }
func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) { func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) {
if len(runes) > maxDisplayWidthCalc && len(runes) > width { width = util.Max(0, width)
trimmed := len(runes) - width var trimmed int32
return runes[trimmed:], int32(trimmed) // Assume that each rune takes at least one column on screen
if len(runes) > width {
diff := len(runes) - width
trimmed = int32(diff)
runes = runes[diff:]
} }
currentWidth := t.displayWidth(runes) currentWidth := t.displayWidth(runes)
var trimmed int32
for currentWidth > width && len(runes) > 0 { for currentWidth > width && len(runes) > 0 {
runes = runes[1:] runes = runes[1:]
@@ -1256,6 +1300,10 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
} }
var ansi *ansiState var ansi *ansiState
for _, line := range t.previewer.lines { for _, line := range t.previewer.lines {
var lbg tui.Color = -1
if ansi != nil {
ansi.lbg = -1
}
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
@@ -1265,12 +1313,13 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
prefixWidth := 0 prefixWidth := 0
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool { _, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
trimmed := []rune(str) trimmed := []rune(str)
if !t.preview.wrap { if !t.previewOpts.wrap {
trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X()) trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X())
} }
str, width := t.processTabs(trimmed, prefixWidth) str, width := t.processTabs(trimmed, prefixWidth)
prefixWidth += width prefixWidth += width
if t.theme.Colored && ansi != nil && ansi.colored() { if t.theme.Colored && ansi != nil && ansi.colored() {
lbg = ansi.lbg
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str) fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
} else { } else {
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str) fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
@@ -1287,7 +1336,12 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
if unchanged && lineNo == 0 { if unchanged && lineNo == 0 {
break break
} }
t.pwindow.Fill("\n") if lbg >= 0 {
t.pwindow.CFill(-1, lbg, tui.AttrRegular,
strings.Repeat(" ", t.pwindow.Width()-t.pwindow.X())+"\n")
} else {
t.pwindow.Fill("\n")
}
} }
lineNo++ lineNo++
} }
@@ -1417,10 +1471,9 @@ func (t *Terminal) rubout(pattern string) {
t.input = append(t.input[:t.cx], after...) t.input = append(t.input[:t.cx], after...)
} }
func keyMatch(key int, event tui.Event) bool { func keyMatch(key tui.Event, event tui.Event) bool {
return event.Type == key || return event.Type == key.Type && event.Char == key.Char ||
event.Type == tui.Rune && int(event.Char) == key-tui.AltZ || key.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
} }
func quoteEntryCmd(entry string) string { func quoteEntryCmd(entry string) string {
@@ -1527,7 +1580,7 @@ func atopi(s string) int {
} }
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int { func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
offsetExpr := t.replacePlaceholder(t.preview.scroll, false, "", list) offsetExpr := t.replacePlaceholder(t.previewOpts.scroll, false, "", list)
nums := strings.Split(offsetExpr, "-") nums := strings.Split(offsetExpr, "-")
switch len(nums) { switch len(nums) {
case 0: case 0:
@@ -1838,7 +1891,8 @@ func (t *Terminal) Loop() {
version++ version++
// We don't display preview window if no match // We don't display preview window if no match
if items[0] != nil { if items[0] != nil {
command := t.replacePlaceholder(commandTemplate, false, string(t.Input()), items) _, query := t.Input()
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
initialOffset := 0 initialOffset := 0
cmd := util.ExecCommand(command, true) cmd := util.ExecCommand(command, true)
if pwindow != nil { if pwindow != nil {
@@ -2009,7 +2063,7 @@ func (t *Terminal) Loop() {
if focusedIndex != currentIndex || version != t.version { if focusedIndex != currentIndex || version != t.version {
version = t.version version = t.version
focusedIndex = currentIndex focusedIndex = currentIndex
refreshPreview(t.preview.command) refreshPreview(t.previewOpts.command)
} }
case reqJump: case reqJump:
if t.merger.Length() == 0 { if t.merger.Length() == 0 {
@@ -2021,7 +2075,7 @@ func (t *Terminal) Loop() {
case reqRefresh: case reqRefresh:
t.suppress = false t.suppress = false
case reqReinit: case reqReinit:
t.tui.Resume(t.fullscreen, true) t.tui.Resume(t.fullscreen, t.sigstop)
t.redraw() t.redraw()
case reqRedraw: case reqRedraw:
t.redraw() t.redraw()
@@ -2034,10 +2088,15 @@ func (t *Terminal) Loop() {
}) })
case reqPreviewDisplay: case reqPreviewDisplay:
result := value.(previewResult) result := value.(previewResult)
t.previewer.version = result.version if t.previewer.version != result.version {
t.previewer.version = result.version
t.previewer.following = t.previewOpts.follow
}
t.previewer.lines = result.lines t.previewer.lines = result.lines
t.previewer.spinner = result.spinner t.previewer.spinner = result.spinner
if result.offset >= 0 { if t.previewer.following {
t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height()
} else if result.offset >= 0 {
t.previewer.offset = util.Constrain(result.offset, 0, len(t.previewer.lines)-1) t.previewer.offset = util.Constrain(result.offset, 0, len(t.previewer.lines)-1)
} }
t.printPreview() t.printPreview()
@@ -2091,19 +2150,20 @@ func (t *Terminal) Loop() {
} }
} }
toggle := func() bool { toggle := func() bool {
if t.cy < t.merger.Length() && t.toggleItem(t.merger.Get(t.cy).item) { current := t.currentItem()
if current != nil && t.toggleItem(current) {
req(reqInfo) req(reqInfo)
return true return true
} }
return false return false
} }
scrollPreview := func(amount int) { scrollPreviewTo := func(newOffset int) {
if !t.previewer.scrollable { if !t.previewer.scrollable {
return return
} }
newOffset := t.previewer.offset + amount t.previewer.following = false
numLines := len(t.previewer.lines) numLines := len(t.previewer.lines)
if t.preview.cycle { if t.previewOpts.cycle {
newOffset = (newOffset + numLines) % numLines newOffset = (newOffset + numLines) % numLines
} }
newOffset = util.Constrain(newOffset, 0, numLines-1) newOffset = util.Constrain(newOffset, 0, numLines-1)
@@ -2112,6 +2172,9 @@ func (t *Terminal) Loop() {
req(reqPreviewRefresh) req(reqPreviewRefresh)
} }
} }
scrollPreviewBy := func(amount int) {
scrollPreviewTo(t.previewer.offset + amount)
}
for key, ret := range t.expect { for key, ret := range t.expect {
if keyMatch(key, event) { if keyMatch(key, event) {
t.pressed = ret t.pressed = ret
@@ -2121,16 +2184,20 @@ func (t *Terminal) Loop() {
} }
} }
var doAction func(action, int) bool actionsFor := func(eventType tui.EventType) []action {
doActions := func(actions []action, mapkey int) bool { return t.keymap[eventType.AsEvent()]
}
var doAction func(action) bool
doActions := func(actions []action) bool {
for _, action := range actions { for _, action := range actions {
if !doAction(action, mapkey) { if !doAction(action) {
return false return false
} }
} }
return true return true
} }
doAction = func(a action, mapkey int) bool { doAction = func(a action) bool {
switch a.t { switch a.t {
case actIgnore: case actIgnore:
case actExecute, actExecuteSilent: case actExecute, actExecuteSilent:
@@ -2144,45 +2211,53 @@ func (t *Terminal) Loop() {
if t.hasPreviewer() { if t.hasPreviewer() {
togglePreview(!t.previewer.enabled) togglePreview(!t.previewer.enabled)
if t.previewer.enabled { if t.previewer.enabled {
valid, list := t.buildPlusList(t.preview.command, false) valid, list := t.buildPlusList(t.previewOpts.command, false)
if valid { if valid {
t.cancelPreview() t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, t.previewBox.Set(reqPreviewEnqueue,
previewRequest{t.preview.command, t.pwindow, list}) previewRequest{t.previewOpts.command, t.pwindow, list})
} }
} }
} }
case actTogglePreviewWrap: case actTogglePreviewWrap:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
t.preview.wrap = !t.preview.wrap t.previewOpts.wrap = !t.previewOpts.wrap
req(reqPreviewRefresh) req(reqPreviewRefresh)
} }
case actToggleSort: case actToggleSort:
t.sort = !t.sort t.sort = !t.sort
changed = true changed = true
case actPreviewTop:
if t.hasPreviewWindow() {
scrollPreviewTo(0)
}
case actPreviewBottom:
if t.hasPreviewWindow() {
scrollPreviewTo(len(t.previewer.lines) - t.pwindow.Height())
}
case actPreviewUp: case actPreviewUp:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
scrollPreview(-1) scrollPreviewBy(-1)
} }
case actPreviewDown: case actPreviewDown:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
scrollPreview(1) scrollPreviewBy(1)
} }
case actPreviewPageUp: case actPreviewPageUp:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
scrollPreview(-t.pwindow.Height()) scrollPreviewBy(-t.pwindow.Height())
} }
case actPreviewPageDown: case actPreviewPageDown:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
scrollPreview(t.pwindow.Height()) scrollPreviewBy(t.pwindow.Height())
} }
case actPreviewHalfPageUp: case actPreviewHalfPageUp:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
scrollPreview(-t.pwindow.Height() / 2) scrollPreviewBy(-t.pwindow.Height() / 2)
} }
case actPreviewHalfPageDown: case actPreviewHalfPageDown:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
scrollPreview(t.pwindow.Height() / 2) scrollPreviewBy(t.pwindow.Height() / 2)
} }
case actBeginningOfLine: case actBeginningOfLine:
t.cx = 0 t.cx = 0
@@ -2192,14 +2267,18 @@ func (t *Terminal) Loop() {
} }
case actPrintQuery: case actPrintQuery:
req(reqPrintQuery) req(reqPrintQuery)
case actChangePrompt:
t.prompt, t.promptLen = t.parsePrompt(a.a)
req(reqPrompt)
case actPreview: case actPreview:
togglePreview(true) togglePreview(true)
refreshPreview(a.a) refreshPreview(a.a)
case actRefreshPreview: case actRefreshPreview:
refreshPreview(t.preview.command) refreshPreview(t.previewOpts.command)
case actReplaceQuery: case actReplaceQuery:
if t.cy >= 0 && t.cy < t.merger.Length() { current := t.currentItem()
t.input = t.merger.Get(t.cy).item.text.ToRunes() if current != nil {
t.input = current.text.ToRunes()
t.cx = len(t.input) t.cx = len(t.input)
} }
case actAbort: case actAbort:
@@ -2280,14 +2359,14 @@ func (t *Terminal) Loop() {
} }
case actToggleIn: case actToggleIn:
if t.layout != layoutDefault { if t.layout != layoutDefault {
return doAction(action{t: actToggleUp}, mapkey) return doAction(action{t: actToggleUp})
} }
return doAction(action{t: actToggleDown}, mapkey) return doAction(action{t: actToggleDown})
case actToggleOut: case actToggleOut:
if t.layout != layoutDefault { if t.layout != layoutDefault {
return doAction(action{t: actToggleDown}, mapkey) return doAction(action{t: actToggleDown})
} }
return doAction(action{t: actToggleUp}, mapkey) 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)
@@ -2321,9 +2400,12 @@ func (t *Terminal) Loop() {
t.version++ t.version++
req(reqList, reqInfo) req(reqList, reqInfo)
} }
case actTop: case actFirst:
t.vset(0) t.vset(0)
req(reqList) req(reqList)
case actLast:
t.vset(t.merger.Length() - 1)
req(reqList)
case actUnixLineDiscard: case actUnixLineDiscard:
beof = len(t.input) == 0 beof = len(t.input) == 0
if t.cx > 0 { if t.cx > 0 {
@@ -2395,9 +2477,21 @@ func (t *Terminal) Loop() {
t.input = trimQuery(t.history.next()) t.input = trimQuery(t.history.next())
t.cx = len(t.input) t.cx = len(t.input)
} }
case actToggleSearch:
t.paused = !t.paused
changed = !t.paused
req(reqPrompt)
case actEnableSearch:
t.paused = false
changed = true
req(reqPrompt)
case actDisableSearch:
t.paused = true
req(reqPrompt)
case actSigStop: case actSigStop:
p, err := os.FindProcess(os.Getpid()) p, err := os.FindProcess(os.Getpid())
if err == nil { if err == nil {
t.sigstop = true
t.tui.Clear() t.tui.Clear()
t.tui.Pause(t.fullscreen) t.tui.Pause(t.fullscreen)
notifyStop(p) notifyStop(p)
@@ -2416,7 +2510,7 @@ func (t *Terminal) Loop() {
t.vmove(me.S, true) t.vmove(me.S, true)
req(reqList) req(reqList)
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) { } else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
scrollPreview(-me.S) scrollPreviewBy(-me.S)
} }
} else if t.window.Enclose(my, mx) { } else if t.window.Enclose(my, mx) {
mx -= t.window.Left() mx -= t.window.Left()
@@ -2441,7 +2535,7 @@ func (t *Terminal) Loop() {
// Double-click // Double-click
if my >= min { if my >= min {
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() { if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
return doActions(t.keymap[tui.DoubleClick], tui.DoubleClick) return doActions(actionsFor(tui.DoubleClick))
} }
} }
} else if me.Down { } else if me.Down {
@@ -2455,9 +2549,9 @@ func (t *Terminal) Loop() {
} }
req(reqList) req(reqList)
if me.Left { if me.Left {
return doActions(t.keymap[tui.LeftClick], tui.LeftClick) return doActions(actionsFor(tui.LeftClick))
} }
return doActions(t.keymap[tui.RightClick], tui.RightClick) return doActions(actionsFor(tui.RightClick))
} }
} }
} }
@@ -2479,33 +2573,29 @@ func (t *Terminal) Loop() {
} }
return true return true
} }
mapkey := event.Type
if t.jumping == jumpDisabled { if t.jumping == jumpDisabled {
actions := t.keymap[mapkey] actions := t.keymap[event.Comparable()]
if mapkey == tui.Rune { if len(actions) == 0 && event.Type == tui.Rune {
mapkey = int(event.Char) + int(tui.AltZ) doAction(action{t: actRune})
if act, prs := t.keymap[mapkey]; prs { } else if !doActions(actions) {
actions = act
}
}
if !doActions(actions, mapkey) {
continue continue
} }
t.truncateQuery() t.truncateQuery()
queryChanged = string(previousInput) != string(t.input) queryChanged = string(previousInput) != string(t.input)
changed = changed || queryChanged changed = changed || queryChanged
if onChanges, prs := t.keymap[tui.Change]; queryChanged && prs { if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs {
if !doActions(onChanges, tui.Change) { if !doActions(onChanges) {
continue continue
} }
} }
if onEOFs, prs := t.keymap[tui.BackwardEOF]; beof && prs { if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
if !doActions(onEOFs, tui.BackwardEOF) { if !doActions(onEOFs) {
continue continue
} }
} }
} else { } else {
if mapkey == tui.Rune { if event.Type == tui.Rune {
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() { if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
t.cy = idx + t.offset t.cy = idx + t.offset
if t.jumping == jumpAcceptEnabled { if t.jumping == jumpAcceptEnabled {
@@ -2519,7 +2609,7 @@ func (t *Terminal) Loop() {
if queryChanged { if queryChanged {
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
_, _, q := hasPreviewFlags(t.preview.command) _, _, q := hasPreviewFlags(t.previewOpts.command)
if q { if q {
t.version++ t.version++
} }

View File

@@ -4,7 +4,7 @@
package tui package tui
type Attr int type Attr int32
func HasFullscreenRenderer() bool { func HasFullscreenRenderer() bool {
return false return false

View File

@@ -1,6 +1,7 @@
package tui package tui
import ( import (
"bytes"
"fmt" "fmt"
"os" "os"
"regexp" "regexp"
@@ -230,7 +231,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
} }
retries := 0 retries := 0
if c == ESC || nonblock { if c == ESC.Int() || nonblock {
retries = r.escDelay / escPollInterval retries = r.escDelay / escPollInterval
} }
buffer = append(buffer, byte(c)) buffer = append(buffer, byte(c))
@@ -245,7 +246,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
continue continue
} }
break break
} else if c == ESC && pc != c { } else if c == ESC.Int() && pc != c {
retries = r.escDelay / escPollInterval retries = r.escDelay / escPollInterval
} else { } else {
retries = 0 retries = 0
@@ -278,11 +279,11 @@ func (r *LightRenderer) GetChar() Event {
}() }()
switch r.buffer[0] { switch r.buffer[0] {
case CtrlC: case CtrlC.Byte():
return Event{CtrlC, 0, nil} return Event{CtrlC, 0, nil}
case CtrlG: case CtrlG.Byte():
return Event{CtrlG, 0, nil} return Event{CtrlG, 0, nil}
case CtrlQ: case CtrlQ.Byte():
return Event{CtrlQ, 0, nil} return Event{CtrlQ, 0, nil}
case 127: case 127:
return Event{BSpace, 0, nil} return Event{BSpace, 0, nil}
@@ -296,7 +297,7 @@ func (r *LightRenderer) GetChar() Event {
return Event{CtrlCaret, 0, nil} return Event{CtrlCaret, 0, nil}
case 31: case 31:
return Event{CtrlSlash, 0, nil} return Event{CtrlSlash, 0, nil}
case ESC: case ESC.Byte():
ev := r.escSequence(&sz) ev := r.escSequence(&sz)
// Second chance // Second chance
if ev.Type == Invalid { if ev.Type == Invalid {
@@ -307,8 +308,8 @@ func (r *LightRenderer) GetChar() Event {
} }
// CTRL-A ~ CTRL-Z // CTRL-A ~ CTRL-Z
if r.buffer[0] <= CtrlZ { if r.buffer[0] <= CtrlZ.Byte() {
return Event{int(r.buffer[0]), 0, nil} return Event{EventType(r.buffer[0]), 0, nil}
} }
char, rsz := utf8.DecodeRune(r.buffer) char, rsz := utf8.DecodeRune(r.buffer)
if char == utf8.RuneError { if char == utf8.RuneError {
@@ -331,26 +332,16 @@ func (r *LightRenderer) escSequence(sz *int) Event {
*sz = 2 *sz = 2
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 { if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil} return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
} }
alt := false alt := false
if len(r.buffer) > 2 && r.buffer[1] == ESC { if len(r.buffer) > 2 && r.buffer[1] == ESC.Byte() {
r.buffer = r.buffer[1:] r.buffer = r.buffer[1:]
alt = true alt = true
} }
switch r.buffer[1] { switch r.buffer[1] {
case ESC: case ESC.Byte():
return Event{ESC, 0, nil} return Event{ESC, 0, nil}
case ' ':
return Event{AltSpace, 0, nil}
case '/':
return Event{AltSlash, 0, nil}
case 'b':
return Event{AltB, 0, nil}
case 'd':
return Event{AltD, 0, nil}
case 'f':
return Event{AltF, 0, nil}
case 127: case 127:
return Event{AltBS, 0, nil} return Event{AltBS, 0, nil}
case '[', 'O': case '[', 'O':
@@ -463,20 +454,54 @@ func (r *LightRenderer) escSequence(sz *int) Event {
} }
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
case ';': case ';':
if len(r.buffer) != 6 { if len(r.buffer) < 6 {
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
} }
*sz = 6 *sz = 6
switch r.buffer[4] { switch r.buffer[4] {
case '2', '5': case '1', '2', '3', '5':
switch r.buffer[5] { alt := r.buffer[4] == '3'
altShift := r.buffer[4] == '1' && r.buffer[5] == '0'
char := r.buffer[5]
if altShift {
if len(r.buffer) < 7 {
return Event{Invalid, 0, nil}
}
*sz = 7
char = r.buffer[6]
}
switch char {
case 'A': case 'A':
if alt {
return Event{AltUp, 0, nil}
}
if altShift {
return Event{AltSUp, 0, nil}
}
return Event{SUp, 0, nil} return Event{SUp, 0, nil}
case 'B': case 'B':
if alt {
return Event{AltDown, 0, nil}
}
if altShift {
return Event{AltSDown, 0, nil}
}
return Event{SDown, 0, nil} return Event{SDown, 0, nil}
case 'C': case 'C':
if alt {
return Event{AltRight, 0, nil}
}
if altShift {
return Event{AltSRight, 0, nil}
}
return Event{SRight, 0, nil} return Event{SRight, 0, nil}
case 'D': case 'D':
if alt {
return Event{AltLeft, 0, nil}
}
if altShift {
return Event{AltSLeft, 0, nil}
}
return Event{SLeft, 0, nil} return Event{SLeft, 0, nil}
} }
} // r.buffer[4] } // r.buffer[4]
@@ -484,11 +509,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
} // r.buffer[2] } // r.buffer[2]
} // r.buffer[2] } // r.buffer[2]
} // r.buffer[1] } // r.buffer[1]
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' { rest := bytes.NewBuffer(r.buffer[1:])
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil} c, size, err := rest.ReadRune()
} if err == nil {
if r.buffer[1] >= '0' && r.buffer[1] <= '9' { *sz = 1 + size
return Event{Alt0 + int(r.buffer[1]) - '0', 0, nil} return AltKey(c)
} }
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
} }

View File

@@ -222,66 +222,69 @@ func (r *FullscreenRenderer) GetChar() Event {
// process keyboard: // process keyboard:
case *tcell.EventKey: case *tcell.EventKey:
alt := (ev.Modifiers() & tcell.ModAlt) > 0 mods := ev.Modifiers()
keyfn := func(r rune) int { alt := (mods & tcell.ModAlt) > 0
shift := (mods & tcell.ModShift) > 0
altShift := alt && shift
keyfn := func(r rune) Event {
if alt { if alt {
return CtrlAltA - 'a' + int(r) return CtrlAltKey(r)
} }
return CtrlA - 'a' + int(r) return EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()
} }
switch ev.Key() { switch ev.Key() {
case tcell.KeyCtrlA: case tcell.KeyCtrlA:
return Event{keyfn('a'), 0, nil} return keyfn('a')
case tcell.KeyCtrlB: case tcell.KeyCtrlB:
return Event{keyfn('b'), 0, nil} return keyfn('b')
case tcell.KeyCtrlC: case tcell.KeyCtrlC:
return Event{keyfn('c'), 0, nil} return keyfn('c')
case tcell.KeyCtrlD: case tcell.KeyCtrlD:
return Event{keyfn('d'), 0, nil} return keyfn('d')
case tcell.KeyCtrlE: case tcell.KeyCtrlE:
return Event{keyfn('e'), 0, nil} return keyfn('e')
case tcell.KeyCtrlF: case tcell.KeyCtrlF:
return Event{keyfn('f'), 0, nil} return keyfn('f')
case tcell.KeyCtrlG: case tcell.KeyCtrlG:
return Event{keyfn('g'), 0, nil} return keyfn('g')
case tcell.KeyCtrlH: case tcell.KeyCtrlH:
return Event{keyfn('h'), 0, nil} return keyfn('h')
case tcell.KeyCtrlI: case tcell.KeyCtrlI:
return Event{keyfn('i'), 0, nil} return keyfn('i')
case tcell.KeyCtrlJ: case tcell.KeyCtrlJ:
return Event{keyfn('j'), 0, nil} return keyfn('j')
case tcell.KeyCtrlK: case tcell.KeyCtrlK:
return Event{keyfn('k'), 0, nil} return keyfn('k')
case tcell.KeyCtrlL: case tcell.KeyCtrlL:
return Event{keyfn('l'), 0, nil} return keyfn('l')
case tcell.KeyCtrlM: case tcell.KeyCtrlM:
return Event{keyfn('m'), 0, nil} return keyfn('m')
case tcell.KeyCtrlN: case tcell.KeyCtrlN:
return Event{keyfn('n'), 0, nil} return keyfn('n')
case tcell.KeyCtrlO: case tcell.KeyCtrlO:
return Event{keyfn('o'), 0, nil} return keyfn('o')
case tcell.KeyCtrlP: case tcell.KeyCtrlP:
return Event{keyfn('p'), 0, nil} return keyfn('p')
case tcell.KeyCtrlQ: case tcell.KeyCtrlQ:
return Event{keyfn('q'), 0, nil} return keyfn('q')
case tcell.KeyCtrlR: case tcell.KeyCtrlR:
return Event{keyfn('r'), 0, nil} return keyfn('r')
case tcell.KeyCtrlS: case tcell.KeyCtrlS:
return Event{keyfn('s'), 0, nil} return keyfn('s')
case tcell.KeyCtrlT: case tcell.KeyCtrlT:
return Event{keyfn('t'), 0, nil} return keyfn('t')
case tcell.KeyCtrlU: case tcell.KeyCtrlU:
return Event{keyfn('u'), 0, nil} return keyfn('u')
case tcell.KeyCtrlV: case tcell.KeyCtrlV:
return Event{keyfn('v'), 0, nil} return keyfn('v')
case tcell.KeyCtrlW: case tcell.KeyCtrlW:
return Event{keyfn('w'), 0, nil} return keyfn('w')
case tcell.KeyCtrlX: case tcell.KeyCtrlX:
return Event{keyfn('x'), 0, nil} return keyfn('x')
case tcell.KeyCtrlY: case tcell.KeyCtrlY:
return Event{keyfn('y'), 0, nil} return keyfn('y')
case tcell.KeyCtrlZ: case tcell.KeyCtrlZ:
return Event{keyfn('z'), 0, nil} return keyfn('z')
case tcell.KeyCtrlSpace: case tcell.KeyCtrlSpace:
return Event{CtrlSpace, 0, nil} return Event{CtrlSpace, 0, nil}
case tcell.KeyCtrlBackslash: case tcell.KeyCtrlBackslash:
@@ -297,21 +300,45 @@ func (r *FullscreenRenderer) GetChar() Event {
return Event{BSpace, 0, nil} return Event{BSpace, 0, nil}
case tcell.KeyUp: case tcell.KeyUp:
if altShift {
return Event{AltSUp, 0, nil}
}
if shift {
return Event{SUp, 0, nil}
}
if alt { if alt {
return Event{AltUp, 0, nil} return Event{AltUp, 0, nil}
} }
return Event{Up, 0, nil} return Event{Up, 0, nil}
case tcell.KeyDown: case tcell.KeyDown:
if altShift {
return Event{AltSDown, 0, nil}
}
if shift {
return Event{SDown, 0, nil}
}
if alt { if alt {
return Event{AltDown, 0, nil} return Event{AltDown, 0, nil}
} }
return Event{Down, 0, nil} return Event{Down, 0, nil}
case tcell.KeyLeft: case tcell.KeyLeft:
if altShift {
return Event{AltSLeft, 0, nil}
}
if shift {
return Event{SLeft, 0, nil}
}
if alt { if alt {
return Event{AltLeft, 0, nil} return Event{AltLeft, 0, nil}
} }
return Event{Left, 0, nil} return Event{Left, 0, nil}
case tcell.KeyRight: case tcell.KeyRight:
if altShift {
return Event{AltSRight, 0, nil}
}
if shift {
return Event{SRight, 0, nil}
}
if alt { if alt {
return Event{AltRight, 0, nil} return Event{AltRight, 0, nil}
} }
@@ -362,18 +389,7 @@ func (r *FullscreenRenderer) GetChar() Event {
case tcell.KeyRune: case tcell.KeyRune:
r := ev.Rune() r := ev.Rune()
if alt { if alt {
switch r { return AltKey(r)
case ' ':
return Event{AltSpace, 0, nil}
case '/':
return Event{AltSlash, 0, nil}
}
if r >= 'a' && r <= 'z' {
return Event{AltA + int(r) - 'a', 0, nil}
}
if r >= '0' && r <= '9' {
return Event{Alt0 + int(r) - '0', 0, nil}
}
} }
return Event{Rune, r, nil} return Event{Rune, r, nil}

View File

@@ -8,8 +8,10 @@ import (
) )
// Types of user action // Types of user action
type EventType int
const ( const (
Rune = iota Rune EventType = iota
CtrlA CtrlA
CtrlB CtrlB
@@ -89,8 +91,6 @@ const (
Change Change
BackwardEOF BackwardEOF
AltSpace
AltSlash
AltBS AltBS
AltUp AltUp
@@ -98,20 +98,43 @@ const (
AltLeft AltLeft
AltRight AltRight
Alt0 AltSUp
AltSDown
AltSLeft
AltSRight
Alt
CtrlAlt
) )
const ( // Reset iota func (t EventType) AsEvent() Event {
AltA = Alt0 + 'a' - '0' + iota return Event{t, 0, nil}
AltB }
AltC
AltD func (t EventType) Int() int {
AltE return int(t)
AltF }
AltZ = AltA + 'z' - 'a'
CtrlAltA = AltZ + 1 func (t EventType) Byte() byte {
CtrlAltM = CtrlAltA + 'm' - 'a' return byte(t)
) }
func (e Event) Comparable() Event {
// Ignore MouseEvent pointer
return Event{e.Type, e.Char, nil}
}
func Key(r rune) Event {
return Event{Rune, r, nil}
}
func AltKey(r rune) Event {
return Event{Alt, r, nil}
}
func CtrlAltKey(r rune) Event {
return Event{CtrlAlt, r, nil}
}
const ( const (
doubleClickDuration = 500 * time.Millisecond doubleClickDuration = 500 * time.Millisecond
@@ -119,6 +142,10 @@ const (
type Color int32 type Color int32
func (c Color) IsDefault() bool {
return c == colDefault
}
func (c Color) is24() bool { func (c Color) is24() bool {
return c > 0 && (c&(1<<24)) > 0 return c > 0 && (c&(1<<24)) > 0
} }
@@ -185,6 +212,11 @@ func (p ColorPair) Attr() Attr {
return p.attr return p.attr
} }
func (p ColorPair) HasBg() bool {
return p.attr&Reverse == 0 && p.bg != colDefault ||
p.attr&Reverse > 0 && p.fg != colDefault
}
func (p ColorPair) merge(other ColorPair, except Color) ColorPair { func (p ColorPair) merge(other ColorPair, except Color) ColorPair {
dup := p dup := p
dup.attr = dup.attr.Merge(other.attr) dup.attr = dup.attr.Merge(other.attr)
@@ -218,6 +250,7 @@ func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
type ColorTheme struct { type ColorTheme struct {
Colored bool Colored bool
Input ColorAttr Input ColorAttr
Disabled ColorAttr
Fg ColorAttr Fg ColorAttr
Bg ColorAttr Bg ColorAttr
PreviewFg ColorAttr PreviewFg ColorAttr
@@ -237,7 +270,7 @@ type ColorTheme struct {
} }
type Event struct { type Event struct {
Type int Type EventType
Char rune Char rune
MouseEvent *MouseEvent MouseEvent *MouseEvent
} }
@@ -389,6 +422,7 @@ var (
ColPrompt ColorPair ColPrompt ColorPair
ColNormal ColorPair ColNormal ColorPair
ColInput ColorPair ColInput ColorPair
ColDisabled ColorPair
ColMatch ColorPair ColMatch ColorPair
ColCursor ColorPair ColCursor ColorPair
ColCursorEmpty ColorPair ColCursorEmpty ColorPair
@@ -411,6 +445,7 @@ func EmptyTheme() *ColorTheme {
return &ColorTheme{ return &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colUndefined, AttrUndefined}, Input: ColorAttr{colUndefined, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined}, Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined}, Bg: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined}, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -433,6 +468,7 @@ func NoColorTheme() *ColorTheme {
return &ColorTheme{ return &ColorTheme{
Colored: false, Colored: false,
Input: ColorAttr{colDefault, AttrRegular}, Input: ColorAttr{colDefault, AttrRegular},
Disabled: ColorAttr{colDefault, AttrRegular},
Fg: ColorAttr{colDefault, AttrRegular}, Fg: ColorAttr{colDefault, AttrRegular},
Bg: ColorAttr{colDefault, AttrRegular}, Bg: ColorAttr{colDefault, AttrRegular},
PreviewFg: ColorAttr{colDefault, AttrRegular}, PreviewFg: ColorAttr{colDefault, AttrRegular},
@@ -460,6 +496,7 @@ func init() {
Default16 = &ColorTheme{ Default16 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined}, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -479,6 +516,7 @@ func init() {
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined}, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -498,6 +536,7 @@ func init() {
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined}, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -532,6 +571,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
return c return c
} }
theme.Input = o(baseTheme.Input, theme.Input) theme.Input = o(baseTheme.Input, theme.Input)
theme.Disabled = o(theme.Input, o(baseTheme.Disabled, theme.Disabled))
theme.Fg = o(baseTheme.Fg, theme.Fg) theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg) theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg)) theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
@@ -565,6 +605,7 @@ func initPalette(theme *ColorTheme) {
ColPrompt = pair(theme.Prompt, theme.Bg) ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg) ColNormal = pair(theme.Fg, theme.Bg)
ColInput = pair(theme.Input, theme.Bg) ColInput = pair(theme.Input, theme.Bg)
ColDisabled = pair(theme.Disabled, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg) ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter) ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter) ColCursorEmpty = pair(blank, theme.Gutter)

View File

@@ -1554,8 +1554,8 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal '> 1', lines[-2] } tmux.until { |lines| assert_equal '> 1', lines[-2] }
end end
def test_change_top def test_change_first_last
tmux.send_keys %(seq 1000 | #{FZF} --bind change:top), :Enter tmux.send_keys %(seq 1000 | #{FZF} --bind change:first,alt-Z:last), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count } tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys :Up tmux.send_keys :Up
tmux.until { |lines| assert_equal '> 2', lines[-4] } tmux.until { |lines| assert_equal '> 2', lines[-4] }
@@ -1565,6 +1565,10 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal '> 10', lines[-4] } tmux.until { |lines| assert_equal '> 10', lines[-4] }
tmux.send_keys 1 tmux.send_keys 1
tmux.until { |lines| assert_equal '> 11', lines[-3] } tmux.until { |lines| assert_equal '> 11', lines[-3] }
tmux.send_keys 'C-u'
tmux.until { |lines| assert_equal '> 1', lines[-3] }
tmux.send_keys :Escape, 'Z'
tmux.until { |lines| assert_equal '> 1000', lines[0] }
tmux.send_keys :Enter tmux.send_keys :Enter
end end
@@ -1654,13 +1658,35 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines[1], ' + green ' } tmux.until { |lines| assert_includes lines[1], ' + green ' }
end end
def test_phony def test_disabled
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --phony --preview 'echo {} {q}'), :Enter tmux.send_keys %(seq 1000 | #{FZF} --query 333 --disabled --bind a:enable-search,b:disable-search,c:toggle-search --preview 'echo {} {q}'), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count } tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 1 333 ' } tmux.until { |lines| assert_includes lines[1], ' 1 333 ' }
tmux.send_keys 'foo' tmux.send_keys 'foo'
tmux.until { |lines| assert_equal 1000, lines.match_count } tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 1 333foo ' } tmux.until { |lines| assert_includes lines[1], ' 1 333foo ' }
# Already disabled, no change
tmux.send_keys 'b'
tmux.until { |lines| assert_equal 1000, lines.match_count }
# Enable search
tmux.send_keys 'a'
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.send_keys :BSpace, :BSpace, :BSpace
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 333 333 ' }
# Toggle search -> disabled again, but retains the previous result
tmux.send_keys 'c'
tmux.send_keys 'foo'
tmux.until { |lines| assert_includes lines[1], ' 333 333foo ' }
tmux.until { |lines| assert_equal 1, lines.match_count }
# Enabled, no match
tmux.send_keys 'c'
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 333foo ' }
end end
def test_reload def test_reload
@@ -1823,6 +1849,20 @@ class TestGoFZF < TestBase
tmux.until { |lines| lines.item_count == 100 } tmux.until { |lines| lines.item_count == 100 }
tmux.until { |lines| lines[1]&.include?('[200]') } tmux.until { |lines| lines[1]&.include?('[200]') }
end end
def test_change_prompt
tmux.send_keys "#{FZF} --bind 'a:change-prompt(a> ),b:change-prompt:b> ' --query foo", :Enter
tmux.until { |lines| assert_equal '> foo', lines[-1] }
tmux.send_keys 'a'
tmux.until { |lines| assert_equal 'a> foo', lines[-1] }
tmux.send_keys 'b'
tmux.until { |lines| assert_equal 'b> foo', lines[-1] }
end
def test_preview_window_follow
tmux.send_keys "#{FZF} --preview 'seq 1000 | nl' --preview-window down:noborder:follow", :Enter
tmux.until { |lines| assert_equal '1000 1000', lines[-1].strip }
end
end end
module TestShell module TestShell