summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2020-12-05 21:16:35 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2020-12-05 21:16:35 +0900
commit2ec382ae0eda85f89a4b977aa39425fb7e710df1 (patch)
tree344315edbca0e7bcf7455daaeb4f1a4f24be3dfd
parentcbfee31593113fd372a728c45b4c73a573839a02 (diff)
Add --preview-window follow option
-rw-r--r--CHANGELOG.md9
-rw-r--r--man/man1/fzf.136
-rw-r--r--src/options.go9
-rw-r--r--src/terminal.go55
-rwxr-xr-xtest/test_go.rb5
5 files changed, 78 insertions, 36 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7609316f..9d2bf95b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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> '
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index f779ad5e..eeb83945 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -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.
diff --git a/src/options.go b/src/options.go
index b3fb78cf..0607e5dc 100644
--- a/src/options.go
+++ b/src/options.go
@@ -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")
diff --git a/src/terminal.go b/src/terminal.go
index 08535484..83b3a7b5 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -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++
}
diff --git a/test/test_go.rb b/test/test_go.rb
index d9d1e1bc..416692e2 100755
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -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