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

Compare commits

...

21 Commits

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

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

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

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

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

    # Default colors and attributes
    fzf

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

    # Default colors with underline+reverse attributes
    fzf

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

Related: https://github.com/junegunn/fzf.vim/issues/1197#issuecomment-739804363
2020-12-07 19:11:00 +09:00
26 changed files with 628 additions and 466 deletions

View File

@@ -1,6 +1,44 @@
CHANGELOG
=========
0.25.0
------
- Text attributes set in `--color` are not reset when fzf sees another
`--color` option for the same element. This allows you to put custom text
attributes in your `$FZF_DEFAULT_OPTS` and still have those attributes
even when you override the colors.
```sh
# Default colors and attributes
fzf
# Apply custom text attributes
export FZF_DEFAULT_OPTS='--color fg+:italic,hl:-1:underline,hl+:-1:reverse:underline'
fzf
# Different colors but you still have the attributes
fzf --color hl:176,hl+:177
# Write "regular" if you want to clear the attributes
fzf --color hl:176:regular,hl+:177:regular
```
- Renamed `--phony` to `--disabled`
- You can dynamically enable and disable the search functionality using the
new `enable-search`, `disable-search`, and `toggle-search` actions
- You can assign a different color to the query string for when search is disabled
```sh
fzf --color query:#ffffff,disabled:#999999 --bind space:toggle-search
```
- Added `last` action to move the cursor to the last match
- The opposite action `top` is renamed to `first`, but `top` is still
recognized as a synonym for backward compatibility
- Added `preview-top` and `preview-bottom` actions
- Extended support for alt key chords: alt with any case-sensitive single character
```sh
fzf --bind alt-,:first,alt-.:last
```
0.24.4
------
- Added `--preview-window` option `follow`

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

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

View File

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

View File

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

View File

@@ -135,8 +135,8 @@ if [[ -z "$TMUX" ]]; then
exit $?
fi
# --height option is not allowed
args=("${args[@]}" "--no-height")
# --height option is not allowed. CTRL-Z is also disabled.
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
# Handle zoomed tmux pane without popup options by moving it to a temp window
if [[ ! "$opt" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf-tmux 1 "Dec 2020" "fzf 0.24.4" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Jan 2021" "fzf 0.25.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "Dec 2020" "fzf 0.24.4" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Jan 2021" "fzf 0.25.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -71,9 +71,10 @@ Transform the presentation of each line using field index expressions
.BI "-d, --delimiter=" "STR"
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
.TP
.BI "--phony"
.BI "--disabled"
Do not perform search. With this option, fzf becomes a simple selector
interface rather than a "fuzzy finder".
interface rather than a "fuzzy finder". You can later enable the search using
\fBenable-search\fR or `\fBtoggle-search\R action.
.SS Search result
.TP
.B "+s, --no-sort"
@@ -323,6 +324,8 @@ color mappings.
\fBbg+ \fRBackground (current line)
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
\fBhl+ \fRHighlighted substrings (current line)
\fBquery \fRQuery string
\fBdisabled \fRQuery string when search is disabled
\fBinfo \fRInfo line (match counters)
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBprompt \fRPrompt
@@ -666,9 +669,7 @@ e.g.
.br
\fIctrl-alt-[a-z]\fR
.br
\fIalt-[a-z]\fR
.br
\fIalt-[0-9]\fR
\fIalt-[*]\fR (Any case-sensitive single character is allowed)
.br
\fIf[1-12]\fR
.br
@@ -692,8 +693,6 @@ e.g.
.br
\fIalt-bspace\fR (\fIalt-bs\fR)
.br
\fIalt-/\fR
.br
\fItab\fR
.br
\fIbtab\fR (\fIshift-tab\fR)
@@ -750,8 +749,8 @@ or any single character
Triggered whenever the query string is changed
e.g.
\fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed
fzf --bind change:top\fR
\fB# Move cursor to the first entry whenever the query is changed
fzf --bind change:first\fR
.RE
\fIbackward-eof\fR
@@ -784,10 +783,13 @@ A key or an event can be bound to one or more of the following actions.
\fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
\fBdeselect-all\fR (deselect all matches)
\fBdisable-search\fR (disable search functionality)
\fBdown\fR \fIctrl-j ctrl-n down\fR
\fBenable-search\fR (enable search functionality)
\fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details)
\fBexecute-silent(...)\fR (see below for the details)
\fBfirst\fR (move to the first match)
\fBforward-char\fR \fIctrl-f right\fR
\fBforward-word\fR \fIalt-f shift-right\fR
\fBignore\fR
@@ -795,6 +797,7 @@ A key or an event can be bound to one or more of the following actions.
\fBjump-accept\fR (jump and accept)
\fBkill-line\fR
\fBkill-word\fR \fIalt-d\fR
\fBlast\fR (move to the last match)
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
\fBpage-down\fR \fIpgdn\fR
\fBpage-up\fR \fIpgup\fR
@@ -807,6 +810,8 @@ A key or an event can be bound to one or more of the following actions.
\fBpreview-page-up\fR
\fBpreview-half-page-down\fR
\fBpreview-half-page-up\fR
\fBpreview-bottom\fR
\fBpreview-top\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprint-query\fR (print query and exit)
\fBrefresh-preview\fR
@@ -820,9 +825,9 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR
\fBtoggle-preview-wrap\fR
\fBtoggle-search\fR (toggle search functionality)
\fBtoggle-sort\fR
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBtop\fR (move to the top result)
\fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR
\fBup\fR \fIctrl-k ctrl-p up\fR
@@ -903,7 +908,7 @@ e.g.
INITIAL_QUERY="foobar"
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
--ansi --phony --query "$INITIAL_QUERY"\fR
--ansi --disabled --query "$INITIAL_QUERY"\fR
.SS PREVIEW BINDING

View File

@@ -831,7 +831,7 @@ function! s:execute_term(dict, command, temps) abort
if has('nvim')
call termopen(command, fzf)
else
let term_opts = {'exit_cb': function(fzf.on_exit)}
let term_opts = {'exit_cb': function(fzf.on_exit), 'term_kill': 'term'}
if is_popup
let term_opts.hidden = 1
else

View File

@@ -176,7 +176,7 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
printf "%q$3 " "$item"
done)
matches=${matches% }
@@ -232,7 +232,7 @@ _fzf_complete() {
if [[ "$cur" == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}}
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [ -n "$selected" ]; then
COMPREPLY=("$selected")

View File

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

View File

@@ -18,7 +18,7 @@ __fzf_select__() {
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
printf '%q ' "$item"
done
echo
@@ -41,7 +41,7 @@ __fzf_cd__() {
local cmd dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
}
__fzf_history__() {
@@ -49,7 +49,7 @@ __fzf_history__() {
output=$(
builtin fc -lnr -2147483648 |
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
) || return
READLINE_LINE=${output#*$'\t'}
if [ -z "$READLINE_POINT" ]; then

View File

@@ -31,7 +31,7 @@ function fzf_key_bindings
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
end
if [ -z "$result" ]
@@ -51,7 +51,7 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $version | cut -f2 -d.)
@@ -80,7 +80,7 @@ function fzf_key_bindings
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if [ -n "$result" ]

View File

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

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2021 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -3,7 +3,7 @@ Package fzf implements fzf, a command-line fuzzy finder.
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi
Copyright (c) 2013-2021 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -237,14 +237,16 @@ func Run(opts *Options, version string, revision string) {
go reader.restart(command)
}
eventBox.Watch(EvtReadNew)
query := []rune{}
for {
delay := true
ticks++
input := func() []rune {
if opts.Phony {
return []rune{}
paused, input := terminal.Input()
if !paused {
query = input
}
return []rune(terminal.Input())
return query
}
eventBox.Wait(func(events *util.Events) {
if _, fin := (*events)[EvtReadFin]; fin {

View File

@@ -7,7 +7,6 @@ import (
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui"
@@ -34,7 +33,7 @@ const usage = `usage: fzf [options]
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
+s, --no-sort Do not sort the result
--tac Reverse the order of the input
--phony Do not perform search
--disabled Do not perform search
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
when the scores are tied [length|begin|end|index]
(default: length)
@@ -211,8 +210,8 @@ type Options struct {
Exit0 bool
Filter *string
ToggleSort bool
Expect map[int]string
Keymap map[int][]action
Expect map[tui.Event]string
Keymap map[tui.Event][]action
Preview previewOpts
PrintQuery bool
ReadZero bool
@@ -272,8 +271,8 @@ func defaultOptions() *Options {
Exit0: false,
Filter: nil,
ToggleSort: false,
Expect: make(map[int]string),
Keymap: make(map[int][]action),
Expect: make(map[tui.Event]string),
Keymap: make(map[tui.Event][]action),
Preview: defaultPreviewOpts(""),
PrintQuery: false,
ReadZero: false,
@@ -445,134 +444,141 @@ func parseBorder(str string, optional bool) tui.BorderShape {
return tui.BorderNone
}
func parseKeyChords(str string, message string) map[int]string {
func parseKeyChords(str string, message string) map[tui.Event]string {
if len(str) == 0 {
errorExit(message)
}
str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string(escapedComma))
tokens := strings.Split(str, ",")
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
tokens = append(tokens, ",")
}
chords := make(map[int]string)
chords := make(map[tui.Event]string)
for _, key := range tokens {
if len(key) == 0 {
continue // ignore
}
key = strings.ReplaceAll(key, string(escapedComma), ",")
lkey := strings.ToLower(key)
chord := 0
add := func(e tui.EventType) {
chords[e.AsEvent()] = key
}
switch lkey {
case "up":
chord = tui.Up
add(tui.Up)
case "down":
chord = tui.Down
add(tui.Down)
case "left":
chord = tui.Left
add(tui.Left)
case "right":
chord = tui.Right
add(tui.Right)
case "enter", "return":
chord = tui.CtrlM
add(tui.CtrlM)
case "space":
chord = tui.AltZ + int(' ')
chords[tui.Key(' ')] = key
case "bspace", "bs":
chord = tui.BSpace
add(tui.BSpace)
case "ctrl-space":
chord = tui.CtrlSpace
add(tui.CtrlSpace)
case "ctrl-^", "ctrl-6":
chord = tui.CtrlCaret
add(tui.CtrlCaret)
case "ctrl-/", "ctrl-_":
chord = tui.CtrlSlash
add(tui.CtrlSlash)
case "ctrl-\\":
chord = tui.CtrlBackSlash
add(tui.CtrlBackSlash)
case "ctrl-]":
chord = tui.CtrlRightBracket
add(tui.CtrlRightBracket)
case "change":
chord = tui.Change
add(tui.Change)
case "backward-eof":
chord = tui.BackwardEOF
add(tui.BackwardEOF)
case "alt-enter", "alt-return":
chord = tui.CtrlAltM
chords[tui.CtrlAltKey('m')] = key
case "alt-space":
chord = tui.AltSpace
case "alt-/":
chord = tui.AltSlash
chords[tui.AltKey(' ')] = key
case "alt-bs", "alt-bspace":
chord = tui.AltBS
add(tui.AltBS)
case "alt-up":
chord = tui.AltUp
add(tui.AltUp)
case "alt-down":
chord = tui.AltDown
add(tui.AltDown)
case "alt-left":
chord = tui.AltLeft
add(tui.AltLeft)
case "alt-right":
chord = tui.AltRight
add(tui.AltRight)
case "tab":
chord = tui.Tab
add(tui.Tab)
case "btab", "shift-tab":
chord = tui.BTab
add(tui.BTab)
case "esc":
chord = tui.ESC
add(tui.ESC)
case "del":
chord = tui.Del
add(tui.Del)
case "home":
chord = tui.Home
add(tui.Home)
case "end":
chord = tui.End
add(tui.End)
case "insert":
chord = tui.Insert
add(tui.Insert)
case "pgup", "page-up":
chord = tui.PgUp
add(tui.PgUp)
case "pgdn", "page-down":
chord = tui.PgDn
add(tui.PgDn)
case "alt-shift-up", "shift-alt-up":
chord = tui.AltSUp
add(tui.AltSUp)
case "alt-shift-down", "shift-alt-down":
chord = tui.AltSDown
add(tui.AltSDown)
case "alt-shift-left", "shift-alt-left":
chord = tui.AltSLeft
add(tui.AltSLeft)
case "alt-shift-right", "shift-alt-right":
chord = tui.AltSRight
add(tui.AltSRight)
case "shift-up":
chord = tui.SUp
add(tui.SUp)
case "shift-down":
chord = tui.SDown
add(tui.SDown)
case "shift-left":
chord = tui.SLeft
add(tui.SLeft)
case "shift-right":
chord = tui.SRight
add(tui.SRight)
case "left-click":
chord = tui.LeftClick
add(tui.LeftClick)
case "right-click":
chord = tui.RightClick
add(tui.RightClick)
case "double-click":
chord = tui.DoubleClick
add(tui.DoubleClick)
case "f10":
chord = tui.F10
add(tui.F10)
case "f11":
chord = tui.F11
add(tui.F11)
case "f12":
chord = tui.F12
add(tui.F12)
default:
runes := []rune(key)
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
chord = tui.CtrlAltA + int(lkey[9]) - 'a'
chords[tui.CtrlAltKey(rune(key[9]))] = key
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
chord = tui.CtrlA + int(lkey[5]) - 'a'
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
chord = tui.AltA + int(lkey[4]) - 'a'
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isNumeric(lkey[4]) {
chord = tui.Alt0 + int(lkey[4]) - '0'
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
r := runes[4]
switch r {
case escapedColon:
r = ':'
case escapedComma:
r = ','
case escapedPlus:
r = '+'
}
chords[tui.AltKey(r)] = key
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
chord = tui.F1 + int(key[1]) - '1'
} else if utf8.RuneCountInString(key) == 1 {
chord = tui.AltZ + int([]rune(key)[0])
add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
} else if len(runes) == 1 {
chords[tui.Key(runes[0])] = key
} else {
errorExit("unsupported key: " + key)
}
}
if chord > 0 {
chords[chord] = key
}
}
return chords
}
@@ -644,71 +650,74 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
fail()
}
cattr := tui.NewColorAttr()
for _, component := range components[1:] {
switch component {
case "regular":
cattr.Attr = tui.AttrRegular
case "bold", "strong":
cattr.Attr |= tui.Bold
case "dim":
cattr.Attr |= tui.Dim
case "italic":
cattr.Attr |= tui.Italic
case "underline":
cattr.Attr |= tui.Underline
case "blink":
cattr.Attr |= tui.Blink
case "reverse":
cattr.Attr |= tui.Reverse
case "":
default:
if rrggbb.MatchString(component) {
cattr.Color = tui.HexToColor(component)
} else {
ansi32, err := strconv.Atoi(component)
if err != nil || ansi32 < -1 || ansi32 > 255 {
fail()
mergeAttr := func(cattr *tui.ColorAttr) {
for _, component := range components[1:] {
switch component {
case "regular":
cattr.Attr = tui.AttrRegular
case "bold", "strong":
cattr.Attr |= tui.Bold
case "dim":
cattr.Attr |= tui.Dim
case "italic":
cattr.Attr |= tui.Italic
case "underline":
cattr.Attr |= tui.Underline
case "blink":
cattr.Attr |= tui.Blink
case "reverse":
cattr.Attr |= tui.Reverse
case "":
default:
if rrggbb.MatchString(component) {
cattr.Color = tui.HexToColor(component)
} else {
ansi32, err := strconv.Atoi(component)
if err != nil || ansi32 < -1 || ansi32 > 255 {
fail()
}
cattr.Color = tui.Color(ansi32)
}
cattr.Color = tui.Color(ansi32)
}
}
}
switch components[0] {
case "input":
theme.Input = cattr
case "query", "input":
mergeAttr(&theme.Input)
case "disabled":
mergeAttr(&theme.Disabled)
case "fg":
theme.Fg = cattr
mergeAttr(&theme.Fg)
case "bg":
theme.Bg = cattr
mergeAttr(&theme.Bg)
case "preview-fg":
theme.PreviewFg = cattr
mergeAttr(&theme.PreviewFg)
case "preview-bg":
theme.PreviewBg = cattr
mergeAttr(&theme.PreviewBg)
case "fg+":
theme.Current = cattr
mergeAttr(&theme.Current)
case "bg+":
theme.DarkBg = cattr
mergeAttr(&theme.DarkBg)
case "gutter":
theme.Gutter = cattr
mergeAttr(&theme.Gutter)
case "hl":
theme.Match = cattr
mergeAttr(&theme.Match)
case "hl+":
theme.CurrentMatch = cattr
mergeAttr(&theme.CurrentMatch)
case "border":
theme.Border = cattr
mergeAttr(&theme.Border)
case "prompt":
theme.Prompt = cattr
mergeAttr(&theme.Prompt)
case "spinner":
theme.Spinner = cattr
mergeAttr(&theme.Spinner)
case "info":
theme.Info = cattr
mergeAttr(&theme.Info)
case "pointer":
theme.Cursor = cattr
mergeAttr(&theme.Cursor)
case "marker":
theme.Selected = cattr
mergeAttr(&theme.Selected)
case "header":
theme.Header = cattr
mergeAttr(&theme.Header)
default:
fail()
}
@@ -719,11 +728,11 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
var executeRegexp *regexp.Regexp
func firstKey(keymap map[int]string) int {
func firstKey(keymap map[tui.Event]string) tui.Event {
for k := range keymap {
return k
}
return 0
return tui.EventType(0).AsEvent()
}
const (
@@ -739,7 +748,7 @@ func init() {
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
}
func parseKeymap(keymap map[int][]action, str string) {
func parseKeymap(keymap map[tui.Event][]action, str string) {
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
symbol := ":"
if strings.HasPrefix(src, "+") {
@@ -775,13 +784,13 @@ func parseKeymap(keymap map[int][]action, str string) {
if len(pair) < 2 {
errorExit("bind action not specified: " + origPairStr)
}
var key int
var key tui.Event
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
key = ':' + tui.AltZ
key = tui.Key(':')
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
key = ',' + tui.AltZ
key = tui.Key(',')
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
key = '+' + tui.AltZ
key = tui.Key('+')
} else {
keys := parseKeyChords(pair[0], "key name required")
key = firstKey(keys)
@@ -868,6 +877,8 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actToggleOut)
case "toggle-all":
appendAction(actToggleAll)
case "toggle-search":
appendAction(actToggleSearch)
case "select-all":
appendAction(actSelectAll)
case "deselect-all":
@@ -878,8 +889,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actDown)
case "up":
appendAction(actUp)
case "top":
appendAction(actTop)
case "first", "top":
appendAction(actFirst)
case "last":
appendAction(actLast)
case "page-up":
appendAction(actPageUp)
case "page-down":
@@ -898,6 +911,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actTogglePreviewWrap)
case "toggle-sort":
appendAction(actToggleSort)
case "preview-top":
appendAction(actPreviewTop)
case "preview-bottom":
appendAction(actPreviewBottom)
case "preview-up":
appendAction(actPreviewUp)
case "preview-down":
@@ -910,6 +927,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actPreviewHalfPageUp)
case "preview-half-page-down":
appendAction(actPreviewHalfPageDown)
case "enable-search":
appendAction(actEnableSearch)
case "disable-search":
appendAction(actDisableSearch)
default:
t := isExecuteAction(specLower)
if t == actIgnore {
@@ -978,7 +999,7 @@ func isExecuteAction(str string) actionType {
return actIgnore
}
func parseToggleSort(keymap map[int][]action, str string) {
func parseToggleSort(keymap map[tui.Event][]action, str string) {
keys := parseKeyChords(str, "key name required")
if len(keys) != 1 {
errorExit("multiple keys specified")
@@ -1185,10 +1206,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Expect[k] = v
}
case "--no-expect":
opts.Expect = make(map[int]string)
case "--no-phony":
opts.Expect = make(map[tui.Event]string)
case "--enabled", "--no-phony":
opts.Phony = false
case "--phony":
case "--disabled", "--phony":
opts.Phony = true
case "--tiebreak":
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
@@ -1509,11 +1530,11 @@ func postProcessOptions(opts *Options) {
}
// Default actions for CTRL-N / CTRL-P when --history is set
if opts.History != nil {
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
opts.Keymap[tui.CtrlP] = toActions(actPreviousHistory)
if _, prs := opts.Keymap[tui.CtrlP.AsEvent()]; !prs {
opts.Keymap[tui.CtrlP.AsEvent()] = toActions(actPreviousHistory)
}
if _, prs := opts.Keymap[tui.CtrlN]; !prs {
opts.Keymap[tui.CtrlN] = toActions(actNextHistory)
if _, prs := opts.Keymap[tui.CtrlN.AsEvent()]; !prs {
opts.Keymap[tui.CtrlN.AsEvent()] = toActions(actNextHistory)
}
}

View File

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

View File

@@ -109,8 +109,8 @@ type Terminal struct {
sort bool
toggleSort bool
delimiter Delimiter
expect map[int]string
keymap map[int][]action
expect map[tui.Event]string
keymap map[tui.Event][]action
pressed string
printQuery bool
history *History
@@ -125,6 +125,7 @@ type Terminal struct {
unicode bool
borderShape tui.BorderShape
cleanExit bool
paused bool
border tui.Window
window tui.Window
pborder tui.Window
@@ -150,6 +151,7 @@ type Terminal struct {
initFunc func()
prevLines []itemLine
suppress bool
sigstop bool
startChan chan bool
killChan chan int
slab *util.Slab
@@ -233,6 +235,7 @@ const (
actSelectAll
actDeselectAll
actToggle
actToggleSearch
actToggleAll
actToggleDown
actToggleUp
@@ -253,6 +256,8 @@ const (
actTogglePreview
actTogglePreviewWrap
actPreview
actPreviewTop
actPreviewBottom
actPreviewUp
actPreviewDown
actPreviewPageUp
@@ -265,8 +270,11 @@ const (
actExecuteSilent
actExecuteMulti // Deprecated
actSigStop
actTop
actFirst
actLast
actReload
actDisableSearch
actEnableSearch
)
type placeholderFlags struct {
@@ -303,62 +311,68 @@ func toActions(types ...actionType) []action {
return actions
}
func defaultKeymap() map[int][]action {
keymap := make(map[int][]action)
keymap[tui.Invalid] = toActions(actInvalid)
keymap[tui.Resize] = toActions(actClearScreen)
keymap[tui.CtrlA] = toActions(actBeginningOfLine)
keymap[tui.CtrlB] = toActions(actBackwardChar)
keymap[tui.CtrlC] = toActions(actAbort)
keymap[tui.CtrlG] = toActions(actAbort)
keymap[tui.CtrlQ] = toActions(actAbort)
keymap[tui.ESC] = toActions(actAbort)
keymap[tui.CtrlD] = toActions(actDeleteCharEOF)
keymap[tui.CtrlE] = toActions(actEndOfLine)
keymap[tui.CtrlF] = toActions(actForwardChar)
keymap[tui.CtrlH] = toActions(actBackwardDeleteChar)
keymap[tui.BSpace] = toActions(actBackwardDeleteChar)
keymap[tui.Tab] = toActions(actToggleDown)
keymap[tui.BTab] = toActions(actToggleUp)
keymap[tui.CtrlJ] = toActions(actDown)
keymap[tui.CtrlK] = toActions(actUp)
keymap[tui.CtrlL] = toActions(actClearScreen)
keymap[tui.CtrlM] = toActions(actAccept)
keymap[tui.CtrlN] = toActions(actDown)
keymap[tui.CtrlP] = toActions(actUp)
keymap[tui.CtrlU] = toActions(actUnixLineDiscard)
keymap[tui.CtrlW] = toActions(actUnixWordRubout)
keymap[tui.CtrlY] = toActions(actYank)
if !util.IsWindows() {
keymap[tui.CtrlZ] = toActions(actSigStop)
func defaultKeymap() map[tui.Event][]action {
keymap := make(map[tui.Event][]action)
add := func(e tui.EventType, a actionType) {
keymap[e.AsEvent()] = toActions(a)
}
addEvent := func(e tui.Event, a actionType) {
keymap[e] = toActions(a)
}
keymap[tui.AltB] = toActions(actBackwardWord)
keymap[tui.SLeft] = toActions(actBackwardWord)
keymap[tui.AltF] = toActions(actForwardWord)
keymap[tui.SRight] = toActions(actForwardWord)
keymap[tui.AltD] = toActions(actKillWord)
keymap[tui.AltBS] = toActions(actBackwardKillWord)
add(tui.Invalid, actInvalid)
add(tui.Resize, actClearScreen)
add(tui.CtrlA, actBeginningOfLine)
add(tui.CtrlB, actBackwardChar)
add(tui.CtrlC, actAbort)
add(tui.CtrlG, actAbort)
add(tui.CtrlQ, actAbort)
add(tui.ESC, actAbort)
add(tui.CtrlD, actDeleteCharEOF)
add(tui.CtrlE, actEndOfLine)
add(tui.CtrlF, actForwardChar)
add(tui.CtrlH, actBackwardDeleteChar)
add(tui.BSpace, actBackwardDeleteChar)
add(tui.Tab, actToggleDown)
add(tui.BTab, actToggleUp)
add(tui.CtrlJ, actDown)
add(tui.CtrlK, actUp)
add(tui.CtrlL, actClearScreen)
add(tui.CtrlM, actAccept)
add(tui.CtrlN, actDown)
add(tui.CtrlP, actUp)
add(tui.CtrlU, actUnixLineDiscard)
add(tui.CtrlW, actUnixWordRubout)
add(tui.CtrlY, actYank)
if !util.IsWindows() {
add(tui.CtrlZ, actSigStop)
}
keymap[tui.Up] = toActions(actUp)
keymap[tui.Down] = toActions(actDown)
keymap[tui.Left] = toActions(actBackwardChar)
keymap[tui.Right] = toActions(actForwardChar)
addEvent(tui.AltKey('b'), actBackwardWord)
add(tui.SLeft, actBackwardWord)
addEvent(tui.AltKey('f'), actForwardWord)
add(tui.SRight, actForwardWord)
addEvent(tui.AltKey('d'), actKillWord)
add(tui.AltBS, actBackwardKillWord)
keymap[tui.Home] = toActions(actBeginningOfLine)
keymap[tui.End] = toActions(actEndOfLine)
keymap[tui.Del] = toActions(actDeleteChar)
keymap[tui.PgUp] = toActions(actPageUp)
keymap[tui.PgDn] = toActions(actPageDown)
add(tui.Up, actUp)
add(tui.Down, actDown)
add(tui.Left, actBackwardChar)
add(tui.Right, actForwardChar)
keymap[tui.SUp] = toActions(actPreviewUp)
keymap[tui.SDown] = toActions(actPreviewDown)
add(tui.Home, actBeginningOfLine)
add(tui.End, actEndOfLine)
add(tui.Del, actDeleteChar)
add(tui.PgUp, actPageUp)
add(tui.PgDn, actPageDown)
keymap[tui.Rune] = toActions(actRune)
keymap[tui.Mouse] = toActions(actMouse)
keymap[tui.DoubleClick] = toActions(actAccept)
keymap[tui.LeftClick] = toActions(actIgnore)
keymap[tui.RightClick] = toActions(actToggle)
add(tui.SUp, actPreviewUp)
add(tui.SDown, actPreviewDown)
add(tui.Mouse, actMouse)
add(tui.DoubleClick, actAccept)
add(tui.LeftClick, actIgnore)
add(tui.RightClick, actToggle)
return keymap
}
@@ -479,6 +493,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
unicode: opts.Unicode,
borderShape: opts.BorderShape,
cleanExit: opts.ClearOnExit,
paused: opts.Phony,
strong: strongAttr,
cycle: opts.Cycle,
header: header,
@@ -501,6 +516,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
eventBox: eventBox,
mutex: sync.Mutex{},
suppress: true,
sigstop: false,
slab: util.MakeSlab(slab16Size, slab32Size),
theme: opts.Theme,
startChan: make(chan bool, 1),
@@ -554,10 +570,10 @@ func (t *Terminal) noInfoLine() bool {
}
// Input returns current query string
func (t *Terminal) Input() []rune {
func (t *Terminal) Input() (bool, []rune) {
t.mutex.Lock()
defer t.mutex.Unlock()
return copySlice(t.input)
return t.paused, copySlice(t.input)
}
// UpdateCount updates the count information
@@ -916,8 +932,12 @@ func (t *Terminal) printPrompt() {
t.prompt()
before, after := t.updatePromptOffset()
t.window.CPrint(tui.ColInput, string(before))
t.window.CPrint(tui.ColInput, string(after))
color := tui.ColInput
if t.paused {
color = tui.ColDisabled
}
t.window.CPrint(color, string(before))
t.window.CPrint(color, string(after))
}
func (t *Terminal) trimMessage(message string, maxWidth int) string {
@@ -1451,10 +1471,9 @@ func (t *Terminal) rubout(pattern string) {
t.input = append(t.input[:t.cx], after...)
}
func keyMatch(key int, event tui.Event) bool {
return event.Type == key ||
event.Type == tui.Rune && int(event.Char) == key-tui.AltZ ||
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
func keyMatch(key tui.Event, event tui.Event) bool {
return event.Type == key.Type && event.Char == key.Char ||
key.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double
}
func quoteEntryCmd(entry string) string {
@@ -1872,7 +1891,8 @@ func (t *Terminal) Loop() {
version++
// We don't display preview window if no match
if items[0] != nil {
command := t.replacePlaceholder(commandTemplate, false, string(t.Input()), items)
_, query := t.Input()
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
initialOffset := 0
cmd := util.ExecCommand(command, true)
if pwindow != nil {
@@ -2055,7 +2075,7 @@ func (t *Terminal) Loop() {
case reqRefresh:
t.suppress = false
case reqReinit:
t.tui.Resume(t.fullscreen, true)
t.tui.Resume(t.fullscreen, t.sigstop)
t.redraw()
case reqRedraw:
t.redraw()
@@ -2130,18 +2150,18 @@ func (t *Terminal) Loop() {
}
}
toggle := func() bool {
if t.cy < t.merger.Length() && t.toggleItem(t.merger.Get(t.cy).item) {
current := t.currentItem()
if current != nil && t.toggleItem(current) {
req(reqInfo)
return true
}
return false
}
scrollPreview := func(amount int) {
scrollPreviewTo := func(newOffset int) {
if !t.previewer.scrollable {
return
}
t.previewer.following = false
newOffset := t.previewer.offset + amount
numLines := len(t.previewer.lines)
if t.previewOpts.cycle {
newOffset = (newOffset + numLines) % numLines
@@ -2152,6 +2172,9 @@ func (t *Terminal) Loop() {
req(reqPreviewRefresh)
}
}
scrollPreviewBy := func(amount int) {
scrollPreviewTo(t.previewer.offset + amount)
}
for key, ret := range t.expect {
if keyMatch(key, event) {
t.pressed = ret
@@ -2161,16 +2184,20 @@ func (t *Terminal) Loop() {
}
}
var doAction func(action, int) bool
doActions := func(actions []action, mapkey int) bool {
actionsFor := func(eventType tui.EventType) []action {
return t.keymap[eventType.AsEvent()]
}
var doAction func(action) bool
doActions := func(actions []action) bool {
for _, action := range actions {
if !doAction(action, mapkey) {
if !doAction(action) {
return false
}
}
return true
}
doAction = func(a action, mapkey int) bool {
doAction = func(a action) bool {
switch a.t {
case actIgnore:
case actExecute, actExecuteSilent:
@@ -2200,29 +2227,37 @@ func (t *Terminal) Loop() {
case actToggleSort:
t.sort = !t.sort
changed = true
case actPreviewTop:
if t.hasPreviewWindow() {
scrollPreviewTo(0)
}
case actPreviewBottom:
if t.hasPreviewWindow() {
scrollPreviewTo(len(t.previewer.lines) - t.pwindow.Height())
}
case actPreviewUp:
if t.hasPreviewWindow() {
scrollPreview(-1)
scrollPreviewBy(-1)
}
case actPreviewDown:
if t.hasPreviewWindow() {
scrollPreview(1)
scrollPreviewBy(1)
}
case actPreviewPageUp:
if t.hasPreviewWindow() {
scrollPreview(-t.pwindow.Height())
scrollPreviewBy(-t.pwindow.Height())
}
case actPreviewPageDown:
if t.hasPreviewWindow() {
scrollPreview(t.pwindow.Height())
scrollPreviewBy(t.pwindow.Height())
}
case actPreviewHalfPageUp:
if t.hasPreviewWindow() {
scrollPreview(-t.pwindow.Height() / 2)
scrollPreviewBy(-t.pwindow.Height() / 2)
}
case actPreviewHalfPageDown:
if t.hasPreviewWindow() {
scrollPreview(t.pwindow.Height() / 2)
scrollPreviewBy(t.pwindow.Height() / 2)
}
case actBeginningOfLine:
t.cx = 0
@@ -2241,8 +2276,9 @@ func (t *Terminal) Loop() {
case actRefreshPreview:
refreshPreview(t.previewOpts.command)
case actReplaceQuery:
if t.cy >= 0 && t.cy < t.merger.Length() {
t.input = t.merger.Get(t.cy).item.text.ToRunes()
current := t.currentItem()
if current != nil {
t.input = current.text.ToRunes()
t.cx = len(t.input)
}
case actAbort:
@@ -2323,14 +2359,14 @@ func (t *Terminal) Loop() {
}
case actToggleIn:
if t.layout != layoutDefault {
return doAction(action{t: actToggleUp}, mapkey)
return doAction(action{t: actToggleUp})
}
return doAction(action{t: actToggleDown}, mapkey)
return doAction(action{t: actToggleDown})
case actToggleOut:
if t.layout != layoutDefault {
return doAction(action{t: actToggleDown}, mapkey)
return doAction(action{t: actToggleDown})
}
return doAction(action{t: actToggleUp}, mapkey)
return doAction(action{t: actToggleUp})
case actToggleDown:
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
t.vmove(-1, true)
@@ -2364,9 +2400,12 @@ func (t *Terminal) Loop() {
t.version++
req(reqList, reqInfo)
}
case actTop:
case actFirst:
t.vset(0)
req(reqList)
case actLast:
t.vset(t.merger.Length() - 1)
req(reqList)
case actUnixLineDiscard:
beof = len(t.input) == 0
if t.cx > 0 {
@@ -2438,9 +2477,21 @@ func (t *Terminal) Loop() {
t.input = trimQuery(t.history.next())
t.cx = len(t.input)
}
case actToggleSearch:
t.paused = !t.paused
changed = !t.paused
req(reqPrompt)
case actEnableSearch:
t.paused = false
changed = true
req(reqPrompt)
case actDisableSearch:
t.paused = true
req(reqPrompt)
case actSigStop:
p, err := os.FindProcess(os.Getpid())
if err == nil {
t.sigstop = true
t.tui.Clear()
t.tui.Pause(t.fullscreen)
notifyStop(p)
@@ -2459,7 +2510,7 @@ func (t *Terminal) Loop() {
t.vmove(me.S, true)
req(reqList)
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
scrollPreview(-me.S)
scrollPreviewBy(-me.S)
}
} else if t.window.Enclose(my, mx) {
mx -= t.window.Left()
@@ -2484,7 +2535,7 @@ func (t *Terminal) Loop() {
// Double-click
if my >= min {
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
return doActions(t.keymap[tui.DoubleClick], tui.DoubleClick)
return doActions(actionsFor(tui.DoubleClick))
}
}
} else if me.Down {
@@ -2498,9 +2549,9 @@ func (t *Terminal) Loop() {
}
req(reqList)
if me.Left {
return doActions(t.keymap[tui.LeftClick], tui.LeftClick)
return doActions(actionsFor(tui.LeftClick))
}
return doActions(t.keymap[tui.RightClick], tui.RightClick)
return doActions(actionsFor(tui.RightClick))
}
}
}
@@ -2522,33 +2573,29 @@ func (t *Terminal) Loop() {
}
return true
}
mapkey := event.Type
if t.jumping == jumpDisabled {
actions := t.keymap[mapkey]
if mapkey == tui.Rune {
mapkey = int(event.Char) + int(tui.AltZ)
if act, prs := t.keymap[mapkey]; prs {
actions = act
}
}
if !doActions(actions, mapkey) {
actions := t.keymap[event.Comparable()]
if len(actions) == 0 && event.Type == tui.Rune {
doAction(action{t: actRune})
} else if !doActions(actions) {
continue
}
t.truncateQuery()
queryChanged = string(previousInput) != string(t.input)
changed = changed || queryChanged
if onChanges, prs := t.keymap[tui.Change]; queryChanged && prs {
if !doActions(onChanges, tui.Change) {
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs {
if !doActions(onChanges) {
continue
}
}
if onEOFs, prs := t.keymap[tui.BackwardEOF]; beof && prs {
if !doActions(onEOFs, tui.BackwardEOF) {
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
if !doActions(onEOFs) {
continue
}
}
} else {
if mapkey == tui.Rune {
if event.Type == tui.Rune {
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
t.cy = idx + t.offset
if t.jumping == jumpAcceptEnabled {

View File

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

View File

@@ -226,65 +226,65 @@ func (r *FullscreenRenderer) GetChar() Event {
alt := (mods & tcell.ModAlt) > 0
shift := (mods & tcell.ModShift) > 0
altShift := alt && shift
keyfn := func(r rune) int {
keyfn := func(r rune) Event {
if alt {
return CtrlAltA - 'a' + int(r)
return CtrlAltKey(r)
}
return CtrlA - 'a' + int(r)
return EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()
}
switch ev.Key() {
case tcell.KeyCtrlA:
return Event{keyfn('a'), 0, nil}
return keyfn('a')
case tcell.KeyCtrlB:
return Event{keyfn('b'), 0, nil}
return keyfn('b')
case tcell.KeyCtrlC:
return Event{keyfn('c'), 0, nil}
return keyfn('c')
case tcell.KeyCtrlD:
return Event{keyfn('d'), 0, nil}
return keyfn('d')
case tcell.KeyCtrlE:
return Event{keyfn('e'), 0, nil}
return keyfn('e')
case tcell.KeyCtrlF:
return Event{keyfn('f'), 0, nil}
return keyfn('f')
case tcell.KeyCtrlG:
return Event{keyfn('g'), 0, nil}
return keyfn('g')
case tcell.KeyCtrlH:
return Event{keyfn('h'), 0, nil}
return keyfn('h')
case tcell.KeyCtrlI:
return Event{keyfn('i'), 0, nil}
return keyfn('i')
case tcell.KeyCtrlJ:
return Event{keyfn('j'), 0, nil}
return keyfn('j')
case tcell.KeyCtrlK:
return Event{keyfn('k'), 0, nil}
return keyfn('k')
case tcell.KeyCtrlL:
return Event{keyfn('l'), 0, nil}
return keyfn('l')
case tcell.KeyCtrlM:
return Event{keyfn('m'), 0, nil}
return keyfn('m')
case tcell.KeyCtrlN:
return Event{keyfn('n'), 0, nil}
return keyfn('n')
case tcell.KeyCtrlO:
return Event{keyfn('o'), 0, nil}
return keyfn('o')
case tcell.KeyCtrlP:
return Event{keyfn('p'), 0, nil}
return keyfn('p')
case tcell.KeyCtrlQ:
return Event{keyfn('q'), 0, nil}
return keyfn('q')
case tcell.KeyCtrlR:
return Event{keyfn('r'), 0, nil}
return keyfn('r')
case tcell.KeyCtrlS:
return Event{keyfn('s'), 0, nil}
return keyfn('s')
case tcell.KeyCtrlT:
return Event{keyfn('t'), 0, nil}
return keyfn('t')
case tcell.KeyCtrlU:
return Event{keyfn('u'), 0, nil}
return keyfn('u')
case tcell.KeyCtrlV:
return Event{keyfn('v'), 0, nil}
return keyfn('v')
case tcell.KeyCtrlW:
return Event{keyfn('w'), 0, nil}
return keyfn('w')
case tcell.KeyCtrlX:
return Event{keyfn('x'), 0, nil}
return keyfn('x')
case tcell.KeyCtrlY:
return Event{keyfn('y'), 0, nil}
return keyfn('y')
case tcell.KeyCtrlZ:
return Event{keyfn('z'), 0, nil}
return keyfn('z')
case tcell.KeyCtrlSpace:
return Event{CtrlSpace, 0, nil}
case tcell.KeyCtrlBackslash:
@@ -389,18 +389,7 @@ func (r *FullscreenRenderer) GetChar() Event {
case tcell.KeyRune:
r := ev.Rune()
if alt {
switch r {
case ' ':
return Event{AltSpace, 0, nil}
case '/':
return Event{AltSlash, 0, nil}
}
if r >= 'a' && r <= 'z' {
return Event{AltA + int(r) - 'a', 0, nil}
}
if r >= '0' && r <= '9' {
return Event{Alt0 + int(r) - '0', 0, nil}
}
return AltKey(r)
}
return Event{Rune, r, nil}

View File

@@ -8,8 +8,10 @@ import (
)
// Types of user action
type EventType int
const (
Rune = iota
Rune EventType = iota
CtrlA
CtrlB
@@ -89,8 +91,6 @@ const (
Change
BackwardEOF
AltSpace
AltSlash
AltBS
AltUp
@@ -103,20 +103,38 @@ const (
AltSLeft
AltSRight
Alt0
Alt
CtrlAlt
)
const ( // Reset iota
AltA = Alt0 + 'a' - '0' + iota
AltB
AltC
AltD
AltE
AltF
AltZ = AltA + 'z' - 'a'
CtrlAltA = AltZ + 1
CtrlAltM = CtrlAltA + 'm' - 'a'
)
func (t EventType) AsEvent() Event {
return Event{t, 0, nil}
}
func (t EventType) Int() int {
return int(t)
}
func (t EventType) Byte() byte {
return byte(t)
}
func (e Event) Comparable() Event {
// Ignore MouseEvent pointer
return Event{e.Type, e.Char, nil}
}
func Key(r rune) Event {
return Event{Rune, r, nil}
}
func AltKey(r rune) Event {
return Event{Alt, r, nil}
}
func CtrlAltKey(r rune) Event {
return Event{CtrlAlt, r, nil}
}
const (
doubleClickDuration = 500 * time.Millisecond
@@ -232,6 +250,7 @@ func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
type ColorTheme struct {
Colored bool
Input ColorAttr
Disabled ColorAttr
Fg ColorAttr
Bg ColorAttr
PreviewFg ColorAttr
@@ -251,7 +270,7 @@ type ColorTheme struct {
}
type Event struct {
Type int
Type EventType
Char rune
MouseEvent *MouseEvent
}
@@ -403,6 +422,7 @@ var (
ColPrompt ColorPair
ColNormal ColorPair
ColInput ColorPair
ColDisabled ColorPair
ColMatch ColorPair
ColCursor ColorPair
ColCursorEmpty ColorPair
@@ -425,6 +445,7 @@ func EmptyTheme() *ColorTheme {
return &ColorTheme{
Colored: true,
Input: ColorAttr{colUndefined, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -447,6 +468,7 @@ func NoColorTheme() *ColorTheme {
return &ColorTheme{
Colored: false,
Input: ColorAttr{colDefault, AttrRegular},
Disabled: ColorAttr{colDefault, AttrRegular},
Fg: ColorAttr{colDefault, AttrRegular},
Bg: ColorAttr{colDefault, AttrRegular},
PreviewFg: ColorAttr{colDefault, AttrRegular},
@@ -474,6 +496,7 @@ func init() {
Default16 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -493,6 +516,7 @@ func init() {
Dark256 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -512,6 +536,7 @@ func init() {
Light256 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@@ -546,6 +571,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
return c
}
theme.Input = o(baseTheme.Input, theme.Input)
theme.Disabled = o(theme.Input, o(baseTheme.Disabled, theme.Disabled))
theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
@@ -579,6 +605,7 @@ func initPalette(theme *ColorTheme) {
ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColInput = pair(theme.Input, theme.Bg)
ColDisabled = pair(theme.Disabled, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter)

View File

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