Compare commits

..

2 Commits

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

View File

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

View File

@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf\-tmux 1 "Dec 2024" "fzf 0.57.0" "fzf\-tmux - open fzf in tmux split pane" .TH fzf\-tmux 1 "Jan 2025" "fzf 0.58.0" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf\-tmux - open fzf in tmux split pane fzf\-tmux - open fzf in tmux split pane

View File

@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Dec 2024" "fzf 0.57.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Jan 2025" "fzf 0.58.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@ -557,16 +557,17 @@ color mappings.
\fBselected\-fg \fRSelected line text \fBselected\-fg \fRSelected line text
\fBpreview\-fg \fRPreview window text \fBpreview\-fg \fRPreview window text
\fBbg \fRBackground \fBbg \fRBackground
\fBlist\-bg \fRBackground in the list section \fBlist\-bg \fRList section background
\fBselected\-bg \fRSelected line background \fBselected\-bg \fRSelected line background
\fBpreview\-bg \fRPreview window background \fBpreview\-bg \fRPreview window background
\fBinput\-bg \fRInput section background
\fBhl \fRHighlighted substrings \fBhl \fRHighlighted substrings
\fBselected\-hl \fRHighlighted substrings in the selected line \fBselected\-hl \fRHighlighted substrings in the selected line
\fBcurrent\-fg (fg+) \fRText (current line) \fBcurrent\-fg (fg+) \fRText (current line)
\fBcurrent\-bg (bg+) \fRBackground (current line) \fBcurrent\-bg (bg+) \fRBackground (current line)
\fBgutter \fRGutter on the left \fBgutter \fRGutter on the left
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line) \fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
\fBquery \fRQuery string \fBquery (input\-fg) \fRQuery string
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR) \fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
\fBinfo \fRInfo line (match counters) \fBinfo \fRInfo line (match counters)
\fBborder \fRBorder around the window (\fB\-\-border\fR and \fB\-\-preview\fR) \fBborder \fRBorder around the window (\fB\-\-border\fR and \fB\-\-preview\fR)
@ -575,9 +576,11 @@ color mappings.
\fBseparator \fRHorizontal separator on info line \fBseparator \fRHorizontal separator on info line
\fBpreview\-border \fRBorder around the preview window (\fB\-\-preview\fR) \fBpreview\-border \fRBorder around the preview window (\fB\-\-preview\fR)
\fBpreview\-scrollbar \fRScrollbar \fBpreview\-scrollbar \fRScrollbar
\fBlabel \fRBorder label (\fB\-\-border\-label\fR, \fB\-\-list\-label\fR, and \fB\-\-preview\-label\fR) \fBinput\-border \fRBorder around the input section (\fB\-\-input\-border\fR)
\fBlabel \fRBorder label (\fB\-\-border\-label\fR, \fB\-\-list\-label\fR, \fB\-\-input\-label\fR, and \fB\-\-preview\-label\fR)
\fBlist\-label \fRBorder label of the list section (\fB\-\-list\-label\fR) \fBlist\-label \fRBorder label of the list section (\fB\-\-list\-label\fR)
\fBpreview\-label \fRBorder label of the preview window (\fB\-\-preview\-label\fR) \fBpreview\-label \fRBorder label of the preview window (\fB\-\-preview\-label\fR)
\fBinput\-label \fRBorder label of the input section (\fB\-\-input\-label\fR)
\fBprompt \fRPrompt \fBprompt \fRPrompt
\fBpointer \fRPointer to the current line \fBpointer \fRPointer to the current line
\fBmarker \fRMulti\-select marker \fBmarker \fRMulti\-select marker
@ -728,6 +731,10 @@ e.g.
.RE .RE
.TP
.BI "\-\-preview\-border" [=STYLE]
Short for \fB\-\-preview\-window=border\-STYLE\fR
.TP .TP
.BI "\-\-preview\-label" [=LABEL] .BI "\-\-preview\-label" [=LABEL]
Label to print on the horizontal border line of the preview window. Label to print on the horizontal border line of the preview window.
@ -1444,6 +1451,7 @@ A key or an event can be bound to one or more of the following actions.
\fBcancel\fR (clear query string if not empty, abort fzf otherwise) \fBcancel\fR (clear query string if not empty, abort fzf otherwise)
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string) \fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR) \fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
\fBchange\-list\-label(...)\fR (change \fB\-\-list\-label\fR to the given string) \fBchange\-list\-label(...)\fR (change \fB\-\-list\-label\fR to the given string)
\fBchange\-multi\fR (enable multi-select mode with no limit) \fBchange\-multi\fR (enable multi-select mode with no limit)
\fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0) \fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
@ -1529,6 +1537,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtransform(...)\fR (transform states using the output of an external command) \fBtransform(...)\fR (transform states using the output of an external command)
\fBtransform\-border\-label(...)\fR (transform border label using an external command) \fBtransform\-border\-label(...)\fR (transform border label using an external command)
\fBtransform\-header(...)\fR (transform header using an external command) \fBtransform\-header(...)\fR (transform header using an external command)
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
\fBtransform\-list\-label(...)\fR (transform list label using an external command) \fBtransform\-list\-label(...)\fR (transform list label using an external command)
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command) \fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
\fBtransform\-prompt(...)\fR (transform prompt string using an external command) \fBtransform\-prompt(...)\fR (transform prompt string using an external command)

View File

@ -26,110 +26,112 @@ func _() {
_ = x[actCancel-15] _ = x[actCancel-15]
_ = x[actChangeBorderLabel-16] _ = x[actChangeBorderLabel-16]
_ = x[actChangeListLabel-17] _ = x[actChangeListLabel-17]
_ = x[actChangeHeader-18] _ = x[actChangeInputLabel-18]
_ = x[actChangeMulti-19] _ = x[actChangeHeader-19]
_ = x[actChangePreviewLabel-20] _ = x[actChangeMulti-20]
_ = x[actChangePrompt-21] _ = x[actChangePreviewLabel-21]
_ = x[actChangeQuery-22] _ = x[actChangePrompt-22]
_ = x[actClearScreen-23] _ = x[actChangeQuery-23]
_ = x[actClearQuery-24] _ = x[actClearScreen-24]
_ = x[actClearSelection-25] _ = x[actClearQuery-25]
_ = x[actClose-26] _ = x[actClearSelection-26]
_ = x[actDeleteChar-27] _ = x[actClose-27]
_ = x[actDeleteCharEof-28] _ = x[actDeleteChar-28]
_ = x[actEndOfLine-29] _ = x[actDeleteCharEof-29]
_ = x[actFatal-30] _ = x[actEndOfLine-30]
_ = x[actForwardChar-31] _ = x[actFatal-31]
_ = x[actForwardWord-32] _ = x[actForwardChar-32]
_ = x[actKillLine-33] _ = x[actForwardWord-33]
_ = x[actKillWord-34] _ = x[actKillLine-34]
_ = x[actUnixLineDiscard-35] _ = x[actKillWord-35]
_ = x[actUnixWordRubout-36] _ = x[actUnixLineDiscard-36]
_ = x[actYank-37] _ = x[actUnixWordRubout-37]
_ = x[actBackwardKillWord-38] _ = x[actYank-38]
_ = x[actSelectAll-39] _ = x[actBackwardKillWord-39]
_ = x[actDeselectAll-40] _ = x[actSelectAll-40]
_ = x[actToggle-41] _ = x[actDeselectAll-41]
_ = x[actToggleSearch-42] _ = x[actToggle-42]
_ = x[actToggleAll-43] _ = x[actToggleSearch-43]
_ = x[actToggleDown-44] _ = x[actToggleAll-44]
_ = x[actToggleUp-45] _ = x[actToggleDown-45]
_ = x[actToggleIn-46] _ = x[actToggleUp-46]
_ = x[actToggleOut-47] _ = x[actToggleIn-47]
_ = x[actToggleTrack-48] _ = x[actToggleOut-48]
_ = x[actToggleTrackCurrent-49] _ = x[actToggleTrack-49]
_ = x[actToggleHeader-50] _ = x[actToggleTrackCurrent-50]
_ = x[actToggleWrap-51] _ = x[actToggleHeader-51]
_ = x[actToggleMultiLine-52] _ = x[actToggleWrap-52]
_ = x[actToggleHscroll-53] _ = x[actToggleMultiLine-53]
_ = x[actTrackCurrent-54] _ = x[actToggleHscroll-54]
_ = x[actUntrackCurrent-55] _ = x[actTrackCurrent-55]
_ = x[actDown-56] _ = x[actUntrackCurrent-56]
_ = x[actUp-57] _ = x[actDown-57]
_ = x[actPageUp-58] _ = x[actUp-58]
_ = x[actPageDown-59] _ = x[actPageUp-59]
_ = x[actPosition-60] _ = x[actPageDown-60]
_ = x[actHalfPageUp-61] _ = x[actPosition-61]
_ = x[actHalfPageDown-62] _ = x[actHalfPageUp-62]
_ = x[actOffsetUp-63] _ = x[actHalfPageDown-63]
_ = x[actOffsetDown-64] _ = x[actOffsetUp-64]
_ = x[actOffsetMiddle-65] _ = x[actOffsetDown-65]
_ = x[actJump-66] _ = x[actOffsetMiddle-66]
_ = x[actJumpAccept-67] _ = x[actJump-67]
_ = x[actPrintQuery-68] _ = x[actJumpAccept-68]
_ = x[actRefreshPreview-69] _ = x[actPrintQuery-69]
_ = x[actReplaceQuery-70] _ = x[actRefreshPreview-70]
_ = x[actToggleSort-71] _ = x[actReplaceQuery-71]
_ = x[actShowPreview-72] _ = x[actToggleSort-72]
_ = x[actHidePreview-73] _ = x[actShowPreview-73]
_ = x[actTogglePreview-74] _ = x[actHidePreview-74]
_ = x[actTogglePreviewWrap-75] _ = x[actTogglePreview-75]
_ = x[actTransform-76] _ = x[actTogglePreviewWrap-76]
_ = x[actTransformBorderLabel-77] _ = x[actTransform-77]
_ = x[actTransformListLabel-78] _ = x[actTransformBorderLabel-78]
_ = x[actTransformHeader-79] _ = x[actTransformListLabel-79]
_ = x[actTransformPreviewLabel-80] _ = x[actTransformInputLabel-80]
_ = x[actTransformPrompt-81] _ = x[actTransformHeader-81]
_ = x[actTransformQuery-82] _ = x[actTransformPreviewLabel-82]
_ = x[actPreview-83] _ = x[actTransformPrompt-83]
_ = x[actChangePreview-84] _ = x[actTransformQuery-84]
_ = x[actChangePreviewWindow-85] _ = x[actPreview-85]
_ = x[actPreviewTop-86] _ = x[actChangePreview-86]
_ = x[actPreviewBottom-87] _ = x[actChangePreviewWindow-87]
_ = x[actPreviewUp-88] _ = x[actPreviewTop-88]
_ = x[actPreviewDown-89] _ = x[actPreviewBottom-89]
_ = x[actPreviewPageUp-90] _ = x[actPreviewUp-90]
_ = x[actPreviewPageDown-91] _ = x[actPreviewDown-91]
_ = x[actPreviewHalfPageUp-92] _ = x[actPreviewPageUp-92]
_ = x[actPreviewHalfPageDown-93] _ = x[actPreviewPageDown-93]
_ = x[actPrevHistory-94] _ = x[actPreviewHalfPageUp-94]
_ = x[actPrevSelected-95] _ = x[actPreviewHalfPageDown-95]
_ = x[actPrint-96] _ = x[actPrevHistory-96]
_ = x[actPut-97] _ = x[actPrevSelected-97]
_ = x[actNextHistory-98] _ = x[actPrint-98]
_ = x[actNextSelected-99] _ = x[actPut-99]
_ = x[actExecute-100] _ = x[actNextHistory-100]
_ = x[actExecuteSilent-101] _ = x[actNextSelected-101]
_ = x[actExecuteMulti-102] _ = x[actExecute-102]
_ = x[actSigStop-103] _ = x[actExecuteSilent-103]
_ = x[actFirst-104] _ = x[actExecuteMulti-104]
_ = x[actLast-105] _ = x[actSigStop-105]
_ = x[actReload-106] _ = x[actFirst-106]
_ = x[actReloadSync-107] _ = x[actLast-107]
_ = x[actDisableSearch-108] _ = x[actReload-108]
_ = x[actEnableSearch-109] _ = x[actReloadSync-109]
_ = x[actSelect-110] _ = x[actDisableSearch-110]
_ = x[actDeselect-111] _ = x[actEnableSearch-111]
_ = x[actUnbind-112] _ = x[actSelect-112]
_ = x[actRebind-113] _ = x[actDeselect-113]
_ = x[actBecome-114] _ = x[actUnbind-114]
_ = x[actShowHeader-115] _ = x[actRebind-115]
_ = x[actHideHeader-116] _ = x[actBecome-116]
_ = x[actShowHeader-117]
_ = x[actHideHeader-118]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader" const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 260, 274, 295, 310, 324, 338, 351, 368, 376, 389, 405, 417, 425, 439, 453, 464, 475, 493, 510, 517, 536, 548, 562, 571, 586, 598, 611, 622, 633, 645, 659, 680, 695, 708, 726, 742, 757, 774, 781, 786, 795, 806, 817, 830, 845, 856, 869, 884, 891, 904, 917, 934, 949, 962, 976, 990, 1006, 1026, 1038, 1061, 1082, 1100, 1124, 1142, 1159, 1169, 1185, 1207, 1220, 1236, 1248, 1262, 1278, 1296, 1316, 1338, 1352, 1367, 1375, 1381, 1395, 1410, 1420, 1436, 1451, 1461, 1469, 1476, 1485, 1498, 1514, 1529, 1538, 1549, 1558, 1567, 1576, 1589, 1602} var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 293, 314, 329, 343, 357, 370, 387, 395, 408, 424, 436, 444, 458, 472, 483, 494, 512, 529, 536, 555, 567, 581, 590, 605, 617, 630, 641, 652, 664, 678, 699, 714, 727, 745, 761, 776, 793, 800, 805, 814, 825, 836, 849, 864, 875, 888, 903, 910, 923, 936, 953, 968, 981, 995, 1009, 1025, 1045, 1057, 1080, 1101, 1123, 1141, 1165, 1183, 1200, 1210, 1226, 1248, 1261, 1277, 1289, 1303, 1319, 1337, 1357, 1379, 1393, 1408, 1416, 1422, 1436, 1451, 1461, 1477, 1492, 1502, 1510, 1517, 1526, 1539, 1555, 1570, 1579, 1590, 1599, 1608, 1617, 1630, 1643}
func (i actionType) String() string { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@ -133,6 +133,7 @@ Usage: fzf [options]
[,[no]hidden][,border-BORDER_OPT] [,[no]hidden][,border-BORDER_OPT]
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES] [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)] [,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
--preview-border[=STYLE] Short for --preview-window=border-STYLE
--preview-label=LABEL --preview-label=LABEL
--preview-label-pos=N Same as --border-label and --border-label-pos, --preview-label-pos=N Same as --border-label and --border-label-pos,
but for preview window but for preview window
@ -521,6 +522,8 @@ type Options struct {
Padding [4]sizeSpec Padding [4]sizeSpec
BorderShape tui.BorderShape BorderShape tui.BorderShape
ListBorderShape tui.BorderShape ListBorderShape tui.BorderShape
InputBorderShape tui.BorderShape
InputLabel labelOpts
BorderLabel labelOpts BorderLabel labelOpts
ListLabel labelOpts ListLabel labelOpts
PreviewLabel labelOpts PreviewLabel labelOpts
@ -1168,7 +1171,7 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
} }
} }
switch components[0] { switch components[0] {
case "query", "input": case "query", "input", "input-fg":
mergeAttr(&theme.Input) mergeAttr(&theme.Input)
case "disabled": case "disabled":
mergeAttr(&theme.Disabled) mergeAttr(&theme.Disabled)
@ -1220,6 +1223,12 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
mergeAttr(&theme.PreviewLabel) mergeAttr(&theme.PreviewLabel)
case "prompt": case "prompt":
mergeAttr(&theme.Prompt) mergeAttr(&theme.Prompt)
case "input-bg":
mergeAttr(&theme.InputBg)
case "input-border":
mergeAttr(&theme.InputBorder)
case "input-label":
mergeAttr(&theme.InputLabel)
case "spinner": case "spinner":
mergeAttr(&theme.Spinner) mergeAttr(&theme.Spinner)
case "info": case "info":
@ -1283,7 +1292,7 @@ const (
func init() { func init() {
executeRegexp = regexp.MustCompile( executeRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|list-label|preview-label)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put|print)`) `(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|list-label|preview-label|input-label)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put|print)`)
splitRegexp = regexp.MustCompile("[,:]+") splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+") actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
} }
@ -1639,14 +1648,16 @@ func isExecuteAction(str string) actionType {
return actRebind return actRebind
case "preview": case "preview":
return actPreview return actPreview
case "change-header":
return actChangeHeader
case "change-list-label": case "change-list-label":
return actChangeListLabel return actChangeListLabel
case "change-border-label": case "change-border-label":
return actChangeBorderLabel return actChangeBorderLabel
case "change-header":
return actChangeHeader
case "change-preview-label": case "change-preview-label":
return actChangePreviewLabel return actChangePreviewLabel
case "change-input-label":
return actChangeInputLabel
case "change-preview-window": case "change-preview-window":
return actChangePreviewWindow return actChangePreviewWindow
case "change-preview": case "change-preview":
@ -1677,6 +1688,8 @@ func isExecuteAction(str string) actionType {
return actTransformBorderLabel return actTransformBorderLabel
case "transform-preview-label": case "transform-preview-label":
return actTransformPreviewLabel return actTransformPreviewLabel
case "transform-input-label":
return actTransformInputLabel
case "transform-header": case "transform-header":
return actTransformHeader return actTransformHeader
case "transform-prompt": case "transform-prompt":
@ -2453,6 +2466,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parsePreviewWindow(&opts.Preview, str); err != nil { if err := parsePreviewWindow(&opts.Preview, str); err != nil {
return err return err
} }
case "--no-preview-border":
opts.Preview.border = tui.BorderNone
case "--preview-border":
hasArg, arg := optionalNextString(allArgs, &i)
if opts.Preview.border, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--height": case "--height":
str, err := nextString(allArgs, &i, "height required: [~]HEIGHT[%]") str, err := nextString(allArgs, &i, "height required: [~]HEIGHT[%]")
if err != nil { if err != nil {
@ -2500,6 +2520,27 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parseLabelPosition(&opts.ListLabel, pos); err != nil { if err := parseLabelPosition(&opts.ListLabel, pos); err != nil {
return err return err
} }
case "--no-input-border":
opts.InputBorderShape = tui.BorderNone
case "--input-border":
hasArg, arg := optionalNextString(allArgs, &i)
if opts.InputBorderShape, err = parseBorder(arg, !hasArg); err != nil {
return err
}
case "--no-input-label":
opts.InputLabel.label = ""
case "--input-label":
if opts.InputLabel.label, err = nextString(allArgs, &i, "input label required"); err != nil {
return err
}
case "--input-label-pos":
pos, err := nextString(allArgs, &i, "input label position required (positive or negative integer or 'center')")
if err != nil {
return err
}
if err := parseLabelPosition(&opts.InputLabel, pos); err != nil {
return err
}
case "--no-border-label": case "--no-border-label":
opts.BorderLabel.label = "" opts.BorderLabel.label = ""
case "--border-label": case "--border-label":
@ -2637,6 +2678,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.BorderShape, err = parseBorder(value, false); err != nil { if opts.BorderShape, err = parseBorder(value, false); err != nil {
return err return err
} }
} else if match, value := optString(arg, "--preview-border="); match {
if opts.Preview.border, err = parseBorder(value, false); err != nil {
return err
}
} else if match, value := optString(arg, "--list-border="); match { } else if match, value := optString(arg, "--list-border="); match {
if opts.ListBorderShape, err = parseBorder(value, false); err != nil { if opts.ListBorderShape, err = parseBorder(value, false); err != nil {
return err return err
@ -2647,6 +2692,16 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parseLabelPosition(&opts.ListLabel, value); err != nil { if err := parseLabelPosition(&opts.ListLabel, value); err != nil {
return err return err
} }
} else if match, value := optString(arg, "--input-border="); match {
if opts.InputBorderShape, err = parseBorder(value, false); err != nil {
return err
}
} else if match, value := optString(arg, "--input-label="); match {
opts.InputLabel.label = value
} else if match, value := optString(arg, "--input-label-pos="); match {
if err := parseLabelPosition(&opts.InputLabel, value); err != nil {
return err
}
} else if match, value := optString(arg, "--border-label="); match { } else if match, value := optString(arg, "--border-label="); match {
opts.BorderLabel.label = value opts.BorderLabel.label = value
} else if match, value := optString(arg, "--border-label-pos="); match { } else if match, value := optString(arg, "--border-label-pos="); match {
@ -2922,6 +2977,10 @@ func postProcessOptions(opts *Options) error {
opts.ListBorderShape = tui.BorderNone opts.ListBorderShape = tui.BorderNone
} }
if opts.InputBorderShape == tui.BorderUndefined {
opts.InputBorderShape = tui.BorderNone
}
if opts.Pointer == nil { if opts.Pointer == nil {
defaultPointer := "▌" defaultPointer := "▌"
if !opts.Unicode { if !opts.Unicode {

View File

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

View File

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

View File

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

View File

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

View File

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