diff --git a/CHANGELOG.md b/CHANGELOG.md index fc8c03a977f..620d0dcd0cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ CHANGELOG ========= +0.58.0 +------ +- Additional border and label for the list section + - Options + - `--list-border[=STYLE]` + - `--list-label=LABEL` + - `--list-label-pos=COL[:bottom]` + - Colors + - `list-fg` + - `list-bg` + - `list-border` + - `list-label` + - Actions + - `change-list-label` + - `transform-list-label` + 0.57.0 ------ - You can now resize the preview window by dragging the border diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index b1601c70e28..0b0654b4ff9 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -552,33 +552,37 @@ color mappings. \fBbw \fRNo colors (equivalent to \fB\-\-no\-color\fR) .B COLOR NAMES: - \fBfg \fRText - \fBselected\-fg \fRSelected line text - \fBpreview\-fg \fRPreview window text - \fBbg \fRBackground - \fBselected\-bg \fRSelected line background - \fBpreview\-bg \fRPreview window 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 - \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) - \fBscrollbar \fRScrollbar - \fBpreview\-border \fRBorder around the preview window (\fB\-\-preview\fR) - \fBpreview\-scrollbar \fRScrollbar - \fBseparator \fRHorizontal separator on info line - \fBlabel \fRBorder label (\fB\-\-border\-label\fR and \fB\-\-preview\-label\fR) - \fBpreview\-label \fRBorder label of the preview window (\fB\-\-preview\-label\fR) - \fBprompt \fRPrompt - \fBpointer \fRPointer to the current line - \fBmarker \fRMulti\-select marker - \fBspinner \fRStreaming input indicator - \fBheader \fRHeader + \fBfg \fRText + \fBlist\-fg \fRText in the list section + \fBselected\-fg \fRSelected line text + \fBpreview\-fg \fRPreview window text + \fBbg \fRBackground + \fBlist\-bg \fRBackground in the list section + \fBselected\-bg \fRSelected line background + \fBpreview\-bg \fRPreview window 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 + \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) + \fBlist\-border \fRBorder around the list section (\fB\-\-list\-border\fR) + \fBscrollbar \fRScrollbar + \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) + \fBlist\-label \fRBorder label of the list section (\fB\-\-list\-label\fR) + \fBpreview\-label \fRBorder label of the preview window (\fB\-\-preview\-label\fR) + \fBprompt \fRPrompt + \fBpointer \fRPointer to the current line + \fBmarker \fRMulti\-select marker + \fBspinner \fRStreaming input indicator + \fBheader \fRHeader .B ANSI COLORS: \fB\-1 \fRDefault terminal foreground/background color @@ -1440,6 +1444,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\-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) \fBchange\-preview(...)\fR (change \fB\-\-preview\fR option) @@ -1524,6 +1529,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\-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) \fBtransform\-query(...)\fR (transform query string using an external command) diff --git a/src/actiontype_string.go b/src/actiontype_string.go index 8cbd76e8a23..675a77778d1 100644 --- a/src/actiontype_string.go +++ b/src/actiontype_string.go @@ -25,109 +25,111 @@ func _() { _ = x[actBackwardWord-14] _ = x[actCancel-15] _ = x[actChangeBorderLabel-16] - _ = x[actChangeHeader-17] - _ = x[actChangeMulti-18] - _ = x[actChangePreviewLabel-19] - _ = x[actChangePrompt-20] - _ = x[actChangeQuery-21] - _ = x[actClearScreen-22] - _ = x[actClearQuery-23] - _ = x[actClearSelection-24] - _ = x[actClose-25] - _ = x[actDeleteChar-26] - _ = x[actDeleteCharEof-27] - _ = x[actEndOfLine-28] - _ = x[actFatal-29] - _ = x[actForwardChar-30] - _ = x[actForwardWord-31] - _ = x[actKillLine-32] - _ = x[actKillWord-33] - _ = x[actUnixLineDiscard-34] - _ = x[actUnixWordRubout-35] - _ = x[actYank-36] - _ = x[actBackwardKillWord-37] - _ = x[actSelectAll-38] - _ = x[actDeselectAll-39] - _ = x[actToggle-40] - _ = x[actToggleSearch-41] - _ = x[actToggleAll-42] - _ = x[actToggleDown-43] - _ = x[actToggleUp-44] - _ = x[actToggleIn-45] - _ = x[actToggleOut-46] - _ = x[actToggleTrack-47] - _ = x[actToggleTrackCurrent-48] - _ = x[actToggleHeader-49] - _ = x[actToggleWrap-50] - _ = x[actToggleMultiLine-51] - _ = x[actToggleHscroll-52] - _ = x[actTrackCurrent-53] - _ = x[actUntrackCurrent-54] - _ = x[actDown-55] - _ = x[actUp-56] - _ = x[actPageUp-57] - _ = x[actPageDown-58] - _ = x[actPosition-59] - _ = x[actHalfPageUp-60] - _ = x[actHalfPageDown-61] - _ = x[actOffsetUp-62] - _ = x[actOffsetDown-63] - _ = x[actOffsetMiddle-64] - _ = x[actJump-65] - _ = x[actJumpAccept-66] - _ = x[actPrintQuery-67] - _ = x[actRefreshPreview-68] - _ = x[actReplaceQuery-69] - _ = x[actToggleSort-70] - _ = x[actShowPreview-71] - _ = x[actHidePreview-72] - _ = x[actTogglePreview-73] - _ = x[actTogglePreviewWrap-74] - _ = x[actTransform-75] - _ = x[actTransformBorderLabel-76] - _ = x[actTransformHeader-77] - _ = x[actTransformPreviewLabel-78] - _ = x[actTransformPrompt-79] - _ = x[actTransformQuery-80] - _ = x[actPreview-81] - _ = x[actChangePreview-82] - _ = x[actChangePreviewWindow-83] - _ = x[actPreviewTop-84] - _ = x[actPreviewBottom-85] - _ = x[actPreviewUp-86] - _ = x[actPreviewDown-87] - _ = x[actPreviewPageUp-88] - _ = x[actPreviewPageDown-89] - _ = x[actPreviewHalfPageUp-90] - _ = x[actPreviewHalfPageDown-91] - _ = x[actPrevHistory-92] - _ = x[actPrevSelected-93] - _ = x[actPrint-94] - _ = x[actPut-95] - _ = x[actNextHistory-96] - _ = x[actNextSelected-97] - _ = x[actExecute-98] - _ = x[actExecuteSilent-99] - _ = x[actExecuteMulti-100] - _ = x[actSigStop-101] - _ = x[actFirst-102] - _ = x[actLast-103] - _ = x[actReload-104] - _ = x[actReloadSync-105] - _ = x[actDisableSearch-106] - _ = x[actEnableSearch-107] - _ = x[actSelect-108] - _ = x[actDeselect-109] - _ = x[actUnbind-110] - _ = x[actRebind-111] - _ = x[actBecome-112] - _ = x[actShowHeader-113] - _ = x[actHideHeader-114] + _ = 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] } -const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader" +const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader" -var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 690, 708, 724, 739, 756, 763, 768, 777, 788, 799, 812, 827, 838, 851, 866, 873, 886, 899, 916, 931, 944, 958, 972, 988, 1008, 1020, 1043, 1061, 1085, 1103, 1120, 1130, 1146, 1168, 1181, 1197, 1209, 1223, 1239, 1257, 1277, 1299, 1313, 1328, 1336, 1342, 1356, 1371, 1381, 1397, 1412, 1422, 1430, 1437, 1446, 1459, 1475, 1490, 1499, 1510, 1519, 1528, 1537, 1550, 1563} +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} 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 c8e72ff17a5..bc0ddeb3c33 100644 --- a/src/options.go +++ b/src/options.go @@ -87,6 +87,14 @@ Usage: fzf [options] [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 @@ -436,100 +444,102 @@ 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 - BorderLabel 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 + 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 { @@ -1166,6 +1176,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro mergeAttr(&theme.Fg) case "bg": mergeAttr(&theme.Bg) + case "list-fg": + mergeAttr(&theme.ListFg) + case "list-bg": + mergeAttr(&theme.ListBg) case "preview-fg": mergeAttr(&theme.PreviewFg) case "preview-bg": @@ -1198,6 +1212,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro mergeAttr(&theme.PreviewScrollbar) case "label": mergeAttr(&theme.BorderLabel) + case "list-label": + mergeAttr(&theme.ListLabel) + case "list-border": + mergeAttr(&theme.ListBorder) case "preview-label": mergeAttr(&theme.PreviewLabel) case "prompt": @@ -1265,7 +1283,7 @@ const ( func init() { executeRegexp = regexp.MustCompile( - `(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-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)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put|print)`) splitRegexp = regexp.MustCompile("[,:]+") actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+") } @@ -1621,6 +1639,8 @@ func isExecuteAction(str string) actionType { return actRebind case "preview": return actPreview + case "change-list-label": + return actChangeListLabel case "change-border-label": return actChangeBorderLabel case "change-header": @@ -1651,6 +1671,8 @@ func isExecuteAction(str string) actionType { return actPut case "transform": return actTransform + case "transform-list-label": + return actTransformListLabel case "transform-border-label": return actTransformBorderLabel case "transform-preview-label": @@ -2456,6 +2478,28 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { if opts.BorderShape, err = parseBorder(arg, !hasArg); err != nil { return err } + case "--list-border": + hasArg, arg := optionalNextString(allArgs, &i) + if opts.ListBorderShape, err = parseBorder(arg, !hasArg); err != nil { + return err + } + case "--no-list-border": + opts.ListBorderShape = tui.BorderNone + case "--no-list-label": + opts.ListLabel.label = "" + case "--list-label": + opts.ListLabel.label, err = nextString(allArgs, &i, "label required") + if err != nil { + return err + } + case "--list-label-pos": + pos, err := nextString(allArgs, &i, "label position required (positive or negative integer or 'center')") + if err != nil { + return err + } + if err := parseLabelPosition(&opts.ListLabel, pos); err != nil { + return err + } case "--no-border-label": opts.BorderLabel.label = "" case "--border-label": @@ -2593,6 +2637,12 @@ 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, "--list-label="); match { + opts.ListLabel.label = value + } else if match, value := optString(arg, "--list-label-pos="); match { + if err := parseLabelPosition(&opts.ListLabel, 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 { @@ -2864,6 +2914,10 @@ func postProcessOptions(opts *Options) error { opts.BorderShape = tui.BorderNone } + if opts.ListBorderShape == tui.BorderUndefined { + opts.ListBorderShape = tui.BorderNone + } + if opts.Pointer == nil { defaultPointer := "▌" if !opts.Unicode { diff --git a/src/terminal.go b/src/terminal.go index 9b805123299..23227a28b60 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -297,11 +297,16 @@ type Terminal struct { listener net.Listener listenUnsafe bool borderShape tui.BorderShape + listBorderShape tui.BorderShape + listLabel labelPrinter + listLabelLen int + listLabelOpts labelOpts cleanExit bool executor *util.Executor paused bool border tui.Window window tui.Window + wborder tui.Window pborder tui.Window pwindow tui.Window borderWidth int @@ -389,6 +394,7 @@ const ( reqReinit reqFullRedraw reqResize + reqRedrawListLabel reqRedrawBorderLabel reqRedrawPreviewLabel reqClose @@ -429,6 +435,7 @@ const ( actBackwardWord actCancel actChangeBorderLabel + actChangeListLabel actChangeHeader actChangeMulti actChangePreviewLabel @@ -489,6 +496,7 @@ const ( actTogglePreviewWrap actTransform actTransformBorderLabel + actTransformListLabel actTransformHeader actTransformPreviewLabel actTransformPrompt @@ -823,7 +831,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor listenAddr: opts.ListenAddr, listenUnsafe: opts.Unsafe, borderShape: opts.BorderShape, + listBorderShape: opts.ListBorderShape, borderWidth: 1, + listLabel: nil, + listLabelOpts: opts.ListLabel, borderLabel: nil, borderLabelOpts: opts.BorderLabel, previewLabel: nil, @@ -880,10 +891,15 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor executing: util.NewAtomicBool(false), lastAction: actStart, lastFocus: minItem.Index()} + + // This should be called before accessing tui.Color* + tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black) + t.prompt, t.promptLen = t.parsePrompt(opts.Prompt) // Pre-calculated empty pointer and marker signs t.pointerEmpty = strings.Repeat(" ", t.pointerLen) t.markerEmpty = strings.Repeat(" ", t.markerLen) + 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 { @@ -973,6 +989,7 @@ func (t *Terminal) environ() []string { env = append(env, "FZF_PROMPT="+string(t.promptString)) env = append(env, "FZF_PREVIEW_LABEL="+t.previewLabelOpts.label) env = append(env, "FZF_BORDER_LABEL="+t.borderLabelOpts.label) + env = append(env, "FZF_LIST_LABEL="+t.listLabelOpts.label) env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count)) env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.merger.Length())) env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected))) @@ -1114,7 +1131,7 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) { // // unless the part has a non-default ANSI state loc := whiteSuffix.FindStringIndex(trimmed) if loc != nil { - blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear, -1, nil}} + blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{tui.ColPrompt.Fg(), tui.ColPrompt.Bg(), tui.AttrClear, -1, nil}} if item.colors != nil { lastColor := (*item.colors)[len(*item.colors)-1] if lastColor.offset[1] < int32(loc[1]) { @@ -1546,6 +1563,9 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { if t.window != nil { t.window = nil } + if t.wborder != nil { + t.wborder = nil + } if t.pborder != nil { t.pborder = nil } @@ -1572,10 +1592,10 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { offsets[1] -= 1 + bw offsets[2] += 1 + bw } - if t.border == nil && t.borderShape != tui.BorderNone { + if t.border == nil && t.borderShape.Visible() { t.border = t.tui.NewWindow( marginInt[0]+offsets[0], marginInt[3]+offsets[1], width+offsets[2], height+offsets[3], - false, tui.MakeBorderStyle(t.borderShape, t.unicode)) + tui.WindowBase, tui.MakeBorderStyle(t.borderShape, t.unicode), true) } // Add padding to margin @@ -1585,6 +1605,33 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { width -= paddingInt[1] + paddingInt[3] height -= paddingInt[0] + paddingInt[2] + hasListBorder := t.listBorderShape.Visible() + innerWidth := width + innerHeight := height + innerMarginInt := marginInt + 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) + } + } + if hasListBorder { + if t.listBorderShape.HasTop() { + innerHeight-- + innerMarginInt[0]++ + } + if t.listBorderShape.HasBottom() { + innerHeight-- + } + if t.listBorderShape.HasLeft() { + innerWidth -= 2 + innerMarginInt[3] += 2 + } + if t.listBorderShape.HasRight() { + innerWidth-- + } + } + t.areaLines = height t.areaColumns = width @@ -1592,6 +1639,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) if forcePreview || t.needPreviewWindow() { var resizePreviewWindows func(previewOpts *previewOpts) + stickToRight := false resizePreviewWindows = func(previewOpts *previewOpts) { t.activePreviewOpts = previewOpts if previewOpts.size.size == 0 { @@ -1602,7 +1650,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { pwidth := w pheight := h previewBorder := tui.MakeBorderStyle(previewOpts.border, t.unicode) - t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder) + t.pborder = t.tui.NewWindow(y, x, w, h, tui.WindowPreview, previewBorder, false) pwidth -= borderColumns(previewOpts.border, bw) pheight -= borderLines(previewOpts.border) if previewOpts.border.HasLeft() { @@ -1617,7 +1665,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { } pwidth = util.Max(0, pwidth) pheight = util.Max(0, pheight) - t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder) + t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, tui.WindowPreview, noBorder, true) if !hadPreviewWindow { t.pwindow.Erase() } @@ -1647,16 +1695,25 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { return } if previewOpts.position == posUp { + innerBorderFn(marginInt[0]+pheight, marginInt[3], width, height-pheight) t.window = t.tui.NewWindow( - marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder) + innerMarginInt[0]+pheight, innerMarginInt[3], innerWidth, innerHeight-pheight, 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( - marginInt[0], marginInt[3], width, height-pheight, false, noBorder) + innerMarginInt[0], innerMarginInt[3], innerWidth, innerHeight-pheight, tui.WindowList, noBorder, true) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight) } case posLeft, posRight: - pwidth := calculateSize(width, previewOpts.size, minWidth, minPreviewWidth) + minListWidth := minWidth + if t.listBorderShape.HasLeft() { + minListWidth += 2 + } + if t.listBorderShape.HasRight() { + minListWidth++ + } + pwidth := calculateSize(width, previewOpts.size, minListWidth, minPreviewWidth) if hasThreshold && pwidth < previewOpts.threshold { t.activePreviewOpts = previewOpts.alternative if forcePreview { @@ -1675,52 +1732,83 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { } if previewOpts.position == posLeft { // Put scrollbar closer to the right border for consistent look - if t.borderShape.HasRight() { - width++ + if t.borderShape.HasRight() && !hasListBorder { + innerWidth++ } // Add a 1-column margin between the preview window and the main window + m := 0 + if !hasListBorder { + m = 1 + } t.window = t.tui.NewWindow( - marginInt[0], marginInt[3]+pwidth+1, width-pwidth-1, height, false, noBorder) + innerMarginInt[0], innerMarginInt[3]+pwidth+m, innerWidth-pwidth-m, innerHeight, tui.WindowList, noBorder, true) // Clear characters on the margin // fzf --bind 'space:preview(seq 100)' --preview-window left,1 - for y := 0; y < height; y++ { - t.window.Move(y, -1) - t.window.Print(" ") + if !hasListBorder { + for y := 0; y < innerHeight; y++ { + t.window.Move(y, -1) + t.window.Print(" ") + } } + innerBorderFn(marginInt[0], marginInt[3]+pwidth, width-pwidth, height) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height) } else { // NOTE: fzf --preview 'cat {}' --preview-window border-left --border - if !previewOpts.border.HasRight() && t.borderShape.HasRight() { + stickToRight = !previewOpts.border.HasRight() && t.borderShape.HasRight() + if stickToRight { + innerWidth++ width++ } + innerBorderFn(marginInt[0], marginInt[3], width-pwidth, height) t.window = t.tui.NewWindow( - marginInt[0], marginInt[3], width-pwidth, height, false, noBorder) + innerMarginInt[0], innerMarginInt[3], innerWidth-pwidth, innerHeight, tui.WindowList, noBorder, true) x := marginInt[3] + width - pwidth createPreviewWindow(marginInt[0], x, pwidth, height) } } } resizePreviewWindows(&t.previewOpts) + + if t.borderShape.HasRight() && !stickToRight { + // Need to clear the extra margin between the borders + // fzf --preview 'seq 1000' --preview-window border-left --bind space:change-preview-window:border-rounded --border vertical + // fzf --preview 'seq 1000' --preview-window up,hidden --bind space:toggle-preview --border vertical + y := 0 + if t.borderShape.HasTop() { + y++ + } + maxY := t.border.Height() + if t.borderShape.HasBottom() { + maxY-- + } + for ; y < maxY; y++ { + t.border.Move(y, t.border.Width()-2) + t.border.Print(" ") + } + } } else { t.activePreviewOpts = &t.previewOpts } // Without preview window if t.window == nil { - if t.borderShape.HasRight() { + if t.borderShape.HasRight() && !hasListBorder { // Put scrollbar closer to the right border for consistent look + innerWidth++ width++ } + innerBorderFn(marginInt[0], marginInt[3], width, height) t.window = t.tui.NewWindow( - marginInt[0], - marginInt[3], - width, - height, false, noBorder) + innerMarginInt[0], + innerMarginInt[3], + innerWidth, + innerHeight, tui.WindowList, 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) } @@ -1839,6 +1927,9 @@ func (t *Terminal) trimMessage(message string, maxWidth int) string { } func (t *Terminal) printInfo() { + if t.window.Width() <= 1 { + return + } pos := 0 line := t.promptLine() maxHeight := t.window.Height() @@ -1977,7 +2068,9 @@ func (t *Terminal) printInfo() { } else { outputPrinter(t.window, maxWidth-1) } - t.window.Print(" ") // Margin + if fillLength >= 0 { + t.window.Print(" ") // Margin + } return } @@ -2115,7 +2208,7 @@ func (t *Terminal) printList() { func (t *Terminal) printBar(lineNum int, forceRedraw bool, barRange [2]int) bool { hasBar := lineNum >= barRange[0] && lineNum < barRange[1] - if hasBar != t.prevLines[lineNum].hasBar || forceRedraw { + if (hasBar != t.prevLines[lineNum].hasBar || forceRedraw) && t.window.Width() > 0 { t.move(lineNum, t.window.Width()-1, true) if len(t.scrollbar) > 0 && hasBar { t.window.CPrint(tui.ColScrollbar, t.scrollbar) @@ -2212,11 +2305,18 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu } if current { preTask := func(marker markerClass) { + w := t.window.Width() - t.pointerLen + if w < 0 { + return + } if len(label) == 0 { t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty) } else { t.window.CPrint(tui.ColCurrentCursor, label) } + if w-t.markerLen < 0 { + return + } if selected { t.window.CPrint(tui.ColCurrentMarker, markerFor(marker)) } else { @@ -2226,11 +2326,18 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu finalLineNum = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true, line, maxLine, forceRedraw, preTask, postTask) } else { preTask := func(marker markerClass) { + w := t.window.Width() - t.pointerLen + if w < 0 { + return + } if len(label) == 0 { t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty) } else { t.window.CPrint(tui.ColCursor, label) } + if w-t.markerLen < 0 { + return + } if selected { t.window.CPrint(tui.ColMarker, markerFor(marker)) } else { @@ -2946,7 +3053,7 @@ func (t *Terminal) flush() { t.placeCursor() if !t.suppress { windows := make([]tui.Window, 0, 4) - if t.borderShape != tui.BorderNone { + if t.borderShape.Visible() { windows = append(windows, t.border) } if t.hasPreviewWindow() { @@ -3855,6 +3962,8 @@ func (t *Terminal) Loop() error { if t.hasPreviewer() { t.previewBox.Set(reqPreviewReady, nil) } + case reqRedrawListLabel: + t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, true) case reqRedrawBorderLabel: t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true) case reqRedrawPreviewLabel: @@ -3952,7 +4061,7 @@ func (t *Terminal) Loop() error { previewDraggingPos := -1 barDragging := false pbarDragging := false - pborderDragging := false + pborderDragging := -1 wasDown := false needBarrier := true @@ -4236,6 +4345,12 @@ func (t *Terminal) Loop() error { } else { req(reqHeader) } + case actChangeListLabel: + t.listLabelOpts.label = a.a + if t.wborder != nil { + t.listLabel, t.listLabelLen = t.ansiLabelPrinter(a.a, &tui.ColListLabel, false) + req(reqRedrawListLabel) + } case actChangeBorderLabel: t.borderLabelOpts.label = a.a if t.border != nil { @@ -4253,6 +4368,13 @@ func (t *Terminal) Loop() error { if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil { return doActions(actions) } + case actTransformListLabel: + label := t.executeCommand(a.a, false, true, true, true, "") + t.listLabelOpts.label = label + if t.wborder != nil { + t.listLabel, t.listLabelLen = t.ansiLabelPrinter(label, &tui.ColListLabel, false) + req(reqRedrawListLabel) + } case actTransformBorderLabel: label := t.executeCommand(a.a, false, true, true, true, "") t.borderLabelOpts.label = label @@ -4684,7 +4806,7 @@ func (t *Terminal) Loop() error { if !me.Down { barDragging = false pbarDragging = false - pborderDragging = false + pborderDragging = -1 previewDraggingPos = -1 } @@ -4740,20 +4862,36 @@ func (t *Terminal) Loop() error { } // Preview border dragging (resizing) - if !pborderDragging && clicked && t.hasPreviewWindow() && t.pborder.Enclose(my, mx) { + if pborderDragging < 0 && clicked && t.hasPreviewWindow() { switch t.activePreviewOpts.position { case posUp: - pborderDragging = my == t.pborder.Top()+t.pborder.Height()-1 + if t.pborder.Enclose(my, mx) && my == t.pborder.Top()+t.pborder.Height()-1 { + pborderDragging = 0 + } else if t.listBorderShape.HasTop() && t.wborder.Enclose(my, mx) && my == t.wborder.Top() { + pborderDragging = 1 + } case posDown: - pborderDragging = my == t.pborder.Top() + if t.pborder.Enclose(my, mx) && my == t.pborder.Top() { + pborderDragging = 0 + } else if t.listBorderShape.HasBottom() && t.wborder.Enclose(my, mx) && my == t.wborder.Top()+t.wborder.Height()-1 { + pborderDragging = 1 + } case posLeft: - pborderDragging = mx == t.pborder.Left()+t.pborder.Width()-1 + if t.pborder.Enclose(my, mx) && mx == t.pborder.Left()+t.pborder.Width()-1 { + pborderDragging = 0 + } else if t.listBorderShape.HasLeft() && t.wborder.Enclose(my, mx) && mx == t.wborder.Left() { + pborderDragging = 1 + } case posRight: - pborderDragging = mx == t.pborder.Left() + if t.pborder.Enclose(my, mx) && mx == t.pborder.Left() { + pborderDragging = 0 + } else if t.listBorderShape.HasRight() && t.wborder.Enclose(my, mx) && mx == t.wborder.Left()+t.wborder.Width()-1 { + pborderDragging = 1 + } } } - if pborderDragging { + if pborderDragging >= 0 { var newSize int var prevSize int switch t.activePreviewOpts.position { @@ -4774,6 +4912,7 @@ func (t *Terminal) Loop() error { offset := mx - t.pborder.Left() newSize = prevSize - offset } + newSize -= pborderDragging if newSize < 1 { newSize = 1 } diff --git a/src/tui/dummy.go b/src/tui/dummy.go index 1a761460163..a49677c6c87 100644 --- a/src/tui/dummy.go +++ b/src/tui/dummy.go @@ -30,6 +30,7 @@ const ( ) func (r *FullscreenRenderer) Init() error { return nil } +func (r *FullscreenRenderer) DefaultTheme() *ColorTheme { return nil } func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {} func (r *FullscreenRenderer) Pause(bool) {} func (r *FullscreenRenderer) Resume(bool, bool) {} @@ -48,6 +49,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 } func (r *FullscreenRenderer) RefreshWindows(windows []Window) {} -func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window { +func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window { return nil } diff --git a/src/tui/light.go b/src/tui/light.go index 95984542b65..f0bb2fdfc6b 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -116,19 +116,19 @@ type LightRenderer struct { } type LightWindow struct { - renderer *LightRenderer - colored bool - preview bool - border BorderStyle - top int - left int - width int - height int - posx int - posy int - tabstop int - fg Color - bg Color + renderer *LightRenderer + colored bool + windowType WindowType + border BorderStyle + top int + left int + width int + height int + posx int + posy int + tabstop int + fg Color + bg Color } func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) { @@ -174,7 +174,6 @@ func (r *LightRenderer) Init() error { return err } r.updateTerminalSize() - initTheme(r.theme, r.defaultTheme(), r.forceBlack) if r.fullscreen { r.smcup() @@ -780,27 +779,32 @@ func (r *LightRenderer) MaxY() int { return r.height } -func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window { +func (r *LightRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window { w := &LightWindow{ - renderer: r, - colored: r.theme.Colored, - preview: preview, - border: borderStyle, - top: top, - left: left, - width: width, - height: height, - tabstop: r.tabstop, - fg: colDefault, - bg: colDefault} - if preview { - w.fg = r.theme.PreviewFg.Color - w.bg = r.theme.PreviewBg.Color - } else { + renderer: r, + colored: r.theme.Colored, + windowType: windowType, + border: borderStyle, + top: top, + left: left, + width: width, + height: height, + tabstop: r.tabstop, + fg: colDefault, + bg: colDefault} + switch windowType { + case WindowBase: w.fg = r.theme.Fg.Color w.bg = r.theme.Bg.Color + case WindowList: + w.fg = r.theme.ListFg.Color + w.bg = r.theme.ListBg.Color + case WindowPreview: + w.fg = r.theme.PreviewFg.Color + w.bg = r.theme.PreviewBg.Color } - if !w.bg.IsDefault() && w.border.shape != BorderNone { + if erase && !w.bg.IsDefault() && w.border.shape != BorderNone { + // fzf --color bg:blue --border --padding 1,2 w.Erase() } w.drawBorder(false) @@ -845,7 +849,10 @@ func (w *LightWindow) drawBorder(onlyHorizontal bool) { func (w *LightWindow) drawBorderHorizontal(top, bottom bool) { color := ColBorder - if w.preview { + switch w.windowType { + case WindowList: + color = ColListBorder + case WindowPreview: color = ColPreviewBorder } hw := runeWidth(w.border.top) @@ -863,7 +870,10 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) { func (w *LightWindow) drawBorderVertical(left, right bool) { vw := runeWidth(w.border.left) color := ColBorder - if w.preview { + switch w.windowType { + case WindowList: + color = ColListBorder + case WindowPreview: color = ColPreviewBorder } for y := 0; y < w.height; y++ { @@ -883,7 +893,10 @@ func (w *LightWindow) drawBorderVertical(left, right bool) { func (w *LightWindow) drawBorderAround(onlyHorizontal bool) { w.Move(0, 0) color := ColBorder - if w.preview { + switch w.windowType { + case WindowList: + color = ColListBorder + case WindowPreview: color = ColPreviewBorder } hw := runeWidth(w.border.top) diff --git a/src/tui/light_unix.go b/src/tui/light_unix.go index c2d5612aef2..76aac2eb096 100644 --- a/src/tui/light_unix.go +++ b/src/tui/light_unix.go @@ -18,7 +18,7 @@ func IsLightRendererSupported() bool { return true } -func (r *LightRenderer) defaultTheme() *ColorTheme { +func (r *LightRenderer) DefaultTheme() *ColorTheme { if strings.Contains(os.Getenv("TERM"), "256") { return Dark256 } diff --git a/src/tui/light_windows.go b/src/tui/light_windows.go index e2a2bbfe19b..cf5126abdbc 100644 --- a/src/tui/light_windows.go +++ b/src/tui/light_windows.go @@ -39,7 +39,7 @@ func IsLightRendererSupported() bool { return canSetVt100 } -func (r *LightRenderer) defaultTheme() *ColorTheme { +func (r *LightRenderer) DefaultTheme() *ColorTheme { // the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178: if !IsLightRendererSupported() || os.Getenv("ConEmuPID") != "" || os.Getenv("TCELL_TRUECOLOR") == "disable" { return Default16 diff --git a/src/tui/tcell.go b/src/tui/tcell.go index a3ce2cb19c4..92336cd0073 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -40,7 +40,7 @@ type Attr int32 type TcellWindow struct { color bool - preview bool + windowType WindowType top int left int width int @@ -106,8 +106,12 @@ func (r *FullscreenRenderer) PassThrough(str string) { func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {} -func (r *FullscreenRenderer) defaultTheme() *ColorTheme { - if _screen.Colors() >= 256 { +func (r *FullscreenRenderer) DefaultTheme() *ColorTheme { + s, e := r.getScreen() + if e != nil { + return Default16 + } + if s.Colors() >= 256 { return Dark256 } return Default16 @@ -148,8 +152,19 @@ var ( _initialResize bool = true ) +func (r *FullscreenRenderer) getScreen() (tcell.Screen, error) { + if _screen == nil { + s, e := tcell.NewScreen() + if e != nil { + return nil, e + } + _screen = s + } + return _screen, nil +} + func (r *FullscreenRenderer) initScreen() error { - s, e := tcell.NewScreen() + s, e := r.getScreen() if e != nil { return e } @@ -161,7 +176,6 @@ func (r *FullscreenRenderer) initScreen() error { } else { s.DisableMouse() } - _screen = s return nil } @@ -174,7 +188,6 @@ func (r *FullscreenRenderer) Init() error { if err := r.initScreen(); err != nil { return err } - initTheme(r.theme, r.defaultTheme(), r.forceBlack) return nil } @@ -537,14 +550,17 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) { _screen.Show() } -func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window { - normal := ColNormal - if preview { - normal = ColPreview +func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window { + normal := ColBorder + switch windowType { + case WindowList: + normal = ColListBorder + case WindowPreview: + normal = ColPreviewBorder } w := &TcellWindow{ color: r.theme.Colored, - preview: preview, + windowType: windowType, top: top, left: left, width: width, @@ -564,11 +580,7 @@ func fill(x, y, w, h int, n ColorPair, r rune) { } func (w *TcellWindow) Erase() { - if w.borderStyle.shape.HasLeft() { - fill(w.left-1, w.top, w.width, w.height-1, w.normal, ' ') - } else { - fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ') - } + fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ') w.drawBorder(false) } @@ -768,10 +780,13 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) { var style tcell.Style if w.color { - if w.preview { - style = ColPreviewBorder.style() - } else { + switch w.windowType { + case WindowBase: style = ColBorder.style() + case WindowList: + style = ColListBorder.style() + case WindowPreview: + style = ColPreviewBorder.style() } } else { style = w.normal.style() diff --git a/src/tui/tui.go b/src/tui/tui.go index 902d13fe14b..e2a891d07d9 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -303,6 +303,8 @@ type ColorTheme struct { Disabled ColorAttr Fg ColorAttr Bg ColorAttr + ListFg ColorAttr + ListBg ColorAttr SelectedFg ColorAttr SelectedBg ColorAttr SelectedMatch ColorAttr @@ -323,9 +325,11 @@ type ColorTheme struct { Scrollbar ColorAttr Border ColorAttr PreviewBorder ColorAttr + PreviewLabel ColorAttr PreviewScrollbar ColorAttr BorderLabel ColorAttr - PreviewLabel ColorAttr + ListLabel ColorAttr + ListBorder ColorAttr } type Event struct { @@ -395,6 +399,10 @@ func (s BorderShape) HasBottom() bool { return true } +func (s BorderShape) Visible() bool { + return s != BorderNone +} + type BorderStyle struct { shape BorderShape top rune @@ -525,7 +533,16 @@ type TermSize struct { PxHeight int } +type WindowType int + +const ( + WindowBase WindowType = iota + WindowList + WindowPreview +) + type Renderer interface { + DefaultTheme() *ColorTheme Init() error Resize(maxHeightFunc func(int) int) Pause(clear bool) @@ -546,7 +563,7 @@ type Renderer interface { Size() TermSize - NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window + NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window } type Window interface { @@ -627,6 +644,8 @@ var ( ColPreviewLabel ColorPair ColPreviewScrollbar ColorPair ColPreviewSpinner ColorPair + ColListBorder ColorPair + ColListLabel ColorPair ) func EmptyTheme() *ColorTheme { @@ -635,6 +654,8 @@ func EmptyTheme() *ColorTheme { Input: ColorAttr{colUndefined, AttrUndefined}, Fg: ColorAttr{colUndefined, AttrUndefined}, Bg: ColorAttr{colUndefined, AttrUndefined}, + ListFg: ColorAttr{colUndefined, AttrUndefined}, + ListBg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, @@ -650,6 +671,8 @@ func EmptyTheme() *ColorTheme { Header: ColorAttr{colUndefined, AttrUndefined}, Border: ColorAttr{colUndefined, AttrUndefined}, BorderLabel: ColorAttr{colUndefined, AttrUndefined}, + ListLabel: ColorAttr{colUndefined, AttrUndefined}, + ListBorder: ColorAttr{colUndefined, AttrUndefined}, Disabled: ColorAttr{colUndefined, AttrUndefined}, PreviewFg: ColorAttr{colUndefined, AttrUndefined}, PreviewBg: ColorAttr{colUndefined, AttrUndefined}, @@ -668,6 +691,8 @@ func NoColorTheme() *ColorTheme { Input: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined}, + ListFg: ColorAttr{colDefault, AttrUndefined}, + ListBg: ColorAttr{colDefault, AttrUndefined}, SelectedFg: ColorAttr{colDefault, AttrUndefined}, SelectedBg: ColorAttr{colDefault, AttrUndefined}, SelectedMatch: ColorAttr{colDefault, AttrUndefined}, @@ -690,6 +715,8 @@ func NoColorTheme() *ColorTheme { PreviewBorder: ColorAttr{colDefault, AttrUndefined}, PreviewScrollbar: ColorAttr{colDefault, AttrUndefined}, PreviewLabel: ColorAttr{colDefault, AttrUndefined}, + ListLabel: ColorAttr{colDefault, AttrUndefined}, + ListBorder: ColorAttr{colDefault, AttrUndefined}, Separator: ColorAttr{colDefault, AttrUndefined}, Scrollbar: ColorAttr{colDefault, AttrUndefined}, } @@ -701,6 +728,8 @@ func init() { Input: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined}, + ListFg: ColorAttr{colUndefined, AttrUndefined}, + ListBg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, @@ -723,6 +752,8 @@ func init() { PreviewBorder: ColorAttr{colUndefined, AttrUndefined}, PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, + ListLabel: ColorAttr{colUndefined, AttrUndefined}, + ListBorder: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined}, } @@ -731,6 +762,8 @@ func init() { Input: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined}, + ListFg: ColorAttr{colUndefined, AttrUndefined}, + ListBg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, @@ -753,6 +786,8 @@ func init() { PreviewBorder: ColorAttr{colUndefined, AttrUndefined}, PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, + ListLabel: ColorAttr{colUndefined, AttrUndefined}, + ListBorder: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined}, } @@ -761,6 +796,8 @@ func init() { Input: ColorAttr{colDefault, AttrUndefined}, Fg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined}, + ListFg: ColorAttr{colUndefined, AttrUndefined}, + ListBg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, @@ -783,12 +820,14 @@ func init() { PreviewBorder: ColorAttr{colUndefined, AttrUndefined}, PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined}, PreviewLabel: ColorAttr{colUndefined, AttrUndefined}, + ListLabel: ColorAttr{colUndefined, AttrUndefined}, + ListBorder: ColorAttr{colUndefined, AttrUndefined}, Separator: ColorAttr{colUndefined, AttrUndefined}, Scrollbar: ColorAttr{colUndefined, AttrUndefined}, } } -func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) { +func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) { if forceBlack { theme.Bg = ColorAttr{colBlack, AttrUndefined} } @@ -820,8 +859,10 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) { theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel) // These colors are not defined in the base themes - theme.SelectedFg = o(theme.Fg, theme.SelectedFg) - theme.SelectedBg = o(theme.Bg, theme.SelectedBg) + theme.ListFg = o(theme.Fg, theme.ListFg) + theme.ListBg = o(theme.Bg, theme.ListBg) + theme.SelectedFg = o(theme.ListFg, theme.SelectedFg) + theme.SelectedBg = o(theme.ListBg, theme.SelectedBg) theme.SelectedMatch = o(theme.Match, theme.SelectedMatch) theme.Disabled = o(theme.Input, theme.Disabled) theme.Gutter = o(theme.DarkBg, theme.Gutter) @@ -829,8 +870,10 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) { theme.PreviewBg = o(theme.Bg, theme.PreviewBg) theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel) theme.PreviewBorder = o(theme.Border, theme.PreviewBorder) - theme.Separator = o(theme.Border, theme.Separator) - theme.Scrollbar = o(theme.Border, theme.Scrollbar) + theme.ListLabel = o(theme.BorderLabel, theme.ListLabel) + theme.ListBorder = o(theme.Border, theme.ListBorder) + theme.Separator = o(theme.ListBorder, theme.Separator) + theme.Scrollbar = o(theme.ListBorder, theme.Scrollbar) theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar) initPalette(theme) @@ -843,19 +886,19 @@ func initPalette(theme *ColorTheme) { } return ColorPair{fg.Color, bg.Color, fg.Attr} } - blank := theme.Fg + blank := theme.ListFg blank.Attr = AttrRegular - ColPrompt = pair(theme.Prompt, theme.Bg) - ColNormal = pair(theme.Fg, theme.Bg) + ColPrompt = pair(theme.Prompt, theme.ListBg) + ColNormal = pair(theme.ListFg, theme.ListBg) ColSelected = pair(theme.SelectedFg, theme.SelectedBg) - ColInput = pair(theme.Input, theme.Bg) - ColDisabled = pair(theme.Disabled, theme.Bg) - ColMatch = pair(theme.Match, theme.Bg) + ColInput = pair(theme.Input, theme.ListBg) + ColDisabled = pair(theme.Disabled, theme.ListBg) + ColMatch = pair(theme.Match, theme.ListBg) ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg) ColCursor = pair(theme.Cursor, theme.Gutter) ColCursorEmpty = pair(blank, theme.Gutter) - if theme.SelectedBg.Color != theme.Bg.Color { + if theme.SelectedBg.Color != theme.ListBg.Color { ColMarker = pair(theme.Marker, theme.SelectedBg) } else { ColMarker = pair(theme.Marker, theme.Gutter) @@ -866,11 +909,11 @@ 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.Bg) - ColInfo = pair(theme.Info, theme.Bg) - ColHeader = pair(theme.Header, theme.Bg) - ColSeparator = pair(theme.Separator, theme.Bg) - ColScrollbar = pair(theme.Scrollbar, theme.Bg) + ColSpinner = pair(theme.Spinner, theme.ListBg) + ColInfo = pair(theme.Info, theme.ListBg) + ColHeader = pair(theme.Header, theme.ListBg) + ColSeparator = pair(theme.Separator, theme.ListBg) + ColScrollbar = pair(theme.Scrollbar, theme.ListBg) ColBorder = pair(theme.Border, theme.Bg) ColBorderLabel = pair(theme.BorderLabel, theme.Bg) ColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg) @@ -878,6 +921,8 @@ func initPalette(theme *ColorTheme) { ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg) ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg) ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg) + ColListLabel = pair(theme.ListLabel, theme.ListBg) + ColListBorder = pair(theme.ListBorder, theme.ListBg) } func runeWidth(r rune) int {