Skip to content

Commit

Permalink
Add preview border style 'line'
Browse files Browse the repository at this point in the history
It draws a single line between the preview window and the rest of the
interface. i.e. automatically choose between 'left', 'right', 'top', and
'bottom' depending on the position of the preview window.
  • Loading branch information
junegunn committed Jan 5, 2025
1 parent a5beb08 commit 0e0b868
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ CHANGELOG
- `change-header-label`
- `transform-header-label`
- Added `--preview-border[=STYLE]` as short for `--preview-window=border[-STYLE]`
- Added new preview border style `line` which draws a single separator line between the preview window and the rest of the interface
- You can specify `border-native` to `--tmux` so that native tmux border is used instead of `--border`. This can be useful if you start a different program from inside the popup.
```sh
fzf --tmux border-native --bind 'enter:execute:less {}'
Expand Down
11 changes: 9 additions & 2 deletions man/man1/fzf.1
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,10 @@ e.g.

.TP
.BI "\-\-preview\-border" [=STYLE]
Short for \fB\-\-preview\-window=border\-STYLE\fR
Short for \fB\-\-preview\-window=border\-STYLE\fR. In addition to the other
styles, \fBline\fR style is also supported for preview border, which draws
a single separator line between the preview window and the rest of the
interface.

.TP
.BI "\-\-preview\-label" [=LABEL]
Expand Down Expand Up @@ -812,7 +815,7 @@ default value 0 (or \fBcenter\fR) will put the label at the center of the
border line.

.TP
.BI "\-\-preview\-window=" "[POSITION][,SIZE[%]][,border\-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]info][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
.BI "\-\-preview\-window=" "[POSITION][,SIZE[%]][,border\-STYLE][,[no]wrap][,[no]follow][,[no]cycle][,[no]info][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"

.RS
.B POSITION: (default: right)
Expand Down Expand Up @@ -855,6 +858,10 @@ e.g. \fBborder\-rounded\fR (border with rounded edges, default),
\fBborder\-sharp\fR (border with sharp edges), \fBborder\-left\fR,
\fBborder\-none\fR, etc.

* In addition to the other border styles, \fBborder\-line\fR style is also
supported, which draws a single separator line between the preview window and
the rest of the interface.

* \fB[:+SCROLL[OFFSETS][/DENOM]]\fR determines the initial scroll offset of the
preview window.

Expand Down
37 changes: 25 additions & 12 deletions src/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,12 @@ Usage: fzf [options]
--preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][,SIZE[%]]
[,[no]wrap][,[no]cycle][,[no]follow][,[no]info]
[,[no]hidden][,border-BORDER_OPT]
[,[no]hidden][,border-STYLE]
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
--preview-border[=STYLE] Short for --preview-window=border-STYLE
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
top|bottom|left|right|line|none] (default: rounded)
--preview-label=LABEL
--preview-label-pos=N Same as --border-label and --border-label-pos,
but for preview window
Expand Down Expand Up @@ -313,6 +315,10 @@ func (o *previewOpts) Toggle() {
o.hidden = !o.hidden
}

func (o *previewOpts) HasBorderRight() bool {
return o.border.HasRight() || o.border == tui.BorderLine && o.position == posLeft
}

func defaultTmuxOptions(index int) *tmuxOptions {
return &tmuxOptions{
position: posCenter,
Expand Down Expand Up @@ -832,8 +838,13 @@ func processScheme(opts *Options) error {
return nil
}

func parseBorder(str string, optional bool) (tui.BorderShape, error) {
func parseBorder(str string, optional bool, allowLine bool) (tui.BorderShape, error) {
switch str {
case "line":
if !allowLine {
return tui.BorderNone, errors.New("'line' is only allowed for preview border")
}
return tui.BorderLine, nil
case "rounded":
return tui.BorderRounded, nil
case "sharp":
Expand Down Expand Up @@ -1900,6 +1911,8 @@ func parsePreviewWindowImpl(opts *previewOpts, input string) error {
opts.position = posRight
case "rounded", "border", "border-rounded":
opts.border = tui.BorderRounded
case "border-line":
opts.border = tui.BorderLine
case "sharp", "border-sharp":
opts.border = tui.BorderSharp
case "border-bold":
Expand Down Expand Up @@ -2501,7 +2514,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
case "--no-preview":
opts.Preview.command = ""
case "--preview-window":
str, err := nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]")
str, err := nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-STYLE][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]")
if err != nil {
return err
}
Expand All @@ -2512,7 +2525,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.Preview.border = tui.BorderNone
case "--preview-border":
hasArg, arg := optionalNextString(allArgs, &i)
if opts.Preview.border, err = parseBorder(arg, !hasArg); err != nil {
if opts.Preview.border, err = parseBorder(arg, !hasArg, true); err != nil {
return err
}
case "--height":
Expand All @@ -2537,12 +2550,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.BorderShape = tui.BorderNone
case "--border":
hasArg, arg := optionalNextString(allArgs, &i)
if opts.BorderShape, err = parseBorder(arg, !hasArg); err != nil {
if opts.BorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
return err
}
case "--list-border":
hasArg, arg := optionalNextString(allArgs, &i)
if opts.ListBorderShape, err = parseBorder(arg, !hasArg); err != nil {
if opts.ListBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
return err
}
case "--no-list-border":
Expand All @@ -2566,7 +2579,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.HeaderBorderShape = tui.BorderNone
case "--header-border":
hasArg, arg := optionalNextString(allArgs, &i)
if opts.HeaderBorderShape, err = parseBorder(arg, !hasArg); err != nil {
if opts.HeaderBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
return err
}
case "--no-header-label":
Expand All @@ -2587,7 +2600,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.InputBorderShape = tui.BorderNone
case "--input-border":
hasArg, arg := optionalNextString(allArgs, &i)
if opts.InputBorderShape, err = parseBorder(arg, !hasArg); err != nil {
if opts.InputBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
return err
}
case "--no-input-label":
Expand Down Expand Up @@ -2738,15 +2751,15 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
} else if match, value := optString(arg, "-d", "--delimiter="); match {
opts.Delimiter = delimiterRegexp(value)
} else if match, value := optString(arg, "--border="); match {
if opts.BorderShape, err = parseBorder(value, false); err != nil {
if opts.BorderShape, err = parseBorder(value, false, false); err != nil {
return err
}
} else if match, value := optString(arg, "--preview-border="); match {
if opts.Preview.border, err = parseBorder(value, false); err != nil {
if opts.Preview.border, err = parseBorder(value, false, true); err != nil {
return err
}
} else if match, value := optString(arg, "--list-border="); match {
if opts.ListBorderShape, err = parseBorder(value, false); err != nil {
if opts.ListBorderShape, err = parseBorder(value, false, false); err != nil {
return err
}
} else if match, value := optString(arg, "--list-label="); match {
Expand All @@ -2756,7 +2769,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
return err
}
} else if match, value := optString(arg, "--input-border="); match {
if opts.InputBorderShape, err = parseBorder(value, false); err != nil {
if opts.InputBorderShape, err = parseBorder(value, false, false); err != nil {
return err
}
} else if match, value := optString(arg, "--input-label="); match {
Expand Down
39 changes: 30 additions & 9 deletions src/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -1513,7 +1513,7 @@ func (t *Terminal) minPreviewSize(opts *previewOpts) (int, int) {

switch opts.position {
case posLeft, posRight:
if len(t.scrollbar) > 0 && !opts.border.HasRight() {
if len(t.scrollbar) > 0 && !opts.HasBorderRight() {
// Need a column to show scrollbar
minPreviewWidth++
}
Expand Down Expand Up @@ -1757,17 +1757,30 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
createPreviewWindow := func(y int, x int, w int, h int) {
pwidth := w
pheight := h
previewBorder := tui.MakeBorderStyle(previewOpts.border, t.unicode)
shape := previewOpts.border
if shape == tui.BorderLine {
switch previewOpts.position {
case posUp:
shape = tui.BorderBottom
case posDown:
shape = tui.BorderTop
case posLeft:
shape = tui.BorderRight
case posRight:
shape = tui.BorderLeft
}
}
previewBorder := tui.MakeBorderStyle(shape, t.unicode)
t.pborder = t.tui.NewWindow(y, x, w, h, tui.WindowPreview, previewBorder, false)
pwidth -= borderColumns(previewOpts.border, bw)
pheight -= borderLines(previewOpts.border)
if previewOpts.border.HasLeft() {
pwidth -= borderColumns(shape, bw)
pheight -= borderLines(shape)
if shape.HasLeft() {
x += 1 + bw
}
if previewOpts.border.HasTop() {
if shape.HasTop() {
y += 1
}
if len(t.scrollbar) > 0 && !previewOpts.border.HasRight() {
if len(t.scrollbar) > 0 && !shape.HasRight() {
// Need a column to show scrollbar
pwidth -= 1
}
Expand Down Expand Up @@ -1800,6 +1813,14 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
if previewOpts.hidden {
return
}
// If none of the inner borders has the right side, but the outer border does, increase the width by 1 column
stickToRight = t.borderShape.HasRight() &&
!previewOpts.HasBorderRight() && !t.listBorderShape.HasRight() && !t.inputBorderShape.HasRight() &&
(!t.headerVisible || !t.headerBorderShape.HasRight() || t.visibleHeaderLines() == 0)
if stickToRight {
innerWidth++
width++
}

maxPreviewLines := availableLines
if t.wborder != nil {
Expand Down Expand Up @@ -1870,7 +1891,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
} else {
// NOTE: fzf --preview 'cat {}' --preview-window border-left --border
stickToRight = !previewOpts.border.HasRight() && t.borderShape.HasRight()
stickToRight = !previewOpts.HasBorderRight() && t.borderShape.HasRight()
if stickToRight {
innerWidth++
width++
Expand Down Expand Up @@ -3187,7 +3208,7 @@ func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int)
t.previewer.xw = xw
}
xshift := -1 - t.borderWidth
if !t.activePreviewOpts.border.HasRight() {
if !t.activePreviewOpts.HasBorderRight() {
xshift = -1
}
yshift := 1
Expand Down
9 changes: 5 additions & 4 deletions src/tui/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ type BorderShape int

const (
BorderUndefined BorderShape = iota
BorderLine
BorderNone
BorderRounded
BorderSharp
Expand All @@ -375,31 +376,31 @@ const (

func (s BorderShape) HasLeft() bool {
switch s {
case BorderNone, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
case BorderNone, BorderLine, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
return false
}
return true
}

func (s BorderShape) HasRight() bool {
switch s {
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
case BorderNone, BorderLine, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
return false
}
return true
}

func (s BorderShape) HasTop() bool {
switch s {
case BorderNone, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
case BorderNone, BorderLine, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
return false
}
return true
}

func (s BorderShape) HasBottom() bool {
switch s {
case BorderNone, BorderLeft, BorderRight, BorderTop, BorderVertical: // No bottom
case BorderNone, BorderLine, BorderLeft, BorderRight, BorderTop, BorderVertical: // No bottom
return false
}
return true
Expand Down

0 comments on commit 0e0b868

Please sign in to comment.