mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-17 10:35:10 +00:00
parent
984049586a
commit
22cbd9fa58
30
CHANGELOG.md
30
CHANGELOG.md
@ -1,6 +1,36 @@
|
||||
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
|
||||
------
|
||||
- Added `--scheme=[default|path|history]` option to choose scoring scheme
|
||||
|
1
Makefile
1
Makefile
@ -155,6 +155,7 @@ target/$(BINARYLOONG64): $(SOURCES)
|
||||
GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
bin/fzf: target/$(BINARY) | bin
|
||||
-rm -f bin/fzf
|
||||
cp -f target/$(BINARY) bin/fzf
|
||||
|
||||
docker:
|
||||
|
@ -177,9 +177,11 @@ actions are affected:
|
||||
Label characters for \fBjump\fR and \fBjump-accept\fR
|
||||
.SS Layout
|
||||
.TP
|
||||
.BI "--height=" "HEIGHT[%]"
|
||||
.BI "--height=" "[~]HEIGHT[%]"
|
||||
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
|
||||
.BI "--min-height=" "HEIGHT"
|
||||
Minimum height when \fB--height\fR is given in percent (default: 10).
|
||||
|
35
src/core.go
35
src/core.go
@ -194,10 +194,17 @@ func Run(opts *Options, version string, revision string) {
|
||||
|
||||
// Terminal I/O
|
||||
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
|
||||
go terminal.Loop()
|
||||
if !deferred {
|
||||
terminal.startChan <- true
|
||||
if !deferred && !heightUnknown {
|
||||
// Start right away
|
||||
terminal.startChan <- fitpad{-1, -1}
|
||||
}
|
||||
|
||||
// Event coordination
|
||||
@ -216,7 +223,19 @@ func Run(opts *Options, version string, revision string) {
|
||||
go reader.restart(command)
|
||||
}
|
||||
eventBox.Watch(EvtReadNew)
|
||||
total := 0
|
||||
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 {
|
||||
delay := true
|
||||
ticks++
|
||||
@ -249,11 +268,15 @@ func Run(opts *Options, version string, revision string) {
|
||||
reading = reading && evt == EvtReadNew
|
||||
}
|
||||
snapshot, count := chunkList.Snapshot()
|
||||
terminal.UpdateCount(count, !reading, value.(*string))
|
||||
total = count
|
||||
terminal.UpdateCount(total, !reading, value.(*string))
|
||||
if opts.Sync {
|
||||
opts.Sync = false
|
||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
||||
}
|
||||
if heightUnknown && !deferred {
|
||||
determine(!reading)
|
||||
}
|
||||
reset := clearCache()
|
||||
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
|
||||
|
||||
@ -295,8 +318,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
if deferred {
|
||||
count := val.Length()
|
||||
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
|
||||
deferred = false
|
||||
terminal.startChan <- true
|
||||
determine(val.final)
|
||||
} else if val.final {
|
||||
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
|
||||
if opts.PrintQuery {
|
||||
@ -313,8 +335,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
}
|
||||
os.Exit(exitNoMatch)
|
||||
}
|
||||
deferred = false
|
||||
terminal.startChan <- true
|
||||
determine(val.final)
|
||||
}
|
||||
}
|
||||
terminal.UpdateList(val, clearSelection())
|
||||
|
@ -53,8 +53,10 @@ const usage = `usage: fzf [options]
|
||||
--jump-labels=CHARS Label characters for jump and jump-accept
|
||||
|
||||
Layout
|
||||
--height=HEIGHT[%] Display fzf window below the cursor with the given
|
||||
height instead of using fullscreen
|
||||
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
||||
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
|
||||
(default: 10)
|
||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||
@ -131,6 +133,12 @@ const (
|
||||
byEnd
|
||||
)
|
||||
|
||||
type heightSpec struct {
|
||||
size float64
|
||||
percent bool
|
||||
auto bool
|
||||
}
|
||||
|
||||
type sizeSpec struct {
|
||||
size float64
|
||||
percent bool
|
||||
@ -180,6 +188,10 @@ type previewOpts struct {
|
||||
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 {
|
||||
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) ||
|
||||
@ -211,7 +223,7 @@ type Options struct {
|
||||
Theme *tui.ColorTheme
|
||||
Black bool
|
||||
Bold bool
|
||||
Height sizeSpec
|
||||
Height heightSpec
|
||||
MinHeight int
|
||||
Layout layoutType
|
||||
Cycle bool
|
||||
@ -1076,11 +1088,6 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) {
|
||||
}
|
||||
if t == actUnbind || t == actRebind {
|
||||
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}
|
||||
}
|
||||
|
||||
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")
|
||||
return size
|
||||
heightSpec.size = size.size
|
||||
heightSpec.percent = size.percent
|
||||
return heightSpec
|
||||
}
|
||||
|
||||
func parseLayout(str string) layoutType {
|
||||
@ -1525,11 +1540,11 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
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]"))
|
||||
case "--height":
|
||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: [~]HEIGHT[%]"))
|
||||
case "--min-height":
|
||||
opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
|
||||
case "--no-height":
|
||||
opts.Height = sizeSpec{}
|
||||
opts.Height = heightSpec{}
|
||||
case "--no-margin":
|
||||
opts.Margin = defaultMargin()
|
||||
case "--no-padding":
|
||||
@ -1709,6 +1724,7 @@ func postProcessOptions(opts *Options) {
|
||||
}
|
||||
|
||||
// Extend the default key map
|
||||
previewEnabled := len(opts.Preview.command) > 0 || hasPreviewAction(opts)
|
||||
keymap := defaultKeymap()
|
||||
for key, actions := range opts.Keymap {
|
||||
var lastChangePreviewWindow *action
|
||||
@ -1719,8 +1735,18 @@ func postProcessOptions(opts *Options) {
|
||||
opts.ToggleSort = true
|
||||
case actChangePreviewWindow:
|
||||
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
|
||||
// and it comes first in the list.
|
||||
// * 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
|
||||
|
||||
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 it contains the whole range
|
||||
if !opts.Extended || len(opts.Nth) == 1 {
|
||||
|
151
src/terminal.go
151
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)
|
||||
|
@ -27,12 +27,13 @@ const (
|
||||
StrikeThrough = Attr(1 << 7)
|
||||
)
|
||||
|
||||
func (r *FullscreenRenderer) Init() {}
|
||||
func (r *FullscreenRenderer) Pause(bool) {}
|
||||
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
||||
func (r *FullscreenRenderer) Clear() {}
|
||||
func (r *FullscreenRenderer) Refresh() {}
|
||||
func (r *FullscreenRenderer) Close() {}
|
||||
func (r *FullscreenRenderer) Init() {}
|
||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||
func (r *FullscreenRenderer) Pause(bool) {}
|
||||
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
||||
func (r *FullscreenRenderer) Clear() {}
|
||||
func (r *FullscreenRenderer) Refresh() {}
|
||||
func (r *FullscreenRenderer) Close() {}
|
||||
|
||||
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||
|
@ -189,6 +189,10 @@ func (r *LightRenderer) Init() {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
|
||||
r.maxHeightFunc = maxHeightFunc
|
||||
}
|
||||
|
||||
func (r *LightRenderer) makeSpace() {
|
||||
r.stderr("\n")
|
||||
r.csi("G")
|
||||
@ -676,6 +680,9 @@ func (r *LightRenderer) MaxX() int {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) MaxY() int {
|
||||
if r.height == 0 {
|
||||
r.updateTerminalSize()
|
||||
}
|
||||
return r.height
|
||||
}
|
||||
|
||||
|
@ -99,6 +99,8 @@ const (
|
||||
AttrClear = Attr(1 << 8)
|
||||
)
|
||||
|
||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||
|
||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
||||
if _screen.Colors() >= 256 {
|
||||
return Dark256
|
||||
|
@ -358,6 +358,7 @@ func MakeTransparentBorder() BorderStyle {
|
||||
|
||||
type Renderer interface {
|
||||
Init()
|
||||
Resize(maxHeightFunc func(int) int)
|
||||
Pause(clear bool)
|
||||
Resume(clear bool, sigcont bool)
|
||||
Clear()
|
||||
|
@ -2245,6 +2245,93 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }
|
||||
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
|
||||
|
||||
module TestShell
|
||||
|
Loading…
Reference in New Issue
Block a user