Add --border-label and --border-label-pos

Close #3022
pull/3046/head
Junegunn Choi 2 years ago
parent 0de1aacb0c
commit e61585f2f3
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627

@ -9,6 +9,21 @@ CHANGELOG
```sh ```sh
seq 100 | fzf --multi --sync --bind 'start:last+select-all+preview(echo welcome)' seq 100 | fzf --multi --sync --bind 'start:last+select-all+preview(echo welcome)'
``` ```
- Added `--border-label` and `--border-label-pos` for putting label on the border
```sh
# ANSI color codes are supported
# (with https://github.com/busyloop/lolcat)
label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
# Border label at the center
fzf --height=10 --border-label="╢ $label ╟" --border --color=label:italic:black
# Left-aligned (positive integer)
fzf --height=10 --border-label="╢ $label ╟" --border=top --border-label-pos=3 --color=label:italic:black
# Right-aligned (negative integer)
fzf --height=10 --border-label="╢ $label ╟" --border=bottom --border-label-pos=-3 --color=label:italic:black
```
0.34.0 0.34.0
------ ------

@ -226,6 +226,45 @@ Draw border around the finder
.BR none .BR none
.br .br
.TP
.BI "--border-label" [=LABEL]
Label to print on the horizontal border line. Should be used with one of the
following \fB--border\fR options.
.br
.B * rounded
.br
.B * sharp
.br
.B * horizontal
.br
.BR "* top" " (up)"
.br
.BR "* bottom" " (down)"
.br
.br
e.g.
\fB# ANSI color codes are supported
# (with https://github.com/busyloop/lolcat)
label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
# Border label at the center
fzf --height=10 --border-label="╢ $label ╟" --border --color=label:italic:black
# Left-aligned (positive integer)
fzf --height=10 --border-label="╢ $label ╟" --border=top --border-label-pos=3 --color=label:italic:black
# Right-aligned (negative integer)
fzf --height=10 --border-label="╢ $label ╟" --border=bottom --border-label-pos=-3 --color=label:italic:black\fR
.TP
.BI "--border-label-pos" [=COL]
Horizontal position of the border label on the border line. Specify a positive
integer as the column position from the left. Specify a negative integer to
right-align the label. The default value 0 (or \fBcenter\fR) will put
the label at the center of the border line.
.TP .TP
.B "--no-unicode" .B "--no-unicode"
Use ASCII characters instead of Unicode box drawing characters to draw border Use ASCII characters instead of Unicode box drawing characters to draw border
@ -356,6 +395,7 @@ color mappings.
\fBdisabled \fRQuery string when search is disabled \fBdisabled \fRQuery string when search is disabled
\fBinfo \fRInfo line (match counters) \fBinfo \fRInfo line (match counters)
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR) \fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBlabel \fRBorder label (\fB--border-label\fR)
\fBprompt \fRPrompt \fBprompt \fRPrompt
\fBpointer \fRPointer to the current line \fBpointer \fRPointer to the current line
\fBmarker \fRMulti-select marker \fBmarker \fRMulti-select marker

@ -63,6 +63,10 @@ const usage = `usage: fzf [options]
--border[=STYLE] Draw border around the finder --border[=STYLE] Draw border around the finder
[rounded|sharp|horizontal|vertical| [rounded|sharp|horizontal|vertical|
top|bottom|left|right|none] (default: rounded) top|bottom|left|right|none] (default: rounded)
--border-label=LABEL Label to print on the border
--border-label-pos=COL Position of the border label
[POSITIVE_INTEGER: columns from left|
NEGATIVE_INTEGER: columns from right] (default: 0)
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L) --margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L) --padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
--info=STYLE Finder info style [default|inline|hidden] --info=STYLE Finder info style [default|inline|hidden]
@ -188,6 +192,13 @@ type previewOpts struct {
alternative *previewOpts alternative *previewOpts
} }
func parseLabelPosition(arg string) int {
if strings.ToLower(arg) == "center" {
return 0
}
return atoi(arg)
}
func (a previewOpts) aboveOrBelow() bool { func (a previewOpts) aboveOrBelow() bool {
return a.size.size > 0 && (a.position == posUp || a.position == posDown) return a.size.size > 0 && (a.position == posUp || a.position == posDown)
} }
@ -258,6 +269,8 @@ type Options struct {
Margin [4]sizeSpec Margin [4]sizeSpec
Padding [4]sizeSpec Padding [4]sizeSpec
BorderShape tui.BorderShape BorderShape tui.BorderShape
Label string
LabelPos int
Unicode bool Unicode bool
Tabstop int Tabstop int
ClearOnExit bool ClearOnExit bool
@ -324,6 +337,8 @@ func defaultOptions() *Options {
Padding: defaultMargin(), Padding: defaultMargin(),
Unicode: true, Unicode: true,
Tabstop: 8, Tabstop: 8,
Label: "",
LabelPos: 0,
ClearOnExit: true, ClearOnExit: true,
Version: false} Version: false}
} }
@ -798,6 +813,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
mergeAttr(&theme.CurrentMatch) mergeAttr(&theme.CurrentMatch)
case "border": case "border":
mergeAttr(&theme.Border) mergeAttr(&theme.Border)
case "label":
mergeAttr(&theme.BorderLabel)
case "prompt": case "prompt":
mergeAttr(&theme.Prompt) mergeAttr(&theme.Prompt)
case "spinner": case "spinner":
@ -1556,6 +1573,11 @@ func parseOptions(opts *Options, allArgs []string) {
case "--border": case "--border":
hasArg, arg := optionalNextString(allArgs, &i) hasArg, arg := optionalNextString(allArgs, &i)
opts.BorderShape = parseBorder(arg, !hasArg) opts.BorderShape = parseBorder(arg, !hasArg)
case "--border-label":
opts.Label = nextString(allArgs, &i, "label required")
case "--border-label-pos":
pos := nextString(allArgs, &i, "label position required (positive or negative integer or 'center')")
opts.LabelPos = parseLabelPosition(pos)
case "--no-unicode": case "--no-unicode":
opts.Unicode = false opts.Unicode = false
case "--unicode": case "--unicode":
@ -1591,6 +1613,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Delimiter = delimiterRegexp(value) opts.Delimiter = delimiterRegexp(value)
} else if match, value := optString(arg, "--border="); match { } else if match, value := optString(arg, "--border="); match {
opts.BorderShape = parseBorder(value, false) opts.BorderShape = parseBorder(value, false)
} else if match, value := optString(arg, "--border-label="); match {
opts.Label = value
} else if match, value := optString(arg, "--border-label-pos="); match {
opts.LabelPos = parseLabelPosition(value)
} else if match, value := optString(arg, "--prompt="); match { } else if match, value := optString(arg, "--prompt="); match {
opts.Prompt = value opts.Prompt = value
} else if match, value := optString(arg, "--pointer="); match { } else if match, value := optString(arg, "--pointer="); match {

@ -114,6 +114,9 @@ type Terminal struct {
spinner []string spinner []string
prompt func() prompt func()
promptLen int promptLen int
borderLabel func()
borderLabelLen int
borderLabelPos int
pointer string pointer string
pointerLen int pointerLen int
pointerEmpty string pointerEmpty string
@ -544,6 +547,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
padding: opts.Padding, padding: opts.Padding,
unicode: opts.Unicode, unicode: opts.Unicode,
borderShape: opts.BorderShape, borderShape: opts.BorderShape,
borderLabel: nil,
borderLabelPos: opts.LabelPos,
cleanExit: opts.ClearOnExit, cleanExit: opts.ClearOnExit,
paused: opts.Phony, paused: opts.Phony,
strong: strongAttr, strong: strongAttr,
@ -587,6 +592,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
// Pre-calculated empty pointer and marker signs // Pre-calculated empty pointer and marker signs
t.pointerEmpty = strings.Repeat(" ", t.pointerLen) t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
t.markerEmpty = strings.Repeat(" ", t.markerLen) t.markerEmpty = strings.Repeat(" ", t.markerLen)
if len(opts.Label) > 0 {
t.borderLabel, t.borderLabelLen = t.parseBorderLabel(opts.Label)
}
return &t return &t
} }
@ -617,6 +625,25 @@ func (t *Terminal) MaxFitAndPad(opts *Options) (int, int) {
return fit, padHeight return fit, padHeight
} }
func (t *Terminal) parseBorderLabel(borderLabel string) (func(), int) {
text, colors, _ := extractColor(borderLabel, nil, nil)
runes := []rune(text)
item := &Item{text: util.RunesToChars(runes), colors: colors}
result := Result{item: item}
var offsets []colorOffset
borderLabelFn := func() {
if offsets == nil {
// tui.Col* are not initialized until renderer.Init()
offsets = result.colorOffsets(nil, t.theme, tui.ColBorderLabel, tui.ColBorderLabel, false)
}
text, _ := t.trimRight(runes, t.border.Width())
t.printColoredString(t.border, text, offsets, tui.ColBorderLabel)
}
borderLabelLen := runewidth.StringWidth(text)
return borderLabelFn, borderLabelLen
}
func (t *Terminal) parsePrompt(prompt string) (func(), int) { func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil) trimmed, colors, _ := extractColor(prompt, state, nil)
@ -911,6 +938,27 @@ func (t *Terminal) resizeWindows() {
false, tui.MakeBorderStyle(t.borderShape, t.unicode)) false, tui.MakeBorderStyle(t.borderShape, t.unicode))
} }
// Print border label
if t.border != nil && t.borderLabel != nil {
switch t.borderShape {
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp:
var col int
if t.borderLabelPos == 0 {
col = util.Max(0, (t.border.Width()-t.borderLabelLen)/2)
} else if t.borderLabelPos < 0 {
col = util.Max(0, t.border.Width()+t.borderLabelPos+1-t.borderLabelLen)
} else {
col = util.Min(t.borderLabelPos-1, t.border.Width()-t.borderLabelLen)
}
row := 0
if t.borderShape == tui.BorderBottom {
row = t.border.Height() - 1
}
t.border.Move(row, col)
t.borderLabel()
}
}
// Add padding to margin // Add padding to margin
for idx, val := range paddingInt { for idx, val := range paddingInt {
marginInt[idx] += val marginInt[idx] += val
@ -1394,6 +1442,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth) displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
} }
t.printColoredString(t.window, text, offsets, colBase)
return displayWidth
}
func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []colorOffset, colBase tui.ColorPair) {
var index int32 var index int32
var substr string var substr string
var prefixWidth int var prefixWidth int
@ -1403,11 +1456,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
e := util.Constrain32(offset.offset[1], index, maxOffset) e := util.Constrain32(offset.offset[1], index, maxOffset)
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth) substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
t.window.CPrint(colBase, substr) window.CPrint(colBase, substr)
if b < e { if b < e {
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth) substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
t.window.CPrint(offset.color, substr) window.CPrint(offset.color, substr)
} }
index = e index = e
@ -1417,9 +1470,8 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
} }
if index < maxOffset { if index < maxOffset {
substr, _ = t.processTabs(text[index:], prefixWidth) substr, _ = t.processTabs(text[index:], prefixWidth)
t.window.CPrint(colBase, substr) window.CPrint(colBase, substr)
} }
return displayWidth
} }
func (t *Terminal) renderPreviewSpinner() { func (t *Terminal) renderPreviewSpinner() {

@ -268,6 +268,7 @@ type ColorTheme struct {
Selected ColorAttr Selected ColorAttr
Header ColorAttr Header ColorAttr
Border ColorAttr Border ColorAttr
BorderLabel ColorAttr
} }
type Event struct { type Event struct {
@ -441,6 +442,7 @@ var (
ColBorder ColorPair ColBorder ColorPair
ColPreview ColorPair ColPreview ColorPair
ColPreviewBorder ColorPair ColPreviewBorder ColorPair
ColBorderLabel ColorPair
) )
func EmptyTheme() *ColorTheme { func EmptyTheme() *ColorTheme {
@ -463,7 +465,9 @@ func EmptyTheme() *ColorTheme {
Cursor: ColorAttr{colUndefined, AttrUndefined}, Cursor: ColorAttr{colUndefined, AttrUndefined},
Selected: ColorAttr{colUndefined, AttrUndefined}, Selected: ColorAttr{colUndefined, AttrUndefined},
Header: ColorAttr{colUndefined, AttrUndefined}, Header: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined}} Border: ColorAttr{colUndefined, AttrUndefined},
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
}
} }
func NoColorTheme() *ColorTheme { func NoColorTheme() *ColorTheme {
@ -486,7 +490,9 @@ func NoColorTheme() *ColorTheme {
Cursor: ColorAttr{colDefault, AttrRegular}, Cursor: ColorAttr{colDefault, AttrRegular},
Selected: ColorAttr{colDefault, AttrRegular}, Selected: ColorAttr{colDefault, AttrRegular},
Header: ColorAttr{colDefault, AttrRegular}, Header: ColorAttr{colDefault, AttrRegular},
Border: ColorAttr{colDefault, AttrRegular}} Border: ColorAttr{colDefault, AttrRegular},
BorderLabel: ColorAttr{colDefault, AttrRegular},
}
} }
func errorExit(message string) { func errorExit(message string) {
@ -514,7 +520,9 @@ func init() {
Cursor: ColorAttr{colRed, AttrUndefined}, Cursor: ColorAttr{colRed, AttrUndefined},
Selected: ColorAttr{colMagenta, AttrUndefined}, Selected: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined}, Header: ColorAttr{colCyan, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined}} Border: ColorAttr{colBlack, AttrUndefined},
BorderLabel: ColorAttr{colWhite, AttrUndefined},
}
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
@ -534,7 +542,9 @@ func init() {
Cursor: ColorAttr{161, AttrUndefined}, Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined}, Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{109, AttrUndefined}, Header: ColorAttr{109, AttrUndefined},
Border: ColorAttr{59, AttrUndefined}} Border: ColorAttr{59, AttrUndefined},
BorderLabel: ColorAttr{145, AttrUndefined},
}
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
@ -554,7 +564,9 @@ func init() {
Cursor: ColorAttr{161, AttrUndefined}, Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined}, Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{31, AttrUndefined}, Header: ColorAttr{31, AttrUndefined},
Border: ColorAttr{145, AttrUndefined}} Border: ColorAttr{145, AttrUndefined},
BorderLabel: ColorAttr{59, AttrUndefined},
}
} }
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) { func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
@ -590,6 +602,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.Selected = o(baseTheme.Selected, theme.Selected) theme.Selected = o(baseTheme.Selected, theme.Selected)
theme.Header = o(baseTheme.Header, theme.Header) theme.Header = o(baseTheme.Header, theme.Header)
theme.Border = o(baseTheme.Border, theme.Border) theme.Border = o(baseTheme.Border, theme.Border)
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
initPalette(theme) initPalette(theme)
} }
@ -622,6 +635,7 @@ func initPalette(theme *ColorTheme) {
ColInfo = pair(theme.Info, theme.Bg) ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg) ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg) ColBorder = pair(theme.Border, theme.Bg)
ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg) ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg) ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
} }

Loading…
Cancel
Save