mirror of https://github.com/Llewellynvdm/fzf.git
Add scrollbar to the preview window
This commit is contained in:
parent
ee5cdb9713
commit
3b2244077d
|
@ -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
|
||||
|
|
115
src/terminal.go
115
src/terminal.go
|
@ -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…
Reference in New Issue