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
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
------

@ -226,6 +226,45 @@ Draw border around the finder
.BR none
.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
.B "--no-unicode"
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
\fBinfo \fRInfo line (match counters)
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBlabel \fRBorder label (\fB--border-label\fR)
\fBprompt \fRPrompt
\fBpointer \fRPointer to the current line
\fBmarker \fRMulti-select marker

@ -18,97 +18,101 @@ import (
const usage = `usage: fzf [options]
Search
-x, --extended Extended-search mode
(enabled by default; +x or --no-extended to disable)
-e, --exact Enable Exact-match
-i Case-insensitive match (default: smart-case match)
+i Case-sensitive match
--scheme=SCHEME Scoring scheme [default|path|history]
--literal Do not normalize latin script letters before matching
-n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero
integer or a range expression ([BEGIN]..[END]).
--with-nth=N[,..] Transform the presentation of each line using
field index expressions
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
+s, --no-sort Do not sort the result
--tac Reverse the order of the input
--disabled Do not perform search
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
when the scores are tied [length|chunk|begin|end|index]
(default: length)
-x, --extended Extended-search mode
(enabled by default; +x or --no-extended to disable)
-e, --exact Enable Exact-match
-i Case-insensitive match (default: smart-case match)
+i Case-sensitive match
--scheme=SCHEME Scoring scheme [default|path|history]
--literal Do not normalize latin script letters before matching
-n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero
integer or a range expression ([BEGIN]..[END]).
--with-nth=N[,..] Transform the presentation of each line using
field index expressions
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
+s, --no-sort Do not sort the result
--tac Reverse the order of the input
--disabled Do not perform search
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
when the scores are tied [length|chunk|begin|end|index]
(default: length)
Interface
-m, --multi[=MAX] Enable multi-select with tab/shift-tab
--no-mouse Disable mouse
--bind=KEYBINDS Custom key bindings. Refer to the man page.
--cycle Enable cyclic scroll
--keep-right Keep the right end of the line visible on overflow
--scroll-off=LINES Number of screen lines to keep above or below when
scrolling to the top or to the bottom (default: 0)
--no-hscroll Disable horizontal scroll
--hscroll-off=COLS Number of screen columns to keep to the right of the
highlighted substring (default: 10)
--filepath-word Make word-wise movements respect path separators
--jump-labels=CHARS Label characters for jump and jump-accept
-m, --multi[=MAX] Enable multi-select with tab/shift-tab
--no-mouse Disable mouse
--bind=KEYBINDS Custom key bindings. Refer to the man page.
--cycle Enable cyclic scroll
--keep-right Keep the right end of the line visible on overflow
--scroll-off=LINES Number of screen lines to keep above or below when
scrolling to the top or to the bottom (default: 0)
--no-hscroll Disable horizontal scroll
--hscroll-off=COLS Number of screen columns to keep to the right of the
highlighted substring (default: 10)
--filepath-word Make word-wise movements respect path separators
--jump-labels=CHARS Label characters for jump and jump-accept
Layout
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
height instead of using fullscreen.
If prefixed with '~', fzf will determine the height
according to the input size.
--min-height=HEIGHT Minimum height when --height is given in percent
(default: 10)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border[=STYLE] Draw border around the finder
[rounded|sharp|horizontal|vertical|
top|bottom|left|right|none] (default: rounded)
--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)
--info=STYLE Finder info style [default|inline|hidden]
--prompt=STR Input prompt (default: '> ')
--pointer=STR Pointer to the current line (default: '>')
--marker=STR Multi-select marker (default: '>')
--header=STR String to print as header
--header-lines=N The first N lines of the input are treated as header
--header-first Print header before the prompt line
--ellipsis=STR Ellipsis to show when line is truncated (default: '..')
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
height instead of using fullscreen.
If prefixed with '~', fzf will determine the height
according to the input size.
--min-height=HEIGHT Minimum height when --height is given in percent
(default: 10)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border[=STYLE] Draw border around the finder
[rounded|sharp|horizontal|vertical|
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)
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
--info=STYLE Finder info style [default|inline|hidden]
--prompt=STR Input prompt (default: '> ')
--pointer=STR Pointer to the current line (default: '>')
--marker=STR Multi-select marker (default: '>')
--header=STR String to print as header
--header-lines=N The first N lines of the input are treated as header
--header-first Print header before the prompt line
--ellipsis=STR Ellipsis to show when line is truncated (default: '..')
Display
--ansi Enable processing of ANSI color codes
--tabstop=SPACES Number of spaces for a tab character (default: 8)
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
--no-bold Do not use bold text
--ansi Enable processing of ANSI color codes
--tabstop=SPACES Number of spaces for a tab character (default: 8)
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
--no-bold Do not use bold text
History
--history=FILE History file
--history-size=N Maximum number of history entries (default: 1000)
--history=FILE History file
--history-size=N Maximum number of history entries (default: 1000)
Preview
--preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][,SIZE[%]]
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
[,border-BORDER_OPT]
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
--preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][,SIZE[%]]
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
[,border-BORDER_OPT]
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
Scripting
-q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line
--expect=KEYS Comma-separated list of keys to complete fzf
--read0 Read input delimited by ASCII NUL characters
--print0 Print output delimited by ASCII NUL characters
--sync Synchronous search for multi-staged filtering
--version Display version information and exit
-q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line
--expect=KEYS Comma-separated list of keys to complete fzf
--read0 Read input delimited by ASCII NUL characters
--print0 Print output delimited by ASCII NUL characters
--sync Synchronous search for multi-staged filtering
--version Display version information and exit
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Default options
(e.g. '--layout=reverse --inline-info')
FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Default options
(e.g. '--layout=reverse --inline-info')
`
@ -188,6 +192,13 @@ type previewOpts struct {
alternative *previewOpts
}
func parseLabelPosition(arg string) int {
if strings.ToLower(arg) == "center" {
return 0
}
return atoi(arg)
}
func (a previewOpts) aboveOrBelow() bool {
return a.size.size > 0 && (a.position == posUp || a.position == posDown)
}
@ -258,6 +269,8 @@ type Options struct {
Margin [4]sizeSpec
Padding [4]sizeSpec
BorderShape tui.BorderShape
Label string
LabelPos int
Unicode bool
Tabstop int
ClearOnExit bool
@ -324,6 +337,8 @@ func defaultOptions() *Options {
Padding: defaultMargin(),
Unicode: true,
Tabstop: 8,
Label: "",
LabelPos: 0,
ClearOnExit: true,
Version: false}
}
@ -798,6 +813,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
mergeAttr(&theme.CurrentMatch)
case "border":
mergeAttr(&theme.Border)
case "label":
mergeAttr(&theme.BorderLabel)
case "prompt":
mergeAttr(&theme.Prompt)
case "spinner":
@ -1556,6 +1573,11 @@ func parseOptions(opts *Options, allArgs []string) {
case "--border":
hasArg, arg := optionalNextString(allArgs, &i)
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":
opts.Unicode = false
case "--unicode":
@ -1591,6 +1613,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Delimiter = delimiterRegexp(value)
} else if match, value := optString(arg, "--border="); match {
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 {
opts.Prompt = value
} else if match, value := optString(arg, "--pointer="); match {

@ -114,6 +114,9 @@ type Terminal struct {
spinner []string
prompt func()
promptLen int
borderLabel func()
borderLabelLen int
borderLabelPos int
pointer string
pointerLen int
pointerEmpty string
@ -544,6 +547,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
padding: opts.Padding,
unicode: opts.Unicode,
borderShape: opts.BorderShape,
borderLabel: nil,
borderLabelPos: opts.LabelPos,
cleanExit: opts.ClearOnExit,
paused: opts.Phony,
strong: strongAttr,
@ -587,6 +592,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
// Pre-calculated empty pointer and marker signs
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
t.markerEmpty = strings.Repeat(" ", t.markerLen)
if len(opts.Label) > 0 {
t.borderLabel, t.borderLabelLen = t.parseBorderLabel(opts.Label)
}
return &t
}
@ -617,6 +625,25 @@ func (t *Terminal) MaxFitAndPad(opts *Options) (int, int) {
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) {
var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil)
@ -911,6 +938,27 @@ func (t *Terminal) resizeWindows() {
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
for idx, val := range paddingInt {
marginInt[idx] += val
@ -1394,6 +1442,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
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 substr string
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)
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
t.window.CPrint(colBase, substr)
window.CPrint(colBase, substr)
if b < e {
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
t.window.CPrint(offset.color, substr)
window.CPrint(offset.color, substr)
}
index = e
@ -1417,9 +1470,8 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
}
if index < maxOffset {
substr, _ = t.processTabs(text[index:], prefixWidth)
t.window.CPrint(colBase, substr)
window.CPrint(colBase, substr)
}
return displayWidth
}
func (t *Terminal) renderPreviewSpinner() {

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

Loading…
Cancel
Save