summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2023-01-06 15:36:12 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2023-01-06 15:36:12 +0900
commit3b2244077d3e7d299943077b33e0564ffcd1f384 (patch)
tree7baafc13c662d489b1019c57d4059f08a028c4f4 /src
parentee5cdb9713715883ac6d2cd87bc8a091ff7c8361 (diff)
downloadfzf-3b2244077d3e7d299943077b33e0564ffcd1f384.tar.gz
Add scrollbar to the preview window
Diffstat (limited to 'src')
-rw-r--r--src/terminal.go115
-rw-r--r--src/tui/tui.go16
2 files changed, 115 insertions, 16 deletions
diff --git a/src/terminal.go b/src/terminal.go
index dfcc4a72..28823675 100644
--- a/src/terminal.go
+++ b/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
diff --git a/src/tui/tui.go b/src/tui/tui.go
index 063c4e6f..c6a65d82 100644
--- a/src/tui/tui.go
+++ b/src/tui/tui.go
@@ -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