m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-15 06:43:47 -05:00

Introduce 'raw' mode

This commit is contained in:
Junegunn Choi
2025-09-28 20:59:20 +09:00
parent b51bc6b50e
commit 65df0abf0e
11 changed files with 554 additions and 187 deletions

View File

@@ -3,30 +3,98 @@ CHANGELOG
0.66.0 0.66.0
------ ------
- Style changes
- Updated `--color base16` (alias: `16`) theme so that it works better with both dark and light themes.
- Narrowed the gutter column by using the left-half block character (`▌`).
- Removed background colors from markers.
- Added `--gutter CHAR` option for customizing the gutter column. Some examples using [box-drawing characters](https://en.wikipedia.org/wiki/Box-drawing_characters):
```sh
# Right-aligned gutter
fzf --gutter '▐'
# Even thinner gutter ### Introducing "raw" mode
fzf --gutter '▎'
# Checker In "raw" mode, non-matching items are also displayed in their original position,
fzf --gutter '▚' but dimmed. This is useful when you want to see the surrounding items of a match
to understand the context. Raw mode can be enabled by using `--raw` option, but
I find it more useful when toggled dynamically using `toggle-raw` action.
# Dotted ```sh
fzf --gutter '▖' export FZF_CTRL_R_OPTS='--bind ctrl-x:toggle-raw'
```
# Full-width ```sh
fzf --gutter '█' tree | fzf --raw --reverse --bind ctrl-x:toggle-raw
```
# No gutter While non-matching items are displayed in dimmed color, they are treated just
fzf --gutter ' ' like matching items in the list, so you place the cursor on them and do any
``` action against them. But if you want to navigate through matching items only,
you can use `down-match` and `up-match` actions, which are from now on bound to
`CTRL-N` and `CTRL-P` respectively. Historically, `CTRL-N` and `CTRL-P` are
bound to `next-history` and `prev-history` when `--history` option is used, so
in that case, you'll have to manually bind the actions to the keys of your
choice, or you can use `ALT-DOWN` and `ALT-UP` instead.
#### Customizing the look
##### Gutter
To distinguish the raw mode, the gutter column is rendered in dashed line using
`▖` character. But you can customize it using `--gutter-raw CHAR` option.
```sh
# If you don't liked the dashed line and you just want a thinner gutter
fzf --bind ctrl-x:toggle-raw --gutter-raw ▎
```
##### Color and style of non-matching items
Non-matching items are displayed in dimmed color by default, but you can change
it using `--color hidden:...` option.
```sh
fzf --raw --color hidden:red:strikethrough
# To unset the default 'dim' attribute, prefix the color spec with 'regular'
fzf --raw --color hidden:regular:red:strikethrough
```
### Style changes
This version introduces some minor changes to the traditional visual style of fzf.
- Narrowed the gutter column by using the left-half block character (`▌`).
- Removed background colors from markers.
- Updated `--color base16` (alias: `16`) theme so that it works better with both dark and light themes.
### Added options
#### `--gutter CHAR`
Added `--gutter CHAR` option for customizing the gutter column. Some examples using [box-drawing characters](https://en.wikipedia.org/wiki/Box-drawing_characters):
```sh
# Right-aligned gutter
fzf --gutter '▐'
# Even thinner gutter
fzf --gutter '▎'
# Checker
fzf --gutter '▚'
# Dotted
fzf --gutter '▖'
# Full-width
fzf --gutter '█'
# No gutter
fzf --gutter ' '
```
#### `--gutter-raw CHAR`
As mentioned above, also added `--gutter-raw CHAR` option for customizing the gutter column in raw mode.
### Compatibility changes
Starting from this version, fzf is built with Go 1.23. Support for some old OS versions has been dropped.
See https://go.dev/wiki/MinimumRequirements.
0.65.2 0.65.2
------ ------

View File

@@ -299,6 +299,7 @@ color mappings. Each entry is separated by a comma and/or whitespaces.
\fBheader (header\-fg) \fRHeader \fBheader (header\-fg) \fRHeader
\fBfooter (footer\-fg) \fRFooter \fBfooter (footer\-fg) \fRFooter
\fBnth \fRParts of the line specified by \fB\-\-nth\fR (only supports attributes) \fBnth \fRParts of the line specified by \fB\-\-nth\fR (only supports attributes)
\fBhidden \fRNon-matching items in raw mode (default: \fBdim\fR)
.B ANSI COLORS: .B ANSI COLORS:
\fB\-1 \fRDefault terminal foreground/background color \fB\-1 \fRDefault terminal foreground/background color
@@ -596,6 +597,9 @@ Indicator for wrapped lines. The default is '↳ ' or '> ' depending on
.B "\-\-no\-multi\-line" .B "\-\-no\-multi\-line"
Disable multi-line display of items when using \fB\-\-read0\fR Disable multi-line display of items when using \fB\-\-read0\fR
.TP .TP
.B "\-\-raw"
Enable raw mode where non-matching items are also displayed in a dimmed color.
.TP
.B "\-\-track" .B "\-\-track"
Make fzf track the current selection when the result list is updated. Make fzf track the current selection when the result list is updated.
This can be useful when browsing logs using fzf with sorting disabled. It is This can be useful when browsing logs using fzf with sorting disabled. It is
@@ -1841,9 +1845,11 @@ A key or an event can be bound to one or more of the following actions.
\fBdelete\-char\fR \fIdel\fR \fBdelete\-char\fR \fIdel\fR
\fBdelete\-char/eof\fR \fIctrl\-d\fR (same as \fBdelete\-char\fR except aborts fzf if query is empty) \fBdelete\-char/eof\fR \fIctrl\-d\fR (same as \fBdelete\-char\fR except aborts fzf if query is empty)
\fBdeselect\fR \fBdeselect\fR
\fBdeselect\-all\fR (deselect all matches; to also clear non-matched selections, use \fBclear\-multi\fR) \fBdeselect\-all\fR (deselect all matches; to also clear non-matching selections, use \fBclear\-multi\fR)
\fBdisable\-search\fR (disable search functionality) \fBdisable\-search\fR (disable search functionality)
\fBdown\fR \fIctrl\-j ctrl\-n down\fR \fBdown\fR \fIctrl\-j down\fR
\fBdown\-match\fR \fIctrl\-n\fR \fIalt\-down\fR (move to the match below the cursor)
\fBdown\-selected\fR (move to the selected item below the cursor)
\fBenable\-search\fR (enable search functionality) \fBenable\-search\fR (enable search functionality)
\fBend\-of\-line\fR \fIctrl\-e end\fR \fBend\-of\-line\fR \fIctrl\-e end\fR
\fBexclude\fR (exclude the current item from the result) \fBexclude\fR (exclude the current item from the result)
@@ -1861,7 +1867,7 @@ A key or an event can be bound to one or more of the following actions.
\fBkill\-word\fR \fIalt\-d\fR \fBkill\-word\fR \fIalt\-d\fR
\fBlast\fR (move to the last match; same as \fBpos(\-1)\fR) \fBlast\fR (move to the last match; same as \fBpos(\-1)\fR)
\fBnext\-history\fR (\fIctrl\-n\fR on \fB\-\-history\fR) \fBnext\-history\fR (\fIctrl\-n\fR on \fB\-\-history\fR)
\fBnext\-selected\fR (move to the next selected item) \fBnext\-selected\fR (synonym to \fBdown\-selected\fR)
\fBpage\-down\fR \fIpgdn\fR \fBpage\-down\fR \fIpgdn\fR
\fBpage\-up\fR \fIpgup\fR \fBpage\-up\fR \fIpgup\fR
\fBhalf\-page\-down\fR \fBhalf\-page\-down\fR
@@ -1874,7 +1880,7 @@ A key or an event can be bound to one or more of the following actions.
\fBoffset\-middle\fR (place the current item is in the middle of the screen) \fBoffset\-middle\fR (place the current item is in the middle of the screen)
\fBpos(...)\fR (move cursor to the numeric position; negative number to count from the end) \fBpos(...)\fR (move cursor to the numeric position; negative number to count from the end)
\fBprev\-history\fR (\fIctrl\-p\fR on \fB\-\-history\fR) \fBprev\-history\fR (\fIctrl\-p\fR on \fB\-\-history\fR)
\fBprev\-selected\fR (move to the previous selected item) \fBprev\-selected\fR (synonym to \fBup\-selected\fR)
\fBpreview(...)\fR (see below for the details) \fBpreview(...)\fR (see below for the details)
\fBpreview\-down\fR \fIshift\-down\fR \fBpreview\-down\fR \fIshift\-down\fR
\fBpreview\-up\fR \fIshift\-up\fR \fBpreview\-up\fR \fIshift\-up\fR
@@ -1909,6 +1915,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle\-multi\-line\fR \fBtoggle\-multi\-line\fR
\fBtoggle\-preview\fR \fBtoggle\-preview\fR
\fBtoggle\-preview\-wrap\fR \fBtoggle\-preview\-wrap\fR
\fBtoggle\-raw\fR
\fBtoggle\-search\fR (toggle search functionality) \fBtoggle\-search\fR (toggle search functionality)
\fBtoggle\-sort\fR \fBtoggle\-sort\fR
\fBtoggle\-track\fR (toggle global tracking option (\fB\-\-track\fR)) \fBtoggle\-track\fR (toggle global tracking option (\fB\-\-track\fR))
@@ -1935,7 +1942,9 @@ A key or an event can be bound to one or more of the following actions.
\fBunix\-line\-discard\fR \fIctrl\-u\fR \fBunix\-line\-discard\fR \fIctrl\-u\fR
\fBunix\-word\-rubout\fR \fIctrl\-w\fR \fBunix\-word\-rubout\fR \fIctrl\-w\fR
\fBuntrack\-current\fR (stop tracking the current item; no-op if global tracking is enabled) \fBuntrack\-current\fR (stop tracking the current item; no-op if global tracking is enabled)
\fBup\fR \fIctrl\-k ctrl\-p up\fR \fBup\fR \fIctrl\-k up\fR
\fBup\-match\fR \fIctrl\-p\fR \fIalt\-up\fR (move to the match above the cursor)
\fBup\-selected\fR (move to the selected item above the cursor)
\fByank\fR \fIctrl\-y\fR \fByank\fR \fIctrl\-y\fR
Each \fBtransform*\fR action has a corresponding \fBbg\-transform*\fR Each \fBtransform*\fR action has a corresponding \fBbg\-transform*\fR

View File

@@ -77,106 +77,109 @@ func _() {
_ = x[actToggleWrap-66] _ = x[actToggleWrap-66]
_ = x[actToggleMultiLine-67] _ = x[actToggleMultiLine-67]
_ = x[actToggleHscroll-68] _ = x[actToggleHscroll-68]
_ = x[actTrackCurrent-69] _ = x[actToggleRaw-69]
_ = x[actToggleInput-70] _ = x[actTrackCurrent-70]
_ = x[actHideInput-71] _ = x[actToggleInput-71]
_ = x[actShowInput-72] _ = x[actHideInput-72]
_ = x[actUntrackCurrent-73] _ = x[actShowInput-73]
_ = x[actDown-74] _ = x[actUntrackCurrent-74]
_ = x[actUp-75] _ = x[actDown-75]
_ = x[actPageUp-76] _ = x[actDownMatch-76]
_ = x[actPageDown-77] _ = x[actUp-77]
_ = x[actPosition-78] _ = x[actUpMatch-78]
_ = x[actHalfPageUp-79] _ = x[actPageUp-79]
_ = x[actHalfPageDown-80] _ = x[actPageDown-80]
_ = x[actOffsetUp-81] _ = x[actPosition-81]
_ = x[actOffsetDown-82] _ = x[actHalfPageUp-82]
_ = x[actOffsetMiddle-83] _ = x[actHalfPageDown-83]
_ = x[actJump-84] _ = x[actOffsetUp-84]
_ = x[actJumpAccept-85] _ = x[actOffsetDown-85]
_ = x[actPrintQuery-86] _ = x[actOffsetMiddle-86]
_ = x[actRefreshPreview-87] _ = x[actJump-87]
_ = x[actReplaceQuery-88] _ = x[actJumpAccept-88]
_ = x[actToggleSort-89] _ = x[actPrintQuery-89]
_ = x[actShowPreview-90] _ = x[actRefreshPreview-90]
_ = x[actHidePreview-91] _ = x[actReplaceQuery-91]
_ = x[actTogglePreview-92] _ = x[actToggleSort-92]
_ = x[actTogglePreviewWrap-93] _ = x[actShowPreview-93]
_ = x[actTransform-94] _ = x[actHidePreview-94]
_ = x[actTransformBorderLabel-95] _ = x[actTogglePreview-95]
_ = x[actTransformGhost-96] _ = x[actTogglePreviewWrap-96]
_ = x[actTransformHeader-97] _ = x[actTransform-97]
_ = x[actTransformFooter-98] _ = x[actTransformBorderLabel-98]
_ = x[actTransformHeaderLabel-99] _ = x[actTransformGhost-99]
_ = x[actTransformFooterLabel-100] _ = x[actTransformHeader-100]
_ = x[actTransformInputLabel-101] _ = x[actTransformFooter-101]
_ = x[actTransformListLabel-102] _ = x[actTransformHeaderLabel-102]
_ = x[actTransformNth-103] _ = x[actTransformFooterLabel-103]
_ = x[actTransformPointer-104] _ = x[actTransformInputLabel-104]
_ = x[actTransformPreviewLabel-105] _ = x[actTransformListLabel-105]
_ = x[actTransformPrompt-106] _ = x[actTransformNth-106]
_ = x[actTransformQuery-107] _ = x[actTransformPointer-107]
_ = x[actTransformSearch-108] _ = x[actTransformPreviewLabel-108]
_ = x[actTrigger-109] _ = x[actTransformPrompt-109]
_ = x[actBgTransform-110] _ = x[actTransformQuery-110]
_ = x[actBgTransformBorderLabel-111] _ = x[actTransformSearch-111]
_ = x[actBgTransformGhost-112] _ = x[actTrigger-112]
_ = x[actBgTransformHeader-113] _ = x[actBgTransform-113]
_ = x[actBgTransformFooter-114] _ = x[actBgTransformBorderLabel-114]
_ = x[actBgTransformHeaderLabel-115] _ = x[actBgTransformGhost-115]
_ = x[actBgTransformFooterLabel-116] _ = x[actBgTransformHeader-116]
_ = x[actBgTransformInputLabel-117] _ = x[actBgTransformFooter-117]
_ = x[actBgTransformListLabel-118] _ = x[actBgTransformHeaderLabel-118]
_ = x[actBgTransformNth-119] _ = x[actBgTransformFooterLabel-119]
_ = x[actBgTransformPointer-120] _ = x[actBgTransformInputLabel-120]
_ = x[actBgTransformPreviewLabel-121] _ = x[actBgTransformListLabel-121]
_ = x[actBgTransformPrompt-122] _ = x[actBgTransformNth-122]
_ = x[actBgTransformQuery-123] _ = x[actBgTransformPointer-123]
_ = x[actBgTransformSearch-124] _ = x[actBgTransformPreviewLabel-124]
_ = x[actBgCancel-125] _ = x[actBgTransformPrompt-125]
_ = x[actSearch-126] _ = x[actBgTransformQuery-126]
_ = x[actPreview-127] _ = x[actBgTransformSearch-127]
_ = x[actPreviewTop-128] _ = x[actBgCancel-128]
_ = x[actPreviewBottom-129] _ = x[actSearch-129]
_ = x[actPreviewUp-130] _ = x[actPreview-130]
_ = x[actPreviewDown-131] _ = x[actPreviewTop-131]
_ = x[actPreviewPageUp-132] _ = x[actPreviewBottom-132]
_ = x[actPreviewPageDown-133] _ = x[actPreviewUp-133]
_ = x[actPreviewHalfPageUp-134] _ = x[actPreviewDown-134]
_ = x[actPreviewHalfPageDown-135] _ = x[actPreviewPageUp-135]
_ = x[actPrevHistory-136] _ = x[actPreviewPageDown-136]
_ = x[actPrevSelected-137] _ = x[actPreviewHalfPageUp-137]
_ = x[actPrint-138] _ = x[actPreviewHalfPageDown-138]
_ = x[actPut-139] _ = x[actPrevHistory-139]
_ = x[actNextHistory-140] _ = x[actPrevSelected-140]
_ = x[actNextSelected-141] _ = x[actPrint-141]
_ = x[actExecute-142] _ = x[actPut-142]
_ = x[actExecuteSilent-143] _ = x[actNextHistory-143]
_ = x[actExecuteMulti-144] _ = x[actNextSelected-144]
_ = x[actSigStop-145] _ = x[actExecute-145]
_ = x[actFirst-146] _ = x[actExecuteSilent-146]
_ = x[actLast-147] _ = x[actExecuteMulti-147]
_ = x[actReload-148] _ = x[actSigStop-148]
_ = x[actReloadSync-149] _ = x[actFirst-149]
_ = x[actDisableSearch-150] _ = x[actLast-150]
_ = x[actEnableSearch-151] _ = x[actReload-151]
_ = x[actSelect-152] _ = x[actReloadSync-152]
_ = x[actDeselect-153] _ = x[actDisableSearch-153]
_ = x[actUnbind-154] _ = x[actEnableSearch-154]
_ = x[actRebind-155] _ = x[actSelect-155]
_ = x[actToggleBind-156] _ = x[actDeselect-156]
_ = x[actBecome-157] _ = x[actUnbind-157]
_ = x[actShowHeader-158] _ = x[actRebind-158]
_ = x[actHideHeader-159] _ = x[actToggleBind-159]
_ = x[actBell-160] _ = x[actBecome-160]
_ = x[actExclude-161] _ = x[actShowHeader-161]
_ = x[actExcludeMulti-162] _ = x[actHideHeader-162]
_ = x[actAsync-163] _ = x[actBell-163]
_ = x[actExclude-164]
_ = x[actExcludeMulti-165]
_ = x[actAsync-166]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync" const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactToggleRawactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactDownMatchactUpactUpMatchactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 258, 267, 287, 301, 316, 331, 351, 371, 390, 408, 422, 434, 450, 466, 487, 509, 524, 538, 552, 565, 582, 590, 603, 619, 631, 639, 653, 667, 684, 695, 706, 720, 738, 755, 762, 781, 803, 815, 829, 838, 853, 865, 878, 889, 900, 912, 926, 947, 962, 975, 993, 1009, 1024, 1038, 1050, 1062, 1079, 1086, 1091, 1100, 1111, 1122, 1135, 1150, 1161, 1174, 1189, 1196, 1209, 1222, 1239, 1254, 1267, 1281, 1295, 1311, 1331, 1343, 1366, 1383, 1401, 1419, 1442, 1465, 1487, 1508, 1523, 1542, 1566, 1584, 1601, 1619, 1629, 1643, 1668, 1687, 1707, 1727, 1752, 1777, 1801, 1824, 1841, 1862, 1888, 1908, 1927, 1947, 1958, 1967, 1977, 1990, 2006, 2018, 2032, 2048, 2066, 2086, 2108, 2122, 2137, 2145, 2151, 2165, 2180, 2190, 2206, 2221, 2231, 2239, 2246, 2255, 2268, 2284, 2299, 2308, 2319, 2328, 2337, 2350, 2359, 2372, 2385, 2392, 2402, 2417, 2425} var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 258, 267, 287, 301, 316, 331, 351, 371, 390, 408, 422, 434, 450, 466, 487, 509, 524, 538, 552, 565, 582, 590, 603, 619, 631, 639, 653, 667, 684, 695, 706, 720, 738, 755, 762, 781, 803, 815, 829, 838, 853, 865, 878, 889, 900, 912, 926, 947, 962, 975, 993, 1009, 1021, 1036, 1050, 1062, 1074, 1091, 1098, 1110, 1115, 1125, 1134, 1145, 1156, 1169, 1184, 1195, 1208, 1223, 1230, 1243, 1256, 1273, 1288, 1301, 1315, 1329, 1345, 1365, 1377, 1400, 1417, 1435, 1453, 1476, 1499, 1521, 1542, 1557, 1576, 1600, 1618, 1635, 1653, 1663, 1677, 1702, 1721, 1741, 1761, 1786, 1811, 1835, 1858, 1875, 1896, 1922, 1942, 1961, 1981, 1992, 2001, 2011, 2024, 2040, 2052, 2066, 2082, 2100, 2120, 2142, 2156, 2171, 2179, 2185, 2199, 2214, 2224, 2240, 2255, 2265, 2273, 2280, 2289, 2302, 2318, 2333, 2342, 2353, 2362, 2371, 2384, 2393, 2406, 2419, 2426, 2436, 2451, 2459}
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

@@ -267,11 +267,11 @@ func Run(opts *Options) (int, error) {
// NOTE: Streaming filter is inherently not compatible with --tail // NOTE: Streaming filter is inherently not compatible with --tail
snapshot, _, _ := chunkList.Snapshot(opts.Tail) snapshot, _, _ := chunkList.Snapshot(opts.Tail)
merger, _ := matcher.scan(MatchRequest{ result := matcher.scan(MatchRequest{
chunks: snapshot, chunks: snapshot,
pattern: pattern}) pattern: pattern})
for i := 0; i < merger.Length(); i++ { for i := 0; i < result.merger.Length(); i++ {
opts.Printer(merger.Get(i).item.AsString(opts.Ansi)) opts.Printer(result.merger.Get(i).item.AsString(opts.Ansi))
found = true found = true
} }
} }
@@ -479,12 +479,13 @@ func Run(opts *Options) (int, error) {
case EvtSearchFin: case EvtSearchFin:
switch val := value.(type) { switch val := value.(type) {
case *Merger: case MatchResult:
merger := val.merger
if deferred { if deferred {
count := val.Length() count := merger.Length()
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 { if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
determine(val.final) determine(merger.final)
} else if val.final { } else if merger.final {
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 { if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
if opts.PrintQuery { if opts.PrintQuery {
opts.Printer(opts.Query) opts.Printer(opts.Query)
@@ -502,7 +503,7 @@ func Run(opts *Options) (int, error) {
} }
} }
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
opts.Printer(transformer(val.Get(i).item)) opts.Printer(transformer(merger.Get(i).item))
} }
if count == 0 { if count == 0 {
exitCode = ExitNoMatch exitCode = ExitNoMatch
@@ -510,7 +511,7 @@ func Run(opts *Options) (int, error) {
stop = true stop = true
return return
} }
determine(val.final) determine(merger.final)
} }
} }
terminal.UpdateList(val) terminal.UpdateList(val)

View File

@@ -19,6 +19,20 @@ type MatchRequest struct {
revision revision revision revision
} }
type MatchResult struct {
merger *Merger
passMerger *Merger
cancelled bool
}
func (mr MatchResult) cacheable() bool {
return mr.merger != nil && mr.merger.cacheable()
}
func (mr MatchResult) final() bool {
return mr.merger != nil && mr.merger.final
}
// Matcher is responsible for performing search // Matcher is responsible for performing search
type Matcher struct { type Matcher struct {
cache *ChunkCache cache *ChunkCache
@@ -29,7 +43,7 @@ type Matcher struct {
reqBox *util.EventBox reqBox *util.EventBox
partitions int partitions int
slab []*util.Slab slab []*util.Slab
mergerCache map[string]*Merger mergerCache map[string]MatchResult
revision revision revision revision
} }
@@ -51,7 +65,7 @@ func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
reqBox: util.NewEventBox(), reqBox: util.NewEventBox(),
partitions: partitions, partitions: partitions,
slab: make([]*util.Slab, partitions), slab: make([]*util.Slab, partitions),
mergerCache: make(map[string]*Merger), mergerCache: make(map[string]MatchResult),
revision: revision} revision: revision}
} }
@@ -85,7 +99,7 @@ func (m *Matcher) Loop() {
cacheCleared := false cacheCleared := false
if request.sort != m.sort || request.revision != m.revision { if request.sort != m.sort || request.revision != m.revision {
m.sort = request.sort m.sort = request.sort
m.mergerCache = make(map[string]*Merger) m.mergerCache = make(map[string]MatchResult)
if !request.revision.compatible(m.revision) { if !request.revision.compatible(m.revision) {
m.cache.Clear() m.cache.Clear()
} }
@@ -95,33 +109,32 @@ func (m *Matcher) Loop() {
// Restart search // Restart search
patternString := request.pattern.AsString() patternString := request.pattern.AsString()
var merger *Merger var result MatchResult
cancelled := false
count := CountItems(request.chunks) count := CountItems(request.chunks)
if !cacheCleared { if !cacheCleared {
if count == prevCount { if count == prevCount {
// Look up mergerCache // Look up mergerCache
if cached, found := m.mergerCache[patternString]; found && cached.final == request.final { if cached, found := m.mergerCache[patternString]; found && cached.final() == request.final {
merger = cached result = cached
} }
} else { } else {
// Invalidate mergerCache // Invalidate mergerCache
prevCount = count prevCount = count
m.mergerCache = make(map[string]*Merger) m.mergerCache = make(map[string]MatchResult)
} }
} }
if merger == nil { if result.merger == nil {
merger, cancelled = m.scan(request) result = m.scan(request)
} }
if !cancelled { if !result.cancelled {
if merger.cacheable() { if result.cacheable() {
m.mergerCache[patternString] = merger m.mergerCache[patternString] = result
} }
merger.final = request.final result.merger.final = request.final
m.eventBox.Set(EvtSearchFin, merger) m.eventBox.Set(EvtSearchFin, result)
} }
} }
} }
@@ -152,16 +165,18 @@ type partialResult struct {
matches []Result matches []Result
} }
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) { func (m *Matcher) scan(request MatchRequest) MatchResult {
startedAt := time.Now() startedAt := time.Now()
numChunks := len(request.chunks) numChunks := len(request.chunks)
if numChunks == 0 { if numChunks == 0 {
return EmptyMerger(request.revision), false m := EmptyMerger(request.revision)
return MatchResult{m, m, false}
} }
pattern := request.pattern pattern := request.pattern
passMerger := PassMerger(&request.chunks, m.tac, request.revision)
if pattern.IsEmpty() { if pattern.IsEmpty() {
return PassMerger(&request.chunks, m.tac, request.revision), false return MatchResult{passMerger, passMerger, false}
} }
minIndex := request.chunks[0].items[0].Index() minIndex := request.chunks[0].items[0].Index()
@@ -224,7 +239,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
} }
if m.reqBox.Peek(reqReset) { if m.reqBox.Peek(reqReset) {
return nil, wait() return MatchResult{nil, nil, wait()}
} }
if time.Since(startedAt) > progressMinDuration { if time.Since(startedAt) > progressMinDuration {
@@ -237,7 +252,8 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
partialResult := <-resultChan partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches partialResults[partialResult.index] = partialResult.matches
} }
return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex, maxIndex), false merger := NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex, maxIndex)
return MatchResult{merger, passMerger, false}
} }
// Reset is called to interrupt/signal the ongoing search // Reset is called to interrupt/signal the ongoing search

View File

@@ -141,6 +141,15 @@ func (mg *Merger) Get(idx int) Result {
panic(fmt.Sprintf("Index out of bounds (unsorted, %d/%d)", idx, mg.count)) panic(fmt.Sprintf("Index out of bounds (unsorted, %d/%d)", idx, mg.count))
} }
func (mg *Merger) ToMap() map[int32]Result {
ret := make(map[int32]Result, mg.count)
for i := 0; i < mg.count; i++ {
result := mg.Get(i)
ret[result.Index()] = result
}
return ret
}
func (mg *Merger) cacheable() bool { func (mg *Merger) cacheable() bool {
return mg.count < mergerCacheMax return mg.count < mergerCacheMax
} }

View File

@@ -98,6 +98,7 @@ Usage: fzf [options]
--wrap Enable line wrap --wrap Enable line wrap
--wrap-sign=STR Indicator for wrapped lines --wrap-sign=STR Indicator for wrapped lines
--no-multi-line Disable multi-line display of items when using --read0 --no-multi-line Disable multi-line display of items when using --read0
--raw Enable raw mode (show non-matching items)
--track Track the current selection when the result is updated --track Track the current selection when the result is updated
--tac Reverse the order of the input --tac Reverse the order of the input
--gap[=N] Render empty lines between each item --gap[=N] Render empty lines between each item
@@ -111,6 +112,7 @@ Usage: fzf [options]
highlighted substring (default: 10) highlighted substring (default: 10)
--jump-labels=CHARS Label characters for jump mode --jump-labels=CHARS Label characters for jump mode
--gutter=CHAR Character used for the gutter column (default: '▌') --gutter=CHAR Character used for the gutter column (default: '▌')
--gutter-raw=CHAR Character used for the gutter column in raw mode (default: '▖')
--pointer=STR Pointer to the current line (default: '▌' or '>') --pointer=STR Pointer to the current line (default: '▌' or '>')
--marker=STR Multi-select marker (default: '┃' or '>') --marker=STR Multi-select marker (default: '┃' or '>')
--marker-multi-line=STR Multi-select marker for multi-line entries; --marker-multi-line=STR Multi-select marker for multi-line entries;
@@ -562,6 +564,7 @@ type Options struct {
AcceptNth func(Delimiter) func([]Token, int32) string AcceptNth func(Delimiter) func([]Token, int32) string
Delimiter Delimiter Delimiter Delimiter
Sort int Sort int
Raw bool
Track trackOption Track trackOption
Tac bool Tac bool
Tail int Tail int
@@ -593,6 +596,7 @@ type Options struct {
JumpLabels string JumpLabels string
Prompt string Prompt string
Gutter *string Gutter *string
GutterRaw *string
Pointer *string Pointer *string
Marker *string Marker *string
MarkerMulti *[3]string MarkerMulti *[3]string
@@ -714,6 +718,7 @@ func defaultOptions() *Options {
JumpLabels: defaultJumpLabels, JumpLabels: defaultJumpLabels,
Prompt: "> ", Prompt: "> ",
Gutter: nil, Gutter: nil,
GutterRaw: nil,
Pointer: nil, Pointer: nil,
Marker: nil, Marker: nil,
MarkerMulti: nil, MarkerMulti: nil,
@@ -1442,6 +1447,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
mergeAttr(&theme.SelectedBg) mergeAttr(&theme.SelectedBg)
case "nth": case "nth":
mergeAttr(&theme.Nth) mergeAttr(&theme.Nth)
case "hidden":
mergeAttr(&theme.Hidden)
case "gutter": case "gutter":
mergeAttr(&theme.Gutter) mergeAttr(&theme.Gutter)
case "hl": case "hl":
@@ -1741,6 +1748,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggleMultiLine) appendAction(actToggleMultiLine)
case "toggle-hscroll": case "toggle-hscroll":
appendAction(actToggleHscroll) appendAction(actToggleHscroll)
case "toggle-raw":
appendAction(actToggleRaw)
case "show-header": case "show-header":
appendAction(actShowHeader) appendAction(actShowHeader)
case "hide-header": case "hide-header":
@@ -1761,8 +1770,12 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggle) appendAction(actToggle)
case "down": case "down":
appendAction(actDown) appendAction(actDown)
case "down-match":
appendAction(actDownMatch)
case "up": case "up":
appendAction(actUp) appendAction(actUp)
case "up-match":
appendAction(actUpMatch)
case "first", "top": case "first", "top":
appendAction(actFirst) appendAction(actFirst)
case "last": case "last":
@@ -1779,9 +1792,9 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actPrevHistory) appendAction(actPrevHistory)
case "next-history": case "next-history":
appendAction(actNextHistory) appendAction(actNextHistory)
case "prev-selected": case "up-selected", "prev-selected":
appendAction(actPrevSelected) appendAction(actPrevSelected)
case "next-selected": case "down-selected", "next-selected":
appendAction(actNextSelected) appendAction(actNextSelected)
case "show-preview": case "show-preview":
appendAction(actShowPreview) appendAction(actShowPreview)
@@ -2682,6 +2695,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
} }
case "+s", "--no-sort": case "+s", "--no-sort":
opts.Sort = 0 opts.Sort = 0
case "--raw":
opts.Raw = true
case "--no-raw":
opts.Raw = false
case "--track": case "--track":
opts.Track = trackEnabled opts.Track = trackEnabled
case "--no-track": case "--no-track":
@@ -2866,6 +2883,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
} }
str = firstLine(str) str = firstLine(str)
opts.Gutter = &str opts.Gutter = &str
case "--gutter-raw":
str, err := nextString("gutter character for raw mode required")
if err != nil {
return err
}
str = firstLine(str)
opts.GutterRaw = &str
case "--pointer": case "--pointer":
str, err := nextString("pointer sign required") str, err := nextString("pointer sign required")
if err != nil { if err != nil {
@@ -3390,6 +3414,12 @@ func validateOptions(opts *Options) error {
} }
} }
if opts.GutterRaw != nil {
if err := validateSign(*opts.GutterRaw, "gutter", 1); err != nil {
return err
}
}
if opts.Scrollbar != nil { if opts.Scrollbar != nil {
runes := []rune(*opts.Scrollbar) runes := []rune(*opts.Scrollbar)
if len(runes) > 2 { if len(runes) > 2 {

View File

@@ -350,8 +350,8 @@ func TestDefaultCtrlNP(t *testing.T) {
t.Error() t.Error()
} }
} }
check([]string{}, tui.CtrlN, actDown) check([]string{}, tui.CtrlN, actDownMatch)
check([]string{}, tui.CtrlP, actUp) check([]string{}, tui.CtrlP, actUpMatch)
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept) check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept) check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)

View File

@@ -274,9 +274,11 @@ type Terminal struct {
footerLabelLen int footerLabelLen int
footerLabelOpts labelOpts footerLabelOpts labelOpts
gutterReverse bool gutterReverse bool
gutterRawReverse bool
pointer string pointer string
pointerLen int pointerLen int
pointerEmpty string pointerEmpty string
pointerEmptyRaw string
marker string marker string
markerLen int markerLen int
markerEmpty string markerEmpty string
@@ -297,6 +299,7 @@ type Terminal struct {
subWordNext string subWordNext string
cx int cx int
cy int cy int
lastMatchingIndex int32
offset int offset int
xoffset int xoffset int
yanked []rune yanked []rune
@@ -383,6 +386,9 @@ type Terminal struct {
printer func(string) printer func(string)
printsep string printsep string
merger *Merger merger *Merger
passMerger *Merger
resultMerger *Merger
matchMap map[int32]Result
selected map[int32]selectedItem selected map[int32]selectedItem
version int64 version int64
revision revision revision revision
@@ -429,6 +435,7 @@ type Terminal struct {
clickFooterColumn int clickFooterColumn int
proxyScript string proxyScript string
numLinesCache map[int32]numLinesCacheValue numLinesCache map[int32]numLinesCacheValue
raw bool
} }
type numLinesCacheValue struct { type numLinesCacheValue struct {
@@ -569,13 +576,16 @@ const (
actToggleWrap actToggleWrap
actToggleMultiLine actToggleMultiLine
actToggleHscroll actToggleHscroll
actToggleRaw
actTrackCurrent actTrackCurrent
actToggleInput actToggleInput
actHideInput actHideInput
actShowInput actShowInput
actUntrackCurrent actUntrackCurrent
actDown actDown
actDownMatch
actUp actUp
actUpMatch
actPageUp actPageUp
actPageDown actPageDown
actPosition actPosition
@@ -796,8 +806,10 @@ func defaultKeymap() map[tui.Event][]*action {
add(tui.CtrlK, actUp) add(tui.CtrlK, actUp)
add(tui.CtrlL, actClearScreen) add(tui.CtrlL, actClearScreen)
add(tui.Enter, actAccept) add(tui.Enter, actAccept)
add(tui.CtrlN, actDown) add(tui.CtrlN, actDownMatch)
add(tui.CtrlP, actUp) add(tui.CtrlP, actUpMatch)
add(tui.AltDown, actDownMatch)
add(tui.AltUp, actUpMatch)
add(tui.CtrlU, actUnixLineDiscard) add(tui.CtrlU, actUnixLineDiscard)
add(tui.CtrlW, actUnixWordRubout) add(tui.CtrlW, actUnixWordRubout)
add(tui.CtrlY, actYank) add(tui.CtrlY, actYank)
@@ -953,6 +965,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
} }
keymapCopy := maps.Clone(opts.Keymap) keymapCopy := maps.Clone(opts.Keymap)
em := EmptyMerger(revision{})
t := Terminal{ t := Terminal{
initDelay: delay, initDelay: delay,
infoCommand: opts.InfoCommand, infoCommand: opts.InfoCommand,
@@ -980,6 +993,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
subWordNext: subWordNext, subWordNext: subWordNext,
cx: len(input), cx: len(input),
cy: 0, cy: 0,
lastMatchingIndex: minItem.Index(),
offset: 0, offset: 0,
xoffset: 0, xoffset: 0,
yanked: []rune{}, yanked: []rune{},
@@ -1039,6 +1053,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
nth: opts.Nth, nth: opts.Nth,
nthCurrent: opts.Nth, nthCurrent: opts.Nth,
tabstop: opts.Tabstop, tabstop: opts.Tabstop,
raw: opts.Raw,
hasStartActions: false, hasStartActions: false,
hasResultActions: false, hasResultActions: false,
hasFocusActions: false, hasFocusActions: false,
@@ -1052,7 +1067,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
printer: opts.Printer, printer: opts.Printer,
printsep: opts.PrintSep, printsep: opts.PrintSep,
proxyScript: opts.ProxyScript, proxyScript: opts.ProxyScript,
merger: EmptyMerger(revision{}), merger: em,
passMerger: em,
resultMerger: em,
matchMap: make(map[int32]Result),
selected: make(map[int32]selectedItem), selected: make(map[int32]selectedItem),
runningCmds: util.NewConcurrentSet[*runningCmd](), runningCmds: util.NewConcurrentSet[*runningCmd](),
reqBox: util.NewEventBox(), reqBox: util.NewEventBox(),
@@ -1093,7 +1111,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible()) tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())
// Gutter character // Gutter character
var gutterChar string var gutterChar, gutterRawChar string
if opts.Gutter != nil { if opts.Gutter != nil {
gutterChar = *opts.Gutter gutterChar = *opts.Gutter
} else if t.unicode && !t.theme.Gutter.Color.IsDefault() { } else if t.unicode && !t.theme.Gutter.Color.IsDefault() {
@@ -1103,12 +1121,24 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
t.gutterReverse = true t.gutterReverse = true
} }
if opts.GutterRaw != nil {
gutterRawChar = *opts.GutterRaw
} else if t.unicode && !t.theme.Gutter.Color.IsDefault() {
// TODO: Doesn't look too good. Maybe use a different color instead, or both?
gutterRawChar = "▖"
} else {
gutterRawChar = ":"
t.gutterRawReverse = false
}
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt) t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
// Pre-calculated empty pointer and marker signs // Pre-calculated empty pointer and marker signs
if t.pointerLen == 0 { if t.pointerLen == 0 {
t.pointerEmpty = "" t.pointerEmpty = ""
t.pointerEmptyRaw = ""
} else { } else {
t.pointerEmpty = gutterChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1)) t.pointerEmpty = gutterChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
t.pointerEmptyRaw = gutterRawChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
} }
t.markerEmpty = strings.Repeat(" ", t.markerLen) t.markerEmpty = strings.Repeat(" ", t.markerLen)
@@ -1282,7 +1312,7 @@ func (t *Terminal) environImpl(forPreview bool) []string {
} }
env = append(env, "FZF_INPUT_STATE="+inputState) env = append(env, "FZF_INPUT_STATE="+inputState)
env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count)) 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_MATCH_COUNT=%d", t.resultMerger.Length()))
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected))) env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
env = append(env, fmt.Sprintf("FZF_LINES=%d", t.areaLines)) env = append(env, fmt.Sprintf("FZF_LINES=%d", t.areaLines))
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns)) env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
@@ -1693,7 +1723,8 @@ func (t *Terminal) UpdateProgress(progress float32) {
} }
// UpdateList updates Merger to display the list // UpdateList updates Merger to display the list
func (t *Terminal) UpdateList(merger *Merger) { func (t *Terminal) UpdateList(result MatchResult) {
merger := result.merger
t.mutex.Lock() t.mutex.Lock()
prevIndex := minItem.Index() prevIndex := minItem.Index()
newRevision := merger.Revision() newRevision := merger.Revision()
@@ -1706,6 +1737,15 @@ func (t *Terminal) UpdateList(merger *Merger) {
} }
t.progress = 100 t.progress = 100
t.merger = merger t.merger = merger
t.resultMerger = merger
t.passMerger = result.passMerger
if t.raw {
t.merger = result.passMerger
t.matchMap = t.resultMerger.ToMap()
} else {
t.merger = result.merger
t.matchMap = make(map[int32]Result)
}
if t.revision != newRevision { if t.revision != newRevision {
if !t.revision.compatible(newRevision) { if !t.revision.compatible(newRevision) {
// Reloaded: clear selection // Reloaded: clear selection
@@ -1754,7 +1794,7 @@ func (t *Terminal) UpdateList(merger *Merger) {
} }
needActivation := false needActivation := false
if !t.reading { if !t.reading {
switch t.merger.Length() { switch t.resultMerger.Length() {
case 0: case 0:
zero := tui.Zero.AsEvent() zero := tui.Zero.AsEvent()
if _, prs := t.keymap[zero]; prs { if _, prs := t.keymap[zero]; prs {
@@ -2801,7 +2841,7 @@ func (t *Terminal) printInfoImpl() {
return return
} }
found := t.merger.Length() found := t.resultMerger.Length()
total := util.Max(found, t.count) total := util.Max(found, t.count)
output := fmt.Sprintf("%d/%d", found, total) output := fmt.Sprintf("%d/%d", found, total)
if t.toggleSort { if t.toggleSort {
@@ -3122,12 +3162,16 @@ func (t *Terminal) gutter(current bool) {
var color tui.ColorPair var color tui.ColorPair
if current { if current {
color = tui.ColCurrentCursorEmpty color = tui.ColCurrentCursorEmpty
} else if t.gutterReverse { } else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
color = tui.ColCursorEmpty color = tui.ColCursorEmpty
} else { } else {
color = tui.ColCursorEmptyChar color = tui.ColCursorEmptyChar
} }
t.window.CPrint(color, t.pointerEmpty) gutter := t.pointerEmpty
if t.raw {
gutter = t.pointerEmptyRaw
}
t.window.CPrint(color, gutter)
} }
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) { func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
@@ -3162,7 +3206,11 @@ func (t *Terminal) printList() {
for line, itemCount := startLine, 0; line <= maxy; line, itemCount = line+1, itemCount+1 { for line, itemCount := startLine, 0; line <= maxy; line, itemCount = line+1, itemCount+1 {
if itemCount < count { if itemCount < count {
item := t.merger.Get(itemCount + t.offset) item := t.merger.Get(itemCount + t.offset)
line = t.printItem(item, line, maxy, itemCount, itemCount == t.cy-t.offset, barRange) current := itemCount == t.cy-t.offset
if current && (!t.raw || t.isItemMatch(item.item)) {
t.lastMatchingIndex = item.Index()
}
line = t.printItem(item, line, maxy, itemCount, current, barRange)
} else if !t.prevLines[line].empty { } else if !t.prevLines[line].empty {
t.renderEmptyLine(line, barRange) t.renderEmptyLine(line, barRange)
} }
@@ -3184,6 +3232,14 @@ func (t *Terminal) printBar(lineNum int, forceRedraw bool, barRange [2]int) bool
func (t *Terminal) printItem(result Result, line int, maxLine int, index int, current bool, barRange [2]int) int { func (t *Terminal) printItem(result Result, line int, maxLine int, index int, current bool, barRange [2]int) int {
item := result.item item := result.item
matched := true
var matchResult Result
if t.raw {
if matchResult, matched = t.matchMap[item.Index()]; matched {
result = matchResult
}
}
_, selected := t.selected[item.Index()] _, selected := t.selected[item.Index()]
label := "" label := ""
extraWidth := 0 extraWidth := 0
@@ -3310,7 +3366,13 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
} }
return indentSize return indentSize
} }
finalLineNum = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true, line, maxLine, forceRedraw, preTask, postTask) base := tui.ColCurrent
match := tui.ColCurrentMatch
if !matched {
base = base.WithFg(t.theme.Hidden)
match = match.WithFg(t.theme.Hidden)
}
finalLineNum = t.printHighlighted(result, base, match, true, true, line, maxLine, forceRedraw, preTask, postTask)
} else { } else {
preTask := func(marker markerClass) int { preTask := func(marker markerClass) int {
w := t.window.Width() - t.pointerLen w := t.window.Width() - t.pointerLen
@@ -3344,6 +3406,10 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
base = base.WithBg(altBg) base = base.WithBg(altBg)
match = match.WithBg(altBg) match = match.WithBg(altBg)
} }
if !matched {
base = base.WithFg(t.theme.Hidden)
match = match.WithFg(t.theme.Hidden)
}
finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask) finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask)
} }
for i := 0; i < t.gap && finalLineNum < maxLine; i++ { for i := 0; i < t.gap && finalLineNum < maxLine; i++ {
@@ -3396,8 +3462,8 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
item := result.item item := result.item
matchOffsets := []Offset{} matchOffsets := []Offset{}
var pos *[]int var pos *[]int
if match && t.merger.pattern != nil { if match && t.resultMerger.pattern != nil {
_, matchOffsets, pos = t.merger.pattern.MatchItem(item, true, t.slab) _, matchOffsets, pos = t.resultMerger.pattern.MatchItem(item, true, t.slab)
} }
charOffsets := matchOffsets charOffsets := matchOffsets
if pos != nil { if pos != nil {
@@ -3429,7 +3495,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
} }
if !wholeCovered && t.nthAttr > 0 { if !wholeCovered && t.nthAttr > 0 {
var tokens []Token var tokens []Token
if item.transformed != nil && item.transformed.revision == t.merger.revision { if item.transformed != nil && item.transformed.revision == t.resultMerger.revision {
tokens = item.transformed.tokens tokens = item.transformed.tokens
} else { } else {
tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent) tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent)
@@ -4677,6 +4743,33 @@ func (t *Terminal) currentItem() *Item {
return nil return nil
} }
func (t *Terminal) isCurrentItemMatch() bool {
cnt := t.merger.Length()
if t.cy >= 0 && cnt > 0 && cnt > t.cy {
if !t.raw {
return true
}
item := t.merger.Get(t.cy).item
return t.isItemMatch(item)
}
return false
}
func (t *Terminal) isItemMatch(item *Item) bool {
_, matched := t.matchMap[item.Index()]
return matched
}
func (t *Terminal) filterSelected() {
filtered := make(map[int32]selectedItem)
for k, v := range t.selected {
if t.isItemMatch(v.item) {
filtered[k] = v
}
}
t.selected = filtered
}
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, [3][]*Item) { func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, [3][]*Item) {
current := t.currentItem() current := t.currentItem()
slot, plus, asterisk, forceUpdate := hasPreviewFlags(template) slot, plus, asterisk, forceUpdate := hasPreviewFlags(template)
@@ -4721,6 +4814,10 @@ func (t *Terminal) selectItem(item *Item) bool {
if len(t.selected) >= t.multi { if len(t.selected) >= t.multi {
return false return false
} }
// TODO: Should we allow selecting non-matching items?
// if t.raw && !t.isItemMatch(item) {
// return false
// }
if _, found := t.selected[item.Index()]; found { if _, found := t.selected[item.Index()]; found {
return true return true
} }
@@ -5917,8 +6014,9 @@ func (t *Terminal) Loop() error {
} }
case actSelectAll: case actSelectAll:
if t.multi > 0 { if t.multi > 0 {
for i := 0; i < t.merger.Length(); i++ { // Limit the scope only to the matching items
if !t.selectItem(t.merger.Get(i).item) { for i := 0; i < t.resultMerger.Length(); i++ {
if !t.selectItem(t.resultMerger.Get(i).item) {
break break
} }
} }
@@ -5926,8 +6024,10 @@ func (t *Terminal) Loop() error {
} }
case actDeselectAll: case actDeselectAll:
if t.multi > 0 { if t.multi > 0 {
for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ { // Also limit the scope only to the matching items, while this may
t.deselectItem(t.merger.Get(i).item) // not be straightforward in raw mode.
for i := 0; i < t.resultMerger.Length() && len(t.selected) > 0; i++ {
t.deselectItem(t.resultMerger.Get(i).item)
} }
req(reqList, reqInfo) req(reqList, reqInfo)
} }
@@ -5955,17 +6055,17 @@ func (t *Terminal) Loop() error {
case actToggleAll: case actToggleAll:
if t.multi > 0 { if t.multi > 0 {
prevIndexes := make(map[int]struct{}) prevIndexes := make(map[int]struct{})
for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ { for i := 0; i < t.resultMerger.Length() && len(t.selected) > 0; i++ {
item := t.merger.Get(i).item item := t.resultMerger.Get(i).item
if _, found := t.selected[item.Index()]; found { if _, found := t.selected[item.Index()]; found {
prevIndexes[i] = struct{}{} prevIndexes[i] = struct{}{}
t.deselectItem(item) t.deselectItem(item)
} }
} }
for i := 0; i < t.merger.Length(); i++ { for i := 0; i < t.resultMerger.Length(); i++ {
if _, found := prevIndexes[i]; !found { if _, found := prevIndexes[i]; !found {
item := t.merger.Get(i).item item := t.resultMerger.Get(i).item
if !t.selectItem(item) { if !t.selectItem(item) {
break break
} }
@@ -5993,19 +6093,77 @@ func (t *Terminal) Loop() error {
t.vmove(1, true) t.vmove(1, true)
req(reqList) req(reqList)
} }
case actDown: case actDown, actDownMatch:
t.vmove(-1, true) if t.raw && a.t == actDownMatch {
if t.resultMerger.Length() > 0 {
prevCy := t.cy
for t.vmove(-1, true) && !t.isCurrentItemMatch() {
}
if !t.isCurrentItemMatch() {
t.vset(prevCy)
}
}
} else {
t.vmove(-1, true)
}
req(reqList) req(reqList)
case actUp: case actUp, actUpMatch:
t.vmove(1, true) if t.raw && a.t == actUpMatch {
if t.resultMerger.Length() > 0 {
prevCy := t.cy
for t.vmove(1, true) && !t.isCurrentItemMatch() {
}
if !t.isCurrentItemMatch() {
t.vset(prevCy)
}
}
} else {
t.vmove(1, true)
}
req(reqList)
case actToggleRaw:
t.raw = !t.raw
prevPos := t.cy - t.offset
prevIndex := t.currentIndex()
if t.raw {
// Build matchMap if not available
if len(t.matchMap) == 0 {
t.matchMap = t.resultMerger.ToMap()
}
t.merger = t.passMerger
} else {
t.merger = t.resultMerger
// Need to remove non-matching items from the selection
if t.multi > 0 && len(t.selected) > 0 {
t.filterSelected()
req(reqInfo)
}
}
// Try to retain position
if prevIndex != minItem.Index() {
i := t.merger.FindIndex(prevIndex)
if i >= 0 {
t.cy = i
} else {
t.cy = t.merger.FindIndex(t.lastMatchingIndex)
}
t.offset = t.cy - prevPos
}
// List needs to be rerendered
t.forceRerenderList()
req(reqList) req(reqList)
case actAccept: case actAccept:
req(reqClose) req(reqClose)
case actAcceptNonEmpty: case actAcceptNonEmpty:
// TODO: Allow accepting unmatched items in raw mode?
if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 { if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {
req(reqClose) req(reqClose)
} }
case actAcceptOrPrintQuery: case actAcceptOrPrintQuery:
// TODO: Allow accepting unmatched items in raw mode?
if len(t.selected) > 0 || t.merger.Length() > 0 { if len(t.selected) > 0 || t.merger.Length() > 0 {
req(reqClose) req(reqClose)
} else { } else {
@@ -6841,7 +6999,7 @@ func (t *Terminal) Loop() error {
reload := changed || newCommand != nil reload := changed || newCommand != nil
var reloadRequest *searchRequest var reloadRequest *searchRequest
if reload { if reload {
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.merger.Revision()} reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.resultMerger.Revision()}
} }
// Dispatch queued background requests // Dispatch queued background requests
@@ -6961,7 +7119,8 @@ func (t *Terminal) constrain() {
} }
} }
func (t *Terminal) vmove(o int, allowCycle bool) { // Returns true if the cursor position is successfully updated
func (t *Terminal) vmove(o int, allowCycle bool) bool {
if t.layout != layoutDefault { if t.layout != layoutDefault {
o *= -1 o *= -1
} }
@@ -6978,7 +7137,7 @@ func (t *Terminal) vmove(o int, allowCycle bool) {
} }
} }
} }
t.vset(dest) return t.vset(dest)
} }
func (t *Terminal) vset(o int) bool { func (t *Terminal) vset(o int) bool {
@@ -7045,9 +7204,9 @@ func (t *Terminal) dumpStatus(params getParams) string {
selected[i] = t.dumpItem(selectedItems[i+params.offset].item) selected[i] = t.dumpItem(selectedItems[i+params.offset].item)
} }
matches := make([]StatusItem, util.Max(0, util.Min(params.limit, t.merger.Length()-params.offset))) matches := make([]StatusItem, util.Max(0, util.Min(params.limit, t.resultMerger.Length()-params.offset)))
for i := range matches { for i := range matches {
matches[i] = t.dumpItem(t.merger.Get(i + params.offset).item) matches[i] = t.dumpItem(t.resultMerger.Get(i + params.offset).item)
} }
var current *StatusItem var current *StatusItem
@@ -7064,7 +7223,7 @@ func (t *Terminal) dumpStatus(params getParams) string {
Position: t.cy, Position: t.cy,
Sort: t.sort, Sort: t.sort,
TotalCount: t.count, TotalCount: t.count,
MatchCount: t.merger.Length(), MatchCount: t.resultMerger.Length(),
Current: current, Current: current,
Matches: matches, Matches: matches,
Selected: selected, Selected: selected,

View File

@@ -381,6 +381,12 @@ func (p ColorPair) WithAttr(attr Attr) ColorPair {
return dup return dup
} }
func (p ColorPair) WithFg(fg ColorAttr) ColorPair {
dup := p
fgPair := ColorPair{fg.Color, colUndefined, fg.Attr}
return dup.Merge(fgPair)
}
func (p ColorPair) WithBg(bg ColorAttr) ColorPair { func (p ColorPair) WithBg(bg ColorAttr) ColorPair {
dup := p dup := p
bgPair := ColorPair{colUndefined, bg.Color, bg.Attr} bgPair := ColorPair{colUndefined, bg.Color, bg.Attr}
@@ -410,6 +416,7 @@ type ColorTheme struct {
ListBg ColorAttr ListBg ColorAttr
AltBg ColorAttr AltBg ColorAttr
Nth ColorAttr Nth ColorAttr
Hidden ColorAttr
SelectedFg ColorAttr SelectedFg ColorAttr
SelectedBg ColorAttr SelectedBg ColorAttr
SelectedMatch ColorAttr SelectedMatch ColorAttr
@@ -866,6 +873,7 @@ func EmptyTheme() *ColorTheme {
FooterLabel: ColorAttr{colUndefined, AttrUndefined}, FooterLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined}, GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined}, Nth: ColorAttr{colUndefined, AttrUndefined},
Hidden: ColorAttr{colUndefined, Dim},
} }
} }
@@ -916,6 +924,7 @@ func NoColorTheme() *ColorTheme {
FooterLabel: ColorAttr{colDefault, AttrUndefined}, FooterLabel: ColorAttr{colDefault, AttrUndefined},
GapLine: ColorAttr{colDefault, AttrUndefined}, GapLine: ColorAttr{colDefault, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined}, Nth: ColorAttr{colUndefined, AttrUndefined},
Hidden: ColorAttr{colUndefined, Dim},
} }
} }
@@ -967,6 +976,7 @@ func init() {
FooterLabel: ColorAttr{colUndefined, AttrUndefined}, FooterLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined}, GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined}, Nth: ColorAttr{colUndefined, AttrUndefined},
Hidden: ColorAttr{colUndefined, Dim},
} }
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Colored: true, Colored: true,
@@ -1015,6 +1025,7 @@ func init() {
FooterLabel: ColorAttr{colUndefined, AttrUndefined}, FooterLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined}, GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined}, Nth: ColorAttr{colUndefined, AttrUndefined},
Hidden: ColorAttr{colUndefined, Dim},
} }
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Colored: true, Colored: true,
@@ -1063,6 +1074,7 @@ func init() {
FooterLabel: ColorAttr{colUndefined, AttrUndefined}, FooterLabel: ColorAttr{colUndefined, AttrUndefined},
GapLine: ColorAttr{colUndefined, AttrUndefined}, GapLine: ColorAttr{colUndefined, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined}, Nth: ColorAttr{colUndefined, AttrUndefined},
Hidden: ColorAttr{colUndefined, Dim},
} }
} }

60
test/test_raw.rb Normal file
View File

@@ -0,0 +1,60 @@
# frozen_string_literal: true
require_relative 'lib/common'
# Testing raw mode
class TestRaw < TestInteractive
def test_raw_mode
tmux.send_keys %(seq 1000 | #{FZF} --raw --bind ctrl-x:toggle-raw --gutter '▌' --multi), :Enter
tmux.until { assert_equal 1000, it.match_count }
tmux.send_keys 1
tmux.until { assert_equal 272, it.match_count }
tmux.send_keys :Up
tmux.until { assert_includes it, '> 2' }
tmux.send_keys 'C-p'
tmux.until do
assert_includes it, '> 10'
assert_includes it, '▖ 9'
end
tmux.send_keys 'C-x'
tmux.until do
assert_includes it, '> 10'
assert_includes it, '▌ 1'
end
tmux.send_keys :Up, 'C-x'
tmux.until do
assert_includes it, '> 11'
assert_includes it, '▖ 10'
end
tmux.send_keys 1
tmux.until { assert_equal 28, it.match_count }
tmux.send_keys 'C-p'
tmux.until do
assert_includes it, '> 101'
assert_includes it, '▖ 100'
end
tmux.send_keys 'C-n'
tmux.until do
assert_includes it, '> 11'
assert_includes it, '▖ 10'
end
tmux.send_keys :Tab, :Tab, :Tab
tmux.until { assert_equal 3, it.select_count }
tmux.send_keys 'C-x'
tmux.until do
assert_equal 1, it.select_count
assert_includes it, '▌ 110'
assert_includes it, '>>11'
end
end
end