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
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) label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
# Border label at the center # 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) # 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) # Right-aligned (negative integer) on the bottom line (:bottom)
fzf --height=10 --border-label="╢ $label ╟" --border=bottom --border-label-pos=-3 --color=label:italic:black 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 - 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 preview window
- Info panel (counter) will be followed by a horizontal separator by default - Info panel (counter) will be followed by a horizontal separator by default
- The color of the separator can be customized via `--color=separator:...` - 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) label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
# Border label at the center # 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) # 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) # Right-aligned (negative integer) on the bottom line (:bottom)
fzf --height=10 --border-label="╢ $label ╟" --border=bottom --border-label-pos=-3 --color=label:italic:black\fR fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --color=label:italic:black\fR
.TP .TP
.BI "--border-label-pos" [=COL] .BI "--border-label-pos" [=N[:top|bottom]]
Horizontal position of the border label on the border line. Specify a positive Position of the border label on the border line. Specify a positive integer as
integer as the column position from the left. Specify a negative integer to the column position from the left. Specify a negative integer to right-align
right-align the label. The default value 0 (or \fBcenter\fR) will put the label. Label is printed on the top border line by default, add
the label at the center of the border line. \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 .TP
.B "--no-unicode" .B "--no-unicode"
@@ -396,7 +397,7 @@ color mappings.
\fBinfo \fRInfo line (match counters) \fBinfo \fRInfo line (match counters)
\fBseparator \fRHorizontal separator on info line (match counters) \fBseparator \fRHorizontal separator on info 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) \fBlabel \fRBorder label (\fB--border-label\fR and \fB--preview-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
@@ -527,6 +528,33 @@ e.g.
sleep 0.01 sleep 0.01
done'\fR done'\fR
.RE .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 .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)]" .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=LABEL Label to print on the border
--border-label-pos=COL Position of the border label --border-label-pos=COL Position of the border label
[POSITIVE_INTEGER: columns from left| [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) --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[:nosep]] --info=STYLE Finder info style [default|inline|hidden[:nosep]]
@@ -97,7 +98,8 @@ const usage = `usage: fzf [options]
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES] [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)] [,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
--preview-label=LABEL --preview-label=LABEL
--preview-label-pos=COL --preview-label-pos=N Same as --border-label and --border-label-pos,
but for preview window
Scripting Scripting
-q, --query=STR Start the finder with the given query -q, --query=STR Start the finder with the given query
@@ -183,6 +185,12 @@ const (
infoHidden infoHidden
) )
type labelOpts struct {
label string
column int
bottom bool
}
type previewOpts struct { type previewOpts struct {
command string command string
position windowPosition position windowPosition
@@ -198,11 +206,21 @@ type previewOpts struct {
alternative *previewOpts alternative *previewOpts
} }
func parseLabelPosition(arg string) int { func parseLabelPosition(opts *labelOpts, arg string) {
if strings.ToLower(arg) == "center" { opts.column = 0
return 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 { func (a previewOpts) aboveOrBelow() bool {
@@ -275,10 +293,8 @@ type Options struct {
Margin [4]sizeSpec Margin [4]sizeSpec
Padding [4]sizeSpec Padding [4]sizeSpec
BorderShape tui.BorderShape BorderShape tui.BorderShape
Label string BorderLabel labelOpts
LabelPos int PreviewLabel labelOpts
PLabel string
PLabelPos int
Unicode bool Unicode bool
Tabstop int Tabstop int
ClearOnExit bool ClearOnExit bool
@@ -345,10 +361,8 @@ func defaultOptions() *Options {
Padding: defaultMargin(), Padding: defaultMargin(),
Unicode: true, Unicode: true,
Tabstop: 8, Tabstop: 8,
Label: "", BorderLabel: labelOpts{},
LabelPos: 0, PreviewLabel: labelOpts{},
PLabel: "",
PLabelPos: 0,
ClearOnExit: true, ClearOnExit: true,
Version: false} Version: false}
} }
@@ -847,7 +861,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
return theme return theme
} }
var executeRegexp *regexp.Regexp var (
executeRegexp *regexp.Regexp
splitRegexp *regexp.Regexp
)
func firstKey(keymap map[tui.Event]string) tui.Event { func firstKey(keymap map[tui.Event]string) tui.Event {
for k := range keymap { for k := range keymap {
@@ -867,6 +884,7 @@ func init() {
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|') // "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile( 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)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`) `(?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) { func parseKeymap(keymap map[tui.Event][]*action, str string) {
@@ -1229,7 +1247,7 @@ func parseInfoStyle(str string) infoStyle {
layout := infoDefault layout := infoDefault
separator := true separator := true
for _, token := range regexp.MustCompile("[,:]").Split(strings.ToLower(str), -1) { for _, token := range splitRegexp.Split(strings.ToLower(str), -1) {
switch token { switch token {
case "default": case "default":
layout = infoDefault layout = infoDefault
@@ -1594,16 +1612,20 @@ 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 "--no-border-label":
opts.BorderLabel.label = ""
case "--border-label": case "--border-label":
opts.Label = nextString(allArgs, &i, "label required") opts.BorderLabel.label = nextString(allArgs, &i, "label required")
case "--border-label-pos": case "--border-label-pos":
pos := nextString(allArgs, &i, "label position required (positive or negative integer or 'center')") 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": case "--preview-label":
opts.PLabel = nextString(allArgs, &i, "preview label required") opts.PreviewLabel.label = nextString(allArgs, &i, "preview label required")
case "--preview-label-pos": case "--preview-label-pos":
pos := nextString(allArgs, &i, "preview label position required (positive or negative integer or 'center')") 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": case "--no-unicode":
opts.Unicode = false opts.Unicode = false
case "--unicode": case "--unicode":
@@ -1640,13 +1662,13 @@ func parseOptions(opts *Options, allArgs []string) {
} 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 { } 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 { } 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 { } 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 { } 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 { } 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 {

View File

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