Compare commits

...

36 Commits

Author SHA1 Message Date
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
Junegunn Choi
e3e76fa8c5 0.24.2 2020-11-03 23:32:24 +09:00
Junegunn Choi
2553806e79 Allow preview window height shorter than 3
Fix #2231
2020-11-03 22:04:01 +09:00
Junegunn Choi
1bcbc5a353 Fix regression where lines are skipped in the preview window
Fix #2239
2020-11-03 21:31:19 +09:00
Junegunn Choi
15d351b0f0 Use default bg color when fg is set to -1 with reverse attribute 2020-11-03 20:51:44 +09:00
Junegunn Choi
c144c95cda [vim] Set maxwidth and maxheight when creating a popup
For me, this fixes invalid popup size problem on Windows GVim
2020-10-31 22:27:58 +09:00
Junegunn Choi
f08f4fd87d [vim] Remove dead code 2020-10-31 22:21:06 +09:00
Junegunn Choi
f8aaeef218 Revert "Prefer LightRenderer on Windows if it's available"
This reverts commit 7915e365b3
due to https://github.com/junegunn/fzf.vim/issues/1152#issuecomment-719696495.
2020-10-31 02:53:10 +09:00
Junegunn Choi
7915e365b3 Prefer LightRenderer on Windows if it's available
Fix #1766
2020-10-31 01:41:57 +09:00
Junegunn Choi
1c68f81c37 [vim] See the last line of "fzf --version" output
The output may contain some unexpected warning messages from the shell
if it's not properly configured. While such extra messages should be
properly addressed by the user, we can ignore them by checking the
last line of the output instead of the first line.

Related: bd3a021ec1
2020-10-30 21:57:09 +09:00
20 changed files with 422 additions and 139 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,35 @@
CHANGELOG CHANGELOG
========= =========
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
------
- Bug fixes and improvements
0.24.1 0.24.1
------ ------
- Fixed broken `--color=[bw|no]` option - Fixed broken `--color=[bw|no]` option

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.24.1 version=0.24.4
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2

View File

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

View File

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

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Oct 2020" "fzf 0.24.1" "fzf - a command-line fuzzy finder" .TH fzf 1 "Dec 2020" "fzf 0.24.4" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -233,6 +233,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.
@@ -416,7 +439,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 +448,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.
@@ -689,6 +728,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
@@ -730,6 +777,7 @@ 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)
@@ -785,7 +833,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 +878,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

View File

@@ -189,7 +189,7 @@ function! fzf#exec(...)
if v:shell_error || empty(output) if v:shell_error || empty(output)
throw printf('Failed to run "%s": %s', command, output) throw printf('Failed to run "%s": %s', command, output)
endif endif
let fzf_version = matchstr(output[0], '[0-9.]\+') let fzf_version = matchstr(output[-1], '[0-9.]\+')
if s:version_requirement(fzf_version, a:1) if s:version_requirement(fzf_version, a:1)
let s:checked[a:1] = 1 let s:checked[a:1] = 1
return s:exec return s:exec
@@ -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
@@ -835,6 +838,9 @@ function! s:execute_term(dict, command, temps) abort
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
@@ -908,23 +915,16 @@ if has('nvim')
endfunction endfunction
else else
function! s:create_popup(hl, opts) abort function! s:create_popup(hl, opts) abort
let is_frame = has_key(a:opts, 'border')
let s:popup_create = {buf -> popup_create(buf, #{ let s:popup_create = {buf -> popup_create(buf, #{
\ line: a:opts.row, \ line: a:opts.row,
\ col: a:opts.col, \ col: a:opts.col,
\ minwidth: a:opts.width, \ minwidth: a:opts.width,
\ maxwidth: a:opts.width,
\ minheight: a:opts.height, \ minheight: a:opts.height,
\ zindex: 50 - is_frame, \ maxheight: a:opts.height,
\ zindex: 1000,
\ })} \ })}
if is_frame autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))
let id = s:popup_create('')
call setwinvar(id, '&wincolor', a:hl)
call setbufline(winbufnr(id), 1, a:opts.border)
execute 'autocmd BufWipeout * ++once call popup_close('..id..')'
return winbufnr(id)
else
autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))
endif
endfunction endfunction
endif endif

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

@@ -45,6 +45,7 @@ __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
local item
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
echo -n "${(q)item} " echo -n "${(q)item} "
done done
@@ -106,6 +107,7 @@ bindkey '\ec' fzf-cd-widget
fzf-history-widget() { fzf-history-widget() {
local selected num local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
[[ -o sharehistory ]] && fc -RI
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 $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
local ret=$? local ret=$?

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

@@ -60,7 +60,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 +83,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 +169,7 @@ type previewOpts struct {
hidden bool hidden bool
wrap bool wrap bool
cycle bool cycle bool
follow bool
border tui.BorderShape border tui.BorderShape
} }
@@ -221,6 +223,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 +232,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 {
@@ -281,6 +284,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,
@@ -521,6 +525,14 @@ func parseKeyChords(str string, message string) map[int]string {
chord = tui.PgUp chord = tui.PgUp
case "pgdn", "page-down": case "pgdn", "page-down":
chord = tui.PgDn chord = tui.PgDn
case "alt-shift-up", "shift-alt-up":
chord = tui.AltSUp
case "alt-shift-down", "shift-alt-down":
chord = tui.AltSDown
case "alt-shift-left", "shift-alt-left":
chord = tui.AltSLeft
case "alt-shift-right", "shift-alt-right":
chord = tui.AltSRight
case "shift-up": case "shift-up":
chord = tui.SUp chord = tui.SUp
case "shift-down": case "shift-down":
@@ -724,7 +736,7 @@ 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[int][]action, str string) {
@@ -738,6 +750,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' {
@@ -911,6 +925,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 +966,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":
@@ -1064,6 +1082,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 +1098,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 +1121,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()
} }
@@ -1324,6 +1346,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 +1359,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 +1433,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 {

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
} }
@@ -119,6 +120,7 @@ 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
@@ -139,7 +141,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
@@ -213,6 +215,7 @@ const (
actBackwardDeleteCharEOF actBackwardDeleteCharEOF
actBackwardWord actBackwardWord
actCancel actCancel
actChangePrompt
actClearScreen actClearScreen
actClearQuery actClearQuery
actClearSelection actClearSelection
@@ -472,6 +475,7 @@ 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,
@@ -490,8 +494,8 @@ 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,
@@ -523,10 +527,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)
@@ -653,10 +656,8 @@ func (t *Terminal) displayWidth(runes []rune) int {
} }
const ( const (
minWidth = 16 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 +671,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 +793,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
@@ -801,27 +822,29 @@ func (t *Terminal) resizeWindows() {
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder) t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
} }
verticalPad := 2 verticalPad := 2
if t.preview.border == tui.BorderNone { minPreviewHeight := 3
if t.previewOpts.border == tui.BorderNone {
verticalPad = 0 verticalPad = 0
minPreviewHeight = 1
} }
switch t.preview.position { switch t.previewOpts.position {
case posUp: case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3, 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, 3, 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)
@@ -1093,13 +1116,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:]
@@ -1254,6 +1280,11 @@ 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")
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
break break
@@ -1262,12 +1293,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)
@@ -1284,6 +1316,12 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
if unchanged && lineNo == 0 { if unchanged && lineNo == 0 {
break break
} }
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++
} }
@@ -1523,7 +1561,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:
@@ -2005,7 +2043,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 {
@@ -2030,10 +2068,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()
@@ -2097,9 +2140,10 @@ func (t *Terminal) Loop() {
if !t.previewer.scrollable { if !t.previewer.scrollable {
return return
} }
t.previewer.following = false
newOffset := t.previewer.offset + amount newOffset := t.previewer.offset + amount
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)
@@ -2140,17 +2184,17 @@ 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:
@@ -2188,11 +2232,14 @@ 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() { if t.cy >= 0 && t.cy < t.merger.Length() {
t.input = t.merger.Get(t.cy).item.text.ToRunes() t.input = t.merger.Get(t.cy).item.text.ToRunes()
@@ -2515,7 +2562,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

@@ -463,20 +463,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]

View File

@@ -222,7 +222,10 @@ func (r *FullscreenRenderer) GetChar() Event {
// process keyboard: // process keyboard:
case *tcell.EventKey: case *tcell.EventKey:
alt := (ev.Modifiers() & tcell.ModAlt) > 0 mods := ev.Modifiers()
alt := (mods & tcell.ModAlt) > 0
shift := (mods & tcell.ModShift) > 0
altShift := alt && shift
keyfn := func(r rune) int { keyfn := func(r rune) int {
if alt { if alt {
return CtrlAltA - 'a' + int(r) return CtrlAltA - 'a' + int(r)
@@ -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}
} }

View File

@@ -98,6 +98,11 @@ const (
AltLeft AltLeft
AltRight AltRight
AltSUp
AltSDown
AltSLeft
AltSRight
Alt0 Alt0
) )
@@ -119,6 +124,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 +194,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)
@@ -554,6 +568,9 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
func initPalette(theme *ColorTheme) { func initPalette(theme *ColorTheme) {
pair := func(fg, bg ColorAttr) ColorPair { pair := func(fg, bg ColorAttr) ColorPair {
if fg.Color == colDefault && (fg.Attr&Reverse) > 0 {
bg.Color = colDefault
}
return ColorPair{fg.Color, bg.Color, fg.Attr} return ColorPair{fg.Color, bg.Color, fg.Attr}
} }
blank := theme.Fg blank := theme.Fg

View File

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