mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2025-03-29 22:12:21 +00:00
Resume preview following if the user scrolls the window to the bottom
This commit is contained in:
parent
0f032235cf
commit
e97e925efb
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user