mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-12-23 03:19:01 +00:00
parent
984049586a
commit
22cbd9fa58
30
CHANGELOG.md
30
CHANGELOG.md
@ -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
|
||||||
|
1
Makefile
1
Makefile
@ -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:
|
||||||
|
@ -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).
|
||||||
|
35
src/core.go
35
src/core.go
@ -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())
|
||||||
|
@ -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 {
|
||||||
|
151
src/terminal.go
151
src/terminal.go
@ -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)
|
||||||
|
@ -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 }
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user