mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-14 11:45:48 -07:00
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)
|
||||
|
Reference in New Issue
Block a user