summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2025-01-06 00:44:59 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2025-01-06 00:44:59 +0900
commit0e0b86834294e1befb9e1ce16239230aa40d1add (patch)
tree4e8fec876561cdb810299c0126a838f27d7b9553
parenta5beb08ed72215e8b015851ad70bf3beca526429 (diff)
downloadfzf-0e0b86834294e1befb9e1ce16239230aa40d1add.tar.gz
Add preview border style 'line'
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.
-rw-r--r--CHANGELOG.md1
-rw-r--r--man/man1/fzf.111
-rw-r--r--src/options.go37
-rw-r--r--src/terminal.go39
-rw-r--r--src/tui/tui.go9
5 files changed, 70 insertions, 27 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e18790b8..e0b8e66e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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 {}'
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 16421c7b..cc80c1a4 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -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]
@@ -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)
@@ -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.
diff --git a/src/options.go b/src/options.go
index f6b7245f..7db7418b 100644
--- a/src/options.go
+++ b/src/options.go
@@ -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
@@ -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,
@@ -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":
@@ -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":
@@ -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
}
@@ -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":
@@ -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":
@@ -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":
@@ -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":
@@ -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 {
@@ -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 {
diff --git a/src/terminal.go b/src/terminal.go
index 764284a9..344246de 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -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++
}
@@ -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
}
@@ -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 {
@@ -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++
@@ -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
diff --git a/src/tui/tui.go b/src/tui/tui.go
index db846f75..2bd9caf9 100644
--- a/src/tui/tui.go
+++ b/src/tui/tui.go
@@ -358,6 +358,7 @@ type BorderShape int
const (
BorderUndefined BorderShape = iota
+ BorderLine
BorderNone
BorderRounded
BorderSharp
@@ -375,7 +376,7 @@ 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
@@ -383,7 +384,7 @@ func (s BorderShape) HasLeft() bool {
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
@@ -391,7 +392,7 @@ func (s BorderShape) HasRight() bool {
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
@@ -399,7 +400,7 @@ func (s BorderShape) HasTop() bool {
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