@xterm/addon-search 0.16.0-beta.12 → 0.16.0-beta.120
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 +87 -65
- package/typings/addon-search.d.ts +16 -2
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, Event } 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;
|
|
@@ -31,6 +31,11 @@ export interface ISearchPosition {
|
|
|
31
31
|
startRow: number;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export interface ISearchResultChangeEvent {
|
|
35
|
+
resultIndex: number;
|
|
36
|
+
resultCount: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
export interface ISearchAddonOptions {
|
|
35
40
|
highlightLimit: number;
|
|
36
41
|
}
|
|
@@ -58,6 +63,11 @@ interface IHighlight extends IDisposable {
|
|
|
58
63
|
match: ISearchResult;
|
|
59
64
|
}
|
|
60
65
|
|
|
66
|
+
interface IMultiHighlight extends IDisposable {
|
|
67
|
+
decorations: IDecoration[];
|
|
68
|
+
match: ISearchResult;
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
|
|
62
72
|
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
|
|
63
73
|
const DEFAULT_HIGHLIGHT_LIMIT = 1000;
|
|
@@ -67,7 +77,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
67
77
|
private _cachedSearchTerm: string | undefined;
|
|
68
78
|
private _highlightedLines: Set<number> = new Set();
|
|
69
79
|
private _highlightDecorations: IHighlight[] = [];
|
|
70
|
-
private
|
|
80
|
+
private _searchResultsWithHighlight: ISearchResult[] = [];
|
|
81
|
+
private _selectedDecoration: MutableDisposable<IMultiHighlight> = this._register(new MutableDisposable());
|
|
71
82
|
private _highlightLimit: number;
|
|
72
83
|
private _lastSearchOptions: ISearchOptions | undefined;
|
|
73
84
|
private _highlightTimeout: number | undefined;
|
|
@@ -80,8 +91,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
80
91
|
private _linesCacheTimeoutId = 0;
|
|
81
92
|
private _linesCacheDisposables = new MutableDisposable();
|
|
82
93
|
|
|
83
|
-
private readonly _onDidChangeResults = this.
|
|
84
|
-
public
|
|
94
|
+
private readonly _onDidChangeResults = this._register(new Emitter<ISearchResultChangeEvent>());
|
|
95
|
+
public get onDidChangeResults(): Event<ISearchResultChangeEvent> { return this._onDidChangeResults.event; }
|
|
85
96
|
|
|
86
97
|
constructor(options?: Partial<ISearchAddonOptions>) {
|
|
87
98
|
super();
|
|
@@ -91,9 +102,9 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
91
102
|
|
|
92
103
|
public activate(terminal: Terminal): void {
|
|
93
104
|
this._terminal = terminal;
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
this.
|
|
105
|
+
this._register(this._terminal.onWriteParsed(() => this._updateMatches()));
|
|
106
|
+
this._register(this._terminal.onResize(() => this._updateMatches()));
|
|
107
|
+
this._register(toDisposable(() => this.clearDecorations()));
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
private _updateMatches(): void {
|
|
@@ -111,8 +122,9 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
111
122
|
|
|
112
123
|
public clearDecorations(retainCachedSearchTerm?: boolean): void {
|
|
113
124
|
this._selectedDecoration.clear();
|
|
114
|
-
|
|
125
|
+
dispose(this._highlightDecorations);
|
|
115
126
|
this._highlightDecorations = [];
|
|
127
|
+
this._searchResultsWithHighlight = [];
|
|
116
128
|
this._highlightedLines.clear();
|
|
117
129
|
if (!retainCachedSearchTerm) {
|
|
118
130
|
this._cachedSearchTerm = undefined;
|
|
@@ -162,15 +174,14 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
162
174
|
// new search, clear out the old decorations
|
|
163
175
|
this.clearDecorations(true);
|
|
164
176
|
|
|
165
|
-
const searchResultsWithHighlight: ISearchResult[] = [];
|
|
166
177
|
let prevResult: ISearchResult | undefined = undefined;
|
|
167
178
|
let result = this._find(term, 0, 0, searchOptions);
|
|
168
179
|
while (result && (prevResult?.row !== result.row || prevResult?.col !== result.col)) {
|
|
169
|
-
if (
|
|
180
|
+
if (this._searchResultsWithHighlight.length >= this._highlightLimit) {
|
|
170
181
|
break;
|
|
171
182
|
}
|
|
172
183
|
prevResult = result;
|
|
173
|
-
|
|
184
|
+
this._searchResultsWithHighlight.push(prevResult);
|
|
174
185
|
result = this._find(
|
|
175
186
|
term,
|
|
176
187
|
prevResult.col + prevResult.term.length >= this._terminal.cols ? prevResult.row + 1 : prevResult.row,
|
|
@@ -178,15 +189,21 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
178
189
|
searchOptions
|
|
179
190
|
);
|
|
180
191
|
}
|
|
181
|
-
for (const match of
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
192
|
+
for (const match of this._searchResultsWithHighlight) {
|
|
193
|
+
const decorations = this._createResultDecorations(match, searchOptions.decorations!, false);
|
|
194
|
+
if (decorations) {
|
|
195
|
+
for (const decoration of decorations) {
|
|
196
|
+
this._storeDecoration(decoration, match);
|
|
197
|
+
}
|
|
186
198
|
}
|
|
187
199
|
}
|
|
188
200
|
}
|
|
189
201
|
|
|
202
|
+
private _storeDecoration(decoration: IDecoration, match: ISearchResult): void {
|
|
203
|
+
this._highlightedLines.add(decoration.marker.line);
|
|
204
|
+
this._highlightDecorations.push({ decoration, match, dispose() { decoration.dispose(); } });
|
|
205
|
+
}
|
|
206
|
+
|
|
190
207
|
private _find(term: string, startRow: number, startCol: number, searchOptions?: ISearchOptions): ISearchResult | undefined {
|
|
191
208
|
if (!this._terminal || !term || term.length === 0) {
|
|
192
209
|
this._terminal?.clearSelection();
|
|
@@ -339,15 +356,15 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
339
356
|
let resultIndex = -1;
|
|
340
357
|
if (this._selectedDecoration.value) {
|
|
341
358
|
const selectedMatch = this._selectedDecoration.value.match;
|
|
342
|
-
for (let i = 0; i < this.
|
|
343
|
-
const match = this.
|
|
359
|
+
for (let i = 0; i < this._searchResultsWithHighlight.length; i++) {
|
|
360
|
+
const match = this._searchResultsWithHighlight[i];
|
|
344
361
|
if (match.row === selectedMatch.row && match.col === selectedMatch.col && match.size === selectedMatch.size) {
|
|
345
362
|
resultIndex = i;
|
|
346
363
|
break;
|
|
347
364
|
}
|
|
348
365
|
}
|
|
349
366
|
}
|
|
350
|
-
this._onDidChangeResults.fire({ resultIndex, resultCount: this.
|
|
367
|
+
this._onDidChangeResults.fire({ resultIndex, resultCount: this._searchResultsWithHighlight.length });
|
|
351
368
|
}
|
|
352
369
|
}
|
|
353
370
|
|
|
@@ -426,11 +443,11 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
426
443
|
const terminal = this._terminal!;
|
|
427
444
|
if (!this._linesCache) {
|
|
428
445
|
this._linesCache = new Array(terminal.buffer.active.length);
|
|
429
|
-
this._linesCacheDisposables.value =
|
|
446
|
+
this._linesCacheDisposables.value = combinedDisposable(
|
|
430
447
|
terminal.onLineFeed(() => this._destroyLinesCache()),
|
|
431
448
|
terminal.onCursorMove(() => this._destroyLinesCache()),
|
|
432
449
|
terminal.onResize(() => this._destroyLinesCache())
|
|
433
|
-
|
|
450
|
+
);
|
|
434
451
|
}
|
|
435
452
|
|
|
436
453
|
window.clearTimeout(this._linesCacheTimeoutId);
|
|
@@ -499,12 +516,16 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
499
516
|
const [stringLine, offsets] = cache;
|
|
500
517
|
|
|
501
518
|
const offset = this._bufferColsToStringOffset(row, col);
|
|
502
|
-
|
|
503
|
-
|
|
519
|
+
let searchTerm = term;
|
|
520
|
+
let searchStringLine = stringLine;
|
|
521
|
+
if (!searchOptions.regex) {
|
|
522
|
+
searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
|
|
523
|
+
searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
|
|
524
|
+
}
|
|
504
525
|
|
|
505
526
|
let resultIndex = -1;
|
|
506
527
|
if (searchOptions.regex) {
|
|
507
|
-
const searchRegex = RegExp(searchTerm, 'g');
|
|
528
|
+
const searchRegex = RegExp(searchTerm, searchOptions.caseSensitive ? 'g' : 'gi');
|
|
508
529
|
let foundTerm: RegExpExecArray | null;
|
|
509
530
|
if (isReverseSearch) {
|
|
510
531
|
// This loop will get the resultIndex of the _last_ regex match in the range 0..offset
|
|
@@ -662,25 +683,9 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
662
683
|
}
|
|
663
684
|
terminal.select(result.col, result.row, result.size);
|
|
664
685
|
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
|
-
}
|
|
686
|
+
const decorations = this._createResultDecorations(result, options, true);
|
|
687
|
+
if (decorations) {
|
|
688
|
+
this._selectedDecoration.value = { decorations, match: result, dispose() { dispose(decorations); } };
|
|
684
689
|
}
|
|
685
690
|
}
|
|
686
691
|
|
|
@@ -720,28 +725,45 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
|
|
|
720
725
|
* @param options the options for the decoration
|
|
721
726
|
* @returns the {@link IDecoration} or undefined if the marker has already been disposed of
|
|
722
727
|
*/
|
|
723
|
-
private
|
|
728
|
+
private _createResultDecorations(result: ISearchResult, options: ISearchDecorationOptions, isActiveResult: boolean): IDecoration[] | undefined {
|
|
724
729
|
const terminal = this._terminal!;
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
730
|
+
|
|
731
|
+
// Gather decoration ranges for this match as it could wrap
|
|
732
|
+
const decorationRanges: [number, number, number][] = [];
|
|
733
|
+
let currentCol = result.col;
|
|
734
|
+
let remainingSize = result.size;
|
|
735
|
+
let markerOffset = -terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row;
|
|
736
|
+
while (remainingSize > 0) {
|
|
737
|
+
const amountThisRow = Math.min(terminal.cols - currentCol, remainingSize);
|
|
738
|
+
decorationRanges.push([markerOffset, currentCol, amountThisRow]);
|
|
739
|
+
currentCol = 0;
|
|
740
|
+
remainingSize -= amountThisRow;
|
|
741
|
+
markerOffset++;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Create the decorations
|
|
745
|
+
const decorations: IDecoration[] = [];
|
|
746
|
+
for (const range of decorationRanges) {
|
|
747
|
+
const marker = terminal.registerMarker(range[0]);
|
|
748
|
+
const decoration = terminal.registerDecoration({
|
|
749
|
+
marker,
|
|
750
|
+
x: range[1],
|
|
751
|
+
width: range[2],
|
|
752
|
+
backgroundColor: isActiveResult ? options.activeMatchBackground : options.matchBackground,
|
|
753
|
+
overviewRulerOptions: this._highlightedLines.has(marker.line) ? undefined : {
|
|
754
|
+
color: isActiveResult ? options.activeMatchColorOverviewRuler : options.matchOverviewRuler,
|
|
755
|
+
position: 'center'
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
if (decoration) {
|
|
759
|
+
const disposables: IDisposable[] = [];
|
|
760
|
+
disposables.push(marker);
|
|
761
|
+
disposables.push(decoration.onRender((e) => this._applyStyles(e, isActiveResult ? options.activeMatchBorder : options.matchBorder, false)));
|
|
762
|
+
disposables.push(decoration.onDispose(() => dispose(disposables)));
|
|
763
|
+
decorations.push(decoration);
|
|
737
764
|
}
|
|
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;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return decorations.length === 0 ? undefined : decorations;
|
|
746
768
|
}
|
|
747
769
|
}
|
|
@@ -75,6 +75,21 @@ declare module '@xterm/addon-search' {
|
|
|
75
75
|
activeMatchColorOverviewRuler: string;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Event data fired when search results change.
|
|
80
|
+
*/
|
|
81
|
+
export interface ISearchResultChangeEvent {
|
|
82
|
+
/**
|
|
83
|
+
* The index of the currently active result, -1 when the threshold of matches is exceeded.
|
|
84
|
+
*/
|
|
85
|
+
resultIndex: number;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The total number of search results found.
|
|
89
|
+
*/
|
|
90
|
+
resultCount: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
/**
|
|
79
94
|
* Options for the search addon.
|
|
80
95
|
*/
|
|
@@ -139,8 +154,7 @@ declare module '@xterm/addon-search' {
|
|
|
139
154
|
/**
|
|
140
155
|
* When decorations are enabled, fires when
|
|
141
156
|
* the search results change.
|
|
142
|
-
* @returns -1 for resultIndex when the threshold of matches is exceeded.
|
|
143
157
|
*/
|
|
144
|
-
readonly onDidChangeResults: IEvent<
|
|
158
|
+
readonly onDidChangeResults: IEvent<ISearchResultChangeEvent>;
|
|
145
159
|
}
|
|
146
160
|
}
|