From ee3916be17a340205875f9ccfaf71a1683a2fdf9 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Thu, 2 Jan 2025 16:24:46 +0900 Subject: [PATCH] Border around the input section (prompt + info) Close #4154 --- CHANGELOG.md | 18 +- man/man1/fzf-tmux.1 | 2 +- man/man1/fzf.1 | 17 +- src/actiontype_string.go | 204 ++++++++-------- src/options.go | 513 ++++++++++++++++++++++----------------- src/terminal.go | 227 ++++++++++++++--- src/tui/light.go | 14 ++ src/tui/tcell.go | 9 + src/tui/tui.go | 36 ++- test/test_go.rb | 77 +++++- 10 files changed, 744 insertions(+), 373 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 620d0dcd0cc..c4904d217b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ CHANGELOG 0.58.0 ------ -- Additional border and label for the list section +- Border and label for the list section - Options - `--list-border[=STYLE]` - `--list-label=LABEL` @@ -16,6 +16,22 @@ CHANGELOG - Actions - `change-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 ------ diff --git a/man/man1/fzf-tmux.1 b/man/man1/fzf-tmux.1 index bfa08162654..357d97b40c0 100644 --- a/man/man1/fzf-tmux.1 +++ b/man/man1/fzf-tmux.1 @@ -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\-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 fzf\-tmux - open fzf in tmux split pane diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 0b0654b4ff9..28d27f181ff 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -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 "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 fzf - a command-line fuzzy finder @@ -557,16 +557,17 @@ color mappings. \fBselected\-fg \fRSelected line text \fBpreview\-fg \fRPreview window text \fBbg \fRBackground - \fBlist\-bg \fRBackground in the list section + \fBlist\-bg \fRList section background \fBselected\-bg \fRSelected line background \fBpreview\-bg \fRPreview window background + \fBinput\-bg \fRInput section background \fBhl \fRHighlighted substrings \fBselected\-hl \fRHighlighted substrings in the selected line \fBcurrent\-fg (fg+) \fRText (current line) \fBcurrent\-bg (bg+) \fRBackground (current line) \fBgutter \fRGutter on the left \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) \fBinfo \fRInfo line (match counters) \fBborder \fRBorder around the window (\fB\-\-border\fR and \fB\-\-preview\fR) @@ -575,9 +576,11 @@ color mappings. \fBseparator \fRHorizontal separator on info line \fBpreview\-border \fRBorder around the preview window (\fB\-\-preview\fR) \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) \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 \fBpointer \fRPointer to the current line \fBmarker \fRMulti\-select marker @@ -728,6 +731,10 @@ e.g. .RE +.TP +.BI "\-\-preview\-border" [=STYLE] +Short for \fB\-\-preview\-window=border\-STYLE\fR + .TP .BI "\-\-preview\-label" [=LABEL] 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) \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\-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\-multi\fR (enable multi-select mode with no limit) \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\-border\-label(...)\fR (transform border label 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\-preview\-label(...)\fR (transform preview label using an external command) \fBtransform\-prompt(...)\fR (transform prompt string using an external command) diff --git a/src/actiontype_string.go b/src/actiontype_string.go index 675a77778d1..2996ce72e2a 100644 --- a/src/actiontype_string.go +++ b/src/actiontype_string.go @@ -26,110 +26,112 @@ func _() { _ = x[actCancel-15] _ = x[actChangeBorderLabel-16] _ = x[actChangeListLabel-17] - _ = x[actChangeHeader-18] - _ = x[actChangeMulti-19] - _ = x[actChangePreviewLabel-20] - _ = x[actChangePrompt-21] - _ = x[actChangeQuery-22] - _ = x[actClearScreen-23] - _ = x[actClearQuery-24] - _ = x[actClearSelection-25] - _ = x[actClose-26] - _ = x[actDeleteChar-27] - _ = x[actDeleteCharEof-28] - _ = x[actEndOfLine-29] - _ = x[actFatal-30] - _ = x[actForwardChar-31] - _ = x[actForwardWord-32] - _ = x[actKillLine-33] - _ = x[actKillWord-34] - _ = x[actUnixLineDiscard-35] - _ = x[actUnixWordRubout-36] - _ = x[actYank-37] - _ = x[actBackwardKillWord-38] - _ = x[actSelectAll-39] - _ = x[actDeselectAll-40] - _ = x[actToggle-41] - _ = x[actToggleSearch-42] - _ = x[actToggleAll-43] - _ = x[actToggleDown-44] - _ = x[actToggleUp-45] - _ = x[actToggleIn-46] - _ = x[actToggleOut-47] - _ = x[actToggleTrack-48] - _ = x[actToggleTrackCurrent-49] - _ = x[actToggleHeader-50] - _ = x[actToggleWrap-51] - _ = x[actToggleMultiLine-52] - _ = x[actToggleHscroll-53] - _ = x[actTrackCurrent-54] - _ = x[actUntrackCurrent-55] - _ = x[actDown-56] - _ = x[actUp-57] - _ = x[actPageUp-58] - _ = x[actPageDown-59] - _ = x[actPosition-60] - _ = x[actHalfPageUp-61] - _ = x[actHalfPageDown-62] - _ = x[actOffsetUp-63] - _ = x[actOffsetDown-64] - _ = x[actOffsetMiddle-65] - _ = x[actJump-66] - _ = x[actJumpAccept-67] - _ = x[actPrintQuery-68] - _ = x[actRefreshPreview-69] - _ = x[actReplaceQuery-70] - _ = x[actToggleSort-71] - _ = x[actShowPreview-72] - _ = x[actHidePreview-73] - _ = x[actTogglePreview-74] - _ = x[actTogglePreviewWrap-75] - _ = x[actTransform-76] - _ = x[actTransformBorderLabel-77] - _ = x[actTransformListLabel-78] - _ = x[actTransformHeader-79] - _ = x[actTransformPreviewLabel-80] - _ = x[actTransformPrompt-81] - _ = x[actTransformQuery-82] - _ = x[actPreview-83] - _ = x[actChangePreview-84] - _ = x[actChangePreviewWindow-85] - _ = x[actPreviewTop-86] - _ = x[actPreviewBottom-87] - _ = x[actPreviewUp-88] - _ = x[actPreviewDown-89] - _ = x[actPreviewPageUp-90] - _ = x[actPreviewPageDown-91] - _ = x[actPreviewHalfPageUp-92] - _ = x[actPreviewHalfPageDown-93] - _ = x[actPrevHistory-94] - _ = x[actPrevSelected-95] - _ = x[actPrint-96] - _ = x[actPut-97] - _ = x[actNextHistory-98] - _ = x[actNextSelected-99] - _ = x[actExecute-100] - _ = x[actExecuteSilent-101] - _ = x[actExecuteMulti-102] - _ = x[actSigStop-103] - _ = x[actFirst-104] - _ = x[actLast-105] - _ = x[actReload-106] - _ = x[actReloadSync-107] - _ = x[actDisableSearch-108] - _ = x[actEnableSearch-109] - _ = x[actSelect-110] - _ = x[actDeselect-111] - _ = x[actUnbind-112] - _ = x[actRebind-113] - _ = x[actBecome-114] - _ = x[actShowHeader-115] - _ = x[actHideHeader-116] + _ = x[actChangeInputLabel-18] + _ = x[actChangeHeader-19] + _ = x[actChangeMulti-20] + _ = x[actChangePreviewLabel-21] + _ = x[actChangePrompt-22] + _ = x[actChangeQuery-23] + _ = x[actClearScreen-24] + _ = x[actClearQuery-25] + _ = x[actClearSelection-26] + _ = x[actClose-27] + _ = x[actDeleteChar-28] + _ = x[actDeleteCharEof-29] + _ = x[actEndOfLine-30] + _ = x[actFatal-31] + _ = x[actForwardChar-32] + _ = x[actForwardWord-33] + _ = x[actKillLine-34] + _ = x[actKillWord-35] + _ = x[actUnixLineDiscard-36] + _ = x[actUnixWordRubout-37] + _ = x[actYank-38] + _ = x[actBackwardKillWord-39] + _ = x[actSelectAll-40] + _ = x[actDeselectAll-41] + _ = x[actToggle-42] + _ = x[actToggleSearch-43] + _ = x[actToggleAll-44] + _ = x[actToggleDown-45] + _ = x[actToggleUp-46] + _ = x[actToggleIn-47] + _ = x[actToggleOut-48] + _ = x[actToggleTrack-49] + _ = x[actToggleTrackCurrent-50] + _ = x[actToggleHeader-51] + _ = x[actToggleWrap-52] + _ = x[actToggleMultiLine-53] + _ = x[actToggleHscroll-54] + _ = x[actTrackCurrent-55] + _ = x[actUntrackCurrent-56] + _ = x[actDown-57] + _ = x[actUp-58] + _ = x[actPageUp-59] + _ = x[actPageDown-60] + _ = x[actPosition-61] + _ = x[actHalfPageUp-62] + _ = x[actHalfPageDown-63] + _ = x[actOffsetUp-64] + _ = x[actOffsetDown-65] + _ = x[actOffsetMiddle-66] + _ = x[actJump-67] + _ = x[actJumpAccept-68] + _ = x[actPrintQuery-69] + _ = x[actRefreshPreview-70] + _ = x[actReplaceQuery-71] + _ = x[actToggleSort-72] + _ = x[actShowPreview-73] + _ = x[actHidePreview-74] + _ = x[actTogglePreview-75] + _ = x[actTogglePreviewWrap-76] + _ = x[actTransform-77] + _ = x[actTransformBorderLabel-78] + _ = x[actTransformListLabel-79] + _ = x[actTransformInputLabel-80] + _ = x[actTransformHeader-81] + _ = x[actTransformPreviewLabel-82] + _ = x[actTransformPrompt-83] + _ = x[actTransformQuery-84] + _ = x[actPreview-85] + _ = x[actChangePreview-86] + _ = x[actChangePreviewWindow-87] + _ = x[actPreviewTop-88] + _ = x[actPreviewBottom-89] + _ = x[actPreviewUp-90] + _ = x[actPreviewDown-91] + _ = x[actPreviewPageUp-92] + _ = x[actPreviewPageDown-93] + _ = x[actPreviewHalfPageUp-94] + _ = x[actPreviewHalfPageDown-95] + _ = x[actPrevHistory-96] + _ = x[actPrevSelected-97] + _ = x[actPrint-98] + _ = x[actPut-99] + _ = x[actNextHistory-100] + _ = x[actNextSelected-101] + _ = x[actExecute-102] + _ = x[actExecuteSilent-103] + _ = x[actExecuteMulti-104] + _ = x[actSigStop-105] + _ = x[actFirst-106] + _ = x[actLast-107] + _ = x[actReload-108] + _ = x[actReloadSync-109] + _ = x[actDisableSearch-110] + _ = x[actEnableSearch-111] + _ = x[actSelect-112] + _ = x[actDeselect-113] + _ = x[actUnbind-114] + _ = x[actRebind-115] + _ = 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 { if i < 0 || i >= actionType(len(_actionType_index)-1) { diff --git a/src/options.go b/src/options.go index 40a2a2e3503..03749568aff 100644 --- a/src/options.go +++ b/src/options.go @@ -27,151 +27,152 @@ Author: Junegunn Choi Usage: fzf [options] Search - -x, --extended Extended-search mode - (enabled by default; +x or --no-extended to disable) - -e, --exact Enable Exact-match - -i, --ignore-case Case-insensitive match (default: smart-case match) - +i, --no-ignore-case Case-sensitive match - --scheme=SCHEME Scoring scheme [default|path|history] - --literal Do not normalize latin script letters before matching - -n, --nth=N[,..] Comma-separated list of field index expressions - for limiting search scope. Each can be a non-zero - integer or a range expression ([BEGIN]..[END]). - --with-nth=N[,..] Transform the presentation of each line using - field index expressions - -d, --delimiter=STR Field delimiter regex (default: AWK-style) - +s, --no-sort Do not sort the result - --tail=NUM Maximum number of items to keep in memory - --track Track the current selection when the result is updated - --tac Reverse the order of the input - --disabled Do not perform search - --tiebreak=CRI[,..] Comma-separated list of sort criteria to apply - when the scores are tied [length|chunk|begin|end|index] - (default: length) + -x, --extended Extended-search mode + (enabled by default; +x or --no-extended to disable) + -e, --exact Enable Exact-match + -i, --ignore-case Case-insensitive match (default: smart-case match) + +i, --no-ignore-case Case-sensitive match + --scheme=SCHEME Scoring scheme [default|path|history] + --literal Do not normalize latin script letters before matching + -n, --nth=N[,..] Comma-separated list of field index expressions + for limiting search scope. Each can be a non-zero + integer or a range expression ([BEGIN]..[END]). + --with-nth=N[,..] Transform the presentation of each line using + field index expressions + -d, --delimiter=STR Field delimiter regex (default: AWK-style) + +s, --no-sort Do not sort the result + --tail=NUM Maximum number of items to keep in memory + --track Track the current selection when the result is updated + --tac Reverse the order of the input + --disabled Do not perform search + --tiebreak=CRI[,..] Comma-separated list of sort criteria to apply + when the scores are tied [length|chunk|begin|end|index] + (default: length) Interface - -m, --multi[=MAX] Enable multi-select with tab/shift-tab - --no-mouse Disable mouse - --bind=KEYBINDS Custom key bindings. Refer to the man page. - --cycle Enable cyclic scroll - --wrap Enable line wrap - --wrap-sign=STR Indicator for wrapped lines - --no-multi-line Disable multi-line display of items when using --read0 - --gap[=N] Render empty lines between each item - --keep-right Keep the right end of the line visible on overflow - --scroll-off=LINES Number of screen lines to keep above or below when - scrolling to the top or to the bottom (default: 0) - --no-hscroll Disable horizontal scroll - --hscroll-off=COLS Number of screen columns to keep to the right of the - highlighted substring (default: 10) - --filepath-word Make word-wise movements respect path separators - --jump-labels=CHARS Label characters for jump mode + -m, --multi[=MAX] Enable multi-select with tab/shift-tab + --no-mouse Disable mouse + --bind=KEYBINDS Custom key bindings. Refer to the man page. + --cycle Enable cyclic scroll + --wrap Enable line wrap + --wrap-sign=STR Indicator for wrapped lines + --no-multi-line Disable multi-line display of items when using --read0 + --gap[=N] Render empty lines between each item + --keep-right Keep the right end of the line visible on overflow + --scroll-off=LINES Number of screen lines to keep above or below when + scrolling to the top or to the bottom (default: 0) + --no-hscroll Disable horizontal scroll + --hscroll-off=COLS Number of screen columns to keep to the right of the + highlighted substring (default: 10) + --filepath-word Make word-wise movements respect path separators + --jump-labels=CHARS Label characters for jump mode Layout - --height=[~]HEIGHT[%] Display fzf window below the cursor with the given - height instead of using fullscreen. - A negative value is calculated as the terminal height - minus the given value. - If prefixed with '~', fzf will determine the height - according to the input size. - --min-height=HEIGHT Minimum height when --height is given in percent - (default: 10) - --tmux[=OPTS] Start fzf in a tmux popup (requires tmux 3.3+) - [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]] - (default: center,50%) - --layout=LAYOUT Choose layout: [default|reverse|reverse-list] - --border[=STYLE] Draw border around the finder - [rounded|sharp|bold|block|thinblock|double|horizontal|vertical| - top|bottom|left|right|none] (default: rounded) - --border-label=LABEL Label to print on the border - --border-label-pos=COL Position of the border label - [POSITIVE_INTEGER: columns from left| - NEGATIVE_INTEGER: columns from right][:bottom] - (default: 0 or center) - --list-border[=STYLE] Draw border around the list section - [rounded|sharp|bold|block|thinblock|double|horizontal|vertical| - top|bottom|left|right|none] (default: none) - --list-label=LABEL Label to print on the list border - --list-label-pos=COL Position of the list label - [POSITIVE_INTEGER: columns from left| - NEGATIVE_INTEGER: columns from right][:bottom] - (default: 0 or center) - --margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L) - --padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L) - --info=STYLE Finder info style - [default|right|hidden|inline[-right][:PREFIX]] - --info-command=COMMAND Command to generate info line - --separator=STR String to form horizontal separator on info line - --no-separator Hide info line separator - --scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window) - --no-scrollbar Hide scrollbar - --prompt=STR Input prompt (default: '> ') - --pointer=STR Pointer to the current line (default: '▌' or '>') - --marker=STR Multi-select marker (default: '┃' or '>') - --marker-multi-line=STR Multi-select marker for multi-line entries; - 3 elements for top, middle, and bottom (default: '╻┃╹') - --header=STR String to print as header - --header-lines=N The first N lines of the input are treated as header - --header-first Print header before the prompt line - --ellipsis=STR Ellipsis to show when line is truncated (default: '··') + --height=[~]HEIGHT[%] Display fzf window below the cursor with the given + height instead of using fullscreen. + A negative value is calculated as the terminal height + minus the given value. + If prefixed with '~', fzf will determine the height + according to the input size. + --min-height=HEIGHT Minimum height when --height is given in percent + (default: 10) + --tmux[=OPTS] Start fzf in a tmux popup (requires tmux 3.3+) + [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]] + (default: center,50%) + --layout=LAYOUT Choose layout: [default|reverse|reverse-list] + --border[=STYLE] Draw border around the finder + [rounded|sharp|bold|block|thinblock|double|horizontal|vertical| + top|bottom|left|right|none] (default: rounded) + --border-label=LABEL Label to print on the border + --border-label-pos=COL Position of the border label + [POSITIVE_INTEGER: columns from left| + NEGATIVE_INTEGER: columns from right][:bottom] + (default: 0 or center) + --list-border[=STYLE] Draw border around the list section + [rounded|sharp|bold|block|thinblock|double|horizontal|vertical| + top|bottom|left|right|none] (default: none) + --list-label=LABEL Label to print on the list border + --list-label-pos=COL Position of the list label + [POSITIVE_INTEGER: columns from left| + NEGATIVE_INTEGER: columns from right][:bottom] + (default: 0 or center) + --margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L) + --padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L) + --info=STYLE Finder info style + [default|right|hidden|inline[-right][:PREFIX]] + --info-command=COMMAND Command to generate info line + --separator=STR String to form horizontal separator on info line + --no-separator Hide info line separator + --scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window) + --no-scrollbar Hide scrollbar + --prompt=STR Input prompt (default: '> ') + --pointer=STR Pointer to the current line (default: '▌' or '>') + --marker=STR Multi-select marker (default: '┃' or '>') + --marker-multi-line=STR Multi-select marker for multi-line entries; + 3 elements for top, middle, and bottom (default: '╻┃╹') + --header=STR String to print as header + --header-lines=N The first N lines of the input are treated as header + --header-first Print header before the prompt line + --ellipsis=STR Ellipsis to show when line is truncated (default: '··') Display - --ansi Enable processing of ANSI color codes - --tabstop=SPACES Number of spaces for a tab character (default: 8) - --color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors - --highlight-line Highlight the whole current line - --no-bold Do not use bold text + --ansi Enable processing of ANSI color codes + --tabstop=SPACES Number of spaces for a tab character (default: 8) + --color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors + --highlight-line Highlight the whole current line + --no-bold Do not use bold text History - --history=FILE History file - --history-size=N Maximum number of history entries (default: 1000) + --history=FILE History file + --history-size=N Maximum number of history entries (default: 1000) Preview - --preview=COMMAND Command to preview highlighted line ({}) - --preview-window=OPT Preview window layout (default: right:50%) - [up|down|left|right][,SIZE[%]] - [,[no]wrap][,[no]cycle][,[no]follow][,[no]info] - [,[no]hidden][,border-BORDER_OPT] - [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES] - [,default][, 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 := "─" if opts.Separator != nil { bar = *opts.Separator @@ -1120,6 +1140,17 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool) 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) { var state *ansiState prompt = firstLine(prompt) @@ -1145,11 +1176,13 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) { } } output := func() { - line := t.promptLine() wrap := t.wrap t.wrap = false - t.printHighlighted( - Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil) + t.withInputWindow(func() { + line := t.promptLine() + t.printHighlighted( + Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil) + }) t.wrap = wrap } _, promptLen := t.processTabs([]rune(trimmed), 0) @@ -1566,6 +1599,12 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { if t.wborder != nil { t.wborder = nil } + if t.inputWindow != nil { + t.inputWindow = nil + } + if t.inputBorder != nil { + t.inputBorder = nil + } if t.pborder != nil { t.pborder = nil } @@ -1605,6 +1644,27 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { width -= paddingInt[1] + paddingInt[3] 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() innerWidth := width innerHeight := height @@ -1612,7 +1672,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { innerBorderFn := func(top int, left int, width int, height int) { if hasListBorder { 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 { @@ -1697,12 +1757,12 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { if previewOpts.position == posUp { innerBorderFn(marginInt[0]+pheight, marginInt[3], width, height-pheight) 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) } else { innerBorderFn(marginInt[0], marginInt[3], width, height-pheight) 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) } case posLeft, posRight: @@ -1741,7 +1801,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { m = 1 } 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 // 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) 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 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) t.window = t.tui.NewWindow( - innerMarginInt[0], + innerMarginInt[0]+shift, innerMarginInt[3], 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 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) } 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) { - 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) minOffset := int(overflow) @@ -1889,6 +1993,9 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) { } func (t *Terminal) promptLine() int { + if t.inputWindow != nil { + return 0 + } if t.headerFirst { max := t.window.Height() - 1 if max <= 0 { // Extremely short terminal @@ -1903,10 +2010,25 @@ func (t *Terminal) promptLine() int { } 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) } func (t *Terminal) printPrompt() { + w := t.window + if t.inputWindow != nil { + w = t.inputWindow + } + if w.Height() == 0 { + return + } t.prompt() before, after := t.updatePromptOffset() @@ -1914,8 +2036,8 @@ func (t *Terminal) printPrompt() { if t.paused { color = tui.ColDisabled } - t.window.CPrint(color, string(before)) - t.window.CPrint(color, string(after)) + w.CPrint(color, string(before)) + w.CPrint(color, string(after)) } 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() { + t.withInputWindow(func() { + t.printInfoImpl() + }) +} + +func (t *Terminal) printInfoImpl() { if t.window.Width() <= 1 { return } @@ -2124,7 +2252,7 @@ func (t *Terminal) printHeader() { return } max := t.window.Height() - if t.headerFirst { + if t.inputWindow == nil && t.headerFirst { max-- if !t.noSeparatorLine() { max-- @@ -2144,7 +2272,7 @@ func (t *Terminal) printHeader() { if needReverse && idx < len(t.header0) { line = len(t.header0) - idx - 1 } - if !t.headerFirst { + if t.inputWindow == nil && !t.headerFirst { line++ if !t.noSeparatorLine() { line++ @@ -2189,10 +2317,7 @@ func (t *Terminal) printList() { count := t.merger.Length() - t.offset // Start line - startLine := 2 + t.visibleHeaderLines() - if t.noSeparatorLine() { - startLine-- - } + startLine := t.promptLines() + t.visibleHeaderLines() maxy += startLine 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 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]} 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 { return t.printBar(lineNum, forceRedraw, barRange) } @@ -3962,6 +4087,8 @@ func (t *Terminal) Loop() error { if t.hasPreviewer() { t.previewBox.Set(reqPreviewReady, nil) } + case reqRedrawInputLabel: + t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, true) case reqRedrawListLabel: t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, true) case reqRedrawBorderLabel: @@ -4345,6 +4472,12 @@ func (t *Terminal) Loop() error { } else { 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: t.listLabelOpts.label = a.a if t.wborder != nil { @@ -4368,6 +4501,13 @@ func (t *Terminal) Loop() error { if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil { 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: label := t.executeCommand(a.a, false, true, true, true, "") t.listLabelOpts.label = label @@ -4927,6 +5067,21 @@ func (t *Terminal) Loop() error { 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 if !t.window.Enclose(my, mx) && !barDragging { break @@ -4935,10 +5090,7 @@ func (t *Terminal) Loop() error { // Translate coordinates mx -= t.window.Left() my -= t.window.Top() - min := 2 + t.visibleHeaderLines() - if t.noSeparatorLine() { - min-- - } + min := t.promptLines() + t.visibleHeaderLines() h := t.window.Height() switch t.layout { case layoutDefault: @@ -4990,7 +5142,7 @@ func (t *Terminal) Loop() error { if me.Down { 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 t.cx = mxCons + t.xoffset } else if my >= min { @@ -5011,7 +5163,7 @@ func (t *Terminal) Loop() error { // Header numLines := t.visibleHeaderLines() lineOffset := 0 - if !t.headerFirst { + if t.inputWindow == nil && !t.headerFirst { // offset for info line if t.noSeparatorLine() { lineOffset = 1 @@ -5339,11 +5491,20 @@ func (t *Terminal) vset(o int) bool { return t.cy == o } -func (t *Terminal) maxItems() int { - max := t.window.Height() - 2 - t.visibleHeaderLines() +// Number of prompt lines in the list window +func (t *Terminal) promptLines() int { + if t.inputWindow != nil { + return 0 + } if t.noSeparatorLine() { - max++ + 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) } diff --git a/src/tui/light.go b/src/tui/light.go index f0bb2fdfc6b..3cb1a8bcbc4 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -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 { + width = util.Max(0, width) + height = util.Max(0, height) w := &LightWindow{ renderer: r, colored: r.theme.Colored, @@ -799,6 +801,9 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, wind case WindowList: w.fg = r.theme.ListFg.Color w.bg = r.theme.ListBg.Color + case WindowInput: + w.fg = r.theme.Input.Color + w.bg = r.theme.InputBg.Color case WindowPreview: w.fg = r.theme.PreviewFg.Color w.bg = r.theme.PreviewBg.Color @@ -820,6 +825,9 @@ func (w *LightWindow) DrawHBorder() { } func (w *LightWindow) drawBorder(onlyHorizontal bool) { + if w.height == 0 { + return + } switch w.border.shape { case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble: w.drawBorderAround(onlyHorizontal) @@ -852,6 +860,8 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) { switch w.windowType { case WindowList: color = ColListBorder + case WindowInput: + color = ColInputBorder case WindowPreview: color = ColPreviewBorder } @@ -873,6 +883,8 @@ func (w *LightWindow) drawBorderVertical(left, right bool) { switch w.windowType { case WindowList: color = ColListBorder + case WindowInput: + color = ColInputBorder case WindowPreview: color = ColPreviewBorder } @@ -896,6 +908,8 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) { switch w.windowType { case WindowList: color = ColListBorder + case WindowInput: + color = ColInputBorder case WindowPreview: color = ColPreviewBorder } diff --git a/src/tui/tcell.go b/src/tui/tcell.go index 92336cd0073..becdabcd04d 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -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 { + width = util.Max(0, width) + height = util.Max(0, height) normal := ColBorder switch windowType { case WindowList: normal = ColListBorder + case WindowInput: + normal = ColInputBorder case WindowPreview: normal = ColPreviewBorder } @@ -768,6 +772,9 @@ func (w *TcellWindow) DrawHBorder() { } func (w *TcellWindow) drawBorder(onlyHorizontal bool) { + if w.height == 0 { + return + } shape := w.borderStyle.shape if shape == BorderNone { return @@ -785,6 +792,8 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) { style = ColBorder.style() case WindowList: style = ColListBorder.style() + case WindowInput: + style = ColInputBorder.style() case WindowPreview: style = ColPreviewBorder.style() } diff --git a/src/tui/tui.go b/src/tui/tui.go index e2a891d07d9..832109ce53c 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -313,6 +313,9 @@ type ColorTheme struct { DarkBg ColorAttr Gutter ColorAttr Prompt ColorAttr + InputBg ColorAttr + InputBorder ColorAttr + InputLabel ColorAttr Match ColorAttr Current ColorAttr CurrentMatch ColorAttr @@ -539,6 +542,7 @@ const ( WindowBase WindowType = iota WindowList WindowPreview + WindowInput ) type Renderer interface { @@ -646,6 +650,8 @@ var ( ColPreviewSpinner ColorPair ColListBorder ColorPair ColListLabel ColorPair + ColInputBorder ColorPair + ColInputLabel ColorPair ) func EmptyTheme() *ColorTheme { @@ -682,6 +688,9 @@ func EmptyTheme() *ColorTheme { PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, Separator: 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}, Separator: 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}, Separator: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined}, + InputBg: ColorAttr{colUndefined, AttrUndefined}, + InputBorder: ColorAttr{colUndefined, AttrUndefined}, + InputLabel: ColorAttr{colUndefined, AttrUndefined}, } Dark256 = &ColorTheme{ Colored: true, @@ -790,6 +805,9 @@ func init() { ListBorder: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined}, + InputBg: ColorAttr{colUndefined, AttrUndefined}, + InputBorder: ColorAttr{colUndefined, AttrUndefined}, + InputLabel: ColorAttr{colUndefined, AttrUndefined}, } Light256 = &ColorTheme{ Colored: true, @@ -824,6 +842,9 @@ func init() { ListBorder: ColorAttr{colUndefined, AttrUndefined}, Separator: 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.Scrollbar = o(theme.ListBorder, theme.Scrollbar) 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) } @@ -889,10 +913,10 @@ func initPalette(theme *ColorTheme) { blank := theme.ListFg blank.Attr = AttrRegular - ColPrompt = pair(theme.Prompt, theme.ListBg) + ColPrompt = pair(theme.Prompt, theme.InputBg) ColNormal = pair(theme.ListFg, theme.ListBg) ColSelected = pair(theme.SelectedFg, theme.SelectedBg) - ColInput = pair(theme.Input, theme.ListBg) + ColInput = pair(theme.Input, theme.InputBg) ColDisabled = pair(theme.Disabled, theme.ListBg) ColMatch = pair(theme.Match, theme.ListBg) ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg) @@ -909,10 +933,10 @@ func initPalette(theme *ColorTheme) { ColCurrentCursorEmpty = pair(blank, theme.DarkBg) ColCurrentMarker = pair(theme.Marker, theme.DarkBg) ColCurrentSelectedEmpty = pair(blank, theme.DarkBg) - ColSpinner = pair(theme.Spinner, theme.ListBg) - ColInfo = pair(theme.Info, theme.ListBg) + ColSpinner = pair(theme.Spinner, theme.InputBg) + ColInfo = pair(theme.Info, theme.InputBg) ColHeader = pair(theme.Header, theme.ListBg) - ColSeparator = pair(theme.Separator, theme.ListBg) + ColSeparator = pair(theme.Separator, theme.InputBg) ColScrollbar = pair(theme.Scrollbar, theme.ListBg) ColBorder = pair(theme.Border, theme.Bg) ColBorderLabel = pair(theme.BorderLabel, theme.Bg) @@ -923,6 +947,8 @@ func initPalette(theme *ColorTheme) { ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg) ColListLabel = pair(theme.ListLabel, 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 { diff --git a/test/test_go.rb b/test/test_go.rb index 1042bfcd840..89d326284fe 100755 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -2646,7 +2646,7 @@ def test_header_first_reverse end 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 __{}__),' \ 'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \ 'c:change-preview(),d:change-preview-window(hidden),' \ @@ -3449,6 +3449,81 @@ def test_gap_2 BLOCK tmux.until { assert_block(block, _1) } 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 module TestShell