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
This commit is contained in:
Junegunn Choi 2021-01-03 00:00:40 +09:00
parent fd8858f8c9
commit d779ff7e6d
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
10 changed files with 132 additions and 49 deletions

View File

@ -1,7 +1,7 @@
CHANGELOG
=========
0.24.5
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
@ -23,6 +23,13 @@ CHANGELOG
# 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

View File

@ -171,19 +171,23 @@ list:
- `element` is an fzf element to apply a color to:
| Element | Description |
| --- | --- |
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
| `hl` / `hl+` | Highlighted substrings (normal / current) |
| `gutter` | Background of the gutter on the left |
| `pointer` | Pointer to the current line (`>`) |
| `marker` | Multi-select marker (`>`) |
| `border` | Border around the window (`--border` and `--preview`) |
| `header` | Header (`--header` or `--header-lines`) |
| `info` | Info line (match counters) |
| `spinner` | Streaming input indicator |
| `prompt` | Prompt before query (`> `) |
| Element | Description |
| --- | --- |
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
| `preview-fg` / `preview-bg` | Preview window text and background |
| `hl` / `hl+` | Highlighted substrings (normal / current) |
| `gutter` | Background of the gutter on the left |
| `pointer` | Pointer to the current line (`>`) |
| `marker` | Multi-select marker (`>`) |
| `border` | Border around the window (`--border` and `--preview`) |
| `header` | Header (`--header` or `--header-lines`) |
| `info` | Info line (match counters) |
| `spinner` | Streaming input indicator |
| `query` | Query string |
| `disabled` | Query string when search is disabled |
| `prompt` | Prompt before query (`> `) |
| `pointer` | Pointer to the current line (`>`) |
- `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups

View File

@ -1,4 +1,4 @@
fzf.txt fzf Last change: December 31 2020
fzf.txt fzf Last change: January 2 2021
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
==============================================================================
@ -200,21 +200,25 @@ list:
<
- `element` is an fzf element to apply a color to:
----------------------+------------------------------------------------------
Element | Description ~
----------------------+------------------------------------------------------
`fg` / `bg` / `hl` | Item (foreground / background / highlight)
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
`hl` / `hl+` | Highlighted substrings (normal / current)
`gutter` | Background of the gutter on the left
`pointer` | Pointer to the current line ( `>` )
`marker` | Multi-select marker ( `>` )
`border` | Border around the window ( `--border` and `--preview` )
`header` | Header ( `--header` or `--header-lines` )
`info` | Info line (match counters)
`spinner` | Streaming input indicator
`prompt` | Prompt before query ( `>` )
----------------------+------------------------------------------------------
----------------------------+------------------------------------------------------
Element | Description ~
----------------------------+------------------------------------------------------
`fg` / `bg` / `hl` | Item (foreground / background / highlight)
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
`preview-fg` / `preview-bg` | Preview window text and background
`hl` / `hl+` | Highlighted substrings (normal / current)
`gutter` | Background of the gutter on the left
`pointer` | Pointer to the current line ( `>` )
`marker` | Multi-select marker ( `>` )
`border` | Border around the window ( `--border` and `--preview` )
`header` | Header ( `--header` or `--header-lines` )
`info` | Info line (match counters)
`spinner` | Streaming input indicator
`query` | Query string
`disabled` | Query string when search is disabled
`prompt` | Prompt before query ( `>` )
`pointer` | Pointer to the current line ( `>` )
----------------------------+------------------------------------------------------
- `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups
- `group1[,group2,...]` is a list of highlight groups that are searched (in

View File

@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf-tmux 1 "Dec 2020" "fzf 0.24.4" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Jan 2021" "fzf 0.25.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane

View File

@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "Dec 2020" "fzf 0.24.5" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Jan 2021" "fzf 0.25.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@ -71,9 +71,10 @@ Transform the presentation of each line using field index expressions
.BI "-d, --delimiter=" "STR"
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
.TP
.BI "--phony"
.BI "--disabled"
Do not perform search. With this option, fzf becomes a simple selector
interface rather than a "fuzzy finder".
interface rather than a "fuzzy finder". You can later enable the search using
\fBenable-search\fR or `\fBtoggle-search\R action.
.SS Search result
.TP
.B "+s, --no-sort"
@ -323,6 +324,8 @@ color mappings.
\fBbg+ \fRBackground (current line)
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
\fBhl+ \fRHighlighted substrings (current line)
\fBquery \fRQuery string
\fBdisabled \fRQuery string when search is disabled
\fBinfo \fRInfo line (match counters)
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBprompt \fRPrompt
@ -780,7 +783,9 @@ A key or an event can be bound to one or more of the following actions.
\fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
\fBdeselect-all\fR (deselect all matches)
\fBdisable-search\fR (disable search functionality)
\fBdown\fR \fIctrl-j ctrl-n down\fR
\fBenable-search\fR (enable search functionality)
\fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details)
\fBexecute-silent(...)\fR (see below for the details)
@ -820,6 +825,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR
\fBtoggle-preview-wrap\fR
\fBtoggle-search\fR (toggle search functionality)
\fBtoggle-sort\fR
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBunix-line-discard\fR \fIctrl-u\fR
@ -902,7 +908,7 @@ e.g.
INITIAL_QUERY="foobar"
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
--ansi --phony --query "$INITIAL_QUERY"\fR
--ansi --disabled --query "$INITIAL_QUERY"\fR
.SS PREVIEW BINDING

View File

@ -237,14 +237,16 @@ func Run(opts *Options, version string, revision string) {
go reader.restart(command)
}
eventBox.Watch(EvtReadNew)
query := []rune{}
for {
delay := true
ticks++
input := func() []rune {
if opts.Phony {
return []rune{}
paused, input := terminal.Input()
if !paused {
query = input
}
return []rune(terminal.Input())
return query
}
eventBox.Wait(func(events *util.Events) {
if _, fin := (*events)[EvtReadFin]; fin {

View File

@ -33,7 +33,7 @@ const usage = `usage: fzf [options]
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
+s, --no-sort Do not sort the result
--tac Reverse the order of the input
--phony Do not perform search
--disabled Do not perform search
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
when the scores are tied [length|begin|end|index]
(default: length)
@ -682,8 +682,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
}
}
switch components[0] {
case "input":
case "query", "input":
mergeAttr(&theme.Input)
case "disabled":
mergeAttr(&theme.Disabled)
case "fg":
mergeAttr(&theme.Fg)
case "bg":
@ -875,6 +877,8 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
appendAction(actToggleOut)
case "toggle-all":
appendAction(actToggleAll)
case "toggle-search":
appendAction(actToggleSearch)
case "select-all":
appendAction(actSelectAll)
case "deselect-all":
@ -923,6 +927,10 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
appendAction(actPreviewHalfPageUp)
case "preview-half-page-down":
appendAction(actPreviewHalfPageDown)
case "enable-search":
appendAction(actEnableSearch)
case "disable-search":
appendAction(actDisableSearch)
default:
t := isExecuteAction(specLower)
if t == actIgnore {
@ -1199,9 +1207,9 @@ func parseOptions(opts *Options, allArgs []string) {
}
case "--no-expect":
opts.Expect = make(map[tui.Event]string)
case "--no-phony":
case "--enabled", "--no-phony":
opts.Phony = false
case "--phony":
case "--disabled", "--phony":
opts.Phony = true
case "--tiebreak":
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))

View File

@ -125,6 +125,7 @@ type Terminal struct {
unicode bool
borderShape tui.BorderShape
cleanExit bool
paused bool
border tui.Window
window tui.Window
pborder tui.Window
@ -233,6 +234,7 @@ const (
actSelectAll
actDeselectAll
actToggle
actToggleSearch
actToggleAll
actToggleDown
actToggleUp
@ -270,6 +272,8 @@ const (
actFirst
actLast
actReload
actDisableSearch
actEnableSearch
)
type placeholderFlags struct {
@ -488,6 +492,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
unicode: opts.Unicode,
borderShape: opts.BorderShape,
cleanExit: opts.ClearOnExit,
paused: opts.Phony,
strong: strongAttr,
cycle: opts.Cycle,
header: header,
@ -563,10 +568,10 @@ func (t *Terminal) noInfoLine() bool {
}
// Input returns current query string
func (t *Terminal) Input() []rune {
func (t *Terminal) Input() (bool, []rune) {
t.mutex.Lock()
defer t.mutex.Unlock()
return copySlice(t.input)
return t.paused, copySlice(t.input)
}
// UpdateCount updates the count information
@ -925,8 +930,12 @@ func (t *Terminal) printPrompt() {
t.prompt()
before, after := t.updatePromptOffset()
t.window.CPrint(tui.ColInput, string(before))
t.window.CPrint(tui.ColInput, string(after))
color := tui.ColInput
if t.paused {
color = tui.ColDisabled
}
t.window.CPrint(color, string(before))
t.window.CPrint(color, string(after))
}
func (t *Terminal) trimMessage(message string, maxWidth int) string {
@ -1880,7 +1889,8 @@ func (t *Terminal) Loop() {
version++
// We don't display preview window if no match
if items[0] != nil {
command := t.replacePlaceholder(commandTemplate, false, string(t.Input()), items)
_, query := t.Input()
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
initialOffset := 0
cmd := util.ExecCommand(command, true)
if pwindow != nil {
@ -2465,6 +2475,17 @@ func (t *Terminal) Loop() {
t.input = trimQuery(t.history.next())
t.cx = len(t.input)
}
case actToggleSearch:
t.paused = !t.paused
changed = !t.paused
req(reqPrompt)
case actEnableSearch:
t.paused = false
changed = true
req(reqPrompt)
case actDisableSearch:
t.paused = true
req(reqPrompt)
case actSigStop:
p, err := os.FindProcess(os.Getpid())
if err == nil {

View File

@ -250,6 +250,7 @@ func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
type ColorTheme struct {
Colored bool
Input ColorAttr
Disabled ColorAttr
Fg ColorAttr
Bg ColorAttr
PreviewFg ColorAttr
@ -421,6 +422,7 @@ var (
ColPrompt ColorPair
ColNormal ColorPair
ColInput ColorPair
ColDisabled ColorPair
ColMatch ColorPair
ColCursor ColorPair
ColCursorEmpty ColorPair
@ -443,6 +445,7 @@ func EmptyTheme() *ColorTheme {
return &ColorTheme{
Colored: true,
Input: ColorAttr{colUndefined, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@ -465,6 +468,7 @@ func NoColorTheme() *ColorTheme {
return &ColorTheme{
Colored: false,
Input: ColorAttr{colDefault, AttrRegular},
Disabled: ColorAttr{colDefault, AttrRegular},
Fg: ColorAttr{colDefault, AttrRegular},
Bg: ColorAttr{colDefault, AttrRegular},
PreviewFg: ColorAttr{colDefault, AttrRegular},
@ -492,6 +496,7 @@ func init() {
Default16 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@ -511,6 +516,7 @@ func init() {
Dark256 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@ -530,6 +536,7 @@ func init() {
Light256 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
@ -564,6 +571,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
return c
}
theme.Input = o(baseTheme.Input, theme.Input)
theme.Disabled = o(theme.Input, o(baseTheme.Disabled, theme.Disabled))
theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
@ -597,6 +605,7 @@ func initPalette(theme *ColorTheme) {
ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColInput = pair(theme.Input, theme.Bg)
ColDisabled = pair(theme.Disabled, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter)

View File

@ -1658,13 +1658,35 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines[1], ' + green ' }
end
def test_phony
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --phony --preview 'echo {} {q}'), :Enter
def test_disabled
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --disabled --bind a:enable-search,b:disable-search,c:toggle-search --preview 'echo {} {q}'), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 1 333 ' }
tmux.send_keys 'foo'
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 1 333foo ' }
# Already disabled, no change
tmux.send_keys 'b'
tmux.until { |lines| assert_equal 1000, lines.match_count }
# Enable search
tmux.send_keys 'a'
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.send_keys :BSpace, :BSpace, :BSpace
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 333 333 ' }
# Toggle search -> disabled again, but retains the previous result
tmux.send_keys 'c'
tmux.send_keys 'foo'
tmux.until { |lines| assert_includes lines[1], ' 333 333foo ' }
tmux.until { |lines| assert_equal 1, lines.match_count }
# Enabled, no match
tmux.send_keys 'c'
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' 333foo ' }
end
def test_reload