m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-18 00:03:39 -05:00

Introduce 'raw' mode

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

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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

View File

@@ -141,6 +141,15 @@ func (mg *Merger) Get(idx int) Result {
panic(fmt.Sprintf("Index out of bounds (unsorted, %d/%d)", idx, mg.count))
}
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
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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 {
@@ -2794,7 +2834,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 {
@@ -3115,12 +3155,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) {
@@ -3155,7 +3199,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)
}
@@ -3177,6 +3225,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
@@ -3303,7 +3359,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
@@ -3337,6 +3399,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++ {
@@ -3389,8 +3455,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 {
@@ -3422,7 +3488,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)
@@ -4670,6 +4736,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)
@@ -4714,6 +4807,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
}
@@ -5910,8 +6007,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
}
}
@@ -5919,8 +6017,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)
}
@@ -5948,17 +6048,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
}
@@ -5986,19 +6086,77 @@ func (t *Terminal) Loop() error {
t.vmove(1, true)
req(reqList)
}
case actDown:
t.vmove(-1, true)
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:
t.vmove(1, true)
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 {
@@ -6834,7 +6992,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
@@ -6954,7 +7112,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
}
@@ -6971,7 +7130,7 @@ func (t *Terminal) vmove(o int, allowCycle bool) {
}
}
}
t.vset(dest)
return t.vset(dest)
}
func (t *Terminal) vset(o int) bool {
@@ -7038,9 +7197,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
@@ -7057,7 +7216,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,

View File

@@ -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},
}
}