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 {
@ -275,10 +293,8 @@ type Options struct {
Margin [4]sizeSpec
Padding [4]sizeSpec
BorderShape tui.BorderShape
Label string
LabelPos int
PLabel string
PLabelPos int
BorderLabel labelOpts
PreviewLabel labelOpts
Unicode bool
Tabstop int
ClearOnExit bool
@ -345,10 +361,8 @@ func defaultOptions() *Options {
Padding: defaultMargin(),
Unicode: true,
Tabstop: 8,
Label: "",
LabelPos: 0,
PLabel: "",
PLabelPos: 0,
BorderLabel: labelOpts{},
PreviewLabel: labelOpts{},
ClearOnExit: true,
Version: false}
}
@ -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)