Compare commits

...

2 Commits

Author SHA1 Message Date
Junegunn Choi
e5cfc988ec
Fix RuboCop error 2025-01-02 16:55:56 +09:00
Junegunn Choi
ee3916be17
Border around the input section (prompt + info)
Close #4154
2025-01-02 16:25:00 +09:00
10 changed files with 743 additions and 373 deletions

View File

@ -3,7 +3,7 @@ CHANGELOG
0.58.0 0.58.0
------ ------
- Additional border and label for the list section - Border and label for the list section
- Options - Options
- `--list-border[=STYLE]` - `--list-border[=STYLE]`
- `--list-label=LABEL` - `--list-label=LABEL`
@ -16,6 +16,22 @@ 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)
- Options
- `--input-border[=STYLE]`
- `--input-label=LABEL`
- `--input-label-pos=COL[:bottom]`
- Colors
- `input-fg` (`query`)
- `input-bg`
- `input-border`
- `input-label`
- Actions
- `change-input-label`
- `transform-input-label`
- Added `--preview-border[=STYLE]` as short for `--preview-window=border-[STYLE]`
- Added `toggle-multi-line` action
- Added `toggle-hscroll` action
0.57.0 0.57.0
------ ------

View File

@ -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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf\-tmux 1 "Dec 2024" "fzf 0.57.0" "fzf\-tmux - open fzf in tmux split pane" .TH fzf\-tmux 1 "Jan 2025" "fzf 0.58.0" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf\-tmux - open fzf in tmux split pane fzf\-tmux - open fzf in tmux split pane

View File

@ -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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Dec 2024" "fzf 0.57.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Jan 2025" "fzf 0.58.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@ -557,16 +557,17 @@ color mappings.
\fBselected\-fg \fRSelected line text \fBselected\-fg \fRSelected line text
\fBpreview\-fg \fRPreview window text \fBpreview\-fg \fRPreview window text
\fBbg \fRBackground \fBbg \fRBackground
\fBlist\-bg \fRBackground in the list section \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
\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)
\fBcurrent\-bg (bg+) \fRBackground (current line) \fBcurrent\-bg (bg+) \fRBackground (current line)
\fBgutter \fRGutter on the left \fBgutter \fRGutter on the left
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line) \fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
\fBquery \fRQuery string \fBquery (input\-fg) \fRQuery string
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR) \fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
\fBinfo \fRInfo line (match counters) \fBinfo \fRInfo line (match counters)
\fBborder \fRBorder around the window (\fB\-\-border\fR and \fB\-\-preview\fR) \fBborder \fRBorder around the window (\fB\-\-border\fR and \fB\-\-preview\fR)
@ -575,9 +576,11 @@ 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
\fBlabel \fRBorder label (\fB\-\-border\-label\fR, \fB\-\-list\-label\fR, and \fB\-\-preview\-label\fR) \fBinput\-border \fRBorder around the input section (\fB\-\-input\-border\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)
\fBprompt \fRPrompt \fBprompt \fRPrompt
\fBpointer \fRPointer to the current line \fBpointer \fRPointer to the current line
\fBmarker \fRMulti\-select marker \fBmarker \fRMulti\-select marker
@ -728,6 +731,10 @@ e.g.
.RE .RE
.TP
.BI "\-\-preview\-border" [=STYLE]
Short for \fB\-\-preview\-window=border\-STYLE\fR
.TP .TP
.BI "\-\-preview\-label" [=LABEL] .BI "\-\-preview\-label" [=LABEL]
Label to print on the horizontal border line of the preview window. Label to print on the horizontal border line of the preview window.
@ -1444,6 +1451,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\-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)
\fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0) \fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
@ -1529,6 +1537,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\-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)
\fBtransform\-prompt(...)\fR (transform prompt string using an external command) \fBtransform\-prompt(...)\fR (transform prompt string using an external command)

View File

@ -26,110 +26,112 @@ func _() {
_ = x[actCancel-15] _ = x[actCancel-15]
_ = x[actChangeBorderLabel-16] _ = x[actChangeBorderLabel-16]
_ = x[actChangeListLabel-17] _ = x[actChangeListLabel-17]
_ = x[actChangeHeader-18] _ = x[actChangeInputLabel-18]
_ = x[actChangeMulti-19] _ = x[actChangeHeader-19]
_ = x[actChangePreviewLabel-20] _ = x[actChangeMulti-20]
_ = x[actChangePrompt-21] _ = x[actChangePreviewLabel-21]
_ = x[actChangeQuery-22] _ = x[actChangePrompt-22]
_ = x[actClearScreen-23] _ = x[actChangeQuery-23]
_ = x[actClearQuery-24] _ = x[actClearScreen-24]
_ = x[actClearSelection-25] _ = x[actClearQuery-25]
_ = x[actClose-26] _ = x[actClearSelection-26]
_ = x[actDeleteChar-27] _ = x[actClose-27]
_ = x[actDeleteCharEof-28] _ = x[actDeleteChar-28]
_ = x[actEndOfLine-29] _ = x[actDeleteCharEof-29]
_ = x[actFatal-30] _ = x[actEndOfLine-30]
_ = x[actForwardChar-31] _ = x[actFatal-31]
_ = x[actForwardWord-32] _ = x[actForwardChar-32]
_ = x[actKillLine-33] _ = x[actForwardWord-33]
_ = x[actKillWord-34] _ = x[actKillLine-34]
_ = x[actUnixLineDiscard-35] _ = x[actKillWord-35]
_ = x[actUnixWordRubout-36] _ = x[actUnixLineDiscard-36]
_ = x[actYank-37] _ = x[actUnixWordRubout-37]
_ = x[actBackwardKillWord-38] _ = x[actYank-38]
_ = x[actSelectAll-39] _ = x[actBackwardKillWord-39]
_ = x[actDeselectAll-40] _ = x[actSelectAll-40]
_ = x[actToggle-41] _ = x[actDeselectAll-41]
_ = x[actToggleSearch-42] _ = x[actToggle-42]
_ = x[actToggleAll-43] _ = x[actToggleSearch-43]
_ = x[actToggleDown-44] _ = x[actToggleAll-44]
_ = x[actToggleUp-45] _ = x[actToggleDown-45]
_ = x[actToggleIn-46] _ = x[actToggleUp-46]
_ = x[actToggleOut-47] _ = x[actToggleIn-47]
_ = x[actToggleTrack-48] _ = x[actToggleOut-48]
_ = x[actToggleTrackCurrent-49] _ = x[actToggleTrack-49]
_ = x[actToggleHeader-50] _ = x[actToggleTrackCurrent-50]
_ = x[actToggleWrap-51] _ = x[actToggleHeader-51]
_ = x[actToggleMultiLine-52] _ = x[actToggleWrap-52]
_ = x[actToggleHscroll-53] _ = x[actToggleMultiLine-53]
_ = x[actTrackCurrent-54] _ = x[actToggleHscroll-54]
_ = x[actUntrackCurrent-55] _ = x[actTrackCurrent-55]
_ = x[actDown-56] _ = x[actUntrackCurrent-56]
_ = x[actUp-57] _ = x[actDown-57]
_ = x[actPageUp-58] _ = x[actUp-58]
_ = x[actPageDown-59] _ = x[actPageUp-59]
_ = x[actPosition-60] _ = x[actPageDown-60]
_ = x[actHalfPageUp-61] _ = x[actPosition-61]
_ = x[actHalfPageDown-62] _ = x[actHalfPageUp-62]
_ = x[actOffsetUp-63] _ = x[actHalfPageDown-63]
_ = x[actOffsetDown-64] _ = x[actOffsetUp-64]
_ = x[actOffsetMiddle-65] _ = x[actOffsetDown-65]
_ = x[actJump-66] _ = x[actOffsetMiddle-66]
_ = x[actJumpAccept-67] _ = x[actJump-67]
_ = x[actPrintQuery-68] _ = x[actJumpAccept-68]
_ = x[actRefreshPreview-69] _ = x[actPrintQuery-69]
_ = x[actReplaceQuery-70] _ = x[actRefreshPreview-70]
_ = x[actToggleSort-71] _ = x[actReplaceQuery-71]
_ = x[actShowPreview-72] _ = x[actToggleSort-72]
_ = x[actHidePreview-73] _ = x[actShowPreview-73]
_ = x[actTogglePreview-74] _ = x[actHidePreview-74]
_ = x[actTogglePreviewWrap-75] _ = x[actTogglePreview-75]
_ = x[actTransform-76] _ = x[actTogglePreviewWrap-76]
_ = x[actTransformBorderLabel-77] _ = x[actTransform-77]
_ = x[actTransformListLabel-78] _ = x[actTransformBorderLabel-78]
_ = x[actTransformHeader-79] _ = x[actTransformListLabel-79]
_ = x[actTransformPreviewLabel-80] _ = x[actTransformInputLabel-80]
_ = x[actTransformPrompt-81] _ = x[actTransformHeader-81]
_ = x[actTransformQuery-82] _ = x[actTransformPreviewLabel-82]
_ = x[actPreview-83] _ = x[actTransformPrompt-83]
_ = x[actChangePreview-84] _ = x[actTransformQuery-84]
_ = x[actChangePreviewWindow-85] _ = x[actPreview-85]
_ = x[actPreviewTop-86] _ = x[actChangePreview-86]
_ = x[actPreviewBottom-87] _ = x[actChangePreviewWindow-87]
_ = x[actPreviewUp-88] _ = x[actPreviewTop-88]
_ = x[actPreviewDown-89] _ = x[actPreviewBottom-89]
_ = x[actPreviewPageUp-90] _ = x[actPreviewUp-90]
_ = x[actPreviewPageDown-91] _ = x[actPreviewDown-91]
_ = x[actPreviewHalfPageUp-92] _ = x[actPreviewPageUp-92]
_ = x[actPreviewHalfPageDown-93] _ = x[actPreviewPageDown-93]
_ = x[actPrevHistory-94] _ = x[actPreviewHalfPageUp-94]
_ = x[actPrevSelected-95] _ = x[actPreviewHalfPageDown-95]
_ = x[actPrint-96] _ = x[actPrevHistory-96]
_ = x[actPut-97] _ = x[actPrevSelected-97]
_ = x[actNextHistory-98] _ = x[actPrint-98]
_ = x[actNextSelected-99] _ = x[actPut-99]
_ = x[actExecute-100] _ = x[actNextHistory-100]
_ = x[actExecuteSilent-101] _ = x[actNextSelected-101]
_ = x[actExecuteMulti-102] _ = x[actExecute-102]
_ = x[actSigStop-103] _ = x[actExecuteSilent-103]
_ = x[actFirst-104] _ = x[actExecuteMulti-104]
_ = x[actLast-105] _ = x[actSigStop-105]
_ = x[actReload-106] _ = x[actFirst-106]
_ = x[actReloadSync-107] _ = x[actLast-107]
_ = x[actDisableSearch-108] _ = x[actReload-108]
_ = x[actEnableSearch-109] _ = x[actReloadSync-109]
_ = x[actSelect-110] _ = x[actDisableSearch-110]
_ = x[actDeselect-111] _ = x[actEnableSearch-111]
_ = x[actUnbind-112] _ = x[actSelect-112]
_ = x[actRebind-113] _ = x[actDeselect-113]
_ = x[actBecome-114] _ = x[actUnbind-114]
_ = x[actShowHeader-115] _ = x[actRebind-115]
_ = x[actHideHeader-116] _ = x[actBecome-116]
_ = x[actShowHeader-117]
_ = x[actHideHeader-118]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader" const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 260, 274, 295, 310, 324, 338, 351, 368, 376, 389, 405, 417, 425, 439, 453, 464, 475, 493, 510, 517, 536, 548, 562, 571, 586, 598, 611, 622, 633, 645, 659, 680, 695, 708, 726, 742, 757, 774, 781, 786, 795, 806, 817, 830, 845, 856, 869, 884, 891, 904, 917, 934, 949, 962, 976, 990, 1006, 1026, 1038, 1061, 1082, 1100, 1124, 1142, 1159, 1169, 1185, 1207, 1220, 1236, 1248, 1262, 1278, 1296, 1316, 1338, 1352, 1367, 1375, 1381, 1395, 1410, 1420, 1436, 1451, 1461, 1469, 1476, 1485, 1498, 1514, 1529, 1538, 1549, 1558, 1567, 1576, 1589, 1602} 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}
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

@ -27,151 +27,152 @@ Author: Junegunn Choi <junegunn.c@gmail.com>
Usage: fzf [options] Usage: fzf [options]
Search Search
-x, --extended Extended-search mode -x, --extended Extended-search mode
(enabled by default; +x or --no-extended to disable) (enabled by default; +x or --no-extended to disable)
-e, --exact Enable Exact-match -e, --exact Enable Exact-match
-i, --ignore-case Case-insensitive match (default: smart-case match) -i, --ignore-case Case-insensitive match (default: smart-case match)
+i, --no-ignore-case Case-sensitive match +i, --no-ignore-case Case-sensitive match
--scheme=SCHEME Scoring scheme [default|path|history] --scheme=SCHEME Scoring scheme [default|path|history]
--literal Do not normalize latin script letters before matching --literal Do not normalize latin script letters before matching
-n, --nth=N[,..] Comma-separated list of field index expressions -n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero for limiting search scope. Each can be a non-zero
integer or a range expression ([BEGIN]..[END]). integer or a range expression ([BEGIN]..[END]).
--with-nth=N[,..] Transform the presentation of each line using --with-nth=N[,..] Transform the presentation of each line using
field index expressions field index expressions
-d, --delimiter=STR Field delimiter regex (default: AWK-style) -d, --delimiter=STR Field delimiter regex (default: AWK-style)
+s, --no-sort Do not sort the result +s, --no-sort Do not sort the result
--tail=NUM Maximum number of items to keep in memory --tail=NUM Maximum number of items to keep in memory
--track Track the current selection when the result is updated --track Track the current selection when the result is updated
--tac Reverse the order of the input --tac Reverse the order of the input
--disabled Do not perform search --disabled Do not perform search
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply --tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
when the scores are tied [length|chunk|begin|end|index] when the scores are tied [length|chunk|begin|end|index]
(default: length) (default: length)
Interface Interface
-m, --multi[=MAX] Enable multi-select with tab/shift-tab -m, --multi[=MAX] Enable multi-select with tab/shift-tab
--no-mouse Disable mouse --no-mouse Disable mouse
--bind=KEYBINDS Custom key bindings. Refer to the man page. --bind=KEYBINDS Custom key bindings. Refer to the man page.
--cycle Enable cyclic scroll --cycle Enable cyclic scroll
--wrap Enable line wrap --wrap Enable line wrap
--wrap-sign=STR Indicator for wrapped lines --wrap-sign=STR Indicator for wrapped lines
--no-multi-line Disable multi-line display of items when using --read0 --no-multi-line Disable multi-line display of items when using --read0
--gap[=N] Render empty lines between each item --gap[=N] Render empty lines between each item
--keep-right Keep the right end of the line visible on overflow --keep-right Keep the right end of the line visible on overflow
--scroll-off=LINES Number of screen lines to keep above or below when --scroll-off=LINES Number of screen lines to keep above or below when
scrolling to the top or to the bottom (default: 0) scrolling to the top or to the bottom (default: 0)
--no-hscroll Disable horizontal scroll --no-hscroll Disable horizontal scroll
--hscroll-off=COLS Number of screen columns to keep to the right of the --hscroll-off=COLS Number of screen columns to keep to the right of the
highlighted substring (default: 10) highlighted substring (default: 10)
--filepath-word Make word-wise movements respect path separators --filepath-word Make word-wise movements respect path separators
--jump-labels=CHARS Label characters for jump mode --jump-labels=CHARS Label characters for jump mode
Layout Layout
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given --height=[~]HEIGHT[%] Display fzf window below the cursor with the given
height instead of using fullscreen. height instead of using fullscreen.
A negative value is calculated as the terminal height A negative value is calculated as the terminal height
minus the given value. minus the given value.
If prefixed with '~', fzf will determine the height If prefixed with '~', fzf will determine the height
according to the input size. according to the input size.
--min-height=HEIGHT Minimum height when --height is given in percent --min-height=HEIGHT Minimum height when --height is given in percent
(default: 10) (default: 10)
--tmux[=OPTS] Start fzf in a tmux popup (requires tmux 3.3+) --tmux[=OPTS] Start fzf in a tmux popup (requires tmux 3.3+)
[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]] [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
(default: center,50%) (default: center,50%)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list] --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border[=STYLE] Draw border around the finder --border[=STYLE] Draw border around the finder
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical| [rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
top|bottom|left|right|none] (default: rounded) top|bottom|left|right|none] (default: rounded)
--border-label=LABEL Label to print on the border --border-label=LABEL Label to print on the border
--border-label-pos=COL Position of the border label --border-label-pos=COL Position of the border label
[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)
--list-border[=STYLE] Draw border around the list section --list-border[=STYLE] Draw border around the list section
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical| [rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
top|bottom|left|right|none] (default: none) top|bottom|left|right|none] (default: none)
--list-label=LABEL Label to print on the list border --list-label=LABEL Label to print on the list border
--list-label-pos=COL Position of the list label --list-label-pos=COL Position of the list label
[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)
--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
[default|right|hidden|inline[-right][:PREFIX]] [default|right|hidden|inline[-right][:PREFIX]]
--info-command=COMMAND Command to generate info line --info-command=COMMAND Command to generate info line
--separator=STR String to form horizontal separator on info line --separator=STR String to form horizontal separator on info line
--no-separator Hide info line separator --no-separator Hide info line separator
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window) --scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
--no-scrollbar Hide scrollbar --no-scrollbar Hide scrollbar
--prompt=STR Input prompt (default: '> ') --prompt=STR Input prompt (default: '> ')
--pointer=STR Pointer to the current line (default: '▌' or '>') --pointer=STR Pointer to the current line (default: '▌' or '>')
--marker=STR Multi-select marker (default: '┃' or '>') --marker=STR Multi-select marker (default: '┃' or '>')
--marker-multi-line=STR Multi-select marker for multi-line entries; --marker-multi-line=STR Multi-select marker for multi-line entries;
3 elements for top, middle, and bottom (default: '') 3 elements for top, middle, and bottom (default: '')
--header=STR String to print as header --header=STR String to print as header
--header-lines=N The first N lines of the input are treated as header --header-lines=N The first N lines of the input are treated as header
--header-first Print header before the prompt line --header-first Print header before the prompt line
--ellipsis=STR Ellipsis to show when line is truncated (default: '··') --ellipsis=STR Ellipsis to show when line is truncated (default: '··')
Display Display
--ansi Enable processing of ANSI color codes --ansi Enable processing of ANSI color codes
--tabstop=SPACES Number of spaces for a tab character (default: 8) --tabstop=SPACES Number of spaces for a tab character (default: 8)
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors --color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
--highlight-line Highlight the whole current line --highlight-line Highlight the whole current line
--no-bold Do not use bold text --no-bold Do not use bold text
History History
--history=FILE History file --history=FILE History file
--history-size=N Maximum number of history entries (default: 1000) --history-size=N Maximum number of history entries (default: 1000)
Preview Preview
--preview=COMMAND Command to preview highlighted line ({}) --preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%) --preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][,SIZE[%]] [up|down|left|right][,SIZE[%]]
[,[no]wrap][,[no]cycle][,[no]follow][,[no]info] [,[no]wrap][,[no]cycle][,[no]follow][,[no]info]
[,[no]hidden][,border-BORDER_OPT] [,[no]hidden][,border-BORDER_OPT]
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES] [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)] [,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
--preview-border[=STYLE] Short for --preview-window=border-STYLE
--preview-label=LABEL --preview-label=LABEL
--preview-label-pos=N Same as --border-label and --border-label-pos, --preview-label-pos=N Same as --border-label and --border-label-pos,
but for preview window but for preview window
Scripting Scripting
-q, --query=STR Start the finder with the given query -q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match -1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match -0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder. -f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line --print-query Print query as the first line
--expect=KEYS Comma-separated list of keys to complete fzf --expect=KEYS Comma-separated list of keys to complete fzf
--read0 Read input delimited by ASCII NUL characters --read0 Read input delimited by ASCII NUL characters
--print0 Print output delimited by ASCII NUL characters --print0 Print output delimited by ASCII NUL characters
--sync Synchronous search for multi-staged filtering --sync Synchronous search for multi-staged filtering
--with-shell=STR Shell command and flags to start child processes with --with-shell=STR Shell command and flags to start child processes with
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /) --listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
(To allow remote process execution, use --listen-unsafe) (To allow remote process execution, use --listen-unsafe)
Directory traversal (Only used when $FZF_DEFAULT_COMMAND is not set) Directory traversal (Only used when $FZF_DEFAULT_COMMAND is not set)
--walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden) --walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden)
--walker-root=DIR [...] List of directories to walk (default: .) --walker-root=DIR [...] List of directories to walk (default: .)
--walker-skip=DIRS Comma-separated list of directory names to skip --walker-skip=DIRS Comma-separated list of directory names to skip
(default: .git,node_modules) (default: .git,node_modules)
Shell integration Shell integration
--bash Print script to set up Bash shell integration --bash Print script to set up Bash shell integration
--zsh Print script to set up Zsh shell integration --zsh Print script to set up Zsh shell integration
--fish Print script to set up Fish shell integration --fish Print script to set up Fish shell integration
Help Help
--version Display version information and exit --version Display version information and exit
--help Show this message --help Show this message
--man Show man page --man Show man page
Environment variables Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Default options (e.g. '--layout=reverse --info=inline') FZF_DEFAULT_OPTS Default options (e.g. '--layout=reverse --info=inline')
FZF_DEFAULT_OPTS_FILE Location of the file to read default options from FZF_DEFAULT_OPTS_FILE Location of the file to read default options from
FZF_API_KEY X-API-Key header for HTTP server (--listen) FZF_API_KEY X-API-Key header for HTTP server (--listen)
` `
@ -444,102 +445,104 @@ type walkerOpts struct {
// Options stores the values of command-line options // Options stores the values of command-line options
type Options struct { type Options struct {
Input chan string Input chan string
Output chan string Output chan string
NoWinpty bool NoWinpty bool
Tmux *tmuxOptions Tmux *tmuxOptions
ForceTtyIn bool ForceTtyIn bool
ProxyScript string ProxyScript string
Bash bool Bash bool
Zsh bool Zsh bool
Fish bool Fish bool
Man bool Man bool
Fuzzy bool Fuzzy bool
FuzzyAlgo algo.Algo FuzzyAlgo algo.Algo
Scheme string Scheme string
Extended bool Extended bool
Phony bool Phony bool
Case Case Case Case
Normalize bool Normalize bool
Nth []Range Nth []Range
WithNth []Range WithNth []Range
Delimiter Delimiter Delimiter Delimiter
Sort int Sort int
Track trackOption Track trackOption
Tac bool Tac bool
Tail int Tail int
Criteria []criterion Criteria []criterion
Multi int Multi int
Ansi bool Ansi bool
Mouse bool Mouse bool
Theme *tui.ColorTheme Theme *tui.ColorTheme
Black bool Black bool
Bold bool Bold bool
Height heightSpec Height heightSpec
MinHeight int MinHeight int
Layout layoutType Layout layoutType
Cycle bool Cycle bool
Wrap bool Wrap bool
WrapSign *string WrapSign *string
MultiLine bool MultiLine bool
CursorLine bool CursorLine bool
KeepRight bool KeepRight bool
Hscroll bool Hscroll bool
HscrollOff int HscrollOff int
ScrollOff int ScrollOff int
FileWord bool FileWord bool
InfoStyle infoStyle InfoStyle infoStyle
InfoPrefix string InfoPrefix string
InfoCommand string InfoCommand string
Separator *string Separator *string
JumpLabels string JumpLabels string
Prompt string Prompt string
Pointer *string Pointer *string
Marker *string Marker *string
MarkerMulti *[3]string MarkerMulti *[3]string
Query string Query string
Select1 bool Select1 bool
Exit0 bool Exit0 bool
Filter *string Filter *string
ToggleSort bool ToggleSort bool
Expect map[tui.Event]string Expect map[tui.Event]string
Keymap map[tui.Event][]*action Keymap map[tui.Event][]*action
Preview previewOpts Preview previewOpts
PrintQuery bool PrintQuery bool
ReadZero bool ReadZero bool
Printer func(string) Printer func(string)
PrintSep string PrintSep string
Sync bool Sync bool
History *History History *History
Header []string Header []string
HeaderLines int HeaderLines int
HeaderFirst bool HeaderFirst bool
Gap int Gap int
Ellipsis *string Ellipsis *string
Scrollbar *string Scrollbar *string
Margin [4]sizeSpec Margin [4]sizeSpec
Padding [4]sizeSpec Padding [4]sizeSpec
BorderShape tui.BorderShape BorderShape tui.BorderShape
ListBorderShape tui.BorderShape ListBorderShape tui.BorderShape
BorderLabel labelOpts InputBorderShape tui.BorderShape
ListLabel labelOpts InputLabel labelOpts
PreviewLabel labelOpts BorderLabel labelOpts
Unicode bool ListLabel labelOpts
Ambidouble bool PreviewLabel labelOpts
Tabstop int Unicode bool
WithShell string Ambidouble bool
ListenAddr *listenAddress Tabstop int
Unsafe bool WithShell string
ClearOnExit bool ListenAddr *listenAddress
WalkerOpts walkerOpts Unsafe bool
WalkerRoot []string ClearOnExit bool
WalkerSkip []string WalkerOpts walkerOpts
Version bool WalkerRoot []string
Help bool WalkerSkip []string
CPUProfile string Version bool
MEMProfile string Help bool
BlockProfile string CPUProfile string
MutexProfile string MEMProfile string
BlockProfile string
MutexProfile string
} }
func filterNonEmpty(input []string) []string { func filterNonEmpty(input []string) []string {
@ -1168,7 +1171,7 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
} }
} }
switch components[0] { switch components[0] {
case "query", "input": case "query", "input", "input-fg":
mergeAttr(&theme.Input) mergeAttr(&theme.Input)
case "disabled": case "disabled":
mergeAttr(&theme.Disabled) mergeAttr(&theme.Disabled)
@ -1220,6 +1223,12 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
mergeAttr(&theme.PreviewLabel) mergeAttr(&theme.PreviewLabel)
case "prompt": case "prompt":
mergeAttr(&theme.Prompt) mergeAttr(&theme.Prompt)
case "input-bg":
mergeAttr(&theme.InputBg)
case "input-border":
mergeAttr(&theme.InputBorder)
case "input-label":
mergeAttr(&theme.InputLabel)
case "spinner": case "spinner":
mergeAttr(&theme.Spinner) mergeAttr(&theme.Spinner)
case "info": case "info":
@ -1283,7 +1292,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)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put|print)`) `(?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)`)
splitRegexp = regexp.MustCompile("[,:]+") splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+") actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
} }
@ -1639,14 +1648,16 @@ func isExecuteAction(str string) actionType {
return actRebind return actRebind
case "preview": case "preview":
return actPreview return actPreview
case "change-header":
return actChangeHeader
case "change-list-label": case "change-list-label":
return actChangeListLabel return actChangeListLabel
case "change-border-label": case "change-border-label":
return actChangeBorderLabel return actChangeBorderLabel
case "change-header":
return actChangeHeader
case "change-preview-label": case "change-preview-label":
return actChangePreviewLabel return actChangePreviewLabel
case "change-input-label":
return actChangeInputLabel
case "change-preview-window": case "change-preview-window":
return actChangePreviewWindow return actChangePreviewWindow
case "change-preview": case "change-preview":
@ -1677,6 +1688,8 @@ func isExecuteAction(str string) actionType {
return actTransformBorderLabel return actTransformBorderLabel
case "transform-preview-label": case "transform-preview-label":
return actTransformPreviewLabel return actTransformPreviewLabel
case "transform-input-label":
return actTransformInputLabel
case "transform-header": case "transform-header":
return actTransformHeader return actTransformHeader
case "transform-prompt": case "transform-prompt":
@ -2453,6 +2466,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parsePreviewWindow(&opts.Preview, str); err != nil { if err := parsePreviewWindow(&opts.Preview, str); err != nil {
return err return err
} }
case "--no-preview-border":
opts.Preview.border = tui.BorderNone
case "--preview-border":
hasArg, arg := optionalNextString(allArgs, &i)
if opts.Preview.border, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--height": case "--height":
str, err := nextString(allArgs, &i, "height required: [~]HEIGHT[%]") str, err := nextString(allArgs, &i, "height required: [~]HEIGHT[%]")
if err != nil { if err != nil {
@ -2500,6 +2520,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-input-border":
opts.InputBorderShape = tui.BorderNone
case "--input-border":
hasArg, arg := optionalNextString(allArgs, &i)
if opts.InputBorderShape, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--no-input-label":
opts.InputLabel.label = ""
case "--input-label":
if opts.InputLabel.label, err = nextString(allArgs, &i, "input label required"); err != nil {
return err
}
case "--input-label-pos":
pos, err := nextString(allArgs, &i, "input label position required (positive or negative integer or 'center')")
if err != nil {
return err
}
if err := parseLabelPosition(&opts.InputLabel, pos); err != nil {
return err
}
case "--no-border-label": case "--no-border-label":
opts.BorderLabel.label = "" opts.BorderLabel.label = ""
case "--border-label": case "--border-label":
@ -2637,6 +2678,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.BorderShape, err = parseBorder(value, false); err != nil { if opts.BorderShape, err = parseBorder(value, false); err != nil {
return err return err
} }
} else if match, value := optString(arg, "--preview-border="); match {
if opts.Preview.border, err = parseBorder(value, false); err != nil {
return err
}
} else if match, value := optString(arg, "--list-border="); match { } else if match, value := optString(arg, "--list-border="); match {
if opts.ListBorderShape, err = parseBorder(value, false); err != nil { if opts.ListBorderShape, err = parseBorder(value, false); err != nil {
return err return err
@ -2647,6 +2692,16 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parseLabelPosition(&opts.ListLabel, value); err != nil { if err := parseLabelPosition(&opts.ListLabel, value); err != nil {
return err return err
} }
} else if match, value := optString(arg, "--input-border="); match {
if opts.InputBorderShape, err = parseBorder(value, false); err != nil {
return err
}
} else if match, value := optString(arg, "--input-label="); match {
opts.InputLabel.label = value
} else if match, value := optString(arg, "--input-label-pos="); match {
if err := parseLabelPosition(&opts.InputLabel, value); err != nil {
return err
}
} else if match, value := optString(arg, "--border-label="); match { } else if match, value := optString(arg, "--border-label="); match {
opts.BorderLabel.label = value opts.BorderLabel.label = value
} else if match, value := optString(arg, "--border-label-pos="); match { } else if match, value := optString(arg, "--border-label-pos="); match {
@ -2922,6 +2977,10 @@ func postProcessOptions(opts *Options) error {
opts.ListBorderShape = tui.BorderNone opts.ListBorderShape = tui.BorderNone
} }
if opts.InputBorderShape == tui.BorderUndefined {
opts.InputBorderShape = tui.BorderNone
}
if opts.Pointer == nil { if opts.Pointer == nil {
defaultPointer := "▌" defaultPointer := "▌"
if !opts.Unicode { if !opts.Unicode {

View File

@ -164,6 +164,7 @@ type eachLine struct {
} }
type itemLine struct { type itemLine struct {
valid bool
firstLine int firstLine int
numLines int numLines int
cy int cy int
@ -179,11 +180,15 @@ type itemLine struct {
} }
func (t *Terminal) markEmptyLine(line int) { func (t *Terminal) markEmptyLine(line int) {
t.prevLines[line] = itemLine{firstLine: line, empty: true} if t.window != t.inputWindow {
t.prevLines[line] = itemLine{valid: true, firstLine: line, empty: true}
}
} }
func (t *Terminal) markOtherLine(line int) { func (t *Terminal) markOtherLine(line int) {
t.prevLines[line] = itemLine{firstLine: line, other: true} if t.window != t.inputWindow {
t.prevLines[line] = itemLine{valid: true, firstLine: line, other: true}
}
} }
type fitpad struct { type fitpad struct {
@ -241,6 +246,9 @@ type Terminal struct {
previewLabel labelPrinter previewLabel labelPrinter
previewLabelLen int previewLabelLen int
previewLabelOpts labelOpts previewLabelOpts labelOpts
inputLabel labelPrinter
inputLabelLen int
inputLabelOpts labelOpts
pointer string pointer string
pointerLen int pointerLen int
pointerEmpty string pointerEmpty string
@ -298,6 +306,7 @@ type Terminal struct {
listenUnsafe bool listenUnsafe bool
borderShape tui.BorderShape borderShape tui.BorderShape
listBorderShape tui.BorderShape listBorderShape tui.BorderShape
inputBorderShape tui.BorderShape
listLabel labelPrinter listLabel labelPrinter
listLabelLen int listLabelLen int
listLabelOpts labelOpts listLabelOpts labelOpts
@ -306,6 +315,8 @@ type Terminal struct {
paused bool paused bool
border tui.Window border tui.Window
window tui.Window window tui.Window
inputWindow tui.Window
inputBorder tui.Window
wborder tui.Window wborder tui.Window
pborder tui.Window pborder tui.Window
pwindow tui.Window pwindow tui.Window
@ -394,6 +405,7 @@ const (
reqReinit reqReinit
reqFullRedraw reqFullRedraw
reqResize reqResize
reqRedrawInputLabel
reqRedrawListLabel reqRedrawListLabel
reqRedrawBorderLabel reqRedrawBorderLabel
reqRedrawPreviewLabel reqRedrawPreviewLabel
@ -436,6 +448,7 @@ const (
actCancel actCancel
actChangeBorderLabel actChangeBorderLabel
actChangeListLabel actChangeListLabel
actChangeInputLabel
actChangeHeader actChangeHeader
actChangeMulti actChangeMulti
actChangePreviewLabel actChangePreviewLabel
@ -497,6 +510,7 @@ const (
actTransform actTransform
actTransformBorderLabel actTransformBorderLabel
actTransformListLabel actTransformListLabel
actTransformInputLabel
actTransformHeader actTransformHeader
actTransformPreviewLabel actTransformPreviewLabel
actTransformPrompt actTransformPrompt
@ -832,6 +846,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
listenUnsafe: opts.Unsafe, listenUnsafe: opts.Unsafe,
borderShape: opts.BorderShape, borderShape: opts.BorderShape,
listBorderShape: opts.ListBorderShape, listBorderShape: opts.ListBorderShape,
inputBorderShape: opts.InputBorderShape,
borderWidth: 1, borderWidth: 1,
listLabel: nil, listLabel: nil,
listLabelOpts: opts.ListLabel, listLabelOpts: opts.ListLabel,
@ -839,6 +854,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
borderLabelOpts: opts.BorderLabel, borderLabelOpts: opts.BorderLabel,
previewLabel: nil, previewLabel: nil,
previewLabelOpts: opts.PreviewLabel, previewLabelOpts: opts.PreviewLabel,
inputLabel: nil,
inputLabelOpts: opts.InputLabel,
cleanExit: opts.ClearOnExit, cleanExit: opts.ClearOnExit,
executor: executor, executor: executor,
paused: opts.Phony, paused: opts.Phony,
@ -902,7 +919,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(opts.ListLabel.label, &tui.ColListLabel, false) t.listLabel, t.listLabelLen = t.ansiLabelPrinter(opts.ListLabel.label, &tui.ColListLabel, false)
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)
if opts.Separator == nil || len(*opts.Separator) > 0 { t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(opts.InputLabel.label, &tui.ColInputLabel, false)
// Disable separator by default if input border is set
if opts.Separator == nil && !t.inputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0 {
bar := "─" bar := "─"
if opts.Separator != nil { if opts.Separator != nil {
bar = *opts.Separator bar = *opts.Separator
@ -1120,6 +1140,17 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
return printFn, length return printFn, length
} }
// Temporarily switch 'window' so that we can use the existing windows with
// a different window
func (t *Terminal) withInputWindow(f func()) {
prevWindow := t.window
if t.inputWindow != nil {
t.window = t.inputWindow
}
f()
t.window = prevWindow
}
func (t *Terminal) parsePrompt(prompt string) (func(), int) { func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState var state *ansiState
prompt = firstLine(prompt) prompt = firstLine(prompt)
@ -1145,11 +1176,13 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
} }
} }
output := func() { output := func() {
line := t.promptLine()
wrap := t.wrap wrap := t.wrap
t.wrap = false t.wrap = false
t.printHighlighted( t.withInputWindow(func() {
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil) line := t.promptLine()
t.printHighlighted(
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil)
})
t.wrap = wrap t.wrap = wrap
} }
_, promptLen := t.processTabs([]rune(trimmed), 0) _, promptLen := t.processTabs([]rune(trimmed), 0)
@ -1566,6 +1599,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.inputWindow != nil {
t.inputWindow = nil
}
if t.inputBorder != nil {
t.inputBorder = nil
}
if t.pborder != nil { if t.pborder != nil {
t.pborder = nil t.pborder = nil
} }
@ -1605,6 +1644,27 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
width -= paddingInt[1] + paddingInt[3] width -= paddingInt[1] + paddingInt[3]
height -= paddingInt[0] + paddingInt[2] height -= paddingInt[0] + paddingInt[2]
// Adjust position and size of the list window if input border is set
inputWindowHeight := 0
inputBorderHeight := 0
shift := 0
shrink := 0
hasInputWindow := t.inputBorderShape.Visible()
if hasInputWindow {
inputWindowHeight = 2
if t.noSeparatorLine() {
inputWindowHeight--
}
inputBorderHeight = borderLines(t.inputBorderShape) + inputWindowHeight
if t.layout == layoutReverse {
shift = inputBorderHeight
shrink = inputBorderHeight
} else {
shift = 0
shrink = inputBorderHeight
}
}
hasListBorder := t.listBorderShape.Visible() hasListBorder := t.listBorderShape.Visible()
innerWidth := width innerWidth := width
innerHeight := height innerHeight := height
@ -1612,7 +1672,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
innerBorderFn := func(top int, left int, width int, height int) { innerBorderFn := func(top int, left int, width int, height int) {
if hasListBorder { if hasListBorder {
t.wborder = t.tui.NewWindow( t.wborder = t.tui.NewWindow(
top, left, width, height, tui.WindowList, tui.MakeBorderStyle(t.listBorderShape, t.unicode), false) top+shift, left, width, height-shrink, tui.WindowList, tui.MakeBorderStyle(t.listBorderShape, t.unicode), false)
} }
} }
if hasListBorder { if hasListBorder {
@ -1697,12 +1757,12 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
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(
innerMarginInt[0]+pheight, innerMarginInt[3], innerWidth, innerHeight-pheight, tui.WindowList, noBorder, true) innerMarginInt[0]+pheight+shift, innerMarginInt[3], innerWidth, innerHeight-pheight-shrink, tui.WindowList, noBorder, true)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight) createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
} else { } else {
innerBorderFn(marginInt[0], marginInt[3], width, height-pheight) innerBorderFn(marginInt[0], marginInt[3], width, height-pheight)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
innerMarginInt[0], innerMarginInt[3], innerWidth, innerHeight-pheight, tui.WindowList, noBorder, true) innerMarginInt[0]+shift, innerMarginInt[3], innerWidth, innerHeight-pheight-shrink, tui.WindowList, noBorder, true)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
} }
case posLeft, posRight: case posLeft, posRight:
@ -1741,7 +1801,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
m = 1 m = 1
} }
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
innerMarginInt[0], innerMarginInt[3]+pwidth+m, innerWidth-pwidth-m, innerHeight, tui.WindowList, noBorder, true) innerMarginInt[0]+shift, innerMarginInt[3]+pwidth+m, innerWidth-pwidth-m, innerHeight-shrink, tui.WindowList, noBorder, true)
// Clear characters on the margin // Clear characters on the margin
// fzf --bind 'space:preview(seq 100)' --preview-window left,1 // fzf --bind 'space:preview(seq 100)' --preview-window left,1
@ -1763,7 +1823,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
} }
innerBorderFn(marginInt[0], marginInt[3], width-pwidth, height) innerBorderFn(marginInt[0], marginInt[3], width-pwidth, height)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
innerMarginInt[0], innerMarginInt[3], innerWidth-pwidth, innerHeight, tui.WindowList, noBorder, true) innerMarginInt[0]+shift, innerMarginInt[3], innerWidth-pwidth, innerHeight-shrink, tui.WindowList, noBorder, true)
x := marginInt[3] + width - pwidth x := marginInt[3] + width - pwidth
createPreviewWindow(marginInt[0], x, pwidth, height) createPreviewWindow(marginInt[0], x, pwidth, height)
} }
@ -1801,16 +1861,56 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
} }
innerBorderFn(marginInt[0], marginInt[3], width, height) innerBorderFn(marginInt[0], marginInt[3], width, height)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
innerMarginInt[0], innerMarginInt[0]+shift,
innerMarginInt[3], innerMarginInt[3],
innerWidth, innerWidth,
innerHeight, tui.WindowList, noBorder, true) innerHeight-shrink, tui.WindowList, noBorder, true)
}
// Set up input border
if hasInputWindow {
w := t.wborder
if t.wborder == nil {
w = t.window
}
if t.layout == layoutReverse {
t.inputBorder = t.tui.NewWindow(
w.Top()-shrink,
w.Left(),
w.Width(),
util.Min(shrink, screenHeight), tui.WindowInput, tui.MakeBorderStyle(t.inputBorderShape, t.unicode), true)
} else {
t.inputBorder = t.tui.NewWindow(
w.Top()+w.Height(),
w.Left(),
w.Width(),
util.Min(shrink, screenHeight), tui.WindowInput, tui.MakeBorderStyle(t.inputBorderShape, t.unicode), true)
}
top := t.inputBorder.Top()
left := t.inputBorder.Left()
if t.inputBorderShape.HasTop() {
top++
}
if t.inputBorderShape.HasLeft() {
left += t.borderWidth + 1
}
width := t.inputBorder.Width() - borderColumns(t.inputBorderShape, t.borderWidth)
if t.inputBorderShape.HasRight() {
width++
}
t.inputWindow = t.tui.NewWindow(
top,
left,
width,
t.inputBorder.Height()-borderLines(t.inputBorderShape),
tui.WindowInput, noBorder, true)
} }
// Print border label // Print border label
t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, false) 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.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)
} }
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) {
@ -1874,7 +1974,11 @@ func (t *Terminal) truncateQuery() {
} }
func (t *Terminal) updatePromptOffset() ([]rune, []rune) { func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
maxWidth := util.Max(1, t.window.Width()-t.promptLen-1) w := t.window
if t.inputWindow != nil {
w = t.inputWindow
}
maxWidth := util.Max(1, w.Width()-t.promptLen-1)
_, overflow := t.trimLeft(t.input[:t.cx], maxWidth) _, overflow := t.trimLeft(t.input[:t.cx], maxWidth)
minOffset := int(overflow) minOffset := int(overflow)
@ -1889,6 +1993,9 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
} }
func (t *Terminal) promptLine() int { func (t *Terminal) promptLine() int {
if t.inputWindow != nil {
return 0
}
if t.headerFirst { if t.headerFirst {
max := t.window.Height() - 1 max := t.window.Height() - 1
if max <= 0 { // Extremely short terminal if max <= 0 { // Extremely short terminal
@ -1903,10 +2010,25 @@ func (t *Terminal) promptLine() int {
} }
func (t *Terminal) placeCursor() { func (t *Terminal) placeCursor() {
if t.inputWindow != nil {
y := t.inputWindow.Height() - 1
if t.layout == layoutReverse {
y = 0
}
t.inputWindow.Move(y, t.promptLen+t.queryLen[0])
return
}
t.move(t.promptLine(), t.promptLen+t.queryLen[0], false) t.move(t.promptLine(), t.promptLen+t.queryLen[0], false)
} }
func (t *Terminal) printPrompt() { func (t *Terminal) printPrompt() {
w := t.window
if t.inputWindow != nil {
w = t.inputWindow
}
if w.Height() == 0 {
return
}
t.prompt() t.prompt()
before, after := t.updatePromptOffset() before, after := t.updatePromptOffset()
@ -1914,8 +2036,8 @@ func (t *Terminal) printPrompt() {
if t.paused { if t.paused {
color = tui.ColDisabled color = tui.ColDisabled
} }
t.window.CPrint(color, string(before)) w.CPrint(color, string(before))
t.window.CPrint(color, string(after)) w.CPrint(color, string(after))
} }
func (t *Terminal) trimMessage(message string, maxWidth int) string { func (t *Terminal) trimMessage(message string, maxWidth int) string {
@ -1927,6 +2049,12 @@ func (t *Terminal) trimMessage(message string, maxWidth int) string {
} }
func (t *Terminal) printInfo() { func (t *Terminal) printInfo() {
t.withInputWindow(func() {
t.printInfoImpl()
})
}
func (t *Terminal) printInfoImpl() {
if t.window.Width() <= 1 { if t.window.Width() <= 1 {
return return
} }
@ -2124,7 +2252,7 @@ func (t *Terminal) printHeader() {
return return
} }
max := t.window.Height() max := t.window.Height()
if t.headerFirst { if t.inputWindow == nil && t.headerFirst {
max-- max--
if !t.noSeparatorLine() { if !t.noSeparatorLine() {
max-- max--
@ -2144,7 +2272,7 @@ func (t *Terminal) printHeader() {
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.headerFirst { if t.inputWindow == nil && !t.headerFirst {
line++ line++
if !t.noSeparatorLine() { if !t.noSeparatorLine() {
line++ line++
@ -2189,10 +2317,7 @@ func (t *Terminal) printList() {
count := t.merger.Length() - t.offset count := t.merger.Length() - t.offset
// Start line // Start line
startLine := 2 + t.visibleHeaderLines() startLine := t.promptLines() + t.visibleHeaderLines()
if t.noSeparatorLine() {
startLine--
}
maxy += startLine maxy += startLine
barRange := [2]int{startLine + barStart, startLine + barStart + barLength} barRange := [2]int{startLine + barStart, startLine + barStart + barLength}
@ -2233,10 +2358,10 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
// Avoid unnecessary redraw // Avoid unnecessary redraw
numLines, _ := t.numItemLines(item, maxLine-line+1) numLines, _ := t.numItemLines(item, maxLine-line+1)
newLine := itemLine{firstLine: line, numLines: numLines, cy: index + t.offset, current: current, selected: selected, label: label, newLine := itemLine{valid: true, firstLine: line, numLines: numLines, cy: index + t.offset, current: current, selected: selected, label: label,
result: result, queryLen: len(t.input), width: 0, hasBar: line >= barRange[0] && line < barRange[1]} result: result, queryLen: len(t.input), width: 0, hasBar: line >= barRange[0] && line < barRange[1]}
prevLine := t.prevLines[line] prevLine := t.prevLines[line]
forceRedraw := prevLine.other || prevLine.firstLine != newLine.firstLine forceRedraw := !prevLine.valid || prevLine.other || prevLine.firstLine != newLine.firstLine
printBar := func(lineNum int, forceRedraw bool) bool { printBar := func(lineNum int, forceRedraw bool) bool {
return t.printBar(lineNum, forceRedraw, barRange) return t.printBar(lineNum, forceRedraw, barRange)
} }
@ -3962,6 +4087,8 @@ func (t *Terminal) Loop() error {
if t.hasPreviewer() { if t.hasPreviewer() {
t.previewBox.Set(reqPreviewReady, nil) t.previewBox.Set(reqPreviewReady, nil)
} }
case reqRedrawInputLabel:
t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, 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:
@ -4345,6 +4472,12 @@ func (t *Terminal) Loop() error {
} else { } else {
req(reqHeader) req(reqHeader)
} }
case actChangeInputLabel:
t.inputLabelOpts.label = a.a
if t.inputBorder != nil {
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(a.a, &tui.ColInputLabel, false)
req(reqRedrawInputLabel)
}
case actChangeListLabel: case actChangeListLabel:
t.listLabelOpts.label = a.a t.listLabelOpts.label = a.a
if t.wborder != nil { if t.wborder != nil {
@ -4368,6 +4501,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 actTransformInputLabel:
label := t.executeCommand(a.a, false, true, true, true, "")
t.inputLabelOpts.label = label
if t.inputBorder != nil {
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(label, &tui.ColInputLabel, false)
req(reqRedrawInputLabel)
}
case actTransformListLabel: case actTransformListLabel:
label := t.executeCommand(a.a, false, true, true, true, "") label := t.executeCommand(a.a, false, true, true, true, "")
t.listLabelOpts.label = label t.listLabelOpts.label = label
@ -4927,6 +5067,21 @@ func (t *Terminal) Loop() error {
break break
} }
// Inside the input window
if t.inputWindow != nil && t.inputWindow.Enclose(my, mx) {
mx -= t.inputWindow.Left()
my -= t.inputWindow.Top()
y := t.inputWindow.Height() - 1
if t.layout == layoutReverse {
y = 0
}
mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))
if my == y && mxCons >= 0 {
t.cx = mxCons + t.xoffset
}
break
}
// Ignored // Ignored
if !t.window.Enclose(my, mx) && !barDragging { if !t.window.Enclose(my, mx) && !barDragging {
break break
@ -4935,10 +5090,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 := 2 + t.visibleHeaderLines() min := t.promptLines() + t.visibleHeaderLines()
if t.noSeparatorLine() {
min--
}
h := t.window.Height() h := t.window.Height()
switch t.layout { switch t.layout {
case layoutDefault: case layoutDefault:
@ -4990,7 +5142,7 @@ func (t *Terminal) Loop() error {
if me.Down { if me.Down {
mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input)) mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))
if my == t.promptLine() && mxCons >= 0 { if t.inputWindow == nil && my == t.promptLine() && mxCons >= 0 {
// Prompt // Prompt
t.cx = mxCons + t.xoffset t.cx = mxCons + t.xoffset
} else if my >= min { } else if my >= min {
@ -5011,7 +5163,7 @@ func (t *Terminal) Loop() error {
// Header // Header
numLines := t.visibleHeaderLines() numLines := t.visibleHeaderLines()
lineOffset := 0 lineOffset := 0
if !t.headerFirst { if t.inputWindow == nil && !t.headerFirst {
// offset for info line // offset for info line
if t.noSeparatorLine() { if t.noSeparatorLine() {
lineOffset = 1 lineOffset = 1
@ -5339,11 +5491,20 @@ func (t *Terminal) vset(o int) bool {
return t.cy == o return t.cy == o
} }
func (t *Terminal) maxItems() int { // Number of prompt lines in the list window
max := t.window.Height() - 2 - t.visibleHeaderLines() func (t *Terminal) promptLines() int {
if t.noSeparatorLine() { if t.inputWindow != nil {
max++ return 0
} }
if t.noSeparatorLine() {
return 1
}
return 2
}
// Number of item lines in the list window
func (t *Terminal) maxItems() int {
max := t.window.Height() - t.visibleHeaderLines() - t.promptLines()
return util.Max(max, 0) return util.Max(max, 0)
} }

View File

@ -780,6 +780,8 @@ func (r *LightRenderer) MaxY() int {
} }
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window { func (r *LightRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {
width = util.Max(0, width)
height = util.Max(0, height)
w := &LightWindow{ w := &LightWindow{
renderer: r, renderer: r,
colored: r.theme.Colored, colored: r.theme.Colored,
@ -799,6 +801,9 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, wind
case WindowList: case WindowList:
w.fg = r.theme.ListFg.Color w.fg = r.theme.ListFg.Color
w.bg = r.theme.ListBg.Color w.bg = r.theme.ListBg.Color
case WindowInput:
w.fg = r.theme.Input.Color
w.bg = r.theme.InputBg.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
@ -820,6 +825,9 @@ func (w *LightWindow) DrawHBorder() {
} }
func (w *LightWindow) drawBorder(onlyHorizontal bool) { func (w *LightWindow) drawBorder(onlyHorizontal bool) {
if w.height == 0 {
return
}
switch w.border.shape { switch w.border.shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble: case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
w.drawBorderAround(onlyHorizontal) w.drawBorderAround(onlyHorizontal)
@ -852,6 +860,8 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
switch w.windowType { switch w.windowType {
case WindowList: case WindowList:
color = ColListBorder color = ColListBorder
case WindowInput:
color = ColInputBorder
case WindowPreview: case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
@ -873,6 +883,8 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
switch w.windowType { switch w.windowType {
case WindowList: case WindowList:
color = ColListBorder color = ColListBorder
case WindowInput:
color = ColInputBorder
case WindowPreview: case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }
@ -896,6 +908,8 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
switch w.windowType { switch w.windowType {
case WindowList: case WindowList:
color = ColListBorder color = ColListBorder
case WindowInput:
color = ColInputBorder
case WindowPreview: case WindowPreview:
color = ColPreviewBorder color = ColPreviewBorder
} }

View File

@ -551,10 +551,14 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
} }
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window { func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {
width = util.Max(0, width)
height = util.Max(0, height)
normal := ColBorder normal := ColBorder
switch windowType { switch windowType {
case WindowList: case WindowList:
normal = ColListBorder normal = ColListBorder
case WindowInput:
normal = ColInputBorder
case WindowPreview: case WindowPreview:
normal = ColPreviewBorder normal = ColPreviewBorder
} }
@ -768,6 +772,9 @@ func (w *TcellWindow) DrawHBorder() {
} }
func (w *TcellWindow) drawBorder(onlyHorizontal bool) { func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
if w.height == 0 {
return
}
shape := w.borderStyle.shape shape := w.borderStyle.shape
if shape == BorderNone { if shape == BorderNone {
return return
@ -785,6 +792,8 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
style = ColBorder.style() style = ColBorder.style()
case WindowList: case WindowList:
style = ColListBorder.style() style = ColListBorder.style()
case WindowInput:
style = ColInputBorder.style()
case WindowPreview: case WindowPreview:
style = ColPreviewBorder.style() style = ColPreviewBorder.style()
} }

View File

@ -313,6 +313,9 @@ type ColorTheme struct {
DarkBg ColorAttr DarkBg ColorAttr
Gutter ColorAttr Gutter ColorAttr
Prompt ColorAttr Prompt ColorAttr
InputBg ColorAttr
InputBorder ColorAttr
InputLabel ColorAttr
Match ColorAttr Match ColorAttr
Current ColorAttr Current ColorAttr
CurrentMatch ColorAttr CurrentMatch ColorAttr
@ -539,6 +542,7 @@ const (
WindowBase WindowType = iota WindowBase WindowType = iota
WindowList WindowList
WindowPreview WindowPreview
WindowInput
) )
type Renderer interface { type Renderer interface {
@ -646,6 +650,8 @@ var (
ColPreviewSpinner ColorPair ColPreviewSpinner ColorPair
ColListBorder ColorPair ColListBorder ColorPair
ColListLabel ColorPair ColListLabel ColorPair
ColInputBorder ColorPair
ColInputLabel ColorPair
) )
func EmptyTheme() *ColorTheme { func EmptyTheme() *ColorTheme {
@ -682,6 +688,9 @@ func EmptyTheme() *ColorTheme {
PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
} }
} }
@ -719,6 +728,9 @@ func NoColorTheme() *ColorTheme {
ListBorder: ColorAttr{colDefault, AttrUndefined}, ListBorder: ColorAttr{colDefault, AttrUndefined},
Separator: ColorAttr{colDefault, AttrUndefined}, Separator: ColorAttr{colDefault, AttrUndefined},
Scrollbar: ColorAttr{colDefault, AttrUndefined}, Scrollbar: ColorAttr{colDefault, AttrUndefined},
InputBg: ColorAttr{colDefault, AttrUndefined},
InputBorder: ColorAttr{colDefault, AttrUndefined},
InputLabel: ColorAttr{colDefault, AttrUndefined},
} }
} }
@ -756,6 +768,9 @@ func init() {
ListBorder: ColorAttr{colUndefined, AttrUndefined}, ListBorder: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
} }
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Colored: true, Colored: true,
@ -790,6 +805,9 @@ func init() {
ListBorder: ColorAttr{colUndefined, AttrUndefined}, ListBorder: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
} }
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Colored: true, Colored: true,
@ -824,6 +842,9 @@ func init() {
ListBorder: ColorAttr{colUndefined, AttrUndefined}, ListBorder: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined},
InputBg: ColorAttr{colUndefined, AttrUndefined},
InputBorder: ColorAttr{colUndefined, AttrUndefined},
InputLabel: ColorAttr{colUndefined, AttrUndefined},
} }
} }
@ -875,6 +896,9 @@ 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))
theme.InputBorder = o(theme.Border, theme.InputBorder)
theme.InputLabel = o(theme.BorderLabel, theme.InputLabel)
initPalette(theme) initPalette(theme)
} }
@ -889,10 +913,10 @@ func initPalette(theme *ColorTheme) {
blank := theme.ListFg blank := theme.ListFg
blank.Attr = AttrRegular blank.Attr = AttrRegular
ColPrompt = pair(theme.Prompt, theme.ListBg) ColPrompt = pair(theme.Prompt, theme.InputBg)
ColNormal = pair(theme.ListFg, theme.ListBg) ColNormal = pair(theme.ListFg, theme.ListBg)
ColSelected = pair(theme.SelectedFg, theme.SelectedBg) ColSelected = pair(theme.SelectedFg, theme.SelectedBg)
ColInput = pair(theme.Input, theme.ListBg) ColInput = pair(theme.Input, theme.InputBg)
ColDisabled = pair(theme.Disabled, theme.ListBg) ColDisabled = pair(theme.Disabled, theme.ListBg)
ColMatch = pair(theme.Match, theme.ListBg) ColMatch = pair(theme.Match, theme.ListBg)
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg) ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
@ -909,10 +933,10 @@ func initPalette(theme *ColorTheme) {
ColCurrentCursorEmpty = pair(blank, theme.DarkBg) ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
ColCurrentMarker = pair(theme.Marker, theme.DarkBg) ColCurrentMarker = pair(theme.Marker, theme.DarkBg)
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg) ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.ListBg) ColSpinner = pair(theme.Spinner, theme.InputBg)
ColInfo = pair(theme.Info, theme.ListBg) ColInfo = pair(theme.Info, theme.InputBg)
ColHeader = pair(theme.Header, theme.ListBg) ColHeader = pair(theme.Header, theme.ListBg)
ColSeparator = pair(theme.Separator, theme.ListBg) 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)
ColBorderLabel = pair(theme.BorderLabel, theme.Bg) ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
@ -923,6 +947,8 @@ func initPalette(theme *ColorTheme) {
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg) ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
ColListLabel = pair(theme.ListLabel, theme.ListBg) ColListLabel = pair(theme.ListLabel, theme.ListBg)
ColListBorder = pair(theme.ListBorder, theme.ListBg) ColListBorder = pair(theme.ListBorder, theme.ListBg)
ColInputBorder = pair(theme.InputBorder, theme.InputBg)
ColInputLabel = pair(theme.InputLabel, theme.InputBg)
} }
func runeWidth(r rune) int { func runeWidth(r rune) int {

View File

@ -2646,7 +2646,7 @@ class TestGoFZF < TestBase
end end
def test_change_preview_window def test_change_preview_window
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --preview-window border-none --bind '" \ tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --no-preview-border --bind '" \
'a:change-preview(echo __{}__),' \ 'a:change-preview(echo __{}__),' \
'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \ 'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \
'c:change-preview(),d:change-preview-window(hidden),' \ 'c:change-preview(),d:change-preview-window(hidden),' \
@ -3449,6 +3449,80 @@ class TestGoFZF < TestBase
BLOCK BLOCK
tmux.until { assert_block(block, _1) } tmux.until { assert_block(block, _1) }
end end
def test_list_border_and_label
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --query 1 --padding 1,2), :Enter
block = <<~BLOCK
11
> 10
3
2
1
19/97
> 1
list
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_input_border_and_label
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), :Enter
block = <<~BLOCK
11
> 10
3
2
1
input
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_list_input_border_and_label
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 )'
).strip, :Enter
block = <<~BLOCK
11
> 10
3
2
1
LIST
INPUT
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
tmux.send_keys :Space
block = <<~BLOCK
11
> 10
3
2
1
list
input
19/97
> 1
BLOCK
tmux.until { assert_block(block, _1) }
end
end end
module TestShell module TestShell