summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2022-10-30 00:12:01 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2022-11-01 13:27:00 +0900
commite61585f2f37c6b1ead971f448af8db26dff1502c (patch)
tree4c8df98b88b9577da95e9300e345aaaa27517e6d /src
parent0de1aacb0cea3a559e07b84e922eb2aea61b8c96 (diff)
downloadfzf-e61585f2f37c6b1ead971f448af8db26dff1502c.tar.gz
Add --border-label and --border-label-pos
Close #3022
Diffstat (limited to 'src')
-rw-r--r--src/options.go180
-rw-r--r--src/terminal.go60
-rw-r--r--src/tui/tui.go24
3 files changed, 178 insertions, 86 deletions
diff --git a/src/options.go b/src/options.go
index acf67281..80ca3e11 100644
--- a/src/options.go
+++ b/src/options.go
@@ -18,97 +18,101 @@ import (
const usage = `usage: fzf [options]
Search
- -x, --extended Extended-search mode
- (enabled by default; +x or --no-extended to disable)
- -e, --exact Enable Exact-match
- -i Case-insensitive match (default: smart-case match)
- +i Case-sensitive match
- --scheme=SCHEME Scoring scheme [default|path|history]
- --literal Do not normalize latin script letters before matching
- -n, --nth=N[,..] Comma-separated list of field index expressions
- for limiting search scope. Each can be a non-zero
- integer or a range expression ([BEGIN]..[END]).
- --with-nth=N[,..] Transform the presentation of each line using
- field index expressions
- -d, --delimiter=STR Field delimiter regex (default: AWK-style)
- +s, --no-sort Do not sort the result
- --tac Reverse the order of the input
- --disabled Do not perform search
- --tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
- when the scores are tied [length|chunk|begin|end|index]
- (default: length)
+ -x, --extended Extended-search mode
+ (enabled by default; +x or --no-extended to disable)
+ -e, --exact Enable Exact-match
+ -i Case-insensitive match (default: smart-case match)
+ +i Case-sensitive match
+ --scheme=SCHEME Scoring scheme [default|path|history]
+ --literal Do not normalize latin script letters before matching
+ -n, --nth=N[,..] Comma-separated list of field index expressions
+ for limiting search scope. Each can be a non-zero
+ integer or a range expression ([BEGIN]..[END]).
+ --with-nth=N[,..] Transform the presentation of each line using
+ field index expressions
+ -d, --delimiter=STR Field delimiter regex (default: AWK-style)
+ +s, --no-sort Do not sort the result
+ --tac Reverse the order of the input
+ --disabled Do not perform search
+ --tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
+ when the scores are tied [length|chunk|begin|end|index]
+ (default: length)
Interface
- -m, --multi[=MAX] Enable multi-select with tab/shift-tab
- --no-mouse Disable mouse
- --bind=KEYBINDS Custom key bindings. Refer to the man page.
- --cycle Enable cyclic scroll
- --keep-right Keep the right end of the line visible on overflow
- --scroll-off=LINES Number of screen lines to keep above or below when
- scrolling to the top or to the bottom (default: 0)
- --no-hscroll Disable horizontal scroll
- --hscroll-off=COLS Number of screen columns to keep to the right of the
- highlighted substring (default: 10)
- --filepath-word Make word-wise movements respect path separators
- --jump-labels=CHARS Label characters for jump and jump-accept
+ -m, --multi[=MAX] Enable multi-select with tab/shift-tab
+ --no-mouse Disable mouse
+ --bind=KEYBINDS Custom key bindings. Refer to the man page.
+ --cycle Enable cyclic scroll
+ --keep-right Keep the right end of the line visible on overflow
+ --scroll-off=LINES Number of screen lines to keep above or below when
+ scrolling to the top or to the bottom (default: 0)
+ --no-hscroll Disable horizontal scroll
+ --hscroll-off=COLS Number of screen columns to keep to the right of the
+ highlighted substring (default: 10)
+ --filepath-word Make word-wise movements respect path separators
+ --jump-labels=CHARS Label characters for jump and jump-accept
Layout
- --height=[~]HEIGHT[%] Display fzf window below the cursor with the given
- height instead of using fullscreen.
- If prefixed with '~', fzf will determine the height
- according to the input size.
- --min-height=HEIGHT Minimum height when --height is given in percent
- (default: 10)
- --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
- --border[=STYLE] Draw border around the finder
- [rounded|sharp|horizontal|vertical|
- top|bottom|left|right|none] (default: rounded)
- --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]
- --prompt=STR Input prompt (default: '> ')
- --pointer=STR Pointer to the current line (default: '>')
- --marker=STR Multi-select marker (default: '>')
- --header=STR String to print as header
- --header-lines=N The first N lines of the input are treated as header
- --header-first Print header before the prompt line
- --ellipsis=STR Ellipsis to show when line is truncated (default: '..')
+ --height=[~]HEIGHT[%] Display fzf window below the cursor with the given
+ height instead of using fullscreen.
+ If prefixed with '~', fzf will determine the height
+ according to the input size.
+ --min-height=HEIGHT Minimum height when --height is given in percent
+ (default: 10)
+ --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
+ --border[=STYLE] Draw border around the finder
+ [rounded|sharp|horizontal|vertical|
+ top|bottom|left|right|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|
+ NEGATIVE_INTEGER: columns from right] (default: 0)
+ --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]
+ --prompt=STR Input prompt (default: '> ')
+ --pointer=STR Pointer to the current line (default: '>')
+ --marker=STR Multi-select marker (default: '>')
+ --header=STR String to print as header
+ --header-lines=N The first N lines of the input are treated as header
+ --header-first Print header before the prompt line
+ --ellipsis=STR Ellipsis to show when line is truncated (default: '..')
Display
- --ansi Enable processing of ANSI color codes
- --tabstop=SPACES Number of spaces for a tab character (default: 8)
- --color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
- --no-bold Do not use bold text
+ --ansi Enable processing of ANSI color codes
+ --tabstop=SPACES Number of spaces for a tab character (default: 8)
+ --color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
+ --no-bold Do not use bold text
History
- --history=FILE History file
- --history-size=N Maximum number of history entries (default: 1000)
+ --history=FILE History file
+ --history-size=N Maximum number of history entries (default: 1000)
Preview
- --preview=COMMAND Command to preview highlighted line ({})
- --preview-window=OPT Preview window layout (default: right:50%)
- [up|down|left|right][,SIZE[%]]
- [,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
- [,border-BORDER_OPT]
- [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
- [,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
+ --preview=COMMAND Command to preview highlighted line ({})
+ --preview-window=OPT Preview window layout (default: right:50%)
+ [up|down|left|right][,SIZE[%]]
+ [,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
+ [,border-BORDER_OPT]
+ [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
+ [,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
Scripting
- -q, --query=STR Start the finder with the given query
- -1, --select-1 Automatically select the only match
- -0, --exit-0 Exit immediately when there's no match
- -f, --filter=STR Filter mode. Do not start interactive finder.
- --print-query Print query as the first line
- --expect=KEYS Comma-separated list of keys to complete fzf
- --read0 Read input delimited by ASCII NUL characters
- --print0 Print output delimited by ASCII NUL characters
- --sync Synchronous search for multi-staged filtering
- --version Display version information and exit
+ -q, --query=STR Start the finder with the given query
+ -1, --select-1 Automatically select the only match
+ -0, --exit-0 Exit immediately when there's no match
+ -f, --filter=STR Filter mode. Do not start interactive finder.
+ --print-query Print query as the first line
+ --expect=KEYS Comma-separated list of keys to complete fzf
+ --read0 Read input delimited by ASCII NUL characters
+ --print0 Print output delimited by ASCII NUL characters
+ --sync Synchronous search for multi-staged filtering
+ --version Display version information and exit
Environment variables
- FZF_DEFAULT_COMMAND Default command to use when input is tty
- FZF_DEFAULT_OPTS Default options
- (e.g. '--layout=reverse --inline-info')
+ FZF_DEFAULT_COMMAND Default command to use when input is tty
+ FZF_DEFAULT_OPTS Default options
+ (e.g. '--layout=reverse --inline-info')
`
@@ -188,6 +192,13 @@ type previewOpts struct {
alternative *previewOpts
}
+func parseLabelPosition(arg string) int {
+ if strings.ToLower(arg) == "center" {
+ return 0
+ }
+ return atoi(arg)
+}
+
func (a previewOpts) aboveOrBelow() bool {
return a.size.size > 0 && (a.position == posUp || a.position == posDown)
}
@@ -258,6 +269,8 @@ type Options struct {
Margin [4]sizeSpec
Padding [4]sizeSpec
BorderShape tui.BorderShape
+ Label string
+ LabelPos int
Unicode bool
Tabstop int
ClearOnExit bool
@@ -324,6 +337,8 @@ func defaultOptions() *Options {
Padding: defaultMargin(),
Unicode: true,
Tabstop: 8,
+ Label: "",
+ LabelPos: 0,
ClearOnExit: true,
Version: false}
}
@@ -798,6 +813,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
mergeAttr(&theme.CurrentMatch)
case "border":
mergeAttr(&theme.Border)
+ case "label":
+ mergeAttr(&theme.BorderLabel)
case "prompt":
mergeAttr(&theme.Prompt)
case "spinner":
@@ -1556,6 +1573,11 @@ func parseOptions(opts *Options, allArgs []string) {
case "--border":
hasArg, arg := optionalNextString(allArgs, &i)
opts.BorderShape = parseBorder(arg, !hasArg)
+ case "--border-label":
+ opts.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)
case "--no-unicode":
opts.Unicode = false
case "--unicode":
@@ -1591,6 +1613,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Delimiter = delimiterRegexp(value)
} 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
+ } else if match, value := optString(arg, "--border-label-pos="); match {
+ opts.LabelPos = parseLabelPosition(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 369bc418..c28b5335 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -114,6 +114,9 @@ type Terminal struct {
spinner []string
prompt func()
promptLen int
+ borderLabel func()
+ borderLabelLen int
+ borderLabelPos int
pointer string
pointerLen int
pointerEmpty string
@@ -544,6 +547,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
padding: opts.Padding,
unicode: opts.Unicode,
borderShape: opts.BorderShape,
+ borderLabel: nil,
+ borderLabelPos: opts.LabelPos,
cleanExit: opts.ClearOnExit,
paused: opts.Phony,
strong: strongAttr,
@@ -587,6 +592,9 @@ 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)
+ }
return &t
}
@@ -617,6 +625,25 @@ func (t *Terminal) MaxFitAndPad(opts *Options) (int, int) {
return fit, padHeight
}
+func (t *Terminal) parseBorderLabel(borderLabel string) (func(), int) {
+ 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() {
+ 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)
+ }
+ borderLabelLen := runewidth.StringWidth(text)
+ return borderLabelFn, borderLabelLen
+}
+
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil)
@@ -911,6 +938,27 @@ func (t *Terminal) resizeWindows() {
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
}
+ // Print border label
+ if t.border != nil && t.borderLabel != nil {
+ switch t.borderShape {
+ case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp:
+ var col int
+ if t.borderLabelPos == 0 {
+ col = util.Max(0, (t.border.Width()-t.borderLabelLen)/2)
+ } else if t.borderLabelPos < 0 {
+ col = util.Max(0, t.border.Width()+t.borderLabelPos+1-t.borderLabelLen)
+ } else {
+ col = util.Min(t.borderLabelPos-1, t.border.Width()-t.borderLabelLen)
+ }
+ row := 0
+ if t.borderShape == tui.BorderBottom {
+ row = t.border.Height() - 1
+ }
+ t.border.Move(row, col)
+ t.borderLabel()
+ }
+ }
+
// Add padding to margin
for idx, val := range paddingInt {
marginInt[idx] += val
@@ -1394,6 +1442,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
}
+ t.printColoredString(t.window, text, offsets, colBase)
+ return displayWidth
+}
+
+func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []colorOffset, colBase tui.ColorPair) {
var index int32
var substr string
var prefixWidth int
@@ -1403,11 +1456,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
e := util.Constrain32(offset.offset[1], index, maxOffset)
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
- t.window.CPrint(colBase, substr)
+ window.CPrint(colBase, substr)
if b < e {
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
- t.window.CPrint(offset.color, substr)
+ window.CPrint(offset.color, substr)
}
index = e
@@ -1417,9 +1470,8 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
}
if index < maxOffset {
substr, _ = t.processTabs(text[index:], prefixWidth)
- t.window.CPrint(colBase, substr)
+ window.CPrint(colBase, substr)
}
- return displayWidth
}
func (t *Terminal) renderPreviewSpinner() {
diff --git a/src/tui/tui.go b/src/tui/tui.go
index 90c5327e..1a9c748a 100644
--- a/src/tui/tui.go
+++ b/src/tui/tui.go
@@ -268,6 +268,7 @@ type ColorTheme struct {
Selected ColorAttr
Header ColorAttr
Border ColorAttr
+ BorderLabel ColorAttr
}
type Event struct {
@@ -441,6 +442,7 @@ var (
ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
+ ColBorderLabel ColorPair
)
func EmptyTheme() *ColorTheme {
@@ -463,7 +465,9 @@ func EmptyTheme() *ColorTheme {
Cursor: ColorAttr{colUndefined, AttrUndefined},
Selected: ColorAttr{colUndefined, AttrUndefined},
Header: ColorAttr{colUndefined, AttrUndefined},
- Border: ColorAttr{colUndefined, AttrUndefined}}
+ Border: ColorAttr{colUndefined, AttrUndefined},
+ BorderLabel: ColorAttr{colUndefined, AttrUndefined},
+ }
}
func NoColorTheme() *ColorTheme {
@@ -486,7 +490,9 @@ func NoColorTheme() *ColorTheme {
Cursor: ColorAttr{colDefault, AttrRegular},
Selected: ColorAttr{colDefault, AttrRegular},
Header: ColorAttr{colDefault, AttrRegular},
- Border: ColorAttr{colDefault, AttrRegular}}
+ Border: ColorAttr{colDefault, AttrRegular},
+ BorderLabel: ColorAttr{colDefault, AttrRegular},
+ }
}
func errorExit(message string) {
@@ -514,7 +520,9 @@ func init() {
Cursor: ColorAttr{colRed, AttrUndefined},
Selected: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined},
- Border: ColorAttr{colBlack, AttrUndefined}}
+ Border: ColorAttr{colBlack, AttrUndefined},
+ BorderLabel: ColorAttr{colWhite, AttrUndefined},
+ }
Dark256 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
@@ -534,7 +542,9 @@ func init() {
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{109, AttrUndefined},
- Border: ColorAttr{59, AttrUndefined}}
+ Border: ColorAttr{59, AttrUndefined},
+ BorderLabel: ColorAttr{145, AttrUndefined},
+ }
Light256 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
@@ -554,7 +564,9 @@ func init() {
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{31, AttrUndefined},
- Border: ColorAttr{145, AttrUndefined}}
+ Border: ColorAttr{145, AttrUndefined},
+ BorderLabel: ColorAttr{59, AttrUndefined},
+ }
}
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
@@ -590,6 +602,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.Selected = o(baseTheme.Selected, theme.Selected)
theme.Header = o(baseTheme.Header, theme.Header)
theme.Border = o(baseTheme.Border, theme.Border)
+ theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
initPalette(theme)
}
@@ -622,6 +635,7 @@ func initPalette(theme *ColorTheme) {
ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
+ ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
}