Compare commits

...

2 Commits

Author SHA1 Message Date
Junegunn Choi
a5beb08ed7
Border around the header section
Close #4159
2025-01-05 23:02:52 +09:00
Junegunn Choi
45fc7b903d
[install] Unset FZF_DEFAULT_OPTS when checking the binary 2025-01-05 11:33:40 +09:00
10 changed files with 758 additions and 293 deletions

View File

@ -16,7 +16,7 @@ CHANGELOG
- Actions - Actions
- `change-list-label` - `change-list-label`
- `transform-list-label` - `transform-list-label`
- Border and label for the input section (prompt and info) - Border and label for the input section (prompt line and info line)
- Options - Options
- `--input-border[=STYLE]` - `--input-border[=STYLE]`
- `--input-label=LABEL` - `--input-label=LABEL`
@ -29,6 +29,19 @@ CHANGELOG
- Actions - Actions
- `change-input-label` - `change-input-label`
- `transform-input-label` - `transform-input-label`
- Border and label for the header section
- Options
- `--header-border[=STYLE]`
- `--header-label=LABEL`
- `--header-label-pos=COL[:bottom]`
- Colors
- `header-fg` (`header`)
- `header-bg`
- `header-border`
- `header-label`
- Actions
- `change-header-label`
- `transform-header-label`
- Added `--preview-border[=STYLE]` as short for `--preview-window=border[-STYLE]` - Added `--preview-border[=STYLE]` as short for `--preview-window=border[-STYLE]`
- You can specify `border-native` to `--tmux` so that native tmux border is used instead of `--border`. This can be useful if you start a different program from inside the popup. - You can specify `border-native` to `--tmux` so that native tmux border is used instead of `--border`. This can be useful if you start a different program from inside the popup.
```sh ```sh

View File

@ -83,7 +83,7 @@ ask() {
check_binary() { check_binary() {
echo -n " - Checking fzf executable ... " echo -n " - Checking fzf executable ... "
local output local output
output=$("$fzf_base"/bin/fzf --version 2>&1) output=$(FZF_DEFAULT_OPTS= "$fzf_base"/bin/fzf --version 2>&1)
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Error: $output" echo "Error: $output"
binary_error="Invalid binary" binary_error="Invalid binary"

View File

@ -308,7 +308,7 @@ Choose the layout (default: default)
A synonym for \fB\-\-layout=reverse\fB A synonym for \fB\-\-layout=reverse\fB
.TP .TP
.BI "\-\-border" [=BORDER_OPT] .BI "\-\-border" [=STYLE]
Draw border around the finder Draw border around the finder
.br .br
@ -387,6 +387,42 @@ 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 \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. \fB0 (or \fBcenter\fR) will put the label at the center of the border line.
.TP
.BI "\-\-list\-border" [=STYLE]
Draw border around the list section
.TP
.BI "\-\-list\-label" [=LABEL]
Label to print on the list border
.TP
.BI "\-\-list\-label\-pos" [=N[:top|bottom]]
Position of the list label
.TP
.BI "\-\-input\-border" [=STYLE]
Draw border around the input section
.TP
.BI "\-\-input\-label" [=LABEL]
Label to print on the input border
.TP
.BI "\-\-input\-label\-pos" [=N[:top|bottom]]
Position of the input label
.TP
.BI "\-\-header\-border" [=STYLE]
Draw border around the header section
.TP
.BI "\-\-header\-label" [=LABEL]
Label to print on the header border
.TP
.BI "\-\-header\-label\-pos" [=N[:top|bottom]]
Position of the header label
.TP .TP
.B "\-\-no\-unicode" .B "\-\-no\-unicode"
Use ASCII characters instead of Unicode drawing characters to draw borders, Use ASCII characters instead of Unicode drawing characters to draw borders,
@ -563,7 +599,8 @@ color mappings.
\fBlist\-bg \fRList section background \fBlist\-bg \fRList section background
\fBselected\-bg \fRSelected line background \fBselected\-bg \fRSelected line background
\fBpreview\-bg \fRPreview window background \fBpreview\-bg \fRPreview window background
\fBinput\-bg \fRInput section background \fBinput\-bg \fRInput window background (\fB\-\-input\-border\fR)
\fBheader\-bg \fRHeader window background (\fB\-\-header\-border\fR)
\fBhl \fRHighlighted substrings \fBhl \fRHighlighted substrings
\fBselected\-hl \fRHighlighted substrings in the selected line \fBselected\-hl \fRHighlighted substrings in the selected line
\fBcurrent\-fg (fg+) \fRText (current line) \fBcurrent\-fg (fg+) \fRText (current line)
@ -579,16 +616,18 @@ color mappings.
\fBseparator \fRHorizontal separator on info line \fBseparator \fRHorizontal separator on info line
\fBpreview\-border \fRBorder around the preview window (\fB\-\-preview\fR) \fBpreview\-border \fRBorder around the preview window (\fB\-\-preview\fR)
\fBpreview\-scrollbar \fRScrollbar \fBpreview\-scrollbar \fRScrollbar
\fBinput\-border \fRBorder around the input section (\fB\-\-input\-border\fR) \fBinput\-border \fRBorder around the input window (\fB\-\-input\-border\fR)
\fBheader\-border \fRBorder around the header window (\fB\-\-header\-border\fR)
\fBlabel \fRBorder label (\fB\-\-border\-label\fR, \fB\-\-list\-label\fR, \fB\-\-input\-label\fR, and \fB\-\-preview\-label\fR) \fBlabel \fRBorder label (\fB\-\-border\-label\fR, \fB\-\-list\-label\fR, \fB\-\-input\-label\fR, and \fB\-\-preview\-label\fR)
\fBlist\-label \fRBorder label of the list section (\fB\-\-list\-label\fR) \fBlist\-label \fRBorder label of the list section (\fB\-\-list\-label\fR)
\fBpreview\-label \fRBorder label of the preview window (\fB\-\-preview\-label\fR) \fBpreview\-label \fRBorder label of the preview window (\fB\-\-preview\-label\fR)
\fBinput\-label \fRBorder label of the input section (\fB\-\-input\-label\fR) \fBinput\-label \fRBorder label of the input window (\fB\-\-input\-label\fR)
\fBheader\-label \fRBorder label of the header window (\fB\-\-header\-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
\fBspinner \fRStreaming input indicator \fBspinner \fRStreaming input indicator
\fBheader \fRHeader \fBheader (header\-fg) \fRHeader
.B ANSI COLORS: .B ANSI COLORS:
\fB\-1 \fRDefault terminal foreground/background color \fB\-1 \fRDefault terminal foreground/background color
@ -1454,6 +1493,7 @@ A key or an event can be bound to one or more of the following actions.
\fBcancel\fR (clear query string if not empty, abort fzf otherwise) \fBcancel\fR (clear query string if not empty, abort fzf otherwise)
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string) \fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR) \fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
\fBchange\-header\-label(...)\fR (change \fB\-\-header\-label\fR to the given string)
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string) \fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
\fBchange\-list\-label(...)\fR (change \fB\-\-list\-label\fR to the given string) \fBchange\-list\-label(...)\fR (change \fB\-\-list\-label\fR to the given string)
\fBchange\-multi\fR (enable multi-select mode with no limit) \fBchange\-multi\fR (enable multi-select mode with no limit)
@ -1540,6 +1580,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtransform(...)\fR (transform states using the output of an external command) \fBtransform(...)\fR (transform states using the output of an external command)
\fBtransform\-border\-label(...)\fR (transform border label using an external command) \fBtransform\-border\-label(...)\fR (transform border label using an external command)
\fBtransform\-header(...)\fR (transform header using an external command) \fBtransform\-header(...)\fR (transform header using an external command)
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
\fBtransform\-input\-label(...)\fR (transform input label using an external command) \fBtransform\-input\-label(...)\fR (transform input label using an external command)
\fBtransform\-list\-label(...)\fR (transform list label using an external command) \fBtransform\-list\-label(...)\fR (transform list label using an external command)
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command) \fBtransform\-preview\-label(...)\fR (transform preview label using an external command)

View File

@ -28,110 +28,112 @@ func _() {
_ = x[actChangeListLabel-17] _ = x[actChangeListLabel-17]
_ = x[actChangeInputLabel-18] _ = x[actChangeInputLabel-18]
_ = x[actChangeHeader-19] _ = x[actChangeHeader-19]
_ = x[actChangeMulti-20] _ = x[actChangeHeaderLabel-20]
_ = x[actChangePreviewLabel-21] _ = x[actChangeMulti-21]
_ = x[actChangePrompt-22] _ = x[actChangePreviewLabel-22]
_ = x[actChangeQuery-23] _ = x[actChangePrompt-23]
_ = x[actClearScreen-24] _ = x[actChangeQuery-24]
_ = x[actClearQuery-25] _ = x[actClearScreen-25]
_ = x[actClearSelection-26] _ = x[actClearQuery-26]
_ = x[actClose-27] _ = x[actClearSelection-27]
_ = x[actDeleteChar-28] _ = x[actClose-28]
_ = x[actDeleteCharEof-29] _ = x[actDeleteChar-29]
_ = x[actEndOfLine-30] _ = x[actDeleteCharEof-30]
_ = x[actFatal-31] _ = x[actEndOfLine-31]
_ = x[actForwardChar-32] _ = x[actFatal-32]
_ = x[actForwardWord-33] _ = x[actForwardChar-33]
_ = x[actKillLine-34] _ = x[actForwardWord-34]
_ = x[actKillWord-35] _ = x[actKillLine-35]
_ = x[actUnixLineDiscard-36] _ = x[actKillWord-36]
_ = x[actUnixWordRubout-37] _ = x[actUnixLineDiscard-37]
_ = x[actYank-38] _ = x[actUnixWordRubout-38]
_ = x[actBackwardKillWord-39] _ = x[actYank-39]
_ = x[actSelectAll-40] _ = x[actBackwardKillWord-40]
_ = x[actDeselectAll-41] _ = x[actSelectAll-41]
_ = x[actToggle-42] _ = x[actDeselectAll-42]
_ = x[actToggleSearch-43] _ = x[actToggle-43]
_ = x[actToggleAll-44] _ = x[actToggleSearch-44]
_ = x[actToggleDown-45] _ = x[actToggleAll-45]
_ = x[actToggleUp-46] _ = x[actToggleDown-46]
_ = x[actToggleIn-47] _ = x[actToggleUp-47]
_ = x[actToggleOut-48] _ = x[actToggleIn-48]
_ = x[actToggleTrack-49] _ = x[actToggleOut-49]
_ = x[actToggleTrackCurrent-50] _ = x[actToggleTrack-50]
_ = x[actToggleHeader-51] _ = x[actToggleTrackCurrent-51]
_ = x[actToggleWrap-52] _ = x[actToggleHeader-52]
_ = x[actToggleMultiLine-53] _ = x[actToggleWrap-53]
_ = x[actToggleHscroll-54] _ = x[actToggleMultiLine-54]
_ = x[actTrackCurrent-55] _ = x[actToggleHscroll-55]
_ = x[actUntrackCurrent-56] _ = x[actTrackCurrent-56]
_ = x[actDown-57] _ = x[actUntrackCurrent-57]
_ = x[actUp-58] _ = x[actDown-58]
_ = x[actPageUp-59] _ = x[actUp-59]
_ = x[actPageDown-60] _ = x[actPageUp-60]
_ = x[actPosition-61] _ = x[actPageDown-61]
_ = x[actHalfPageUp-62] _ = x[actPosition-62]
_ = x[actHalfPageDown-63] _ = x[actHalfPageUp-63]
_ = x[actOffsetUp-64] _ = x[actHalfPageDown-64]
_ = x[actOffsetDown-65] _ = x[actOffsetUp-65]
_ = x[actOffsetMiddle-66] _ = x[actOffsetDown-66]
_ = x[actJump-67] _ = x[actOffsetMiddle-67]
_ = x[actJumpAccept-68] _ = x[actJump-68]
_ = x[actPrintQuery-69] _ = x[actJumpAccept-69]
_ = x[actRefreshPreview-70] _ = x[actPrintQuery-70]
_ = x[actReplaceQuery-71] _ = x[actRefreshPreview-71]
_ = x[actToggleSort-72] _ = x[actReplaceQuery-72]
_ = x[actShowPreview-73] _ = x[actToggleSort-73]
_ = x[actHidePreview-74] _ = x[actShowPreview-74]
_ = x[actTogglePreview-75] _ = x[actHidePreview-75]
_ = x[actTogglePreviewWrap-76] _ = x[actTogglePreview-76]
_ = x[actTransform-77] _ = x[actTogglePreviewWrap-77]
_ = x[actTransformBorderLabel-78] _ = x[actTransform-78]
_ = x[actTransformListLabel-79] _ = x[actTransformBorderLabel-79]
_ = x[actTransformInputLabel-80] _ = x[actTransformListLabel-80]
_ = x[actTransformHeader-81] _ = x[actTransformInputLabel-81]
_ = x[actTransformPreviewLabel-82] _ = x[actTransformHeader-82]
_ = x[actTransformPrompt-83] _ = x[actTransformHeaderLabel-83]
_ = x[actTransformQuery-84] _ = x[actTransformPreviewLabel-84]
_ = x[actPreview-85] _ = x[actTransformPrompt-85]
_ = x[actChangePreview-86] _ = x[actTransformQuery-86]
_ = x[actChangePreviewWindow-87] _ = x[actPreview-87]
_ = x[actPreviewTop-88] _ = x[actChangePreview-88]
_ = x[actPreviewBottom-89] _ = x[actChangePreviewWindow-89]
_ = x[actPreviewUp-90] _ = x[actPreviewTop-90]
_ = x[actPreviewDown-91] _ = x[actPreviewBottom-91]
_ = x[actPreviewPageUp-92] _ = x[actPreviewUp-92]
_ = x[actPreviewPageDown-93] _ = x[actPreviewDown-93]
_ = x[actPreviewHalfPageUp-94] _ = x[actPreviewPageUp-94]
_ = x[actPreviewHalfPageDown-95] _ = x[actPreviewPageDown-95]
_ = x[actPrevHistory-96] _ = x[actPreviewHalfPageUp-96]
_ = x[actPrevSelected-97] _ = x[actPreviewHalfPageDown-97]
_ = x[actPrint-98] _ = x[actPrevHistory-98]
_ = x[actPut-99] _ = x[actPrevSelected-99]
_ = x[actNextHistory-100] _ = x[actPrint-100]
_ = x[actNextSelected-101] _ = x[actPut-101]
_ = x[actExecute-102] _ = x[actNextHistory-102]
_ = x[actExecuteSilent-103] _ = x[actNextSelected-103]
_ = x[actExecuteMulti-104] _ = x[actExecute-104]
_ = x[actSigStop-105] _ = x[actExecuteSilent-105]
_ = x[actFirst-106] _ = x[actExecuteMulti-106]
_ = x[actLast-107] _ = x[actSigStop-107]
_ = x[actReload-108] _ = x[actFirst-108]
_ = x[actReloadSync-109] _ = x[actLast-109]
_ = x[actDisableSearch-110] _ = x[actReload-110]
_ = x[actEnableSearch-111] _ = x[actReloadSync-111]
_ = x[actSelect-112] _ = x[actDisableSearch-112]
_ = x[actDeselect-113] _ = x[actEnableSearch-113]
_ = x[actUnbind-114] _ = x[actSelect-114]
_ = x[actRebind-115] _ = x[actDeselect-115]
_ = x[actBecome-116] _ = x[actUnbind-116]
_ = x[actShowHeader-117] _ = x[actRebind-117]
_ = x[actHideHeader-118] _ = x[actBecome-118]
_ = x[actShowHeader-119]
_ = x[actHideHeader-120]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader" const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 293, 314, 329, 343, 357, 370, 387, 395, 408, 424, 436, 444, 458, 472, 483, 494, 512, 529, 536, 555, 567, 581, 590, 605, 617, 630, 641, 652, 664, 678, 699, 714, 727, 745, 761, 776, 793, 800, 805, 814, 825, 836, 849, 864, 875, 888, 903, 910, 923, 936, 953, 968, 981, 995, 1009, 1025, 1045, 1057, 1080, 1101, 1123, 1141, 1165, 1183, 1200, 1210, 1226, 1248, 1261, 1277, 1289, 1303, 1319, 1337, 1357, 1379, 1393, 1408, 1416, 1422, 1436, 1451, 1461, 1477, 1492, 1502, 1510, 1517, 1526, 1539, 1555, 1570, 1579, 1590, 1599, 1608, 1617, 1630, 1643} var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 377, 390, 407, 415, 428, 444, 456, 464, 478, 492, 503, 514, 532, 549, 556, 575, 587, 601, 610, 625, 637, 650, 661, 672, 684, 698, 719, 734, 747, 765, 781, 796, 813, 820, 825, 834, 845, 856, 869, 884, 895, 908, 923, 930, 943, 956, 973, 988, 1001, 1015, 1029, 1045, 1065, 1077, 1100, 1121, 1143, 1161, 1184, 1208, 1226, 1243, 1253, 1269, 1291, 1304, 1320, 1332, 1346, 1362, 1380, 1400, 1422, 1436, 1451, 1459, 1465, 1479, 1494, 1504, 1520, 1535, 1545, 1553, 1560, 1569, 1582, 1598, 1613, 1622, 1633, 1642, 1651, 1660, 1673, 1686}
func (i actionType) String() string { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@ -103,6 +103,14 @@ Usage: fzf [options]
[POSITIVE_INTEGER: columns from left| [POSITIVE_INTEGER: columns from left|
NEGATIVE_INTEGER: columns from right][:bottom] NEGATIVE_INTEGER: columns from right][:bottom]
(default: 0 or center) (default: 0 or center)
--header-border[=STYLE] Draw border around the header section
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
top|bottom|left|right|none] (default: rounded)
--header-label=LABEL Label to print on the header border
--header-label-pos=COL Position of the header label
[POSITIVE_INTEGER: columns from left|
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 --info=STYLE Finder info style
@ -545,7 +553,9 @@ type Options struct {
BorderShape tui.BorderShape BorderShape tui.BorderShape
ListBorderShape tui.BorderShape ListBorderShape tui.BorderShape
InputBorderShape tui.BorderShape InputBorderShape tui.BorderShape
HeaderBorderShape tui.BorderShape
InputLabel labelOpts InputLabel labelOpts
HeaderLabel labelOpts
BorderLabel labelOpts BorderLabel labelOpts
ListLabel labelOpts ListLabel labelOpts
PreviewLabel labelOpts PreviewLabel labelOpts
@ -1251,6 +1261,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
mergeAttr(&theme.InputBorder) mergeAttr(&theme.InputBorder)
case "input-label": case "input-label":
mergeAttr(&theme.InputLabel) mergeAttr(&theme.InputLabel)
case "header-border":
mergeAttr(&theme.HeaderBorder)
case "header-label":
mergeAttr(&theme.HeaderLabel)
case "spinner": case "spinner":
mergeAttr(&theme.Spinner) mergeAttr(&theme.Spinner)
case "info": case "info":
@ -1259,8 +1273,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
mergeAttr(&theme.Cursor) mergeAttr(&theme.Cursor)
case "marker": case "marker":
mergeAttr(&theme.Marker) mergeAttr(&theme.Marker)
case "header": case "header", "header-fg":
mergeAttr(&theme.Header) mergeAttr(&theme.Header)
case "header-bg":
mergeAttr(&theme.HeaderBg)
default: default:
fail() fail()
} }
@ -1314,7 +1330,7 @@ const (
func init() { func init() {
executeRegexp = regexp.MustCompile( executeRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|list-label|preview-label|input-label)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put|print)`) `(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put|print)`)
splitRegexp = regexp.MustCompile("[,:]+") splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+") actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
} }
@ -1680,6 +1696,8 @@ func isExecuteAction(str string) actionType {
return actChangePreviewLabel return actChangePreviewLabel
case "change-input-label": case "change-input-label":
return actChangeInputLabel return actChangeInputLabel
case "change-header-label":
return actChangeHeaderLabel
case "change-preview-window": case "change-preview-window":
return actChangePreviewWindow return actChangePreviewWindow
case "change-preview": case "change-preview":
@ -1712,6 +1730,8 @@ func isExecuteAction(str string) actionType {
return actTransformPreviewLabel return actTransformPreviewLabel
case "transform-input-label": case "transform-input-label":
return actTransformInputLabel return actTransformInputLabel
case "transform-header-label":
return actTransformHeaderLabel
case "transform-header": case "transform-header":
return actTransformHeader return actTransformHeader
case "transform-prompt": case "transform-prompt":
@ -2542,6 +2562,27 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parseLabelPosition(&opts.ListLabel, pos); err != nil { if err := parseLabelPosition(&opts.ListLabel, pos); err != nil {
return err return err
} }
case "--no-header-border":
opts.HeaderBorderShape = tui.BorderNone
case "--header-border":
hasArg, arg := optionalNextString(allArgs, &i)
if opts.HeaderBorderShape, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--no-header-label":
opts.HeaderLabel.label = ""
case "--header-label":
if opts.HeaderLabel.label, err = nextString(allArgs, &i, "header label required"); err != nil {
return err
}
case "--header-label-pos":
pos, err := nextString(allArgs, &i, "header label position required (positive or negative integer or 'center')")
if err != nil {
return err
}
if err := parseLabelPosition(&opts.HeaderLabel, pos); err != nil {
return err
}
case "--no-input-border": case "--no-input-border":
opts.InputBorderShape = tui.BorderNone opts.InputBorderShape = tui.BorderNone
case "--input-border": case "--input-border":
@ -3003,6 +3044,10 @@ func postProcessOptions(opts *Options) error {
opts.InputBorderShape = tui.BorderNone opts.InputBorderShape = tui.BorderNone
} }
if opts.HeaderBorderShape == tui.BorderUndefined {
opts.HeaderBorderShape = tui.BorderNone
}
if opts.Pointer == nil { if opts.Pointer == nil {
defaultPointer := "▌" defaultPointer := "▌"
if !opts.Unicode { if !opts.Unicode {

View File

@ -180,13 +180,13 @@ type itemLine struct {
} }
func (t *Terminal) markEmptyLine(line int) { func (t *Terminal) markEmptyLine(line int) {
if t.window != t.inputWindow { if t.window != t.inputWindow && t.window != t.headerWindow {
t.prevLines[line] = itemLine{valid: true, firstLine: line, empty: true} t.prevLines[line] = itemLine{valid: true, firstLine: line, empty: true}
} }
} }
func (t *Terminal) markOtherLine(line int) { func (t *Terminal) markOtherLine(line int) {
if t.window != t.inputWindow { if t.window != t.inputWindow && t.window != t.headerWindow {
t.prevLines[line] = itemLine{valid: true, firstLine: line, other: true} t.prevLines[line] = itemLine{valid: true, firstLine: line, other: true}
} }
} }
@ -249,6 +249,9 @@ type Terminal struct {
inputLabel labelPrinter inputLabel labelPrinter
inputLabelLen int inputLabelLen int
inputLabelOpts labelOpts inputLabelOpts labelOpts
headerLabel labelPrinter
headerLabelLen int
headerLabelOpts labelOpts
pointer string pointer string
pointerLen int pointerLen int
pointerEmpty string pointerEmpty string
@ -307,6 +310,7 @@ type Terminal struct {
borderShape tui.BorderShape borderShape tui.BorderShape
listBorderShape tui.BorderShape listBorderShape tui.BorderShape
inputBorderShape tui.BorderShape inputBorderShape tui.BorderShape
headerBorderShape tui.BorderShape
listLabel labelPrinter listLabel labelPrinter
listLabelLen int listLabelLen int
listLabelOpts labelOpts listLabelOpts labelOpts
@ -317,6 +321,8 @@ type Terminal struct {
window tui.Window window tui.Window
inputWindow tui.Window inputWindow tui.Window
inputBorder tui.Window inputBorder tui.Window
headerWindow tui.Window
headerBorder tui.Window
wborder tui.Window wborder tui.Window
pborder tui.Window pborder tui.Window
pwindow tui.Window pwindow tui.Window
@ -406,6 +412,7 @@ const (
reqFullRedraw reqFullRedraw
reqResize reqResize
reqRedrawInputLabel reqRedrawInputLabel
reqRedrawHeaderLabel
reqRedrawListLabel reqRedrawListLabel
reqRedrawBorderLabel reqRedrawBorderLabel
reqRedrawPreviewLabel reqRedrawPreviewLabel
@ -450,6 +457,7 @@ const (
actChangeListLabel actChangeListLabel
actChangeInputLabel actChangeInputLabel
actChangeHeader actChangeHeader
actChangeHeaderLabel
actChangeMulti actChangeMulti
actChangePreviewLabel actChangePreviewLabel
actChangePrompt actChangePrompt
@ -512,6 +520,7 @@ const (
actTransformListLabel actTransformListLabel
actTransformInputLabel actTransformInputLabel
actTransformHeader actTransformHeader
actTransformHeaderLabel
actTransformPreviewLabel actTransformPreviewLabel
actTransformPrompt actTransformPrompt
actTransformQuery actTransformQuery
@ -847,6 +856,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
borderShape: opts.BorderShape, borderShape: opts.BorderShape,
listBorderShape: opts.ListBorderShape, listBorderShape: opts.ListBorderShape,
inputBorderShape: opts.InputBorderShape, inputBorderShape: opts.InputBorderShape,
headerBorderShape: opts.HeaderBorderShape,
borderWidth: 1, borderWidth: 1,
listLabel: nil, listLabel: nil,
listLabelOpts: opts.ListLabel, listLabelOpts: opts.ListLabel,
@ -856,6 +866,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
previewLabelOpts: opts.PreviewLabel, previewLabelOpts: opts.PreviewLabel,
inputLabel: nil, inputLabel: nil,
inputLabelOpts: opts.InputLabel, inputLabelOpts: opts.InputLabel,
headerLabel: nil,
headerLabelOpts: opts.HeaderLabel,
cleanExit: opts.ClearOnExit, cleanExit: opts.ClearOnExit,
executor: executor, executor: executor,
paused: opts.Phony, paused: opts.Phony,
@ -910,7 +922,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
lastFocus: minItem.Index()} lastFocus: minItem.Index()}
// This should be called before accessing tui.Color* // This should be called before accessing tui.Color*
tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black) tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt) t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
// Pre-calculated empty pointer and marker signs // Pre-calculated empty pointer and marker signs
@ -920,6 +932,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, false) t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, false)
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(opts.PreviewLabel.label, &tui.ColPreviewLabel, false) t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(opts.PreviewLabel.label, &tui.ColPreviewLabel, false)
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(opts.InputLabel.label, &tui.ColInputLabel, false) t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(opts.InputLabel.label, &tui.ColInputLabel, false)
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(opts.HeaderLabel.label, &tui.ColHeaderLabel, false)
// Disable separator by default if input border is set // Disable separator by default if input border is set
if opts.Separator == nil && !t.inputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0 { if opts.Separator == nil && !t.inputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0 {
@ -1064,6 +1077,13 @@ func (t *Terminal) visibleHeaderLines() int {
return len(t.header0) + t.headerLines return len(t.header0) + t.headerLines
} }
func (t *Terminal) visibleHeaderLinesInList() int {
if t.headerWindow != nil {
return 0
}
return t.visibleHeaderLines()
}
// Extra number of lines needed to display fzf // Extra number of lines needed to display fzf
func (t *Terminal) extraLines() int { func (t *Terminal) extraLines() int {
extra := t.visibleHeaderLines() + 1 extra := t.visibleHeaderLines() + 1
@ -1148,10 +1168,10 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
// Temporarily switch 'window' so that we can use the existing windows with // Temporarily switch 'window' so that we can use the existing windows with
// a different window // a different window
func (t *Terminal) withInputWindow(f func()) { func (t *Terminal) withWindow(w tui.Window, f func()) {
prevWindow := t.window prevWindow := t.window
if t.inputWindow != nil { if w != nil {
t.window = t.inputWindow t.window = w
} }
f() f()
t.window = prevWindow t.window = prevWindow
@ -1184,7 +1204,7 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
output := func() { output := func() {
wrap := t.wrap wrap := t.wrap
t.wrap = false t.wrap = false
t.withInputWindow(func() { t.withWindow(t.inputWindow, func() {
line := t.promptLine() line := t.promptLine()
t.printHighlighted( t.printHighlighted(
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil) Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil)
@ -1605,6 +1625,12 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
if t.wborder != nil { if t.wborder != nil {
t.wborder = nil t.wborder = nil
} }
if t.headerWindow != nil {
t.headerWindow = nil
}
if t.headerBorder != nil {
t.headerBorder = nil
}
if t.inputWindow != nil { if t.inputWindow != nil {
t.inputWindow = nil t.inputWindow = nil
} }
@ -1651,26 +1677,42 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
height -= paddingInt[0] + paddingInt[2] height -= paddingInt[0] + paddingInt[2]
// Adjust position and size of the list window if input border is set // Adjust position and size of the list window if input border is set
inputWindowHeight := 0
inputBorderHeight := 0 inputBorderHeight := 0
availableLines := height
shift := 0 shift := 0
shrink := 0 shrink := 0
hasInputWindow := t.inputBorderShape.Visible() hasInputWindow := t.inputBorderShape.Visible() || t.headerBorderShape.Visible()
if hasInputWindow { if hasInputWindow {
inputWindowHeight = 2 inputWindowHeight := 2
if t.noSeparatorLine() { if t.noSeparatorLine() {
inputWindowHeight-- inputWindowHeight--
} }
inputBorderHeight = borderLines(t.inputBorderShape) + inputWindowHeight inputBorderHeight = util.Min(availableLines, borderLines(t.inputBorderShape)+inputWindowHeight)
if t.layout == layoutReverse { if t.layout == layoutReverse {
shift = inputBorderHeight shift = inputBorderHeight
shrink = inputBorderHeight shrink = inputBorderHeight
} else { } else {
shift = 0
shrink = inputBorderHeight shrink = inputBorderHeight
} }
availableLines -= inputBorderHeight
} }
// Adjust position and size of the list window if header border is set
hasHeaderWindow := t.visibleHeaderLines() > 0 && (t.headerBorderShape.Visible() || t.inputBorderShape.Visible())
headerBorderHeight := 0
if hasHeaderWindow {
headerWindowHeight := t.visibleHeaderLines()
headerBorderHeight = util.Min(availableLines, borderLines(t.headerBorderShape)+headerWindowHeight)
if t.layout == layoutReverse {
shift += headerBorderHeight
shrink += headerBorderHeight
} else {
shrink += headerBorderHeight
}
availableLines -= headerBorderHeight
}
// Set up list border
hasListBorder := t.listBorderShape.Visible() hasListBorder := t.listBorderShape.Visible()
innerWidth := width innerWidth := width
innerHeight := height innerHeight := height
@ -1729,8 +1771,6 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
// Need a column to show scrollbar // Need a column to show scrollbar
pwidth -= 1 pwidth -= 1
} }
pwidth = util.Max(0, pwidth)
pheight = util.Max(0, pheight)
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, tui.WindowPreview, noBorder, true) t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, tui.WindowPreview, noBorder, true)
if !hadPreviewWindow { if !hadPreviewWindow {
t.pwindow.Erase() t.pwindow.Erase()
@ -1760,6 +1800,14 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
if previewOpts.hidden { if previewOpts.hidden {
return return
} }
maxPreviewLines := availableLines
if t.wborder != nil {
maxPreviewLines -= t.wborder.Height()
} else {
maxPreviewLines -= util.Max(0, innerHeight-pheight-shrink)
}
pheight = util.Min(pheight, maxPreviewLines)
if previewOpts.position == posUp { if previewOpts.position == posUp {
innerBorderFn(marginInt[0]+pheight, marginInt[3], width, height-pheight) innerBorderFn(marginInt[0]+pheight, marginInt[3], width, height-pheight)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
@ -1873,43 +1921,73 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
innerHeight-shrink, tui.WindowList, noBorder, true) innerHeight-shrink, tui.WindowList, noBorder, true)
} }
createInnerWindow := func(b tui.Window, shape tui.BorderShape, windowType tui.WindowType) tui.Window {
top := b.Top()
left := b.Left()
if shape.HasTop() {
top++
}
if shape.HasLeft() {
left += t.borderWidth + 1
}
width := b.Width() - borderColumns(shape, t.borderWidth)
if shape.HasRight() {
width++
}
height := b.Height() - borderLines(shape)
return t.tui.NewWindow(top, left, width, height, windowType, noBorder, true)
}
// Set up input border // Set up input border
if hasInputWindow {
w := t.wborder w := t.wborder
if t.wborder == nil { if t.wborder == nil {
w = t.window w = t.window
} }
if hasInputWindow {
var btop int
if hasHeaderWindow && t.headerFirst {
if t.layout == layoutReverse { if t.layout == layoutReverse {
t.inputBorder = t.tui.NewWindow( btop = w.Top() - inputBorderHeight
w.Top()-shrink,
w.Left(),
w.Width(),
util.Min(shrink, screenHeight), tui.WindowInput, tui.MakeBorderStyle(t.inputBorderShape, t.unicode), true)
} else { } else {
btop = w.Top() + w.Height()
}
} else {
if t.layout == layoutReverse {
btop = w.Top() - shrink
} else {
btop = w.Top() + w.Height() + headerBorderHeight
}
}
t.inputBorder = t.tui.NewWindow( t.inputBorder = t.tui.NewWindow(
w.Top()+w.Height(), btop,
w.Left(), w.Left(),
w.Width(), w.Width(),
util.Min(shrink, screenHeight), tui.WindowInput, tui.MakeBorderStyle(t.inputBorderShape, t.unicode), true) inputBorderHeight, tui.WindowInput, tui.MakeBorderStyle(t.inputBorderShape, t.unicode), true)
t.inputWindow = createInnerWindow(t.inputBorder, t.inputBorderShape, tui.WindowInput)
} }
top := t.inputBorder.Top()
left := t.inputBorder.Left() // Set up header border
if t.inputBorderShape.HasTop() { if hasHeaderWindow {
top++ var btop int
if hasInputWindow && t.headerFirst {
if t.layout == layoutReverse {
btop = w.Top() - shrink
} else {
btop = w.Top() + w.Height() + inputBorderHeight
} }
if t.inputBorderShape.HasLeft() { } else {
left += t.borderWidth + 1 if t.layout == layoutReverse {
btop = w.Top() - headerBorderHeight
} else {
btop = w.Top() + w.Height()
} }
width := t.inputBorder.Width() - borderColumns(t.inputBorderShape, t.borderWidth)
if t.inputBorderShape.HasRight() {
width++
} }
t.inputWindow = t.tui.NewWindow( t.headerBorder = t.tui.NewWindow(
top, btop,
left, w.Left(),
width, w.Width(),
t.inputBorder.Height()-borderLines(t.inputBorderShape), headerBorderHeight, tui.WindowHeader, tui.MakeBorderStyle(t.headerBorderShape, t.unicode), true)
tui.WindowInput, noBorder, true) t.headerWindow = createInnerWindow(t.headerBorder, t.headerBorderShape, tui.WindowHeader)
} }
// Print border label // Print border label
@ -1917,6 +1995,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false) t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.border, false) t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.border, false)
t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, false) t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, false)
t.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, false)
} }
func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) { func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
@ -1960,7 +2039,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
case layoutDefault: case layoutDefault:
y = h - y - 1 y = h - y - 1
case layoutReverseList: case layoutReverseList:
n := 2 + t.visibleHeaderLines() n := 2 + t.visibleHeaderLinesInList()
if t.noSeparatorLine() { if t.noSeparatorLine() {
n-- n--
} }
@ -2014,7 +2093,7 @@ func (t *Terminal) promptLine() int {
if !t.noSeparatorLine() { if !t.noSeparatorLine() {
max-- max--
} }
return util.Min(t.visibleHeaderLines(), max) return util.Min(t.visibleHeaderLinesInList(), max)
} }
return 0 return 0
} }
@ -2059,9 +2138,7 @@ func (t *Terminal) trimMessage(message string, maxWidth int) string {
} }
func (t *Terminal) printInfo() { func (t *Terminal) printInfo() {
t.withInputWindow(func() { t.withWindow(t.inputWindow, func() { t.printInfoImpl() })
t.printInfoImpl()
})
} }
func (t *Terminal) printInfoImpl() { func (t *Terminal) printInfoImpl() {
@ -2258,11 +2335,23 @@ func (t *Terminal) printInfoImpl() {
} }
func (t *Terminal) printHeader() { func (t *Terminal) printHeader() {
headerLines := t.visibleHeaderLines()
if t.headerBorderShape.Visible() && (t.headerWindow == nil && headerLines > 0 || t.headerWindow != nil && headerLines != t.headerWindow.Height()) {
t.resizeWindows(false, true)
t.printList()
t.printPrompt()
t.printInfo()
t.printPreview()
}
t.withWindow(t.headerWindow, func() { t.printHeaderImpl() })
}
func (t *Terminal) printHeaderImpl() {
if t.visibleHeaderLines() == 0 { if t.visibleHeaderLines() == 0 {
return return
} }
max := t.window.Height() max := t.window.Height()
if t.inputWindow == nil && t.headerFirst { if t.inputWindow == nil && t.headerWindow == nil && t.headerFirst {
max-- max--
if !t.noSeparatorLine() { if !t.noSeparatorLine() {
max-- max--
@ -2276,13 +2365,14 @@ func (t *Terminal) printHeader() {
} }
// Wrapping is not supported for header // Wrapping is not supported for header
wrap := t.wrap wrap := t.wrap
indent := strings.Repeat(" ", t.pointerLen+t.markerLen)
t.wrap = false t.wrap = false
for idx, lineStr := range append(append([]string{}, t.header0...), t.header...) { for idx, lineStr := range append(append([]string{}, t.header0...), t.header...) {
line := idx line := idx
if needReverse && idx < len(t.header0) { if needReverse && idx < len(t.header0) {
line = len(t.header0) - idx - 1 line = len(t.header0) - idx - 1
} }
if t.inputWindow == nil && !t.headerFirst { if t.inputWindow == nil && t.headerWindow == nil && !t.headerFirst {
line++ line++
if !t.noSeparatorLine() { if !t.noSeparatorLine() {
line++ line++
@ -2299,7 +2389,7 @@ func (t *Terminal) printHeader() {
t.printHighlighted(Result{item: item}, t.printHighlighted(Result{item: item},
tui.ColHeader, tui.ColHeader, false, false, line, line, true, tui.ColHeader, tui.ColHeader, false, false, line, line, true,
func(markerClass) { t.window.Print(strings.Repeat(" ", t.pointerLen+t.markerLen)) }, nil) func(markerClass) { t.window.Print(indent) }, nil)
} }
t.wrap = wrap t.wrap = wrap
} }
@ -2327,7 +2417,7 @@ func (t *Terminal) printList() {
count := t.merger.Length() - t.offset count := t.merger.Length() - t.offset
// Start line // Start line
startLine := t.promptLines() + t.visibleHeaderLines() startLine := t.promptLines() + t.visibleHeaderLinesInList()
maxy += startLine maxy += startLine
barRange := [2]int{startLine + barStart, startLine + barStart + barLength} barRange := [2]int{startLine + barStart, startLine + barStart + barLength}
@ -3187,7 +3277,7 @@ func (t *Terminal) printAll() {
func (t *Terminal) flush() { func (t *Terminal) flush() {
t.placeCursor() t.placeCursor()
if !t.suppress { if !t.suppress {
windows := make([]tui.Window, 0, 7) windows := make([]tui.Window, 0, 9)
if t.border != nil { if t.border != nil {
windows = append(windows, t.border) windows = append(windows, t.border)
} }
@ -3203,6 +3293,12 @@ func (t *Terminal) flush() {
if t.window != nil { if t.window != nil {
windows = append(windows, t.window) windows = append(windows, t.window)
} }
if t.headerBorder != nil {
windows = append(windows, t.headerBorder)
}
if t.headerWindow != nil {
windows = append(windows, t.headerWindow)
}
if t.inputBorder != nil { if t.inputBorder != nil {
windows = append(windows, t.inputBorder) windows = append(windows, t.inputBorder)
} }
@ -4110,6 +4206,8 @@ func (t *Terminal) Loop() error {
} }
case reqRedrawInputLabel: case reqRedrawInputLabel:
t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, true) t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, true)
case reqRedrawHeaderLabel:
t.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, true)
case reqRedrawListLabel: case reqRedrawListLabel:
t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, true) t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, true)
case reqRedrawBorderLabel: case reqRedrawBorderLabel:
@ -4493,6 +4591,12 @@ func (t *Terminal) Loop() error {
} else { } else {
req(reqHeader) req(reqHeader)
} }
case actChangeHeaderLabel:
t.headerLabelOpts.label = a.a
if t.headerBorder != nil {
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(a.a, &tui.ColHeaderLabel, false)
req(reqRedrawHeaderLabel)
}
case actChangeInputLabel: case actChangeInputLabel:
t.inputLabelOpts.label = a.a t.inputLabelOpts.label = a.a
if t.inputBorder != nil { if t.inputBorder != nil {
@ -4522,6 +4626,13 @@ func (t *Terminal) Loop() error {
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil { if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
return doActions(actions) return doActions(actions)
} }
case actTransformHeaderLabel:
label := t.executeCommand(a.a, false, true, true, true, "")
t.headerLabelOpts.label = label
if t.headerBorder != nil {
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(label, &tui.ColHeaderLabel, false)
req(reqRedrawHeaderLabel)
}
case actTransformInputLabel: case actTransformInputLabel:
label := t.executeCommand(a.a, false, true, true, true, "") label := t.executeCommand(a.a, false, true, true, true, "")
t.inputLabelOpts.label = label t.inputLabelOpts.label = label
@ -5028,25 +5139,25 @@ func (t *Terminal) Loop() error {
case posUp: case posUp:
if t.pborder.Enclose(my, mx) && my == t.pborder.Top()+t.pborder.Height()-1 { if t.pborder.Enclose(my, mx) && my == t.pborder.Top()+t.pborder.Height()-1 {
pborderDragging = 0 pborderDragging = 0
} else if t.listBorderShape.HasTop() && t.wborder.Enclose(my, mx) && my == t.wborder.Top() { } else if t.listBorderShape.HasTop() && t.pborder.EncloseX(mx) && my == t.wborder.Top() {
pborderDragging = 1 pborderDragging = 1
} }
case posDown: case posDown:
if t.pborder.Enclose(my, mx) && my == t.pborder.Top() { if t.pborder.Enclose(my, mx) && my == t.pborder.Top() {
pborderDragging = 0 pborderDragging = 0
} else if t.listBorderShape.HasBottom() && t.wborder.Enclose(my, mx) && my == t.wborder.Top()+t.wborder.Height()-1 { } else if t.listBorderShape.HasBottom() && t.pborder.EncloseX(mx) && my == t.wborder.Top()+t.wborder.Height()-1 {
pborderDragging = 1 pborderDragging = 1
} }
case posLeft: case posLeft:
if t.pborder.Enclose(my, mx) && mx == t.pborder.Left()+t.pborder.Width()-1 { if t.pborder.Enclose(my, mx) && mx == t.pborder.Left()+t.pborder.Width()-1 {
pborderDragging = 0 pborderDragging = 0
} else if t.listBorderShape.HasLeft() && t.wborder.Enclose(my, mx) && mx == t.wborder.Left() { } else if t.listBorderShape.HasLeft() && t.pborder.EncloseY(my) && mx == t.wborder.Left() {
pborderDragging = 1 pborderDragging = 1
} }
case posRight: case posRight:
if t.pborder.Enclose(my, mx) && mx == t.pborder.Left() { if t.pborder.Enclose(my, mx) && mx == t.pborder.Left() {
pborderDragging = 0 pborderDragging = 0
} else if t.listBorderShape.HasRight() && t.wborder.Enclose(my, mx) && mx == t.wborder.Left()+t.wborder.Width()-1 { } else if t.listBorderShape.HasRight() && t.pborder.EncloseY(my) && mx == t.wborder.Left()+t.wborder.Width()-1 {
pborderDragging = 1 pborderDragging = 1
} }
} }
@ -5103,6 +5214,20 @@ func (t *Terminal) Loop() error {
break break
} }
// Inside the header window
// TODO: Should we trigger this on mouse up instead?
// Should we still trigger it when the position has changed from the down event?
if t.headerVisible && t.headerWindow != nil && t.headerWindow.Enclose(my, mx) {
mx -= t.headerWindow.Left() + t.pointerLen + t.markerLen
my -= t.headerWindow.Top()
if mx < 0 {
break
}
t.clickHeaderLine = my + 1
t.clickHeaderColumn = mx + 1
return doActions(actionsFor(tui.ClickHeader))
}
// Ignored // Ignored
if !t.window.Enclose(my, mx) && !barDragging { if !t.window.Enclose(my, mx) && !barDragging {
break break
@ -5111,7 +5236,7 @@ func (t *Terminal) Loop() error {
// Translate coordinates // Translate coordinates
mx -= t.window.Left() mx -= t.window.Left()
my -= t.window.Top() my -= t.window.Top()
min := t.promptLines() + t.visibleHeaderLines() min := t.promptLines() + t.visibleHeaderLinesInList()
h := t.window.Height() h := t.window.Height()
switch t.layout { switch t.layout {
case layoutDefault: case layoutDefault:
@ -5180,9 +5305,10 @@ func (t *Terminal) Loop() error {
} }
} }
return doActions(actionsFor(evt)) return doActions(actionsFor(evt))
} else if t.headerVisible { } else if t.headerVisible && t.headerWindow == nil {
// Header // Header
numLines := t.visibleHeaderLines() // TODO: Should we trigger this on mouse up instead?
numLines := t.visibleHeaderLinesInList()
lineOffset := 0 lineOffset := 0
if t.inputWindow == nil && !t.headerFirst { if t.inputWindow == nil && !t.headerFirst {
// offset for info line // offset for info line
@ -5193,7 +5319,7 @@ func (t *Terminal) Loop() error {
} }
} }
my -= lineOffset my -= lineOffset
mx -= 2 // offset gutter mx -= t.pointerLen + t.markerLen
if my >= 0 && my < numLines && mx >= 0 { if my >= 0 && my < numLines && mx >= 0 {
if t.layout == layoutReverse { if t.layout == layoutReverse {
t.clickHeaderLine = my + 1 t.clickHeaderLine = my + 1
@ -5525,7 +5651,7 @@ func (t *Terminal) promptLines() int {
// Number of item lines in the list window // Number of item lines in the list window
func (t *Terminal) maxItems() int { func (t *Terminal) maxItems() int {
max := t.window.Height() - t.visibleHeaderLines() - t.promptLines() max := t.window.Height() - t.visibleHeaderLinesInList() - t.promptLines()
return util.Max(max, 0) return util.Max(max, 0)
} }

View File

@ -804,6 +804,9 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, wind
case WindowInput: case WindowInput:
w.fg = r.theme.Input.Color w.fg = r.theme.Input.Color
w.bg = r.theme.InputBg.Color w.bg = r.theme.InputBg.Color
case WindowHeader:
w.fg = r.theme.Header.Color
w.bg = r.theme.HeaderBg.Color
case WindowPreview: case WindowPreview:
w.fg = r.theme.PreviewFg.Color w.fg = r.theme.PreviewFg.Color
w.bg = r.theme.PreviewBg.Color w.bg = r.theme.PreviewBg.Color
@ -862,6 +865,8 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
color = ColListBorder color = ColListBorder
case WindowInput: case WindowInput:
color = ColInputBorder color = ColInputBorder
case WindowHeader:
color = ColHeaderBorder
case WindowPreview: case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
@ -885,6 +890,8 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
color = ColListBorder color = ColListBorder
case WindowInput: case WindowInput:
color = ColInputBorder color = ColInputBorder
case WindowHeader:
color = ColHeaderBorder
case WindowPreview: case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
@ -910,6 +917,8 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
color = ColListBorder color = ColListBorder
case WindowInput: case WindowInput:
color = ColInputBorder color = ColInputBorder
case WindowHeader:
color = ColHeaderBorder
case WindowPreview: case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
@ -970,9 +979,16 @@ func (w *LightWindow) Y() int {
return w.posy return w.posy
} }
func (w *LightWindow) EncloseX(x int) bool {
return x >= w.left && x < (w.left+w.width)
}
func (w *LightWindow) EncloseY(y int) bool {
return y >= w.top && y < (w.top+w.height)
}
func (w *LightWindow) Enclose(y int, x int) bool { func (w *LightWindow) Enclose(y int, x int) bool {
return x >= w.left && x < (w.left+w.width) && return w.EncloseX(x) && w.EncloseY(y)
y >= w.top && y < (w.top+w.height)
} }
func (w *LightWindow) Move(y int, x int) { func (w *LightWindow) Move(y int, x int) {

View File

@ -557,6 +557,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
switch windowType { switch windowType {
case WindowList: case WindowList:
normal = ColListBorder normal = ColListBorder
case WindowHeader:
normal = ColHeaderBorder
case WindowInput: case WindowInput:
normal = ColInputBorder normal = ColInputBorder
case WindowPreview: case WindowPreview:
@ -593,9 +595,16 @@ func (w *TcellWindow) EraseMaybe() bool {
return true return true
} }
func (w *TcellWindow) EncloseX(x int) bool {
return x >= w.left && x < (w.left+w.width)
}
func (w *TcellWindow) EncloseY(y int) bool {
return y >= w.top && y < (w.top+w.height)
}
func (w *TcellWindow) Enclose(y int, x int) bool { func (w *TcellWindow) Enclose(y int, x int) bool {
return x >= w.left && x < (w.left+w.width) && return w.EncloseX(x) && w.EncloseY(y)
y >= w.top && y < (w.top+w.height)
} }
func (w *TcellWindow) Move(y int, x int) { func (w *TcellWindow) Move(y int, x int) {
@ -792,6 +801,8 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
style = ColBorder.style() style = ColBorder.style()
case WindowList: case WindowList:
style = ColListBorder.style() style = ColListBorder.style()
case WindowHeader:
style = ColHeaderBorder.style()
case WindowInput: case WindowInput:
style = ColInputBorder.style() style = ColInputBorder.style()
case WindowPreview: case WindowPreview:

View File

@ -324,6 +324,9 @@ type ColorTheme struct {
Cursor ColorAttr Cursor ColorAttr
Marker ColorAttr Marker ColorAttr
Header ColorAttr Header ColorAttr
HeaderBg ColorAttr
HeaderBorder ColorAttr
HeaderLabel ColorAttr
Separator ColorAttr Separator ColorAttr
Scrollbar ColorAttr Scrollbar ColorAttr
Border ColorAttr Border ColorAttr
@ -543,6 +546,7 @@ const (
WindowList WindowList
WindowPreview WindowPreview
WindowInput WindowInput
WindowHeader
) )
type Renderer interface { type Renderer interface {
@ -583,6 +587,8 @@ type Window interface {
X() int X() int
Y() int Y() int
EncloseX(x int) bool
EncloseY(y int) bool
Enclose(y int, x int) bool Enclose(y int, x int) bool
Move(y int, x int) Move(y int, x int)
@ -639,6 +645,8 @@ var (
ColSpinner ColorPair ColSpinner ColorPair
ColInfo ColorPair ColInfo ColorPair
ColHeader ColorPair ColHeader ColorPair
ColHeaderBorder ColorPair
ColHeaderLabel ColorPair
ColSeparator ColorPair ColSeparator ColorPair
ColScrollbar ColorPair ColScrollbar ColorPair
ColBorder ColorPair ColBorder ColorPair
@ -691,6 +699,9 @@ func EmptyTheme() *ColorTheme {
InputBg: ColorAttr{colUndefined, AttrUndefined}, InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined}, InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined}, InputLabel: ColorAttr{colUndefined, AttrUndefined},
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
} }
} }
@ -731,6 +742,9 @@ func NoColorTheme() *ColorTheme {
InputBg: ColorAttr{colDefault, AttrUndefined}, InputBg: ColorAttr{colDefault, AttrUndefined},
InputBorder: ColorAttr{colDefault, AttrUndefined}, InputBorder: ColorAttr{colDefault, AttrUndefined},
InputLabel: ColorAttr{colDefault, AttrUndefined}, InputLabel: ColorAttr{colDefault, AttrUndefined},
HeaderBg: ColorAttr{colDefault, AttrUndefined},
HeaderBorder: ColorAttr{colDefault, AttrUndefined},
HeaderLabel: ColorAttr{colDefault, AttrUndefined},
} }
} }
@ -845,10 +859,13 @@ func init() {
InputBg: ColorAttr{colUndefined, AttrUndefined}, InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined}, InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined}, InputLabel: ColorAttr{colUndefined, AttrUndefined},
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
} }
} }
func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) { func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInputWindow bool, hasHeaderWindow bool) {
if forceBlack { if forceBlack {
theme.Bg = ColorAttr{colBlack, AttrUndefined} theme.Bg = ColorAttr{colBlack, AttrUndefined}
} }
@ -896,9 +913,22 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.Separator = o(theme.ListBorder, theme.Separator) theme.Separator = o(theme.ListBorder, theme.Separator)
theme.Scrollbar = o(theme.ListBorder, theme.Scrollbar) theme.Scrollbar = o(theme.ListBorder, theme.Scrollbar)
theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar) theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar)
theme.InputBg = o(theme.Bg, o(theme.ListBg, theme.InputBg)) if hasInputWindow {
theme.InputBg = o(theme.Bg, theme.InputBg)
} else {
// We shouldn't use input-bg if there's no separate input window
// e.g. fzf --color 'list-bg:green,input-bg:red' --no-input-border
theme.InputBg = o(theme.Bg, theme.ListBg)
}
theme.InputBorder = o(theme.Border, theme.InputBorder) theme.InputBorder = o(theme.Border, theme.InputBorder)
theme.InputLabel = o(theme.BorderLabel, theme.InputLabel) theme.InputLabel = o(theme.BorderLabel, theme.InputLabel)
if hasHeaderWindow {
theme.HeaderBg = o(theme.Bg, theme.HeaderBg)
} else {
theme.HeaderBg = o(theme.Bg, theme.ListBg)
}
theme.HeaderBorder = o(theme.Border, theme.HeaderBorder)
theme.HeaderLabel = o(theme.BorderLabel, theme.HeaderLabel)
initPalette(theme) initPalette(theme)
} }
@ -935,7 +965,6 @@ func initPalette(theme *ColorTheme) {
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg) ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.InputBg) ColSpinner = pair(theme.Spinner, theme.InputBg)
ColInfo = pair(theme.Info, theme.InputBg) ColInfo = pair(theme.Info, theme.InputBg)
ColHeader = pair(theme.Header, theme.ListBg)
ColSeparator = pair(theme.Separator, theme.InputBg) ColSeparator = pair(theme.Separator, theme.InputBg)
ColScrollbar = pair(theme.Scrollbar, theme.ListBg) ColScrollbar = pair(theme.Scrollbar, theme.ListBg)
ColBorder = pair(theme.Border, theme.Bg) ColBorder = pair(theme.Border, theme.Bg)
@ -949,6 +978,9 @@ func initPalette(theme *ColorTheme) {
ColListBorder = pair(theme.ListBorder, theme.ListBg) ColListBorder = pair(theme.ListBorder, theme.ListBg)
ColInputBorder = pair(theme.InputBorder, theme.InputBg) ColInputBorder = pair(theme.InputBorder, theme.InputBg)
ColInputLabel = pair(theme.InputLabel, theme.InputBg) ColInputLabel = pair(theme.InputLabel, theme.InputBg)
ColHeader = pair(theme.Header, theme.HeaderBg)
ColHeaderBorder = pair(theme.HeaderBorder, theme.HeaderBg)
ColHeaderLabel = pair(theme.HeaderLabel, theme.HeaderBg)
} }
func runeWidth(r rune) int { func runeWidth(r rune) int {

View File

@ -3485,6 +3485,24 @@ class TestGoFZF < TestBase
tmux.until { assert_block(block, _1) } tmux.until { assert_block(block, _1) }
end end
def test_input_border_and_label_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 --header-first), :Enter
block = <<~BLOCK
11
> 10
input
19/97
> 1
3
2
1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_list_input_border_and_label def test_list_input_border_and_label
tmux.send_keys %( tmux.send_keys %(
seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \ seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \
@ -3494,32 +3512,193 @@ class TestGoFZF < TestBase
block = <<~BLOCK block = <<~BLOCK
11 11
> 10 > 10
3 LIST
2 3
1 2
LIST 1
INPUT INPUT
19/97 19/97
> 1 > 1
BLOCK BLOCK
tmux.until { assert_block(block, _1) } tmux.until { assert_block(block, _1) }
tmux.send_keys :Space tmux.send_keys :Space
block = <<~BLOCK block = <<~BLOCK
11 11
> 10 > 10
3 list
2 3
1 2
list 1
input input
19/97 19/97
> 1 > 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_list_input_border_and_label_header_first
tmux.send_keys %(
seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \
--bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \
--bind 'space:change-input-label( input )+change-list-label( list )' --header-first
).strip, :Enter
block = <<~BLOCK
11
> 10
LIST
INPUT
19/97
> 1
3
2
1
BLOCK
tmux.until { assert_block(block, _1) }
tmux.send_keys :Space
block = <<~BLOCK
11
> 10
list
input
19/97
> 1
3
2
1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label
tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter
block = <<~BLOCK
12
11
> 10
3
2
1
header
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter
block = <<~BLOCK
12
11
> 10
19/97
> 1
3
2
1
header
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label_with_list_border
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter
block = <<~BLOCK
12
11
> 10
list
3
2
1
header
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_header_border_and_label_with_list_border_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter
block = <<~BLOCK
12
11
> 10
list
19/97
> 1
3
2
1
header
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_all_borders
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom), :Enter
block = <<~BLOCK
12
11
> 10
list
3
2
1
header
19/97
> 1
input
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_all_borders_header_first
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom --header-first), :Enter
block = <<~BLOCK
12
11
> 10
list
19/97
> 1
input
3
2
1
header
BLOCK BLOCK
tmux.until { assert_block(block, _1) } tmux.until { assert_block(block, _1) }
end end