From 22cbd9fa58512ffdcc975bab37a55467d5e10968 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Thu, 8 Sep 2022 01:01:22 +0900 Subject: Implement height range (--height ~[VALUE][%]) Close #2953 --- src/terminal.go | 151 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 111 insertions(+), 40 deletions(-) (limited to 'src/terminal.go') diff --git a/src/terminal.go b/src/terminal.go index 981f13a4..493c8b9f 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -100,6 +100,11 @@ type itemLine struct { result Result } +type fitpad struct { + fit int + pad int +} + var emptyLine = itemLine{} // Terminal represents terminal input/output @@ -183,7 +188,7 @@ type Terminal struct { prevLines []itemLine suppress bool sigstop bool - startChan chan bool + startChan chan fitpad killChan chan int slab *util.Slab theme *tui.ColorTheme @@ -439,6 +444,13 @@ func makeSpinner(unicode bool) []string { return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`} } +func evaluateHeight(opts *Options, termHeight int) int { + if opts.Height.percent { + return util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight) + } + return int(opts.Height.size) +} + // NewTerminal returns new Terminal object func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { input := trimQuery(opts.Query) @@ -465,7 +477,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { strongAttr = tui.AttrRegular } var renderer tui.Renderer - fullscreen := opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100 + fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100) if fullscreen { if tui.HasFullscreenRenderer() { renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse) @@ -475,24 +487,16 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { } } else { maxHeightFunc := func(termHeight int) int { - var maxHeight int - if opts.Height.percent { - maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight) - } else { - maxHeight = int(opts.Height.size) - } - + // Minimum height required to render fzf excluding margin and padding effectiveMinHeight := minHeight - if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) { - effectiveMinHeight *= 2 + if previewBox != nil && opts.Preview.aboveOrBelow() { + effectiveMinHeight += 1 + borderLines(opts.Preview.border) } if opts.InfoStyle != infoDefault { effectiveMinHeight-- } - if opts.BorderShape != tui.BorderNone { - effectiveMinHeight += 2 - } - return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight)) + effectiveMinHeight += borderLines(opts.BorderShape) + return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight)) } renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc) } @@ -572,7 +576,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { sigstop: false, slab: util.MakeSlab(slab16Size, slab32Size), theme: opts.Theme, - startChan: make(chan bool, 1), + startChan: make(chan fitpad, 1), killChan: make(chan int), tui: renderer, initFunc: func() { renderer.Init() }, @@ -587,6 +591,32 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { return &t } +func borderLines(shape tui.BorderShape) int { + switch shape { + case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp: + return 2 + case tui.BorderTop, tui.BorderBottom: + return 1 + } + return 0 +} + +// Extra number of lines needed to display fzf +func (t *Terminal) extraLines() int { + extra := len(t.header0) + t.headerLines + 1 + if !t.noInfoLine() { + extra++ + } + return extra +} + +func (t *Terminal) MaxFitAndPad(opts *Options) (int, int) { + _, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding() + padHeight := marginInt[0] + marginInt[2] + paddingInt[0] + paddingInt[2] + fit := screenHeight - padHeight - t.extraLines() + return fit, padHeight +} + func (t *Terminal) parsePrompt(prompt string) (func(), int) { var state *ansiState trimmed, colors, _ := extractColor(prompt, state, nil) @@ -725,22 +755,23 @@ func (t *Terminal) displayWidth(runes []rune) int { const ( minWidth = 4 - minHeight = 4 + minHeight = 3 ) func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int { max := base - occupied + if max < minSize { + max = minSize + } if size.percent { return util.Constrain(int(float64(base)*0.01*size.size), minSize, max) } return util.Constrain(int(size.size)+pad, minSize, max) } -func (t *Terminal) resizeWindows() { +func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) { screenWidth := t.tui.MaxX() screenHeight := t.tui.MaxY() - t.prevLines = make([]itemLine, screenHeight) - marginInt := [4]int{} // TRBL paddingInt := [4]int{} // TRBL sizeSpecToInt := func(index int, spec sizeSpec) int { @@ -789,31 +820,48 @@ func (t *Terminal) resizeWindows() { } adjust := func(idx1 int, idx2 int, max int, min int) { - if max >= min { - margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2] - if max-margin < min { - desired := max - min - paddingInt[idx1] = desired * paddingInt[idx1] / margin - paddingInt[idx2] = desired * paddingInt[idx2] / margin - marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin) - marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin) - } + if min > max { + min = max + } + margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2] + if max-margin < min { + desired := max - min + paddingInt[idx1] = desired * paddingInt[idx1] / margin + paddingInt[idx2] = desired * paddingInt[idx2] / margin + marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin) + marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin) } } - previewVisible := t.isPreviewEnabled() && t.previewOpts.size.size > 0 minAreaWidth := minWidth minAreaHeight := minHeight - if previewVisible { + if t.noInfoLine() { + minAreaHeight -= 1 + } + if t.isPreviewVisible() { + minPreviewHeight := 1 + borderLines(t.previewOpts.border) + minPreviewWidth := 5 switch t.previewOpts.position { case posUp, posDown: - minAreaHeight *= 2 + minAreaHeight += minPreviewHeight + minAreaWidth = util.Max(minPreviewWidth, minAreaWidth) case posLeft, posRight: - minAreaWidth *= 2 + minAreaWidth += minPreviewWidth + minAreaHeight = util.Max(minPreviewHeight, minAreaHeight) } } adjust(1, 3, screenWidth, minAreaWidth) adjust(0, 2, screenHeight, minAreaHeight) + + return screenWidth, screenHeight, marginInt, paddingInt +} + +func (t *Terminal) resizeWindows() { + screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding() + width := screenWidth - marginInt[1] - marginInt[3] + height := screenHeight - marginInt[0] - marginInt[2] + + t.prevLines = make([]itemLine, screenHeight) if t.border != nil { t.border.Close() } @@ -832,8 +880,6 @@ func (t *Terminal) resizeWindows() { // Reset preview version so that full redraw occurs t.previewed.version = 0 - width := screenWidth - marginInt[1] - marginInt[3] - height := screenHeight - marginInt[0] - marginInt[2] switch t.borderShape { case tui.BorderHorizontal: t.border = t.tui.NewWindow( @@ -865,16 +911,16 @@ func (t *Terminal) resizeWindows() { false, tui.MakeBorderStyle(t.borderShape, t.unicode)) } - // Add padding + // Add padding to margin for idx, val := range paddingInt { marginInt[idx] += val } - width = screenWidth - marginInt[1] - marginInt[3] - height = screenHeight - marginInt[0] - marginInt[2] + width -= paddingInt[1] + paddingInt[3] + height -= paddingInt[0] + paddingInt[2] // Set up preview window noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) - if previewVisible { + if t.isPreviewVisible() { var resizePreviewWindows func(previewOpts previewOpts) resizePreviewWindows = func(previewOpts previewOpts) { hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil @@ -1863,6 +1909,10 @@ func (t *Terminal) isPreviewEnabled() bool { return t.hasPreviewer() && t.previewer.enabled } +func (t *Terminal) isPreviewVisible() bool { + return t.isPreviewEnabled() && t.previewOpts.size.size > 0 +} + func (t *Terminal) hasPreviewWindow() bool { return t.pwindow != nil && t.isPreviewEnabled() } @@ -1962,7 +2012,28 @@ func (t *Terminal) cancelPreview() { // Loop is called to start Terminal I/O func (t *Terminal) Loop() { // prof := profile.Start(profile.ProfilePath("/tmp/")) - <-t.startChan + fitpad := <-t.startChan + fit := fitpad.fit + if fit >= 0 { + pad := fitpad.pad + t.tui.Resize(func(termHeight int) int { + contentHeight := fit + t.extraLines() + if t.hasPreviewer() { + 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) + } else { + contentHeight += int(t.previewOpts.size.size) + borderLines(t.previewOpts.border) + } + } else { + // Minimum height if preview window can appear + contentHeight = util.Max(contentHeight, 1+borderLines(t.previewOpts.border)) + } + } + return util.Min(termHeight, contentHeight+pad) + }) + } { // Late initialization intChan := make(chan os.Signal, 1) signal.Notify(intChan, os.Interrupt, syscall.SIGTERM) -- cgit v1.2.3