@xterm/addon-search 0.16.0-beta.11 → 0.16.0-beta.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/addon-search.js +1 -1
- package/lib/addon-search.js.map +1 -1
- package/lib/addon-search.mjs +44 -0
- package/lib/addon-search.mjs.map +7 -0
- package/package.json +7 -4
- package/src/SearchAddon.ts +73 -57
package/src/SearchAddon.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Terminal, IDisposable, ITerminalAddon, IDecoration } from '@xterm/xterm';
|
|
7
7
|
import type { SearchAddon as ISearchApi } from '@xterm/addon-search';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { Emitter } from 'vs/base/common/event';
|
|
9
|
+
import { combinedDisposable, Disposable, dispose, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
|
10
10
|
|
|
11
11
|
export interface ISearchOptions {
|
|
12
12
|
regex?: boolean;
|
|
@@ -58,6 +58,11 @@ interface IHighlight extends IDisposable {
|
|
|
58
58
|
match: ISearchResult;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
interface IMultiHighlight extends IDisposable {
|
|
62
|
+
decorations: IDecoration[];
|
|
63
|
+
match: ISearchResult;
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
|
|
62
67
|
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
|
|
63
68
|
const DEFAULT_HIGHLIGHT_LIMIT = 1000;
|
|
@@ -67,7 +72,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
67
72
|
private _cachedSearchTerm: string | undefined;
|
|
68
73
|
private _highlightedLines: Set<number> = new Set();
|
|
69
74
|
private _highlightDecorations: IHighlight[] = [];
|
|
70
|
-
private _selectedDecoration: MutableDisposable<
|
|
75
|
+
private _selectedDecoration: MutableDisposable<IMultiHighlight> = this._register(new MutableDisposable());
|
|
71
76
|
private _highlightLimit: number;
|
|
72
77
|
private _lastSearchOptions: ISearchOptions | undefined;
|
|
73
78
|
private _highlightTimeout: number | undefined;
|
|
@@ -80,7 +85,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
80
85
|
private _linesCacheTimeoutId = 0;
|
|
81
86
|
private _linesCacheDisposables = new MutableDisposable();
|
|
82
87
|
|
|
83
|
-
private readonly _onDidChangeResults = this.
|
|
88
|
+
private readonly _onDidChangeResults = this._register(new Emitter<{ resultIndex: number, resultCount: number }>());
|
|
84
89
|
public readonly onDidChangeResults = this._onDidChangeResults.event;
|
|
85
90
|
|
|
86
91
|
constructor(options?: Partial<ISearchAddonOptions>) {
|
|
@@ -91,9 +96,9 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
91
96
|
|
|
92
97
|
public activate(terminal: Terminal): void {
|
|
93
98
|
this._terminal = terminal;
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
this.
|
|
99
|
+
this._register(this._terminal.onWriteParsed(() => this._updateMatches()));
|
|
100
|
+
this._register(this._terminal.onResize(() => this._updateMatches()));
|
|
101
|
+
this._register(toDisposable(() => this.clearDecorations()));
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
private _updateMatches(): void {
|
|
@@ -111,7 +116,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
111
116
|
|
|
112
117
|
public clearDecorations(retainCachedSearchTerm?: boolean): void {
|
|
113
118
|
this._selectedDecoration.clear();
|
|
114
|
-
|
|
119
|
+
dispose(this._highlightDecorations);
|
|
115
120
|
this._highlightDecorations = [];
|
|
116
121
|
this._highlightedLines.clear();
|
|
117
122
|
if (!retainCachedSearchTerm) {
|
|
@@ -179,14 +184,20 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
179
184
|
);
|
|
180
185
|
}
|
|
181
186
|
for (const match of searchResultsWithHighlight) {
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
const decorations = this._createResultDecorations(match, searchOptions.decorations!, false);
|
|
188
|
+
if (decorations) {
|
|
189
|
+
for (const decoration of decorations) {
|
|
190
|
+
this._storeDecoration(decoration, match);
|
|
191
|
+
}
|
|
186
192
|
}
|
|
187
193
|
}
|
|
188
194
|
}
|
|
189
195
|
|
|
196
|
+
private _storeDecoration(decoration: IDecoration, match: ISearchResult): void {
|
|
197
|
+
this._highlightedLines.add(decoration.marker.line);
|
|
198
|
+
this._highlightDecorations.push({ decoration, match, dispose() { decoration.dispose(); } });
|
|
199
|
+
}
|
|
200
|
+
|
|
190
201
|
private _find(term: string, startRow: number, startCol: number, searchOptions?: ISearchOptions): ISearchResult | undefined {
|
|
191
202
|
if (!this._terminal || !term || term.length === 0) {
|
|
192
203
|
this._terminal?.clearSelection();
|
|
@@ -426,11 +437,11 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
426
437
|
const terminal = this._terminal!;
|
|
427
438
|
if (!this._linesCache) {
|
|
428
439
|
this._linesCache = new Array(terminal.buffer.active.length);
|
|
429
|
-
this._linesCacheDisposables.value =
|
|
440
|
+
this._linesCacheDisposables.value = combinedDisposable(
|
|
430
441
|
terminal.onLineFeed(() => this._destroyLinesCache()),
|
|
431
442
|
terminal.onCursorMove(() => this._destroyLinesCache()),
|
|
432
443
|
terminal.onResize(() => this._destroyLinesCache())
|
|
433
|
-
|
|
444
|
+
);
|
|
434
445
|
}
|
|
435
446
|
|
|
436
447
|
window.clearTimeout(this._linesCacheTimeoutId);
|
|
@@ -499,12 +510,16 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
499
510
|
const [stringLine, offsets] = cache;
|
|
500
511
|
|
|
501
512
|
const offset = this._bufferColsToStringOffset(row, col);
|
|
502
|
-
|
|
503
|
-
|
|
513
|
+
let searchTerm = term;
|
|
514
|
+
let searchStringLine = stringLine;
|
|
515
|
+
if (!searchOptions.regex) {
|
|
516
|
+
searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
|
|
517
|
+
searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
|
|
518
|
+
}
|
|
504
519
|
|
|
505
520
|
let resultIndex = -1;
|
|
506
521
|
if (searchOptions.regex) {
|
|
507
|
-
const searchRegex = RegExp(searchTerm, 'g');
|
|
522
|
+
const searchRegex = RegExp(searchTerm, searchOptions.caseSensitive ? 'g' : 'gi');
|
|
508
523
|
let foundTerm: RegExpExecArray | null;
|
|
509
524
|
if (isReverseSearch) {
|
|
510
525
|
// This loop will get the resultIndex of the _last_ regex match in the range 0..offset
|
|
@@ -662,25 +677,9 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
662
677
|
}
|
|
663
678
|
terminal.select(result.col, result.row, result.size);
|
|
664
679
|
if (options) {
|
|
665
|
-
const
|
|
666
|
-
if (
|
|
667
|
-
|
|
668
|
-
marker,
|
|
669
|
-
x: result.col,
|
|
670
|
-
width: result.size,
|
|
671
|
-
backgroundColor: options.activeMatchBackground,
|
|
672
|
-
layer: 'top',
|
|
673
|
-
overviewRulerOptions: {
|
|
674
|
-
color: options.activeMatchColorOverviewRuler
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
if (decoration) {
|
|
678
|
-
const disposables: IDisposable[] = [];
|
|
679
|
-
disposables.push(marker);
|
|
680
|
-
disposables.push(decoration.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true)));
|
|
681
|
-
disposables.push(decoration.onDispose(() => disposeArray(disposables)));
|
|
682
|
-
this._selectedDecoration.value = { decoration, match: result, dispose() { decoration.dispose(); } };
|
|
683
|
-
}
|
|
680
|
+
const decorations = this._createResultDecorations(result, options, true);
|
|
681
|
+
if (decorations) {
|
|
682
|
+
this._selectedDecoration.value = { decorations, match: result, dispose() { dispose(decorations); } };
|
|
684
683
|
}
|
|
685
684
|
}
|
|
686
685
|
|
|
@@ -720,28 +719,45 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
720
719
|
* @param options the options for the decoration
|
|
721
720
|
* @returns the {@link IDecoration} or undefined if the marker has already been disposed of
|
|
722
721
|
*/
|
|
723
|
-
private
|
|
722
|
+
private _createResultDecorations(result: ISearchResult, options: ISearchDecorationOptions, isActiveResult: boolean): IDecoration[] | undefined {
|
|
724
723
|
const terminal = this._terminal!;
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
724
|
+
|
|
725
|
+
// Gather decoration ranges for this match as it could wrap
|
|
726
|
+
const decorationRanges: [number, number, number][] = [];
|
|
727
|
+
let currentCol = result.col;
|
|
728
|
+
let remainingSize = result.size;
|
|
729
|
+
let markerOffset = -terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row;
|
|
730
|
+
while (remainingSize > 0) {
|
|
731
|
+
const amountThisRow = Math.min(terminal.cols - currentCol, remainingSize);
|
|
732
|
+
decorationRanges.push([markerOffset, currentCol, amountThisRow]);
|
|
733
|
+
currentCol = 0;
|
|
734
|
+
remainingSize -= amountThisRow;
|
|
735
|
+
markerOffset++;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Create the decorations
|
|
739
|
+
const decorations: IDecoration[] = [];
|
|
740
|
+
for (const range of decorationRanges) {
|
|
741
|
+
const marker = terminal.registerMarker(range[0]);
|
|
742
|
+
const decoration = terminal.registerDecoration({
|
|
743
|
+
marker,
|
|
744
|
+
x: range[1],
|
|
745
|
+
width: range[2],
|
|
746
|
+
backgroundColor: isActiveResult ? options.activeMatchBackground : options.matchBackground,
|
|
747
|
+
overviewRulerOptions: this._highlightedLines.has(marker.line) ? undefined : {
|
|
748
|
+
color: isActiveResult ? options.activeMatchColorOverviewRuler : options.matchOverviewRuler,
|
|
749
|
+
position: 'center'
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
if (decoration) {
|
|
753
|
+
const disposables: IDisposable[] = [];
|
|
754
|
+
disposables.push(marker);
|
|
755
|
+
disposables.push(decoration.onRender((e) => this._applyStyles(e, isActiveResult ? options.activeMatchBorder : options.matchBorder, false)));
|
|
756
|
+
disposables.push(decoration.onDispose(() => dispose(disposables)));
|
|
757
|
+
decorations.push(decoration);
|
|
737
758
|
}
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
disposables.push(marker);
|
|
742
|
-
disposables.push(findResultDecoration.onRender((e) => this._applyStyles(e, options.matchBorder, false)));
|
|
743
|
-
disposables.push(findResultDecoration.onDispose(() => disposeArray(disposables)));
|
|
744
|
-
}
|
|
745
|
-
return findResultDecoration;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return decorations.length === 0 ? undefined : decorations;
|
|
746
762
|
}
|
|
747
763
|
}
|