summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2022-10-31 00:22:41 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2022-11-01 13:30:40 +0900
commitc09ec8e4d152dbbb6e0fe20f0182e4052cfe410e (patch)
tree25fa869c1175c60937c82a2f1a5e38083fed994a
parent31bbaad06e3815082d5cfa3b3bc7817da5e6ecd1 (diff)
Allow putting border label on the bottom line
Related #3022
-rw-r--r--CHANGELOG.md11
-rw-r--r--man/man1/fzf.148
-rw-r--r--src/options.go298
-rw-r--r--src/terminal.go49
4 files changed, 229 insertions, 177 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 42f70e3e..30ed4656 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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:...`
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 9d46d50c..8a534ee4 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -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)]"
diff --git a/src/options.go b/src/options.go
index d536dae6..1b57fe16 100644
--- a/src/options.go
+++ b/src/options.go
@@ -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 {
diff --git a/src/terminal.go b/src/terminal.go
index 2764ff85..57055c17 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -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)