diff --git a/addons/addon-search/src/SearchAddon.ts b/addons/addon-search/src/SearchAddon.ts index 451434d9..ffbe9401 100644 --- a/addons/addon-search/src/SearchAddon.ts +++ b/addons/addon-search/src/SearchAddon.ts @@ -5,7 +5,7 @@ import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm'; import type { SearchAddon as ISearchApi, ISearchOptions, ISearchAddonOptions, ISearchResultChangeEvent } from '@xterm/addon-search'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { disposableTimeout } from 'vs/base/common/async'; import { SearchLineCache } from './SearchLineCache'; @@ -42,6 +42,11 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp private _decorationManager: DecorationManager | undefined; private _resultTracker = this._register(new SearchResultTracker()); + private readonly _onAfterSearch = this._register(new Emitter()); + public readonly onAfterSearch = this._onAfterSearch.event; + private readonly _onBeforeSearch = this._register(new Emitter()); + public readonly onBeforeSearch = this._onBeforeSearch.event; + public get onDidChangeResults(): Event { return this._resultTracker.onDidChangeResults; } @@ -98,6 +103,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp throw new Error('Cannot use addon until it has been loaded'); } + this._onBeforeSearch.fire(); + this._state.lastSearchOptions = searchOptions; if (this._state.shouldUpdateHighlighting(term, searchOptions)) { @@ -108,6 +115,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp this._fireResults(searchOptions); this._state.cachedSearchTerm = term; + this._onAfterSearch.fire(); + return found; } @@ -173,6 +182,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp throw new Error('Cannot use addon until it has been loaded'); } + this._onBeforeSearch.fire(); + this._state.lastSearchOptions = searchOptions; if (this._state.shouldUpdateHighlighting(term, searchOptions)) { @@ -183,6 +194,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp this._fireResults(searchOptions); this._state.cachedSearchTerm = term; + this._onAfterSearch.fire(); + return found; } diff --git a/addons/addon-search/test/SearchAddon.test.ts b/addons/addon-search/test/SearchAddon.test.ts index 7b551e0c..a4ccb6c0 100644 --- a/addons/addon-search/test/SearchAddon.test.ts +++ b/addons/addon-search/test/SearchAddon.test.ts @@ -385,6 +385,34 @@ test.describe('Search Tests', () => { }); }); + test.describe('onBeforeSearch and onAfterSearch', () => { + test.beforeEach(async () => { + await ctx.page.evaluate(` + window.events = []; + window.search.onBeforeSearch(() => window.events.push('before')); + window.search.onAfterSearch(() => window.events.push('after')); + `); + }); + test('should fire before and after findNext', async () => { + await ctx.proxy.write('abc'); + await ctx.page.evaluate(`window.search.findNext('a')`); + deepStrictEqual(await ctx.page.evaluate('window.events'), ['before', 'after']); + }); + + test('should fire before and after findPrevious', async () => { + await ctx.proxy.write('abc'); + await ctx.page.evaluate(`window.search.findPrevious('a')`); + deepStrictEqual(await ctx.page.evaluate('window.events'), ['before', 'after']); + }); + + test('should fire for each search call', async () => { + await ctx.proxy.write('abc abc'); + await ctx.page.evaluate(`window.search.findNext('abc')`); + await ctx.page.evaluate(`window.search.findNext('abc')`); + deepStrictEqual(await ctx.page.evaluate('window.events'), ['before', 'after', 'before', 'after']); + }); + }); + test.describe('Regression tests', () => { test.describe('#2444 wrapped line content not being found', () => { let fixture: string; diff --git a/addons/addon-search/typings/addon-search.d.ts b/addons/addon-search/typings/addon-search.d.ts index 4c0df5a6..40034da9 100644 --- a/addons/addon-search/typings/addon-search.d.ts +++ b/addons/addon-search/typings/addon-search.d.ts @@ -152,8 +152,17 @@ declare module '@xterm/addon-search' { public clearActiveDecoration(): void; /** - * When decorations are enabled, fires when - * the search results change. + * Fires after a search is performed. + */ + readonly onAfterSearch: IEvent; + + /** + * Fires before a search is performed. + */ + readonly onBeforeSearch: IEvent; + + /** + * When decorations are enabled, fires when the search results change. */ readonly onDidChangeResults: IEvent; }