mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-29 03:13:50 -07:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
00a3610331 | ||
|
f502725120 | ||
|
b62a74b315 | ||
|
2ec382ae0e | ||
|
cbfee31593 | ||
|
6d647e13ff | ||
|
d2af3ff98d | ||
|
052d17e66a | ||
|
a9bc954e17 | ||
|
2983426771 | ||
|
c61eb94b3f | ||
|
3829eab1cf | ||
|
3fe8eeedc5 | ||
|
1efef88b6e | ||
|
7acdaf0b43 | ||
|
1ed25d76ba | ||
|
474c1f5e32 | ||
|
8b71fea5dc | ||
|
7bd99a22ee | ||
|
75b8cca3b3 | ||
|
ef2c29d5d4 | ||
|
218b3c8274 | ||
|
db9cb2ddda | ||
|
722d66e85a | ||
|
f6269f0193 | ||
|
520eae817a | ||
|
d099941360 | ||
|
e3e76fa8c5 | ||
|
2553806e79 | ||
|
1bcbc5a353 | ||
|
15d351b0f0 | ||
|
c144c95cda | ||
|
f08f4fd87d | ||
|
f8aaeef218 | ||
|
7915e365b3 | ||
|
1c68f81c37 |
@@ -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
|
||||||
|
29
CHANGELOG.md
29
CHANGELOG.md
@@ -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
|
||||||
|
2
install
2
install
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
129
man/man1/fzf.1
129
man/man1/fzf.1
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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" ;;
|
||||||
|
@@ -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=$?
|
||||||
|
12
src/ansi.go
12
src/ansi.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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")
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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})
|
||||||
|
@@ -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)
|
||||||
|
147
src/terminal.go
147
src/terminal.go
@@ -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++
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
type Attr int
|
type Attr int32
|
||||||
|
|
||||||
func HasFullscreenRenderer() bool {
|
func HasFullscreenRenderer() bool {
|
||||||
return false
|
return false
|
||||||
|
@@ -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]
|
||||||
|
@@ -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}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user