mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-06-03 07:50:49 +00:00
Add support for an alternative preview window layout
Close #2804 Close #2844 Related #2277
This commit is contained in:
parent
8df872a482
commit
82b46726fc
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -3,6 +3,16 @@ CHANGELOG
|
||||||
|
|
||||||
0.31.0
|
0.31.0
|
||||||
------
|
------
|
||||||
|
- Added support for an alternative preview window layout that is activated
|
||||||
|
when the size of the preview window is smaller than a certain threshold.
|
||||||
|
```sh
|
||||||
|
# If the width of the preview window is smaller than 50 columns,
|
||||||
|
# it will be displayed above the search window.
|
||||||
|
fzf --preview 'cat {}' --preview-window 'right,50%,border-left,<50(up,30%,border-bottom)'
|
||||||
|
|
||||||
|
# Or you can just hide it like so
|
||||||
|
fzf --preview 'cat {}' --preview-window '<50(hidden)'
|
||||||
|
```
|
||||||
- Use SGR mouse mode to support larger terminals
|
- Use SGR mouse mode to support larger terminals
|
||||||
- Bug fixes and improvements
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Apr 2022" "fzf 0.30.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Jul 2022" "fzf 0.31.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
|
@ -470,7 +470,7 @@ e.g.
|
||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"
|
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B POSITION: (default: right)
|
.B POSITION: (default: right)
|
||||||
|
@ -488,9 +488,9 @@ default until \fBtoggle-preview\fR action is triggered.
|
||||||
execute the command in the background.
|
execute the command in the background.
|
||||||
|
|
||||||
* Long lines are truncated by default. Line wrap can be enabled with
|
* Long lines are truncated by default. Line wrap can be enabled with
|
||||||
\fB:wrap\fR flag.
|
\fBwrap\fR flag.
|
||||||
|
|
||||||
* Preview window will automatically scroll to the bottom when \fB:follow\fR
|
* Preview window will automatically scroll to the bottom when \fBfollow\fR
|
||||||
flag is set, similarly to how \fBtail -f\fR works.
|
flag is set, similarly to how \fBtail -f\fR works.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
|
@ -502,7 +502,7 @@ e.g.
|
||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
|
* Cyclic scrolling is enabled with \fBcycle\fR flag.
|
||||||
|
|
||||||
* To change the style of the border of the preview window, specify one of
|
* To change the style of the border of the preview window, specify one of
|
||||||
the options for \fB--border\fR with \fBborder-\fR prefix.
|
the options for \fB--border\fR with \fBborder-\fR prefix.
|
||||||
|
@ -552,6 +552,15 @@ e.g.
|
||||||
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
* You can specify an alternative set of options that are used only when the size
|
||||||
|
of the preview window is below a certain threshold. Note that only one
|
||||||
|
alternative layout is allowed.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
e.g.
|
||||||
|
\fBfzf --preview 'cat {}' --preview-window 'right,border-left,<30(up,30%,border-bottom)'\fR
|
||||||
|
.RE
|
||||||
|
|
||||||
.SS Scripting
|
.SS Scripting
|
||||||
.TP
|
.TP
|
||||||
.BI "-q, --query=" "STR"
|
.BI "-q, --query=" "STR"
|
||||||
|
|
|
@ -89,7 +89,7 @@ const usage = `usage: fzf [options]
|
||||||
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
||||||
[,border-BORDER_OPT]
|
[,border-BORDER_OPT]
|
||||||
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
||||||
[,default]
|
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
|
||||||
|
|
||||||
Scripting
|
Scripting
|
||||||
-q, --query=STR Start the finder with the given query
|
-q, --query=STR Start the finder with the given query
|
||||||
|
@ -175,10 +175,14 @@ type previewOpts struct {
|
||||||
follow bool
|
follow bool
|
||||||
border tui.BorderShape
|
border tui.BorderShape
|
||||||
headerLines int
|
headerLines int
|
||||||
|
threshold int
|
||||||
|
alternative *previewOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
||||||
|
@ -247,7 +251,7 @@ type Options struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultPreviewOpts(command string) previewOpts {
|
func defaultPreviewOpts(command string) previewOpts {
|
||||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0}
|
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultOptions() *Options {
|
func defaultOptions() *Options {
|
||||||
|
@ -1169,12 +1173,19 @@ func parseInfoStyle(str string) infoStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
delimRegex := regexp.MustCompile("[:,]") // : for backward compatibility
|
tokenRegex := regexp.MustCompile(`[:,]*(<([1-9][0-9]*)\(([^)<]+)\)|[^,:]+)`)
|
||||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||||
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
|
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
|
||||||
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
|
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
|
||||||
tokens := delimRegex.Split(input, -1)
|
tokens := tokenRegex.FindAllStringSubmatch(input, -1)
|
||||||
for _, token := range tokens {
|
var alternative string
|
||||||
|
for _, match := range tokens {
|
||||||
|
if len(match[2]) > 0 {
|
||||||
|
opts.threshold = atoi(match[2])
|
||||||
|
alternative = match[3]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
token := match[1]
|
||||||
switch token {
|
switch token {
|
||||||
case "":
|
case "":
|
||||||
case "default":
|
case "default":
|
||||||
|
@ -1233,6 +1244,12 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(alternative) > 0 {
|
||||||
|
alternativeOpts := *opts
|
||||||
|
opts.alternative = &alternativeOpts
|
||||||
|
opts.alternative.alternative = nil
|
||||||
|
parsePreviewWindow(opts.alternative, alternative)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMargin(opt string, margin string) [4]sizeSpec {
|
func parseMargin(opt string, margin string) [4]sizeSpec {
|
||||||
|
|
154
src/terminal.go
154
src/terminal.go
|
@ -819,12 +819,15 @@ func (t *Terminal) resizeWindows() {
|
||||||
}
|
}
|
||||||
if t.window != nil {
|
if t.window != nil {
|
||||||
t.window.Close()
|
t.window.Close()
|
||||||
|
t.window = nil
|
||||||
}
|
}
|
||||||
if t.pborder != nil {
|
if t.pborder != nil {
|
||||||
t.pborder.Close()
|
t.pborder.Close()
|
||||||
|
t.pborder = nil
|
||||||
}
|
}
|
||||||
if t.pwindow != nil {
|
if t.pwindow != nil {
|
||||||
t.pwindow.Close()
|
t.pwindow.Close()
|
||||||
|
t.pwindow = nil
|
||||||
}
|
}
|
||||||
// Reset preview version so that full redraw occurs
|
// Reset preview version so that full redraw occurs
|
||||||
t.previewed.version = 0
|
t.previewed.version = 0
|
||||||
|
@ -869,76 +872,97 @@ func (t *Terminal) resizeWindows() {
|
||||||
width = screenWidth - marginInt[1] - marginInt[3]
|
width = screenWidth - marginInt[1] - marginInt[3]
|
||||||
height = screenHeight - marginInt[0] - marginInt[2]
|
height = screenHeight - marginInt[0] - marginInt[2]
|
||||||
|
|
||||||
|
// Set up preview window
|
||||||
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
||||||
if previewVisible {
|
if previewVisible {
|
||||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
var resizePreviewWindows func(previewOpts previewOpts)
|
||||||
pwidth := w
|
resizePreviewWindows = func(previewOpts previewOpts) {
|
||||||
pheight := h
|
if previewOpts.hidden {
|
||||||
var previewBorder tui.BorderStyle
|
return
|
||||||
if t.previewOpts.border == tui.BorderNone {
|
|
||||||
previewBorder = tui.MakeTransparentBorder()
|
|
||||||
} else {
|
|
||||||
previewBorder = tui.MakeBorderStyle(t.previewOpts.border, t.unicode)
|
|
||||||
}
|
}
|
||||||
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil
|
||||||
switch t.previewOpts.border {
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||||
case tui.BorderSharp, tui.BorderRounded:
|
pwidth := w
|
||||||
pwidth -= 4
|
pheight := h
|
||||||
pheight -= 2
|
var previewBorder tui.BorderStyle
|
||||||
x += 2
|
if previewOpts.border == tui.BorderNone {
|
||||||
y += 1
|
previewBorder = tui.MakeTransparentBorder()
|
||||||
case tui.BorderLeft:
|
} else {
|
||||||
pwidth -= 2
|
previewBorder = tui.MakeBorderStyle(previewOpts.border, t.unicode)
|
||||||
x += 2
|
}
|
||||||
case tui.BorderRight:
|
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
||||||
pwidth -= 2
|
switch previewOpts.border {
|
||||||
case tui.BorderTop:
|
case tui.BorderSharp, tui.BorderRounded:
|
||||||
pheight -= 1
|
pwidth -= 4
|
||||||
y += 1
|
pheight -= 2
|
||||||
case tui.BorderBottom:
|
x += 2
|
||||||
pheight -= 1
|
y += 1
|
||||||
case tui.BorderHorizontal:
|
case tui.BorderLeft:
|
||||||
pheight -= 2
|
pwidth -= 2
|
||||||
y += 1
|
x += 2
|
||||||
case tui.BorderVertical:
|
case tui.BorderRight:
|
||||||
pwidth -= 4
|
pwidth -= 2
|
||||||
x += 2
|
case tui.BorderTop:
|
||||||
|
pheight -= 1
|
||||||
|
y += 1
|
||||||
|
case tui.BorderBottom:
|
||||||
|
pheight -= 1
|
||||||
|
case tui.BorderHorizontal:
|
||||||
|
pheight -= 2
|
||||||
|
y += 1
|
||||||
|
case tui.BorderVertical:
|
||||||
|
pwidth -= 4
|
||||||
|
x += 2
|
||||||
|
}
|
||||||
|
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
|
||||||
|
}
|
||||||
|
verticalPad := 2
|
||||||
|
minPreviewHeight := 3
|
||||||
|
switch previewOpts.border {
|
||||||
|
case tui.BorderNone, tui.BorderVertical, tui.BorderLeft, tui.BorderRight:
|
||||||
|
verticalPad = 0
|
||||||
|
minPreviewHeight = 1
|
||||||
|
case tui.BorderTop, tui.BorderBottom:
|
||||||
|
verticalPad = 1
|
||||||
|
minPreviewHeight = 2
|
||||||
|
}
|
||||||
|
switch previewOpts.position {
|
||||||
|
case posUp, posDown:
|
||||||
|
pheight := calculateSize(height, previewOpts.size, minHeight, minPreviewHeight, verticalPad)
|
||||||
|
if hasThreshold && pheight < previewOpts.threshold {
|
||||||
|
resizePreviewWindows(*previewOpts.alternative)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if previewOpts.position == posUp {
|
||||||
|
t.window = t.tui.NewWindow(
|
||||||
|
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
||||||
|
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
||||||
|
} else {
|
||||||
|
t.window = t.tui.NewWindow(
|
||||||
|
marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
|
||||||
|
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
||||||
|
}
|
||||||
|
case posLeft, posRight:
|
||||||
|
pwidth := calculateSize(width, previewOpts.size, minWidth, 5, 4)
|
||||||
|
if hasThreshold && pwidth < previewOpts.threshold {
|
||||||
|
fmt.Println("Alternative", (*previewOpts.alternative).position == posDown)
|
||||||
|
resizePreviewWindows(*previewOpts.alternative)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if previewOpts.position == posLeft {
|
||||||
|
t.window = t.tui.NewWindow(
|
||||||
|
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
|
||||||
|
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
||||||
|
} else {
|
||||||
|
t.window = t.tui.NewWindow(
|
||||||
|
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
|
||||||
|
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
|
|
||||||
}
|
}
|
||||||
verticalPad := 2
|
resizePreviewWindows(t.previewOpts)
|
||||||
minPreviewHeight := 3
|
}
|
||||||
switch t.previewOpts.border {
|
if t.window == nil {
|
||||||
case tui.BorderNone, tui.BorderVertical, tui.BorderLeft, tui.BorderRight:
|
|
||||||
verticalPad = 0
|
|
||||||
minPreviewHeight = 1
|
|
||||||
case tui.BorderTop, tui.BorderBottom:
|
|
||||||
verticalPad = 1
|
|
||||||
minPreviewHeight = 2
|
|
||||||
}
|
|
||||||
switch t.previewOpts.position {
|
|
||||||
case posUp:
|
|
||||||
pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
|
|
||||||
t.window = t.tui.NewWindow(
|
|
||||||
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
|
||||||
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
|
||||||
case posDown:
|
|
||||||
pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
|
|
||||||
t.window = t.tui.NewWindow(
|
|
||||||
marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
|
|
||||||
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
|
||||||
case posLeft:
|
|
||||||
pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
|
|
||||||
t.window = t.tui.NewWindow(
|
|
||||||
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
|
|
||||||
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
|
||||||
case posRight:
|
|
||||||
pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
|
|
||||||
t.window = t.tui.NewWindow(
|
|
||||||
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
|
|
||||||
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0],
|
marginInt[0],
|
||||||
marginInt[3],
|
marginInt[3],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user