mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-25 06:07:42 +00:00
parent
c33258832e
commit
17dd833925
@ -690,6 +690,7 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBpage-up\fR \fIpgup\fR
|
||||
\fBhalf-page-down\fR
|
||||
\fBhalf-page-up\fR
|
||||
\fBpreview(...)\fR (see below for the details)
|
||||
\fBpreview-down\fR \fIshift-down\fR
|
||||
\fBpreview-up\fR \fIshift-up\fR
|
||||
\fBpreview-page-down\fR
|
||||
@ -783,6 +784,24 @@ e.g.
|
||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
||||
--ansi --phony --query "$INITIAL_QUERY"\fR
|
||||
|
||||
.SS PREVIEW BINDING
|
||||
|
||||
With \fBpreview(...)\fR action, you can specify multiple different preview
|
||||
commands in addition to the default preview command given by \fB--preview\fR
|
||||
option.
|
||||
|
||||
e.g.
|
||||
|
||||
# Default preview command with an extra preview binding
|
||||
fzf --preview 'file {}' --bind '?:preview:cat {}'
|
||||
|
||||
# A preview binding with no default preview command
|
||||
# (Preview window is initially empty)
|
||||
fzf --bind '?:preview:cat {}'
|
||||
|
||||
# Preview window hidden by default, it appears when you first hit '?'
|
||||
fzf --bind '?:preview:cat {}' --preview-window hidden
|
||||
|
||||
.SH AUTHOR
|
||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||
|
||||
|
@ -684,7 +684,7 @@ func init() {
|
||||
// Backreferences are not supported.
|
||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||
executeRegexp = regexp.MustCompile(
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload):.+|[:+](execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview):.+|[:+](execute(?:-multi|-silent)?|reload|preview)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
}
|
||||
|
||||
func parseKeymap(keymap map[int][]action, str string) {
|
||||
@ -696,6 +696,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
prefix := symbol + "execute"
|
||||
if strings.HasPrefix(src[1:], "reload") {
|
||||
prefix = symbol + "reload"
|
||||
} else if strings.HasPrefix(src[1:], "preview") {
|
||||
prefix = symbol + "preview"
|
||||
} else if src[len(prefix)] == '-' {
|
||||
c := src[len(prefix)+1]
|
||||
if c == 's' || c == 'S' {
|
||||
@ -863,6 +865,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
switch t {
|
||||
case actReload:
|
||||
offset = len("reload")
|
||||
case actPreview:
|
||||
offset = len("preview")
|
||||
case actExecuteSilent:
|
||||
offset = len("execute-silent")
|
||||
case actExecuteMulti:
|
||||
@ -900,6 +904,8 @@ func isExecuteAction(str string) actionType {
|
||||
switch prefix {
|
||||
case "reload":
|
||||
return actReload
|
||||
case "preview":
|
||||
return actPreview
|
||||
case "execute":
|
||||
return actExecute
|
||||
case "execute-silent":
|
||||
|
@ -229,6 +229,7 @@ const (
|
||||
actToggleSort
|
||||
actTogglePreview
|
||||
actTogglePreviewWrap
|
||||
actPreview
|
||||
actPreviewUp
|
||||
actPreviewDown
|
||||
actPreviewPageUp
|
||||
@ -256,6 +257,11 @@ type searchRequest struct {
|
||||
command *string
|
||||
}
|
||||
|
||||
type previewRequest struct {
|
||||
template string
|
||||
list []*Item
|
||||
}
|
||||
|
||||
func toActions(types ...actionType) []action {
|
||||
actions := make([]action, len(types))
|
||||
for idx, t := range types {
|
||||
@ -327,6 +333,17 @@ func trimQuery(query string) []rune {
|
||||
return []rune(strings.Replace(query, "\t", " ", -1))
|
||||
}
|
||||
|
||||
func hasPreviewAction(opts *Options) bool {
|
||||
for _, actions := range opts.Keymap {
|
||||
for _, action := range actions {
|
||||
if action.t == actPreview {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NewTerminal returns new Terminal object
|
||||
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
input := trimQuery(opts.Query)
|
||||
@ -344,7 +361,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
delay = initialDelay
|
||||
}
|
||||
var previewBox *util.EventBox
|
||||
if len(opts.Preview.command) > 0 {
|
||||
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) {
|
||||
previewBox = util.NewEventBox()
|
||||
}
|
||||
strongAttr := tui.Bold
|
||||
@ -1584,20 +1601,23 @@ func (t *Terminal) Loop() {
|
||||
if t.hasPreviewer() {
|
||||
go func() {
|
||||
for {
|
||||
var request []*Item
|
||||
var items []*Item
|
||||
var commandTemplate string
|
||||
t.previewBox.Wait(func(events *util.Events) {
|
||||
for req, value := range *events {
|
||||
switch req {
|
||||
case reqPreviewEnqueue:
|
||||
request = value.([]*Item)
|
||||
request := value.(previewRequest)
|
||||
commandTemplate = request.template
|
||||
items = request.list
|
||||
}
|
||||
}
|
||||
events.Clear()
|
||||
})
|
||||
// We don't display preview window if no match
|
||||
if request[0] != nil {
|
||||
command := replacePlaceholder(t.preview.command,
|
||||
t.ansi, t.delimiter, t.printsep, false, string(t.Input()), request)
|
||||
if items[0] != nil {
|
||||
command := replacePlaceholder(commandTemplate,
|
||||
t.ansi, t.delimiter, t.printsep, false, string(t.Input()), items)
|
||||
cmd := util.ExecCommand(command, true)
|
||||
if t.pwindow != nil {
|
||||
env := os.Environ()
|
||||
@ -1660,11 +1680,11 @@ func (t *Terminal) Loop() {
|
||||
t.killPreview(code)
|
||||
}
|
||||
|
||||
refreshPreview := func() {
|
||||
if t.isPreviewEnabled() {
|
||||
_, list := t.buildPlusList(t.preview.command, false)
|
||||
refreshPreview := func(command string) {
|
||||
if len(command) > 0 && t.isPreviewEnabled() {
|
||||
_, list := t.buildPlusList(command, false)
|
||||
t.cancelPreview()
|
||||
t.previewBox.Set(reqPreviewEnqueue, list)
|
||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, list})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1694,7 +1714,7 @@ func (t *Terminal) Loop() {
|
||||
if focusedIndex != currentIndex || version != t.version {
|
||||
version = t.version
|
||||
focusedIndex = currentIndex
|
||||
refreshPreview()
|
||||
refreshPreview(t.preview.command)
|
||||
}
|
||||
case reqJump:
|
||||
if t.merger.Length() == 0 {
|
||||
@ -1760,6 +1780,14 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
togglePreview := func(enabled bool) {
|
||||
if t.previewer.enabled != enabled {
|
||||
t.previewer.enabled = enabled
|
||||
t.tui.Clear()
|
||||
t.resizeWindows()
|
||||
req(reqPrompt, reqList, reqInfo, reqHeader)
|
||||
}
|
||||
}
|
||||
toggle := func() bool {
|
||||
if t.cy < t.merger.Length() && t.toggleItem(t.merger.Get(t.cy).item) {
|
||||
req(reqInfo)
|
||||
@ -1808,17 +1836,15 @@ func (t *Terminal) Loop() {
|
||||
return false
|
||||
case actTogglePreview:
|
||||
if t.hasPreviewer() {
|
||||
t.previewer.enabled = !t.previewer.enabled
|
||||
t.tui.Clear()
|
||||
t.resizeWindows()
|
||||
togglePreview(!t.previewer.enabled)
|
||||
if t.previewer.enabled {
|
||||
valid, list := t.buildPlusList(t.preview.command, false)
|
||||
if valid {
|
||||
t.cancelPreview()
|
||||
t.previewBox.Set(reqPreviewEnqueue, list)
|
||||
t.previewBox.Set(reqPreviewEnqueue,
|
||||
previewRequest{t.preview.command, list})
|
||||
}
|
||||
}
|
||||
req(reqPrompt, reqList, reqInfo, reqHeader)
|
||||
}
|
||||
case actTogglePreviewWrap:
|
||||
if t.hasPreviewWindow() {
|
||||
@ -1852,8 +1878,11 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
case actPrintQuery:
|
||||
req(reqPrintQuery)
|
||||
case actPreview:
|
||||
togglePreview(true)
|
||||
refreshPreview(a.a)
|
||||
case actRefreshPreview:
|
||||
refreshPreview()
|
||||
refreshPreview(t.preview.command)
|
||||
case actReplaceQuery:
|
||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||
t.input = t.merger.Get(t.cy).item.text.ToRunes()
|
||||
|
@ -1757,6 +1757,36 @@ class TestGoFZF < TestBase
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| lines.item_count == 100 && lines.match_count == 100 }
|
||||
end
|
||||
|
||||
def test_preview_bindings_with_default_preview
|
||||
tmux.send_keys "seq 10 | #{FZF} --preview 'echo [{}]' --bind 'a:preview(echo [{}{}]),b:preview(echo [{}{}{}]),c:refresh-preview'", :Enter
|
||||
tmux.until { |lines| lines.item_count == 10 }
|
||||
tmux.until { |lines| assert_includes lines[1], '[1]' }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_includes lines[1], '[11]' }
|
||||
tmux.send_keys 'c'
|
||||
tmux.until { |lines| assert_includes lines[1], '[1]' }
|
||||
tmux.send_keys 'b'
|
||||
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines[1], '[2]' }
|
||||
end
|
||||
|
||||
def test_preview_bindings_without_default_preview
|
||||
tmux.send_keys "seq 10 | #{FZF} --bind 'a:preview(echo [{}{}]),b:preview(echo [{}{}{}]),c:refresh-preview'", :Enter
|
||||
tmux.until { |lines| lines.item_count == 10 }
|
||||
tmux.until { |lines| refute_includes lines[1], '1' }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_includes lines[1], '[11]' }
|
||||
tmux.send_keys 'c' # does nothing
|
||||
tmux.until { |lines| assert_includes lines[1], '[11]' }
|
||||
tmux.send_keys 'b'
|
||||
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
||||
tmux.send_keys 9
|
||||
tmux.until { |lines| lines.match_count == 1 }
|
||||
tmux.until { |lines| refute_includes lines[1], '2' }
|
||||
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
|
Loading…
Reference in New Issue
Block a user