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:
106
CHANGELOG.md
106
CHANGELOG.md
@@ -3,30 +3,98 @@ CHANGELOG
|
||||
|
||||
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
|
||||
fzf --gutter '▎'
|
||||
### Introducing "raw" mode
|
||||
|
||||
# Checker
|
||||
fzf --gutter '▚'
|
||||
In "raw" mode, non-matching items are also displayed in their original position,
|
||||
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
|
||||
fzf --gutter '▖'
|
||||
```sh
|
||||
export FZF_CTRL_R_OPTS='--bind ctrl-x:toggle-raw'
|
||||
```
|
||||
|
||||
# Full-width
|
||||
fzf --gutter '█'
|
||||
```sh
|
||||
tree | fzf --raw --reverse --bind ctrl-x:toggle-raw
|
||||
```
|
||||
|
||||
# No gutter
|
||||
fzf --gutter ' '
|
||||
```
|
||||
While non-matching items are displayed in dimmed color, they are treated just
|
||||
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
|
||||
------
|
||||
|
||||
@@ -299,6 +299,7 @@ color mappings. Each entry is separated by a comma and/or whitespaces.
|
||||
\fBheader (header\-fg) \fRHeader
|
||||
\fBfooter (footer\-fg) \fRFooter
|
||||
\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:
|
||||
\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"
|
||||
Disable multi-line display of items when using \fB\-\-read0\fR
|
||||
.TP
|
||||
.B "\-\-raw"
|
||||
Enable raw mode where non-matching items are also displayed in a dimmed color.
|
||||
.TP
|
||||
.B "\-\-track"
|
||||
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
|
||||
@@ -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/eof\fR \fIctrl\-d\fR (same as \fBdelete\-char\fR except aborts fzf if query is empty)
|
||||
\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)
|
||||
\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)
|
||||
\fBend\-of\-line\fR \fIctrl\-e end\fR
|
||||
\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
|
||||
\fBlast\fR (move to the last match; same as \fBpos(\-1)\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\-up\fR \fIpgup\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)
|
||||
\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\-selected\fR (move to the previous selected item)
|
||||
\fBprev\-selected\fR (synonym to \fBup\-selected\fR)
|
||||
\fBpreview(...)\fR (see below for the details)
|
||||
\fBpreview\-down\fR \fIshift\-down\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\-preview\fR
|
||||
\fBtoggle\-preview\-wrap\fR
|
||||
\fBtoggle\-raw\fR
|
||||
\fBtoggle\-search\fR (toggle search functionality)
|
||||
\fBtoggle\-sort\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\-word\-rubout\fR \fIctrl\-w\fR
|
||||
\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
|
||||
|
||||
Each \fBtransform*\fR action has a corresponding \fBbg\-transform*\fR
|
||||
|
||||
@@ -77,106 +77,109 @@ func _() {
|
||||
_ = x[actToggleWrap-66]
|
||||
_ = x[actToggleMultiLine-67]
|
||||
_ = x[actToggleHscroll-68]
|
||||
_ = x[actTrackCurrent-69]
|
||||
_ = x[actToggleInput-70]
|
||||
_ = x[actHideInput-71]
|
||||
_ = x[actShowInput-72]
|
||||
_ = x[actUntrackCurrent-73]
|
||||
_ = x[actDown-74]
|
||||
_ = x[actUp-75]
|
||||
_ = x[actPageUp-76]
|
||||
_ = x[actPageDown-77]
|
||||
_ = x[actPosition-78]
|
||||
_ = x[actHalfPageUp-79]
|
||||
_ = x[actHalfPageDown-80]
|
||||
_ = x[actOffsetUp-81]
|
||||
_ = x[actOffsetDown-82]
|
||||
_ = x[actOffsetMiddle-83]
|
||||
_ = x[actJump-84]
|
||||
_ = x[actJumpAccept-85]
|
||||
_ = x[actPrintQuery-86]
|
||||
_ = x[actRefreshPreview-87]
|
||||
_ = x[actReplaceQuery-88]
|
||||
_ = x[actToggleSort-89]
|
||||
_ = x[actShowPreview-90]
|
||||
_ = x[actHidePreview-91]
|
||||
_ = x[actTogglePreview-92]
|
||||
_ = x[actTogglePreviewWrap-93]
|
||||
_ = x[actTransform-94]
|
||||
_ = x[actTransformBorderLabel-95]
|
||||
_ = x[actTransformGhost-96]
|
||||
_ = x[actTransformHeader-97]
|
||||
_ = x[actTransformFooter-98]
|
||||
_ = x[actTransformHeaderLabel-99]
|
||||
_ = x[actTransformFooterLabel-100]
|
||||
_ = x[actTransformInputLabel-101]
|
||||
_ = x[actTransformListLabel-102]
|
||||
_ = x[actTransformNth-103]
|
||||
_ = x[actTransformPointer-104]
|
||||
_ = x[actTransformPreviewLabel-105]
|
||||
_ = x[actTransformPrompt-106]
|
||||
_ = x[actTransformQuery-107]
|
||||
_ = x[actTransformSearch-108]
|
||||
_ = x[actTrigger-109]
|
||||
_ = x[actBgTransform-110]
|
||||
_ = x[actBgTransformBorderLabel-111]
|
||||
_ = x[actBgTransformGhost-112]
|
||||
_ = x[actBgTransformHeader-113]
|
||||
_ = x[actBgTransformFooter-114]
|
||||
_ = x[actBgTransformHeaderLabel-115]
|
||||
_ = x[actBgTransformFooterLabel-116]
|
||||
_ = x[actBgTransformInputLabel-117]
|
||||
_ = x[actBgTransformListLabel-118]
|
||||
_ = x[actBgTransformNth-119]
|
||||
_ = x[actBgTransformPointer-120]
|
||||
_ = x[actBgTransformPreviewLabel-121]
|
||||
_ = x[actBgTransformPrompt-122]
|
||||
_ = x[actBgTransformQuery-123]
|
||||
_ = x[actBgTransformSearch-124]
|
||||
_ = x[actBgCancel-125]
|
||||
_ = x[actSearch-126]
|
||||
_ = x[actPreview-127]
|
||||
_ = x[actPreviewTop-128]
|
||||
_ = x[actPreviewBottom-129]
|
||||
_ = x[actPreviewUp-130]
|
||||
_ = x[actPreviewDown-131]
|
||||
_ = x[actPreviewPageUp-132]
|
||||
_ = x[actPreviewPageDown-133]
|
||||
_ = x[actPreviewHalfPageUp-134]
|
||||
_ = x[actPreviewHalfPageDown-135]
|
||||
_ = x[actPrevHistory-136]
|
||||
_ = x[actPrevSelected-137]
|
||||
_ = x[actPrint-138]
|
||||
_ = x[actPut-139]
|
||||
_ = x[actNextHistory-140]
|
||||
_ = x[actNextSelected-141]
|
||||
_ = x[actExecute-142]
|
||||
_ = x[actExecuteSilent-143]
|
||||
_ = x[actExecuteMulti-144]
|
||||
_ = x[actSigStop-145]
|
||||
_ = x[actFirst-146]
|
||||
_ = x[actLast-147]
|
||||
_ = x[actReload-148]
|
||||
_ = x[actReloadSync-149]
|
||||
_ = x[actDisableSearch-150]
|
||||
_ = x[actEnableSearch-151]
|
||||
_ = x[actSelect-152]
|
||||
_ = x[actDeselect-153]
|
||||
_ = x[actUnbind-154]
|
||||
_ = x[actRebind-155]
|
||||
_ = x[actToggleBind-156]
|
||||
_ = x[actBecome-157]
|
||||
_ = x[actShowHeader-158]
|
||||
_ = x[actHideHeader-159]
|
||||
_ = x[actBell-160]
|
||||
_ = x[actExclude-161]
|
||||
_ = x[actExcludeMulti-162]
|
||||
_ = x[actAsync-163]
|
||||
_ = x[actToggleRaw-69]
|
||||
_ = x[actTrackCurrent-70]
|
||||
_ = x[actToggleInput-71]
|
||||
_ = x[actHideInput-72]
|
||||
_ = x[actShowInput-73]
|
||||
_ = x[actUntrackCurrent-74]
|
||||
_ = x[actDown-75]
|
||||
_ = x[actDownMatch-76]
|
||||
_ = x[actUp-77]
|
||||
_ = x[actUpMatch-78]
|
||||
_ = x[actPageUp-79]
|
||||
_ = x[actPageDown-80]
|
||||
_ = x[actPosition-81]
|
||||
_ = x[actHalfPageUp-82]
|
||||
_ = x[actHalfPageDown-83]
|
||||
_ = x[actOffsetUp-84]
|
||||
_ = x[actOffsetDown-85]
|
||||
_ = x[actOffsetMiddle-86]
|
||||
_ = x[actJump-87]
|
||||
_ = x[actJumpAccept-88]
|
||||
_ = x[actPrintQuery-89]
|
||||
_ = x[actRefreshPreview-90]
|
||||
_ = x[actReplaceQuery-91]
|
||||
_ = x[actToggleSort-92]
|
||||
_ = x[actShowPreview-93]
|
||||
_ = x[actHidePreview-94]
|
||||
_ = x[actTogglePreview-95]
|
||||
_ = x[actTogglePreviewWrap-96]
|
||||
_ = x[actTransform-97]
|
||||
_ = x[actTransformBorderLabel-98]
|
||||
_ = x[actTransformGhost-99]
|
||||
_ = x[actTransformHeader-100]
|
||||
_ = x[actTransformFooter-101]
|
||||
_ = x[actTransformHeaderLabel-102]
|
||||
_ = x[actTransformFooterLabel-103]
|
||||
_ = x[actTransformInputLabel-104]
|
||||
_ = x[actTransformListLabel-105]
|
||||
_ = x[actTransformNth-106]
|
||||
_ = x[actTransformPointer-107]
|
||||
_ = x[actTransformPreviewLabel-108]
|
||||
_ = x[actTransformPrompt-109]
|
||||
_ = x[actTransformQuery-110]
|
||||
_ = x[actTransformSearch-111]
|
||||
_ = x[actTrigger-112]
|
||||
_ = x[actBgTransform-113]
|
||||
_ = x[actBgTransformBorderLabel-114]
|
||||
_ = x[actBgTransformGhost-115]
|
||||
_ = x[actBgTransformHeader-116]
|
||||
_ = x[actBgTransformFooter-117]
|
||||
_ = x[actBgTransformHeaderLabel-118]
|
||||
_ = x[actBgTransformFooterLabel-119]
|
||||
_ = x[actBgTransformInputLabel-120]
|
||||
_ = x[actBgTransformListLabel-121]
|
||||
_ = x[actBgTransformNth-122]
|
||||
_ = x[actBgTransformPointer-123]
|
||||
_ = x[actBgTransformPreviewLabel-124]
|
||||
_ = x[actBgTransformPrompt-125]
|
||||
_ = x[actBgTransformQuery-126]
|
||||
_ = x[actBgTransformSearch-127]
|
||||
_ = x[actBgCancel-128]
|
||||
_ = x[actSearch-129]
|
||||
_ = x[actPreview-130]
|
||||
_ = x[actPreviewTop-131]
|
||||
_ = x[actPreviewBottom-132]
|
||||
_ = x[actPreviewUp-133]
|
||||
_ = x[actPreviewDown-134]
|
||||
_ = x[actPreviewPageUp-135]
|
||||
_ = x[actPreviewPageDown-136]
|
||||
_ = x[actPreviewHalfPageUp-137]
|
||||
_ = x[actPreviewHalfPageDown-138]
|
||||
_ = x[actPrevHistory-139]
|
||||
_ = x[actPrevSelected-140]
|
||||
_ = x[actPrint-141]
|
||||
_ = x[actPut-142]
|
||||
_ = x[actNextHistory-143]
|
||||
_ = x[actNextSelected-144]
|
||||
_ = x[actExecute-145]
|
||||
_ = x[actExecuteSilent-146]
|
||||
_ = x[actExecuteMulti-147]
|
||||
_ = x[actSigStop-148]
|
||||
_ = x[actFirst-149]
|
||||
_ = x[actLast-150]
|
||||
_ = x[actReload-151]
|
||||
_ = x[actReloadSync-152]
|
||||
_ = x[actDisableSearch-153]
|
||||
_ = x[actEnableSearch-154]
|
||||
_ = x[actSelect-155]
|
||||
_ = x[actDeselect-156]
|
||||
_ = x[actUnbind-157]
|
||||
_ = x[actRebind-158]
|
||||
_ = x[actToggleBind-159]
|
||||
_ = x[actBecome-160]
|
||||
_ = x[actShowHeader-161]
|
||||
_ = x[actHideHeader-162]
|
||||
_ = 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 {
|
||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||
|
||||
19
src/core.go
19
src/core.go
@@ -267,11 +267,11 @@ func Run(opts *Options) (int, error) {
|
||||
|
||||
// NOTE: Streaming filter is inherently not compatible with --tail
|
||||
snapshot, _, _ := chunkList.Snapshot(opts.Tail)
|
||||
merger, _ := matcher.scan(MatchRequest{
|
||||
result := matcher.scan(MatchRequest{
|
||||
chunks: snapshot,
|
||||
pattern: pattern})
|
||||
for i := 0; i < merger.Length(); i++ {
|
||||
opts.Printer(merger.Get(i).item.AsString(opts.Ansi))
|
||||
for i := 0; i < result.merger.Length(); i++ {
|
||||
opts.Printer(result.merger.Get(i).item.AsString(opts.Ansi))
|
||||
found = true
|
||||
}
|
||||
}
|
||||
@@ -479,12 +479,13 @@ func Run(opts *Options) (int, error) {
|
||||
|
||||
case EvtSearchFin:
|
||||
switch val := value.(type) {
|
||||
case *Merger:
|
||||
case MatchResult:
|
||||
merger := val.merger
|
||||
if deferred {
|
||||
count := val.Length()
|
||||
count := merger.Length()
|
||||
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
|
||||
determine(val.final)
|
||||
} else if val.final {
|
||||
determine(merger.final)
|
||||
} else if merger.final {
|
||||
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
|
||||
if opts.PrintQuery {
|
||||
opts.Printer(opts.Query)
|
||||
@@ -502,7 +503,7 @@ func Run(opts *Options) (int, error) {
|
||||
}
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
opts.Printer(transformer(val.Get(i).item))
|
||||
opts.Printer(transformer(merger.Get(i).item))
|
||||
}
|
||||
if count == 0 {
|
||||
exitCode = ExitNoMatch
|
||||
@@ -510,7 +511,7 @@ func Run(opts *Options) (int, error) {
|
||||
stop = true
|
||||
return
|
||||
}
|
||||
determine(val.final)
|
||||
determine(merger.final)
|
||||
}
|
||||
}
|
||||
terminal.UpdateList(val)
|
||||
|
||||
@@ -19,6 +19,20 @@ type MatchRequest struct {
|
||||
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
|
||||
type Matcher struct {
|
||||
cache *ChunkCache
|
||||
@@ -29,7 +43,7 @@ type Matcher struct {
|
||||
reqBox *util.EventBox
|
||||
partitions int
|
||||
slab []*util.Slab
|
||||
mergerCache map[string]*Merger
|
||||
mergerCache map[string]MatchResult
|
||||
revision revision
|
||||
}
|
||||
|
||||
@@ -51,7 +65,7 @@ func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
|
||||
reqBox: util.NewEventBox(),
|
||||
partitions: partitions,
|
||||
slab: make([]*util.Slab, partitions),
|
||||
mergerCache: make(map[string]*Merger),
|
||||
mergerCache: make(map[string]MatchResult),
|
||||
revision: revision}
|
||||
}
|
||||
|
||||
@@ -85,7 +99,7 @@ func (m *Matcher) Loop() {
|
||||
cacheCleared := false
|
||||
if request.sort != m.sort || request.revision != m.revision {
|
||||
m.sort = request.sort
|
||||
m.mergerCache = make(map[string]*Merger)
|
||||
m.mergerCache = make(map[string]MatchResult)
|
||||
if !request.revision.compatible(m.revision) {
|
||||
m.cache.Clear()
|
||||
}
|
||||
@@ -95,33 +109,32 @@ func (m *Matcher) Loop() {
|
||||
|
||||
// Restart search
|
||||
patternString := request.pattern.AsString()
|
||||
var merger *Merger
|
||||
cancelled := false
|
||||
var result MatchResult
|
||||
count := CountItems(request.chunks)
|
||||
|
||||
if !cacheCleared {
|
||||
if count == prevCount {
|
||||
// Look up mergerCache
|
||||
if cached, found := m.mergerCache[patternString]; found && cached.final == request.final {
|
||||
merger = cached
|
||||
if cached, found := m.mergerCache[patternString]; found && cached.final() == request.final {
|
||||
result = cached
|
||||
}
|
||||
} else {
|
||||
// Invalidate mergerCache
|
||||
prevCount = count
|
||||
m.mergerCache = make(map[string]*Merger)
|
||||
m.mergerCache = make(map[string]MatchResult)
|
||||
}
|
||||
}
|
||||
|
||||
if merger == nil {
|
||||
merger, cancelled = m.scan(request)
|
||||
if result.merger == nil {
|
||||
result = m.scan(request)
|
||||
}
|
||||
|
||||
if !cancelled {
|
||||
if merger.cacheable() {
|
||||
m.mergerCache[patternString] = merger
|
||||
if !result.cancelled {
|
||||
if result.cacheable() {
|
||||
m.mergerCache[patternString] = result
|
||||
}
|
||||
merger.final = request.final
|
||||
m.eventBox.Set(EvtSearchFin, merger)
|
||||
result.merger.final = request.final
|
||||
m.eventBox.Set(EvtSearchFin, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,16 +165,18 @@ type partialResult struct {
|
||||
matches []Result
|
||||
}
|
||||
|
||||
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
||||
func (m *Matcher) scan(request MatchRequest) MatchResult {
|
||||
startedAt := time.Now()
|
||||
|
||||
numChunks := len(request.chunks)
|
||||
if numChunks == 0 {
|
||||
return EmptyMerger(request.revision), false
|
||||
m := EmptyMerger(request.revision)
|
||||
return MatchResult{m, m, false}
|
||||
}
|
||||
pattern := request.pattern
|
||||
passMerger := PassMerger(&request.chunks, m.tac, request.revision)
|
||||
if pattern.IsEmpty() {
|
||||
return PassMerger(&request.chunks, m.tac, request.revision), false
|
||||
return MatchResult{passMerger, passMerger, false}
|
||||
}
|
||||
|
||||
minIndex := request.chunks[0].items[0].Index()
|
||||
@@ -224,7 +239,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
||||
}
|
||||
|
||||
if m.reqBox.Peek(reqReset) {
|
||||
return nil, wait()
|
||||
return MatchResult{nil, nil, wait()}
|
||||
}
|
||||
|
||||
if time.Since(startedAt) > progressMinDuration {
|
||||
@@ -237,7 +252,8 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
||||
partialResult := <-resultChan
|
||||
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
|
||||
|
||||
@@ -141,6 +141,15 @@ func (mg *Merger) Get(idx int) Result {
|
||||
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 {
|
||||
return mg.count < mergerCacheMax
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ Usage: fzf [options]
|
||||
--wrap Enable line wrap
|
||||
--wrap-sign=STR Indicator for wrapped lines
|
||||
--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
|
||||
--tac Reverse the order of the input
|
||||
--gap[=N] Render empty lines between each item
|
||||
@@ -111,6 +112,7 @@ Usage: fzf [options]
|
||||
highlighted substring (default: 10)
|
||||
--jump-labels=CHARS Label characters for jump mode
|
||||
--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 '>')
|
||||
--marker=STR Multi-select marker (default: '┃' or '>')
|
||||
--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
|
||||
Delimiter Delimiter
|
||||
Sort int
|
||||
Raw bool
|
||||
Track trackOption
|
||||
Tac bool
|
||||
Tail int
|
||||
@@ -593,6 +596,7 @@ type Options struct {
|
||||
JumpLabels string
|
||||
Prompt string
|
||||
Gutter *string
|
||||
GutterRaw *string
|
||||
Pointer *string
|
||||
Marker *string
|
||||
MarkerMulti *[3]string
|
||||
@@ -714,6 +718,7 @@ func defaultOptions() *Options {
|
||||
JumpLabels: defaultJumpLabels,
|
||||
Prompt: "> ",
|
||||
Gutter: nil,
|
||||
GutterRaw: nil,
|
||||
Pointer: nil,
|
||||
Marker: nil,
|
||||
MarkerMulti: nil,
|
||||
@@ -1442,6 +1447,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
|
||||
mergeAttr(&theme.SelectedBg)
|
||||
case "nth":
|
||||
mergeAttr(&theme.Nth)
|
||||
case "hidden":
|
||||
mergeAttr(&theme.Hidden)
|
||||
case "gutter":
|
||||
mergeAttr(&theme.Gutter)
|
||||
case "hl":
|
||||
@@ -1741,6 +1748,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
||||
appendAction(actToggleMultiLine)
|
||||
case "toggle-hscroll":
|
||||
appendAction(actToggleHscroll)
|
||||
case "toggle-raw":
|
||||
appendAction(actToggleRaw)
|
||||
case "show-header":
|
||||
appendAction(actShowHeader)
|
||||
case "hide-header":
|
||||
@@ -1761,8 +1770,12 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
||||
appendAction(actToggle)
|
||||
case "down":
|
||||
appendAction(actDown)
|
||||
case "down-match":
|
||||
appendAction(actDownMatch)
|
||||
case "up":
|
||||
appendAction(actUp)
|
||||
case "up-match":
|
||||
appendAction(actUpMatch)
|
||||
case "first", "top":
|
||||
appendAction(actFirst)
|
||||
case "last":
|
||||
@@ -1779,9 +1792,9 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
||||
appendAction(actPrevHistory)
|
||||
case "next-history":
|
||||
appendAction(actNextHistory)
|
||||
case "prev-selected":
|
||||
case "up-selected", "prev-selected":
|
||||
appendAction(actPrevSelected)
|
||||
case "next-selected":
|
||||
case "down-selected", "next-selected":
|
||||
appendAction(actNextSelected)
|
||||
case "show-preview":
|
||||
appendAction(actShowPreview)
|
||||
@@ -2682,6 +2695,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
||||
}
|
||||
case "+s", "--no-sort":
|
||||
opts.Sort = 0
|
||||
case "--raw":
|
||||
opts.Raw = true
|
||||
case "--no-raw":
|
||||
opts.Raw = false
|
||||
case "--track":
|
||||
opts.Track = trackEnabled
|
||||
case "--no-track":
|
||||
@@ -2866,6 +2883,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
||||
}
|
||||
str = firstLine(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":
|
||||
str, err := nextString("pointer sign required")
|
||||
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 {
|
||||
runes := []rune(*opts.Scrollbar)
|
||||
if len(runes) > 2 {
|
||||
|
||||
@@ -350,8 +350,8 @@ func TestDefaultCtrlNP(t *testing.T) {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
check([]string{}, tui.CtrlN, actDown)
|
||||
check([]string{}, tui.CtrlP, actUp)
|
||||
check([]string{}, tui.CtrlN, actDownMatch)
|
||||
check([]string{}, tui.CtrlP, actUpMatch)
|
||||
|
||||
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
||||
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
||||
|
||||
221
src/terminal.go
221
src/terminal.go
@@ -274,9 +274,11 @@ type Terminal struct {
|
||||
footerLabelLen int
|
||||
footerLabelOpts labelOpts
|
||||
gutterReverse bool
|
||||
gutterRawReverse bool
|
||||
pointer string
|
||||
pointerLen int
|
||||
pointerEmpty string
|
||||
pointerEmptyRaw string
|
||||
marker string
|
||||
markerLen int
|
||||
markerEmpty string
|
||||
@@ -297,6 +299,7 @@ type Terminal struct {
|
||||
subWordNext string
|
||||
cx int
|
||||
cy int
|
||||
lastMatchingIndex int32
|
||||
offset int
|
||||
xoffset int
|
||||
yanked []rune
|
||||
@@ -383,6 +386,9 @@ type Terminal struct {
|
||||
printer func(string)
|
||||
printsep string
|
||||
merger *Merger
|
||||
passMerger *Merger
|
||||
resultMerger *Merger
|
||||
matchMap map[int32]Result
|
||||
selected map[int32]selectedItem
|
||||
version int64
|
||||
revision revision
|
||||
@@ -429,6 +435,7 @@ type Terminal struct {
|
||||
clickFooterColumn int
|
||||
proxyScript string
|
||||
numLinesCache map[int32]numLinesCacheValue
|
||||
raw bool
|
||||
}
|
||||
|
||||
type numLinesCacheValue struct {
|
||||
@@ -569,13 +576,16 @@ const (
|
||||
actToggleWrap
|
||||
actToggleMultiLine
|
||||
actToggleHscroll
|
||||
actToggleRaw
|
||||
actTrackCurrent
|
||||
actToggleInput
|
||||
actHideInput
|
||||
actShowInput
|
||||
actUntrackCurrent
|
||||
actDown
|
||||
actDownMatch
|
||||
actUp
|
||||
actUpMatch
|
||||
actPageUp
|
||||
actPageDown
|
||||
actPosition
|
||||
@@ -796,8 +806,10 @@ func defaultKeymap() map[tui.Event][]*action {
|
||||
add(tui.CtrlK, actUp)
|
||||
add(tui.CtrlL, actClearScreen)
|
||||
add(tui.Enter, actAccept)
|
||||
add(tui.CtrlN, actDown)
|
||||
add(tui.CtrlP, actUp)
|
||||
add(tui.CtrlN, actDownMatch)
|
||||
add(tui.CtrlP, actUpMatch)
|
||||
add(tui.AltDown, actDownMatch)
|
||||
add(tui.AltUp, actUpMatch)
|
||||
add(tui.CtrlU, actUnixLineDiscard)
|
||||
add(tui.CtrlW, actUnixWordRubout)
|
||||
add(tui.CtrlY, actYank)
|
||||
@@ -953,6 +965,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
}
|
||||
keymapCopy := maps.Clone(opts.Keymap)
|
||||
|
||||
em := EmptyMerger(revision{})
|
||||
t := Terminal{
|
||||
initDelay: delay,
|
||||
infoCommand: opts.InfoCommand,
|
||||
@@ -980,6 +993,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
subWordNext: subWordNext,
|
||||
cx: len(input),
|
||||
cy: 0,
|
||||
lastMatchingIndex: minItem.Index(),
|
||||
offset: 0,
|
||||
xoffset: 0,
|
||||
yanked: []rune{},
|
||||
@@ -1039,6 +1053,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
nth: opts.Nth,
|
||||
nthCurrent: opts.Nth,
|
||||
tabstop: opts.Tabstop,
|
||||
raw: opts.Raw,
|
||||
hasStartActions: false,
|
||||
hasResultActions: false,
|
||||
hasFocusActions: false,
|
||||
@@ -1052,7 +1067,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
printer: opts.Printer,
|
||||
printsep: opts.PrintSep,
|
||||
proxyScript: opts.ProxyScript,
|
||||
merger: EmptyMerger(revision{}),
|
||||
merger: em,
|
||||
passMerger: em,
|
||||
resultMerger: em,
|
||||
matchMap: make(map[int32]Result),
|
||||
selected: make(map[int32]selectedItem),
|
||||
runningCmds: util.NewConcurrentSet[*runningCmd](),
|
||||
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())
|
||||
|
||||
// Gutter character
|
||||
var gutterChar string
|
||||
var gutterChar, gutterRawChar string
|
||||
if opts.Gutter != nil {
|
||||
gutterChar = *opts.Gutter
|
||||
} 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
|
||||
}
|
||||
|
||||
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)
|
||||
// Pre-calculated empty pointer and marker signs
|
||||
if t.pointerLen == 0 {
|
||||
t.pointerEmpty = ""
|
||||
t.pointerEmptyRaw = ""
|
||||
} else {
|
||||
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)
|
||||
|
||||
@@ -1282,7 +1312,7 @@ func (t *Terminal) environImpl(forPreview bool) []string {
|
||||
}
|
||||
env = append(env, "FZF_INPUT_STATE="+inputState)
|
||||
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_LINES=%d", t.areaLines))
|
||||
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
|
||||
func (t *Terminal) UpdateList(merger *Merger) {
|
||||
func (t *Terminal) UpdateList(result MatchResult) {
|
||||
merger := result.merger
|
||||
t.mutex.Lock()
|
||||
prevIndex := minItem.Index()
|
||||
newRevision := merger.Revision()
|
||||
@@ -1706,6 +1737,15 @@ func (t *Terminal) UpdateList(merger *Merger) {
|
||||
}
|
||||
t.progress = 100
|
||||
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.compatible(newRevision) {
|
||||
// Reloaded: clear selection
|
||||
@@ -1754,7 +1794,7 @@ func (t *Terminal) UpdateList(merger *Merger) {
|
||||
}
|
||||
needActivation := false
|
||||
if !t.reading {
|
||||
switch t.merger.Length() {
|
||||
switch t.resultMerger.Length() {
|
||||
case 0:
|
||||
zero := tui.Zero.AsEvent()
|
||||
if _, prs := t.keymap[zero]; prs {
|
||||
@@ -2801,7 +2841,7 @@ func (t *Terminal) printInfoImpl() {
|
||||
return
|
||||
}
|
||||
|
||||
found := t.merger.Length()
|
||||
found := t.resultMerger.Length()
|
||||
total := util.Max(found, t.count)
|
||||
output := fmt.Sprintf("%d/%d", found, total)
|
||||
if t.toggleSort {
|
||||
@@ -3122,12 +3162,16 @@ func (t *Terminal) gutter(current bool) {
|
||||
var color tui.ColorPair
|
||||
if current {
|
||||
color = tui.ColCurrentCursorEmpty
|
||||
} else if t.gutterReverse {
|
||||
} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
|
||||
color = tui.ColCursorEmpty
|
||||
} else {
|
||||
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) {
|
||||
@@ -3162,7 +3206,11 @@ func (t *Terminal) printList() {
|
||||
for line, itemCount := startLine, 0; line <= maxy; line, itemCount = line+1, itemCount+1 {
|
||||
if itemCount < count {
|
||||
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 {
|
||||
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 {
|
||||
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()]
|
||||
label := ""
|
||||
extraWidth := 0
|
||||
@@ -3310,7 +3366,13 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
||||
}
|
||||
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 {
|
||||
preTask := func(marker markerClass) int {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
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
|
||||
matchOffsets := []Offset{}
|
||||
var pos *[]int
|
||||
if match && t.merger.pattern != nil {
|
||||
_, matchOffsets, pos = t.merger.pattern.MatchItem(item, true, t.slab)
|
||||
if match && t.resultMerger.pattern != nil {
|
||||
_, matchOffsets, pos = t.resultMerger.pattern.MatchItem(item, true, t.slab)
|
||||
}
|
||||
charOffsets := matchOffsets
|
||||
if pos != nil {
|
||||
@@ -3429,7 +3495,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
}
|
||||
if !wholeCovered && t.nthAttr > 0 {
|
||||
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
|
||||
} else {
|
||||
tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent)
|
||||
@@ -4677,6 +4743,33 @@ func (t *Terminal) currentItem() *Item {
|
||||
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) {
|
||||
current := t.currentItem()
|
||||
slot, plus, asterisk, forceUpdate := hasPreviewFlags(template)
|
||||
@@ -4721,6 +4814,10 @@ func (t *Terminal) selectItem(item *Item) bool {
|
||||
if len(t.selected) >= t.multi {
|
||||
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 {
|
||||
return true
|
||||
}
|
||||
@@ -5917,8 +6014,9 @@ func (t *Terminal) Loop() error {
|
||||
}
|
||||
case actSelectAll:
|
||||
if t.multi > 0 {
|
||||
for i := 0; i < t.merger.Length(); i++ {
|
||||
if !t.selectItem(t.merger.Get(i).item) {
|
||||
// Limit the scope only to the matching items
|
||||
for i := 0; i < t.resultMerger.Length(); i++ {
|
||||
if !t.selectItem(t.resultMerger.Get(i).item) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -5926,8 +6024,10 @@ func (t *Terminal) Loop() error {
|
||||
}
|
||||
case actDeselectAll:
|
||||
if t.multi > 0 {
|
||||
for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
|
||||
t.deselectItem(t.merger.Get(i).item)
|
||||
// Also limit the scope only to the matching items, while this may
|
||||
// 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)
|
||||
}
|
||||
@@ -5955,17 +6055,17 @@ func (t *Terminal) Loop() error {
|
||||
case actToggleAll:
|
||||
if t.multi > 0 {
|
||||
prevIndexes := make(map[int]struct{})
|
||||
for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
|
||||
item := t.merger.Get(i).item
|
||||
for i := 0; i < t.resultMerger.Length() && len(t.selected) > 0; i++ {
|
||||
item := t.resultMerger.Get(i).item
|
||||
if _, found := t.selected[item.Index()]; found {
|
||||
prevIndexes[i] = struct{}{}
|
||||
t.deselectItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < t.merger.Length(); i++ {
|
||||
for i := 0; i < t.resultMerger.Length(); i++ {
|
||||
if _, found := prevIndexes[i]; !found {
|
||||
item := t.merger.Get(i).item
|
||||
item := t.resultMerger.Get(i).item
|
||||
if !t.selectItem(item) {
|
||||
break
|
||||
}
|
||||
@@ -5993,19 +6093,77 @@ func (t *Terminal) Loop() error {
|
||||
t.vmove(1, true)
|
||||
req(reqList)
|
||||
}
|
||||
case actDown:
|
||||
case actDown, actDownMatch:
|
||||
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)
|
||||
case actUp:
|
||||
case actUp, actUpMatch:
|
||||
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)
|
||||
case actAccept:
|
||||
req(reqClose)
|
||||
case actAcceptNonEmpty:
|
||||
// TODO: Allow accepting unmatched items in raw mode?
|
||||
if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {
|
||||
req(reqClose)
|
||||
}
|
||||
case actAcceptOrPrintQuery:
|
||||
// TODO: Allow accepting unmatched items in raw mode?
|
||||
if len(t.selected) > 0 || t.merger.Length() > 0 {
|
||||
req(reqClose)
|
||||
} else {
|
||||
@@ -6841,7 +6999,7 @@ func (t *Terminal) Loop() error {
|
||||
reload := changed || newCommand != nil
|
||||
var reloadRequest *searchRequest
|
||||
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
|
||||
@@ -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 {
|
||||
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 {
|
||||
@@ -7045,9 +7204,9 @@ func (t *Terminal) dumpStatus(params getParams) string {
|
||||
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 {
|
||||
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
|
||||
@@ -7064,7 +7223,7 @@ func (t *Terminal) dumpStatus(params getParams) string {
|
||||
Position: t.cy,
|
||||
Sort: t.sort,
|
||||
TotalCount: t.count,
|
||||
MatchCount: t.merger.Length(),
|
||||
MatchCount: t.resultMerger.Length(),
|
||||
Current: current,
|
||||
Matches: matches,
|
||||
Selected: selected,
|
||||
|
||||
@@ -381,6 +381,12 @@ func (p ColorPair) WithAttr(attr Attr) ColorPair {
|
||||
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 {
|
||||
dup := p
|
||||
bgPair := ColorPair{colUndefined, bg.Color, bg.Attr}
|
||||
@@ -410,6 +416,7 @@ type ColorTheme struct {
|
||||
ListBg ColorAttr
|
||||
AltBg ColorAttr
|
||||
Nth ColorAttr
|
||||
Hidden ColorAttr
|
||||
SelectedFg ColorAttr
|
||||
SelectedBg ColorAttr
|
||||
SelectedMatch ColorAttr
|
||||
@@ -866,6 +873,7 @@ func EmptyTheme() *ColorTheme {
|
||||
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||
Hidden: ColorAttr{colUndefined, Dim},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,6 +924,7 @@ func NoColorTheme() *ColorTheme {
|
||||
FooterLabel: ColorAttr{colDefault, AttrUndefined},
|
||||
GapLine: ColorAttr{colDefault, AttrUndefined},
|
||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||
Hidden: ColorAttr{colUndefined, Dim},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -967,6 +976,7 @@ func init() {
|
||||
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||
Hidden: ColorAttr{colUndefined, Dim},
|
||||
}
|
||||
Dark256 = &ColorTheme{
|
||||
Colored: true,
|
||||
@@ -1015,6 +1025,7 @@ func init() {
|
||||
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||
Hidden: ColorAttr{colUndefined, Dim},
|
||||
}
|
||||
Light256 = &ColorTheme{
|
||||
Colored: true,
|
||||
@@ -1063,6 +1074,7 @@ func init() {
|
||||
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||
Hidden: ColorAttr{colUndefined, Dim},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
60
test/test_raw.rb
Normal file
60
test/test_raw.rb
Normal 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
|
||||
Reference in New Issue
Block a user