Implement height range (--height ~[VALUE][%])

Close #2953
This commit is contained in:
Junegunn Choi 2022-09-08 01:01:22 +09:00
parent 984049586a
commit 22cbd9fa58
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
11 changed files with 329 additions and 67 deletions

View File

@ -1,6 +1,36 @@
CHANGELOG CHANGELOG
========= =========
0.34.0
------
- Added support for adaptive `--height`. If the `--height` value is prefixed
with `~`, fzf will automatically determine the height in the range according
to the input size.
```sh
seq 1 | fzf --height ~70% --border --padding 1 --margin 1
seq 10 | fzf --height ~70% --border --padding 1 --margin 1
seq 100 | fzf --height ~70% --border --padding 1 --margin 1
```
- There are a few limitations
- Not compatible with percent top/bottom margin/padding
```sh
# This is not allowed (top/bottom margin in percent value)
fzf --height ~50% --border --margin 5%,10%
# This is allowed (top/bottom margin in fixed value)
fzf --height ~50% --border --margin 2,10%
```
- fzf will not start until it can determine the right height for the input
```sh
# fzf will open immediately
(sleep 2; seq 10) | fzf --height 50%
# fzf will open after 2 seconds
(sleep 2; seq 10) | fzf --height ~50%
(sleep 2; seq 1000) | fzf --height ~50%
```
- Fixed tcell renderer used to render full-screen fzf on Windows
0.33.0 0.33.0
------ ------
- Added `--scheme=[default|path|history]` option to choose scoring scheme - Added `--scheme=[default|path|history]` option to choose scoring scheme

View File

@ -155,6 +155,7 @@ target/$(BINARYLOONG64): $(SOURCES)
GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@ GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@
bin/fzf: target/$(BINARY) | bin bin/fzf: target/$(BINARY) | bin
-rm -f bin/fzf
cp -f target/$(BINARY) bin/fzf cp -f target/$(BINARY) bin/fzf
docker: docker:

View File

@ -177,9 +177,11 @@ actions are affected:
Label characters for \fBjump\fR and \fBjump-accept\fR Label characters for \fBjump\fR and \fBjump-accept\fR
.SS Layout .SS Layout
.TP .TP
.BI "--height=" "HEIGHT[%]" .BI "--height=" "[~]HEIGHT[%]"
Display fzf window below the cursor with the given height instead of using Display fzf window below the cursor with the given height instead of using
the full screen. the full screen. When prefixed with \fB~\fR, fzf will automatically determine
the height in the range according to the input size. Note that adaptive height
is not compatible with top/bottom margin and padding given in percent size.
.TP .TP
.BI "--min-height=" "HEIGHT" .BI "--min-height=" "HEIGHT"
Minimum height when \fB--height\fR is given in percent (default: 10). Minimum height when \fB--height\fR is given in percent (default: 10).

View File

@ -194,10 +194,17 @@ func Run(opts *Options, version string, revision string) {
// Terminal I/O // Terminal I/O
terminal := NewTerminal(opts, eventBox) terminal := NewTerminal(opts, eventBox)
maxFit := 0 // Maximum number of items that can fit on screen
padHeight := 0
heightUnknown := opts.Height.auto
if heightUnknown {
maxFit, padHeight = terminal.MaxFitAndPad(opts)
}
deferred := opts.Select1 || opts.Exit0 deferred := opts.Select1 || opts.Exit0
go terminal.Loop() go terminal.Loop()
if !deferred { if !deferred && !heightUnknown {
terminal.startChan <- true // Start right away
terminal.startChan <- fitpad{-1, -1}
} }
// Event coordination // Event coordination
@ -216,7 +223,19 @@ func Run(opts *Options, version string, revision string) {
go reader.restart(command) go reader.restart(command)
} }
eventBox.Watch(EvtReadNew) eventBox.Watch(EvtReadNew)
total := 0
query := []rune{} query := []rune{}
determine := func(final bool) {
if heightUnknown {
if total >= maxFit || final {
heightUnknown = false
terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
}
} else if deferred {
deferred = false
terminal.startChan <- fitpad{-1, -1}
}
}
for { for {
delay := true delay := true
ticks++ ticks++
@ -249,11 +268,15 @@ func Run(opts *Options, version string, revision string) {
reading = reading && evt == EvtReadNew reading = reading && evt == EvtReadNew
} }
snapshot, count := chunkList.Snapshot() snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading, value.(*string)) total = count
terminal.UpdateCount(total, !reading, value.(*string))
if opts.Sync { if opts.Sync {
opts.Sync = false opts.Sync = false
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false) terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
} }
if heightUnknown && !deferred {
determine(!reading)
}
reset := clearCache() reset := clearCache()
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset) matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
@ -295,8 +318,7 @@ func Run(opts *Options, version string, revision string) {
if deferred { if deferred {
count := val.Length() count := val.Length()
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 { if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
deferred = false determine(val.final)
terminal.startChan <- true
} else if val.final { } else if val.final {
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 { if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
if opts.PrintQuery { if opts.PrintQuery {
@ -313,8 +335,7 @@ func Run(opts *Options, version string, revision string) {
} }
os.Exit(exitNoMatch) os.Exit(exitNoMatch)
} }
deferred = false determine(val.final)
terminal.startChan <- true
} }
} }
terminal.UpdateList(val, clearSelection()) terminal.UpdateList(val, clearSelection())

View File

@ -53,8 +53,10 @@ const usage = `usage: fzf [options]
--jump-labels=CHARS Label characters for jump and jump-accept --jump-labels=CHARS Label characters for jump and jump-accept
Layout Layout
--height=HEIGHT[%] Display fzf window below the cursor with the given --height=[~]HEIGHT[%] Display fzf window below the cursor with the given
height instead of using fullscreen height instead of using fullscreen.
If prefixed with '~', fzf will determine the height
according to the input size.
--min-height=HEIGHT Minimum height when --height is given in percent --min-height=HEIGHT Minimum height when --height is given in percent
(default: 10) (default: 10)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list] --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
@ -131,6 +133,12 @@ const (
byEnd byEnd
) )
type heightSpec struct {
size float64
percent bool
auto bool
}
type sizeSpec struct { type sizeSpec struct {
size float64 size float64
percent bool percent bool
@ -180,6 +188,10 @@ type previewOpts struct {
alternative *previewOpts alternative *previewOpts
} }
func (a previewOpts) aboveOrBelow() bool {
return a.size.size > 0 && (a.position == posUp || a.position == posDown)
}
func (a previewOpts) sameLayout(b previewOpts) bool { 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 && 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 && a.alternative.sameLayout(*b.alternative) ||
@ -211,7 +223,7 @@ type Options struct {
Theme *tui.ColorTheme Theme *tui.ColorTheme
Black bool Black bool
Bold bool Bold bool
Height sizeSpec Height heightSpec
MinHeight int MinHeight int
Layout layoutType Layout layoutType
Cycle bool Cycle bool
@ -1076,11 +1088,6 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) {
} }
if t == actUnbind || t == actRebind { if t == actUnbind || t == actRebind {
parseKeyChords(actionArg, spec[0:offset]+" target required") parseKeyChords(actionArg, spec[0:offset]+" target required")
} else if t == actChangePreviewWindow {
opts := previewOpts{}
for _, arg := range strings.Split(actionArg, "|") {
parsePreviewWindow(&opts, arg)
}
} }
} }
} }
@ -1160,9 +1167,17 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
return sizeSpec{val, percent} return sizeSpec{val, percent}
} }
func parseHeight(str string) sizeSpec { func parseHeight(str string) heightSpec {
heightSpec := heightSpec{}
if strings.HasPrefix(str, "~") {
heightSpec.auto = true
str = str[1:]
}
size := parseSize(str, 100, "height") size := parseSize(str, 100, "height")
return size heightSpec.size = size.size
heightSpec.percent = size.percent
return heightSpec
} }
func parseLayout(str string) layoutType { func parseLayout(str string) layoutType {
@ -1525,11 +1540,11 @@ func parseOptions(opts *Options, allArgs []string) {
parsePreviewWindow(&opts.Preview, parsePreviewWindow(&opts.Preview,
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]")) nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"))
case "--height": case "--height":
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]")) opts.Height = parseHeight(nextString(allArgs, &i, "height required: [~]HEIGHT[%]"))
case "--min-height": case "--min-height":
opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT") opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
case "--no-height": case "--no-height":
opts.Height = sizeSpec{} opts.Height = heightSpec{}
case "--no-margin": case "--no-margin":
opts.Margin = defaultMargin() opts.Margin = defaultMargin()
case "--no-padding": case "--no-padding":
@ -1709,6 +1724,7 @@ func postProcessOptions(opts *Options) {
} }
// Extend the default key map // Extend the default key map
previewEnabled := len(opts.Preview.command) > 0 || hasPreviewAction(opts)
keymap := defaultKeymap() keymap := defaultKeymap()
for key, actions := range opts.Keymap { for key, actions := range opts.Keymap {
var lastChangePreviewWindow *action var lastChangePreviewWindow *action
@ -1719,8 +1735,18 @@ func postProcessOptions(opts *Options) {
opts.ToggleSort = true opts.ToggleSort = true
case actChangePreviewWindow: case actChangePreviewWindow:
lastChangePreviewWindow = act lastChangePreviewWindow = act
if !previewEnabled {
// Doesn't matter
continue
}
opts := previewOpts{}
for _, arg := range strings.Split(act.a, "|") {
// Make sure that each expression is valid
parsePreviewWindow(&opts, arg)
}
} }
} }
// Re-organize actions so that we only keep the last change-preview-window // Re-organize actions so that we only keep the last change-preview-window
// and it comes first in the list. // and it comes first in the list.
// * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20) // * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20)
@ -1738,6 +1764,19 @@ func postProcessOptions(opts *Options) {
} }
opts.Keymap = keymap opts.Keymap = keymap
if opts.Height.auto {
for _, s := range []sizeSpec{opts.Margin[0], opts.Margin[2]} {
if s.percent {
errorExit("adaptive height is not compatible with top/bottom percent margin")
}
}
for _, s := range []sizeSpec{opts.Padding[0], opts.Padding[2]} {
if s.percent {
errorExit("adaptive height is not compatible with top/bottom percent padding")
}
}
}
// If we're not using extended search mode, --nth option becomes irrelevant // If we're not using extended search mode, --nth option becomes irrelevant
// if it contains the whole range // if it contains the whole range
if !opts.Extended || len(opts.Nth) == 1 { if !opts.Extended || len(opts.Nth) == 1 {

View File

@ -100,6 +100,11 @@ type itemLine struct {
result Result result Result
} }
type fitpad struct {
fit int
pad int
}
var emptyLine = itemLine{} var emptyLine = itemLine{}
// Terminal represents terminal input/output // Terminal represents terminal input/output
@ -183,7 +188,7 @@ type Terminal struct {
prevLines []itemLine prevLines []itemLine
suppress bool suppress bool
sigstop bool sigstop bool
startChan chan bool startChan chan fitpad
killChan chan int killChan chan int
slab *util.Slab slab *util.Slab
theme *tui.ColorTheme theme *tui.ColorTheme
@ -439,6 +444,13 @@ func makeSpinner(unicode bool) []string {
return []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 // NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := trimQuery(opts.Query) input := trimQuery(opts.Query)
@ -465,7 +477,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
strongAttr = tui.AttrRegular strongAttr = tui.AttrRegular
} }
var renderer tui.Renderer 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 fullscreen {
if tui.HasFullscreenRenderer() { if tui.HasFullscreenRenderer() {
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse) renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
@ -475,24 +487,16 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
} }
} else { } else {
maxHeightFunc := func(termHeight int) int { maxHeightFunc := func(termHeight int) int {
var maxHeight int // Minimum height required to render fzf excluding margin and padding
if opts.Height.percent {
maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
} else {
maxHeight = int(opts.Height.size)
}
effectiveMinHeight := minHeight effectiveMinHeight := minHeight
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) { if previewBox != nil && opts.Preview.aboveOrBelow() {
effectiveMinHeight *= 2 effectiveMinHeight += 1 + borderLines(opts.Preview.border)
} }
if opts.InfoStyle != infoDefault { if opts.InfoStyle != infoDefault {
effectiveMinHeight-- effectiveMinHeight--
} }
if opts.BorderShape != tui.BorderNone { effectiveMinHeight += borderLines(opts.BorderShape)
effectiveMinHeight += 2 return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
}
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
} }
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc) 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, sigstop: false,
slab: util.MakeSlab(slab16Size, slab32Size), slab: util.MakeSlab(slab16Size, slab32Size),
theme: opts.Theme, theme: opts.Theme,
startChan: make(chan bool, 1), startChan: make(chan fitpad, 1),
killChan: make(chan int), killChan: make(chan int),
tui: renderer, tui: renderer,
initFunc: func() { renderer.Init() }, initFunc: func() { renderer.Init() },
@ -587,6 +591,32 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
return &t 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) { func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil) trimmed, colors, _ := extractColor(prompt, state, nil)
@ -725,22 +755,23 @@ func (t *Terminal) displayWidth(runes []rune) int {
const ( const (
minWidth = 4 minWidth = 4
minHeight = 4 minHeight = 3
) )
func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int { func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
max := base - occupied max := base - occupied
if max < minSize {
max = minSize
}
if size.percent { if size.percent {
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max) return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
} }
return util.Constrain(int(size.size)+pad, 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() screenWidth := t.tui.MaxX()
screenHeight := t.tui.MaxY() screenHeight := t.tui.MaxY()
t.prevLines = make([]itemLine, screenHeight)
marginInt := [4]int{} // TRBL marginInt := [4]int{} // TRBL
paddingInt := [4]int{} // TRBL paddingInt := [4]int{} // TRBL
sizeSpecToInt := func(index int, spec sizeSpec) int { 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) { adjust := func(idx1 int, idx2 int, max int, min int) {
if max >= min { if min > max {
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2] min = max
if max-margin < min { }
desired := max - min margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
paddingInt[idx1] = desired * paddingInt[idx1] / margin if max-margin < min {
paddingInt[idx2] = desired * paddingInt[idx2] / margin desired := max - min
marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin) paddingInt[idx1] = desired * paddingInt[idx1] / margin
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/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 minAreaWidth := minWidth
minAreaHeight := minHeight minAreaHeight := minHeight
if previewVisible { if t.noInfoLine() {
minAreaHeight -= 1
}
if t.isPreviewVisible() {
minPreviewHeight := 1 + borderLines(t.previewOpts.border)
minPreviewWidth := 5
switch t.previewOpts.position { switch t.previewOpts.position {
case posUp, posDown: case posUp, posDown:
minAreaHeight *= 2 minAreaHeight += minPreviewHeight
minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
case posLeft, posRight: case posLeft, posRight:
minAreaWidth *= 2 minAreaWidth += minPreviewWidth
minAreaHeight = util.Max(minPreviewHeight, minAreaHeight)
} }
} }
adjust(1, 3, screenWidth, minAreaWidth) adjust(1, 3, screenWidth, minAreaWidth)
adjust(0, 2, screenHeight, minAreaHeight) 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 { if t.border != nil {
t.border.Close() t.border.Close()
} }
@ -832,8 +880,6 @@ func (t *Terminal) resizeWindows() {
// Reset preview version so that full redraw occurs // Reset preview version so that full redraw occurs
t.previewed.version = 0 t.previewed.version = 0
width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2]
switch t.borderShape { switch t.borderShape {
case tui.BorderHorizontal: case tui.BorderHorizontal:
t.border = t.tui.NewWindow( t.border = t.tui.NewWindow(
@ -865,16 +911,16 @@ func (t *Terminal) resizeWindows() {
false, tui.MakeBorderStyle(t.borderShape, t.unicode)) false, tui.MakeBorderStyle(t.borderShape, t.unicode))
} }
// Add padding // Add padding to margin
for idx, val := range paddingInt { for idx, val := range paddingInt {
marginInt[idx] += val marginInt[idx] += val
} }
width = screenWidth - marginInt[1] - marginInt[3] width -= paddingInt[1] + paddingInt[3]
height = screenHeight - marginInt[0] - marginInt[2] height -= paddingInt[0] + paddingInt[2]
// Set up preview window // Set up preview window
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if previewVisible { if t.isPreviewVisible() {
var resizePreviewWindows func(previewOpts previewOpts) var resizePreviewWindows func(previewOpts previewOpts)
resizePreviewWindows = func(previewOpts previewOpts) { resizePreviewWindows = func(previewOpts previewOpts) {
hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil
@ -1863,6 +1909,10 @@ func (t *Terminal) isPreviewEnabled() bool {
return t.hasPreviewer() && t.previewer.enabled return t.hasPreviewer() && t.previewer.enabled
} }
func (t *Terminal) isPreviewVisible() bool {
return t.isPreviewEnabled() && t.previewOpts.size.size > 0
}
func (t *Terminal) hasPreviewWindow() bool { func (t *Terminal) hasPreviewWindow() bool {
return t.pwindow != nil && t.isPreviewEnabled() return t.pwindow != nil && t.isPreviewEnabled()
} }
@ -1962,7 +2012,28 @@ func (t *Terminal) cancelPreview() {
// Loop is called to start Terminal I/O // Loop is called to start Terminal I/O
func (t *Terminal) Loop() { func (t *Terminal) Loop() {
// prof := profile.Start(profile.ProfilePath("/tmp/")) // 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 { // Late initialization
intChan := make(chan os.Signal, 1) intChan := make(chan os.Signal, 1)
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM) signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)

View File

@ -27,12 +27,13 @@ const (
StrikeThrough = Attr(1 << 7) StrikeThrough = Attr(1 << 7)
) )
func (r *FullscreenRenderer) Init() {} func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Pause(bool) {} func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) Resume(bool, bool) {} func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Clear() {} func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) Refresh() {} func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) Close() {} func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) GetChar() Event { return Event{} } func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) MaxX() int { return 0 } func (r *FullscreenRenderer) MaxX() int { return 0 }

View File

@ -189,6 +189,10 @@ func (r *LightRenderer) Init() {
} }
} }
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
r.maxHeightFunc = maxHeightFunc
}
func (r *LightRenderer) makeSpace() { func (r *LightRenderer) makeSpace() {
r.stderr("\n") r.stderr("\n")
r.csi("G") r.csi("G")
@ -676,6 +680,9 @@ func (r *LightRenderer) MaxX() int {
} }
func (r *LightRenderer) MaxY() int { func (r *LightRenderer) MaxY() int {
if r.height == 0 {
r.updateTerminalSize()
}
return r.height return r.height
} }

View File

@ -99,6 +99,8 @@ const (
AttrClear = Attr(1 << 8) AttrClear = Attr(1 << 8)
) )
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) defaultTheme() *ColorTheme { func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
if _screen.Colors() >= 256 { if _screen.Colors() >= 256 {
return Dark256 return Dark256

View File

@ -358,6 +358,7 @@ func MakeTransparentBorder() BorderStyle {
type Renderer interface { type Renderer interface {
Init() Init()
Resize(maxHeightFunc func(int) int)
Pause(clear bool) Pause(clear bool)
Resume(clear bool, sigcont bool) Resume(clear bool, sigcont bool)
Clear() Clear()

View File

@ -2245,6 +2245,93 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal 1, lines.match_count } tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) } tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }
end end
def assert_block(expected, lines)
cols = expected.lines.map(&:chomp).map(&:length).max
actual = lines.reverse.take(expected.lines.length).reverse.map { _1[0, cols].rstrip + "\n" }.join
assert_equal expected, actual
end
def test_height_range_fit
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border', :Enter
expected = <<~OUTPUT
3
2
> 1
> < 3/3
OUTPUT
tmux.until { assert_block(expected, _1) }
end
def test_height_range_fit_preview_above
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border --preview "seq {}" --preview-window up,60%', :Enter
expected = <<~OUTPUT
1
3
2
> 1
> < 3/3
OUTPUT
tmux.until { assert_block(expected, _1) }
end
def test_height_range_fit_preview_above_alternative
tmux.send_keys 'seq 3 | fzf --height ~100% --border=sharp --preview "seq {}" --preview-window up,40%,border-bottom --padding 1 --exit-0 --header hello --header-lines=2', :Enter
expected = <<~OUTPUT
1
2
3
> 3
2
1
hello
1/1
>
OUTPUT
tmux.until { assert_block(expected, _1) }
end
def test_height_range_fit_preview_left
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
>
OUTPUT
tmux.until { assert_block(expected, _1) }
end
def test_height_range_overflow
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border', :Enter
expected = <<~OUTPUT
2
> 1
> < 100/100
OUTPUT
tmux.until { assert_block(expected, _1) }
end
end end
module TestShell module TestShell