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
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

View File

@ -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()

View File

@ -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