mirror of
https://github.com/junegunn/fzf.git
synced 2026-03-07 15:42:28 +08:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce58d08ee3 | ||
|
|
997a7e5947 | ||
|
|
88e48619d6 | ||
|
|
2db14b4308 | ||
|
|
90c4269d4e | ||
|
|
6087055305 | ||
|
|
2f9df91171 | ||
|
|
12e24d368c | ||
|
|
55193ee4dc | ||
|
|
ff6a3bbee0 | ||
|
|
dce248ac6d | ||
|
|
0ff13dcfbe | ||
|
|
4d6a7757b8 | ||
|
|
b9804f5873 | ||
|
|
98a3b1fff8 | ||
|
|
6df5ca17e8 | ||
|
|
09ca45f7db | ||
|
|
09fe3a4180 |
64
.github/labeler.yml
vendored
Normal file
64
.github/labeler.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
go:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- src/**
|
||||||
|
- main.go
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
|
||||||
|
shell:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- shell/**
|
||||||
|
|
||||||
|
bash:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- shell/**/*.bash
|
||||||
|
|
||||||
|
zsh:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- shell/**/*.zsh
|
||||||
|
|
||||||
|
fish:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- shell/**/*.fish
|
||||||
|
|
||||||
|
vim:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- plugin/**
|
||||||
|
|
||||||
|
docs:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- '*.md'
|
||||||
|
- doc/**
|
||||||
|
- man/**
|
||||||
|
|
||||||
|
ci:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- .github/**
|
||||||
|
|
||||||
|
build:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- Makefile
|
||||||
|
- .goreleaser.yml
|
||||||
|
- Dockerfile
|
||||||
|
|
||||||
|
test:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- test/**
|
||||||
|
- src/**/*_test.go
|
||||||
|
|
||||||
|
install:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- install
|
||||||
|
- install.ps1
|
||||||
|
- uninstall
|
||||||
17
.github/workflows/labeler.yml
vendored
Normal file
17
.github/workflows/labeler.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Label PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
label:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/labeler@v5
|
||||||
|
with:
|
||||||
|
configuration-path: .github/labeler.yml
|
||||||
@@ -1881,6 +1881,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
|
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
|
||||||
\fBchange\-ghost(...)\fR (change ghost text to the given string)
|
\fBchange\-ghost(...)\fR (change ghost text to the given string)
|
||||||
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
|
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
|
||||||
|
\fBchange\-header\-lines(N)\fR (change the number of \fB\-\-header\-lines\fR)
|
||||||
\fBchange\-header\-label(...)\fR (change \fB\-\-header\-label\fR to the given string)
|
\fBchange\-header\-label(...)\fR (change \fB\-\-header\-label\fR to the given string)
|
||||||
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
|
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
|
||||||
\fBchange\-list\-label(...)\fR (change \fB\-\-list\-label\fR to the given string)
|
\fBchange\-list\-label(...)\fR (change \fB\-\-list\-label\fR to the given string)
|
||||||
@@ -1987,6 +1988,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtransform\-border\-label(...)\fR (transform border label using an external command)
|
\fBtransform\-border\-label(...)\fR (transform border label using an external command)
|
||||||
\fBtransform\-ghost(...)\fR (transform ghost text using an external command)
|
\fBtransform\-ghost(...)\fR (transform ghost text using an external command)
|
||||||
\fBtransform\-header(...)\fR (transform header using an external command)
|
\fBtransform\-header(...)\fR (transform header using an external command)
|
||||||
|
\fBtransform\-header\-lines(...)\fR (transform the number of \fB\-\-header\-lines\fR using an external command)
|
||||||
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
|
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
|
||||||
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
|
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
|
||||||
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
|
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
|
||||||
|
|||||||
@@ -30,161 +30,164 @@ func _() {
|
|||||||
_ = x[actChangeBorderLabel-19]
|
_ = x[actChangeBorderLabel-19]
|
||||||
_ = x[actChangeGhost-20]
|
_ = x[actChangeGhost-20]
|
||||||
_ = x[actChangeHeader-21]
|
_ = x[actChangeHeader-21]
|
||||||
_ = x[actChangeFooter-22]
|
_ = x[actChangeHeaderLines-22]
|
||||||
_ = x[actChangeHeaderLabel-23]
|
_ = x[actChangeFooter-23]
|
||||||
_ = x[actChangeFooterLabel-24]
|
_ = x[actChangeHeaderLabel-24]
|
||||||
_ = x[actChangeInputLabel-25]
|
_ = x[actChangeFooterLabel-25]
|
||||||
_ = x[actChangeListLabel-26]
|
_ = x[actChangeInputLabel-26]
|
||||||
_ = x[actChangeMulti-27]
|
_ = x[actChangeListLabel-27]
|
||||||
_ = x[actChangeNth-28]
|
_ = x[actChangeMulti-28]
|
||||||
_ = x[actChangePointer-29]
|
_ = x[actChangeNth-29]
|
||||||
_ = x[actChangePreview-30]
|
_ = x[actChangePointer-30]
|
||||||
_ = x[actChangePreviewLabel-31]
|
_ = x[actChangePreview-31]
|
||||||
_ = x[actChangePreviewWindow-32]
|
_ = x[actChangePreviewLabel-32]
|
||||||
_ = x[actChangePrompt-33]
|
_ = x[actChangePreviewWindow-33]
|
||||||
_ = x[actChangeQuery-34]
|
_ = x[actChangePrompt-34]
|
||||||
_ = x[actClearScreen-35]
|
_ = x[actChangeQuery-35]
|
||||||
_ = x[actClearQuery-36]
|
_ = x[actClearScreen-36]
|
||||||
_ = x[actClearSelection-37]
|
_ = x[actClearQuery-37]
|
||||||
_ = x[actClose-38]
|
_ = x[actClearSelection-38]
|
||||||
_ = x[actDeleteChar-39]
|
_ = x[actClose-39]
|
||||||
_ = x[actDeleteCharEof-40]
|
_ = x[actDeleteChar-40]
|
||||||
_ = x[actEndOfLine-41]
|
_ = x[actDeleteCharEof-41]
|
||||||
_ = x[actFatal-42]
|
_ = x[actEndOfLine-42]
|
||||||
_ = x[actForwardChar-43]
|
_ = x[actFatal-43]
|
||||||
_ = x[actForwardWord-44]
|
_ = x[actForwardChar-44]
|
||||||
_ = x[actForwardSubWord-45]
|
_ = x[actForwardWord-45]
|
||||||
_ = x[actKillLine-46]
|
_ = x[actForwardSubWord-46]
|
||||||
_ = x[actKillWord-47]
|
_ = x[actKillLine-47]
|
||||||
_ = x[actKillSubWord-48]
|
_ = x[actKillWord-48]
|
||||||
_ = x[actUnixLineDiscard-49]
|
_ = x[actKillSubWord-49]
|
||||||
_ = x[actUnixWordRubout-50]
|
_ = x[actUnixLineDiscard-50]
|
||||||
_ = x[actYank-51]
|
_ = x[actUnixWordRubout-51]
|
||||||
_ = x[actBackwardKillWord-52]
|
_ = x[actYank-52]
|
||||||
_ = x[actBackwardKillSubWord-53]
|
_ = x[actBackwardKillWord-53]
|
||||||
_ = x[actSelectAll-54]
|
_ = x[actBackwardKillSubWord-54]
|
||||||
_ = x[actDeselectAll-55]
|
_ = x[actSelectAll-55]
|
||||||
_ = x[actToggle-56]
|
_ = x[actDeselectAll-56]
|
||||||
_ = x[actToggleSearch-57]
|
_ = x[actToggle-57]
|
||||||
_ = x[actToggleAll-58]
|
_ = x[actToggleSearch-58]
|
||||||
_ = x[actToggleDown-59]
|
_ = x[actToggleAll-59]
|
||||||
_ = x[actToggleUp-60]
|
_ = x[actToggleDown-60]
|
||||||
_ = x[actToggleIn-61]
|
_ = x[actToggleUp-61]
|
||||||
_ = x[actToggleOut-62]
|
_ = x[actToggleIn-62]
|
||||||
_ = x[actToggleTrack-63]
|
_ = x[actToggleOut-63]
|
||||||
_ = x[actToggleTrackCurrent-64]
|
_ = x[actToggleTrack-64]
|
||||||
_ = x[actToggleHeader-65]
|
_ = x[actToggleTrackCurrent-65]
|
||||||
_ = x[actToggleWrap-66]
|
_ = x[actToggleHeader-66]
|
||||||
_ = x[actToggleWrapWord-67]
|
_ = x[actToggleWrap-67]
|
||||||
_ = x[actToggleMultiLine-68]
|
_ = x[actToggleWrapWord-68]
|
||||||
_ = x[actToggleHscroll-69]
|
_ = x[actToggleMultiLine-69]
|
||||||
_ = x[actToggleRaw-70]
|
_ = x[actToggleHscroll-70]
|
||||||
_ = x[actEnableRaw-71]
|
_ = x[actToggleRaw-71]
|
||||||
_ = x[actDisableRaw-72]
|
_ = x[actEnableRaw-72]
|
||||||
_ = x[actTrackCurrent-73]
|
_ = x[actDisableRaw-73]
|
||||||
_ = x[actToggleInput-74]
|
_ = x[actTrackCurrent-74]
|
||||||
_ = x[actHideInput-75]
|
_ = x[actToggleInput-75]
|
||||||
_ = x[actShowInput-76]
|
_ = x[actHideInput-76]
|
||||||
_ = x[actUntrackCurrent-77]
|
_ = x[actShowInput-77]
|
||||||
_ = x[actDown-78]
|
_ = x[actUntrackCurrent-78]
|
||||||
_ = x[actDownMatch-79]
|
_ = x[actDown-79]
|
||||||
_ = x[actUp-80]
|
_ = x[actDownMatch-80]
|
||||||
_ = x[actUpMatch-81]
|
_ = x[actUp-81]
|
||||||
_ = x[actPageUp-82]
|
_ = x[actUpMatch-82]
|
||||||
_ = x[actPageDown-83]
|
_ = x[actPageUp-83]
|
||||||
_ = x[actPosition-84]
|
_ = x[actPageDown-84]
|
||||||
_ = x[actHalfPageUp-85]
|
_ = x[actPosition-85]
|
||||||
_ = x[actHalfPageDown-86]
|
_ = x[actHalfPageUp-86]
|
||||||
_ = x[actOffsetUp-87]
|
_ = x[actHalfPageDown-87]
|
||||||
_ = x[actOffsetDown-88]
|
_ = x[actOffsetUp-88]
|
||||||
_ = x[actOffsetMiddle-89]
|
_ = x[actOffsetDown-89]
|
||||||
_ = x[actJump-90]
|
_ = x[actOffsetMiddle-90]
|
||||||
_ = x[actJumpAccept-91]
|
_ = x[actJump-91]
|
||||||
_ = x[actPrintQuery-92]
|
_ = x[actJumpAccept-92]
|
||||||
_ = x[actRefreshPreview-93]
|
_ = x[actPrintQuery-93]
|
||||||
_ = x[actReplaceQuery-94]
|
_ = x[actRefreshPreview-94]
|
||||||
_ = x[actToggleSort-95]
|
_ = x[actReplaceQuery-95]
|
||||||
_ = x[actShowPreview-96]
|
_ = x[actToggleSort-96]
|
||||||
_ = x[actHidePreview-97]
|
_ = x[actShowPreview-97]
|
||||||
_ = x[actTogglePreview-98]
|
_ = x[actHidePreview-98]
|
||||||
_ = x[actTogglePreviewWrap-99]
|
_ = x[actTogglePreview-99]
|
||||||
_ = x[actTogglePreviewWrapWord-100]
|
_ = x[actTogglePreviewWrap-100]
|
||||||
_ = x[actTransform-101]
|
_ = x[actTogglePreviewWrapWord-101]
|
||||||
_ = x[actTransformBorderLabel-102]
|
_ = x[actTransform-102]
|
||||||
_ = x[actTransformGhost-103]
|
_ = x[actTransformBorderLabel-103]
|
||||||
_ = x[actTransformHeader-104]
|
_ = x[actTransformGhost-104]
|
||||||
_ = x[actTransformFooter-105]
|
_ = x[actTransformHeader-105]
|
||||||
_ = x[actTransformHeaderLabel-106]
|
_ = x[actTransformHeaderLines-106]
|
||||||
_ = x[actTransformFooterLabel-107]
|
_ = x[actTransformFooter-107]
|
||||||
_ = x[actTransformInputLabel-108]
|
_ = x[actTransformHeaderLabel-108]
|
||||||
_ = x[actTransformListLabel-109]
|
_ = x[actTransformFooterLabel-109]
|
||||||
_ = x[actTransformNth-110]
|
_ = x[actTransformInputLabel-110]
|
||||||
_ = x[actTransformPointer-111]
|
_ = x[actTransformListLabel-111]
|
||||||
_ = x[actTransformPreviewLabel-112]
|
_ = x[actTransformNth-112]
|
||||||
_ = x[actTransformPrompt-113]
|
_ = x[actTransformPointer-113]
|
||||||
_ = x[actTransformQuery-114]
|
_ = x[actTransformPreviewLabel-114]
|
||||||
_ = x[actTransformSearch-115]
|
_ = x[actTransformPrompt-115]
|
||||||
_ = x[actTrigger-116]
|
_ = x[actTransformQuery-116]
|
||||||
_ = x[actBgTransform-117]
|
_ = x[actTransformSearch-117]
|
||||||
_ = x[actBgTransformBorderLabel-118]
|
_ = x[actTrigger-118]
|
||||||
_ = x[actBgTransformGhost-119]
|
_ = x[actBgTransform-119]
|
||||||
_ = x[actBgTransformHeader-120]
|
_ = x[actBgTransformBorderLabel-120]
|
||||||
_ = x[actBgTransformFooter-121]
|
_ = x[actBgTransformGhost-121]
|
||||||
_ = x[actBgTransformHeaderLabel-122]
|
_ = x[actBgTransformHeader-122]
|
||||||
_ = x[actBgTransformFooterLabel-123]
|
_ = x[actBgTransformHeaderLines-123]
|
||||||
_ = x[actBgTransformInputLabel-124]
|
_ = x[actBgTransformFooter-124]
|
||||||
_ = x[actBgTransformListLabel-125]
|
_ = x[actBgTransformHeaderLabel-125]
|
||||||
_ = x[actBgTransformNth-126]
|
_ = x[actBgTransformFooterLabel-126]
|
||||||
_ = x[actBgTransformPointer-127]
|
_ = x[actBgTransformInputLabel-127]
|
||||||
_ = x[actBgTransformPreviewLabel-128]
|
_ = x[actBgTransformListLabel-128]
|
||||||
_ = x[actBgTransformPrompt-129]
|
_ = x[actBgTransformNth-129]
|
||||||
_ = x[actBgTransformQuery-130]
|
_ = x[actBgTransformPointer-130]
|
||||||
_ = x[actBgTransformSearch-131]
|
_ = x[actBgTransformPreviewLabel-131]
|
||||||
_ = x[actBgCancel-132]
|
_ = x[actBgTransformPrompt-132]
|
||||||
_ = x[actSearch-133]
|
_ = x[actBgTransformQuery-133]
|
||||||
_ = x[actPreview-134]
|
_ = x[actBgTransformSearch-134]
|
||||||
_ = x[actPreviewTop-135]
|
_ = x[actBgCancel-135]
|
||||||
_ = x[actPreviewBottom-136]
|
_ = x[actSearch-136]
|
||||||
_ = x[actPreviewUp-137]
|
_ = x[actPreview-137]
|
||||||
_ = x[actPreviewDown-138]
|
_ = x[actPreviewTop-138]
|
||||||
_ = x[actPreviewPageUp-139]
|
_ = x[actPreviewBottom-139]
|
||||||
_ = x[actPreviewPageDown-140]
|
_ = x[actPreviewUp-140]
|
||||||
_ = x[actPreviewHalfPageUp-141]
|
_ = x[actPreviewDown-141]
|
||||||
_ = x[actPreviewHalfPageDown-142]
|
_ = x[actPreviewPageUp-142]
|
||||||
_ = x[actPrevHistory-143]
|
_ = x[actPreviewPageDown-143]
|
||||||
_ = x[actPrevSelected-144]
|
_ = x[actPreviewHalfPageUp-144]
|
||||||
_ = x[actPrint-145]
|
_ = x[actPreviewHalfPageDown-145]
|
||||||
_ = x[actPut-146]
|
_ = x[actPrevHistory-146]
|
||||||
_ = x[actNextHistory-147]
|
_ = x[actPrevSelected-147]
|
||||||
_ = x[actNextSelected-148]
|
_ = x[actPrint-148]
|
||||||
_ = x[actExecute-149]
|
_ = x[actPut-149]
|
||||||
_ = x[actExecuteSilent-150]
|
_ = x[actNextHistory-150]
|
||||||
_ = x[actExecuteMulti-151]
|
_ = x[actNextSelected-151]
|
||||||
_ = x[actSigStop-152]
|
_ = x[actExecute-152]
|
||||||
_ = x[actBest-153]
|
_ = x[actExecuteSilent-153]
|
||||||
_ = x[actFirst-154]
|
_ = x[actExecuteMulti-154]
|
||||||
_ = x[actLast-155]
|
_ = x[actSigStop-155]
|
||||||
_ = x[actReload-156]
|
_ = x[actBest-156]
|
||||||
_ = x[actReloadSync-157]
|
_ = x[actFirst-157]
|
||||||
_ = x[actDisableSearch-158]
|
_ = x[actLast-158]
|
||||||
_ = x[actEnableSearch-159]
|
_ = x[actReload-159]
|
||||||
_ = x[actSelect-160]
|
_ = x[actReloadSync-160]
|
||||||
_ = x[actDeselect-161]
|
_ = x[actDisableSearch-161]
|
||||||
_ = x[actUnbind-162]
|
_ = x[actEnableSearch-162]
|
||||||
_ = x[actRebind-163]
|
_ = x[actSelect-163]
|
||||||
_ = x[actToggleBind-164]
|
_ = x[actDeselect-164]
|
||||||
_ = x[actBecome-165]
|
_ = x[actUnbind-165]
|
||||||
_ = x[actShowHeader-166]
|
_ = x[actRebind-166]
|
||||||
_ = x[actHideHeader-167]
|
_ = x[actToggleBind-167]
|
||||||
_ = x[actBell-168]
|
_ = x[actBecome-168]
|
||||||
_ = x[actExclude-169]
|
_ = x[actShowHeader-169]
|
||||||
_ = x[actExcludeMulti-170]
|
_ = x[actHideHeader-170]
|
||||||
_ = x[actAsync-171]
|
_ = x[actBell-171]
|
||||||
|
_ = x[actExclude-172]
|
||||||
|
_ = x[actExcludeMulti-173]
|
||||||
|
_ = x[actAsync-174]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleWrapWordactToggleMultiLineactToggleHscrollactToggleRawactEnableRawactDisableRawactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactDownMatchactUpactUpMatchactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTogglePreviewWrapWordactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactBestactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeHeaderLinesactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleWrapWordactToggleMultiLineactToggleHscrollactToggleRawactEnableRawactDisableRawactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactDownMatchactUpactUpMatchactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTogglePreviewWrapWordactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformHeaderLinesactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformHeaderLinesactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactBestactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
|
||||||
|
|
||||||
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, 992, 1010, 1026, 1038, 1050, 1063, 1078, 1092, 1104, 1116, 1133, 1140, 1152, 1157, 1167, 1176, 1187, 1198, 1211, 1226, 1237, 1250, 1265, 1272, 1285, 1298, 1315, 1330, 1343, 1357, 1371, 1387, 1407, 1431, 1443, 1466, 1483, 1501, 1519, 1542, 1565, 1587, 1608, 1623, 1642, 1666, 1684, 1701, 1719, 1729, 1743, 1768, 1787, 1807, 1827, 1852, 1877, 1901, 1924, 1941, 1962, 1988, 2008, 2027, 2047, 2058, 2067, 2077, 2090, 2106, 2118, 2132, 2148, 2166, 2186, 2208, 2222, 2237, 2245, 2251, 2265, 2280, 2290, 2306, 2321, 2331, 2338, 2346, 2353, 2362, 2375, 2391, 2406, 2415, 2426, 2435, 2444, 2457, 2466, 2479, 2492, 2499, 2509, 2524, 2532}
|
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, 336, 351, 371, 391, 410, 428, 442, 454, 470, 486, 507, 529, 544, 558, 572, 585, 602, 610, 623, 639, 651, 659, 673, 687, 704, 715, 726, 740, 758, 775, 782, 801, 823, 835, 849, 858, 873, 885, 898, 909, 920, 932, 946, 967, 982, 995, 1012, 1030, 1046, 1058, 1070, 1083, 1098, 1112, 1124, 1136, 1153, 1160, 1172, 1177, 1187, 1196, 1207, 1218, 1231, 1246, 1257, 1270, 1285, 1292, 1305, 1318, 1335, 1350, 1363, 1377, 1391, 1407, 1427, 1451, 1463, 1486, 1503, 1521, 1544, 1562, 1585, 1608, 1630, 1651, 1666, 1685, 1709, 1727, 1744, 1762, 1772, 1786, 1811, 1830, 1850, 1875, 1895, 1920, 1945, 1969, 1992, 2009, 2030, 2056, 2076, 2095, 2115, 2126, 2135, 2145, 2158, 2174, 2186, 2200, 2216, 2234, 2254, 2276, 2290, 2305, 2313, 2319, 2333, 2348, 2358, 2374, 2389, 2399, 2406, 2414, 2421, 2430, 2443, 2459, 2474, 2483, 2494, 2503, 2512, 2525, 2534, 2547, 2560, 2567, 2577, 2592, 2600}
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -52,6 +52,20 @@ func (cl *ChunkList) lastChunk() *Chunk {
|
|||||||
return cl.chunks[len(cl.chunks)-1]
|
return cl.chunks[len(cl.chunks)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetItems returns the first n items from the given chunks
|
||||||
|
func GetItems(chunks []*Chunk, n int) []Item {
|
||||||
|
items := make([]Item, 0, n)
|
||||||
|
for _, chunk := range chunks {
|
||||||
|
for i := 0; i < chunk.count && len(items) < n; i++ {
|
||||||
|
items = append(items, chunk.items[i])
|
||||||
|
}
|
||||||
|
if len(items) >= n {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
// CountItems returns the total number of Items
|
// CountItems returns the total number of Items
|
||||||
func CountItems(cs []*Chunk) int {
|
func CountItems(cs []*Chunk) int {
|
||||||
if len(cs) == 0 {
|
if len(cs) == 0 {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const (
|
|||||||
progressMinDuration = 200 * time.Millisecond
|
progressMinDuration = 200 * time.Millisecond
|
||||||
|
|
||||||
// Capacity of each chunk
|
// Capacity of each chunk
|
||||||
chunkSize int = 100
|
chunkSize int = 1000
|
||||||
|
|
||||||
// Pre-allocated memory slices to minimize GC
|
// Pre-allocated memory slices to minimize GC
|
||||||
slab16Size int = 100 * 1024 // 200KB * 32 = 12.8MB
|
slab16Size int = 100 * 1024 // 200KB * 32 = 12.8MB
|
||||||
@@ -65,7 +65,6 @@ const (
|
|||||||
EvtSearchNew
|
EvtSearchNew
|
||||||
EvtSearchProgress
|
EvtSearchProgress
|
||||||
EvtSearchFin
|
EvtSearchFin
|
||||||
EvtHeader
|
|
||||||
EvtReady
|
EvtReady
|
||||||
EvtQuit
|
EvtQuit
|
||||||
)
|
)
|
||||||
|
|||||||
99
src/core.go
99
src/core.go
@@ -2,6 +2,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -17,7 +18,6 @@ Reader -> EvtReadNew -> Matcher (restart)
|
|||||||
Terminal -> EvtSearchNew:bool -> Matcher (restart)
|
Terminal -> EvtSearchNew:bool -> Matcher (restart)
|
||||||
Matcher -> EvtSearchProgress -> Terminal (update info)
|
Matcher -> EvtSearchProgress -> Terminal (update info)
|
||||||
Matcher -> EvtSearchFin -> Terminal (update list)
|
Matcher -> EvtSearchFin -> Terminal (update list)
|
||||||
Matcher -> EvtHeader -> Terminal (update header)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type revision struct {
|
type revision struct {
|
||||||
@@ -113,14 +113,8 @@ func Run(opts *Options) (int, error) {
|
|||||||
cache := NewChunkCache()
|
cache := NewChunkCache()
|
||||||
var chunkList *ChunkList
|
var chunkList *ChunkList
|
||||||
var itemIndex int32
|
var itemIndex int32
|
||||||
header := make([]string, 0, opts.HeaderLines)
|
|
||||||
if opts.WithNth == nil {
|
if opts.WithNth == nil {
|
||||||
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
||||||
if len(header) < opts.HeaderLines {
|
|
||||||
header = append(header, byteString(data))
|
|
||||||
eventBox.Set(EvtHeader, header)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
item.text, item.colors = ansiProcessor(data)
|
item.text, item.colors = ansiProcessor(data)
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
itemIndex++
|
itemIndex++
|
||||||
@@ -147,11 +141,6 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
transformed := nthTransformer(tokens, itemIndex)
|
transformed := nthTransformer(tokens, itemIndex)
|
||||||
if len(header) < opts.HeaderLines {
|
|
||||||
header = append(header, transformed)
|
|
||||||
eventBox.Set(EvtHeader, header)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
||||||
|
|
||||||
// We should not trim trailing whitespaces with background colors
|
// We should not trim trailing whitespaces with background colors
|
||||||
@@ -193,7 +182,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync && opts.Bench == 0
|
||||||
var reader *Reader
|
var reader *Reader
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
reader = NewReader(func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
@@ -236,15 +225,17 @@ func Run(opts *Options) (int, error) {
|
|||||||
denylist = make(map[int32]struct{})
|
denylist = make(map[int32]struct{})
|
||||||
denyMutex.Unlock()
|
denyMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
headerLines := int32(opts.HeaderLines)
|
||||||
|
headerUpdated := false
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
denyMutex.Lock()
|
denyMutex.Lock()
|
||||||
denylistCopy := maps.Clone(denylist)
|
denylistCopy := maps.Clone(denylist)
|
||||||
denyMutex.Unlock()
|
denyMutex.Unlock()
|
||||||
return BuildPattern(cache, patternCache,
|
return BuildPattern(cache, patternCache,
|
||||||
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
||||||
opts.Filter == nil, nth, opts.Delimiter, inputRevision, runes, denylistCopy)
|
opts.Filter == nil, nth, opts.Delimiter, inputRevision, runes, denylistCopy, headerLines)
|
||||||
}
|
}
|
||||||
matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision, opts.Threads)
|
||||||
|
|
||||||
// Filtering mode
|
// Filtering mode
|
||||||
if opts.Filter != nil {
|
if opts.Filter != nil {
|
||||||
@@ -265,8 +256,11 @@ func Run(opts *Options) (int, error) {
|
|||||||
func(runes []byte) bool {
|
func(runes []byte) bool {
|
||||||
item := Item{}
|
item := Item{}
|
||||||
if chunkList.trans(&item, runes) {
|
if chunkList.trans(&item, runes) {
|
||||||
|
if item.Index() < headerLines {
|
||||||
|
return false
|
||||||
|
}
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
if result, _, _ := pattern.MatchItem(&item, false, slab); result.item != nil {
|
||||||
opts.Printer(transformer(&item))
|
opts.Printer(transformer(&item))
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
@@ -281,6 +275,46 @@ 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)
|
||||||
|
|
||||||
|
if opts.Bench > 0 {
|
||||||
|
// Benchmark mode: repeat scan for the given duration
|
||||||
|
totalItems := CountItems(snapshot)
|
||||||
|
var matchCount int
|
||||||
|
var times []time.Duration
|
||||||
|
deadline := time.Now().Add(opts.Bench)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
cache.Clear()
|
||||||
|
start := time.Now()
|
||||||
|
result := matcher.scan(MatchRequest{
|
||||||
|
chunks: snapshot,
|
||||||
|
pattern: pattern})
|
||||||
|
times = append(times, time.Since(start))
|
||||||
|
matchCount = result.merger.Length()
|
||||||
|
}
|
||||||
|
// Print stats
|
||||||
|
var total time.Duration
|
||||||
|
minD, maxD := times[0], times[0]
|
||||||
|
for _, d := range times {
|
||||||
|
total += d
|
||||||
|
if d < minD {
|
||||||
|
minD = d
|
||||||
|
}
|
||||||
|
if d > maxD {
|
||||||
|
maxD = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
avg := total / time.Duration(len(times))
|
||||||
|
selectivity := float64(matchCount) / float64(totalItems) * 100
|
||||||
|
fmt.Printf(" %d iterations avg: %.2fms min: %.2fms max: %.2fms total: %.2fs items: %d matches: %d (%.2f%%)\n",
|
||||||
|
len(times),
|
||||||
|
float64(avg.Microseconds())/1000,
|
||||||
|
float64(minD.Microseconds())/1000,
|
||||||
|
float64(maxD.Microseconds())/1000,
|
||||||
|
total.Seconds(),
|
||||||
|
totalItems, matchCount, selectivity)
|
||||||
|
return ExitOk, nil
|
||||||
|
}
|
||||||
|
|
||||||
result := matcher.scan(MatchRequest{
|
result := matcher.scan(MatchRequest{
|
||||||
chunks: snapshot,
|
chunks: snapshot,
|
||||||
pattern: pattern})
|
pattern: pattern})
|
||||||
@@ -330,10 +364,11 @@ func Run(opts *Options) (int, error) {
|
|||||||
query := []rune{}
|
query := []rune{}
|
||||||
determine := func(final bool) {
|
determine := func(final bool) {
|
||||||
if heightUnknown {
|
if heightUnknown {
|
||||||
if total >= maxFit || final {
|
items := max(0, total-int(headerLines))
|
||||||
|
if items >= maxFit || final {
|
||||||
deferred = false
|
deferred = false
|
||||||
heightUnknown = false
|
heightUnknown = false
|
||||||
terminal.startChan <- fitpad{min(total, maxFit), padHeight}
|
terminal.startChan <- fitpad{min(items, maxFit), padHeight}
|
||||||
}
|
}
|
||||||
} else if deferred {
|
} else if deferred {
|
||||||
deferred = false
|
deferred = false
|
||||||
@@ -349,11 +384,11 @@ func Run(opts *Options) (int, error) {
|
|||||||
clearDenylist()
|
clearDenylist()
|
||||||
}
|
}
|
||||||
reading = true
|
reading = true
|
||||||
|
headerUpdated = false
|
||||||
startTick = ticks
|
startTick = ticks
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
inputRevision.bumpMajor()
|
inputRevision.bumpMajor()
|
||||||
header = make([]string, 0, opts.HeaderLines)
|
|
||||||
readyChan := make(chan bool)
|
readyChan := make(chan bool)
|
||||||
go reader.restart(command, environ, readyChan)
|
go reader.restart(command, environ, readyChan)
|
||||||
<-readyChan
|
<-readyChan
|
||||||
@@ -411,7 +446,11 @@ func Run(opts *Options) (int, error) {
|
|||||||
snapshotRevision = inputRevision
|
snapshotRevision = inputRevision
|
||||||
}
|
}
|
||||||
total = count
|
total = count
|
||||||
terminal.UpdateCount(total, !reading, value.(*string))
|
terminal.UpdateCount(max(0, total-int(headerLines)), !reading, value.(*string))
|
||||||
|
if headerLines > 0 && !headerUpdated {
|
||||||
|
terminal.UpdateHeader(GetItems(snapshot, int(headerLines)))
|
||||||
|
headerUpdated = int32(total) >= headerLines
|
||||||
|
}
|
||||||
if heightUnknown && !deferred {
|
if heightUnknown && !deferred {
|
||||||
determine(!reading)
|
determine(!reading)
|
||||||
}
|
}
|
||||||
@@ -421,6 +460,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
var command *commandSpec
|
var command *commandSpec
|
||||||
var environ []string
|
var environ []string
|
||||||
var changed bool
|
var changed bool
|
||||||
|
headerLinesChanged := false
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
case searchRequest:
|
case searchRequest:
|
||||||
sort = val.sort
|
sort = val.sort
|
||||||
@@ -441,6 +481,12 @@ func Run(opts *Options) (int, error) {
|
|||||||
nth = *val.nth
|
nth = *val.nth
|
||||||
bump = true
|
bump = true
|
||||||
}
|
}
|
||||||
|
if val.headerLines != nil {
|
||||||
|
headerLines = int32(*val.headerLines)
|
||||||
|
headerUpdated = false
|
||||||
|
headerLinesChanged = true
|
||||||
|
bump = true
|
||||||
|
}
|
||||||
if bump {
|
if bump {
|
||||||
patternCache = make(map[string]*Pattern)
|
patternCache = make(map[string]*Pattern)
|
||||||
cache.Clear()
|
cache.Clear()
|
||||||
@@ -477,6 +523,14 @@ func Run(opts *Options) (int, error) {
|
|||||||
snapshotRevision = inputRevision
|
snapshotRevision = inputRevision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if headerLinesChanged {
|
||||||
|
terminal.UpdateCount(max(0, total-int(headerLines)), !reading, nil)
|
||||||
|
if headerLines > 0 {
|
||||||
|
terminal.UpdateHeader(GetItems(snapshot, int(headerLines)))
|
||||||
|
} else {
|
||||||
|
terminal.UpdateHeader(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
matcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision)
|
matcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision)
|
||||||
delay = false
|
delay = false
|
||||||
|
|
||||||
@@ -486,11 +540,6 @@ func Run(opts *Options) (int, error) {
|
|||||||
terminal.UpdateProgress(val)
|
terminal.UpdateProgress(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
case EvtHeader:
|
|
||||||
headerPadded := make([]string, opts.HeaderLines)
|
|
||||||
copy(headerPadded, value.([]string))
|
|
||||||
terminal.UpdateHeader(headerPadded)
|
|
||||||
|
|
||||||
case EvtSearchFin:
|
case EvtSearchFin:
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
case MatchResult:
|
case MatchResult:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -43,6 +42,7 @@ type Matcher struct {
|
|||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
partitions int
|
partitions int
|
||||||
slab []*util.Slab
|
slab []*util.Slab
|
||||||
|
sortBuf [][]Result
|
||||||
mergerCache map[string]MatchResult
|
mergerCache map[string]MatchResult
|
||||||
revision revision
|
revision revision
|
||||||
}
|
}
|
||||||
@@ -54,8 +54,11 @@ const (
|
|||||||
|
|
||||||
// NewMatcher returns a new Matcher
|
// NewMatcher returns a new Matcher
|
||||||
func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
|
func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
|
||||||
sort bool, tac bool, eventBox *util.EventBox, revision revision) *Matcher {
|
sort bool, tac bool, eventBox *util.EventBox, revision revision, threads int) *Matcher {
|
||||||
partitions := min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
partitions := min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
||||||
|
if threads > 0 {
|
||||||
|
partitions = threads
|
||||||
|
}
|
||||||
return &Matcher{
|
return &Matcher{
|
||||||
cache: cache,
|
cache: cache,
|
||||||
patternBuilder: patternBuilder,
|
patternBuilder: patternBuilder,
|
||||||
@@ -65,6 +68,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),
|
||||||
|
sortBuf: make([][]Result, partitions),
|
||||||
mergerCache: make(map[string]MatchResult),
|
mergerCache: make(map[string]MatchResult),
|
||||||
revision: revision}
|
revision: revision}
|
||||||
}
|
}
|
||||||
@@ -174,7 +178,7 @@ func (m *Matcher) scan(request MatchRequest) MatchResult {
|
|||||||
return MatchResult{m, m, false}
|
return MatchResult{m, m, false}
|
||||||
}
|
}
|
||||||
pattern := request.pattern
|
pattern := request.pattern
|
||||||
passMerger := PassMerger(&request.chunks, m.tac, request.revision)
|
passMerger := PassMerger(&request.chunks, m.tac, request.revision, pattern.startIndex)
|
||||||
if pattern.IsEmpty() {
|
if pattern.IsEmpty() {
|
||||||
return MatchResult{passMerger, passMerger, false}
|
return MatchResult{passMerger, passMerger, false}
|
||||||
}
|
}
|
||||||
@@ -212,11 +216,7 @@ func (m *Matcher) scan(request MatchRequest) MatchResult {
|
|||||||
sliceMatches = append(sliceMatches, matches...)
|
sliceMatches = append(sliceMatches, matches...)
|
||||||
}
|
}
|
||||||
if m.sort && request.pattern.sortable {
|
if m.sort && request.pattern.sortable {
|
||||||
if m.tac {
|
m.sortBuf[idx] = radixSortResults(sliceMatches, m.tac, m.sortBuf[idx])
|
||||||
sort.Sort(ByRelevanceTac(sliceMatches))
|
|
||||||
} else {
|
|
||||||
sort.Sort(ByRelevance(sliceMatches))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
resultChan <- partialResult{idx, sliceMatches}
|
resultChan <- partialResult{idx, sliceMatches}
|
||||||
}(idx, m.slab[idx], chunks)
|
}(idx, m.slab[idx], chunks)
|
||||||
|
|||||||
@@ -10,42 +10,46 @@ func EmptyMerger(revision revision) *Merger {
|
|||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
// a single, globally-sorted list
|
// a single, globally-sorted list
|
||||||
type Merger struct {
|
type Merger struct {
|
||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
lists [][]Result
|
lists [][]Result
|
||||||
merged []Result
|
merged []Result
|
||||||
chunks *[]*Chunk
|
chunks *[]*Chunk
|
||||||
cursors []int
|
cursors []int
|
||||||
sorted bool
|
sorted bool
|
||||||
tac bool
|
tac bool
|
||||||
final bool
|
final bool
|
||||||
count int
|
count int
|
||||||
pass bool
|
pass bool
|
||||||
revision revision
|
startIndex int
|
||||||
minIndex int32
|
revision revision
|
||||||
maxIndex int32
|
minIndex int32
|
||||||
|
maxIndex int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// PassMerger returns a new Merger that simply returns the items in the
|
// PassMerger returns a new Merger that simply returns the items in the
|
||||||
// original order
|
// original order. startIndex items are skipped from the beginning.
|
||||||
func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
func PassMerger(chunks *[]*Chunk, tac bool, revision revision, startIndex int32) *Merger {
|
||||||
var minIndex, maxIndex int32
|
var minIndex, maxIndex int32
|
||||||
if len(*chunks) > 0 {
|
if len(*chunks) > 0 {
|
||||||
minIndex = (*chunks)[0].items[0].Index()
|
minIndex = (*chunks)[0].items[0].Index()
|
||||||
maxIndex = (*chunks)[len(*chunks)-1].lastIndex(minIndex)
|
maxIndex = (*chunks)[len(*chunks)-1].lastIndex(minIndex)
|
||||||
}
|
}
|
||||||
|
si := int(startIndex)
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: nil,
|
pattern: nil,
|
||||||
chunks: chunks,
|
chunks: chunks,
|
||||||
tac: tac,
|
tac: tac,
|
||||||
count: 0,
|
count: 0,
|
||||||
pass: true,
|
pass: true,
|
||||||
revision: revision,
|
startIndex: si,
|
||||||
minIndex: minIndex,
|
revision: revision,
|
||||||
maxIndex: maxIndex}
|
minIndex: minIndex + startIndex,
|
||||||
|
maxIndex: maxIndex}
|
||||||
|
|
||||||
for _, chunk := range *mg.chunks {
|
for _, chunk := range *mg.chunks {
|
||||||
mg.count += chunk.count
|
mg.count += chunk.count
|
||||||
}
|
}
|
||||||
|
mg.count = max(0, mg.count-si)
|
||||||
return &mg
|
return &mg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +117,7 @@ func (mg *Merger) Get(idx int) Result {
|
|||||||
if mg.tac {
|
if mg.tac {
|
||||||
idx = mg.count - idx - 1
|
idx = mg.count - idx - 1
|
||||||
}
|
}
|
||||||
|
idx += mg.startIndex
|
||||||
firstChunk := (*mg.chunks)[0]
|
firstChunk := (*mg.chunks)[0]
|
||||||
if firstChunk.count < chunkSize && idx >= firstChunk.count {
|
if firstChunk.count < chunkSize && idx >= firstChunk.count {
|
||||||
idx -= firstChunk.count
|
idx -= firstChunk.count
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
@@ -677,6 +678,8 @@ type Options struct {
|
|||||||
WalkerSkip []string
|
WalkerSkip []string
|
||||||
Version bool
|
Version bool
|
||||||
Help bool
|
Help bool
|
||||||
|
Threads int
|
||||||
|
Bench time.Duration
|
||||||
CPUProfile string
|
CPUProfile string
|
||||||
MEMProfile string
|
MEMProfile string
|
||||||
BlockProfile string
|
BlockProfile string
|
||||||
@@ -1626,7 +1629,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header|footer|search|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header-lines|header|footer|search|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger)`)
|
||||||
splitRegexp = regexp.MustCompile("[,:]+")
|
splitRegexp = regexp.MustCompile("[,:]+")
|
||||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||||
}
|
}
|
||||||
@@ -2037,6 +2040,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actPreview
|
return actPreview
|
||||||
case "change-header":
|
case "change-header":
|
||||||
return actChangeHeader
|
return actChangeHeader
|
||||||
|
case "change-header-lines":
|
||||||
|
return actChangeHeaderLines
|
||||||
case "change-footer":
|
case "change-footer":
|
||||||
return actChangeFooter
|
return actChangeFooter
|
||||||
case "change-list-label":
|
case "change-list-label":
|
||||||
@@ -2097,6 +2102,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actTransformFooter
|
return actTransformFooter
|
||||||
case "transform-header":
|
case "transform-header":
|
||||||
return actTransformHeader
|
return actTransformHeader
|
||||||
|
case "transform-header-lines":
|
||||||
|
return actTransformHeaderLines
|
||||||
case "transform-ghost":
|
case "transform-ghost":
|
||||||
return actTransformGhost
|
return actTransformGhost
|
||||||
case "transform-nth":
|
case "transform-nth":
|
||||||
@@ -2127,6 +2134,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actBgTransformFooter
|
return actBgTransformFooter
|
||||||
case "bg-transform-header":
|
case "bg-transform-header":
|
||||||
return actBgTransformHeader
|
return actBgTransformHeader
|
||||||
|
case "bg-transform-header-lines":
|
||||||
|
return actBgTransformHeaderLines
|
||||||
case "bg-transform-ghost":
|
case "bg-transform-ghost":
|
||||||
return actBgTransformGhost
|
return actBgTransformGhost
|
||||||
case "bg-transform-nth":
|
case "bg-transform-nth":
|
||||||
@@ -3367,6 +3376,23 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
opts.WalkerSkip = filterNonEmpty(strings.Split(str, ","))
|
opts.WalkerSkip = filterNonEmpty(strings.Split(str, ","))
|
||||||
|
case "--threads":
|
||||||
|
if opts.Threads, err = nextInt("number of threads required"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if opts.Threads < 0 {
|
||||||
|
return errors.New("--threads must be a positive integer")
|
||||||
|
}
|
||||||
|
case "--bench":
|
||||||
|
str, err := nextString("duration required (e.g. 3s, 500ms)")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dur, err := time.ParseDuration(str)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("invalid duration for --bench: " + str)
|
||||||
|
}
|
||||||
|
opts.Bench = dur
|
||||||
case "--profile-cpu":
|
case "--profile-cpu":
|
||||||
if opts.CPUProfile, err = nextString("file path required: cpu"); err != nil {
|
if opts.CPUProfile, err = nextString("file path required: cpu"); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
100
src/pattern.go
100
src/pattern.go
@@ -64,6 +64,9 @@ type Pattern struct {
|
|||||||
procFun map[termType]algo.Algo
|
procFun map[termType]algo.Algo
|
||||||
cache *ChunkCache
|
cache *ChunkCache
|
||||||
denylist map[int32]struct{}
|
denylist map[int32]struct{}
|
||||||
|
startIndex int32
|
||||||
|
directAlgo algo.Algo
|
||||||
|
directTerm *term
|
||||||
}
|
}
|
||||||
|
|
||||||
var _splitRegex *regexp.Regexp
|
var _splitRegex *regexp.Regexp
|
||||||
@@ -74,7 +77,7 @@ func init() {
|
|||||||
|
|
||||||
// BuildPattern builds Pattern object from the given arguments
|
// BuildPattern builds Pattern object from the given arguments
|
||||||
func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, revision revision, runes []rune, denylist map[int32]struct{}) *Pattern {
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, revision revision, runes []rune, denylist map[int32]struct{}, startIndex int32) *Pattern {
|
||||||
|
|
||||||
var asString string
|
var asString string
|
||||||
if extended {
|
if extended {
|
||||||
@@ -146,9 +149,11 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
|
|||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
denylist: denylist,
|
denylist: denylist,
|
||||||
|
startIndex: startIndex,
|
||||||
procFun: make(map[termType]algo.Algo)}
|
procFun: make(map[termType]algo.Algo)}
|
||||||
|
|
||||||
ptr.cacheKey = ptr.buildCacheKey()
|
ptr.cacheKey = ptr.buildCacheKey()
|
||||||
|
ptr.directAlgo, ptr.directTerm = ptr.buildDirectAlgo(fuzzyAlgo)
|
||||||
ptr.procFun[termFuzzy] = fuzzyAlgo
|
ptr.procFun[termFuzzy] = fuzzyAlgo
|
||||||
ptr.procFun[termEqual] = algo.EqualMatch
|
ptr.procFun[termEqual] = algo.EqualMatch
|
||||||
ptr.procFun[termExact] = algo.ExactMatchNaive
|
ptr.procFun[termExact] = algo.ExactMatchNaive
|
||||||
@@ -272,6 +277,22 @@ func (p *Pattern) buildCacheKey() string {
|
|||||||
return strings.Join(cacheableTerms, "\t")
|
return strings.Join(cacheableTerms, "\t")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildDirectAlgo returns the algo function and term for the direct fast path
|
||||||
|
// in matchChunk. Returns (nil, nil) if the pattern is not suitable.
|
||||||
|
// Requirements: extended mode, single term set with single non-inverse fuzzy term, no nth.
|
||||||
|
func (p *Pattern) buildDirectAlgo(fuzzyAlgo algo.Algo) (algo.Algo, *term) {
|
||||||
|
if !p.extended || len(p.nth) > 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if len(p.termSets) == 1 && len(p.termSets[0]) == 1 {
|
||||||
|
t := &p.termSets[0][0]
|
||||||
|
if !t.inv && t.typ == termFuzzy {
|
||||||
|
return fuzzyAlgo, t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CacheKey is used to build string to be used as the key of result cache
|
// CacheKey is used to build string to be used as the key of result cache
|
||||||
func (p *Pattern) CacheKey() string {
|
func (p *Pattern) CacheKey() string {
|
||||||
return p.cacheKey
|
return p.cacheKey
|
||||||
@@ -301,18 +322,56 @@ func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result {
|
|||||||
func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Result {
|
func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Result {
|
||||||
matches := []Result{}
|
matches := []Result{}
|
||||||
|
|
||||||
if len(p.denylist) == 0 {
|
// Skip header items in chunks that contain them
|
||||||
// Huge code duplication for minimizing unnecessary map lookups
|
startIdx := 0
|
||||||
|
if p.startIndex > 0 && chunk.count > 0 && chunk.items[0].Index() < p.startIndex {
|
||||||
|
startIdx = int(p.startIndex - chunk.items[0].Index())
|
||||||
|
if startIdx >= chunk.count {
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path: single fuzzy term, no nth, no denylist.
|
||||||
|
// Calls the algo function directly, bypassing MatchItem/extendedMatch/iter
|
||||||
|
// and avoiding per-match []Offset heap allocation.
|
||||||
|
if p.directAlgo != nil && len(p.denylist) == 0 {
|
||||||
|
t := p.directTerm
|
||||||
if space == nil {
|
if space == nil {
|
||||||
for idx := 0; idx < chunk.count; idx++ {
|
for idx := startIdx; idx < chunk.count; idx++ {
|
||||||
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
|
res, _ := p.directAlgo(t.caseSensitive, t.normalize, p.forward,
|
||||||
matches = append(matches, *match)
|
&chunk.items[idx].text, t.text, p.withPos, slab)
|
||||||
|
if res.Start >= 0 {
|
||||||
|
matches = append(matches, buildResultFromBounds(
|
||||||
|
&chunk.items[idx], res.Score,
|
||||||
|
int(res.Start), int(res.End), int(res.End), true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, result := range space {
|
for _, result := range space {
|
||||||
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match != nil {
|
res, _ := p.directAlgo(t.caseSensitive, t.normalize, p.forward,
|
||||||
matches = append(matches, *match)
|
&result.item.text, t.text, p.withPos, slab)
|
||||||
|
if res.Start >= 0 {
|
||||||
|
matches = append(matches, buildResultFromBounds(
|
||||||
|
result.item, res.Score,
|
||||||
|
int(res.Start), int(res.End), int(res.End), true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.denylist) == 0 {
|
||||||
|
// Huge code duplication for minimizing unnecessary map lookups
|
||||||
|
if space == nil {
|
||||||
|
for idx := startIdx; idx < chunk.count; idx++ {
|
||||||
|
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match.item != nil {
|
||||||
|
matches = append(matches, match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, result := range space {
|
||||||
|
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match.item != nil {
|
||||||
|
matches = append(matches, match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,12 +379,12 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
if space == nil {
|
if space == nil {
|
||||||
for idx := 0; idx < chunk.count; idx++ {
|
for idx := startIdx; idx < chunk.count; idx++ {
|
||||||
if _, prs := p.denylist[chunk.items[idx].Index()]; prs {
|
if _, prs := p.denylist[chunk.items[idx].Index()]; prs {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
|
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match.item != nil {
|
||||||
matches = append(matches, *match)
|
matches = append(matches, match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -333,30 +392,29 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
|
|||||||
if _, prs := p.denylist[result.item.Index()]; prs {
|
if _, prs := p.denylist[result.item.Index()]; prs {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match != nil {
|
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match.item != nil {
|
||||||
matches = append(matches, *match)
|
matches = append(matches, match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchItem returns true if the Item is a match
|
// MatchItem returns the match result if the Item is a match.
|
||||||
func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result, []Offset, *[]int) {
|
// A zero-value Result (with item == nil) indicates no match.
|
||||||
|
func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (Result, []Offset, *[]int) {
|
||||||
if p.extended {
|
if p.extended {
|
||||||
if offsets, bonus, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {
|
if offsets, bonus, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {
|
||||||
result := buildResult(item, offsets, bonus)
|
return buildResult(item, offsets, bonus), offsets, pos
|
||||||
return &result, offsets, pos
|
|
||||||
}
|
}
|
||||||
return nil, nil, nil
|
return Result{}, nil, nil
|
||||||
}
|
}
|
||||||
offset, bonus, pos := p.basicMatch(item, withPos, slab)
|
offset, bonus, pos := p.basicMatch(item, withPos, slab)
|
||||||
if sidx := offset[0]; sidx >= 0 {
|
if sidx := offset[0]; sidx >= 0 {
|
||||||
offsets := []Offset{offset}
|
offsets := []Offset{offset}
|
||||||
result := buildResult(item, offsets, bonus)
|
return buildResult(item, offsets, bonus), offsets, pos
|
||||||
return &result, offsets, pos
|
|
||||||
}
|
}
|
||||||
return nil, nil, nil
|
return Result{}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func buildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
|
return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
|
||||||
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
|
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
|
||||||
withPos, cacheable, nth, delimiter, revision{}, runes, nil)
|
withPos, cacheable, nth, delimiter, revision{}, runes, nil, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExact(t *testing.T) {
|
func TestExact(t *testing.T) {
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
sort.Sort(ByOrder(offsets))
|
sort.Sort(ByOrder(offsets))
|
||||||
}
|
}
|
||||||
|
|
||||||
result := Result{item: item}
|
|
||||||
numChars := item.text.Length()
|
|
||||||
minBegin := math.MaxUint16
|
minBegin := math.MaxUint16
|
||||||
minEnd := math.MaxUint16
|
minEnd := math.MaxUint16
|
||||||
maxEnd := 0
|
maxEnd := 0
|
||||||
@@ -49,6 +47,14 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return buildResultFromBounds(item, score, minBegin, minEnd, maxEnd, validOffsetFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildResultFromBounds builds a Result from pre-computed offset bounds.
|
||||||
|
func buildResultFromBounds(item *Item, score int, minBegin, minEnd, maxEnd int, validOffsetFound bool) Result {
|
||||||
|
result := Result{item: item}
|
||||||
|
numChars := item.text.Length()
|
||||||
|
|
||||||
for idx, criterion := range sortCriteria {
|
for idx, criterion := range sortCriteria {
|
||||||
val := uint16(math.MaxUint16)
|
val := uint16(math.MaxUint16)
|
||||||
switch criterion {
|
switch criterion {
|
||||||
@@ -75,7 +81,6 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
val = item.TrimLength()
|
val = item.TrimLength()
|
||||||
case byPathname:
|
case byPathname:
|
||||||
if validOffsetFound {
|
if validOffsetFound {
|
||||||
// lastDelim := strings.LastIndexByte(item.text.ToString(), '/')
|
|
||||||
lastDelim := -1
|
lastDelim := -1
|
||||||
s := item.text.ToString()
|
s := item.text.ToString()
|
||||||
for i := len(s) - 1; i >= 0; i-- {
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
@@ -334,3 +339,79 @@ func (a ByRelevanceTac) Swap(i, j int) {
|
|||||||
func (a ByRelevanceTac) Less(i, j int) bool {
|
func (a ByRelevanceTac) Less(i, j int) bool {
|
||||||
return compareRanks(a[i], a[j], true)
|
return compareRanks(a[i], a[j], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// radixSortResults sorts Results by their points key using LSD radix sort.
|
||||||
|
// O(n) time complexity vs O(n log n) for comparison sort.
|
||||||
|
// The sort is stable, so equal-key items maintain original (item-index) order.
|
||||||
|
// For tac mode, runs of equal keys are reversed after sorting.
|
||||||
|
func radixSortResults(a []Result, tac bool, scratch []Result) []Result {
|
||||||
|
n := len(a)
|
||||||
|
if n < 128 {
|
||||||
|
if tac {
|
||||||
|
sort.Sort(ByRelevanceTac(a))
|
||||||
|
} else {
|
||||||
|
sort.Sort(ByRelevance(a))
|
||||||
|
}
|
||||||
|
return scratch[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(scratch) < n {
|
||||||
|
scratch = make([]Result, n)
|
||||||
|
}
|
||||||
|
buf := scratch[:n]
|
||||||
|
src, dst := a, buf
|
||||||
|
scattered := 0
|
||||||
|
|
||||||
|
for pass := range 8 {
|
||||||
|
shift := uint(pass) * 8
|
||||||
|
|
||||||
|
var count [256]int
|
||||||
|
for i := range src {
|
||||||
|
count[byte(sortKey(&src[i])>>shift)]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if all items have the same byte value at this position
|
||||||
|
if count[byte(sortKey(&src[0])>>shift)] == n {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset [256]int
|
||||||
|
for i := 1; i < 256; i++ {
|
||||||
|
offset[i] = offset[i-1] + count[i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range src {
|
||||||
|
b := byte(sortKey(&src[i]) >> shift)
|
||||||
|
dst[offset[b]] = src[i]
|
||||||
|
offset[b]++
|
||||||
|
}
|
||||||
|
|
||||||
|
src, dst = dst, src
|
||||||
|
scattered++
|
||||||
|
}
|
||||||
|
|
||||||
|
// If odd number of scatters, data is in buf, copy back to a
|
||||||
|
if scattered%2 == 1 {
|
||||||
|
copy(a, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle tac: reverse runs of equal keys so equal-key items
|
||||||
|
// are in reverse item-index order
|
||||||
|
if tac {
|
||||||
|
i := 0
|
||||||
|
for i < n {
|
||||||
|
ki := sortKey(&a[i])
|
||||||
|
j := i + 1
|
||||||
|
for j < n && sortKey(&a[j]) == ki {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
if j-i > 1 {
|
||||||
|
for l, r := i, j-1; l < r; l, r = l+1, r-1 {
|
||||||
|
a[l], a[r] = a[r], a[l]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scratch
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !386 && !amd64
|
//go:build !386 && !amd64 && !arm64
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
@@ -14,3 +14,7 @@ func compareRanks(irank Result, jrank Result, tac bool) bool {
|
|||||||
}
|
}
|
||||||
return (irank.item.Index() <= jrank.item.Index()) != tac
|
return (irank.item.Index() <= jrank.item.Index()) != tac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortKey(r *Result) uint64 {
|
||||||
|
return uint64(r.points[0]) | uint64(r.points[1])<<16 | uint64(r.points[2])<<32 | uint64(r.points[3])<<48
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"math/rand"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -182,3 +183,60 @@ func TestColorOffset(t *testing.T) {
|
|||||||
assert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))
|
assert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRadixSortResults(t *testing.T) {
|
||||||
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
|
rng := rand.New(rand.NewSource(42))
|
||||||
|
|
||||||
|
for _, n := range []int{128, 256, 500, 1000} {
|
||||||
|
for _, tac := range []bool{false, true} {
|
||||||
|
// Build items with random points and indices
|
||||||
|
items := make([]*Item, n)
|
||||||
|
for i := range items {
|
||||||
|
items[i] = &Item{text: util.Chars{Index: int32(i)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]Result, n)
|
||||||
|
for i := range results {
|
||||||
|
results[i] = Result{
|
||||||
|
item: items[i],
|
||||||
|
points: [4]uint16{
|
||||||
|
uint16(rng.Intn(256)),
|
||||||
|
uint16(rng.Intn(256)),
|
||||||
|
uint16(rng.Intn(256)),
|
||||||
|
uint16(rng.Intn(256)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make some duplicates to test stability
|
||||||
|
for i := 0; i < n/4; i++ {
|
||||||
|
j := rng.Intn(n)
|
||||||
|
k := rng.Intn(n)
|
||||||
|
results[j].points = results[k].points
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy for reference sort
|
||||||
|
expected := make([]Result, n)
|
||||||
|
copy(expected, results)
|
||||||
|
if tac {
|
||||||
|
sort.Sort(ByRelevanceTac(expected))
|
||||||
|
} else {
|
||||||
|
sort.Sort(ByRelevance(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radix sort
|
||||||
|
var scratch []Result
|
||||||
|
scratch = radixSortResults(results, tac, scratch)
|
||||||
|
|
||||||
|
for i := range results {
|
||||||
|
if results[i] != expected[i] {
|
||||||
|
t.Errorf("n=%d tac=%v: mismatch at index %d: got item %d, want item %d",
|
||||||
|
n, tac, i, results[i].item.Index(), expected[i].item.Index())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build 386 || amd64
|
//go:build 386 || amd64 || arm64
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
@@ -14,3 +14,7 @@ func compareRanks(irank Result, jrank Result, tac bool) bool {
|
|||||||
}
|
}
|
||||||
return (irank.item.Index() <= jrank.item.Index()) != tac
|
return (irank.item.Index() <= jrank.item.Index()) != tac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortKey(r *Result) uint64 {
|
||||||
|
return *(*uint64)(unsafe.Pointer(&r.points[0]))
|
||||||
|
}
|
||||||
|
|||||||
@@ -314,6 +314,7 @@ type Terminal struct {
|
|||||||
sort bool
|
sort bool
|
||||||
toggleSort bool
|
toggleSort bool
|
||||||
track trackOption
|
track trackOption
|
||||||
|
targetIndex int32
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
expect map[tui.Event]string
|
expect map[tui.Event]string
|
||||||
keymap map[tui.Event][]*action
|
keymap map[tui.Event][]*action
|
||||||
@@ -327,7 +328,7 @@ type Terminal struct {
|
|||||||
headerVisible bool
|
headerVisible bool
|
||||||
headerFirst bool
|
headerFirst bool
|
||||||
headerLines int
|
headerLines int
|
||||||
header []string
|
header []Item
|
||||||
header0 []string
|
header0 []string
|
||||||
footer []string
|
footer []string
|
||||||
ellipsis string
|
ellipsis string
|
||||||
@@ -542,6 +543,7 @@ const (
|
|||||||
actChangeBorderLabel
|
actChangeBorderLabel
|
||||||
actChangeGhost
|
actChangeGhost
|
||||||
actChangeHeader
|
actChangeHeader
|
||||||
|
actChangeHeaderLines
|
||||||
actChangeFooter
|
actChangeFooter
|
||||||
actChangeHeaderLabel
|
actChangeHeaderLabel
|
||||||
actChangeFooterLabel
|
actChangeFooterLabel
|
||||||
@@ -627,6 +629,7 @@ const (
|
|||||||
actTransformBorderLabel
|
actTransformBorderLabel
|
||||||
actTransformGhost
|
actTransformGhost
|
||||||
actTransformHeader
|
actTransformHeader
|
||||||
|
actTransformHeaderLines
|
||||||
actTransformFooter
|
actTransformFooter
|
||||||
actTransformHeaderLabel
|
actTransformHeaderLabel
|
||||||
actTransformFooterLabel
|
actTransformFooterLabel
|
||||||
@@ -645,6 +648,7 @@ const (
|
|||||||
actBgTransformBorderLabel
|
actBgTransformBorderLabel
|
||||||
actBgTransformGhost
|
actBgTransformGhost
|
||||||
actBgTransformHeader
|
actBgTransformHeader
|
||||||
|
actBgTransformHeaderLines
|
||||||
actBgTransformFooter
|
actBgTransformFooter
|
||||||
actBgTransformHeaderLabel
|
actBgTransformHeaderLabel
|
||||||
actBgTransformFooterLabel
|
actBgTransformFooterLabel
|
||||||
@@ -710,6 +714,7 @@ func processExecution(action actionType) bool {
|
|||||||
actTransformBorderLabel,
|
actTransformBorderLabel,
|
||||||
actTransformGhost,
|
actTransformGhost,
|
||||||
actTransformHeader,
|
actTransformHeader,
|
||||||
|
actTransformHeaderLines,
|
||||||
actTransformFooter,
|
actTransformFooter,
|
||||||
actTransformHeaderLabel,
|
actTransformHeaderLabel,
|
||||||
actTransformFooterLabel,
|
actTransformFooterLabel,
|
||||||
@@ -725,6 +730,7 @@ func processExecution(action actionType) bool {
|
|||||||
actBgTransformBorderLabel,
|
actBgTransformBorderLabel,
|
||||||
actBgTransformGhost,
|
actBgTransformGhost,
|
||||||
actBgTransformHeader,
|
actBgTransformHeader,
|
||||||
|
actBgTransformHeaderLines,
|
||||||
actBgTransformFooter,
|
actBgTransformFooter,
|
||||||
actBgTransformHeaderLabel,
|
actBgTransformHeaderLabel,
|
||||||
actBgTransformFooterLabel,
|
actBgTransformFooterLabel,
|
||||||
@@ -761,14 +767,15 @@ type placeholderFlags struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type searchRequest struct {
|
type searchRequest struct {
|
||||||
sort bool
|
sort bool
|
||||||
sync bool
|
sync bool
|
||||||
nth *[]Range
|
nth *[]Range
|
||||||
command *commandSpec
|
headerLines *int
|
||||||
environ []string
|
command *commandSpec
|
||||||
changed bool
|
environ []string
|
||||||
denylist []int32
|
changed bool
|
||||||
revision revision
|
denylist []int32
|
||||||
|
revision revision
|
||||||
}
|
}
|
||||||
|
|
||||||
type previewRequest struct {
|
type previewRequest struct {
|
||||||
@@ -1022,6 +1029,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
sort: opts.Sort > 0,
|
sort: opts.Sort > 0,
|
||||||
toggleSort: opts.ToggleSort,
|
toggleSort: opts.ToggleSort,
|
||||||
track: opts.Track,
|
track: opts.Track,
|
||||||
|
targetIndex: minItem.Index(),
|
||||||
delimiter: opts.Delimiter,
|
delimiter: opts.Delimiter,
|
||||||
expect: opts.Expect,
|
expect: opts.Expect,
|
||||||
keymap: opts.Keymap,
|
keymap: opts.Keymap,
|
||||||
@@ -1063,7 +1071,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
headerFirst: opts.HeaderFirst,
|
headerFirst: opts.HeaderFirst,
|
||||||
headerLines: opts.HeaderLines,
|
headerLines: opts.HeaderLines,
|
||||||
gap: opts.Gap,
|
gap: opts.Gap,
|
||||||
header: []string{},
|
header: []Item{},
|
||||||
footer: opts.Footer,
|
footer: opts.Footer,
|
||||||
header0: opts.Header,
|
header0: opts.Header,
|
||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
@@ -1755,8 +1763,14 @@ func (t *Terminal) changeFooter(footer string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateHeader updates the header
|
// UpdateHeader updates the header
|
||||||
func (t *Terminal) UpdateHeader(header []string) {
|
func (t *Terminal) UpdateHeader(header []Item) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
|
// Pad to t.headerLines so that click coordinate mapping works correctly
|
||||||
|
if len(header) < t.headerLines {
|
||||||
|
padded := make([]Item, t.headerLines)
|
||||||
|
copy(padded, header)
|
||||||
|
header = padded
|
||||||
|
}
|
||||||
t.header = header
|
t.header = header
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
t.reqBox.Set(reqHeader, nil)
|
t.reqBox.Set(reqHeader, nil)
|
||||||
@@ -1788,6 +1802,10 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
|||||||
prevIndex = merger.First().item.Index()
|
prevIndex = merger.First().item.Index()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if t.targetIndex != minItem.Index() {
|
||||||
|
prevIndex = t.targetIndex
|
||||||
|
t.targetIndex = minItem.Index()
|
||||||
|
}
|
||||||
t.progress = 100
|
t.progress = 100
|
||||||
t.merger = merger
|
t.merger = merger
|
||||||
t.resultMerger = merger
|
t.resultMerger = merger
|
||||||
@@ -3079,11 +3097,11 @@ func (t *Terminal) printHeader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.withWindow(t.headerWindow, func() {
|
t.withWindow(t.headerWindow, func() {
|
||||||
var lines []string
|
var headerItems []Item
|
||||||
if !t.hasHeaderLinesWindow() {
|
if !t.hasHeaderLinesWindow() {
|
||||||
lines = t.header
|
headerItems = t.header
|
||||||
}
|
}
|
||||||
t.printHeaderImpl(t.headerWindow, t.headerBorderShape, t.header0, lines)
|
t.printHeaderImpl(t.headerWindow, t.headerBorderShape, t.header0, headerItems)
|
||||||
})
|
})
|
||||||
if w, shape := t.determineHeaderLinesShape(); w {
|
if w, shape := t.determineHeaderLinesShape(); w {
|
||||||
t.withWindow(t.headerLinesWindow, func() {
|
t.withWindow(t.headerLinesWindow, func() {
|
||||||
@@ -3145,7 +3163,7 @@ func (t *Terminal) headerIndentImpl(base int, borderShape tui.BorderShape) int {
|
|||||||
return indentSize
|
return indentSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShape, lines1 []string, lines2 []string) {
|
func (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShape, lines1 []string, lines2 []Item) {
|
||||||
max := t.window.Height()
|
max := t.window.Height()
|
||||||
if !t.inputless && t.inputWindow == nil && window == nil && t.headerFirst {
|
if !t.inputless && t.inputWindow == nil && window == nil && t.headerFirst {
|
||||||
max--
|
max--
|
||||||
@@ -3172,7 +3190,8 @@ func (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShap
|
|||||||
}
|
}
|
||||||
indent := strings.Repeat(" ", indentSize)
|
indent := strings.Repeat(" ", indentSize)
|
||||||
t.wrap = false
|
t.wrap = false
|
||||||
for idx, lineStr := range append(append([]string{}, lines1...), lines2...) {
|
totalLines := len(lines1) + len(lines2)
|
||||||
|
for idx := 0; idx < totalLines; idx++ {
|
||||||
line := idx
|
line := idx
|
||||||
if needReverse && idx < len(lines1) {
|
if needReverse && idx < len(lines1) {
|
||||||
line = len(lines1) - idx - 1
|
line = len(lines1) - idx - 1
|
||||||
@@ -3186,11 +3205,18 @@ func (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShap
|
|||||||
if line >= max {
|
if line >= max {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
trimmed, colors, newState := extractColor(lineStr, state, nil)
|
|
||||||
state = newState
|
var item *Item
|
||||||
item := &Item{
|
if idx < len(lines1) {
|
||||||
text: util.ToChars([]byte(trimmed)),
|
trimmed, colors, newState := extractColor(lines1[idx], state, nil)
|
||||||
colors: colors}
|
state = newState
|
||||||
|
item = &Item{
|
||||||
|
text: util.ToChars([]byte(trimmed)),
|
||||||
|
colors: colors}
|
||||||
|
} else {
|
||||||
|
headerItem := lines2[idx-len(lines1)]
|
||||||
|
item = &headerItem
|
||||||
|
}
|
||||||
|
|
||||||
t.printHighlighted(Result{item: item},
|
t.printHighlighted(Result{item: item},
|
||||||
tui.ColHeader, tui.ColHeader, false, false, false, line, line, true,
|
tui.ColHeader, tui.ColHeader, false, false, false, line, line, true,
|
||||||
@@ -5288,9 +5314,13 @@ func (t *Terminal) addClickHeaderWord(env []string) []string {
|
|||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: t.header is padded with empty strings so that its size is equal to t.headerLines
|
|
||||||
nthBase := 0
|
nthBase := 0
|
||||||
headers := [2][]string{t.header, t.header0}
|
// Convert header items to strings for click handling
|
||||||
|
headerStrs := make([]string, len(t.header))
|
||||||
|
for i, item := range t.header {
|
||||||
|
headerStrs[i] = item.text.ToString()
|
||||||
|
}
|
||||||
|
headers := [2][]string{headerStrs, t.header0}
|
||||||
if t.layout == layoutReverse {
|
if t.layout == layoutReverse {
|
||||||
headers[0], headers[1] = headers[1], headers[0]
|
headers[0], headers[1] = headers[1], headers[0]
|
||||||
}
|
}
|
||||||
@@ -5892,6 +5922,7 @@ func (t *Terminal) Loop() error {
|
|||||||
events := []util.EventType{}
|
events := []util.EventType{}
|
||||||
changed := false
|
changed := false
|
||||||
var newNth *[]Range
|
var newNth *[]Range
|
||||||
|
var newHeaderLines *int
|
||||||
req := func(evts ...util.EventType) {
|
req := func(evts ...util.EventType) {
|
||||||
for _, event := range evts {
|
for _, event := range evts {
|
||||||
events = append(events, event)
|
events = append(events, event)
|
||||||
@@ -5908,6 +5939,7 @@ func (t *Terminal) Loop() error {
|
|||||||
events = []util.EventType{}
|
events = []util.EventType{}
|
||||||
changed = false
|
changed = false
|
||||||
newNth = nil
|
newNth = nil
|
||||||
|
newHeaderLines = nil
|
||||||
beof := false
|
beof := false
|
||||||
queryChanged := false
|
queryChanged := false
|
||||||
denylist := []int32{}
|
denylist := []int32{}
|
||||||
@@ -6247,6 +6279,23 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
case actPrintQuery:
|
case actPrintQuery:
|
||||||
req(reqPrintQuery)
|
req(reqPrintQuery)
|
||||||
|
case actChangeHeaderLines, actTransformHeaderLines, actBgTransformHeaderLines:
|
||||||
|
capture(true, func(expr string) {
|
||||||
|
if n, err := strconv.Atoi(expr); err == nil && n >= 0 && n != t.headerLines {
|
||||||
|
t.headerLines = n
|
||||||
|
newHeaderLines = &n
|
||||||
|
changed = true
|
||||||
|
// Deselect items that are now part of the header
|
||||||
|
for idx := range t.selected {
|
||||||
|
if idx < int32(n) {
|
||||||
|
delete(t.selected, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Tell UpdateList to reposition cursor to the current item
|
||||||
|
t.targetIndex = t.currentIndex()
|
||||||
|
req(reqList, reqPrompt, reqInfo, reqHeader)
|
||||||
|
}
|
||||||
|
})
|
||||||
case actChangeMulti:
|
case actChangeMulti:
|
||||||
multi := t.multi
|
multi := t.multi
|
||||||
if a.a == "" {
|
if a.a == "" {
|
||||||
@@ -7428,7 +7477,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.resultMerger.Revision()}
|
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, headerLines: newHeaderLines, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.resultMerger.Revision()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch queued background requests
|
// Dispatch queued background requests
|
||||||
|
|||||||
@@ -1755,7 +1755,7 @@ class TestCore < TestInteractive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
tmux.send_keys %(seq 100 | #{FZF} --multi --reverse --preview-window 0 --preview 'env | grep ^FZF_ | sort > #{tempname}' --no-input --bind enter:show-input+refresh-preview,space:disable-search+refresh-preview), :Enter
|
tmux.send_keys %({ echo foo; seq 100; } | #{FZF} --header-lines 1 --multi --reverse --preview-window 0 --preview 'env | grep ^FZF_ | sort > #{tempname}' --no-input --bind enter:show-input+refresh-preview,space:disable-search+refresh-preview), :Enter
|
||||||
expected = {
|
expected = {
|
||||||
FZF_DIRECTION: 'down',
|
FZF_DIRECTION: 'down',
|
||||||
FZF_TOTAL_COUNT: '100',
|
FZF_TOTAL_COUNT: '100',
|
||||||
@@ -2176,6 +2176,80 @@ class TestCore < TestInteractive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_change_header_lines
|
||||||
|
tmux.send_keys %(seq 10 | #{FZF} --header-lines 3 --bind 'space:change-header-lines(5),enter:transform-header-lines(echo 1)'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 7, lines.item_count
|
||||||
|
assert lines.any_include?('> 4')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 5, lines.item_count
|
||||||
|
assert lines.any_include?('> 6')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 9, lines.item_count
|
||||||
|
assert lines.any_include?('> 6')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_header_lines_to_zero
|
||||||
|
tmux.send_keys %(seq 5 | #{FZF} --header-lines 3 --bind 'space:bg-transform-header-lines(echo 0)'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 2, lines.item_count
|
||||||
|
assert lines.any_include?('> 4')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 5, lines.item_count
|
||||||
|
# All items are now in the list, cursor stays on item 4
|
||||||
|
assert lines.any_include?('> 4')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_header_lines_deselect
|
||||||
|
# Selected items that become part of the header should be deselected
|
||||||
|
tmux.send_keys %(seq 10 | #{FZF} --multi --header-lines 0 --bind 'space:change-header-lines(3),enter:change-header-lines(1)'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 10, lines.item_count
|
||||||
|
assert lines.any_include?('> 1')
|
||||||
|
end
|
||||||
|
# Select items 1, 2, 3 (these will become header lines)
|
||||||
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.select_count }
|
||||||
|
# Also select item 4 (this should remain selected)
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_equal 4, lines.select_count }
|
||||||
|
# Change header-lines to 3: items 1, 2, 3 become headers and should be deselected
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 7, lines.item_count
|
||||||
|
assert_equal 1, lines.select_count
|
||||||
|
assert lines.any_include?('> 5')
|
||||||
|
end
|
||||||
|
# Change header-lines to 1
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 9, lines.item_count
|
||||||
|
assert_equal 1, lines.select_count
|
||||||
|
assert lines.any_include?('> 5')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_header_lines_reverse
|
||||||
|
tmux.send_keys %(seq 10 | #{FZF} --header-lines 2 --reverse --bind 'space:change-header-lines(4)'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 8, lines.item_count
|
||||||
|
assert lines.any_include?('> 3')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 6, lines.item_count
|
||||||
|
assert lines.any_include?('> 5')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_zero_width_characters
|
def test_zero_width_characters
|
||||||
tmux.send_keys %(for i in {1..1000}; do string+="a̱$i"; printf '\\e[43m%s\\e[0m\\n' "$string"; done | #{FZF} --ansi --query a500 --ellipsis XX), :Enter
|
tmux.send_keys %(for i in {1..1000}; do string+="a̱$i"; printf '\\e[43m%s\\e[0m\\n' "$string"; done | #{FZF} --ansi --query a500 --ellipsis XX), :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
|
|||||||
@@ -326,4 +326,27 @@ class TestFilter < TestBase
|
|||||||
writelines(['emp001 Alice Engineering', 'emp002 Bob Marketing'])
|
writelines(['emp001 Alice Engineering', 'emp002 Bob Marketing'])
|
||||||
assert_equal 'emp001', `#{FZF} -d' ' --with-nth 2 --accept-nth 1 -f Alice < #{tempname}`.chomp
|
assert_equal 'emp001', `#{FZF} -d' ' --with-nth 2 --accept-nth 1 -f Alice < #{tempname}`.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_header_lines_filter
|
||||||
|
assert_equal %w[4 5 6 7 8 9 10],
|
||||||
|
`seq 10 | #{FZF} --header-lines 3 -f ""`.lines(chomp: true)
|
||||||
|
assert_equal %w[5],
|
||||||
|
`seq 10 | #{FZF} --header-lines 3 -f 5`.lines(chomp: true)
|
||||||
|
# Header items should not be matched
|
||||||
|
assert_empty `seq 10 | #{FZF} --header-lines 3 -f "^1$"`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_lines_filter_with_nth
|
||||||
|
writelines(%w[a:1 b:2 c:3 d:4 e:5])
|
||||||
|
assert_equal %w[c:3 d:4 e:5],
|
||||||
|
`#{FZF} --header-lines 2 -d: --with-nth 2 -f "" < #{tempname}`.lines(chomp: true)
|
||||||
|
assert_equal %w[d:4],
|
||||||
|
`#{FZF} --header-lines 2 -d: --with-nth 2 -f 4 < #{tempname}`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_lines_all_headers
|
||||||
|
# When all lines are header lines, no results
|
||||||
|
assert_empty `seq 3 | #{FZF} --header-lines 10 -f ""`.chomp
|
||||||
|
assert_equal 1, $CHILD_STATUS.exitstatus
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user