diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index a008c60a..056df678 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -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\-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\-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\-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) @@ -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\-ghost(...)\fR (transform ghost text 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\-input\-label(...)\fR (transform input label using an external command) \fBtransform\-list\-label(...)\fR (transform list label using an external command) diff --git a/src/actiontype_string.go b/src/actiontype_string.go index f85f73bc..ec7fd954 100644 --- a/src/actiontype_string.go +++ b/src/actiontype_string.go @@ -30,161 +30,164 @@ func _() { _ = x[actChangeBorderLabel-19] _ = x[actChangeGhost-20] _ = x[actChangeHeader-21] - _ = x[actChangeFooter-22] - _ = x[actChangeHeaderLabel-23] - _ = x[actChangeFooterLabel-24] - _ = x[actChangeInputLabel-25] - _ = x[actChangeListLabel-26] - _ = x[actChangeMulti-27] - _ = x[actChangeNth-28] - _ = x[actChangePointer-29] - _ = x[actChangePreview-30] - _ = x[actChangePreviewLabel-31] - _ = x[actChangePreviewWindow-32] - _ = x[actChangePrompt-33] - _ = x[actChangeQuery-34] - _ = x[actClearScreen-35] - _ = x[actClearQuery-36] - _ = x[actClearSelection-37] - _ = x[actClose-38] - _ = x[actDeleteChar-39] - _ = x[actDeleteCharEof-40] - _ = x[actEndOfLine-41] - _ = x[actFatal-42] - _ = x[actForwardChar-43] - _ = x[actForwardWord-44] - _ = x[actForwardSubWord-45] - _ = x[actKillLine-46] - _ = x[actKillWord-47] - _ = x[actKillSubWord-48] - _ = x[actUnixLineDiscard-49] - _ = x[actUnixWordRubout-50] - _ = x[actYank-51] - _ = x[actBackwardKillWord-52] - _ = x[actBackwardKillSubWord-53] - _ = x[actSelectAll-54] - _ = x[actDeselectAll-55] - _ = x[actToggle-56] - _ = x[actToggleSearch-57] - _ = x[actToggleAll-58] - _ = x[actToggleDown-59] - _ = x[actToggleUp-60] - _ = x[actToggleIn-61] - _ = x[actToggleOut-62] - _ = x[actToggleTrack-63] - _ = x[actToggleTrackCurrent-64] - _ = x[actToggleHeader-65] - _ = x[actToggleWrap-66] - _ = x[actToggleWrapWord-67] - _ = x[actToggleMultiLine-68] - _ = x[actToggleHscroll-69] - _ = x[actToggleRaw-70] - _ = x[actEnableRaw-71] - _ = x[actDisableRaw-72] - _ = x[actTrackCurrent-73] - _ = x[actToggleInput-74] - _ = x[actHideInput-75] - _ = x[actShowInput-76] - _ = x[actUntrackCurrent-77] - _ = x[actDown-78] - _ = x[actDownMatch-79] - _ = x[actUp-80] - _ = x[actUpMatch-81] - _ = x[actPageUp-82] - _ = x[actPageDown-83] - _ = x[actPosition-84] - _ = x[actHalfPageUp-85] - _ = x[actHalfPageDown-86] - _ = x[actOffsetUp-87] - _ = x[actOffsetDown-88] - _ = x[actOffsetMiddle-89] - _ = x[actJump-90] - _ = x[actJumpAccept-91] - _ = x[actPrintQuery-92] - _ = x[actRefreshPreview-93] - _ = x[actReplaceQuery-94] - _ = x[actToggleSort-95] - _ = x[actShowPreview-96] - _ = x[actHidePreview-97] - _ = x[actTogglePreview-98] - _ = x[actTogglePreviewWrap-99] - _ = x[actTogglePreviewWrapWord-100] - _ = x[actTransform-101] - _ = x[actTransformBorderLabel-102] - _ = x[actTransformGhost-103] - _ = x[actTransformHeader-104] - _ = x[actTransformFooter-105] - _ = x[actTransformHeaderLabel-106] - _ = x[actTransformFooterLabel-107] - _ = x[actTransformInputLabel-108] - _ = x[actTransformListLabel-109] - _ = x[actTransformNth-110] - _ = x[actTransformPointer-111] - _ = x[actTransformPreviewLabel-112] - _ = x[actTransformPrompt-113] - _ = x[actTransformQuery-114] - _ = x[actTransformSearch-115] - _ = x[actTrigger-116] - _ = x[actBgTransform-117] - _ = x[actBgTransformBorderLabel-118] - _ = x[actBgTransformGhost-119] - _ = x[actBgTransformHeader-120] - _ = x[actBgTransformFooter-121] - _ = x[actBgTransformHeaderLabel-122] - _ = x[actBgTransformFooterLabel-123] - _ = x[actBgTransformInputLabel-124] - _ = x[actBgTransformListLabel-125] - _ = x[actBgTransformNth-126] - _ = x[actBgTransformPointer-127] - _ = x[actBgTransformPreviewLabel-128] - _ = x[actBgTransformPrompt-129] - _ = x[actBgTransformQuery-130] - _ = x[actBgTransformSearch-131] - _ = x[actBgCancel-132] - _ = x[actSearch-133] - _ = x[actPreview-134] - _ = x[actPreviewTop-135] - _ = x[actPreviewBottom-136] - _ = x[actPreviewUp-137] - _ = x[actPreviewDown-138] - _ = x[actPreviewPageUp-139] - _ = x[actPreviewPageDown-140] - _ = x[actPreviewHalfPageUp-141] - _ = x[actPreviewHalfPageDown-142] - _ = x[actPrevHistory-143] - _ = x[actPrevSelected-144] - _ = x[actPrint-145] - _ = x[actPut-146] - _ = x[actNextHistory-147] - _ = x[actNextSelected-148] - _ = x[actExecute-149] - _ = x[actExecuteSilent-150] - _ = x[actExecuteMulti-151] - _ = x[actSigStop-152] - _ = x[actBest-153] - _ = x[actFirst-154] - _ = x[actLast-155] - _ = x[actReload-156] - _ = x[actReloadSync-157] - _ = x[actDisableSearch-158] - _ = x[actEnableSearch-159] - _ = x[actSelect-160] - _ = x[actDeselect-161] - _ = x[actUnbind-162] - _ = x[actRebind-163] - _ = x[actToggleBind-164] - _ = x[actBecome-165] - _ = x[actShowHeader-166] - _ = x[actHideHeader-167] - _ = x[actBell-168] - _ = x[actExclude-169] - _ = x[actExcludeMulti-170] - _ = x[actAsync-171] + _ = x[actChangeHeaderLines-22] + _ = x[actChangeFooter-23] + _ = x[actChangeHeaderLabel-24] + _ = x[actChangeFooterLabel-25] + _ = x[actChangeInputLabel-26] + _ = x[actChangeListLabel-27] + _ = x[actChangeMulti-28] + _ = x[actChangeNth-29] + _ = x[actChangePointer-30] + _ = x[actChangePreview-31] + _ = x[actChangePreviewLabel-32] + _ = x[actChangePreviewWindow-33] + _ = x[actChangePrompt-34] + _ = x[actChangeQuery-35] + _ = x[actClearScreen-36] + _ = x[actClearQuery-37] + _ = x[actClearSelection-38] + _ = x[actClose-39] + _ = x[actDeleteChar-40] + _ = x[actDeleteCharEof-41] + _ = x[actEndOfLine-42] + _ = x[actFatal-43] + _ = x[actForwardChar-44] + _ = x[actForwardWord-45] + _ = x[actForwardSubWord-46] + _ = x[actKillLine-47] + _ = x[actKillWord-48] + _ = x[actKillSubWord-49] + _ = x[actUnixLineDiscard-50] + _ = x[actUnixWordRubout-51] + _ = x[actYank-52] + _ = x[actBackwardKillWord-53] + _ = x[actBackwardKillSubWord-54] + _ = x[actSelectAll-55] + _ = x[actDeselectAll-56] + _ = x[actToggle-57] + _ = x[actToggleSearch-58] + _ = x[actToggleAll-59] + _ = x[actToggleDown-60] + _ = x[actToggleUp-61] + _ = x[actToggleIn-62] + _ = x[actToggleOut-63] + _ = x[actToggleTrack-64] + _ = x[actToggleTrackCurrent-65] + _ = x[actToggleHeader-66] + _ = x[actToggleWrap-67] + _ = x[actToggleWrapWord-68] + _ = x[actToggleMultiLine-69] + _ = x[actToggleHscroll-70] + _ = x[actToggleRaw-71] + _ = x[actEnableRaw-72] + _ = x[actDisableRaw-73] + _ = x[actTrackCurrent-74] + _ = x[actToggleInput-75] + _ = x[actHideInput-76] + _ = x[actShowInput-77] + _ = x[actUntrackCurrent-78] + _ = x[actDown-79] + _ = x[actDownMatch-80] + _ = x[actUp-81] + _ = x[actUpMatch-82] + _ = x[actPageUp-83] + _ = x[actPageDown-84] + _ = x[actPosition-85] + _ = x[actHalfPageUp-86] + _ = x[actHalfPageDown-87] + _ = x[actOffsetUp-88] + _ = x[actOffsetDown-89] + _ = x[actOffsetMiddle-90] + _ = x[actJump-91] + _ = x[actJumpAccept-92] + _ = x[actPrintQuery-93] + _ = x[actRefreshPreview-94] + _ = x[actReplaceQuery-95] + _ = x[actToggleSort-96] + _ = x[actShowPreview-97] + _ = x[actHidePreview-98] + _ = x[actTogglePreview-99] + _ = x[actTogglePreviewWrap-100] + _ = x[actTogglePreviewWrapWord-101] + _ = x[actTransform-102] + _ = x[actTransformBorderLabel-103] + _ = x[actTransformGhost-104] + _ = x[actTransformHeader-105] + _ = x[actTransformHeaderLines-106] + _ = x[actTransformFooter-107] + _ = x[actTransformHeaderLabel-108] + _ = x[actTransformFooterLabel-109] + _ = x[actTransformInputLabel-110] + _ = x[actTransformListLabel-111] + _ = x[actTransformNth-112] + _ = x[actTransformPointer-113] + _ = x[actTransformPreviewLabel-114] + _ = x[actTransformPrompt-115] + _ = x[actTransformQuery-116] + _ = x[actTransformSearch-117] + _ = x[actTrigger-118] + _ = x[actBgTransform-119] + _ = x[actBgTransformBorderLabel-120] + _ = x[actBgTransformGhost-121] + _ = x[actBgTransformHeader-122] + _ = x[actBgTransformHeaderLines-123] + _ = x[actBgTransformFooter-124] + _ = x[actBgTransformHeaderLabel-125] + _ = x[actBgTransformFooterLabel-126] + _ = x[actBgTransformInputLabel-127] + _ = x[actBgTransformListLabel-128] + _ = x[actBgTransformNth-129] + _ = x[actBgTransformPointer-130] + _ = x[actBgTransformPreviewLabel-131] + _ = x[actBgTransformPrompt-132] + _ = x[actBgTransformQuery-133] + _ = x[actBgTransformSearch-134] + _ = x[actBgCancel-135] + _ = x[actSearch-136] + _ = x[actPreview-137] + _ = x[actPreviewTop-138] + _ = x[actPreviewBottom-139] + _ = x[actPreviewUp-140] + _ = x[actPreviewDown-141] + _ = x[actPreviewPageUp-142] + _ = x[actPreviewPageDown-143] + _ = x[actPreviewHalfPageUp-144] + _ = x[actPreviewHalfPageDown-145] + _ = x[actPrevHistory-146] + _ = x[actPrevSelected-147] + _ = x[actPrint-148] + _ = x[actPut-149] + _ = x[actNextHistory-150] + _ = x[actNextSelected-151] + _ = x[actExecute-152] + _ = x[actExecuteSilent-153] + _ = x[actExecuteMulti-154] + _ = x[actSigStop-155] + _ = x[actBest-156] + _ = x[actFirst-157] + _ = x[actLast-158] + _ = x[actReload-159] + _ = x[actReloadSync-160] + _ = x[actDisableSearch-161] + _ = x[actEnableSearch-162] + _ = x[actSelect-163] + _ = x[actDeselect-164] + _ = x[actUnbind-165] + _ = x[actRebind-166] + _ = x[actToggleBind-167] + _ = x[actBecome-168] + _ = x[actShowHeader-169] + _ = x[actHideHeader-170] + _ = 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 { if i < 0 || i >= actionType(len(_actionType_index)-1) { diff --git a/src/chunklist.go b/src/chunklist.go index ce4a56a0..63b6188c 100644 --- a/src/chunklist.go +++ b/src/chunklist.go @@ -52,6 +52,20 @@ func (cl *ChunkList) lastChunk() *Chunk { 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 func CountItems(cs []*Chunk) int { if len(cs) == 0 { diff --git a/src/constants.go b/src/constants.go index 5f5c8ca1..9f964c48 100644 --- a/src/constants.go +++ b/src/constants.go @@ -65,7 +65,6 @@ const ( EvtSearchNew EvtSearchProgress EvtSearchFin - EvtHeader EvtReady EvtQuit ) diff --git a/src/core.go b/src/core.go index debcc3f1..414d7e8d 100644 --- a/src/core.go +++ b/src/core.go @@ -17,7 +17,6 @@ Reader -> EvtReadNew -> Matcher (restart) Terminal -> EvtSearchNew:bool -> Matcher (restart) Matcher -> EvtSearchProgress -> Terminal (update info) Matcher -> EvtSearchFin -> Terminal (update list) -Matcher -> EvtHeader -> Terminal (update header) */ type revision struct { @@ -113,14 +112,8 @@ func Run(opts *Options) (int, error) { cache := NewChunkCache() var chunkList *ChunkList var itemIndex int32 - header := make([]string, 0, opts.HeaderLines) if opts.WithNth == nil { 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.Index = itemIndex itemIndex++ @@ -147,11 +140,6 @@ func Run(opts *Options) (int, error) { } } 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)) // We should not trim trailing whitespaces with background colors @@ -236,13 +224,15 @@ func Run(opts *Options) (int, error) { denylist = make(map[int32]struct{}) denyMutex.Unlock() } + headerLines := int32(opts.HeaderLines) + headerUpdated := false patternBuilder := func(runes []rune) *Pattern { denyMutex.Lock() denylistCopy := maps.Clone(denylist) denyMutex.Unlock() return BuildPattern(cache, patternCache, 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) @@ -265,6 +255,9 @@ func Run(opts *Options) (int, error) { func(runes []byte) bool { item := Item{} if chunkList.trans(&item, runes) { + if item.Index() < headerLines { + return false + } mutex.Lock() if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil { opts.Printer(transformer(&item)) @@ -349,11 +342,11 @@ func Run(opts *Options) (int, error) { clearDenylist() } reading = true + headerUpdated = false startTick = ticks chunkList.Clear() itemIndex = 0 inputRevision.bumpMajor() - header = make([]string, 0, opts.HeaderLines) readyChan := make(chan bool) go reader.restart(command, environ, readyChan) <-readyChan @@ -411,7 +404,11 @@ func Run(opts *Options) (int, error) { snapshotRevision = inputRevision } 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 { determine(!reading) } @@ -421,6 +418,7 @@ func Run(opts *Options) (int, error) { var command *commandSpec var environ []string var changed bool + headerLinesChanged := false switch val := value.(type) { case searchRequest: sort = val.sort @@ -441,6 +439,12 @@ func Run(opts *Options) (int, error) { nth = *val.nth bump = true } + if val.headerLines != nil { + headerLines = int32(*val.headerLines) + headerUpdated = false + headerLinesChanged = true + bump = true + } if bump { patternCache = make(map[string]*Pattern) cache.Clear() @@ -477,6 +481,14 @@ func Run(opts *Options) (int, error) { 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) delay = false @@ -486,11 +498,6 @@ func Run(opts *Options) (int, error) { terminal.UpdateProgress(val) } - case EvtHeader: - headerPadded := make([]string, opts.HeaderLines) - copy(headerPadded, value.([]string)) - terminal.UpdateHeader(headerPadded) - case EvtSearchFin: switch val := value.(type) { case MatchResult: diff --git a/src/matcher.go b/src/matcher.go index bc02c77b..eb22abd8 100644 --- a/src/matcher.go +++ b/src/matcher.go @@ -174,7 +174,7 @@ func (m *Matcher) scan(request MatchRequest) MatchResult { return MatchResult{m, m, false} } pattern := request.pattern - passMerger := PassMerger(&request.chunks, m.tac, request.revision) + passMerger := PassMerger(&request.chunks, m.tac, request.revision, pattern.startIndex) if pattern.IsEmpty() { return MatchResult{passMerger, passMerger, false} } diff --git a/src/merger.go b/src/merger.go index b9bdabb8..d4deaf4f 100644 --- a/src/merger.go +++ b/src/merger.go @@ -10,42 +10,46 @@ func EmptyMerger(revision revision) *Merger { // Merger holds a set of locally sorted lists of items and provides the view of // a single, globally-sorted list type Merger struct { - pattern *Pattern - lists [][]Result - merged []Result - chunks *[]*Chunk - cursors []int - sorted bool - tac bool - final bool - count int - pass bool - revision revision - minIndex int32 - maxIndex int32 + pattern *Pattern + lists [][]Result + merged []Result + chunks *[]*Chunk + cursors []int + sorted bool + tac bool + final bool + count int + pass bool + startIndex int + revision revision + minIndex int32 + maxIndex int32 } // PassMerger returns a new Merger that simply returns the items in the -// original order -func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger { +// original order. startIndex items are skipped from the beginning. +func PassMerger(chunks *[]*Chunk, tac bool, revision revision, startIndex int32) *Merger { var minIndex, maxIndex int32 if len(*chunks) > 0 { minIndex = (*chunks)[0].items[0].Index() maxIndex = (*chunks)[len(*chunks)-1].lastIndex(minIndex) } + si := int(startIndex) mg := Merger{ - pattern: nil, - chunks: chunks, - tac: tac, - count: 0, - pass: true, - revision: revision, - minIndex: minIndex, - maxIndex: maxIndex} + pattern: nil, + chunks: chunks, + tac: tac, + count: 0, + pass: true, + startIndex: si, + revision: revision, + minIndex: minIndex + startIndex, + maxIndex: maxIndex} for _, chunk := range *mg.chunks { mg.count += chunk.count } + mg.count = max(0, mg.count-si) return &mg } @@ -113,6 +117,7 @@ func (mg *Merger) Get(idx int) Result { if mg.tac { idx = mg.count - idx - 1 } + idx += mg.startIndex firstChunk := (*mg.chunks)[0] if firstChunk.count < chunkSize && idx >= firstChunk.count { idx -= firstChunk.count diff --git a/src/options.go b/src/options.go index 84c9a525..47b0095a 100644 --- a/src/options.go +++ b/src/options.go @@ -1626,7 +1626,7 @@ const ( func init() { 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("[,:]+") actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+") } @@ -2037,6 +2037,8 @@ func isExecuteAction(str string) actionType { return actPreview case "change-header": return actChangeHeader + case "change-header-lines": + return actChangeHeaderLines case "change-footer": return actChangeFooter case "change-list-label": @@ -2097,6 +2099,8 @@ func isExecuteAction(str string) actionType { return actTransformFooter case "transform-header": return actTransformHeader + case "transform-header-lines": + return actTransformHeaderLines case "transform-ghost": return actTransformGhost case "transform-nth": @@ -2127,6 +2131,8 @@ func isExecuteAction(str string) actionType { return actBgTransformFooter case "bg-transform-header": return actBgTransformHeader + case "bg-transform-header-lines": + return actBgTransformHeaderLines case "bg-transform-ghost": return actBgTransformGhost case "bg-transform-nth": diff --git a/src/pattern.go b/src/pattern.go index 8e6966c3..4e34c62f 100644 --- a/src/pattern.go +++ b/src/pattern.go @@ -64,6 +64,7 @@ type Pattern struct { procFun map[termType]algo.Algo cache *ChunkCache denylist map[int32]struct{} + startIndex int32 } var _splitRegex *regexp.Regexp @@ -74,7 +75,7 @@ func init() { // 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, - 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 if extended { @@ -146,6 +147,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo delimiter: delimiter, cache: cache, denylist: denylist, + startIndex: startIndex, procFun: make(map[termType]algo.Algo)} ptr.cacheKey = ptr.buildCacheKey() @@ -301,10 +303,19 @@ func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result { func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Result { matches := []Result{} + // Skip header items in chunks that contain them + 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 + } + } + if len(p.denylist) == 0 { // Huge code duplication for minimizing unnecessary map lookups 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 { matches = append(matches, *match) } @@ -320,7 +331,7 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re } 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 { continue } diff --git a/src/pattern_test.go b/src/pattern_test.go index 8e566263..c0c6e962 100644 --- a/src/pattern_test.go +++ b/src/pattern_test.go @@ -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 { return BuildPattern(NewChunkCache(), make(map[string]*Pattern), 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) { diff --git a/src/terminal.go b/src/terminal.go index d761dd77..5187fa04 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -314,6 +314,7 @@ type Terminal struct { sort bool toggleSort bool track trackOption + targetIndex int32 delimiter Delimiter expect map[tui.Event]string keymap map[tui.Event][]*action @@ -327,7 +328,7 @@ type Terminal struct { headerVisible bool headerFirst bool headerLines int - header []string + header []Item header0 []string footer []string ellipsis string @@ -542,6 +543,7 @@ const ( actChangeBorderLabel actChangeGhost actChangeHeader + actChangeHeaderLines actChangeFooter actChangeHeaderLabel actChangeFooterLabel @@ -627,6 +629,7 @@ const ( actTransformBorderLabel actTransformGhost actTransformHeader + actTransformHeaderLines actTransformFooter actTransformHeaderLabel actTransformFooterLabel @@ -645,6 +648,7 @@ const ( actBgTransformBorderLabel actBgTransformGhost actBgTransformHeader + actBgTransformHeaderLines actBgTransformFooter actBgTransformHeaderLabel actBgTransformFooterLabel @@ -710,6 +714,7 @@ func processExecution(action actionType) bool { actTransformBorderLabel, actTransformGhost, actTransformHeader, + actTransformHeaderLines, actTransformFooter, actTransformHeaderLabel, actTransformFooterLabel, @@ -725,6 +730,7 @@ func processExecution(action actionType) bool { actBgTransformBorderLabel, actBgTransformGhost, actBgTransformHeader, + actBgTransformHeaderLines, actBgTransformFooter, actBgTransformHeaderLabel, actBgTransformFooterLabel, @@ -761,14 +767,15 @@ type placeholderFlags struct { } type searchRequest struct { - sort bool - sync bool - nth *[]Range - command *commandSpec - environ []string - changed bool - denylist []int32 - revision revision + sort bool + sync bool + nth *[]Range + headerLines *int + command *commandSpec + environ []string + changed bool + denylist []int32 + revision revision } type previewRequest struct { @@ -1022,6 +1029,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor sort: opts.Sort > 0, toggleSort: opts.ToggleSort, track: opts.Track, + targetIndex: minItem.Index(), delimiter: opts.Delimiter, expect: opts.Expect, keymap: opts.Keymap, @@ -1063,7 +1071,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor headerFirst: opts.HeaderFirst, headerLines: opts.HeaderLines, gap: opts.Gap, - header: []string{}, + header: []Item{}, footer: opts.Footer, header0: opts.Header, ansi: opts.Ansi, @@ -1364,7 +1372,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_TOTAL_COUNT=%d", max(0, t.count-t.headerLines))) 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)) @@ -1755,8 +1763,14 @@ func (t *Terminal) changeFooter(footer string) { } // UpdateHeader updates the header -func (t *Terminal) UpdateHeader(header []string) { +func (t *Terminal) UpdateHeader(header []Item) { 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.mutex.Unlock() t.reqBox.Set(reqHeader, nil) @@ -1788,6 +1802,10 @@ func (t *Terminal) UpdateList(result MatchResult) { prevIndex = merger.First().item.Index() } } + if t.targetIndex != minItem.Index() { + prevIndex = t.targetIndex + t.targetIndex = minItem.Index() + } t.progress = 100 t.merger = merger t.resultMerger = merger @@ -3079,11 +3097,11 @@ func (t *Terminal) printHeader() { } t.withWindow(t.headerWindow, func() { - var lines []string + var headerItems []Item 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 { t.withWindow(t.headerLinesWindow, func() { @@ -3145,7 +3163,7 @@ func (t *Terminal) headerIndentImpl(base int, borderShape tui.BorderShape) int { 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() if !t.inputless && t.inputWindow == nil && window == nil && t.headerFirst { max-- @@ -3172,7 +3190,8 @@ func (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShap } indent := strings.Repeat(" ", indentSize) 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 if needReverse && idx < len(lines1) { line = len(lines1) - idx - 1 @@ -3186,11 +3205,18 @@ func (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShap if line >= max { continue } - trimmed, colors, newState := extractColor(lineStr, state, nil) - state = newState - item := &Item{ - text: util.ToChars([]byte(trimmed)), - colors: colors} + + var item *Item + if idx < len(lines1) { + trimmed, colors, newState := extractColor(lines1[idx], state, nil) + state = newState + item = &Item{ + text: util.ToChars([]byte(trimmed)), + colors: colors} + } else { + headerItem := lines2[idx-len(lines1)] + item = &headerItem + } t.printHighlighted(Result{item: item}, tui.ColHeader, tui.ColHeader, false, false, false, line, line, true, @@ -5288,9 +5314,13 @@ func (t *Terminal) addClickHeaderWord(env []string) []string { return env } - // NOTE: t.header is padded with empty strings so that its size is equal to t.headerLines 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 { headers[0], headers[1] = headers[1], headers[0] } @@ -5892,6 +5922,7 @@ func (t *Terminal) Loop() error { events := []util.EventType{} changed := false var newNth *[]Range + var newHeaderLines *int req := func(evts ...util.EventType) { for _, event := range evts { events = append(events, event) @@ -5908,6 +5939,7 @@ func (t *Terminal) Loop() error { events = []util.EventType{} changed = false newNth = nil + newHeaderLines = nil beof := false queryChanged := false denylist := []int32{} @@ -6247,6 +6279,23 @@ func (t *Terminal) Loop() error { } case actPrintQuery: 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: multi := t.multi if a.a == "" { @@ -7428,7 +7477,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.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 diff --git a/test/test_core.rb b/test/test_core.rb index 3f932eed..2ca9e26e 100644 --- a/test/test_core.rb +++ b/test/test_core.rb @@ -2176,6 +2176,80 @@ class TestCore < TestInteractive 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 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| diff --git a/test/test_filter.rb b/test/test_filter.rb index e9ca0302..35b00854 100644 --- a/test/test_filter.rb +++ b/test/test_filter.rb @@ -326,4 +326,27 @@ class TestFilter < TestBase writelines(['emp001 Alice Engineering', 'emp002 Bob Marketing']) assert_equal 'emp001', `#{FZF} -d' ' --with-nth 2 --accept-nth 1 -f Alice < #{tempname}`.chomp 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