mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-14 19:55:49 -07: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:
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "May 2025" "fzf 0.62.0" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Jun 2025" "fzf 0.63.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -503,6 +503,8 @@ Draw border around the finder
|
||||
.br
|
||||
.BR vertical " Vertical lines on each side of the finder"
|
||||
.br
|
||||
.BR line " Single line border (position automatically determined)"
|
||||
.br
|
||||
.BR top " (up)"
|
||||
.br
|
||||
.BR bottom " (down)"
|
||||
@@ -518,6 +520,9 @@ If you use a terminal emulator where each box-drawing character takes
|
||||
2 columns, try setting \fB\-\-ambidouble\fR. If the border is still not properly
|
||||
rendered, set \fB\-\-no\-unicode\fR.
|
||||
|
||||
\fBline\fR style draws a single separator line at the top when \fB\-\-height\fR
|
||||
is used.
|
||||
|
||||
.TP
|
||||
.BI "\-\-border\-label" [=LABEL]
|
||||
Label to print on the horizontal border line. Should be used with one of the
|
||||
@@ -661,7 +666,8 @@ Do not display scrollbar. A synonym for \fB\-\-scrollbar=''\fB
|
||||
|
||||
.TP
|
||||
.BI "\-\-list\-border" [=STYLE]
|
||||
Draw border around the list section
|
||||
Draw border around the list section. \fBline\fR style is not supported for
|
||||
this border.
|
||||
|
||||
.TP
|
||||
.BI "\-\-list\-label" [=LABEL]
|
||||
@@ -748,7 +754,8 @@ actions are affected:
|
||||
\fBkill\-word\fR
|
||||
.TP
|
||||
.BI "\-\-input\-border" [=STYLE]
|
||||
Draw border around the input section
|
||||
Draw border around the input section. \fBline\fR style draws a single separator
|
||||
line between the input section and the list section.
|
||||
|
||||
.TP
|
||||
.BI "\-\-input\-label" [=LABEL]
|
||||
@@ -848,8 +855,7 @@ e.g.
|
||||
|
||||
.TP
|
||||
.BI "\-\-preview\-border" [=STYLE]
|
||||
Short for \fB\-\-preview\-window=border\-STYLE\fR. In addition to the other
|
||||
styles, \fBline\fR style is also supported for preview border, which draws
|
||||
Short for \fB\-\-preview\-window=border\-STYLE\fR. \fBline\fR style draws
|
||||
a single separator line between the preview window and the rest of the
|
||||
interface.
|
||||
|
||||
@@ -1005,7 +1011,8 @@ Print header before the prompt line. When both normal header and header lines
|
||||
(\fB\-\-header\-lines\fR) are present, this applies only to the normal header.
|
||||
.TP
|
||||
.BI "\-\-header\-border" [=STYLE]
|
||||
Draw border around the header section
|
||||
Draw border around the header section. \fBline\fR style draws a single
|
||||
separator line between the header window and the list section.
|
||||
|
||||
.TP
|
||||
.BI "\-\-header\-label" [=LABEL]
|
||||
@@ -1019,7 +1026,30 @@ Position of the header label
|
||||
.BI "\-\-header\-lines\-border" [=STYLE]
|
||||
Display header from \fB--header\-lines\fR with a separate border. Pass
|
||||
\fBnone\fR to still separate the header lines but without a border. To combine
|
||||
two headers, use \fB\-\-no\-header\-lines\-border\fR.
|
||||
two headers, use \fB\-\-no\-header\-lines\-border\fR. \fBline\fR style draws
|
||||
a single separator line between the header lines and the list section.
|
||||
|
||||
.SS FOOTER
|
||||
|
||||
.TP
|
||||
.BI "\-\-footer=" "STR"
|
||||
The given string will be printed as the sticky footer. The lines are displayed
|
||||
in the given order from top to bottom regardless of \fB\-\-layout\fR option, and
|
||||
are not affected by \fB\-\-with\-nth\fR. ANSI color codes are processed even when
|
||||
\fB\-\-ansi\fR is not set.
|
||||
|
||||
.TP
|
||||
.BI "\-\-footer\-border" [=STYLE]
|
||||
Draw border around the header section. \fBline\fR style draws a single
|
||||
separator line between the footer and the list section.
|
||||
|
||||
.TP
|
||||
.BI "\-\-footer\-label" [=LABEL]
|
||||
Label to print on the footer border
|
||||
|
||||
.TP
|
||||
.BI "\-\-footer\-label\-pos" [=N[:top|bottom]]
|
||||
Position of the footer label
|
||||
|
||||
.SS SCRIPTING
|
||||
.TP
|
||||
|
@@ -29,128 +29,132 @@ func _() {
|
||||
_ = x[actChangeBorderLabel-18]
|
||||
_ = x[actChangeGhost-19]
|
||||
_ = x[actChangeHeader-20]
|
||||
_ = x[actChangeHeaderLabel-21]
|
||||
_ = x[actChangeInputLabel-22]
|
||||
_ = x[actChangeListLabel-23]
|
||||
_ = x[actChangeMulti-24]
|
||||
_ = x[actChangeNth-25]
|
||||
_ = x[actChangePointer-26]
|
||||
_ = x[actChangePreview-27]
|
||||
_ = x[actChangePreviewLabel-28]
|
||||
_ = x[actChangePreviewWindow-29]
|
||||
_ = x[actChangePrompt-30]
|
||||
_ = x[actChangeQuery-31]
|
||||
_ = x[actClearScreen-32]
|
||||
_ = x[actClearQuery-33]
|
||||
_ = x[actClearSelection-34]
|
||||
_ = x[actClose-35]
|
||||
_ = x[actDeleteChar-36]
|
||||
_ = x[actDeleteCharEof-37]
|
||||
_ = x[actEndOfLine-38]
|
||||
_ = x[actFatal-39]
|
||||
_ = x[actForwardChar-40]
|
||||
_ = x[actForwardWord-41]
|
||||
_ = x[actKillLine-42]
|
||||
_ = x[actKillWord-43]
|
||||
_ = x[actUnixLineDiscard-44]
|
||||
_ = x[actUnixWordRubout-45]
|
||||
_ = x[actYank-46]
|
||||
_ = x[actBackwardKillWord-47]
|
||||
_ = x[actSelectAll-48]
|
||||
_ = x[actDeselectAll-49]
|
||||
_ = x[actToggle-50]
|
||||
_ = x[actToggleSearch-51]
|
||||
_ = x[actToggleAll-52]
|
||||
_ = x[actToggleDown-53]
|
||||
_ = x[actToggleUp-54]
|
||||
_ = x[actToggleIn-55]
|
||||
_ = x[actToggleOut-56]
|
||||
_ = x[actToggleTrack-57]
|
||||
_ = x[actToggleTrackCurrent-58]
|
||||
_ = x[actToggleHeader-59]
|
||||
_ = x[actToggleWrap-60]
|
||||
_ = x[actToggleMultiLine-61]
|
||||
_ = x[actToggleHscroll-62]
|
||||
_ = x[actTrackCurrent-63]
|
||||
_ = x[actToggleInput-64]
|
||||
_ = x[actHideInput-65]
|
||||
_ = x[actShowInput-66]
|
||||
_ = x[actUntrackCurrent-67]
|
||||
_ = x[actDown-68]
|
||||
_ = x[actUp-69]
|
||||
_ = x[actPageUp-70]
|
||||
_ = x[actPageDown-71]
|
||||
_ = x[actPosition-72]
|
||||
_ = x[actHalfPageUp-73]
|
||||
_ = x[actHalfPageDown-74]
|
||||
_ = x[actOffsetUp-75]
|
||||
_ = x[actOffsetDown-76]
|
||||
_ = x[actOffsetMiddle-77]
|
||||
_ = x[actJump-78]
|
||||
_ = x[actJumpAccept-79]
|
||||
_ = x[actPrintQuery-80]
|
||||
_ = x[actRefreshPreview-81]
|
||||
_ = x[actReplaceQuery-82]
|
||||
_ = x[actToggleSort-83]
|
||||
_ = x[actShowPreview-84]
|
||||
_ = x[actHidePreview-85]
|
||||
_ = x[actTogglePreview-86]
|
||||
_ = x[actTogglePreviewWrap-87]
|
||||
_ = x[actTransform-88]
|
||||
_ = x[actTransformBorderLabel-89]
|
||||
_ = x[actTransformGhost-90]
|
||||
_ = x[actTransformHeader-91]
|
||||
_ = x[actTransformHeaderLabel-92]
|
||||
_ = x[actTransformInputLabel-93]
|
||||
_ = x[actTransformListLabel-94]
|
||||
_ = x[actTransformNth-95]
|
||||
_ = x[actTransformPointer-96]
|
||||
_ = x[actTransformPreviewLabel-97]
|
||||
_ = x[actTransformPrompt-98]
|
||||
_ = x[actTransformQuery-99]
|
||||
_ = x[actTransformSearch-100]
|
||||
_ = x[actSearch-101]
|
||||
_ = x[actPreview-102]
|
||||
_ = x[actPreviewTop-103]
|
||||
_ = x[actPreviewBottom-104]
|
||||
_ = x[actPreviewUp-105]
|
||||
_ = x[actPreviewDown-106]
|
||||
_ = x[actPreviewPageUp-107]
|
||||
_ = x[actPreviewPageDown-108]
|
||||
_ = x[actPreviewHalfPageUp-109]
|
||||
_ = x[actPreviewHalfPageDown-110]
|
||||
_ = x[actPrevHistory-111]
|
||||
_ = x[actPrevSelected-112]
|
||||
_ = x[actPrint-113]
|
||||
_ = x[actPut-114]
|
||||
_ = x[actNextHistory-115]
|
||||
_ = x[actNextSelected-116]
|
||||
_ = x[actExecute-117]
|
||||
_ = x[actExecuteSilent-118]
|
||||
_ = x[actExecuteMulti-119]
|
||||
_ = x[actSigStop-120]
|
||||
_ = x[actFirst-121]
|
||||
_ = x[actLast-122]
|
||||
_ = x[actReload-123]
|
||||
_ = x[actReloadSync-124]
|
||||
_ = x[actDisableSearch-125]
|
||||
_ = x[actEnableSearch-126]
|
||||
_ = x[actSelect-127]
|
||||
_ = x[actDeselect-128]
|
||||
_ = x[actUnbind-129]
|
||||
_ = x[actRebind-130]
|
||||
_ = x[actToggleBind-131]
|
||||
_ = x[actBecome-132]
|
||||
_ = x[actShowHeader-133]
|
||||
_ = x[actHideHeader-134]
|
||||
_ = x[actBell-135]
|
||||
_ = x[actExclude-136]
|
||||
_ = x[actExcludeMulti-137]
|
||||
_ = x[actChangeFooter-21]
|
||||
_ = x[actChangeHeaderLabel-22]
|
||||
_ = x[actChangeFooterLabel-23]
|
||||
_ = x[actChangeInputLabel-24]
|
||||
_ = x[actChangeListLabel-25]
|
||||
_ = x[actChangeMulti-26]
|
||||
_ = x[actChangeNth-27]
|
||||
_ = x[actChangePointer-28]
|
||||
_ = x[actChangePreview-29]
|
||||
_ = x[actChangePreviewLabel-30]
|
||||
_ = x[actChangePreviewWindow-31]
|
||||
_ = x[actChangePrompt-32]
|
||||
_ = x[actChangeQuery-33]
|
||||
_ = x[actClearScreen-34]
|
||||
_ = x[actClearQuery-35]
|
||||
_ = x[actClearSelection-36]
|
||||
_ = x[actClose-37]
|
||||
_ = x[actDeleteChar-38]
|
||||
_ = x[actDeleteCharEof-39]
|
||||
_ = x[actEndOfLine-40]
|
||||
_ = x[actFatal-41]
|
||||
_ = x[actForwardChar-42]
|
||||
_ = x[actForwardWord-43]
|
||||
_ = x[actKillLine-44]
|
||||
_ = x[actKillWord-45]
|
||||
_ = x[actUnixLineDiscard-46]
|
||||
_ = x[actUnixWordRubout-47]
|
||||
_ = x[actYank-48]
|
||||
_ = x[actBackwardKillWord-49]
|
||||
_ = x[actSelectAll-50]
|
||||
_ = x[actDeselectAll-51]
|
||||
_ = x[actToggle-52]
|
||||
_ = x[actToggleSearch-53]
|
||||
_ = x[actToggleAll-54]
|
||||
_ = x[actToggleDown-55]
|
||||
_ = x[actToggleUp-56]
|
||||
_ = x[actToggleIn-57]
|
||||
_ = x[actToggleOut-58]
|
||||
_ = x[actToggleTrack-59]
|
||||
_ = x[actToggleTrackCurrent-60]
|
||||
_ = x[actToggleHeader-61]
|
||||
_ = x[actToggleWrap-62]
|
||||
_ = x[actToggleMultiLine-63]
|
||||
_ = x[actToggleHscroll-64]
|
||||
_ = x[actTrackCurrent-65]
|
||||
_ = x[actToggleInput-66]
|
||||
_ = x[actHideInput-67]
|
||||
_ = x[actShowInput-68]
|
||||
_ = x[actUntrackCurrent-69]
|
||||
_ = x[actDown-70]
|
||||
_ = x[actUp-71]
|
||||
_ = x[actPageUp-72]
|
||||
_ = x[actPageDown-73]
|
||||
_ = x[actPosition-74]
|
||||
_ = x[actHalfPageUp-75]
|
||||
_ = x[actHalfPageDown-76]
|
||||
_ = x[actOffsetUp-77]
|
||||
_ = x[actOffsetDown-78]
|
||||
_ = x[actOffsetMiddle-79]
|
||||
_ = x[actJump-80]
|
||||
_ = x[actJumpAccept-81]
|
||||
_ = x[actPrintQuery-82]
|
||||
_ = x[actRefreshPreview-83]
|
||||
_ = x[actReplaceQuery-84]
|
||||
_ = x[actToggleSort-85]
|
||||
_ = x[actShowPreview-86]
|
||||
_ = x[actHidePreview-87]
|
||||
_ = x[actTogglePreview-88]
|
||||
_ = x[actTogglePreviewWrap-89]
|
||||
_ = x[actTransform-90]
|
||||
_ = x[actTransformBorderLabel-91]
|
||||
_ = x[actTransformGhost-92]
|
||||
_ = x[actTransformHeader-93]
|
||||
_ = x[actTransformFooter-94]
|
||||
_ = x[actTransformHeaderLabel-95]
|
||||
_ = x[actTransformFooterLabel-96]
|
||||
_ = x[actTransformInputLabel-97]
|
||||
_ = x[actTransformListLabel-98]
|
||||
_ = x[actTransformNth-99]
|
||||
_ = x[actTransformPointer-100]
|
||||
_ = x[actTransformPreviewLabel-101]
|
||||
_ = x[actTransformPrompt-102]
|
||||
_ = x[actTransformQuery-103]
|
||||
_ = x[actTransformSearch-104]
|
||||
_ = x[actSearch-105]
|
||||
_ = x[actPreview-106]
|
||||
_ = x[actPreviewTop-107]
|
||||
_ = x[actPreviewBottom-108]
|
||||
_ = x[actPreviewUp-109]
|
||||
_ = x[actPreviewDown-110]
|
||||
_ = x[actPreviewPageUp-111]
|
||||
_ = x[actPreviewPageDown-112]
|
||||
_ = x[actPreviewHalfPageUp-113]
|
||||
_ = x[actPreviewHalfPageDown-114]
|
||||
_ = x[actPrevHistory-115]
|
||||
_ = x[actPrevSelected-116]
|
||||
_ = x[actPrint-117]
|
||||
_ = x[actPut-118]
|
||||
_ = x[actNextHistory-119]
|
||||
_ = x[actNextSelected-120]
|
||||
_ = x[actExecute-121]
|
||||
_ = x[actExecuteSilent-122]
|
||||
_ = x[actExecuteMulti-123]
|
||||
_ = x[actSigStop-124]
|
||||
_ = x[actFirst-125]
|
||||
_ = x[actLast-126]
|
||||
_ = x[actReload-127]
|
||||
_ = x[actReloadSync-128]
|
||||
_ = x[actDisableSearch-129]
|
||||
_ = x[actEnableSearch-130]
|
||||
_ = x[actSelect-131]
|
||||
_ = x[actDeselect-132]
|
||||
_ = x[actUnbind-133]
|
||||
_ = x[actRebind-134]
|
||||
_ = x[actToggleBind-135]
|
||||
_ = x[actBecome-136]
|
||||
_ = x[actShowHeader-137]
|
||||
_ = x[actHideHeader-138]
|
||||
_ = x[actBell-139]
|
||||
_ = x[actExclude-140]
|
||||
_ = x[actExcludeMulti-141]
|
||||
}
|
||||
|
||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeHeaderLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformHeaderLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMulti"
|
||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMulti"
|
||||
|
||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 249, 269, 283, 298, 318, 337, 355, 369, 381, 397, 413, 434, 456, 471, 485, 499, 512, 529, 537, 550, 566, 578, 586, 600, 614, 625, 636, 654, 671, 678, 697, 709, 723, 732, 747, 759, 772, 783, 794, 806, 820, 841, 856, 869, 887, 903, 918, 932, 944, 956, 973, 980, 985, 994, 1005, 1016, 1029, 1044, 1055, 1068, 1083, 1090, 1103, 1116, 1133, 1148, 1161, 1175, 1189, 1205, 1225, 1237, 1260, 1277, 1295, 1318, 1340, 1361, 1376, 1395, 1419, 1437, 1454, 1472, 1481, 1491, 1504, 1520, 1532, 1546, 1562, 1580, 1600, 1622, 1636, 1651, 1659, 1665, 1679, 1694, 1704, 1720, 1735, 1745, 1753, 1760, 1769, 1782, 1798, 1813, 1822, 1833, 1842, 1851, 1864, 1873, 1886, 1899, 1906, 1916, 1931}
|
||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 249, 269, 283, 298, 313, 333, 353, 372, 390, 404, 416, 432, 448, 469, 491, 506, 520, 534, 547, 564, 572, 585, 601, 613, 621, 635, 649, 660, 671, 689, 706, 713, 732, 744, 758, 767, 782, 794, 807, 818, 829, 841, 855, 876, 891, 904, 922, 938, 953, 967, 979, 991, 1008, 1015, 1020, 1029, 1040, 1051, 1064, 1079, 1090, 1103, 1118, 1125, 1138, 1151, 1168, 1183, 1196, 1210, 1224, 1240, 1260, 1272, 1295, 1312, 1330, 1348, 1371, 1394, 1416, 1437, 1452, 1471, 1495, 1513, 1530, 1548, 1557, 1567, 1580, 1596, 1608, 1622, 1638, 1656, 1676, 1698, 1712, 1727, 1735, 1741, 1755, 1770, 1780, 1796, 1811, 1821, 1829, 1836, 1845, 1858, 1874, 1889, 1898, 1909, 1918, 1927, 1940, 1949, 1962, 1975, 1982, 1992, 2007}
|
||||
|
||||
func (i actionType) String() string {
|
||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||
|
@@ -39,7 +39,7 @@ func (r revision) compatible(other revision) bool {
|
||||
// Run starts fzf
|
||||
func Run(opts *Options) (int, error) {
|
||||
if opts.Filter == nil {
|
||||
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
|
||||
if opts.useTmux() {
|
||||
return runTmux(os.Args, opts)
|
||||
}
|
||||
|
||||
|
108
src/options.go
108
src/options.go
@@ -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
|
||||
}
|
||||
|
232
src/terminal.go
232
src/terminal.go
@@ -180,14 +180,18 @@ type itemLine struct {
|
||||
other bool
|
||||
}
|
||||
|
||||
func (t *Terminal) inListWindow() bool {
|
||||
return t.window != t.inputWindow && t.window != t.headerWindow && t.window != t.headerLinesWindow && t.window != t.footerWindow
|
||||
}
|
||||
|
||||
func (t *Terminal) markEmptyLine(line int) {
|
||||
if t.window != t.inputWindow && t.window != t.headerWindow {
|
||||
if t.inListWindow() {
|
||||
t.prevLines[line] = itemLine{valid: true, firstLine: line, empty: true}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) markOtherLine(line int) {
|
||||
if t.window != t.inputWindow && t.window != t.headerWindow {
|
||||
if t.inListWindow() {
|
||||
t.prevLines[line] = itemLine{valid: true, firstLine: line, other: true}
|
||||
}
|
||||
}
|
||||
@@ -254,6 +258,9 @@ type Terminal struct {
|
||||
headerLabel labelPrinter
|
||||
headerLabelLen int
|
||||
headerLabelOpts labelOpts
|
||||
footerLabel labelPrinter
|
||||
footerLabelLen int
|
||||
footerLabelOpts labelOpts
|
||||
pointer string
|
||||
pointerLen int
|
||||
pointerEmpty string
|
||||
@@ -301,6 +308,7 @@ type Terminal struct {
|
||||
headerLines int
|
||||
header []string
|
||||
header0 []string
|
||||
footer []string
|
||||
ellipsis string
|
||||
scrollbar string
|
||||
previewScrollbar string
|
||||
@@ -322,6 +330,7 @@ type Terminal struct {
|
||||
inputBorderShape tui.BorderShape
|
||||
headerBorderShape tui.BorderShape
|
||||
headerLinesShape tui.BorderShape
|
||||
footerBorderShape tui.BorderShape
|
||||
listLabel labelPrinter
|
||||
listLabelLen int
|
||||
listLabelOpts labelOpts
|
||||
@@ -337,6 +346,8 @@ type Terminal struct {
|
||||
headerBorder tui.Window
|
||||
headerLinesWindow tui.Window
|
||||
headerLinesBorder tui.Window
|
||||
footerWindow tui.Window
|
||||
footerBorder tui.Window
|
||||
wborder tui.Window
|
||||
pborder tui.Window
|
||||
pwindow tui.Window
|
||||
@@ -426,6 +437,7 @@ const (
|
||||
reqPrompt util.EventType = iota
|
||||
reqInfo
|
||||
reqHeader
|
||||
reqFooter
|
||||
reqList
|
||||
reqJump
|
||||
reqActivate
|
||||
@@ -434,6 +446,7 @@ const (
|
||||
reqResize
|
||||
reqRedrawInputLabel
|
||||
reqRedrawHeaderLabel
|
||||
reqRedrawFooterLabel
|
||||
reqRedrawListLabel
|
||||
reqRedrawBorderLabel
|
||||
reqRedrawPreviewLabel
|
||||
@@ -479,7 +492,9 @@ const (
|
||||
actChangeBorderLabel
|
||||
actChangeGhost
|
||||
actChangeHeader
|
||||
actChangeFooter
|
||||
actChangeHeaderLabel
|
||||
actChangeFooterLabel
|
||||
actChangeInputLabel
|
||||
actChangeListLabel
|
||||
actChangeMulti
|
||||
@@ -550,7 +565,9 @@ const (
|
||||
actTransformBorderLabel
|
||||
actTransformGhost
|
||||
actTransformHeader
|
||||
actTransformFooter
|
||||
actTransformHeaderLabel
|
||||
actTransformFooterLabel
|
||||
actTransformInputLabel
|
||||
actTransformListLabel
|
||||
actTransformNth
|
||||
@@ -907,6 +924,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
inputBorderShape: opts.InputBorderShape,
|
||||
headerBorderShape: opts.HeaderBorderShape,
|
||||
headerLinesShape: opts.HeaderLinesShape,
|
||||
footerBorderShape: opts.FooterBorderShape,
|
||||
borderWidth: 1,
|
||||
listLabel: nil,
|
||||
listLabelOpts: opts.ListLabel,
|
||||
@@ -918,6 +936,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
inputLabelOpts: opts.InputLabel,
|
||||
headerLabel: nil,
|
||||
headerLabelOpts: opts.HeaderLabel,
|
||||
footerLabel: nil,
|
||||
footerLabelOpts: opts.FooterLabel,
|
||||
cleanExit: opts.ClearOnExit,
|
||||
executor: executor,
|
||||
paused: opts.Phony,
|
||||
@@ -929,6 +949,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
headerLines: opts.HeaderLines,
|
||||
gap: opts.Gap,
|
||||
header: []string{},
|
||||
footer: opts.Footer,
|
||||
header0: opts.Header,
|
||||
ansi: opts.Ansi,
|
||||
nthAttr: opts.Theme.Nth.Attr,
|
||||
@@ -992,6 +1013,52 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
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.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(opts.HeaderLabel.label, &tui.ColHeaderLabel, false)
|
||||
t.footerLabel, t.footerLabelLen = t.ansiLabelPrinter(opts.FooterLabel.label, &tui.ColFooterLabel, false)
|
||||
|
||||
// Determine border shape
|
||||
if t.borderShape == tui.BorderLine {
|
||||
if t.fullscreen {
|
||||
t.borderShape = tui.BorderNone
|
||||
} else {
|
||||
t.borderShape = tui.BorderTop
|
||||
}
|
||||
}
|
||||
|
||||
// Determine input border shape
|
||||
if t.inputBorderShape == tui.BorderLine {
|
||||
if t.layout == layoutReverse {
|
||||
t.inputBorderShape = tui.BorderBottom
|
||||
} else {
|
||||
t.inputBorderShape = tui.BorderTop
|
||||
}
|
||||
}
|
||||
|
||||
// Determine header border shape
|
||||
if t.headerBorderShape == tui.BorderLine {
|
||||
if t.layout == layoutReverse {
|
||||
t.headerBorderShape = tui.BorderBottom
|
||||
} else {
|
||||
t.headerBorderShape = tui.BorderTop
|
||||
}
|
||||
}
|
||||
|
||||
// Determine header lines border shape
|
||||
if t.headerLinesShape == tui.BorderLine {
|
||||
if t.layout == layoutDefault {
|
||||
t.headerLinesShape = tui.BorderTop
|
||||
} else {
|
||||
t.headerLinesShape = tui.BorderBottom
|
||||
}
|
||||
}
|
||||
|
||||
// Determine footer border shape
|
||||
if t.footerBorderShape == tui.BorderLine {
|
||||
if t.layout == layoutReverse {
|
||||
t.footerBorderShape = tui.BorderTop
|
||||
} else {
|
||||
t.footerBorderShape = tui.BorderBottom
|
||||
}
|
||||
}
|
||||
|
||||
// Disable separator by default if input border is set
|
||||
if opts.Separator == nil && !t.inputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0 {
|
||||
@@ -1208,6 +1275,10 @@ func (t *Terminal) extraLines() int {
|
||||
}
|
||||
extra += t.headerLines
|
||||
}
|
||||
if len(t.footer) > 0 {
|
||||
extra += borderLines(t.footerBorderShape)
|
||||
extra += len(t.footer)
|
||||
}
|
||||
return extra
|
||||
}
|
||||
|
||||
@@ -1475,6 +1546,16 @@ func (t *Terminal) changeHeader(header string) bool {
|
||||
return needFullRedraw
|
||||
}
|
||||
|
||||
func (t *Terminal) changeFooter(footer string) bool {
|
||||
var lines []string
|
||||
if len(footer) > 0 {
|
||||
lines = strings.Split(strings.TrimSuffix(footer, "\n"), "\n")
|
||||
}
|
||||
needFullRedraw := len(t.footer) != len(lines)
|
||||
t.footer = lines
|
||||
return needFullRedraw
|
||||
}
|
||||
|
||||
// UpdateHeader updates the header
|
||||
func (t *Terminal) UpdateHeader(header []string) {
|
||||
t.mutex.Lock()
|
||||
@@ -1835,6 +1916,12 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
if t.headerBorder != nil {
|
||||
t.headerBorder = nil
|
||||
}
|
||||
if t.footerWindow != nil {
|
||||
t.footerWindow = nil
|
||||
}
|
||||
if t.footerBorder != nil {
|
||||
t.footerBorder = nil
|
||||
}
|
||||
if t.headerLinesWindow != nil {
|
||||
t.headerLinesWindow = nil
|
||||
}
|
||||
@@ -1889,17 +1976,19 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
// Adjust position and size of the list window if input border is set
|
||||
inputBorderHeight := 0
|
||||
availableLines := height
|
||||
|
||||
shift := 0
|
||||
shrink := 0
|
||||
hasHeaderWindow := t.hasHeaderWindow()
|
||||
hasFooterWindow := len(t.footer) > 0
|
||||
hasHeaderLinesWindow, headerLinesShape := t.determineHeaderLinesShape()
|
||||
hasInputWindow := !t.inputless && (t.inputBorderShape.Visible() || hasHeaderWindow || hasHeaderLinesWindow)
|
||||
inputWindowHeight := 2
|
||||
if t.noSeparatorLine() {
|
||||
inputWindowHeight--
|
||||
}
|
||||
if hasInputWindow {
|
||||
inputWindowHeight := 2
|
||||
if t.noSeparatorLine() {
|
||||
inputWindowHeight--
|
||||
}
|
||||
inputBorderHeight = util.Min(availableLines, borderLines(t.inputBorderShape)+inputWindowHeight)
|
||||
inputBorderHeight = util.Constrain(borderLines(t.inputBorderShape)+inputWindowHeight, 0, availableLines)
|
||||
if t.layout == layoutReverse {
|
||||
shift = inputBorderHeight
|
||||
shrink = inputBorderHeight
|
||||
@@ -1907,6 +1996,17 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
shrink = inputBorderHeight
|
||||
}
|
||||
availableLines -= inputBorderHeight
|
||||
} else if !t.inputless {
|
||||
availableLines -= inputWindowHeight
|
||||
}
|
||||
|
||||
// FIXME: Needed?
|
||||
if t.needPreviewWindow() {
|
||||
_, minPreviewHeight := t.minPreviewSize(t.activePreviewOpts)
|
||||
switch t.activePreviewOpts.position {
|
||||
case posUp, posDown:
|
||||
availableLines -= minPreviewHeight
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust position and size of the list window if header border is set
|
||||
@@ -1916,7 +2016,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
if hasHeaderLinesWindow {
|
||||
headerWindowHeight -= t.headerLines
|
||||
}
|
||||
headerBorderHeight = util.Min(availableLines, borderLines(t.headerBorderShape)+headerWindowHeight)
|
||||
headerBorderHeight = util.Constrain(borderLines(t.headerBorderShape)+headerWindowHeight, 0, availableLines)
|
||||
if t.layout == layoutReverse {
|
||||
shift += headerBorderHeight
|
||||
shrink += headerBorderHeight
|
||||
@@ -1928,7 +2028,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
|
||||
headerLinesHeight := 0
|
||||
if hasHeaderLinesWindow {
|
||||
headerLinesHeight = util.Min(availableLines, borderLines(headerLinesShape)+t.headerLines)
|
||||
headerLinesHeight = util.Constrain(borderLines(headerLinesShape)+t.headerLines, 0, availableLines)
|
||||
if t.layout != layoutDefault {
|
||||
shift += headerLinesHeight
|
||||
shrink += headerLinesHeight
|
||||
@@ -1938,6 +2038,17 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
availableLines -= headerLinesHeight
|
||||
}
|
||||
|
||||
footerBorderHeight := 0
|
||||
if hasFooterWindow {
|
||||
// Footer lines should not take all available lines
|
||||
footerBorderHeight = util.Constrain(borderLines(t.footerBorderShape)+len(t.footer), 0, availableLines)
|
||||
shrink += footerBorderHeight
|
||||
if t.layout != layoutReverse {
|
||||
shift += footerBorderHeight
|
||||
}
|
||||
availableLines -= footerBorderHeight
|
||||
}
|
||||
|
||||
// Set up list border
|
||||
hasListBorder := t.listBorderShape.Visible()
|
||||
innerWidth := width
|
||||
@@ -2041,13 +2152,8 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
width++
|
||||
}
|
||||
|
||||
maxPreviewLines := availableLines
|
||||
if t.wborder != nil {
|
||||
maxPreviewLines -= t.wborder.Height()
|
||||
} else {
|
||||
maxPreviewLines -= util.Max(0, innerHeight-pheight-shrink)
|
||||
}
|
||||
pheight = util.Min(pheight, maxPreviewLines)
|
||||
pheight = util.Constrain(pheight, minPreviewHeight, availableLines)
|
||||
|
||||
if previewOpts.position == posUp {
|
||||
innerBorderFn(marginInt[0]+pheight, marginInt[3], width, height-pheight)
|
||||
t.window = t.tui.NewWindow(
|
||||
@@ -2210,7 +2316,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
case layoutDefault:
|
||||
btop = w.Top() + w.Height() + headerBorderHeight + headerLinesHeight
|
||||
case layoutReverse:
|
||||
btop = w.Top() - shrink
|
||||
btop = w.Top() - shrink + footerBorderHeight
|
||||
case layoutReverseList:
|
||||
btop = w.Top() + w.Height() + headerBorderHeight
|
||||
}
|
||||
@@ -2238,7 +2344,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
var btop int
|
||||
if hasInputWindow && t.headerFirst {
|
||||
if t.layout == layoutReverse {
|
||||
btop = w.Top() - shrink
|
||||
btop = w.Top() - shrink + footerBorderHeight
|
||||
} else if t.layout == layoutReverseList {
|
||||
btop = w.Top() + w.Height() + inputBorderHeight
|
||||
} else {
|
||||
@@ -2294,12 +2400,31 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
t.headerLinesWindow = createInnerWindow(t.headerLinesBorder, headerLinesShape, tui.WindowHeader, 0)
|
||||
}
|
||||
|
||||
// Set up footer
|
||||
if hasFooterWindow {
|
||||
var btop int
|
||||
if t.layout == layoutReverse {
|
||||
btop = w.Top() + w.Height()
|
||||
} else if t.layout == layoutReverseList {
|
||||
btop = w.Top() - footerBorderHeight - headerLinesHeight
|
||||
} else {
|
||||
btop = w.Top() - footerBorderHeight
|
||||
}
|
||||
t.footerBorder = t.tui.NewWindow(
|
||||
btop,
|
||||
w.Left(),
|
||||
w.Width(),
|
||||
footerBorderHeight, tui.WindowFooter, tui.MakeBorderStyle(t.footerBorderShape, t.unicode), true)
|
||||
t.footerWindow = createInnerWindow(t.footerBorder, t.footerBorderShape, tui.WindowFooter, 0)
|
||||
}
|
||||
|
||||
// Print border label
|
||||
t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, 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.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, false)
|
||||
t.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, false)
|
||||
t.printLabel(t.footerBorder, t.footerLabel, t.footerLabelOpts, t.footerLabelLen, t.footerBorderShape, false)
|
||||
}
|
||||
|
||||
func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
|
||||
@@ -2343,7 +2468,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
||||
case layoutDefault:
|
||||
y = h - y - 1
|
||||
case layoutReverseList:
|
||||
if t.window == t.inputWindow || t.window == t.headerWindow {
|
||||
if !t.inListWindow() && t.window != t.headerLinesWindow {
|
||||
// From bottom to top
|
||||
y = h - y - 1
|
||||
} else {
|
||||
@@ -2690,8 +2815,10 @@ func (t *Terminal) resizeIfNeeded() bool {
|
||||
if t.hasHeaderLinesWindow() {
|
||||
primaryHeaderLines -= t.headerLines
|
||||
}
|
||||
// FIXME: Full redraw is triggered if there are too many lines in the header
|
||||
// so that the header window cannot display all of them.
|
||||
needHeaderLinesWindow := t.hasHeaderLinesWindow()
|
||||
if (t.headerBorderShape.Visible() || t.hasHeaderLinesWindow()) &&
|
||||
if (t.headerBorderShape.Visible() || needHeaderLinesWindow) &&
|
||||
(t.headerWindow == nil && primaryHeaderLines > 0 || t.headerWindow != nil && primaryHeaderLines != t.headerWindow.Height()) ||
|
||||
needHeaderLinesWindow && (t.headerLinesWindow == nil || t.headerLinesWindow != nil && t.headerLines != t.headerLinesWindow.Height()) ||
|
||||
!needHeaderLinesWindow && t.headerLinesWindow != nil {
|
||||
@@ -2720,6 +2847,41 @@ func (t *Terminal) printHeader() {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) printFooter() {
|
||||
if len(t.footer) == 0 {
|
||||
return
|
||||
}
|
||||
indentSize := t.headerIndent(t.footerBorderShape)
|
||||
indent := strings.Repeat(" ", indentSize)
|
||||
max := util.Min(len(t.footer), t.footerWindow.Height())
|
||||
|
||||
// Wrapping is not supported for footer
|
||||
wrap := t.wrap
|
||||
t.wrap = false
|
||||
t.withWindow(t.footerWindow, func() {
|
||||
var state *ansiState
|
||||
for idx, lineStr := range t.footer[:max] {
|
||||
line := idx
|
||||
if t.layout != layoutReverse {
|
||||
line = max - idx - 1
|
||||
}
|
||||
trimmed, colors, newState := extractColor(lineStr, state, nil)
|
||||
state = newState
|
||||
item := &Item{
|
||||
text: util.ToChars([]byte(trimmed)),
|
||||
colors: colors}
|
||||
|
||||
t.printHighlighted(Result{item: item},
|
||||
tui.ColFooter, tui.ColFooter, false, false, line, line, true,
|
||||
func(markerClass) int {
|
||||
t.footerWindow.Print(indent)
|
||||
return indentSize
|
||||
}, nil)
|
||||
}
|
||||
})
|
||||
t.wrap = wrap
|
||||
}
|
||||
|
||||
func (t *Terminal) headerIndent(borderShape tui.BorderShape) int {
|
||||
indentSize := t.pointerLen + t.markerLen
|
||||
if t.listBorderShape.HasLeft() {
|
||||
@@ -2792,7 +2954,7 @@ func (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShap
|
||||
}
|
||||
|
||||
func (t *Terminal) canSpanMultiLines() bool {
|
||||
return t.multiLine || t.wrap || t.gap > 0
|
||||
return (t.multiLine || t.wrap || t.gap > 0) && t.inListWindow()
|
||||
}
|
||||
|
||||
func (t *Terminal) renderBar(line int, barRange [2]int) {
|
||||
@@ -3767,6 +3929,7 @@ func (t *Terminal) printAll() {
|
||||
t.printPrompt()
|
||||
t.printInfo()
|
||||
t.printHeader()
|
||||
t.printFooter()
|
||||
t.printPreview()
|
||||
}
|
||||
|
||||
@@ -4515,6 +4678,7 @@ func (t *Terminal) Loop() error {
|
||||
t.reqBox.Set(reqPrompt, nil)
|
||||
t.reqBox.Set(reqInfo, nil)
|
||||
t.reqBox.Set(reqHeader, nil)
|
||||
t.reqBox.Set(reqFooter, nil)
|
||||
if t.initDelay > 0 {
|
||||
go func() {
|
||||
timer := time.NewTimer(t.initDelay)
|
||||
@@ -4797,6 +4961,10 @@ func (t *Terminal) Loop() error {
|
||||
if !t.resizeIfNeeded() {
|
||||
t.printHeader()
|
||||
}
|
||||
case reqFooter:
|
||||
if !t.resizeIfNeeded() {
|
||||
t.printFooter()
|
||||
}
|
||||
case reqActivate:
|
||||
t.suppress = false
|
||||
if t.hasPreviewer() {
|
||||
@@ -4806,6 +4974,8 @@ func (t *Terminal) Loop() error {
|
||||
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 reqRedrawFooterLabel:
|
||||
t.printLabel(t.footerBorder, t.footerLabel, t.footerLabelOpts, t.footerLabelLen, t.footerBorderShape, true)
|
||||
case reqRedrawListLabel:
|
||||
t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, true)
|
||||
case reqRedrawBorderLabel:
|
||||
@@ -4996,7 +5166,7 @@ func (t *Terminal) Loop() error {
|
||||
}
|
||||
updatePreviewWindow := func(forcePreview bool) {
|
||||
t.resizeWindows(forcePreview, false)
|
||||
req(reqPrompt, reqList, reqInfo, reqHeader)
|
||||
req(reqPrompt, reqList, reqInfo, reqHeader, reqFooter)
|
||||
}
|
||||
toggle := func() bool {
|
||||
current := t.currentItem()
|
||||
@@ -5271,6 +5441,16 @@ func (t *Terminal) Loop() error {
|
||||
} else {
|
||||
req(reqHeader)
|
||||
}
|
||||
case actChangeFooter, actTransformFooter:
|
||||
footer := a.a
|
||||
if a.t == actTransformFooter {
|
||||
footer = t.captureLines(a.a)
|
||||
}
|
||||
if t.changeFooter(footer) {
|
||||
req(reqFullRedraw)
|
||||
} else {
|
||||
req(reqFooter)
|
||||
}
|
||||
case actChangeHeaderLabel, actTransformHeaderLabel:
|
||||
label := a.a
|
||||
if a.t == actTransformHeaderLabel {
|
||||
@@ -5279,6 +5459,14 @@ func (t *Terminal) Loop() error {
|
||||
t.headerLabelOpts.label = label
|
||||
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(label, &tui.ColHeaderLabel, false)
|
||||
req(reqRedrawHeaderLabel)
|
||||
case actChangeFooterLabel, actTransformFooterLabel:
|
||||
label := a.a
|
||||
if a.t == actTransformFooterLabel {
|
||||
label = t.captureLine(a.a)
|
||||
}
|
||||
t.footerLabelOpts.label = label
|
||||
t.footerLabel, t.footerLabelLen = t.ansiLabelPrinter(label, &tui.ColFooterLabel, false)
|
||||
req(reqRedrawFooterLabel)
|
||||
case actChangeInputLabel, actTransformInputLabel:
|
||||
label := a.a
|
||||
if a.t == actTransformInputLabel {
|
||||
|
@@ -11,10 +11,14 @@ func runTmux(args []string, opts *Options) (int, error) {
|
||||
// Prepare arguments
|
||||
fzf, rest := args[0], args[1:]
|
||||
args = []string{"--bind=ctrl-z:ignore"}
|
||||
if !opts.Tmux.border && opts.BorderShape == tui.BorderUndefined {
|
||||
if !opts.Tmux.border && (opts.BorderShape == tui.BorderUndefined || opts.BorderShape == tui.BorderLine) {
|
||||
// We append --border option at the end, because `--style=full:STYLE`
|
||||
// may have changed the default border style.
|
||||
rest = append(rest, "--border")
|
||||
if tui.DefaultBorderShape == tui.BorderRounded {
|
||||
rest = append(rest, "--border=rounded")
|
||||
} else {
|
||||
rest = append(rest, "--border=sharp")
|
||||
}
|
||||
}
|
||||
if opts.Tmux.border && opts.Margin == defaultMargin() {
|
||||
args = append(args, "--margin=0,1")
|
||||
|
@@ -829,11 +829,14 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, wind
|
||||
case WindowHeader:
|
||||
w.fg = r.theme.Header.Color
|
||||
w.bg = r.theme.HeaderBg.Color
|
||||
case WindowFooter:
|
||||
w.fg = r.theme.Footer.Color
|
||||
w.bg = r.theme.FooterBg.Color
|
||||
case WindowPreview:
|
||||
w.fg = r.theme.PreviewFg.Color
|
||||
w.bg = r.theme.PreviewBg.Color
|
||||
}
|
||||
if erase && !w.bg.IsDefault() && w.border.shape != BorderNone {
|
||||
if erase && !w.bg.IsDefault() && w.border.shape != BorderNone && w.height > 0 {
|
||||
// fzf --color bg:blue --border --padding 1,2
|
||||
w.Erase()
|
||||
}
|
||||
@@ -889,6 +892,8 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
||||
color = ColInputBorder
|
||||
case WindowHeader:
|
||||
color = ColHeaderBorder
|
||||
case WindowFooter:
|
||||
color = ColFooterBorder
|
||||
case WindowPreview:
|
||||
color = ColPreviewBorder
|
||||
}
|
||||
@@ -914,6 +919,8 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
|
||||
color = ColInputBorder
|
||||
case WindowHeader:
|
||||
color = ColHeaderBorder
|
||||
case WindowFooter:
|
||||
color = ColFooterBorder
|
||||
case WindowPreview:
|
||||
color = ColPreviewBorder
|
||||
}
|
||||
@@ -941,6 +948,8 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
||||
color = ColInputBorder
|
||||
case WindowHeader:
|
||||
color = ColHeaderBorder
|
||||
case WindowFooter:
|
||||
color = ColFooterBorder
|
||||
case WindowPreview:
|
||||
color = ColPreviewBorder
|
||||
}
|
||||
|
@@ -600,6 +600,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
||||
normal = ColNormal
|
||||
case WindowHeader:
|
||||
normal = ColHeader
|
||||
case WindowFooter:
|
||||
normal = ColFooter
|
||||
case WindowInput:
|
||||
normal = ColInput
|
||||
case WindowPreview:
|
||||
@@ -865,6 +867,8 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
||||
style = ColListBorder.style()
|
||||
case WindowHeader:
|
||||
style = ColHeaderBorder.style()
|
||||
case WindowFooter:
|
||||
style = ColFooterBorder.style()
|
||||
case WindowInput:
|
||||
style = ColInputBorder.style()
|
||||
case WindowPreview:
|
||||
|
@@ -359,6 +359,10 @@ type ColorTheme struct {
|
||||
HeaderBg ColorAttr
|
||||
HeaderBorder ColorAttr
|
||||
HeaderLabel ColorAttr
|
||||
Footer ColorAttr
|
||||
FooterBg ColorAttr
|
||||
FooterBorder ColorAttr
|
||||
FooterLabel ColorAttr
|
||||
Separator ColorAttr
|
||||
Scrollbar ColorAttr
|
||||
Border ColorAttr
|
||||
@@ -612,6 +616,7 @@ const (
|
||||
WindowPreview
|
||||
WindowInput
|
||||
WindowHeader
|
||||
WindowFooter
|
||||
)
|
||||
|
||||
type Renderer interface {
|
||||
@@ -720,6 +725,9 @@ var (
|
||||
ColHeader ColorPair
|
||||
ColHeaderBorder ColorPair
|
||||
ColHeaderLabel ColorPair
|
||||
ColFooter ColorPair
|
||||
ColFooterBorder ColorPair
|
||||
ColFooterLabel ColorPair
|
||||
ColSeparator ColorPair
|
||||
ColScrollbar ColorPair
|
||||
ColGapLine ColorPair
|
||||
@@ -758,6 +766,7 @@ func EmptyTheme() *ColorTheme {
|
||||
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
||||
Marker: ColorAttr{colUndefined, AttrUndefined},
|
||||
Header: ColorAttr{colUndefined, AttrUndefined},
|
||||
Footer: ColorAttr{colUndefined, AttrUndefined},
|
||||
Border: ColorAttr{colUndefined, AttrUndefined},
|
||||
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
@@ -778,6 +787,9 @@ func EmptyTheme() *ColorTheme {
|
||||
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||
}
|
||||
@@ -825,6 +837,9 @@ func NoColorTheme() *ColorTheme {
|
||||
HeaderBg: ColorAttr{colDefault, AttrUndefined},
|
||||
HeaderBorder: ColorAttr{colDefault, AttrUndefined},
|
||||
HeaderLabel: ColorAttr{colDefault, AttrUndefined},
|
||||
FooterBg: ColorAttr{colDefault, AttrUndefined},
|
||||
FooterBorder: ColorAttr{colDefault, AttrUndefined},
|
||||
FooterLabel: ColorAttr{colDefault, AttrUndefined},
|
||||
GapLine: ColorAttr{colDefault, AttrUndefined},
|
||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||
}
|
||||
@@ -852,6 +867,7 @@ func init() {
|
||||
Cursor: ColorAttr{colRed, AttrUndefined},
|
||||
Marker: ColorAttr{colMagenta, AttrUndefined},
|
||||
Header: ColorAttr{colCyan, AttrUndefined},
|
||||
Footer: ColorAttr{colCyan, AttrUndefined},
|
||||
Border: ColorAttr{colBlack, AttrUndefined},
|
||||
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
||||
Ghost: ColorAttr{colUndefined, Dim},
|
||||
@@ -869,6 +885,12 @@ func init() {
|
||||
InputBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
InputBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||
InputLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||
}
|
||||
@@ -893,6 +915,7 @@ func init() {
|
||||
Cursor: ColorAttr{161, AttrUndefined},
|
||||
Marker: ColorAttr{168, AttrUndefined},
|
||||
Header: ColorAttr{109, AttrUndefined},
|
||||
Footer: ColorAttr{109, AttrUndefined},
|
||||
Border: ColorAttr{59, AttrUndefined},
|
||||
BorderLabel: ColorAttr{145, AttrUndefined},
|
||||
Ghost: ColorAttr{colUndefined, Dim},
|
||||
@@ -910,6 +933,12 @@ func init() {
|
||||
InputBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
InputBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||
InputLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||
}
|
||||
@@ -934,6 +963,7 @@ func init() {
|
||||
Cursor: ColorAttr{161, AttrUndefined},
|
||||
Marker: ColorAttr{168, AttrUndefined},
|
||||
Header: ColorAttr{31, AttrUndefined},
|
||||
Footer: ColorAttr{31, AttrUndefined},
|
||||
Border: ColorAttr{145, AttrUndefined},
|
||||
BorderLabel: ColorAttr{59, AttrUndefined},
|
||||
Ghost: ColorAttr{colUndefined, Dim},
|
||||
@@ -954,6 +984,9 @@ func init() {
|
||||
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||
}
|
||||
@@ -989,6 +1022,7 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
|
||||
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
||||
theme.Marker = o(baseTheme.Marker, theme.Marker)
|
||||
theme.Header = o(baseTheme.Header, theme.Header)
|
||||
theme.Footer = o(baseTheme.Footer, theme.Footer)
|
||||
theme.Border = o(baseTheme.Border, theme.Border)
|
||||
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
|
||||
|
||||
@@ -1042,6 +1076,10 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
|
||||
theme.HeaderBorder = o(theme.Border, theme.HeaderBorder)
|
||||
theme.HeaderLabel = o(theme.BorderLabel, theme.HeaderLabel)
|
||||
|
||||
theme.FooterBg = o(theme.Bg, theme.FooterBg)
|
||||
theme.FooterBorder = o(theme.Border, theme.FooterBorder)
|
||||
theme.FooterLabel = o(theme.BorderLabel, theme.FooterLabel)
|
||||
|
||||
initPalette(theme)
|
||||
}
|
||||
|
||||
@@ -1095,6 +1133,9 @@ func initPalette(theme *ColorTheme) {
|
||||
ColHeader = pair(theme.Header, theme.HeaderBg)
|
||||
ColHeaderBorder = pair(theme.HeaderBorder, theme.HeaderBg)
|
||||
ColHeaderLabel = pair(theme.HeaderLabel, theme.HeaderBg)
|
||||
ColFooter = pair(theme.Footer, theme.FooterBg)
|
||||
ColFooterBorder = pair(theme.FooterBorder, theme.FooterBg)
|
||||
ColFooterLabel = pair(theme.FooterLabel, theme.FooterBg)
|
||||
}
|
||||
|
||||
func runeWidth(r rune) int {
|
||||
|
@@ -97,24 +97,12 @@ func Min32(first int32, second int32) int32 {
|
||||
|
||||
// Constrain32 limits the given 32-bit integer with the upper and lower bounds
|
||||
func Constrain32(val int32, min int32, max int32) int32 {
|
||||
if val < min {
|
||||
return min
|
||||
}
|
||||
if val > max {
|
||||
return max
|
||||
}
|
||||
return val
|
||||
return Max32(Min32(val, max), min)
|
||||
}
|
||||
|
||||
// Constrain limits the given integer with the upper and lower bounds
|
||||
func Constrain(val int, min int, max int) int {
|
||||
if val < min {
|
||||
return min
|
||||
}
|
||||
if val > max {
|
||||
return max
|
||||
}
|
||||
return val
|
||||
return Max(Min(val, max), min)
|
||||
}
|
||||
|
||||
func AsUint16(val int) uint16 {
|
||||
|
@@ -979,6 +979,126 @@ class TestLayout < TestInteractive
|
||||
end
|
||||
end
|
||||
|
||||
def test_layout_default_with_footer
|
||||
prefix = %[
|
||||
seq 3 | #{FZF} --no-list-border --height ~100% \
|
||||
--border sharp --footer "$(seq 201 202)" --footer-label FOOT --footer-label-pos 3 \
|
||||
--header-label HEAD --header-label-pos 3:bottom \
|
||||
--bind 'space:transform-footer-label(echo foot)+change-header-label(head)'
|
||||
].strip + ' '
|
||||
suffixes = [
|
||||
%(),
|
||||
%[--header "$(seq 101 102)"],
|
||||
%[--header "$(seq 101 102)" --header-first],
|
||||
%[--header "$(seq 101 102)" --header-lines 2],
|
||||
%[--header "$(seq 101 102)" --header-lines 2 --header-first],
|
||||
%[--header "$(seq 101 102)" --header-border sharp],
|
||||
%[--header "$(seq 101 102)" --header-border sharp --header-first],
|
||||
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2],
|
||||
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp],
|
||||
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --input-border sharp],
|
||||
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --no-input],
|
||||
%[--header "$(seq 101 102)" --footer-border sharp --input-border line],
|
||||
%[--header "$(seq 101 102)" --style full:sharp --header-first]
|
||||
]
|
||||
output = <<~BLOCK
|
||||
┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌───────── ┌──────── ┌──────── ┌─────────
|
||||
│ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ ┌─FOOT─ │ ┌─FOOT──
|
||||
│ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ │ 201 │ │ 201
|
||||
│ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT── │ ──FOOT─ │ │ 202 │ │ 202
|
||||
│ 3 │ 3 │ 3 │ > 3 │ > 3 │ 3 │ 3 │ > 3 │ > 3 │ > 3 │ > 3 │ └────── │ └───────
|
||||
│ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ ┌────── │ ┌────── │ ┌─────── │ ┌────── │ 3 │ ┌───────
|
||||
│ > 1 │ > 1 │ > 1 │ 1 │ 1 │ > 1 │ > 1 │ │ 2 │ │ 2 │ │ 2 │ │ 2 │ 2 │ │ 3
|
||||
│ 3/3 ─ │ 101 │ 3/3 ─ │ 101 │ 1/1 ─ │ ┌────── │ 3/3 ─ │ │ 1 │ │ 1 │ │ 1 │ │ 1 │ > 1 │ │ 2
|
||||
│ > │ 102 │ > │ 102 │ > │ │ 101 │ > │ │ 101 │ └────── │ └─────── │ └────── │ 101 │ │ > 1
|
||||
└──────── │ 3/3 ─ │ 101 │ 1/1 ─ │ 101 │ │ 102 │ ┌────── │ │ 102 │ ┌────── │ ┌─────── │ ┌────── │ 102 │ └───────
|
||||
│ > │ 102 │ > │ 102 │ └─HEAD─ │ │ 101 │ └─HEAD─ │ │ 101 │ │ 1/1 │ │ 101 │ ─────── │ ┌───────
|
||||
└──────── └──────── └──────── └──────── │ 3/3 ─ │ │ 102 │ 1/1 ─ │ │ 102 │ │ > │ │ 102 │ 3/3 │ │ >
|
||||
│ > │ └─HEAD─ │ > │ └─HEAD─ │ └─────── │ └─HEAD─ │ > │ └───────
|
||||
└──────── └──────── └──────── │ 1/1 ─ │ ┌─────── └──────── └──────── │ ┌───────
|
||||
│ > │ │ 101 │ │ 101
|
||||
└──────── │ │ 102 │ │ 102
|
||||
│ └─HEAD── │ └─HEAD──
|
||||
└───────── └─────────
|
||||
BLOCK
|
||||
|
||||
expects = []
|
||||
output.each_line.first.scan(/\S+/) do
|
||||
offset = Regexp.last_match.offset(0)
|
||||
expects << output.lines.filter_map { it[offset[0]...offset[1]]&.strip }.take_while { !it.empty? }.join("\n")
|
||||
end
|
||||
|
||||
suffixes.zip(expects).each do |suffix, block|
|
||||
tmux.send_keys(prefix + suffix, :Enter)
|
||||
tmux.until { assert_block(block, it) }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { assert_block(block.downcase, it) }
|
||||
|
||||
teardown
|
||||
setup
|
||||
end
|
||||
end
|
||||
|
||||
def test_layout_reverse_list_with_footer
|
||||
prefix = %[
|
||||
seq 3 | #{FZF} --layout reverse-list --no-list-border --height ~100% \
|
||||
--border sharp --footer "$(seq 201 202)" --footer-label FOOT --footer-label-pos 3 \
|
||||
--header-label HEAD --header-label-pos 3:bottom \
|
||||
--bind 'space:transform-footer-label(echo foot)+change-header-label(head)'
|
||||
].strip + ' '
|
||||
suffixes = [
|
||||
%(),
|
||||
%[--header "$(seq 101 102)"],
|
||||
%[--header "$(seq 101 102)" --header-first],
|
||||
%[--header "$(seq 101 102)" --header-lines 2],
|
||||
%[--header "$(seq 101 102)" --header-lines 2 --header-first],
|
||||
%[--header "$(seq 101 102)" --header-border sharp],
|
||||
%[--header "$(seq 101 102)" --header-border sharp --header-first],
|
||||
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2],
|
||||
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp],
|
||||
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --input-border sharp],
|
||||
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --no-input],
|
||||
%[--header "$(seq 101 102)" --footer-border sharp --input-border line],
|
||||
%[--header "$(seq 101 102)" --style full:sharp --header-first]
|
||||
]
|
||||
output = <<~BLOCK
|
||||
┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌───────── ┌──────── ┌──────── ┌─────────
|
||||
│ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ ┌─FOOT─ │ ┌─FOOT──
|
||||
│ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ │ 201 │ │ 201
|
||||
│ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT── │ ──FOOT─ │ │ 202 │ │ 202
|
||||
│ > 1 │ > 1 │ > 1 │ 1 │ 1 │ > 1 │ > 1 │ 1 │ ┌────── │ ┌─────── │ ┌────── │ └────── │ └───────
|
||||
│ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ │ 1 │ │ 1 │ │ 1 │ > 1 │ ┌───────
|
||||
│ 3 │ 3 │ 3 │ > 3 │ > 3 │ 3 │ 3 │ > 3 │ │ 2 │ │ 2 │ │ 2 │ 2 │ │ > 1
|
||||
│ 3/3 ─ │ 101 │ 3/3 ─ │ 101 │ 1/1 ─ │ ┌────── │ 3/3 ─ │ ┌────── │ └────── │ └─────── │ └────── │ 3 │ │ 2
|
||||
│ > │ 102 │ > │ 102 │ > │ │ 101 │ > │ │ 101 │ > 3 │ > 3 │ > 3 │ 101 │ │ 3
|
||||
└──────── │ 3/3 ─ │ 101 │ 1/1 ─ │ 101 │ │ 102 │ ┌────── │ │ 102 │ ┌────── │ ┌─────── │ ┌────── │ 102 │ └───────
|
||||
│ > │ 102 │ > │ 102 │ └─HEAD─ │ │ 101 │ └─HEAD─ │ │ 101 │ │ 1/1 │ │ 101 │ ─────── │ ┌───────
|
||||
└──────── └──────── └──────── └──────── │ 3/3 ─ │ │ 102 │ 1/1 ─ │ │ 102 │ │ > │ │ 102 │ 3/3 │ │ >
|
||||
│ > │ └─HEAD─ │ > │ └─HEAD─ │ └─────── │ └─HEAD─ │ > │ └───────
|
||||
└──────── └──────── └──────── │ 1/1 ─ │ ┌─────── └──────── └──────── │ ┌───────
|
||||
│ > │ │ 101 │ │ 101
|
||||
└──────── │ │ 102 │ │ 102
|
||||
│ └─HEAD── │ └─HEAD──
|
||||
└───────── └─────────
|
||||
BLOCK
|
||||
|
||||
expects = []
|
||||
output.each_line.first.scan(/\S+/) do
|
||||
offset = Regexp.last_match.offset(0)
|
||||
expects << output.lines.filter_map { it[offset[0]...offset[1]]&.strip }.take_while { !it.empty? }.join("\n")
|
||||
end
|
||||
|
||||
suffixes.zip(expects).each do |suffix, block|
|
||||
tmux.send_keys(prefix + suffix, :Enter)
|
||||
tmux.until { assert_block(block, it) }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { assert_block(block.downcase, it) }
|
||||
|
||||
teardown
|
||||
setup
|
||||
end
|
||||
end
|
||||
|
||||
def test_change_header_and_label_at_once
|
||||
tmux.send_keys %(seq 10 | #{FZF} --border sharp --header-border sharp --header-label-pos 3 --bind 'focus:change-header(header)+change-header-label(label)'), :Enter
|
||||
block = <<~BLOCK
|
||||
@@ -1033,4 +1153,79 @@ class TestLayout < TestInteractive
|
||||
BLOCK
|
||||
tmux.until { assert_block(block, it) }
|
||||
end
|
||||
|
||||
def test_combinations
|
||||
skip unless ENV['LONGTEST']
|
||||
|
||||
base = [
|
||||
'--pointer=@',
|
||||
'--exact',
|
||||
'--query=123',
|
||||
'--header="$(seq 101 103)"',
|
||||
'--header-lines=3',
|
||||
'--footer "$(seq 201 203)"',
|
||||
'--preview "echo foobar"'
|
||||
]
|
||||
options = [
|
||||
['--separator==', '--no-separator'],
|
||||
['--info=default', '--info=inline', '--info=inline-right'],
|
||||
['--no-input-border', '--input-border'],
|
||||
['--no-header-border', '--header-border=none', '--header-border'],
|
||||
['--no-header-lines-border', '--header-lines-border'],
|
||||
['--no-footer-border', '--footer-border'],
|
||||
['--no-list-border', '--list-border'],
|
||||
['--preview-window=right', '--preview-window=up', '--preview-window=down', '--preview-window=left'],
|
||||
['--header-first', '--no-header-first'],
|
||||
['--layout=default', '--layout=reverse', '--layout=reverse-list']
|
||||
]
|
||||
# Combination of all options
|
||||
combinations = options[0].product(*options.drop(1))
|
||||
combinations.each_with_index do |combination, index|
|
||||
opts = base + combination
|
||||
command = %(seq 1001 2000 | #{FZF} #{opts.join(' ')})
|
||||
puts "# #{index + 1}/#{combinations.length}\n#{command}"
|
||||
tmux.send_keys command, :Enter
|
||||
tmux.until do |lines|
|
||||
layout = combination.find { it.start_with?('--layout=') }.split('=').last
|
||||
header_first = combination.include?('--header-first')
|
||||
|
||||
# Input
|
||||
input = lines.index { it.include?('> 123') }
|
||||
assert(input)
|
||||
|
||||
# Info
|
||||
info = lines.index { it.include?('11/997') }
|
||||
assert(info)
|
||||
|
||||
assert(layout == 'reverse' ? input <= info : input >= info)
|
||||
|
||||
# List
|
||||
item1 = lines.index { it.include?('1230') }
|
||||
item2 = lines.index { it.include?('1231') }
|
||||
assert_equal(item1, layout == 'default' ? item2 + 1 : item2 - 1)
|
||||
|
||||
# Preview
|
||||
assert(lines.any? { it.include?('foobar') })
|
||||
|
||||
# Header
|
||||
header1 = lines.index { it.include?('101') }
|
||||
header2 = lines.index { it.include?('102') }
|
||||
assert_equal(header2, header1 + 1)
|
||||
assert((layout == 'reverse') == header_first ? input > header1 : input < header1)
|
||||
|
||||
# Footer
|
||||
footer1 = lines.index { it.include?('201') }
|
||||
footer2 = lines.index { it.include?('202') }
|
||||
assert_equal(footer2, footer1 + 1)
|
||||
assert(layout == 'reverse' ? footer1 > item2 : footer1 < item2)
|
||||
|
||||
# Header lines
|
||||
hline1 = lines.index { it.include?('1001') }
|
||||
hline2 = lines.index { it.include?('1002') }
|
||||
assert_equal(hline1, layout == 'default' ? hline2 + 1 : hline2 - 1)
|
||||
assert(layout == 'reverse' ? hline1 > header1 : hline1 < header1)
|
||||
end
|
||||
tmux.send_keys :Enter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Reference in New Issue
Block a user