From e97e925efb0470dfafedc034f38eb37f70848de2 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Thu, 12 Jan 2023 23:12:26 +0900 Subject: [PATCH] Resume preview following if the user scrolls the window to the bottom --- CHANGELOG.md | 5 ++++ src/terminal.go | 51 ++++++++++++++++++++++++++++++++------ test/test_go.rb | 65 +++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 111 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 718c932..04b276f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,11 @@ CHANGELOG color for `border`. Same holds true for `scrollbar`. This is to reduce the number of configuration items required to achieve a consistent color scheme. + - If `follow` flag is specified in `--preview-window` option, fzf will + automatically scroll to the bottom of the streaming preview output. But + when the user manually scrolls the window, the following stops. With + this version, fzf will resume following if the user scrolls the window + to the bottom. - Added color name `preview-label` for `--preview-label` (defaults to `label` for `--border-label`) - Minor bug fixes and improvements diff --git a/src/terminal.go b/src/terminal.go index 80c9e1a..8cd9a14 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -68,6 +68,38 @@ const ( jumpAcceptEnabled ) +type resumableState int + +const ( + disabledState resumableState = iota + pausedState + enabledState +) + +func (s resumableState) Enabled() bool { + return s == enabledState +} + +func (s *resumableState) Force(flag bool) { + if flag { + *s = enabledState + } else { + *s = disabledState + } +} + +func (s *resumableState) Set(flag bool) { + if *s == disabledState { + return + } + + if flag { + *s = enabledState + } else { + *s = pausedState + } +} + type previewer struct { version int64 lines []string @@ -75,7 +107,7 @@ type previewer struct { enabled bool scrollable bool final bool - following bool + following resumableState spinner string bar []bool } @@ -601,7 +633,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { reqBox: util.NewEventBox(), initialPreviewOpts: opts.Preview, previewOpts: opts.Preview, - previewer: previewer{0, []string{}, 0, len(opts.Preview.command) > 0, false, true, false, "", []bool{}}, + previewer: previewer{0, []string{}, 0, len(opts.Preview.command) > 0, false, true, disabledState, "", []bool{}}, previewed: previewed{0, 0, 0, false}, previewBox: previewBox, eventBox: eventBox, @@ -2639,12 +2671,15 @@ func (t *Terminal) Loop() { result := value.(previewResult) if t.previewer.version != result.version { t.previewer.version = result.version - t.previewer.following = t.previewOpts.follow + t.previewer.following.Force(t.previewOpts.follow) + if t.previewer.following.Enabled() { + t.previewer.offset = 0 + } } t.previewer.lines = result.lines t.previewer.spinner = result.spinner - if t.previewer.following { - t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height() + if t.previewer.following.Enabled() { + t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.previewOpts.headerLines)) } else if result.offset >= 0 { t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1) } @@ -2741,7 +2776,6 @@ func (t *Terminal) Loop() { if !t.previewer.scrollable { return } - t.previewer.following = false numLines := len(t.previewer.lines) headerLines := t.previewOpts.headerLines if t.previewOpts.cycle { @@ -2751,6 +2785,7 @@ func (t *Terminal) Loop() { newOffset = util.Constrain(newOffset, headerLines, numLines-1) if t.previewer.offset != newOffset { t.previewer.offset = newOffset + t.previewer.following.Set(t.previewer.offset >= numLines-(t.pwindow.Height()-headerLines)) req(reqPreviewRefresh) } } @@ -3177,7 +3212,7 @@ func (t *Terminal) Loop() { y = util.Constrain(y, 0, effectiveHeight-barLength) // offset = (total - maxItems) * barStart / (maxItems - barLength) t.previewer.offset = headerLines + int(math.Ceil(float64(y)*float64(numLines-effectiveHeight)/float64(effectiveHeight-barLength))) - t.previewer.following = false + t.previewer.following.Set(t.previewer.offset >= numLines-effectiveHeight) req(reqPreviewRefresh) } break @@ -3322,7 +3357,7 @@ func (t *Terminal) Loop() { } // Resume following - t.previewer.following = t.previewOpts.follow + t.previewer.following.Force(t.previewOpts.follow) case actNextSelected, actPrevSelected: if len(t.selected) > 0 { total := t.merger.Length() diff --git a/test/test_go.rb b/test/test_go.rb index 8031e81..60322e1 100755 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -1943,8 +1943,69 @@ class TestGoFZF < TestBase 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 } + file = Tempfile.new('fzf-follow') + file.sync = true + + tmux.send_keys %[seq 100 | #{FZF} --preview 'tail -f "#{file.path}"' --preview-window follow --bind 'up:preview-up,down:preview-down,space:change-preview-window:follow|nofollow' --preview-window '~3'], :Enter + tmux.until { |lines| lines.item_count == 100 } + + # Write to the temporary file, and check if the preview window is showing + # the last line of the file + 3.times { file.puts _1 } # header lines + 1000.times { file.puts _1 } + tmux.until { |lines| assert_includes lines[1], '/1003' } + tmux.until { |lines| assert_includes lines[-2], '999' } + + # Scroll the preview window and fzf should stop following the file content + tmux.send_keys :Up + tmux.until { |lines| assert_includes lines[-2], '998' } + file.puts 'foo', 'bar' + tmux.until do |lines| + assert_includes lines[1], '/1005' + assert_includes lines[-2], '998' + end + + # Scroll back to the bottom and fzf should start following the file again + %w[999 foo bar].each do |item| + wait do + tmux.send_keys :Down + tmux.until { |lines| assert_includes lines[-2], item } + end + end + file.puts 'baz' + tmux.until do |lines| + assert_includes lines[1], '/1006' + assert_includes lines[-2], 'baz' + end + + # Scroll upwards to stop following + tmux.send_keys :Up + wait { |line| assert_includes lines[-2], 'bar' } + file.puts 'aaa' + tmux.until do |lines| + assert_includes lines[1], '/1007' + assert_includes lines[-2], 'bar' + end + + # Manually enable following + tmux.send_keys :Space + tmux.until { |lines| assert_includes lines[-2], 'aaa' } + file.puts 'bbb' + tmux.until do |lines| + assert_includes lines[1], '/1008' + assert_includes lines[-2], 'bbb' + end + + # Disable following + tmux.send_keys :Space + file.puts 'ccc', 'ddd' + tmux.until do |lines| + assert_includes lines[1], '/1010' + assert_includes lines[-2], 'bbb' + end + rescue + file.close + file.unlink end def test_toggle_preview_wrap