m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-14 14:23:47 -05:00

Add footer

Options:
  --footer=STR             String to print as footer
  --footer-border[=STYLE]  Draw border around the footer section
                           [rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
                            top|bottom|left|right|line|none] (default: line)
  --footer-label=LABEL     Label to print on the footer border
  --footer-label-pos=COL   Position of the footer label
                           [POSITIVE_INTEGER: columns from left|
                            NEGATIVE_INTEGER: columns from right][:bottom]
                           (default: 0 or center)

The default border type for footer is 'line', which draws a single
separator between the footer and the list. It changes its position
depending on `--layout`, so you don't have to manually switch between
'top' and 'bottom'

The 'line' style is now supported by other border types as well.
`--list-border` is the only exception.
This commit is contained in:
Junegunn Choi
2025-06-10 00:26:57 +09:00
parent 39db026161
commit 3b68dcdd81
11 changed files with 721 additions and 182 deletions

View File

@@ -83,7 +83,7 @@ Usage: fzf [options]
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
--border[=STYLE] Draw border around the finder
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
top|bottom|left|right|none] (default: rounded)
top|bottom|left|right|line|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|
@@ -140,7 +140,7 @@ Usage: fzf [options]
--filepath-word Make word-wise movements respect path separators
--input-border[=STYLE] Draw border around the input section
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
top|bottom|left|right|none] (default: rounded)
top|bottom|left|right|line|none] (default: rounded)
--input-label=LABEL Label to print on the input border
--input-label-pos=COL Position of the input label
[POSITIVE_INTEGER: columns from left|
@@ -168,7 +168,7 @@ Usage: fzf [options]
--header-first Print header before the prompt line
--header-border[=STYLE] Draw border around the header section
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
top|bottom|left|right|none] (default: rounded)
top|bottom|left|right|line|none] (default: rounded)
--header-lines-border[=STYLE]
Display header from --header-lines with a separate border.
Pass 'none' to still separate it but without a border.
@@ -178,6 +178,17 @@ Usage: fzf [options]
NEGATIVE_INTEGER: columns from right][:bottom]
(default: 0 or center)
FOOTER
--footer=STR String to print as footer
--footer-border[=STYLE] Draw border around the footer section
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
top|bottom|left|right|line|none] (default: line)
--footer-label=LABEL Label to print on the footer border
--footer-label-pos=COL Position of the footer label
[POSITIVE_INTEGER: columns from left|
NEGATIVE_INTEGER: columns from right][:bottom]
(default: 0 or center)
SCRIPTING
-q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match
@@ -599,6 +610,7 @@ type Options struct {
Header []string
HeaderLines int
HeaderFirst bool
Footer []string
Gap int
GapLine *string
Ellipsis *string
@@ -610,8 +622,10 @@ type Options struct {
InputBorderShape tui.BorderShape
HeaderBorderShape tui.BorderShape
HeaderLinesShape tui.BorderShape
FooterBorderShape tui.BorderShape
InputLabel labelOpts
HeaderLabel labelOpts
FooterLabel labelOpts
BorderLabel labelOpts
ListLabel labelOpts
PreviewLabel labelOpts
@@ -716,6 +730,7 @@ func defaultOptions() *Options {
Header: make([]string, 0),
HeaderLines: 0,
HeaderFirst: false,
Footer: make([]string, 0),
Gap: 0,
Ellipsis: nil,
Scrollbar: nil,
@@ -880,12 +895,9 @@ func parseAlgo(str string) (algo.Algo, error) {
return nil, errors.New("invalid algorithm (expected: v1 or v2)")
}
func parseBorder(str string, optional bool, allowLine bool) (tui.BorderShape, error) {
func parseBorder(str string, optional bool) (tui.BorderShape, error) {
switch str {
case "line":
if !allowLine {
return tui.BorderNone, errors.New("'line' is only allowed for preview border")
}
return tui.BorderLine, nil
case "rounded":
return tui.BorderRounded, nil
@@ -1348,6 +1360,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
mergeAttr(&theme.HeaderBorder)
case "header-label":
mergeAttr(&theme.HeaderLabel)
case "footer-border":
mergeAttr(&theme.FooterBorder)
case "footer-label":
mergeAttr(&theme.FooterLabel)
case "spinner":
mergeAttr(&theme.Spinner)
case "info":
@@ -1360,6 +1376,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
mergeAttr(&theme.Header)
case "header-bg":
mergeAttr(&theme.HeaderBg)
case "footer", "footer-fg":
mergeAttr(&theme.Footer)
case "footer-bg":
mergeAttr(&theme.FooterBg)
case "gap-line":
mergeAttr(&theme.GapLine)
default:
@@ -1415,7 +1435,7 @@ const (
func init() {
executeRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header|search|nth|pointer|ghost)|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header|footer|search|nth|pointer|ghost)|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
@@ -1800,6 +1820,8 @@ func isExecuteAction(str string) actionType {
return actPreview
case "change-header":
return actChangeHeader
case "change-footer":
return actChangeFooter
case "change-list-label":
return actChangeListLabel
case "change-border-label":
@@ -1810,6 +1832,8 @@ func isExecuteAction(str string) actionType {
return actChangeInputLabel
case "change-header-label":
return actChangeHeaderLabel
case "change-footer-label":
return actChangeFooterLabel
case "change-ghost":
return actChangeGhost
case "change-pointer":
@@ -1850,6 +1874,10 @@ func isExecuteAction(str string) actionType {
return actTransformInputLabel
case "transform-header-label":
return actTransformHeaderLabel
case "transform-footer-label":
return actTransformFooterLabel
case "transform-footer":
return actTransformFooter
case "transform-header":
return actTransformHeader
case "transform-ghost":
@@ -2729,6 +2757,14 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.HeaderLines, err = nextInt("number of header lines required"); err != nil {
return err
}
case "--no-footer":
opts.Footer = []string{}
case "--footer":
str, err := nextString("footer string required")
if err != nil {
return err
}
opts.Footer = strLines(str)
case "--header-first":
opts.HeaderFirst = true
case "--no-header-first":
@@ -2773,7 +2809,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.Preview.border = tui.BorderNone
case "--preview-border":
hasArg, arg := optionalNextString()
if opts.Preview.border, err = parseBorder(arg, !hasArg, true); err != nil {
if opts.Preview.border, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--height":
@@ -2812,14 +2848,17 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.BorderShape = tui.BorderNone
case "--border":
hasArg, arg := optionalNextString()
if opts.BorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
if opts.BorderShape, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--list-border":
hasArg, arg := optionalNextString()
if opts.ListBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
if opts.ListBorderShape, err = parseBorder(arg, !hasArg); err != nil {
return err
}
if opts.ListBorderShape == tui.BorderLine {
return errors.New("list border cannot be 'line'")
}
case "--no-list-border":
opts.ListBorderShape = tui.BorderNone
case "--no-list-label":
@@ -2841,14 +2880,14 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.HeaderBorderShape = tui.BorderNone
case "--header-border":
hasArg, arg := optionalNextString()
if opts.HeaderBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
if opts.HeaderBorderShape, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--no-header-lines-border":
opts.HeaderLinesShape = tui.BorderNone
case "--header-lines-border":
hasArg, arg := optionalNextString()
if opts.HeaderLinesShape, err = parseBorder(arg, !hasArg, false); err != nil {
if opts.HeaderLinesShape, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--no-header-label":
@@ -2865,11 +2904,32 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parseLabelPosition(&opts.HeaderLabel, pos); err != nil {
return err
}
case "--no-footer-border":
opts.FooterBorderShape = tui.BorderNone
case "--footer-border":
hasArg, arg := optionalNextString()
if opts.FooterBorderShape, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--no-footer-label":
opts.FooterLabel.label = ""
case "--footer-label":
if opts.FooterLabel.label, err = nextString("footer label required"); err != nil {
return err
}
case "--footer-label-pos":
pos, err := nextString("footer label position required (positive or negative integer or 'center')")
if err != nil {
return err
}
if err := parseLabelPosition(&opts.FooterLabel, pos); err != nil {
return err
}
case "--no-input-border":
opts.InputBorderShape = tui.BorderNone
case "--input-border":
hasArg, arg := optionalNextString()
if opts.InputBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
if opts.InputBorderShape, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--no-input-label":
@@ -3077,6 +3137,7 @@ func applyPreset(opts *Options, preset string) error {
opts.ListBorderShape = tui.BorderUndefined
opts.InputBorderShape = tui.BorderUndefined
opts.HeaderBorderShape = tui.BorderUndefined
opts.FooterBorderShape = tui.BorderUndefined
opts.Preview.border = defaultBorderShape
opts.Preview.info = true
opts.InfoStyle = infoDefault
@@ -3088,6 +3149,7 @@ func applyPreset(opts *Options, preset string) error {
opts.ListBorderShape = tui.BorderUndefined
opts.InputBorderShape = tui.BorderUndefined
opts.HeaderBorderShape = tui.BorderUndefined
opts.FooterBorderShape = tui.BorderLine
opts.Preview.border = tui.BorderLine
opts.Preview.info = false
opts.InfoStyle = infoDefault
@@ -3103,16 +3165,22 @@ func applyPreset(opts *Options, preset string) error {
}
if len(tokens) == 2 && len(tokens[1]) > 0 {
var err error
defaultBorderShape, err = parseBorder(tokens[1], false, false)
defaultBorderShape, err = parseBorder(tokens[1], false)
if err != nil {
return err
}
}
opts.ListBorderShape = defaultBorderShape
if defaultBorderShape != tui.BorderLine {
opts.ListBorderShape = defaultBorderShape
}
opts.InputBorderShape = defaultBorderShape
opts.HeaderBorderShape = defaultBorderShape
opts.FooterBorderShape = defaultBorderShape
opts.Preview.border = defaultBorderShape
if defaultBorderShape == tui.BorderLine {
opts.BorderShape = defaultBorderShape
}
opts.Preview.info = true
opts.InfoStyle = infoInlineRight
opts.Theme.Gutter = tui.NewColorAttr()
@@ -3185,6 +3253,10 @@ func noSeparatorLine(style infoStyle, separator bool) bool {
return false
}
func (opts *Options) useTmux() bool {
return opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index
}
func (opts *Options) noSeparatorLine() bool {
if opts.Inputless {
return true
@@ -3216,6 +3288,10 @@ func postProcessOptions(opts *Options) error {
opts.HeaderBorderShape = tui.BorderNone
}
if opts.FooterBorderShape == tui.BorderUndefined {
opts.FooterBorderShape = tui.BorderLine
}
if opts.HeaderLinesShape == tui.BorderNone {
opts.HeaderLinesShape = tui.BorderPhantom
}