Add scrollbar to the preview window

pull/3117/head
Junegunn Choi 1 year ago
parent ee5cdb9713
commit 3b2244077d
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627

@ -12,7 +12,7 @@ CHANGELOG
# Send actions to the server
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
```
- Added scrollbar on the main search window
- Added draggable scrollbar to the main search window and the preview window
```sh
# Hide scrollbar
fzf --no-scrollbar

@ -77,6 +77,7 @@ type previewer struct {
final bool
following bool
spinner string
bar []bool
}
type previewed struct {
@ -601,7 +602,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
reqBox: util.NewEventBox(),
initialPreviewOpts: opts.Preview,
previewOpts: opts.Preview,
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, "", []bool{}},
previewed: previewed{0, 0, 0, false},
previewBox: previewBox,
eventBox: eventBox,
@ -774,27 +775,24 @@ func (t *Terminal) noInfoLine() bool {
return t.infoStyle != infoDefault
}
func (t *Terminal) getScrollbar() (int, int) {
total := t.merger.Length()
if total == 0 {
func getScrollbar(total int, height int, offset int) (int, int) {
if total == 0 || total <= height {
return 0, 0
}
maxItems := t.maxItems()
barLength := util.Max(1, maxItems*maxItems/total)
if total <= maxItems {
return 0, 0
}
barLength := util.Max(1, height*height/total)
var barStart int
if total == maxItems {
if total == height {
barStart = 0
} else {
barStart = (maxItems - barLength) * t.offset / (total - maxItems)
barStart = (height - barLength) * offset / (total - height)
}
return barLength, barStart
}
func (t *Terminal) getScrollbar() (int, int) {
return getScrollbar(t.merger.Length(), t.maxItems(), t.offset)
}
// Input returns current query string
func (t *Terminal) Input() (bool, []rune) {
t.mutex.Lock()
@ -1106,6 +1104,10 @@ func (t *Terminal) resizeWindows() {
pwidth -= 4
x += 2
}
if len(t.scrollbar) > 0 && !previewOpts.border.HasRight() {
// Need a column to show scrollbar
pwidth -= 1
}
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
}
verticalPad := 2
@ -1127,6 +1129,10 @@ func (t *Terminal) resizeWindows() {
}
return
}
// Put scrollbar closer to the right border for consistent look
if t.borderShape.HasRight() {
width++
}
if previewOpts.position == posUp {
t.window = t.tui.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
@ -1145,19 +1151,35 @@ func (t *Terminal) resizeWindows() {
return
}
if previewOpts.position == posLeft {
// Put scrollbar closer to the right border for consistent look
if t.borderShape.HasRight() {
width++
}
// Add a 1-column margin between the preview window and the main window
t.window = t.tui.NewWindow(
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
marginInt[0], marginInt[3]+pwidth+1, width-pwidth-1, height, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
} else {
t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
// NOTE: fzf --preview 'cat {}' --preview-window border-left --border
x := marginInt[3] + width - pwidth
if !previewOpts.border.HasRight() && t.borderShape.HasRight() {
pwidth++
}
createPreviewWindow(marginInt[0], x, pwidth, height)
}
}
}
resizePreviewWindows(t.previewOpts)
}
// Without preview window
if t.window == nil {
if t.borderShape.HasRight() {
// Put scrollbar closer to the right border for consistent look
width++
}
t.window = t.tui.NewWindow(
marginInt[0],
marginInt[3],
@ -1677,6 +1699,14 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
if !unchanged {
t.pwindow.FinishFill()
}
if len(t.scrollbar) == 0 {
return
}
effectiveHeight := height - headerLines
barLength, barStart := getScrollbar(len(body), effectiveHeight, util.Min(len(body)-effectiveHeight, t.previewer.offset-headerLines))
t.renderPreviewScrollbar(headerLines, barLength, barStart)
}
func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {
@ -1741,6 +1771,40 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
}
}
func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int) {
height := t.pwindow.Height()
w := t.pborder.Width()
if len(t.previewer.bar) != height {
t.previewer.bar = make([]bool, height)
}
xshift := -2
if !t.previewOpts.border.HasRight() {
xshift = -1
}
yshift := 1
if !t.previewOpts.border.HasTop() {
yshift = 0
}
for i := yoff; i < height; i++ {
x := w + xshift
y := i + yshift
// Avoid unnecessary redraws
bar := i >= yoff+barStart && i < yoff+barStart+barLength
if bar == t.previewer.bar[i] {
continue
}
t.previewer.bar[i] = bar
t.pborder.Move(y, x)
if i >= yoff+barStart && i < yoff+barStart+barLength {
t.pborder.CPrint(tui.ColScrollbar, t.scrollbar)
} else {
t.pborder.Print(" ")
}
}
}
func (t *Terminal) printPreview() {
if !t.hasPreviewWindow() {
return
@ -2598,6 +2662,7 @@ func (t *Terminal) Loop() {
}()
previewDraggingPos := -1
barDragging := false
pbarDragging := false
wasDown := false
for looping {
var newCommand *string
@ -3048,6 +3113,7 @@ func (t *Terminal) Loop() {
wasDown = me.Down
if !me.Down {
barDragging = false
pbarDragging = false
previewDraggingPos = -1
}
@ -3066,7 +3132,7 @@ func (t *Terminal) Loop() {
}
// Preview dragging
if me.Down && (previewDraggingPos > 0 || clicked && t.hasPreviewWindow() && t.pwindow.Enclose(my, mx)) {
if me.Down && (previewDraggingPos >= 0 || clicked && t.hasPreviewWindow() && t.pwindow.Enclose(my, mx)) {
if previewDraggingPos > 0 {
scrollPreviewBy(previewDraggingPos - my)
}
@ -3074,6 +3140,23 @@ func (t *Terminal) Loop() {
break
}
// Prevew scrollbar dragging
headerLines := t.previewOpts.headerLines
pbarDragging = me.Down && (pbarDragging || clicked && t.hasPreviewWindow() && my >= t.pwindow.Top()+headerLines && my < t.pwindow.Top()+t.pwindow.Height() && mx == t.pwindow.Left()+t.pwindow.Width())
if pbarDragging {
effectiveHeight := t.pwindow.Height() - headerLines
numLines := len(t.previewer.lines) - headerLines
barLength, _ := getScrollbar(numLines, effectiveHeight, util.Min(numLines-effectiveHeight, t.previewer.offset-headerLines))
if barLength > 0 {
y := my - t.pwindow.Top() - headerLines - barLength/2
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)))
req(reqPreviewRefresh)
}
break
}
// Ignored
if !t.window.Enclose(my, mx) && !barDragging {
break

@ -307,6 +307,22 @@ const (
BorderRight
)
func (s BorderShape) HasRight() bool {
switch s {
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
return false
}
return true
}
func (s BorderShape) HasTop() bool {
switch s {
case BorderNone, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
return false
}
return true
}
type BorderStyle struct {
shape BorderShape
horizontal rune

@ -2380,13 +2380,13 @@ class TestGoFZF < TestBase
tmux.send_keys "seq 3 | fzf --height ~100% --border=vertical --preview 'seq {}' --preview-window left,5,border-right --padding 1 --exit-0 --header $'hello\\nworld' --header-lines=2", :Enter
expected = <<~OUTPUT
1 > 3
2 2
3 1
hello
world
1/1
>
1 > 3
2 2
3 1
hello
world
1/1
>
OUTPUT
tmux.until { assert_block(expected, _1) }

Loading…
Cancel
Save