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
|
\fBpage-up\fR \fIpgup\fR
|
||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
|
\fBpreview(...)\fR (see below for the details)
|
||||||
\fBpreview-down\fR \fIshift-down\fR
|
\fBpreview-down\fR \fIshift-down\fR
|
||||||
\fBpreview-up\fR \fIshift-up\fR
|
\fBpreview-up\fR \fIshift-up\fR
|
||||||
\fBpreview-page-down\fR
|
\fBpreview-page-down\fR
|
||||||
@ -783,6 +784,24 @@ e.g.
|
|||||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
||||||
--ansi --phony --query "$INITIAL_QUERY"\fR
|
--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
|
.SH AUTHOR
|
||||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||||
|
|
||||||
|
@ -684,7 +684,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):.+|[:+](execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview):.+|[:+](execute(?:-multi|-silent)?|reload|preview)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[int][]action, str string) {
|
func parseKeymap(keymap map[int][]action, str string) {
|
||||||
@ -696,6 +696,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
prefix := symbol + "execute"
|
prefix := symbol + "execute"
|
||||||
if strings.HasPrefix(src[1:], "reload") {
|
if strings.HasPrefix(src[1:], "reload") {
|
||||||
prefix = symbol + "reload"
|
prefix = symbol + "reload"
|
||||||
|
} else if strings.HasPrefix(src[1:], "preview") {
|
||||||
|
prefix = symbol + "preview"
|
||||||
} else if src[len(prefix)] == '-' {
|
} else if src[len(prefix)] == '-' {
|
||||||
c := src[len(prefix)+1]
|
c := src[len(prefix)+1]
|
||||||
if c == 's' || c == 'S' {
|
if c == 's' || c == 'S' {
|
||||||
@ -863,6 +865,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
switch t {
|
switch t {
|
||||||
case actReload:
|
case actReload:
|
||||||
offset = len("reload")
|
offset = len("reload")
|
||||||
|
case actPreview:
|
||||||
|
offset = len("preview")
|
||||||
case actExecuteSilent:
|
case actExecuteSilent:
|
||||||
offset = len("execute-silent")
|
offset = len("execute-silent")
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
@ -900,6 +904,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
switch prefix {
|
switch prefix {
|
||||||
case "reload":
|
case "reload":
|
||||||
return actReload
|
return actReload
|
||||||
|
case "preview":
|
||||||
|
return actPreview
|
||||||
case "execute":
|
case "execute":
|
||||||
return actExecute
|
return actExecute
|
||||||
case "execute-silent":
|
case "execute-silent":
|
||||||
|
@ -229,6 +229,7 @@ const (
|
|||||||
actToggleSort
|
actToggleSort
|
||||||
actTogglePreview
|
actTogglePreview
|
||||||
actTogglePreviewWrap
|
actTogglePreviewWrap
|
||||||
|
actPreview
|
||||||
actPreviewUp
|
actPreviewUp
|
||||||
actPreviewDown
|
actPreviewDown
|
||||||
actPreviewPageUp
|
actPreviewPageUp
|
||||||
@ -256,6 +257,11 @@ type searchRequest struct {
|
|||||||
command *string
|
command *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type previewRequest struct {
|
||||||
|
template string
|
||||||
|
list []*Item
|
||||||
|
}
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []action {
|
||||||
actions := make([]action, len(types))
|
actions := make([]action, len(types))
|
||||||
for idx, t := range types {
|
for idx, t := range types {
|
||||||
@ -327,6 +333,17 @@ func trimQuery(query string) []rune {
|
|||||||
return []rune(strings.Replace(query, "\t", " ", -1))
|
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
|
// NewTerminal returns new Terminal object
|
||||||
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
input := trimQuery(opts.Query)
|
input := trimQuery(opts.Query)
|
||||||
@ -344,7 +361,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
delay = initialDelay
|
delay = initialDelay
|
||||||
}
|
}
|
||||||
var previewBox *util.EventBox
|
var previewBox *util.EventBox
|
||||||
if len(opts.Preview.command) > 0 {
|
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) {
|
||||||
previewBox = util.NewEventBox()
|
previewBox = util.NewEventBox()
|
||||||
}
|
}
|
||||||
strongAttr := tui.Bold
|
strongAttr := tui.Bold
|
||||||
@ -1584,20 +1601,23 @@ func (t *Terminal) Loop() {
|
|||||||
if t.hasPreviewer() {
|
if t.hasPreviewer() {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
var request []*Item
|
var items []*Item
|
||||||
|
var commandTemplate string
|
||||||
t.previewBox.Wait(func(events *util.Events) {
|
t.previewBox.Wait(func(events *util.Events) {
|
||||||
for req, value := range *events {
|
for req, value := range *events {
|
||||||
switch req {
|
switch req {
|
||||||
case reqPreviewEnqueue:
|
case reqPreviewEnqueue:
|
||||||
request = value.([]*Item)
|
request := value.(previewRequest)
|
||||||
|
commandTemplate = request.template
|
||||||
|
items = request.list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
// We don't display preview window if no match
|
// We don't display preview window if no match
|
||||||
if request[0] != nil {
|
if items[0] != nil {
|
||||||
command := replacePlaceholder(t.preview.command,
|
command := replacePlaceholder(commandTemplate,
|
||||||
t.ansi, t.delimiter, t.printsep, false, string(t.Input()), request)
|
t.ansi, t.delimiter, t.printsep, false, string(t.Input()), items)
|
||||||
cmd := util.ExecCommand(command, true)
|
cmd := util.ExecCommand(command, true)
|
||||||
if t.pwindow != nil {
|
if t.pwindow != nil {
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
@ -1660,11 +1680,11 @@ func (t *Terminal) Loop() {
|
|||||||
t.killPreview(code)
|
t.killPreview(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshPreview := func() {
|
refreshPreview := func(command string) {
|
||||||
if t.isPreviewEnabled() {
|
if len(command) > 0 && t.isPreviewEnabled() {
|
||||||
_, list := t.buildPlusList(t.preview.command, false)
|
_, list := t.buildPlusList(command, false)
|
||||||
t.cancelPreview()
|
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 {
|
if focusedIndex != currentIndex || version != t.version {
|
||||||
version = t.version
|
version = t.version
|
||||||
focusedIndex = currentIndex
|
focusedIndex = currentIndex
|
||||||
refreshPreview()
|
refreshPreview(t.preview.command)
|
||||||
}
|
}
|
||||||
case reqJump:
|
case reqJump:
|
||||||
if t.merger.Length() == 0 {
|
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 {
|
toggle := func() bool {
|
||||||
if t.cy < t.merger.Length() && t.toggleItem(t.merger.Get(t.cy).item) {
|
if t.cy < t.merger.Length() && t.toggleItem(t.merger.Get(t.cy).item) {
|
||||||
req(reqInfo)
|
req(reqInfo)
|
||||||
@ -1808,17 +1836,15 @@ func (t *Terminal) Loop() {
|
|||||||
return false
|
return false
|
||||||
case actTogglePreview:
|
case actTogglePreview:
|
||||||
if t.hasPreviewer() {
|
if t.hasPreviewer() {
|
||||||
t.previewer.enabled = !t.previewer.enabled
|
togglePreview(!t.previewer.enabled)
|
||||||
t.tui.Clear()
|
|
||||||
t.resizeWindows()
|
|
||||||
if t.previewer.enabled {
|
if t.previewer.enabled {
|
||||||
valid, list := t.buildPlusList(t.preview.command, false)
|
valid, list := t.buildPlusList(t.preview.command, false)
|
||||||
if valid {
|
if valid {
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue, list)
|
t.previewBox.Set(reqPreviewEnqueue,
|
||||||
|
previewRequest{t.preview.command, list})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req(reqPrompt, reqList, reqInfo, reqHeader)
|
|
||||||
}
|
}
|
||||||
case actTogglePreviewWrap:
|
case actTogglePreviewWrap:
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
@ -1852,8 +1878,11 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actPrintQuery:
|
case actPrintQuery:
|
||||||
req(reqPrintQuery)
|
req(reqPrintQuery)
|
||||||
|
case actPreview:
|
||||||
|
togglePreview(true)
|
||||||
|
refreshPreview(a.a)
|
||||||
case actRefreshPreview:
|
case actRefreshPreview:
|
||||||
refreshPreview()
|
refreshPreview(t.preview.command)
|
||||||
case actReplaceQuery:
|
case actReplaceQuery:
|
||||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||||
t.input = t.merger.Get(t.cy).item.text.ToRunes()
|
t.input = t.merger.Get(t.cy).item.text.ToRunes()
|
||||||
|
@ -1757,6 +1757,36 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :BSpace
|
tmux.send_keys :BSpace
|
||||||
tmux.until { |lines| lines.item_count == 100 && lines.match_count == 100 }
|
tmux.until { |lines| lines.item_count == 100 && lines.match_count == 100 }
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
Loading…
Reference in New Issue
Block a user