From 687d8277e738fa258f45ea04fd664ba16195686a Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 20 Aug 2025 10:53:40 -0700 Subject: [PATCH 1/3] Fix terminal find when wrapped --- addons/addon-search/src/SearchAddon.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/addons/addon-search/src/SearchAddon.ts b/addons/addon-search/src/SearchAddon.ts index 03a42619..0a39cda2 100644 --- a/addons/addon-search/src/SearchAddon.ts +++ b/addons/addon-search/src/SearchAddon.ts @@ -72,6 +72,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA private _cachedSearchTerm: string | undefined; private _highlightedLines: Set = new Set(); private _highlightDecorations: IHighlight[] = []; + private _searchResultsWithHighlight: ISearchResult[] = []; private _selectedDecoration: MutableDisposable = this._register(new MutableDisposable()); private _highlightLimit: number; private _lastSearchOptions: ISearchOptions | undefined; @@ -118,6 +119,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA this._selectedDecoration.clear(); dispose(this._highlightDecorations); this._highlightDecorations = []; + this._searchResultsWithHighlight = []; this._highlightedLines.clear(); if (!retainCachedSearchTerm) { this._cachedSearchTerm = undefined; @@ -167,15 +169,14 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA // new search, clear out the old decorations this.clearDecorations(true); - const searchResultsWithHighlight: ISearchResult[] = []; let prevResult: ISearchResult | undefined = undefined; let result = this._find(term, 0, 0, searchOptions); while (result && (prevResult?.row !== result.row || prevResult?.col !== result.col)) { - if (searchResultsWithHighlight.length >= this._highlightLimit) { + if (this._searchResultsWithHighlight.length >= this._highlightLimit) { break; } prevResult = result; - searchResultsWithHighlight.push(prevResult); + this._searchResultsWithHighlight.push(prevResult); result = this._find( term, prevResult.col + prevResult.term.length >= this._terminal.cols ? prevResult.row + 1 : prevResult.row, @@ -183,7 +184,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA searchOptions ); } - for (const match of searchResultsWithHighlight) { + for (const match of this._searchResultsWithHighlight) { const decorations = this._createResultDecorations(match, searchOptions.decorations!, false); if (decorations) { for (const decoration of decorations) { @@ -350,15 +351,16 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA let resultIndex = -1; if (this._selectedDecoration.value) { const selectedMatch = this._selectedDecoration.value.match; - for (let i = 0; i < this._highlightDecorations.length; i++) { - const match = this._highlightDecorations[i].match; + // Problem: this.highlightDecorations.length was showing all lines of highlighted. + for (let i = 0; i < this._searchResultsWithHighlight.length; i++) { + const match = this._searchResultsWithHighlight[i]; if (match.row === selectedMatch.row && match.col === selectedMatch.col && match.size === selectedMatch.size) { resultIndex = i; break; } } } - this._onDidChangeResults.fire({ resultIndex, resultCount: this._highlightDecorations.length }); + this._onDidChangeResults.fire({ resultIndex, resultCount: this._searchResultsWithHighlight.length }); } } From b53096f4a29dc11925079ef4149c4ff0b77b32bb Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 20 Aug 2025 10:55:47 -0700 Subject: [PATCH 2/3] Clean code --- addons/addon-search/src/SearchAddon.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/addon-search/src/SearchAddon.ts b/addons/addon-search/src/SearchAddon.ts index 0a39cda2..8395e437 100644 --- a/addons/addon-search/src/SearchAddon.ts +++ b/addons/addon-search/src/SearchAddon.ts @@ -351,7 +351,6 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA let resultIndex = -1; if (this._selectedDecoration.value) { const selectedMatch = this._selectedDecoration.value.match; - // Problem: this.highlightDecorations.length was showing all lines of highlighted. for (let i = 0; i < this._searchResultsWithHighlight.length; i++) { const match = this._searchResultsWithHighlight[i]; if (match.row === selectedMatch.row && match.col === selectedMatch.col && match.size === selectedMatch.size) { From 95a9531ec17aa430652281e643cf603b6455c5b4 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 20 Aug 2025 22:49:03 -0700 Subject: [PATCH 3/3] Try to add tests --- addons/addon-search/test/SearchAddon.test.ts | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/addons/addon-search/test/SearchAddon.test.ts b/addons/addon-search/test/SearchAddon.test.ts index 0045cf1c..232686b4 100644 --- a/addons/addon-search/test/SearchAddon.test.ts +++ b/addons/addon-search/test/SearchAddon.test.ts @@ -480,6 +480,57 @@ test.describe('Search Tests', () => { ]); }); }); + + test.describe('Wrapped line search functionality', () => { + test('should correctly count matches across multiple wrapped lines', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + + const content = 'a'.repeat(300); + await ctx.proxy.write(content); + strictEqual(await ctx.page.evaluate(`window.search.findNext('${content}', { decorations: { activeMatchColorOverviewRuler: '#ff0000', matchOverviewRuler: '#ffff00' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 } + ]); + }); + + test('should handle reverse search across wrapped lines', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + + const content = 'x'.repeat(300); + await ctx.proxy.write(content); + strictEqual(await ctx.page.evaluate(`window.search.findPrevious('${content}', { decorations: { activeMatchColorOverviewRuler: '#ff0000', matchOverviewRuler: '#ffff00' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 } + ]); + }); + + test('should update counts when content changes across wrapped lines', async () => { + await ctx.page.evaluate(` + window.calls = []; + window.search.onDidChangeResults(e => window.calls.push(e)); + `); + + const content = 'z'.repeat(300); + await ctx.proxy.write(content); + strictEqual(await ctx.page.evaluate(`window.search.findNext('${content}', { decorations: { activeMatchColorOverviewRuler: '#ff0000', matchOverviewRuler: '#ffff00' } })`), true); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 } + ]); + + await ctx.proxy.write('\\n\\r' + content); + await timeout(300); + deepStrictEqual(await ctx.page.evaluate('window.calls'), [ + { resultCount: 1, resultIndex: 0 }, + { resultCount: 2, resultIndex: 0 } + ]); + }); + }); }); function makeData(length: number): string {