diff options
author | Junegunn Choi <junegunn.c@gmail.com> | 2022-07-20 12:08:54 +0900 |
---|---|---|
committer | Junegunn Choi <junegunn.c@gmail.com> | 2022-07-20 12:08:54 +0900 |
commit | 82b46726fc9e568e463af184c065fee2bf7045b7 (patch) | |
tree | 59760f81d9a0587d7218101193747522c449aee7 | |
parent | 8df872a482799377fc03c3ef58f408484bf6b7bf (diff) |
Add support for an alternative preview window layout
Close #2804
Close #2844
Related #2277
-rw-r--r-- | CHANGELOG.md | 10 | ||||
-rw-r--r-- | man/man1/fzf.1 | 19 | ||||
-rw-r--r-- | src/options.go | 29 | ||||
-rw-r--r-- | src/terminal.go | 154 |
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], |