Resume preview following if the user scrolls the window to the bottom

This commit is contained in:
Junegunn Choi 2023-01-12 23:12:26 +09:00
parent 0f032235cf
commit e97e925efb
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
3 changed files with 111 additions and 10 deletions

View File

@ -109,6 +109,11 @@ CHANGELOG
color for `border`. Same holds true for `scrollbar`. This is to reduce color for `border`. Same holds true for `scrollbar`. This is to reduce
the number of configuration items required to achieve a consistent color the number of configuration items required to achieve a consistent color
scheme. 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` - Added color name `preview-label` for `--preview-label` (defaults to `label`
for `--border-label`) for `--border-label`)
- Minor bug fixes and improvements - Minor bug fixes and improvements

View File

@ -68,6 +68,38 @@ const (
jumpAcceptEnabled 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 { type previewer struct {
version int64 version int64
lines []string lines []string
@ -75,7 +107,7 @@ type previewer struct {
enabled bool enabled bool
scrollable bool scrollable bool
final bool final bool
following bool following resumableState
spinner string spinner string
bar []bool bar []bool
} }
@ -601,7 +633,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
reqBox: util.NewEventBox(), reqBox: util.NewEventBox(),
initialPreviewOpts: opts.Preview, initialPreviewOpts: opts.Preview,
previewOpts: 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}, previewed: previewed{0, 0, 0, false},
previewBox: previewBox, previewBox: previewBox,
eventBox: eventBox, eventBox: eventBox,
@ -2639,12 +2671,15 @@ func (t *Terminal) Loop() {
result := value.(previewResult) result := value.(previewResult)
if t.previewer.version != result.version { if t.previewer.version != result.version {
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.lines = result.lines
t.previewer.spinner = result.spinner t.previewer.spinner = result.spinner
if t.previewer.following { if t.previewer.following.Enabled() {
t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height() t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.previewOpts.headerLines))
} else if result.offset >= 0 { } else if result.offset >= 0 {
t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1) 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 { if !t.previewer.scrollable {
return return
} }
t.previewer.following = false
numLines := len(t.previewer.lines) numLines := len(t.previewer.lines)
headerLines := t.previewOpts.headerLines headerLines := t.previewOpts.headerLines
if t.previewOpts.cycle { if t.previewOpts.cycle {
@ -2751,6 +2785,7 @@ func (t *Terminal) Loop() {
newOffset = util.Constrain(newOffset, headerLines, numLines-1) newOffset = util.Constrain(newOffset, headerLines, numLines-1)
if t.previewer.offset != newOffset { if t.previewer.offset != newOffset {
t.previewer.offset = newOffset t.previewer.offset = newOffset
t.previewer.following.Set(t.previewer.offset >= numLines-(t.pwindow.Height()-headerLines))
req(reqPreviewRefresh) req(reqPreviewRefresh)
} }
} }
@ -3177,7 +3212,7 @@ func (t *Terminal) Loop() {
y = util.Constrain(y, 0, effectiveHeight-barLength) y = util.Constrain(y, 0, effectiveHeight-barLength)
// offset = (total - maxItems) * barStart / (maxItems - barLength) // offset = (total - maxItems) * barStart / (maxItems - barLength)
t.previewer.offset = headerLines + int(math.Ceil(float64(y)*float64(numLines-effectiveHeight)/float64(effectiveHeight-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) req(reqPreviewRefresh)
} }
break break
@ -3322,7 +3357,7 @@ func (t *Terminal) Loop() {
} }
// Resume following // Resume following
t.previewer.following = t.previewOpts.follow t.previewer.following.Force(t.previewOpts.follow)
case actNextSelected, actPrevSelected: case actNextSelected, actPrevSelected:
if len(t.selected) > 0 { if len(t.selected) > 0 {
total := t.merger.Length() total := t.merger.Length()

View File

@ -1943,8 +1943,69 @@ class TestGoFZF < TestBase
end end
def test_preview_window_follow def test_preview_window_follow
tmux.send_keys "#{FZF} --preview 'seq 1000 | nl' --preview-window down:noborder:follow", :Enter file = Tempfile.new('fzf-follow')
tmux.until { |lines| assert_equal '1000 1000', lines[-1].strip } 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 end
def test_toggle_preview_wrap def test_toggle_preview_wrap