mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-06-12 20:22:21 +00:00
parent
34f0d4d0c4
commit
347c4b2625
51
ADVANCED.md
51
ADVANCED.md
|
@ -16,6 +16,7 @@ Advanced fzf examples
|
||||||
* [Ripgrep integration](#ripgrep-integration)
|
* [Ripgrep integration](#ripgrep-integration)
|
||||||
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
||||||
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
|
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
|
||||||
|
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
|
||||||
* [Log tailing](#log-tailing)
|
* [Log tailing](#log-tailing)
|
||||||
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
||||||
* [Files listed in `git status`](#files-listed-in-git-status)
|
* [Files listed in `git status`](#files-listed-in-git-status)
|
||||||
|
@ -354,6 +355,56 @@ IFS=: read -ra selected < <(
|
||||||
reduce the number of intermediate Ripgrep processes while we're typing in
|
reduce the number of intermediate Ripgrep processes while we're typing in
|
||||||
a query.
|
a query.
|
||||||
|
|
||||||
|
### Switching to fzf-only search mode
|
||||||
|
|
||||||
|
*(Requires fzf 0.27.1 or above)*
|
||||||
|
|
||||||
|
In the previous example, we lost fuzzy matching capability as we completely
|
||||||
|
delegated search functionality to Ripgrep. But we can dynamically switch to
|
||||||
|
fzf-only search mode by *"unbinding"* `reload` action from `change` event.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Two-phase filtering with Ripgrep and fzf
|
||||||
|
#
|
||||||
|
# 1. Search for text in files using Ripgrep
|
||||||
|
# 2. Interactively restart Ripgrep with reload action
|
||||||
|
# * Press alt-enter to switch to fzf-only filtering
|
||||||
|
# 3. Open the file in Vim
|
||||||
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
|
INITIAL_QUERY="${*:-}"
|
||||||
|
IFS=: read -ra selected < <(
|
||||||
|
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
||||||
|
fzf --ansi \
|
||||||
|
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||||
|
--disabled --query "$INITIAL_QUERY" \
|
||||||
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
|
--bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \
|
||||||
|
--prompt '1. ripgrep> ' \
|
||||||
|
--delimiter : \
|
||||||
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
||||||
|
)
|
||||||
|
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
||||||
|
```
|
||||||
|
|
||||||
|
* Phase 1. Filtering with Ripgrep
|
||||||
|
![image](https://user-images.githubusercontent.com/700826/119213880-735e8a80-bafd-11eb-8493-123e4be24fbc.png)
|
||||||
|
* Phase 2. Filtering with fzf
|
||||||
|
![image](https://user-images.githubusercontent.com/700826/119213887-7e191f80-bafd-11eb-98c9-71a1af9d7aab.png)
|
||||||
|
|
||||||
|
- We added `--prompt` option to show that fzf is initially running in "Ripgrep
|
||||||
|
launcher mode".
|
||||||
|
- We added `alt-enter` binding that
|
||||||
|
1. unbinds `change` event, so Ripgrep is no longer restarted on key press
|
||||||
|
2. changes the prompt to `2. fzf>`
|
||||||
|
3. enables search functionality of fzf
|
||||||
|
4. clears the current query string that was used to start Ripgrep process
|
||||||
|
5. and unbinds `alt-enter` itself as this is a one-off event
|
||||||
|
- We reverted `--color` option for customizing how the matching chunks are
|
||||||
|
displayed in the second phase
|
||||||
|
|
||||||
Log tailing
|
Log tailing
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -1,6 +1,22 @@
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.27.1
|
||||||
|
------
|
||||||
|
- Added `unbind` action. In the following Ripgrep launcher example, you can
|
||||||
|
use `unbind(reload)` to switch to fzf-only filtering mode.
|
||||||
|
- See https://github.com/junegunn/fzf/blob/master/ADVANCED.md#switching-to-fzf-only-search-mode
|
||||||
|
- Vim plugin
|
||||||
|
- Vim plugin will stop immediately even when the source command hasn't finished
|
||||||
|
```vim
|
||||||
|
" fzf will read the stream file while allowing other processes to append to it
|
||||||
|
call fzf#run({'source': 'cat /dev/null > /tmp/stream; tail -f /tmp/stream'})
|
||||||
|
```
|
||||||
|
- It is now possible to open popup window relative to the currrent window
|
||||||
|
```vim
|
||||||
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
|
||||||
|
```
|
||||||
|
|
||||||
0.27.0
|
0.27.0
|
||||||
------
|
------
|
||||||
- More border options for `--preview-window`
|
- More border options for `--preview-window`
|
||||||
|
|
|
@ -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 "Apr 2021" "fzf 0.27.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "May 2021" "fzf 0.27.1" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
|
@ -852,6 +852,7 @@ A key or an event can be bound to one or more of the following actions.
|
||||||
\fBtoggle-search\fR (toggle search functionality)
|
\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
|
||||||
|
\fBunbind(...)\fR (unbind bindings)
|
||||||
\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
|
||||||
|
|
|
@ -748,7 +748,7 @@ func init() {
|
||||||
// Backreferences are not supported.
|
// Backreferences are not supported.
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[tui.Event][]action, str string) {
|
func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||||
|
@ -762,6 +762,8 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||||
prefix = symbol + "reload"
|
prefix = symbol + "reload"
|
||||||
} else if strings.HasPrefix(src[1:], "preview") {
|
} else if strings.HasPrefix(src[1:], "preview") {
|
||||||
prefix = symbol + "preview"
|
prefix = symbol + "preview"
|
||||||
|
} else if strings.HasPrefix(src[1:], "unbind") {
|
||||||
|
prefix = symbol + "unbind"
|
||||||
} else if strings.HasPrefix(src[1:], "change-prompt") {
|
} else if strings.HasPrefix(src[1:], "change-prompt") {
|
||||||
prefix = symbol + "change-prompt"
|
prefix = symbol + "change-prompt"
|
||||||
} else if src[len(prefix)] == '-' {
|
} else if src[len(prefix)] == '-' {
|
||||||
|
@ -957,6 +959,8 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||||
offset = len("preview")
|
offset = len("preview")
|
||||||
case actChangePrompt:
|
case actChangePrompt:
|
||||||
offset = len("change-prompt")
|
offset = len("change-prompt")
|
||||||
|
case actUnbind:
|
||||||
|
offset = len("unbind")
|
||||||
case actExecuteSilent:
|
case actExecuteSilent:
|
||||||
offset = len("execute-silent")
|
offset = len("execute-silent")
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
|
@ -964,15 +968,21 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||||
default:
|
default:
|
||||||
offset = len("execute")
|
offset = len("execute")
|
||||||
}
|
}
|
||||||
|
var actionArg string
|
||||||
if spec[offset] == ':' {
|
if spec[offset] == ':' {
|
||||||
if specIndex == len(specs)-1 {
|
if specIndex == len(specs)-1 {
|
||||||
actions = append(actions, action{t: t, a: spec[offset+1:]})
|
actionArg = spec[offset+1:]
|
||||||
|
actions = append(actions, action{t: t, a: actionArg})
|
||||||
} else {
|
} else {
|
||||||
prevSpec = spec + "+"
|
prevSpec = spec + "+"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
actions = append(actions, action{t: t, a: spec[offset+1 : len(spec)-1]})
|
actionArg = spec[offset+1 : len(spec)-1]
|
||||||
|
actions = append(actions, action{t: t, a: actionArg})
|
||||||
|
}
|
||||||
|
if t == actUnbind {
|
||||||
|
parseKeyChords(actionArg, "unbind target required")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -994,6 +1004,8 @@ func isExecuteAction(str string) actionType {
|
||||||
switch prefix {
|
switch prefix {
|
||||||
case "reload":
|
case "reload":
|
||||||
return actReload
|
return actReload
|
||||||
|
case "unbind":
|
||||||
|
return actUnbind
|
||||||
case "preview":
|
case "preview":
|
||||||
return actPreview
|
return actPreview
|
||||||
case "change-prompt":
|
case "change-prompt":
|
||||||
|
|
|
@ -284,6 +284,7 @@ const (
|
||||||
actEnableSearch
|
actEnableSearch
|
||||||
actSelect
|
actSelect
|
||||||
actDeselect
|
actDeselect
|
||||||
|
actUnbind
|
||||||
)
|
)
|
||||||
|
|
||||||
type placeholderFlags struct {
|
type placeholderFlags struct {
|
||||||
|
@ -2657,6 +2658,11 @@ func (t *Terminal) Loop() {
|
||||||
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||||
newCommand = &command
|
newCommand = &command
|
||||||
}
|
}
|
||||||
|
case actUnbind:
|
||||||
|
keys := parseKeyChords(a.a, "PANIC")
|
||||||
|
for key := range keys {
|
||||||
|
delete(t.keymap, key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -2042,6 +2042,17 @@ class TestGoFZF < TestBase
|
||||||
tmux.send_keys 'C-K'
|
tmux.send_keys 'C-K'
|
||||||
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_unbind
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind 'c:clear-query,d:unbind(c,d)'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.send_keys 'ab'
|
||||||
|
tmux.until { |lines| assert_equal '> ab', lines[-1] }
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.until { |lines| assert_equal '>', lines[-1] }
|
||||||
|
tmux.send_keys 'dabcd'
|
||||||
|
tmux.until { |lines| assert_equal '> abcd', lines[-1] }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
|
Loading…
Reference in New Issue
Block a user