mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-06-03 07:50:49 +00:00
Implement change-preview and change-preview-window actions
The new actions are named with 'change-' prefix to differentiate from the pre-existing, one-off 'preview(...)' action. Fix #2360 Fix #2505 Fix #2666 Related #2435 Related #2376 - Can set up multiple bindings with different change-preview-window actions - Not possible to "rotate" through the options with a single binding - Enlarge or shrink not possible
This commit is contained in:
parent
7da287e3aa
commit
20b4e6953e
|
@ -1,6 +1,13 @@
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.29.0
|
||||||
|
------
|
||||||
|
- Added `change-preview(...)` action to change the `--preview` command
|
||||||
|
- cf. `preview(...)` is a one-off action that doesn't change the default
|
||||||
|
preview command
|
||||||
|
- Added `change-preview-window(...)` action
|
||||||
|
|
||||||
0.28.0
|
0.28.0
|
||||||
------
|
------
|
||||||
- Added `--header-first` option to print header before the prompt line
|
- Added `--header-first` option to print header before the prompt line
|
||||||
|
|
114
man/man1/fzf.1
114
man/man1/fzf.1
|
@ -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 "Nov 2021" "fzf 0.28.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Dec 2021" "fzf 0.29.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
|
@ -810,77 +810,79 @@ e.g.
|
||||||
A key or an event can be bound to one or more of the following actions.
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
\fBaccept\fR \fIenter double-click\fR
|
\fBaccept\fR \fIenter double-click\fR
|
||||||
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||||
\fBbackward-char\fR \fIctrl-b left\fR
|
\fBbackward-char\fR \fIctrl-b left\fR
|
||||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||||
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
||||||
\fBclear-screen\fR \fIctrl-l\fR
|
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option)
|
||||||
\fBclear-selection\fR (clear multi-selection)
|
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||||
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
\fBclear-screen\fR \fIctrl-l\fR
|
||||||
\fBclear-query\fR (clear query string)
|
\fBclear-selection\fR (clear multi-selection)
|
||||||
\fBdelete-char\fR \fIdel\fR
|
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
||||||
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
\fBclear-query\fR (clear query string)
|
||||||
|
\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\fR
|
\fBdeselect\fR
|
||||||
\fBdeselect-all\fR (deselect all matches)
|
\fBdeselect-all\fR (deselect all matches)
|
||||||
\fBdisable-search\fR (disable search functionality)
|
\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)
|
\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)
|
\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
|
||||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||||
\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)
|
\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
|
||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
\fBpreview(...)\fR (see below for the details)
|
\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
|
||||||
\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-bottom\fR
|
||||||
\fBpreview-top\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)
|
||||||
\fBput\fR (put the character to the prompt)
|
\fBput\fR (put the character to the prompt)
|
||||||
\fBrefresh-preview\fR
|
\fBrefresh-preview\fR
|
||||||
\fBreload(...)\fR (see below for the details)
|
\fBreload(...)\fR (see below for the details)
|
||||||
\fBreplace-query\fR (replace query string with the current selection)
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBselect\fR
|
\fBselect\fR
|
||||||
\fBselect-all\fR (select all matches)
|
\fBselect-all\fR (select all matches)
|
||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR (toggle all matches)
|
\fBtoggle-all\fR (toggle all matches)
|
||||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\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-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)
|
\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
|
||||||
\fByank\fR \fIctrl-y\fR
|
\fByank\fR \fIctrl-y\fR
|
||||||
|
|
||||||
.SS ACTION COMPOSITION
|
.SS ACTION COMPOSITION
|
||||||
|
|
||||||
|
|
|
@ -176,6 +176,14 @@ type previewOpts struct {
|
||||||
headerLines int
|
headerLines int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a previewOpts) sameLayout(b previewOpts) bool {
|
||||||
|
return a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
||||||
|
return a.wrap == b.wrap && a.headerLines == b.headerLines
|
||||||
|
}
|
||||||
|
|
||||||
// Options stores the values of command-line options
|
// Options stores the values of command-line options
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Fuzzy bool
|
Fuzzy bool
|
||||||
|
@ -787,7 +795,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|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[tui.Event][]action, str string) {
|
func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||||
|
@ -799,6 +807,10 @@ func parseKeymap(keymap map[tui.Event][]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:], "change-preview-window") {
|
||||||
|
prefix = symbol + "change-preview-window"
|
||||||
|
} else if strings.HasPrefix(src[1:], "change-preview") {
|
||||||
|
prefix = symbol + "change-preview"
|
||||||
} 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") {
|
} else if strings.HasPrefix(src[1:], "unbind") {
|
||||||
|
@ -1002,6 +1014,10 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||||
offset = len("reload")
|
offset = len("reload")
|
||||||
case actPreview:
|
case actPreview:
|
||||||
offset = len("preview")
|
offset = len("preview")
|
||||||
|
case actChangePreviewWindow:
|
||||||
|
offset = len("change-preview-window")
|
||||||
|
case actChangePreview:
|
||||||
|
offset = len("change-preview")
|
||||||
case actChangePrompt:
|
case actChangePrompt:
|
||||||
offset = len("change-prompt")
|
offset = len("change-prompt")
|
||||||
case actUnbind:
|
case actUnbind:
|
||||||
|
@ -1028,6 +1044,9 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||||
}
|
}
|
||||||
if t == actUnbind {
|
if t == actUnbind {
|
||||||
parseKeyChords(actionArg, "unbind target required")
|
parseKeyChords(actionArg, "unbind target required")
|
||||||
|
} else if t == actChangePreviewWindow {
|
||||||
|
opts := previewOpts{}
|
||||||
|
parsePreviewWindow(&opts, actionArg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1053,6 +1072,10 @@ func isExecuteAction(str string) actionType {
|
||||||
return actUnbind
|
return actUnbind
|
||||||
case "preview":
|
case "preview":
|
||||||
return actPreview
|
return actPreview
|
||||||
|
case "change-preview-window":
|
||||||
|
return actChangePreviewWindow
|
||||||
|
case "change-preview":
|
||||||
|
return actChangePreview
|
||||||
case "change-prompt":
|
case "change-prompt":
|
||||||
return actChangePrompt
|
return actChangePrompt
|
||||||
case "execute":
|
case "execute":
|
||||||
|
@ -1633,11 +1656,29 @@ func postProcessOptions(opts *Options) {
|
||||||
// Extend the default key map
|
// Extend the default key map
|
||||||
keymap := defaultKeymap()
|
keymap := defaultKeymap()
|
||||||
for key, actions := range opts.Keymap {
|
for key, actions := range opts.Keymap {
|
||||||
|
lastChangePreviewWindow := action{t: actIgnore}
|
||||||
for _, act := range actions {
|
for _, act := range actions {
|
||||||
if act.t == actToggleSort {
|
switch act.t {
|
||||||
|
case actToggleSort:
|
||||||
|
// To display "+S"/"-S" on info line
|
||||||
opts.ToggleSort = true
|
opts.ToggleSort = true
|
||||||
|
case actChangePreviewWindow:
|
||||||
|
lastChangePreviewWindow = act
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Re-organize actions so that we only keep the last change-preview-window
|
||||||
|
// and it comes first in the list.
|
||||||
|
// * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20)
|
||||||
|
// -> change-preview-window(up,+20)+preview(sleep 3; cat {})
|
||||||
|
if lastChangePreviewWindow.t == actChangePreviewWindow {
|
||||||
|
reordered := []action{lastChangePreviewWindow}
|
||||||
|
for _, act := range actions {
|
||||||
|
if act.t != actChangePreviewWindow {
|
||||||
|
reordered = append(reordered, act)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions = reordered
|
||||||
|
}
|
||||||
keymap[key] = actions
|
keymap[key] = actions
|
||||||
}
|
}
|
||||||
opts.Keymap = keymap
|
opts.Keymap = keymap
|
||||||
|
|
373
src/terminal.go
373
src/terminal.go
|
@ -104,88 +104,89 @@ var emptyLine = itemLine{}
|
||||||
|
|
||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
infoStyle infoStyle
|
infoStyle infoStyle
|
||||||
spinner []string
|
spinner []string
|
||||||
prompt func()
|
prompt func()
|
||||||
promptLen int
|
promptLen int
|
||||||
pointer string
|
pointer string
|
||||||
pointerLen int
|
pointerLen int
|
||||||
pointerEmpty string
|
pointerEmpty string
|
||||||
marker string
|
marker string
|
||||||
markerLen int
|
markerLen int
|
||||||
markerEmpty string
|
markerEmpty string
|
||||||
queryLen [2]int
|
queryLen [2]int
|
||||||
layout layoutType
|
layout layoutType
|
||||||
fullscreen bool
|
fullscreen bool
|
||||||
keepRight bool
|
keepRight bool
|
||||||
hscroll bool
|
hscroll bool
|
||||||
hscrollOff int
|
hscrollOff int
|
||||||
scrollOff int
|
scrollOff int
|
||||||
wordRubout string
|
wordRubout string
|
||||||
wordNext string
|
wordNext string
|
||||||
cx int
|
cx int
|
||||||
cy int
|
cy int
|
||||||
offset int
|
offset int
|
||||||
xoffset int
|
xoffset int
|
||||||
yanked []rune
|
yanked []rune
|
||||||
input []rune
|
input []rune
|
||||||
multi int
|
multi int
|
||||||
sort bool
|
sort bool
|
||||||
toggleSort bool
|
toggleSort bool
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
expect map[tui.Event]string
|
expect map[tui.Event]string
|
||||||
keymap map[tui.Event][]action
|
keymap map[tui.Event][]action
|
||||||
pressed string
|
pressed string
|
||||||
printQuery bool
|
printQuery bool
|
||||||
history *History
|
history *History
|
||||||
cycle bool
|
cycle bool
|
||||||
headerFirst bool
|
headerFirst bool
|
||||||
headerLines int
|
headerLines int
|
||||||
header []string
|
header []string
|
||||||
header0 []string
|
header0 []string
|
||||||
ansi bool
|
ansi bool
|
||||||
tabstop int
|
tabstop int
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
padding [4]sizeSpec
|
padding [4]sizeSpec
|
||||||
strong tui.Attr
|
strong tui.Attr
|
||||||
unicode bool
|
unicode bool
|
||||||
borderShape tui.BorderShape
|
borderShape tui.BorderShape
|
||||||
cleanExit bool
|
cleanExit bool
|
||||||
paused bool
|
paused bool
|
||||||
border tui.Window
|
border tui.Window
|
||||||
window tui.Window
|
window tui.Window
|
||||||
pborder tui.Window
|
pborder tui.Window
|
||||||
pwindow tui.Window
|
pwindow tui.Window
|
||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
running bool
|
running bool
|
||||||
failed *string
|
failed *string
|
||||||
jumping jumpMode
|
jumping jumpMode
|
||||||
jumpLabels string
|
jumpLabels string
|
||||||
printer func(string)
|
printer func(string)
|
||||||
printsep string
|
printsep string
|
||||||
merger *Merger
|
merger *Merger
|
||||||
selected map[int32]selectedItem
|
selected map[int32]selectedItem
|
||||||
version int64
|
version int64
|
||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
previewOpts previewOpts
|
initialPreviewOpts previewOpts
|
||||||
previewer previewer
|
previewOpts previewOpts
|
||||||
previewed previewed
|
previewer previewer
|
||||||
previewBox *util.EventBox
|
previewed previewed
|
||||||
eventBox *util.EventBox
|
previewBox *util.EventBox
|
||||||
mutex sync.Mutex
|
eventBox *util.EventBox
|
||||||
initFunc func()
|
mutex sync.Mutex
|
||||||
prevLines []itemLine
|
initFunc func()
|
||||||
suppress bool
|
prevLines []itemLine
|
||||||
sigstop bool
|
suppress bool
|
||||||
startChan chan bool
|
sigstop bool
|
||||||
killChan chan int
|
startChan chan bool
|
||||||
slab *util.Slab
|
killChan chan int
|
||||||
theme *tui.ColorTheme
|
slab *util.Slab
|
||||||
tui tui.Renderer
|
theme *tui.ColorTheme
|
||||||
executing *util.AtomicBool
|
tui tui.Renderer
|
||||||
|
executing *util.AtomicBool
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
|
@ -286,6 +287,8 @@ const (
|
||||||
actTogglePreview
|
actTogglePreview
|
||||||
actTogglePreviewWrap
|
actTogglePreviewWrap
|
||||||
actPreview
|
actPreview
|
||||||
|
actChangePreview
|
||||||
|
actChangePreviewWindow
|
||||||
actPreviewTop
|
actPreviewTop
|
||||||
actPreviewBottom
|
actPreviewBottom
|
||||||
actPreviewUp
|
actPreviewUp
|
||||||
|
@ -324,9 +327,10 @@ type searchRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type previewRequest struct {
|
type previewRequest struct {
|
||||||
template string
|
template string
|
||||||
pwindow tui.Window
|
pwindow tui.Window
|
||||||
list []*Item
|
scrollOffset int
|
||||||
|
list []*Item
|
||||||
}
|
}
|
||||||
|
|
||||||
type previewResult struct {
|
type previewResult struct {
|
||||||
|
@ -416,7 +420,7 @@ func trimQuery(query string) []rune {
|
||||||
func hasPreviewAction(opts *Options) bool {
|
func hasPreviewAction(opts *Options) bool {
|
||||||
for _, actions := range opts.Keymap {
|
for _, actions := range opts.Keymap {
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
if action.t == actPreview {
|
if action.t == actPreview || action.t == actChangePreview {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -496,72 +500,73 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
||||||
}
|
}
|
||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
infoStyle: opts.InfoStyle,
|
infoStyle: opts.InfoStyle,
|
||||||
spinner: makeSpinner(opts.Unicode),
|
spinner: makeSpinner(opts.Unicode),
|
||||||
queryLen: [2]int{0, 0},
|
queryLen: [2]int{0, 0},
|
||||||
layout: opts.Layout,
|
layout: opts.Layout,
|
||||||
fullscreen: fullscreen,
|
fullscreen: fullscreen,
|
||||||
keepRight: opts.KeepRight,
|
keepRight: opts.KeepRight,
|
||||||
hscroll: opts.Hscroll,
|
hscroll: opts.Hscroll,
|
||||||
hscrollOff: opts.HscrollOff,
|
hscrollOff: opts.HscrollOff,
|
||||||
scrollOff: opts.ScrollOff,
|
scrollOff: opts.ScrollOff,
|
||||||
wordRubout: wordRubout,
|
wordRubout: wordRubout,
|
||||||
wordNext: wordNext,
|
wordNext: wordNext,
|
||||||
cx: len(input),
|
cx: len(input),
|
||||||
cy: 0,
|
cy: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
xoffset: 0,
|
xoffset: 0,
|
||||||
yanked: []rune{},
|
yanked: []rune{},
|
||||||
input: input,
|
input: input,
|
||||||
multi: opts.Multi,
|
multi: opts.Multi,
|
||||||
sort: opts.Sort > 0,
|
sort: opts.Sort > 0,
|
||||||
toggleSort: opts.ToggleSort,
|
toggleSort: opts.ToggleSort,
|
||||||
delimiter: opts.Delimiter,
|
delimiter: opts.Delimiter,
|
||||||
expect: opts.Expect,
|
expect: opts.Expect,
|
||||||
keymap: opts.Keymap,
|
keymap: opts.Keymap,
|
||||||
pressed: "",
|
pressed: "",
|
||||||
printQuery: opts.PrintQuery,
|
printQuery: opts.PrintQuery,
|
||||||
history: opts.History,
|
history: opts.History,
|
||||||
margin: opts.Margin,
|
margin: opts.Margin,
|
||||||
padding: opts.Padding,
|
padding: opts.Padding,
|
||||||
unicode: opts.Unicode,
|
unicode: opts.Unicode,
|
||||||
borderShape: opts.BorderShape,
|
borderShape: opts.BorderShape,
|
||||||
cleanExit: opts.ClearOnExit,
|
cleanExit: opts.ClearOnExit,
|
||||||
paused: opts.Phony,
|
paused: opts.Phony,
|
||||||
strong: strongAttr,
|
strong: strongAttr,
|
||||||
cycle: opts.Cycle,
|
cycle: opts.Cycle,
|
||||||
headerFirst: opts.HeaderFirst,
|
headerFirst: opts.HeaderFirst,
|
||||||
headerLines: opts.HeaderLines,
|
headerLines: opts.HeaderLines,
|
||||||
header: header,
|
header: header,
|
||||||
header0: header,
|
header0: header,
|
||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
reading: true,
|
reading: true,
|
||||||
running: true,
|
running: true,
|
||||||
failed: nil,
|
failed: nil,
|
||||||
jumping: jumpDisabled,
|
jumping: jumpDisabled,
|
||||||
jumpLabels: opts.JumpLabels,
|
jumpLabels: opts.JumpLabels,
|
||||||
printer: opts.Printer,
|
printer: opts.Printer,
|
||||||
printsep: opts.PrintSep,
|
printsep: opts.PrintSep,
|
||||||
merger: EmptyMerger,
|
merger: EmptyMerger,
|
||||||
selected: make(map[int32]selectedItem),
|
selected: make(map[int32]selectedItem),
|
||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
previewOpts: opts.Preview,
|
initialPreviewOpts: opts.Preview,
|
||||||
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
|
previewOpts: opts.Preview,
|
||||||
previewed: previewed{0, 0, 0, false},
|
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
|
||||||
previewBox: previewBox,
|
previewed: previewed{0, 0, 0, false},
|
||||||
eventBox: eventBox,
|
previewBox: previewBox,
|
||||||
mutex: sync.Mutex{},
|
eventBox: eventBox,
|
||||||
suppress: true,
|
mutex: sync.Mutex{},
|
||||||
sigstop: false,
|
suppress: true,
|
||||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
sigstop: false,
|
||||||
theme: opts.Theme,
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||||
startChan: make(chan bool, 1),
|
theme: opts.Theme,
|
||||||
killChan: make(chan int),
|
startChan: make(chan bool, 1),
|
||||||
tui: renderer,
|
killChan: make(chan int),
|
||||||
initFunc: func() { renderer.Init() },
|
tui: renderer,
|
||||||
executing: util.NewAtomicBool(false)}
|
initFunc: func() { renderer.Init() },
|
||||||
|
executing: util.NewAtomicBool(false)}
|
||||||
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
||||||
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
||||||
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
||||||
|
@ -1642,9 +1647,14 @@ func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input str
|
||||||
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
func (t *Terminal) evaluateScrollOffset() int {
|
||||||
|
if t.pwindow == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only need the current item to calculate the scroll offset
|
||||||
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
|
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
|
||||||
t.replacePlaceholder(t.previewOpts.scroll, false, "", list), "")
|
t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil}), "")
|
||||||
|
|
||||||
atoi := func(s string) int {
|
atoi := func(s string) int {
|
||||||
n, e := strconv.Atoi(s)
|
n, e := strconv.Atoi(s)
|
||||||
|
@ -1655,20 +1665,21 @@ func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
base := -1
|
base := -1
|
||||||
|
height := util.Max(0, t.pwindow.Height()-t.previewOpts.headerLines)
|
||||||
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
|
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
|
||||||
if strings.HasPrefix(component, "-/") {
|
if strings.HasPrefix(component, "-/") {
|
||||||
component = component[1:]
|
component = component[1:]
|
||||||
}
|
}
|
||||||
if component[0] == '/' {
|
if component[0] == '/' {
|
||||||
denom := atoi(component[1:])
|
denom := atoi(component[1:])
|
||||||
if denom == 0 {
|
if denom != 0 {
|
||||||
return base
|
base -= height / denom
|
||||||
}
|
}
|
||||||
return base - height/denom
|
break
|
||||||
}
|
}
|
||||||
base += atoi(component)
|
base += atoi(component)
|
||||||
}
|
}
|
||||||
return base
|
return util.Max(0, base)
|
||||||
}
|
}
|
||||||
|
|
||||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||||
|
@ -1972,12 +1983,14 @@ func (t *Terminal) Loop() {
|
||||||
var items []*Item
|
var items []*Item
|
||||||
var commandTemplate string
|
var commandTemplate string
|
||||||
var pwindow tui.Window
|
var pwindow tui.Window
|
||||||
|
initialOffset := 0
|
||||||
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.(previewRequest)
|
request := value.(previewRequest)
|
||||||
commandTemplate = request.template
|
commandTemplate = request.template
|
||||||
|
initialOffset = request.scrollOffset
|
||||||
items = request.list
|
items = request.list
|
||||||
pwindow = request.pwindow
|
pwindow = request.pwindow
|
||||||
}
|
}
|
||||||
|
@ -1989,11 +2002,9 @@ func (t *Terminal) Loop() {
|
||||||
if items[0] != nil {
|
if items[0] != nil {
|
||||||
_, query := t.Input()
|
_, query := t.Input()
|
||||||
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
||||||
initialOffset := 0
|
|
||||||
cmd := util.ExecCommand(command, true)
|
cmd := util.ExecCommand(command, true)
|
||||||
if pwindow != nil {
|
if pwindow != nil {
|
||||||
height := pwindow.Height()
|
height := pwindow.Height()
|
||||||
initialOffset = util.Max(0, t.evaluateScrollOffset(items, util.Max(0, height-t.previewOpts.headerLines)))
|
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
lines := fmt.Sprintf("LINES=%d", height)
|
lines := fmt.Sprintf("LINES=%d", height)
|
||||||
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
||||||
|
@ -2128,7 +2139,7 @@ func (t *Terminal) Loop() {
|
||||||
if len(command) > 0 && t.isPreviewEnabled() {
|
if len(command) > 0 && t.isPreviewEnabled() {
|
||||||
_, list := t.buildPlusList(command, false)
|
_, list := t.buildPlusList(command, false)
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, list})
|
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.evaluateScrollOffset(), list})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2253,13 +2264,16 @@ func (t *Terminal) Loop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
togglePreview := func(enabled bool) {
|
togglePreview := func(enabled bool) bool {
|
||||||
if t.previewer.enabled != enabled {
|
if t.previewer.enabled != enabled {
|
||||||
t.previewer.enabled = enabled
|
t.previewer.enabled = enabled
|
||||||
|
// We need to immediately update t.pwindow so we don't use reqRedraw
|
||||||
t.tui.Clear()
|
t.tui.Clear()
|
||||||
t.resizeWindows()
|
t.resizeWindows()
|
||||||
req(reqPrompt, reqList, reqInfo, reqHeader)
|
req(reqPrompt, reqList, reqInfo, reqHeader)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
toggle := func() bool {
|
toggle := func() bool {
|
||||||
current := t.currentItem()
|
current := t.currentItem()
|
||||||
|
@ -2327,7 +2341,7 @@ func (t *Terminal) Loop() {
|
||||||
if valid {
|
if valid {
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue,
|
t.previewBox.Set(reqPreviewEnqueue,
|
||||||
previewRequest{t.previewOpts.command, t.pwindow, list})
|
previewRequest{t.previewOpts.command, t.pwindow, t.evaluateScrollOffset(), list})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2707,6 +2721,39 @@ func (t *Terminal) Loop() {
|
||||||
for key := range keys {
|
for key := range keys {
|
||||||
delete(t.keymap, key)
|
delete(t.keymap, key)
|
||||||
}
|
}
|
||||||
|
case actChangePreview:
|
||||||
|
if t.previewOpts.command != a.a {
|
||||||
|
togglePreview(len(a.a) > 0)
|
||||||
|
t.previewOpts.command = a.a
|
||||||
|
refreshPreview(t.previewOpts.command)
|
||||||
|
}
|
||||||
|
case actChangePreviewWindow:
|
||||||
|
currentPreviewOpts := t.previewOpts
|
||||||
|
|
||||||
|
// Reset preview options and apply the additional options
|
||||||
|
t.previewOpts = t.initialPreviewOpts
|
||||||
|
parsePreviewWindow(&t.previewOpts, a.a)
|
||||||
|
|
||||||
|
if t.previewOpts.hidden {
|
||||||
|
togglePreview(false)
|
||||||
|
} else {
|
||||||
|
// Full redraw
|
||||||
|
if !currentPreviewOpts.sameLayout(t.previewOpts) {
|
||||||
|
if togglePreview(true) {
|
||||||
|
refreshPreview(t.previewOpts.command)
|
||||||
|
} else {
|
||||||
|
req(reqRedraw)
|
||||||
|
}
|
||||||
|
} else if !currentPreviewOpts.sameContentLayout(t.previewOpts) {
|
||||||
|
t.previewed.version = 0
|
||||||
|
req(reqPreviewRefresh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust scroll offset
|
||||||
|
if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.previewOpts.scroll {
|
||||||
|
scrollPreviewTo(t.evaluateScrollOffset())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -2142,6 +2142,55 @@ class TestGoFZF < TestBase
|
||||||
assert_equal expected.chomp, lines.take(6).join("\n")
|
assert_equal expected.chomp, lines.take(6).join("\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_change_preview_window
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --preview-window border-none --bind '" \
|
||||||
|
'a:change-preview(echo __{}__),' \
|
||||||
|
'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \
|
||||||
|
'c:change-preview(),d:change-preview-window(hidden),' \
|
||||||
|
"e:preview(printf ::%${FZF_PREVIEW_COLUMNS}s{})+change-preview-window(up),f:change-preview-window(up,wrap)'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[0], '[[1]]' }
|
||||||
|
|
||||||
|
# change-preview action permanently changes the preview command set by --preview
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], '__1__' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[0], '__2__' }
|
||||||
|
|
||||||
|
# When multiple change-preview-window actions are bound to a single key,
|
||||||
|
# the last one wins and the updated options are immediately applied to the new preview
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert_equal '==2==', lines[0] }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal '==3==', lines[0] }
|
||||||
|
|
||||||
|
# change-preview with an empty preview command closes the preview window
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.until { |lines| refute_includes lines[0], '==' }
|
||||||
|
|
||||||
|
# change-preview again to re-open the preview window
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_equal '__3__', lines[0] }
|
||||||
|
|
||||||
|
# Hide the preview window with hidden flag
|
||||||
|
tmux.send_keys 'd'
|
||||||
|
tmux.until { |lines| refute_includes lines[0], '__3__' }
|
||||||
|
|
||||||
|
# One-off preview
|
||||||
|
tmux.send_keys 'e'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal '::', lines[0]
|
||||||
|
refute_includes lines[1], '3'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wrapped
|
||||||
|
tmux.send_keys 'f'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal '::', lines[0]
|
||||||
|
assert_equal ' 3', lines[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
|
Loading…
Reference in New Issue
Block a user