Allow putting border label on the bottom line

Related #3022
This commit is contained in:
Junegunn Choi 2022-10-31 00:22:41 +09:00
parent 31bbaad06e
commit c09ec8e4d1
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
4 changed files with 229 additions and 177 deletions

View File

@ -16,15 +16,18 @@ CHANGELOG
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
fzf --height=10 --border --border-label="╢ $label ╟" --color=label:italic:black
# Left-aligned (positive integer)
fzf --height=10 --border-label="╢ $label ╟" --border=top --border-label-pos=3 --color=label:italic:black
fzf --height=10 --border --border-label="╢ $label ╟" --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
# Right-aligned (negative integer) on the bottom line (:bottom)
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --color=label:italic:black
```
- Also added `--preview-label` and `--preview-label-pos` for the border of the
```sh
fzf --preview 'cat {}' --border --preview-label=' Preview ' --preview-label-pos=2
```
preview window
- Info panel (counter) will be followed by a horizontal separator by default
- The color of the separator can be customized via `--color=separator:...`

View File

@ -250,20 +250,21 @@ e.g.
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
fzf --height=10 --border --border-label="╢ $label ╟" --color=label:italic:black
# Left-aligned (positive integer)
fzf --height=10 --border-label="╢ $label ╟" --border=top --border-label-pos=3 --color=label:italic:black
fzf --height=10 --border --border-label="╢ $label ╟" --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
# Right-aligned (negative integer) on the bottom line (:bottom)
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --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.
.BI "--border-label-pos" [=N[:top|bottom]]
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. Label is printed on the top border line by default, add
\fB:bottom\fR to put it on the border line on the bottom. The default value
\fB0 (or \fBcenter\fR) will put the label at the center of the border line.
.TP
.B "--no-unicode"
@ -396,7 +397,7 @@ color mappings.
\fBinfo \fRInfo line (match counters)
\fBseparator \fRHorizontal separator on info line (match counters)
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBlabel \fRBorder label (\fB--border-label\fR)
\fBlabel \fRBorder label (\fB--border-label\fR and \fB--preview-label\fR)
\fBprompt \fRPrompt
\fBpointer \fRPointer to the current line
\fBmarker \fRMulti-select marker
@ -527,6 +528,33 @@ e.g.
sleep 0.01
done'\fR
.RE
.TP
.BI "--preview-label" [=LABEL]
Label to print on the horizontal border line of the preview window.
Should be used with one of the following \fB--preview-window\fR options.
.br
.B * border-rounded (default)
.br
.B * border-sharp
.br
.B * border-horizontal
.br
.B * border-top
.br
.B * border-bottom
.br
.TP
.BI "--preview-label-pos" [=N[:top|bottom]]
Position of the border label on the border line of the preview window. Specify
a positive integer as the column position from the left. Specify a negative
integer to right-align the label. Label is printed on the top border line by
default, add \fB:bottom\fR to put it on the border line on the bottom. The
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]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"

View File

@ -66,7 +66,8 @@ const usage = `usage: fzf [options]
--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)
NEGATIVE_INTEGER: columns from right][:bottom]
(default: 0 or center)
--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[:nosep]]
@ -97,7 +98,8 @@ const usage = `usage: fzf [options]
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
--preview-label=LABEL
--preview-label-pos=COL
--preview-label-pos=N Same as --border-label and --border-label-pos,
but for preview window
Scripting
-q, --query=STR Start the finder with the given query
@ -183,6 +185,12 @@ const (
infoHidden
)
type labelOpts struct {
label string
column int
bottom bool
}
type previewOpts struct {
command string
position windowPosition
@ -198,11 +206,21 @@ type previewOpts struct {
alternative *previewOpts
}
func parseLabelPosition(arg string) int {
if strings.ToLower(arg) == "center" {
return 0
func parseLabelPosition(opts *labelOpts, arg string) {
opts.column = 0
opts.bottom = false
for _, token := range splitRegexp.Split(strings.ToLower(arg), -1) {
switch token {
case "center":
opts.column = 0
case "bottom":
opts.bottom = true
case "top":
opts.bottom = false
default:
opts.column = atoi(token)
}
}
return atoi(arg)
}
func (a previewOpts) aboveOrBelow() bool {
@ -221,68 +239,66 @@ func (a previewOpts) sameContentLayout(b previewOpts) bool {
// Options stores the values of command-line options
type Options struct {
Fuzzy bool
FuzzyAlgo algo.Algo
Scheme string
Extended bool
Phony bool
Case Case
Normalize bool
Nth []Range
WithNth []Range
Delimiter Delimiter
Sort int
Tac bool
Criteria []criterion
Multi int
Ansi bool
Mouse bool
Theme *tui.ColorTheme
Black bool
Bold bool
Height heightSpec
MinHeight int
Layout layoutType
Cycle bool
KeepRight bool
Hscroll bool
HscrollOff int
ScrollOff int
FileWord bool
InfoStyle infoStyle
JumpLabels string
Prompt string
Pointer string
Marker string
Query string
Select1 bool
Exit0 bool
Filter *string
ToggleSort bool
Expect map[tui.Event]string
Keymap map[tui.Event][]*action
Preview previewOpts
PrintQuery bool
ReadZero bool
Printer func(string)
PrintSep string
Sync bool
History *History
Header []string
HeaderLines int
HeaderFirst bool
Ellipsis string
Margin [4]sizeSpec
Padding [4]sizeSpec
BorderShape tui.BorderShape
Label string
LabelPos int
PLabel string
PLabelPos int
Unicode bool
Tabstop int
ClearOnExit bool
Version bool
Fuzzy bool
FuzzyAlgo algo.Algo
Scheme string
Extended bool
Phony bool
Case Case
Normalize bool
Nth []Range
WithNth []Range
Delimiter Delimiter
Sort int
Tac bool
Criteria []criterion
Multi int
Ansi bool
Mouse bool
Theme *tui.ColorTheme
Black bool
Bold bool
Height heightSpec
MinHeight int
Layout layoutType
Cycle bool
KeepRight bool
Hscroll bool
HscrollOff int
ScrollOff int
FileWord bool
InfoStyle infoStyle
JumpLabels string
Prompt string
Pointer string
Marker string
Query string
Select1 bool
Exit0 bool
Filter *string
ToggleSort bool
Expect map[tui.Event]string
Keymap map[tui.Event][]*action
Preview previewOpts
PrintQuery bool
ReadZero bool
Printer func(string)
PrintSep string
Sync bool
History *History
Header []string
HeaderLines int
HeaderFirst bool
Ellipsis string
Margin [4]sizeSpec
Padding [4]sizeSpec
BorderShape tui.BorderShape
BorderLabel labelOpts
PreviewLabel labelOpts
Unicode bool
Tabstop int
ClearOnExit bool
Version bool
}
func defaultPreviewOpts(command string) previewOpts {
@ -291,66 +307,64 @@ func defaultPreviewOpts(command string) previewOpts {
func defaultOptions() *Options {
return &Options{
Fuzzy: true,
FuzzyAlgo: algo.FuzzyMatchV2,
Scheme: "default",
Extended: true,
Phony: false,
Case: CaseSmart,
Normalize: true,
Nth: make([]Range, 0),
WithNth: make([]Range, 0),
Delimiter: Delimiter{},
Sort: 1000,
Tac: false,
Criteria: []criterion{byScore, byLength},
Multi: 0,
Ansi: false,
Mouse: true,
Theme: tui.EmptyTheme(),
Black: false,
Bold: true,
MinHeight: 10,
Layout: layoutDefault,
Cycle: false,
KeepRight: false,
Hscroll: true,
HscrollOff: 10,
ScrollOff: 0,
FileWord: false,
InfoStyle: infoStyle{layout: infoDefault, separator: true},
JumpLabels: defaultJumpLabels,
Prompt: "> ",
Pointer: ">",
Marker: ">",
Query: "",
Select1: false,
Exit0: false,
Filter: nil,
ToggleSort: false,
Expect: make(map[tui.Event]string),
Keymap: make(map[tui.Event][]*action),
Preview: defaultPreviewOpts(""),
PrintQuery: false,
ReadZero: false,
Printer: func(str string) { fmt.Println(str) },
PrintSep: "\n",
Sync: false,
History: nil,
Header: make([]string, 0),
HeaderLines: 0,
HeaderFirst: false,
Ellipsis: "..",
Margin: defaultMargin(),
Padding: defaultMargin(),
Unicode: true,
Tabstop: 8,
Label: "",
LabelPos: 0,
PLabel: "",
PLabelPos: 0,
ClearOnExit: true,
Version: false}
Fuzzy: true,
FuzzyAlgo: algo.FuzzyMatchV2,
Scheme: "default",
Extended: true,
Phony: false,
Case: CaseSmart,
Normalize: true,
Nth: make([]Range, 0),
WithNth: make([]Range, 0),
Delimiter: Delimiter{},
Sort: 1000,
Tac: false,
Criteria: []criterion{byScore, byLength},
Multi: 0,
Ansi: false,
Mouse: true,
Theme: tui.EmptyTheme(),
Black: false,
Bold: true,
MinHeight: 10,
Layout: layoutDefault,
Cycle: false,
KeepRight: false,
Hscroll: true,
HscrollOff: 10,
ScrollOff: 0,
FileWord: false,
InfoStyle: infoStyle{layout: infoDefault, separator: true},
JumpLabels: defaultJumpLabels,
Prompt: "> ",
Pointer: ">",
Marker: ">",
Query: "",
Select1: false,
Exit0: false,
Filter: nil,
ToggleSort: false,
Expect: make(map[tui.Event]string),
Keymap: make(map[tui.Event][]*action),
Preview: defaultPreviewOpts(""),
PrintQuery: false,
ReadZero: false,
Printer: func(str string) { fmt.Println(str) },
PrintSep: "\n",
Sync: false,
History: nil,
Header: make([]string, 0),
HeaderLines: 0,
HeaderFirst: false,
Ellipsis: "..",
Margin: defaultMargin(),
Padding: defaultMargin(),
Unicode: true,
Tabstop: 8,
BorderLabel: labelOpts{},
PreviewLabel: labelOpts{},
ClearOnExit: true,
Version: false}
}
func help(code int) {
@ -847,7 +861,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
return theme
}
var executeRegexp *regexp.Regexp
var (
executeRegexp *regexp.Regexp
splitRegexp *regexp.Regexp
)
func firstKey(keymap map[tui.Event]string) tui.Event {
for k := range keymap {
@ -867,6 +884,7 @@ func init() {
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile(
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
splitRegexp = regexp.MustCompile("[,:]+")
}
func parseKeymap(keymap map[tui.Event][]*action, str string) {
@ -1229,7 +1247,7 @@ func parseInfoStyle(str string) infoStyle {
layout := infoDefault
separator := true
for _, token := range regexp.MustCompile("[,:]").Split(strings.ToLower(str), -1) {
for _, token := range splitRegexp.Split(strings.ToLower(str), -1) {
switch token {
case "default":
layout = infoDefault
@ -1594,16 +1612,20 @@ func parseOptions(opts *Options, allArgs []string) {
case "--border":
hasArg, arg := optionalNextString(allArgs, &i)
opts.BorderShape = parseBorder(arg, !hasArg)
case "--no-border-label":
opts.BorderLabel.label = ""
case "--border-label":
opts.Label = nextString(allArgs, &i, "label required")
opts.BorderLabel.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)
parseLabelPosition(&opts.BorderLabel, pos)
case "--no-preview-label":
opts.PreviewLabel.label = ""
case "--preview-label":
opts.PLabel = nextString(allArgs, &i, "preview label required")
opts.PreviewLabel.label = nextString(allArgs, &i, "preview label required")
case "--preview-label-pos":
pos := nextString(allArgs, &i, "preview label position required (positive or negative integer or 'center')")
opts.PLabelPos = parseLabelPosition(pos)
parseLabelPosition(&opts.PreviewLabel, pos)
case "--no-unicode":
opts.Unicode = false
case "--unicode":
@ -1640,13 +1662,13 @@ func parseOptions(opts *Options, allArgs []string) {
} 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
opts.BorderLabel.label = value
} else if match, value := optString(arg, "--border-label-pos="); match {
opts.LabelPos = parseLabelPosition(value)
parseLabelPosition(&opts.BorderLabel, value)
} else if match, value := optString(arg, "--preview-label="); match {
opts.PLabel = value
opts.PreviewLabel.label = value
} else if match, value := optString(arg, "--preview-label-pos="); match {
opts.PLabelPos = parseLabelPosition(value)
parseLabelPosition(&opts.PreviewLabel, value)
} else if match, value := optString(arg, "--prompt="); match {
opts.Prompt = value
} else if match, value := optString(arg, "--pointer="); match {

View File

@ -114,12 +114,12 @@ type Terminal struct {
spinner []string
prompt func()
promptLen int
borderLabel func()
borderLabel func(tui.Window)
borderLabelLen int
borderLabelPos int
previewLabel func()
borderLabelOpts labelOpts
previewLabel func(tui.Window)
previewLabelLen int
previewLabelPos int
previewLabelOpts labelOpts
pointer string
pointerLen int
pointerEmpty string
@ -551,9 +551,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
unicode: opts.Unicode,
borderShape: opts.BorderShape,
borderLabel: nil,
borderLabelPos: opts.LabelPos,
borderLabelOpts: opts.BorderLabel,
previewLabel: nil,
previewLabelPos: opts.PLabelPos,
previewLabelOpts: opts.PreviewLabel,
cleanExit: opts.ClearOnExit,
paused: opts.Phony,
strong: strongAttr,
@ -597,12 +597,8 @@ 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)
}
if len(opts.PLabel) > 0 {
t.previewLabel, t.previewLabelLen = t.parseBorderLabel(opts.PLabel)
}
t.borderLabel, t.borderLabelLen = t.parseBorderLabel(opts.BorderLabel.label)
t.previewLabel, t.previewLabelLen = t.parseBorderLabel(opts.PreviewLabel.label)
return &t
}
@ -633,20 +629,23 @@ func (t *Terminal) MaxFitAndPad(opts *Options) (int, int) {
return fit, padHeight
}
func (t *Terminal) parseBorderLabel(borderLabel string) (func(), int) {
func (t *Terminal) parseBorderLabel(borderLabel string) (func(tui.Window), int) {
if len(borderLabel) == 0 {
return nil, 0
}
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() {
borderLabelFn := func(window tui.Window) {
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)
text, _ := t.trimRight(runes, window.Width())
t.printColoredString(window, text, offsets, tui.ColBorderLabel)
}
borderLabelLen := runewidth.StringWidth(text)
return borderLabelFn, borderLabelLen
@ -1052,7 +1051,7 @@ func (t *Terminal) resizeWindows() {
}
// Print border label
printLabel := func(window tui.Window, render func(), pos int, length int, borderShape tui.BorderShape) {
printLabel := func(window tui.Window, render func(tui.Window), opts labelOpts, length int, borderShape tui.BorderShape) {
if window == nil || render == nil {
return
}
@ -1060,23 +1059,23 @@ func (t *Terminal) resizeWindows() {
switch borderShape {
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp:
var col int
if pos == 0 {
if opts.column == 0 {
col = util.Max(0, (window.Width()-length)/2)
} else if pos < 0 {
col = util.Max(0, window.Width()+pos+1-length)
} else if opts.column < 0 {
col = util.Max(0, window.Width()+opts.column+1-length)
} else {
col = util.Min(pos-1, window.Width()-length)
col = util.Min(opts.column-1, window.Width()-length)
}
row := 0
if borderShape == tui.BorderBottom {
if borderShape == tui.BorderBottom || opts.bottom {
row = window.Height() - 1
}
window.Move(row, col)
render()
render(window)
}
}
printLabel(t.border, t.borderLabel, t.borderLabelPos, t.borderLabelLen, t.borderShape)
printLabel(t.pborder, t.previewLabel, t.previewLabelPos, t.previewLabelLen, t.previewOpts.border)
printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape)
printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border)
for i := 0; i < t.window.Height(); i++ {
t.window.MoveAndClear(i, 0)