summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2025-01-02 16:24:46 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2025-01-02 16:25:00 +0900
commitee3916be17a340205875f9ccfaf71a1683a2fdf9 (patch)
tree0cc0e5d4d7067f0135f01431795a9f4541f7e532
parentfd513f8af8191cd60364497f4184b8accd988282 (diff)
downloadfzf-ee3916be17a340205875f9ccfaf71a1683a2fdf9.tar.gz
Border around the input section (prompt + info)
Close #4154
-rw-r--r--CHANGELOG.md18
-rw-r--r--man/man1/fzf-tmux.12
-rw-r--r--man/man1/fzf.117
-rw-r--r--src/actiontype_string.go204
-rw-r--r--src/options.go513
-rw-r--r--src/terminal.go227
-rw-r--r--src/tui/light.go14
-rw-r--r--src/tui/tcell.go9
-rw-r--r--src/tui/tui.go36
-rwxr-xr-xtest/test_go.rb77
10 files changed, 744 insertions, 373 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 620d0dcd..c4904d21 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 bfa08162..357d97b4 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 0b0654b4..28d27f18 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
@@ -729,6 +732,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.
Should be used with one of the following \fB\-\-preview\-window\fR options.
@@ -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 675a7777..2996ce72 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 40a2a2e3..03749568 100644
--- a/src/options.go
+++ b/src/options.go
@@ -27,151 +27,152 @@ Author: Junegunn Choi <junegunn.c@gmail.com>
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][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
+ --preview=COMMAND Command to preview highlighted line ({})
+ --preview-window=OPT Preview window layout (default: right:50%)
+ [up|down|left|right][,SIZE[%]]
+ [,[no]wrap][,[no]cycle][,[no]follow][,[no]info]
+ [,[no]hidden][,border-BORDER_OPT]
+ [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
+ [,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
+ --preview-border[=STYLE] Short for --preview-window=border-STYLE
--preview-label=LABEL
- --preview-label-pos=N Same as --border-label and --border-label-pos,
- but for preview window
+ --preview-label-pos=N Same as --border-label and --border-label-pos,
+ but for preview window
Scripting
- -q, --query=STR Start the finder with the given query
- -1, --select-1 Automatically select the only match
- -0, --exit-0 Exit immediately when there's no match
- -f, --filter=STR Filter mode. Do not start interactive finder.
- --print-query Print query as the first line
- --expect=KEYS Comma-separated list of keys to complete fzf
- --read0 Read input delimited by ASCII NUL characters
- --print0 Print output delimited by ASCII NUL characters
- --sync Synchronous search for multi-staged filtering
- --with-shell=STR Shell command and flags to start child processes with
- --listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
- (To allow remote process execution, use --listen-unsafe)
-
- Directory traversal (Only used when $FZF_DEFAULT_COMMAND is not set)
- --walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden)
- --walker-root=DIR [...] List of directories to walk (default: .)
- --walker-skip=DIRS Comma-separated list of directory names to skip
- (default: .git,node_modules)
+ -q, --query=STR Start the finder with the given query
+ -1, --select-1 Automatically select the only match
+ -0, --exit-0 Exit immediately when there's no match
+ -f, --filter=STR Filter mode. Do not start interactive finder.
+ --print-query Print query as the first line
+ --expect=KEYS Comma-separated list of keys to complete fzf
+ --read0 Read input delimited by ASCII NUL characters
+ --print0 Print output delimited by ASCII NUL characters
+ --sync Synchronous search for multi-staged filtering
+ --with-shell=STR Shell command and flags to start child processes with
+ --listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
+ (To allow remote process execution, use --listen-unsafe)
+
+ Directory traversal (Only used when $FZF_DEFAULT_COMMAND is not set)
+ --walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden)
+ --walker-root=DIR [...] List of directories to walk (default: .)
+ --walker-skip=DIRS Comma-separated list of directory names to skip
+ (default: .git,node_modules)
Shell integration
- --bash Print script to set up Bash shell integration
- --zsh Print script to set up Zsh shell integration
- --fish Print script to set up Fish shell integration
+ --bash Print script to set up Bash shell integration
+ --zsh Print script to set up Zsh shell integration
+ --fish Print script to set up Fish shell integration
Help
- --version Display version information and exit
- --help Show this message
- --man Show man page
+ --version Display version information and exit
+ --help Show this message
+ --man Show man page
Environment variables
- 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_FILE Location of the file to read default options from
- FZF_API_KEY X-API-Key header for HTTP server (--listen)
+ 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_FILE Location of the file to read default options from
+ 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
type Options struct {
- Input chan string
- Output chan string
- NoWinpty bool
- Tmux *tmuxOptions
- ForceTtyIn bool
- ProxyScript string
- Bash bool
- Zsh bool
- Fish bool
- Man bool
- Fuzzy bool
- FuzzyAlgo algo.Algo
- Scheme string
- Extended bool
- Phony bool
- Case Case
- Normalize bool
- Nth []Range
- WithNth []Range
- Delimiter Delimiter
- Sort int
- Track trackOption
- Tac bool
- Tail int
- Criteria []criterion
- Multi int
- Ansi bool
- Mouse bool
- Theme *tui.ColorTheme
- Black bool
- Bold bool
- Height heightSpec
- MinHeight int
- Layout layoutType
- Cycle bool
- Wrap bool
- WrapSign *string
- MultiLine bool
- CursorLine bool
- KeepRight bool
- Hscroll bool
- HscrollOff int
- ScrollOff int
- FileWord bool
- InfoStyle infoStyle
- InfoPrefix string
- InfoCommand string
- Separator *string
- JumpLabels string
- Prompt string
- Pointer *string
- Marker *string
- MarkerMulti *[3]string
- Query string
- Select1 bool
- Exit0 bool
- Filter *string
- ToggleSort bool
- Expect map[tui.Event]string
- Keymap map[tui.Event][]*action
- Preview previewOpts
- PrintQuery bool
- ReadZero bool
- Printer func(string)
- PrintSep string
- Sync bool
- History *History
- Header []string
- HeaderLines int
- HeaderFirst bool
- Gap int
- Ellipsis *string
- Scrollbar *string
- Margin [4]sizeSpec
- Padding [4]sizeSpec
- BorderShape tui.BorderShape
- ListBorderShape tui.BorderShape
- BorderLabel labelOpts
- ListLabel labelOpts
- PreviewLabel labelOpts
- Unicode bool
- Ambidouble bool
- Tabstop int
- WithShell string
- ListenAddr *listenAddress
- Unsafe bool
- ClearOnExit bool
- WalkerOpts walkerOpts
- WalkerRoot []string
- WalkerSkip []string
- Version bool
- Help bool
- CPUProfile string
- MEMProfile string
- BlockProfile string
- MutexProfile string
+ Input chan string
+ Output chan string
+ NoWinpty bool
+ Tmux *tmuxOptions
+ ForceTtyIn bool
+ ProxyScript string
+ Bash bool
+ Zsh bool
+ Fish bool
+ Man bool
+ Fuzzy bool
+ FuzzyAlgo algo.Algo
+ Scheme string
+ Extended bool
+ Phony bool
+ Case Case
+ Normalize bool
+ Nth []Range
+ WithNth []Range
+ Delimiter Delimiter
+ Sort int
+ Track trackOption
+ Tac bool
+ Tail int
+ Criteria []criterion
+ Multi int
+ Ansi bool
+ Mouse bool
+ Theme *tui.ColorTheme
+ Black bool
+ Bold bool
+ Height heightSpec
+ MinHeight int
+ Layout layoutType
+ Cycle bool
+ Wrap bool
+ WrapSign *string
+ MultiLine bool
+ CursorLine bool
+ KeepRight bool
+ Hscroll bool
+ HscrollOff int
+ ScrollOff int
+ FileWord bool
+ InfoStyle infoStyle
+ InfoPrefix string
+ InfoCommand string
+ Separator *string
+ JumpLabels string
+ Prompt string
+ Pointer *string
+ Marker *string
+ MarkerMulti *[3]string
+ Query string
+ Select1 bool
+ Exit0 bool
+ Filter *string
+ ToggleSort bool
+ Expect map[tui.Event]string
+ Keymap map[tui.Event][]*action
+ Preview previewOpts
+ PrintQuery bool
+ ReadZero bool
+ Printer func(string)
+ PrintSep string
+ Sync bool
+ History *History
+ Header []string
+ HeaderLines int
+ HeaderFirst bool
+ Gap int
+ Ellipsis *string
+ Scrollbar *string
+ Margin [4]sizeSpec
+ Padding [4]sizeSpec
+ BorderShape tui.BorderShape
+ ListBorderShape tui.BorderShape
+ InputBorderShape tui.BorderShape
+ InputLabel labelOpts
+ BorderLabel labelOpts
+ ListLabel labelOpts
+ PreviewLabel labelOpts
+ Unicode bool
+ Ambidouble bool
+ Tabstop int
+ WithShell string
+ ListenAddr *listenAddress
+ Unsafe bool
+ ClearOnExit bool
+ WalkerOpts walkerOpts
+ WalkerRoot []string
+ WalkerSkip []string
+ Version bool
+ Help bool
+ CPUProfile string
+ MEMProfile string
+ BlockProfile string
+ MutexProfile string
}
func filterNonEmpty(input []string) []string {
@@ -1168,7 +1171,7 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
}
}
switch components[0] {
- case "query", "input":
+ case "query", "input", "input-fg":
mergeAttr(&theme.Input)
case "disabled":
mergeAttr(&theme.Disabled)
@@ -1220,6 +1223,12 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
mergeAttr(&theme.PreviewLabel)
case "prompt":
mergeAttr(&theme.Prompt)
+ case "input-bg":
+ mergeAttr(&theme.InputBg)
+ case "input-border":
+ mergeAttr(&theme.InputBorder)
+ case "input-label":
+ mergeAttr(&theme.InputLabel)
case "spinner":
mergeAttr(&theme.Spinner)
case "info":
@@ -1283,7 +1292,7 @@ const (
func init() {
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("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
@@ -1639,14 +1648,16 @@ func isExecuteAction(str string) actionType {
return actRebind
case "preview":
return actPreview
+ case "change-header":
+ return actChangeHeader
case "change-list-label":
return actChangeListLabel
case "change-border-label":
return actChangeBorderLabel
- case "change-header":
- return actChangeHeader
case "change-preview-label":
return actChangePreviewLabel
+ case "change-input-label":
+ return actChangeInputLabel
case "change-preview-window":
return actChangePreviewWindow
case "change-preview":
@@ -1677,6 +1688,8 @@ func isExecuteAction(str string) actionType {
return actTransformBorderLabel
case "transform-preview-label":
return actTransformPreviewLabel
+ case "transform-input-label":
+ return actTransformInputLabel
case "transform-header":
return actTransformHeader
case "transform-prompt":
@@ -2453,6 +2466,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parsePreviewWindow(&opts.Preview, str); err != nil {
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":
str, err := nextString(allArgs, &i, "height required: [~]HEIGHT[%]")
if err != nil {
@@ -2500,6 +2520,27 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parseLabelPosition(&opts.ListLabel, pos); err != nil {
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":
opts.BorderLabel.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 {
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 {
if opts.ListBorderShape, err = parseBorder(value, false); err != nil {
return err
@@ -2647,6 +2692,16 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parseLabelPosition(&opts.ListLabel, value); err != nil {
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 {
opts.BorderLabel.label = value
} else if match, value := optString(arg, "--border-label-pos="); match {
@@ -2922,6 +2977,10 @@ func postProcessOptions(opts *Options) error {
opts.ListBorderShape = tui.BorderNone
}
+ if opts.InputBorderShape == tui.BorderUndefined {
+ opts.InputBorderShape = tui.BorderNone
+ }
+
if opts.Pointer == nil {
defaultPointer := "▌"
if !opts.Unicode {
diff --git a/src/terminal.go b/src/terminal.go
index 23227a28..9489e4c5 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -164,6 +164,7 @@ type eachLine struct {
}
type itemLine struct {
+ valid bool
firstLine int
numLines int
cy int
@@ -179,11 +180,15 @@ type itemLine struct {
}
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) {
- 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 {
@@ -241,6 +246,9 @@ type Terminal struct {
previewLabel labelPrinter
previewLabelLen int
previewLabelOpts labelOpts
+ inputLabel labelPrinter
+ inputLabelLen int
+ inputLabelOpts labelOpts
pointer string
pointerLen int
pointerEmpty string
@@ -298,6 +306,7 @@ type Terminal struct {
listenUnsafe bool
borderShape tui.BorderShape
listBorderShape tui.BorderShape
+ inputBorderShape tui.BorderShape
listLabel labelPrinter
listLabelLen int
listLabelOpts labelOpts
@@ -306,6 +315,8 @@ type Terminal struct {
paused bool
border tui.Window
window tui.Window
+ inputWindow tui.Window
+ inputBorder tui.Window
wborder tui.Window
pborder tui.Window
pwindow tui.Window
@@ -394,6 +405,7 @@ const (
reqReinit
reqFullRedraw
reqResize
+ reqRedrawInputLabel
reqRedrawListLabel
reqRedrawBorderLabel
reqRedrawPreviewLabel
@@ -436,6 +448,7 @@ const (
actCancel
actChangeBorderLabel
actChangeListLabel
+ actChangeInputLabel
actChangeHeader
actChangeMulti
actChangePreviewLabel
@@ -497,6 +510,7 @@ const (
actTransform
actTransformBorderLabel
actTransformListLabel
+ actTransformInputLabel
actTransformHeader
actTransformPreviewLabel
actTransformPrompt
@@ -832,6 +846,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
listenUnsafe: opts.Unsafe,
borderShape: opts.BorderShape,
listBorderShape: opts.ListBorderShape,
+ inputBorderShape: opts.InputBorderShape,
borderWidth: 1,
listLabel: nil,
listLabelOpts: opts.ListLabel,
@@ -839,6 +854,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
borderLabelOpts: opts.BorderLabel,
previewLabel: nil,
previewLabelOpts: opts.PreviewLabel,
+ inputLabel: nil,
+ inputLabelOpts: opts.InputLabel,
cleanExit: opts.ClearOnExit,
executor: executor,
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.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, 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 := "─"
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 f0bb2fdf..3cb1a8bc 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 92336cd0..becdabcd 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 e2a891d0..832109ce 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 1042bfcd..89d32628 100755
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -2646,7 +2646,7 @@ class TestGoFZF < TestBase
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 @@ class TestGoFZF < TestBase
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