Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add search result count to SearchAddon #3716

Merged
merged 32 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b78cb4c
add find count
meganrogge Mar 23, 2022
bb0f5d6
Merge branch 'xtermjs:master' into findCount
meganrogge Mar 28, 2022
c1c0df4
remove index for now
meganrogge Mar 28, 2022
7cc868b
Merge branch 'findCount' of https://github.com/meganrogge/xterm.js in…
meganrogge Mar 28, 2022
6e7d1de
add jsdoc
meganrogge Mar 28, 2022
6ff999b
update jsdoc
meganrogge Mar 28, 2022
463a0e9
property instead of method
meganrogge Mar 29, 2022
49a170f
return undefined when no search results
meganrogge Mar 29, 2022
691f3c9
initialize maps in highlightAllMatches
meganrogge Mar 29, 2022
9a93d5c
! -> ?
meganrogge Mar 29, 2022
d764281
add event to indicate when results have changed
meganrogge Mar 29, 2022
4b6198e
Update addons/xterm-addon-search/src/SearchAddon.ts
meganrogge Mar 29, 2022
f59db29
refactor
meganrogge Mar 29, 2022
dc490d1
Merge branch 'findCount' of https://github.com/meganrogge/xterm.js in…
meganrogge Mar 29, 2022
3df6be8
clear decorations find previous
meganrogge Mar 30, 2022
e2541a1
import
meganrogge Mar 30, 2022
1f00d13
correct ts config
meganrogge Mar 30, 2022
c7d24bf
Update addons/xterm-addon-search/tsconfig.json
meganrogge Mar 30, 2022
f93e166
Update addons/xterm-addon-search/src/SearchAddon.ts
meganrogge Mar 30, 2022
cd0658d
Update addons/xterm-addon-search/src/SearchAddon.ts
meganrogge Mar 30, 2022
f1a75b4
Merge branch 'master' into findCount
meganrogge Mar 30, 2022
56ab94d
refactor to fire event
meganrogge Mar 30, 2022
371f552
Merge branch 'findCount' of https://github.com/meganrogge/xterm.js in…
meganrogge Mar 30, 2022
7099b03
clean up
meganrogge Mar 30, 2022
3995283
revert changes to d.ts
meganrogge Mar 30, 2022
2bce7f4
d.ts
meganrogge Mar 30, 2022
c01aa9e
fix remaining issues
meganrogge Mar 30, 2022
c2df04a
add webpack config change
meganrogge Mar 30, 2022
0c325c8
fix check
meganrogge Mar 30, 2022
2bbcf8c
remove bad conditional
meganrogge Mar 30, 2022
e5fd206
zero based indexing
meganrogge Mar 30, 2022
0ea5e2b
check not undefined
meganrogge Mar 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 95 additions & 7 deletions addons/xterm-addon-search/src/SearchAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,69 @@
*/

import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm';
interface IListener<T, U = void> {
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
(arg1: T, arg2: U): void;
}

export interface IEvent<T, U = void> {
(listener: (arg1: T, arg2: U) => any): IDisposable;
}

export interface IEventEmitter<T, U = void> {
event: IEvent<T, U>;
fire(arg1: T, arg2: U): void;
dispose(): void;
}

export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
private _listeners: IListener<T, U>[] = [];
private _event?: IEvent<T, U>;
private _disposed: boolean = false;

public get event(): IEvent<T, U> {
if (!this._event) {
this._event = (listener: (arg1: T, arg2: U) => any) => {
this._listeners.push(listener);
const disposable = {
dispose: () => {
if (!this._disposed) {
for (let i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
}
}
}
}
};
return disposable;
};
}
return this._event;
}

public fire(arg1: T, arg2: U): void {
const queue: IListener<T, U>[] = [];
for (let i = 0; i < this._listeners.length; i++) {
queue.push(this._listeners[i]);
}
for (let i = 0; i < queue.length; i++) {
queue[i].call(undefined, arg1, arg2);
}
}

public dispose(): void {
if (this._listeners) {
this._listeners.length = 0;
}
this._disposed = true;
}
}

export function forwardEvent<T>(from: IEvent<T>, to: IEventEmitter<T>): IDisposable {
return from(e => to.fire(e));
}


export interface ISearchOptions {
regex?: boolean;
Expand Down Expand Up @@ -53,8 +116,8 @@ export class SearchAddon implements ITerminalAddon {
private _dataChanged: boolean = false;
private _cachedSearchTerm: string | undefined;
private _selectedDecoration: IDecoration | undefined;
private _resultDecorations: Map<number, IDecoration[]> = new Map<number, IDecoration[]>();
private _searchResults: Map<string, ISearchResult> = new Map();
private _resultDecorations: Map<number, IDecoration[]> | undefined;
private _searchResults: Map<string, ISearchResult> | undefined;
private _onDataDisposable: IDisposable | undefined;
private _lastSearchOptions: ISearchOptions | undefined;
private _highlightTimeout: number | undefined;
Expand All @@ -68,6 +131,9 @@ export class SearchAddon implements ITerminalAddon {
private _cursorMoveListener: IDisposable | undefined;
private _resizeListener: IDisposable | undefined;

private readonly _onDidChangeResults = new EventEmitter<void>();
public readonly onDidChangeResults = this._onDidChangeResults.event;

public activate(terminal: Terminal): void {
this._terminal = terminal;
this._onDataDisposable = this._terminal.onData(() => {
Expand All @@ -76,7 +142,7 @@ export class SearchAddon implements ITerminalAddon {
window.clearTimeout(this._highlightTimeout);
}
this._highlightTimeout = setTimeout(() => {
if (this._lastSearchOptions?.decorations && this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) {
if (this._lastSearchOptions?.decorations && this._cachedSearchTerm && this._resultDecorations?.size && this._resultDecorations.size > 0 && this._lastSearchOptions) {
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions);
}
}, 200);
Expand All @@ -91,13 +157,16 @@ export class SearchAddon implements ITerminalAddon {
public clearDecorations(): void {
this._selectedDecoration?.dispose();
this._terminal?.clearSelection();
this._searchResults.clear();
this._searchResults?.clear();
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
this._disposeDecorations();
this._cachedSearchTerm = undefined;
this._dataChanged = true;
}

private _disposeDecorations(): void {
if (!this._resultDecorations) {
return;
}
this._resultDecorations.forEach(decorations => {
for (const d of decorations) {
d.dispose();
Expand All @@ -106,6 +175,14 @@ export class SearchAddon implements ITerminalAddon {
this._resultDecorations.clear();
}

/**
* @returns the last search result count or undefined
* if decorations aren't enabled
*/
public get resultCount(): number | undefined {
return this._searchResults?.size || undefined;
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Find the next instance of the term, then scroll to and select it. If it
* doesn't exist, do nothing.
Expand All @@ -129,6 +206,12 @@ export class SearchAddon implements ITerminalAddon {
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
if (!this._searchResults) {
this._searchResults = new Map<string, ISearchResult>();
}
if (!this._resultDecorations) {
this._resultDecorations = new Map<number, IDecoration[]>();
}
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
if (!term || term.length === 0) {
this.clearDecorations();
return;
Expand All @@ -137,6 +220,8 @@ export class SearchAddon implements ITerminalAddon {
if (term === this._cachedSearchTerm && !this._dataChanged) {
return;
}
const dataDrivenChange = term === this._cachedSearchTerm && this._dataChanged;
const lastSearchCount = this._searchResults.size;
// new search, clear out the old decorations
this._disposeDecorations();
this._searchResults.clear();
Expand All @@ -153,9 +238,9 @@ export class SearchAddon implements ITerminalAddon {
this._searchResults.forEach(result => {
const resultDecoration = this._createResultDecoration(result, searchOptions.decorations!);
if (resultDecoration) {
const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || [];
const decorationsForLine = this._resultDecorations!.get(resultDecoration.marker.line) || [];
decorationsForLine.push(resultDecoration);
this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine);
this._resultDecorations!.set(resultDecoration.marker.line, decorationsForLine);
}
});
if (this._dataChanged) {
Expand All @@ -164,6 +249,9 @@ export class SearchAddon implements ITerminalAddon {
if (this._searchResults.size > 0) {
this._cachedSearchTerm = term;
}
if (dataDrivenChange && lastSearchCount !== this._searchResults.size) {
this._onDidChangeResults.fire();
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
}
}

private _find(term: string, startRow: number, startCol: number, searchOptions?: ISearchOptions): ISearchResult | undefined {
Expand Down Expand Up @@ -669,7 +757,7 @@ export class SearchAddon implements ITerminalAddon {
marker,
x: result.col,
width: result.size,
overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : {
overviewRulerOptions: this._resultDecorations?.get(marker.line) && !this._dataChanged ? undefined : {
color: decorations.matchOverviewRuler, position: 'center'
}
});
Expand Down
15 changes: 14 additions & 1 deletion addons/xterm-addon-search/typings/xterm-addon-search.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* @license MIT
*/

import { Terminal, ILinkMatcherOptions, IDisposable, ITerminalAddon } from 'xterm';
import { IEvent } from 'node-pty';
import { Terminal, ITerminalAddon } from 'xterm';

declare module 'xterm-addon-search' {
/**
Expand Down Expand Up @@ -110,5 +111,17 @@ declare module 'xterm-addon-search' {
* Clears the decorations and selection
*/
public clearDecorations(): void;

/**
* @returns the last search result count or undefined
* if decorations aren't enabled
*/
public get resultCount(): number | undefined;

/**
* An event listener for when the search results change
* as a result of the buffer changing
*/
onDidChangeResults: IEvent<void>;
}
}