Add --preview-window follow option

This commit is contained in:
Junegunn Choi 2020-12-05 21:16:35 +09:00
parent cbfee31593
commit 2ec382ae0e
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
5 changed files with 78 additions and 36 deletions

View File

@ -3,6 +3,15 @@ CHANGELOG
0.24.4
------
- Added `--preview-window` option `follow`
```sh
# Preview window will automatically scroll to the bottom
fzf --preview-window follow --preview 'for i in $(seq 100000); do
echo "$i"
sleep 0.01
(( i % 300 == 0 )) && printf "\033[2J"
done'
```
- Added `change-prompt` action
```sh
fzf --prompt 'foo> ' --bind $'a:change-prompt:\x1b[31mbar> '

View File

@ -439,7 +439,7 @@ e.g.
done'\fR
.RE
.TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
.RS
.B POSITION: (default: right)
@ -448,27 +448,43 @@ e.g.
\fBleft
\fBright
\fRDetermines the layout of the preview window. If the argument contains
\fB:hidden\fR, the preview window will be hidden by default until
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
Line wrap can be enabled with \fB:wrap\fR flag. Cyclic scrolling is enabled
with \fB:cycle\fR flag.
\fRDetermines the layout of the preview window.
If size is given as 0, preview window will not be visible, but fzf will still
* If the argument contains \fB:hidden\fR, the preview window will be hidden by
default until \fBtoggle-preview\fR action is triggered.
* If size is given as 0, preview window will not be visible, but fzf will still
execute the command in the background.
To change the style of the border of the preview window, specify one of
* Long lines are truncated by default. Line wrap can be enabled with
\fB:wrap\fR flag.
* Preview window will automatically scroll to the bottom when \fB:follow\fR
flag is set, similarly to how \fBtail -f\fR works.
.RS
e.g.
\fBfzf --preview-window follow --preview 'for i in $(seq 100000); do
echo "$i"
sleep 0.01
(( i % 300 == 0 )) && printf "\\033[2J"
done'\fR
.RE
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
* To change the style of the border of the preview window, specify one of
\fBrounded\fR (border with rounded edges, default), \fBsharp\fR (border with
sharp edges), or \fBnoborder\fR (no border).
\fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview
* \fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview
window. \fBSCROLL\fR can be either a numeric integer or a single-field index
expression that refers to a numeric integer. The optional \fB-OFFSET\fR part is
for adjusting the base offset so that you can see the text above it. It should
be given as a numeric integer (\fB-INTEGER\fR), or as a denominator form
(\fB-/INTEGER\fR) for specifying a fraction of the preview window height.
\fBdefault\fR resets all options previously set to the default.
* \fBdefault\fR resets all options previously set to the default.
.RS
e.g.

View File

@ -83,7 +83,7 @@ const usage = `usage: fzf [options]
--preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][:SIZE[%]]
[:[no]wrap][:[no]cycle][:[no]hidden]
[:[no]wrap][:[no]cycle][:[no]follow][:[no]hidden]
[:rounded|sharp|noborder]
[:+SCROLL[-OFFSET]]
[:default]
@ -169,6 +169,7 @@ type previewOpts struct {
hidden bool
wrap bool
cycle bool
follow bool
border tui.BorderShape
}
@ -231,7 +232,7 @@ type Options struct {
}
func defaultPreviewOpts(command string) previewOpts {
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, tui.BorderRounded}
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded}
}
func defaultOptions() *Options {
@ -1081,6 +1082,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
opts.border = tui.BorderSharp
case "noborder":
opts.border = tui.BorderNone
case "follow":
opts.follow = true
case "nofollow":
opts.follow = false
default:
if sizeRegex.MatchString(token) {
opts.size = parseSize(token, 99, "window size")

View File

@ -51,6 +51,7 @@ type previewer struct {
enabled bool
scrollable bool
final bool
following bool
spinner string
}
@ -140,7 +141,7 @@ type Terminal struct {
selected map[int32]selectedItem
version int64
reqBox *util.EventBox
preview previewOpts
previewOpts previewOpts
previewer previewer
previewed previewed
previewBox *util.EventBox
@ -493,8 +494,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
merger: EmptyMerger,
selected: make(map[int32]selectedItem),
reqBox: util.NewEventBox(),
preview: opts.Preview,
previewer: previewer{0, []string{}, 0, previewBox != nil && !opts.Preview.hidden, false, true, ""},
previewOpts: opts.Preview,
previewer: previewer{0, []string{}, 0, previewBox != nil && !opts.Preview.hidden, false, true, false, ""},
previewed: previewed{0, 0, 0, false},
previewBox: previewBox,
eventBox: eventBox,
@ -732,11 +733,11 @@ func (t *Terminal) resizeWindows() {
}
}
previewVisible := t.isPreviewEnabled() && t.preview.size.size > 0
previewVisible := t.isPreviewEnabled() && t.previewOpts.size.size > 0
minAreaWidth := minWidth
minAreaHeight := minHeight
if previewVisible {
switch t.preview.position {
switch t.previewOpts.position {
case posUp, posDown:
minAreaHeight *= 2
case posLeft, posRight:
@ -805,8 +806,8 @@ func (t *Terminal) resizeWindows() {
createPreviewWindow := func(y int, x int, w int, h int) {
pwidth := w
pheight := h
if t.preview.border != tui.BorderNone {
previewBorder := tui.MakeBorderStyle(t.preview.border, t.unicode)
if t.previewOpts.border != tui.BorderNone {
previewBorder := tui.MakeBorderStyle(t.previewOpts.border, t.unicode)
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
pwidth -= 4
pheight -= 2
@ -822,28 +823,28 @@ func (t *Terminal) resizeWindows() {
}
verticalPad := 2
minPreviewHeight := 3
if t.preview.border == tui.BorderNone {
if t.previewOpts.border == tui.BorderNone {
verticalPad = 0
minPreviewHeight = 1
}
switch t.preview.position {
switch t.previewOpts.position {
case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, minPreviewHeight, verticalPad)
pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
t.window = t.tui.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, minPreviewHeight, verticalPad)
pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5, 4)
pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
t.window = t.tui.NewWindow(
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5, 4)
pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
@ -1291,7 +1292,7 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
prefixWidth := 0
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
trimmed := []rune(str)
if !t.preview.wrap {
if !t.previewOpts.wrap {
trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X())
}
str, width := t.processTabs(trimmed, prefixWidth)
@ -1559,7 +1560,7 @@ func atopi(s string) int {
}
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
offsetExpr := t.replacePlaceholder(t.preview.scroll, false, "", list)
offsetExpr := t.replacePlaceholder(t.previewOpts.scroll, false, "", list)
nums := strings.Split(offsetExpr, "-")
switch len(nums) {
case 0:
@ -2041,7 +2042,7 @@ func (t *Terminal) Loop() {
if focusedIndex != currentIndex || version != t.version {
version = t.version
focusedIndex = currentIndex
refreshPreview(t.preview.command)
refreshPreview(t.previewOpts.command)
}
case reqJump:
if t.merger.Length() == 0 {
@ -2066,10 +2067,15 @@ func (t *Terminal) Loop() {
})
case reqPreviewDisplay:
result := value.(previewResult)
t.previewer.version = result.version
if t.previewer.version != result.version {
t.previewer.version = result.version
t.previewer.following = t.previewOpts.follow
}
t.previewer.lines = result.lines
t.previewer.spinner = result.spinner
if result.offset >= 0 {
if t.previewer.following {
t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height()
} else if result.offset >= 0 {
t.previewer.offset = util.Constrain(result.offset, 0, len(t.previewer.lines)-1)
}
t.printPreview()
@ -2133,9 +2139,10 @@ func (t *Terminal) Loop() {
if !t.previewer.scrollable {
return
}
t.previewer.following = false
newOffset := t.previewer.offset + amount
numLines := len(t.previewer.lines)
if t.preview.cycle {
if t.previewOpts.cycle {
newOffset = (newOffset + numLines) % numLines
}
newOffset = util.Constrain(newOffset, 0, numLines-1)
@ -2176,17 +2183,17 @@ func (t *Terminal) Loop() {
if t.hasPreviewer() {
togglePreview(!t.previewer.enabled)
if t.previewer.enabled {
valid, list := t.buildPlusList(t.preview.command, false)
valid, list := t.buildPlusList(t.previewOpts.command, false)
if valid {
t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue,
previewRequest{t.preview.command, t.pwindow, list})
previewRequest{t.previewOpts.command, t.pwindow, list})
}
}
}
case actTogglePreviewWrap:
if t.hasPreviewWindow() {
t.preview.wrap = !t.preview.wrap
t.previewOpts.wrap = !t.previewOpts.wrap
req(reqPreviewRefresh)
}
case actToggleSort:
@ -2231,7 +2238,7 @@ func (t *Terminal) Loop() {
togglePreview(true)
refreshPreview(a.a)
case actRefreshPreview:
refreshPreview(t.preview.command)
refreshPreview(t.previewOpts.command)
case actReplaceQuery:
if t.cy >= 0 && t.cy < t.merger.Length() {
t.input = t.merger.Get(t.cy).item.text.ToRunes()
@ -2554,7 +2561,7 @@ func (t *Terminal) Loop() {
if queryChanged {
if t.isPreviewEnabled() {
_, _, q := hasPreviewFlags(t.preview.command)
_, _, q := hasPreviewFlags(t.previewOpts.command)
if q {
t.version++
}

View File

@ -1832,6 +1832,11 @@ class TestGoFZF < TestBase
tmux.send_keys 'b'
tmux.until { |lines| assert_equal 'b> foo', lines[-1] }
end
def test_preview_window_follow
tmux.send_keys "#{FZF} --preview 'seq 1000 | nl' --preview-window down:noborder:follow", :Enter
tmux.until { |lines| assert_equal '1000 1000', lines[-1].strip }
end
end
module TestShell