mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-06-03 07:50:49 +00:00
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
|
# Send actions to the server
|
||||||
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
|
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
|
```sh
|
||||||
# Hide scrollbar
|
# Hide scrollbar
|
||||||
fzf --no-scrollbar
|
fzf --no-scrollbar
|
||||||
|
|
115
src/terminal.go
115
src/terminal.go
|
@ -77,6 +77,7 @@ type previewer struct {
|
||||||
final bool
|
final bool
|
||||||
following bool
|
following bool
|
||||||
spinner string
|
spinner string
|
||||||
|
bar []bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type previewed struct {
|
type previewed struct {
|
||||||
|
@ -601,7 +602,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, showPreviewWindow, false, true, false, ""},
|
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, "", []bool{}},
|
||||||
previewed: previewed{0, 0, 0, false},
|
previewed: previewed{0, 0, 0, false},
|
||||||
previewBox: previewBox,
|
previewBox: previewBox,
|
||||||
eventBox: eventBox,
|
eventBox: eventBox,
|
||||||
|
@ -774,27 +775,24 @@ func (t *Terminal) noInfoLine() bool {
|
||||||
return t.infoStyle != infoDefault
|
return t.infoStyle != infoDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) getScrollbar() (int, int) {
|
func getScrollbar(total int, height int, offset int) (int, int) {
|
||||||
total := t.merger.Length()
|
if total == 0 || total <= height {
|
||||||
if total == 0 {
|
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
|
barLength := util.Max(1, height*height/total)
|
||||||
maxItems := t.maxItems()
|
|
||||||
barLength := util.Max(1, maxItems*maxItems/total)
|
|
||||||
if total <= maxItems {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var barStart int
|
var barStart int
|
||||||
if total == maxItems {
|
if total == height {
|
||||||
barStart = 0
|
barStart = 0
|
||||||
} else {
|
} else {
|
||||||
barStart = (maxItems - barLength) * t.offset / (total - maxItems)
|
barStart = (height - barLength) * offset / (total - height)
|
||||||
}
|
}
|
||||||
return barLength, barStart
|
return barLength, barStart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) getScrollbar() (int, int) {
|
||||||
|
return getScrollbar(t.merger.Length(), t.maxItems(), t.offset)
|
||||||
|
}
|
||||||
|
|
||||||
// Input returns current query string
|
// Input returns current query string
|
||||||
func (t *Terminal) Input() (bool, []rune) {
|
func (t *Terminal) Input() (bool, []rune) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
|
@ -1106,6 +1104,10 @@ func (t *Terminal) resizeWindows() {
|
||||||
pwidth -= 4
|
pwidth -= 4
|
||||||
x += 2
|
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)
|
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
|
||||||
}
|
}
|
||||||
verticalPad := 2
|
verticalPad := 2
|
||||||
|
@ -1127,6 +1129,10 @@ func (t *Terminal) resizeWindows() {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Put scrollbar closer to the right border for consistent look
|
||||||
|
if t.borderShape.HasRight() {
|
||||||
|
width++
|
||||||
|
}
|
||||||
if previewOpts.position == posUp {
|
if previewOpts.position == posUp {
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
||||||
|
@ -1145,19 +1151,35 @@ func (t *Terminal) resizeWindows() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if previewOpts.position == posLeft {
|
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(
|
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)
|
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
||||||
} else {
|
} else {
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
|
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)
|
resizePreviewWindows(t.previewOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Without preview window
|
||||||
if t.window == nil {
|
if t.window == nil {
|
||||||
|
if t.borderShape.HasRight() {
|
||||||
|
// Put scrollbar closer to the right border for consistent look
|
||||||
|
width++
|
||||||
|
}
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0],
|
marginInt[0],
|
||||||
marginInt[3],
|
marginInt[3],
|
||||||
|
@ -1677,6 +1699,14 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
|
||||||
if !unchanged {
|
if !unchanged {
|
||||||
t.pwindow.FinishFill()
|
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) {
|
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() {
|
func (t *Terminal) printPreview() {
|
||||||
if !t.hasPreviewWindow() {
|
if !t.hasPreviewWindow() {
|
||||||
return
|
return
|
||||||
|
@ -2598,6 +2662,7 @@ func (t *Terminal) Loop() {
|
||||||
}()
|
}()
|
||||||
previewDraggingPos := -1
|
previewDraggingPos := -1
|
||||||
barDragging := false
|
barDragging := false
|
||||||
|
pbarDragging := false
|
||||||
wasDown := false
|
wasDown := false
|
||||||
for looping {
|
for looping {
|
||||||
var newCommand *string
|
var newCommand *string
|
||||||
|
@ -3048,6 +3113,7 @@ func (t *Terminal) Loop() {
|
||||||
wasDown = me.Down
|
wasDown = me.Down
|
||||||
if !me.Down {
|
if !me.Down {
|
||||||
barDragging = false
|
barDragging = false
|
||||||
|
pbarDragging = false
|
||||||
previewDraggingPos = -1
|
previewDraggingPos = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3066,7 +3132,7 @@ func (t *Terminal) Loop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preview dragging
|
// 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 {
|
if previewDraggingPos > 0 {
|
||||||
scrollPreviewBy(previewDraggingPos - my)
|
scrollPreviewBy(previewDraggingPos - my)
|
||||||
}
|
}
|
||||||
|
@ -3074,6 +3140,23 @@ func (t *Terminal) Loop() {
|
||||||
break
|
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
|
// Ignored
|
||||||
if !t.window.Enclose(my, mx) && !barDragging {
|
if !t.window.Enclose(my, mx) && !barDragging {
|
||||||
break
|
break
|
||||||
|
|
|
@ -307,6 +307,22 @@ const (
|
||||||
BorderRight
|
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 {
|
type BorderStyle struct {
|
||||||
shape BorderShape
|
shape BorderShape
|
||||||
horizontal rune
|
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
|
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
|
expected = <<~OUTPUT
|
||||||
│
|
│
|
||||||
│ 1 │> 3
|
│ 1 │ > 3
|
||||||
│ 2 │ 2
|
│ 2 │ 2
|
||||||
│ 3 │ 1
|
│ 3 │ 1
|
||||||
│ │ hello
|
│ │ hello
|
||||||
│ │ world
|
│ │ world
|
||||||
│ │ 1/1 ─
|
│ │ 1/1 ─
|
||||||
│ │>
|
│ │ >
|
||||||
│
|
│
|
||||||
OUTPUT
|
OUTPUT
|
||||||
tmux.until { assert_block(expected, _1) }
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
|
Loading…
Reference in New Issue
Block a user