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 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 0.24.4
------ ------
- Added `--preview-window` option `follow` - Added `--preview-window` option `follow`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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