summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2022-07-20 12:08:54 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2022-07-20 12:08:54 +0900
commit82b46726fc9e568e463af184c065fee2bf7045b7 (patch)
tree59760f81d9a0587d7218101193747522c449aee7
parent8df872a482799377fc03c3ef58f408484bf6b7bf (diff)
Add support for an alternative preview window layout
Close #2804 Close #2844 Related #2277
-rw-r--r--CHANGELOG.md10
-rw-r--r--man/man1/fzf.119
-rw-r--r--src/options.go29
-rw-r--r--src/terminal.go154
4 files changed, 136 insertions, 76 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96a9945a..b5a8519f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,16 @@ CHANGELOG
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
- Bug fixes and improvements
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index d4c0de16..098c3743 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -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
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
fzf - a command-line fuzzy finder
@@ -470,7 +470,7 @@ e.g.
done'\fR
.RE
.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
.B POSITION: (default: right)
@@ -488,9 +488,9 @@ default until \fBtoggle-preview\fR action is triggered.
execute the command in the background.
* 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.
.RS
@@ -502,7 +502,7 @@ e.g.
done'\fR
.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
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
.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
.TP
.BI "-q, --query=" "STR"
diff --git a/src/options.go b/src/options.go
index b0afacda..93178df5 100644
--- a/src/options.go
+++ b/src/options.go
@@ -89,7 +89,7 @@ const usage = `usage: fzf [options]
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
[,border-BORDER_OPT]
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
- [,default]
+ [,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
Scripting
-q, --query=STR Start the finder with the given query
@@ -175,10 +175,14 @@ type previewOpts struct {
follow bool
border tui.BorderShape
headerLines int
+ threshold int
+ alternative *previewOpts
}
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 {
@@ -247,7 +251,7 @@ type Options struct {
}
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 {
@@ -1169,12 +1173,19 @@ func parseInfoStyle(str string) infoStyle {
}
func parsePreviewWindow(opts *previewOpts, input string) {
- delimRegex := regexp.MustCompile("[:,]") // : for backward compatibility
+ tokenRegex := regexp.MustCompile(`[:,]*(<([1-9][0-9]*)\(([^)<]+)\)|[^,:]+)`)
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
- tokens := delimRegex.Split(input, -1)
- for _, token := range tokens {
+ tokens := tokenRegex.FindAllStringSubmatch(input, -1)
+ 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 {
case "":
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 {
diff --git a/src/terminal.go b/src/terminal.go
index 3c13b373..96bb6411 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -819,12 +819,15 @@ func (t *Terminal) resizeWindows() {
}
if t.window != nil {
t.window.Close()
+ t.window = nil
}
if t.pborder != nil {
t.pborder.Close()
+ t.pborder = nil
}
if t.pwindow != nil {
t.pwindow.Close()
+ t.pwindow = nil
}
// Reset preview version so that full redraw occurs
t.previewed.version = 0
@@ -869,76 +872,97 @@ func (t *Terminal) resizeWindows() {
width = screenWidth - marginInt[1] - marginInt[3]
height = screenHeight - marginInt[0] - marginInt[2]
+ // Set up preview window
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if previewVisible {
- createPreviewWindow := func(y int, x int, w int, h int) {
- pwidth := w
- pheight := h
- var previewBorder tui.BorderStyle
- if t.previewOpts.border == tui.BorderNone {
- previewBorder = tui.MakeTransparentBorder()
- } else {
- previewBorder = tui.MakeBorderStyle(t.previewOpts.border, t.unicode)
+ var resizePreviewWindows func(previewOpts previewOpts)
+ resizePreviewWindows = func(previewOpts previewOpts) {
+ if previewOpts.hidden {
+ return
+ }
+ hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil
+ createPreviewWindow := func(y int, x int, w int, h int) {
+ pwidth := w
+ pheight := h
+ var previewBorder tui.BorderStyle
+ if previewOpts.border == tui.BorderNone {
+ previewBorder = tui.MakeTransparentBorder()
+ } else {
+ previewBorder = tui.MakeBorderStyle(previewOpts.border, t.unicode)
+ }
+ t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
+ switch previewOpts.border {
+ case tui.BorderSharp, tui.BorderRounded:
+ pwidth -= 4
+ pheight -= 2
+ x += 2
+ y += 1
+ case tui.BorderLeft:
+ pwidth -= 2
+ x += 2
+ case tui.BorderRight:
+ pwidth -= 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)
}
- t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
- switch t.previewOpts.border {
- case tui.BorderSharp, tui.BorderRounded:
- pwidth -= 4
- pheight -= 2
- x += 2
- y += 1
- case tui.BorderLeft:
- pwidth -= 2
- x += 2
- case tui.BorderRight:
- pwidth -= 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
+ 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
- minPreviewHeight := 3
- switch t.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 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 {
+ resizePreviewWindows(t.previewOpts)
+ }
+ if t.window == nil {
t.window = t.tui.NewWindow(
marginInt[0],
marginInt[3],