@xterm/addon-search 0.16.0-beta.124 → 0.16.0-beta.126
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 +12 -12
- package/lib/addon-search.mjs.map +4 -4
- package/package.json +3 -3
- package/src/SearchAddon.ts +16 -101
- package/src/SearchLineCache.ts +114 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xterm/addon-search",
|
|
3
|
-
"version": "0.16.0-beta.
|
|
3
|
+
"version": "0.16.0-beta.126",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "The xterm.js authors",
|
|
6
6
|
"url": "https://xtermjs.org/"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"start": "node ../../demo/start"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@xterm/xterm": "^5.6.0-beta.
|
|
25
|
+
"@xterm/xterm": "^5.6.0-beta.126"
|
|
26
26
|
},
|
|
27
|
-
"commit": "
|
|
27
|
+
"commit": "b12a9393f1cbb994877404ae0ebf62d6250f83ba"
|
|
28
28
|
}
|
package/src/SearchAddon.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
import type { Terminal, IDisposable, ITerminalAddon, IDecoration } from '@xterm/xterm';
|
|
7
7
|
import type { SearchAddon as ISearchApi, ISearchOptions, ISearchDecorationOptions } from '@xterm/addon-search';
|
|
8
8
|
import { Emitter, Event } from 'vs/base/common/event';
|
|
9
|
-
import {
|
|
9
|
+
import { Disposable, dispose, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
|
10
|
+
import { disposableTimeout } from 'vs/base/common/async';
|
|
11
|
+
import { SearchLineCache } from './SearchLineCache';
|
|
10
12
|
|
|
11
13
|
interface IInternalSearchOptions {
|
|
12
14
|
noScroll?: boolean;
|
|
@@ -33,17 +35,6 @@ export interface ISearchResult {
|
|
|
33
35
|
size: number;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
type LineCacheEntry = [
|
|
37
|
-
/**
|
|
38
|
-
* The string representation of a line (as opposed to the buffer cell representation).
|
|
39
|
-
*/
|
|
40
|
-
lineAsString: string,
|
|
41
|
-
/**
|
|
42
|
-
* The offsets where each line starts when the entry describes a wrapped line.
|
|
43
|
-
*/
|
|
44
|
-
lineOffsets: number[]
|
|
45
|
-
];
|
|
46
|
-
|
|
47
38
|
interface IHighlight extends IDisposable {
|
|
48
39
|
decoration: IDecoration;
|
|
49
40
|
match: ISearchResult;
|
|
@@ -65,12 +56,6 @@ const enum Constants {
|
|
|
65
56
|
*/
|
|
66
57
|
NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?',
|
|
67
58
|
|
|
68
|
-
/**
|
|
69
|
-
* Time-to-live for cached search results in milliseconds. After this duration, cached search
|
|
70
|
-
* results will be invalidated to ensure they remain consistent with terminal content changes.
|
|
71
|
-
*/
|
|
72
|
-
LINES_CACHE_TIME_TO_LIVE = 15000,
|
|
73
|
-
|
|
74
59
|
/**
|
|
75
60
|
* Default maximum number of search results to highlight simultaneously. This limit prevents
|
|
76
61
|
* performance degradation when searching for very common terms that would result in excessive
|
|
@@ -85,18 +70,11 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
|
|
|
85
70
|
private _highlightedLines: Set<number> = new Set();
|
|
86
71
|
private _highlightDecorations: IHighlight[] = [];
|
|
87
72
|
private _searchResultsWithHighlight: ISearchResult[] = [];
|
|
88
|
-
private _selectedDecoration
|
|
73
|
+
private _selectedDecoration = this._register(new MutableDisposable<IMultiHighlight>());
|
|
89
74
|
private _highlightLimit: number;
|
|
90
75
|
private _lastSearchOptions: ISearchOptions | undefined;
|
|
91
|
-
private _highlightTimeout
|
|
92
|
-
|
|
93
|
-
* translateBufferLineToStringWithWrap is a fairly expensive call.
|
|
94
|
-
* We memoize the calls into an array that has a time based ttl.
|
|
95
|
-
* _linesCache is also invalidated when the terminal cursor moves.
|
|
96
|
-
*/
|
|
97
|
-
private _linesCache: LineCacheEntry[] | undefined;
|
|
98
|
-
private _linesCacheTimeoutId = 0;
|
|
99
|
-
private _linesCacheDisposables = new MutableDisposable();
|
|
76
|
+
private _highlightTimeout = this._register(new MutableDisposable<IDisposable>());
|
|
77
|
+
private _lineCache = this._register(new MutableDisposable<SearchLineCache>());
|
|
100
78
|
|
|
101
79
|
private readonly _onDidChangeResults = this._register(new Emitter<ISearchResultChangeEvent>());
|
|
102
80
|
public get onDidChangeResults(): Event<ISearchResultChangeEvent> { return this._onDidChangeResults.event; }
|
|
@@ -109,17 +87,16 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
|
|
|
109
87
|
|
|
110
88
|
public activate(terminal: Terminal): void {
|
|
111
89
|
this._terminal = terminal;
|
|
90
|
+
this._lineCache.value = new SearchLineCache(terminal);
|
|
112
91
|
this._register(this._terminal.onWriteParsed(() => this._updateMatches()));
|
|
113
92
|
this._register(this._terminal.onResize(() => this._updateMatches()));
|
|
114
93
|
this._register(toDisposable(() => this.clearDecorations()));
|
|
115
94
|
}
|
|
116
95
|
|
|
117
96
|
private _updateMatches(): void {
|
|
118
|
-
|
|
119
|
-
window.clearTimeout(this._highlightTimeout);
|
|
120
|
-
}
|
|
97
|
+
this._highlightTimeout.clear();
|
|
121
98
|
if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) {
|
|
122
|
-
this._highlightTimeout =
|
|
99
|
+
this._highlightTimeout.value = disposableTimeout(() => {
|
|
123
100
|
const term = this._cachedSearchTerm;
|
|
124
101
|
this._cachedSearchTerm = undefined;
|
|
125
102
|
this.findPrevious(term!, { ...this._lastSearchOptions, incremental: true }, { noScroll: true });
|
|
@@ -223,7 +200,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
|
|
|
223
200
|
|
|
224
201
|
let result: ISearchResult | undefined = undefined;
|
|
225
202
|
|
|
226
|
-
this.
|
|
203
|
+
this._lineCache.value!.initLinesCache();
|
|
227
204
|
|
|
228
205
|
const searchPosition: ISearchPosition = {
|
|
229
206
|
startRow,
|
|
@@ -271,7 +248,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
|
|
|
271
248
|
}
|
|
272
249
|
}
|
|
273
250
|
|
|
274
|
-
this.
|
|
251
|
+
this._lineCache.value!.initLinesCache();
|
|
275
252
|
|
|
276
253
|
const searchPosition: ISearchPosition = {
|
|
277
254
|
startRow,
|
|
@@ -392,7 +369,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
|
|
|
392
369
|
let startCol = this._terminal.cols;
|
|
393
370
|
const isReverseSearch = true;
|
|
394
371
|
|
|
395
|
-
this.
|
|
372
|
+
this._lineCache.value!.initLinesCache();
|
|
396
373
|
const searchPosition: ISearchPosition = {
|
|
397
374
|
startRow,
|
|
398
375
|
startCol
|
|
@@ -443,32 +420,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
|
|
|
443
420
|
return this._selectResult(result, searchOptions?.decorations, internalSearchOptions?.noScroll);
|
|
444
421
|
}
|
|
445
422
|
|
|
446
|
-
/**
|
|
447
|
-
* Sets up a line cache with a ttl
|
|
448
|
-
*/
|
|
449
|
-
private _initLinesCache(): void {
|
|
450
|
-
const terminal = this._terminal!;
|
|
451
|
-
if (!this._linesCache) {
|
|
452
|
-
this._linesCache = new Array(terminal.buffer.active.length);
|
|
453
|
-
this._linesCacheDisposables.value = combinedDisposable(
|
|
454
|
-
terminal.onLineFeed(() => this._destroyLinesCache()),
|
|
455
|
-
terminal.onCursorMove(() => this._destroyLinesCache()),
|
|
456
|
-
terminal.onResize(() => this._destroyLinesCache())
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
window.clearTimeout(this._linesCacheTimeoutId);
|
|
461
|
-
this._linesCacheTimeoutId = window.setTimeout(() => this._destroyLinesCache(), Constants.LINES_CACHE_TIME_TO_LIVE);
|
|
462
|
-
}
|
|
463
423
|
|
|
464
|
-
private _destroyLinesCache(): void {
|
|
465
|
-
this._linesCache = undefined;
|
|
466
|
-
this._linesCacheDisposables.clear();
|
|
467
|
-
if (this._linesCacheTimeoutId) {
|
|
468
|
-
window.clearTimeout(this._linesCacheTimeoutId);
|
|
469
|
-
this._linesCacheTimeoutId = 0;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
424
|
|
|
473
425
|
/**
|
|
474
426
|
* A found substring is a whole word if it doesn't have an alphanumeric character directly
|
|
@@ -513,12 +465,10 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
|
|
|
513
465
|
searchPosition.startCol += terminal.cols;
|
|
514
466
|
return this._findInLine(term, searchPosition, searchOptions);
|
|
515
467
|
}
|
|
516
|
-
let cache = this.
|
|
468
|
+
let cache = this._lineCache.value!.getLineFromCache(row);
|
|
517
469
|
if (!cache) {
|
|
518
|
-
cache = this.
|
|
519
|
-
|
|
520
|
-
this._linesCache[row] = cache;
|
|
521
|
-
}
|
|
470
|
+
cache = this._lineCache.value!.translateBufferLineToStringWithWrap(row, true);
|
|
471
|
+
this._lineCache.value!.setLineInCache(row, cache);
|
|
522
472
|
}
|
|
523
473
|
const [stringLine, offsets] = cache;
|
|
524
474
|
|
|
@@ -639,42 +589,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
|
|
|
639
589
|
return offset;
|
|
640
590
|
}
|
|
641
591
|
|
|
642
|
-
|
|
643
|
-
* Translates a buffer line to a string, including subsequent lines if they are wraps.
|
|
644
|
-
* Wide characters will count as two columns in the resulting string. This
|
|
645
|
-
* function is useful for getting the actual text underneath the raw selection
|
|
646
|
-
* position.
|
|
647
|
-
* @param lineIndex The index of the line being translated.
|
|
648
|
-
* @param trimRight Whether to trim whitespace to the right.
|
|
649
|
-
*/
|
|
650
|
-
private _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): LineCacheEntry {
|
|
651
|
-
const terminal = this._terminal!;
|
|
652
|
-
const strings = [];
|
|
653
|
-
const lineOffsets = [0];
|
|
654
|
-
let line = terminal.buffer.active.getLine(lineIndex);
|
|
655
|
-
while (line) {
|
|
656
|
-
const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
|
|
657
|
-
const lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
|
658
|
-
let string = line.translateToString(!lineWrapsToNext && trimRight);
|
|
659
|
-
if (lineWrapsToNext && nextLine) {
|
|
660
|
-
const lastCell = line.getCell(line.length - 1);
|
|
661
|
-
const lastCellIsNull = lastCell && lastCell.getCode() === 0 && lastCell.getWidth() === 1;
|
|
662
|
-
// a wide character wrapped to the next line
|
|
663
|
-
if (lastCellIsNull && nextLine.getCell(0)?.getWidth() === 2) {
|
|
664
|
-
string = string.slice(0, -1);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
strings.push(string);
|
|
668
|
-
if (lineWrapsToNext) {
|
|
669
|
-
lineOffsets.push(lineOffsets[lineOffsets.length - 1] + string.length);
|
|
670
|
-
} else {
|
|
671
|
-
break;
|
|
672
|
-
}
|
|
673
|
-
lineIndex++;
|
|
674
|
-
line = nextLine;
|
|
675
|
-
}
|
|
676
|
-
return [strings.join(''), lineOffsets];
|
|
677
|
-
}
|
|
592
|
+
|
|
678
593
|
|
|
679
594
|
/**
|
|
680
595
|
* Selects and scrolls to a result.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
3
|
+
* @license MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Terminal } from '@xterm/xterm';
|
|
7
|
+
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
|
8
|
+
import { disposableTimeout } from 'vs/base/common/async';
|
|
9
|
+
|
|
10
|
+
export type LineCacheEntry = [
|
|
11
|
+
/**
|
|
12
|
+
* The string representation of a line (as opposed to the buffer cell representation).
|
|
13
|
+
*/
|
|
14
|
+
lineAsString: string,
|
|
15
|
+
/**
|
|
16
|
+
* The offsets where each line starts when the entry describes a wrapped line.
|
|
17
|
+
*/
|
|
18
|
+
lineOffsets: number[]
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Configuration constants for the search line cache functionality.
|
|
23
|
+
*/
|
|
24
|
+
const enum Constants {
|
|
25
|
+
/**
|
|
26
|
+
* Time-to-live for cached search results in milliseconds. After this duration, cached search
|
|
27
|
+
* results will be invalidated to ensure they remain consistent with terminal content changes.
|
|
28
|
+
*/
|
|
29
|
+
LINES_CACHE_TIME_TO_LIVE = 15000
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class SearchLineCache extends Disposable {
|
|
33
|
+
/**
|
|
34
|
+
* translateBufferLineToStringWithWrap is a fairly expensive call.
|
|
35
|
+
* We memoize the calls into an array that has a time based ttl.
|
|
36
|
+
* _linesCache is also invalidated when the terminal cursor moves.
|
|
37
|
+
*/
|
|
38
|
+
private _linesCache: LineCacheEntry[] | undefined;
|
|
39
|
+
private _linesCacheTimeout = this._register(new MutableDisposable());
|
|
40
|
+
private _linesCacheDisposables = this._register(new MutableDisposable());
|
|
41
|
+
|
|
42
|
+
constructor(private _terminal: Terminal) {
|
|
43
|
+
super();
|
|
44
|
+
this._register(toDisposable(() => this._destroyLinesCache()));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sets up a line cache with a ttl
|
|
49
|
+
*/
|
|
50
|
+
public initLinesCache(): void {
|
|
51
|
+
if (!this._linesCache) {
|
|
52
|
+
this._linesCache = new Array(this._terminal.buffer.active.length);
|
|
53
|
+
this._linesCacheDisposables.value = combinedDisposable(
|
|
54
|
+
this._terminal.onLineFeed(() => this._destroyLinesCache()),
|
|
55
|
+
this._terminal.onCursorMove(() => this._destroyLinesCache()),
|
|
56
|
+
this._terminal.onResize(() => this._destroyLinesCache())
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this._linesCacheTimeout.value = disposableTimeout(() => this._destroyLinesCache(), Constants.LINES_CACHE_TIME_TO_LIVE);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private _destroyLinesCache(): void {
|
|
64
|
+
this._linesCache = undefined;
|
|
65
|
+
this._linesCacheDisposables.clear();
|
|
66
|
+
this._linesCacheTimeout.clear();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public getLineFromCache(row: number): LineCacheEntry | undefined {
|
|
70
|
+
return this._linesCache?.[row];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public setLineInCache(row: number, entry: LineCacheEntry): void {
|
|
74
|
+
if (this._linesCache) {
|
|
75
|
+
this._linesCache[row] = entry;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Translates a buffer line to a string, including subsequent lines if they are wraps.
|
|
81
|
+
* Wide characters will count as two columns in the resulting string. This
|
|
82
|
+
* function is useful for getting the actual text underneath the raw selection
|
|
83
|
+
* position.
|
|
84
|
+
* @param lineIndex The index of the line being translated.
|
|
85
|
+
* @param trimRight Whether to trim whitespace to the right.
|
|
86
|
+
*/
|
|
87
|
+
public translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): LineCacheEntry {
|
|
88
|
+
const strings = [];
|
|
89
|
+
const lineOffsets = [0];
|
|
90
|
+
let line = this._terminal.buffer.active.getLine(lineIndex);
|
|
91
|
+
while (line) {
|
|
92
|
+
const nextLine = this._terminal.buffer.active.getLine(lineIndex + 1);
|
|
93
|
+
const lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
|
94
|
+
let string = line.translateToString(!lineWrapsToNext && trimRight);
|
|
95
|
+
if (lineWrapsToNext && nextLine) {
|
|
96
|
+
const lastCell = line.getCell(line.length - 1);
|
|
97
|
+
const lastCellIsNull = lastCell && lastCell.getCode() === 0 && lastCell.getWidth() === 1;
|
|
98
|
+
// a wide character wrapped to the next line
|
|
99
|
+
if (lastCellIsNull && nextLine.getCell(0)?.getWidth() === 2) {
|
|
100
|
+
string = string.slice(0, -1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
strings.push(string);
|
|
104
|
+
if (lineWrapsToNext) {
|
|
105
|
+
lineOffsets.push(lineOffsets[lineOffsets.length - 1] + string.length);
|
|
106
|
+
} else {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
lineIndex++;
|
|
110
|
+
line = nextLine;
|
|
111
|
+
}
|
|
112
|
+
return [strings.join(''), lineOffsets];
|
|
113
|
+
}
|
|
114
|
+
}
|