From da9179335c637d91ab49256dd3c3e7dcdf319e19 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sun, 8 Dec 2024 20:03:15 +0900 Subject: [PATCH] Respect the properties of the currently active preview window options --- src/options.go | 46 ++++++++++++++++++++---- src/terminal.go | 93 +++++++++++++++++++++++++------------------------ test/test_go.rb | 8 +++++ 3 files changed, 95 insertions(+), 52 deletions(-) diff --git a/src/options.go b/src/options.go index 9238ac6..b4df2c1 100644 --- a/src/options.go +++ b/src/options.go @@ -381,14 +381,46 @@ func (a previewOpts) aboveOrBelow() bool { return a.size.size > 0 && (a.position == posUp || a.position == posDown) } -func (a previewOpts) sameLayout(b previewOpts) bool { - return a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden && a.threshold == b.threshold && - (a.alternative != nil && b.alternative != nil && a.alternative.sameLayout(*b.alternative) || - a.alternative == nil && b.alternative == nil) -} +type previewOptsCompare int -func (a previewOpts) sameContentLayout(b previewOpts) bool { - return a.wrap == b.wrap && a.headerLines == b.headerLines && a.info == b.info +const ( + previewOptsSame previewOptsCompare = iota + previewOptsDifferentContentLayout + previewOptsDifferentLayout +) + +func (o *previewOpts) compare(active *previewOpts, b *previewOpts) previewOptsCompare { + a := o + + sameThreshold := o.position == b.position && o.threshold == b.threshold + // Alternative layout is being used + if o.alternative == active { + a = active + + // If the other also has an alternative layout, + if b.alternative != nil { + // and if the same condition is the same, compare alt vs. alt. + if sameThreshold { + b = b.alternative + } else { + // If not, we pessimistically decide that the layouts may not be the same + return previewOptsDifferentLayout + } + } + } else if b.alternative != nil && !sameThreshold { + // We may choose the other's alternative layout, so let's be conservative. + return previewOptsDifferentLayout + } + + if !(a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden) { + return previewOptsDifferentLayout + } + + if a.wrap == b.wrap && a.headerLines == b.headerLines && a.info == b.info && a.scroll == b.scroll { + return previewOptsSame + } + + return previewOptsDifferentContentLayout } func firstLine(s string) string { diff --git a/src/terminal.go b/src/terminal.go index 725bf8c..74ad7f7 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -856,6 +856,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor reqBox: util.NewEventBox(), initialPreviewOpts: opts.Preview, previewOpts: opts.Preview, + activePreviewOpts: &opts.Preview, previewer: previewer{0, []string{}, 0, false, true, disabledState, "", []bool{}}, previewed: previewed{0, 0, 0, false, false, false, false}, previewBox: previewBox, @@ -1498,9 +1499,9 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) { minAreaHeight -= 1 } if t.needPreviewWindow() { - minPreviewHeight := 1 + borderLines(t.previewOpts.border) + minPreviewHeight := 1 + borderLines(t.activePreviewOpts.border) minPreviewWidth := 5 - switch t.previewOpts.position { + switch t.activePreviewOpts.position { case posUp, posDown: minAreaHeight += minPreviewHeight minAreaWidth = util.Max(minPreviewWidth, minAreaWidth) @@ -1542,7 +1543,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) { t.previewed.version = 0 bw := t.borderWidth - offsets := [4]int{} // TRWH + offsets := [4]int{} // TRWH if t.borderShape.HasTop() { offsets[0] -= 1 offsets[3] += 1 @@ -1593,10 +1594,10 @@ func (t *Terminal) resizeWindows(forcePreview bool) { t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder) pwidth -= borderColumns(previewOpts.border, bw) pheight -= borderLines(previewOpts.border) - if t.previewOpts.border.HasLeft() { + if previewOpts.border.HasLeft() { x += 1 + bw } - if t.previewOpts.border.HasTop() { + if previewOpts.border.HasTop() { y += 1 } if len(t.scrollbar) > 0 && !previewOpts.border.HasRight() { @@ -1714,7 +1715,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) { // Print border label t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false) - t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, false) + t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.border, false) } func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) { @@ -2531,7 +2532,7 @@ func (t *Terminal) renderPreviewSpinner() { spin := t.previewer.spinner if len(spin) > 0 || t.previewer.scrollable { maxWidth := t.pwindow.Width() - if !t.previewer.scrollable || !t.previewOpts.info { + if !t.previewer.scrollable || !t.activePreviewOpts.info { if maxWidth > 0 { t.pwindow.Move(0, maxWidth-1) t.pwindow.CPrint(tui.ColPreviewSpinner, spin) @@ -2573,7 +2574,7 @@ func (t *Terminal) renderPreviewArea(unchanged bool) { height := t.pwindow.Height() body := t.previewer.lines - headerLines := t.previewOpts.headerLines + headerLines := t.activePreviewOpts.headerLines // Do not enable preview header lines if it's value is too large if headerLines > 0 && headerLines < util.Min(len(body), height) { header := t.previewer.lines[0:headerLines] @@ -2773,7 +2774,7 @@ Loop: _, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool { trimmed := []rune(str) isTrimmed := false - if !t.previewOpts.wrap { + if !t.activePreviewOpts.wrap { trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X()) } if url == nil && ansi != nil && ansi.url != nil { @@ -2799,7 +2800,7 @@ Loop: } } return !isTrimmed && - (fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine) + (fillRet == tui.FillContinue || t.activePreviewOpts.wrap && fillRet == tui.FillNextLine) }) if url != nil { t.pwindow.LinkEnd() @@ -2840,11 +2841,11 @@ func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int) t.previewer.bar = make([]bool, height) } xshift := -1 - t.borderWidth - if !t.previewOpts.border.HasRight() { + if !t.activePreviewOpts.border.HasRight() { xshift = -1 } yshift := 1 - if !t.previewOpts.border.HasTop() { + if !t.activePreviewOpts.border.HasTop() { yshift = 0 } for i := yoff; i < height; i++ { @@ -2876,7 +2877,7 @@ func (t *Terminal) printPreview() { unchanged := (t.previewed.filled || numLines == t.previewed.numLines) && t.previewer.version == t.previewed.version && t.previewer.offset == t.previewed.offset - t.previewer.scrollable = t.previewer.offset > t.previewOpts.headerLines || numLines > height + t.previewer.scrollable = t.previewer.offset > t.activePreviewOpts.headerLines || numLines > height t.renderPreviewArea(unchanged) t.renderPreviewSpinner() t.previewed.numLines = numLines @@ -3093,7 +3094,7 @@ func (t *Terminal) evaluateScrollOffset() int { } // We only need the current item to calculate the scroll offset - replaced, tempFiles := t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil}) + replaced, tempFiles := t.replacePlaceholder(t.activePreviewOpts.scroll, false, "", []*Item{t.currentItem(), nil}) removeFiles(tempFiles) offsetExpr := offsetTrimCharsRegex.ReplaceAllString(replaced, "") @@ -3106,7 +3107,7 @@ func (t *Terminal) evaluateScrollOffset() int { } base := -1 - height := util.Max(0, t.pwindow.Height()-t.previewOpts.headerLines) + height := util.Max(0, t.pwindow.Height()-t.activePreviewOpts.headerLines) for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) { if strings.HasPrefix(component, "-/") { component = component[1:] @@ -3352,12 +3353,12 @@ func (t *Terminal) hasPreviewer() bool { } func (t *Terminal) needPreviewWindow() bool { - return t.hasPreviewer() && len(t.previewOpts.command) > 0 && t.previewOpts.Visible() + return t.hasPreviewer() && len(t.previewOpts.command) > 0 && t.activePreviewOpts.Visible() } // Check if previewer is currently in action (invisible previewer with size 0 or visible previewer) func (t *Terminal) canPreview() bool { - return t.hasPreviewer() && (!t.previewOpts.Visible() && !t.previewOpts.hidden || t.hasPreviewWindow()) + return t.hasPreviewer() && (!t.activePreviewOpts.Visible() && !t.activePreviewOpts.hidden || t.hasPreviewWindow()) } func (t *Terminal) hasPreviewWindow() bool { @@ -3486,16 +3487,16 @@ func (t *Terminal) Loop() error { t.tui.Resize(func(termHeight int) int { contentHeight := fit + t.extraLines() if t.needPreviewWindow() { - if t.previewOpts.aboveOrBelow() { - if t.previewOpts.size.percent { - newContentHeight := int(float64(contentHeight) * 100. / (100. - t.previewOpts.size.size)) - contentHeight = util.Max(contentHeight+1+borderLines(t.previewOpts.border), newContentHeight) + if t.activePreviewOpts.aboveOrBelow() { + if t.activePreviewOpts.size.percent { + newContentHeight := int(float64(contentHeight) * 100. / (100. - t.activePreviewOpts.size.size)) + contentHeight = util.Max(contentHeight+1+borderLines(t.activePreviewOpts.border), newContentHeight) } else { - contentHeight += int(t.previewOpts.size.size) + borderLines(t.previewOpts.border) + contentHeight += int(t.activePreviewOpts.size.size) + borderLines(t.activePreviewOpts.border) } } else { // Minimum height if preview window can appear - contentHeight = util.Max(contentHeight, 1+borderLines(t.previewOpts.border)) + contentHeight = util.Max(contentHeight, 1+borderLines(t.activePreviewOpts.border)) } } return util.Min(termHeight, contentHeight+pad) @@ -3843,7 +3844,7 @@ func (t *Terminal) Loop() error { case reqRedrawBorderLabel: t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true) case reqRedrawPreviewLabel: - t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, true) + t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.border, true) case reqReinit: t.tui.Resume(t.fullscreen, true) t.fullRedraw() @@ -3871,7 +3872,7 @@ func (t *Terminal) Loop() error { result := value.(previewResult) if t.previewer.version != result.version { t.previewer.version = result.version - t.previewer.following.Force(t.previewOpts.follow) + t.previewer.following.Force(t.activePreviewOpts.follow) if t.previewer.following.Enabled() { t.previewer.offset = 0 } @@ -3879,9 +3880,9 @@ func (t *Terminal) Loop() error { t.previewer.lines = result.lines t.previewer.spinner = result.spinner if t.hasPreviewWindow() && t.previewer.following.Enabled() { - t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.previewOpts.headerLines)) + t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.activePreviewOpts.headerLines)) } 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.activePreviewOpts.headerLines, len(t.previewer.lines)-1) } t.printPreview() case reqPreviewRefresh: @@ -4037,8 +4038,8 @@ func (t *Terminal) Loop() error { return } numLines := len(t.previewer.lines) - headerLines := t.previewOpts.headerLines - if t.previewOpts.cycle { + headerLines := t.activePreviewOpts.headerLines + if t.activePreviewOpts.cycle { offsetRange := numLines - headerLines newOffset = ((newOffset-headerLines)+offsetRange)%offsetRange + headerLines } @@ -4137,7 +4138,7 @@ func (t *Terminal) Loop() error { } case actTogglePreviewWrap: if t.hasPreviewWindow() { - t.previewOpts.wrap = !t.previewOpts.wrap + t.activePreviewOpts.wrap = !t.activePreviewOpts.wrap // Reset preview version so that full redraw occurs t.previewed.version = 0 req(reqPreviewRefresh) @@ -4699,7 +4700,7 @@ func (t *Terminal) Loop() error { } // Preview scrollbar dragging - headerLines := t.previewOpts.headerLines + headerLines := t.activePreviewOpts.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 @@ -4719,12 +4720,12 @@ func (t *Terminal) Loop() error { // Preview border dragging (resizing) pborderDragging = me.Down && (pborderDragging || clicked && t.hasPreviewWindow() && t.pborder.Enclose(my, mx)) if pborderDragging { - previewWidth := t.pwindow.Width() + borderColumns(t.previewOpts.border, t.borderWidth) - previewHeight := t.pwindow.Height() + borderLines(t.previewOpts.border) - minPreviewWidth := 1 + borderColumns(t.previewOpts.border, t.borderWidth) - minPreviewHeight := 1 + borderLines(t.previewOpts.border) + previewWidth := t.pwindow.Width() + borderColumns(t.activePreviewOpts.border, t.borderWidth) + previewHeight := t.pwindow.Height() + borderLines(t.activePreviewOpts.border) + minPreviewWidth := 1 + borderColumns(t.activePreviewOpts.border, t.borderWidth) + minPreviewHeight := 1 + borderLines(t.activePreviewOpts.border) - if len(t.scrollbar) > 0 && t.previewOpts.position == posLeft && !t.previewOpts.border.HasRight() { + if len(t.scrollbar) > 0 && t.activePreviewOpts.position == posLeft && !t.activePreviewOpts.border.HasRight() { // Need a column to show scrollbar minPreviewWidth++ } @@ -4739,15 +4740,15 @@ func (t *Terminal) Loop() error { previewTop := t.pwindow.Top() // Unlike window, pwindow does not include it's border, so // Left and Top have to be adjusted. - if t.previewOpts.border.HasLeft() { + if t.activePreviewOpts.border.HasLeft() { previewLeft -= 1 + t.borderWidth } - if t.previewOpts.border.HasTop() { + if t.activePreviewOpts.border.HasTop() { previewTop -= 1 } var newSize int - switch t.previewOpts.position { + switch t.activePreviewOpts.position { case posUp: top := previewTop + minPreviewHeight // +1 since index to size @@ -4769,11 +4770,11 @@ func (t *Terminal) Loop() error { } // don't update if the size did not change (e.g. off-axis movement) - if !t.previewOpts.size.percent && t.previewOpts.size.size == float64(newSize) { + if !t.activePreviewOpts.size.percent && t.activePreviewOpts.size.size == float64(newSize) { break } - t.previewOpts.size = sizeSpec{float64(newSize), false} + t.activePreviewOpts.size = sizeSpec{float64(newSize), false} updatePreviewWindow(false) req(reqPreviewRefresh) break @@ -4922,6 +4923,7 @@ func (t *Terminal) Loop() error { refreshPreview(t.previewOpts.command) } case actChangePreviewWindow: + // NOTE: We intentionally use "previewOpts" instead of "activePreviewOpts" here currentPreviewOpts := t.previewOpts // Reset preview options and apply the additional options @@ -4939,7 +4941,8 @@ func (t *Terminal) Loop() error { } // Full redraw - if !currentPreviewOpts.sameLayout(t.previewOpts) { + switch currentPreviewOpts.compare(t.activePreviewOpts, &t.previewOpts) { + case previewOptsDifferentLayout: // Preview command can be running in the background if the size of // the preview window is 0 but not 'hidden' wasHidden := currentPreviewOpts.hidden @@ -4947,20 +4950,20 @@ func (t *Terminal) Loop() error { if wasHidden && t.hasPreviewWindow() { // Restart refreshPreview(t.previewOpts.command) - } else if t.previewOpts.hidden { + } else if t.activePreviewOpts.hidden { // Cancel t.cancelPreview() } else { // Refresh req(reqPreviewRefresh) } - } else if !currentPreviewOpts.sameContentLayout(t.previewOpts) { + case previewOptsDifferentContentLayout: t.previewed.version = 0 req(reqPreviewRefresh) } // Adjust scroll offset - if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.previewOpts.scroll { + if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.activePreviewOpts.scroll { scrollPreviewTo(t.evaluateScrollOffset()) } diff --git a/test/test_go.rb b/test/test_go.rb index 433fdef..e343b96 100755 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -3073,6 +3073,14 @@ class TestGoFZF < TestBase tmux.until { |lines| assert_includes lines, '/1/1/' } end + def test_alternative_preview_window_opts + tmux.send_keys "seq 10 | #{FZF} --preview-window '~5,2,+0,<100000(~0,+100,wrap,noinfo)' --preview 'seq 1000'", :Enter + tmux.until { |lines| assert_equal 10, lines.item_count } + tmux.until do |lines| + assert_equal ['╭────╮', '│ 10 │', '│ 0 │', '│ 10 │', '│ 1 │'], lines.take(5).map(&:strip) + end + end + def test_become tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter tmux.until { |lines| assert_equal 100, lines.item_count }