Skip to content

Commit e9c547c

Browse files
authored
Merge pull request #5337 from Tyriar/tyriar/5336
Fix finding terms across wrapped lines
2 parents e29c236 + 0307e2c commit e9c547c

File tree

1 file changed

+57
-45
lines changed

1 file changed

+57
-45
lines changed

addons/addon-search/src/SearchAddon.ts

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ interface IHighlight extends IDisposable {
5858
match: ISearchResult;
5959
}
6060

61+
interface IMultiHighlight extends IDisposable {
62+
decorations: IDecoration[];
63+
match: ISearchResult;
64+
}
65+
6166
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
6267
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
6368
const DEFAULT_HIGHLIGHT_LIMIT = 1000;
@@ -67,7 +72,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
6772
private _cachedSearchTerm: string | undefined;
6873
private _highlightedLines: Set<number> = new Set();
6974
private _highlightDecorations: IHighlight[] = [];
70-
private _selectedDecoration: MutableDisposable<IHighlight> = this._register(new MutableDisposable());
75+
private _selectedDecoration: MutableDisposable<IMultiHighlight> = this._register(new MutableDisposable());
7176
private _highlightLimit: number;
7277
private _lastSearchOptions: ISearchOptions | undefined;
7378
private _highlightTimeout: number | undefined;
@@ -179,14 +184,20 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
179184
);
180185
}
181186
for (const match of searchResultsWithHighlight) {
182-
const decoration = this._createResultDecoration(match, searchOptions.decorations!);
183-
if (decoration) {
184-
this._highlightedLines.add(decoration.marker.line);
185-
this._highlightDecorations.push({ decoration, match, dispose() { decoration.dispose(); } });
187+
const decorations = this._createResultDecorations(match, searchOptions.decorations!, false);
188+
if (decorations) {
189+
for (const decoration of decorations) {
190+
this._storeDecoration(decoration, match);
191+
}
186192
}
187193
}
188194
}
189195

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+
190201
private _find(term: string, startRow: number, startCol: number, searchOptions?: ISearchOptions): ISearchResult | undefined {
191202
if (!this._terminal || !term || term.length === 0) {
192203
this._terminal?.clearSelection();
@@ -666,25 +677,9 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
666677
}
667678
terminal.select(result.col, result.row, result.size);
668679
if (options) {
669-
const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row);
670-
if (marker) {
671-
const decoration = terminal.registerDecoration({
672-
marker,
673-
x: result.col,
674-
width: result.size,
675-
backgroundColor: options.activeMatchBackground,
676-
layer: 'top',
677-
overviewRulerOptions: {
678-
color: options.activeMatchColorOverviewRuler
679-
}
680-
});
681-
if (decoration) {
682-
const disposables: IDisposable[] = [];
683-
disposables.push(marker);
684-
disposables.push(decoration.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true)));
685-
disposables.push(decoration.onDispose(() => dispose(disposables)));
686-
this._selectedDecoration.value = { decoration, match: result, dispose() { decoration.dispose(); } };
687-
}
680+
const decorations = this._createResultDecorations(result, options, true);
681+
if (decorations) {
682+
this._selectedDecoration.value = { decorations, match: result, dispose() { dispose(decorations); } };
688683
}
689684
}
690685

@@ -724,28 +719,45 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
724719
* @param options the options for the decoration
725720
* @returns the {@link IDecoration} or undefined if the marker has already been disposed of
726721
*/
727-
private _createResultDecoration(result: ISearchResult, options: ISearchDecorationOptions): IDecoration | undefined {
722+
private _createResultDecorations(result: ISearchResult, options: ISearchDecorationOptions, isActiveResult: boolean): IDecoration[] | undefined {
728723
const terminal = this._terminal!;
729-
const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row);
730-
if (!marker) {
731-
return undefined;
732-
}
733-
const findResultDecoration = terminal.registerDecoration({
734-
marker,
735-
x: result.col,
736-
width: result.size,
737-
backgroundColor: options.matchBackground,
738-
overviewRulerOptions: this._highlightedLines.has(marker.line) ? undefined : {
739-
color: options.matchOverviewRuler,
740-
position: 'center'
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);
741758
}
742-
});
743-
if (findResultDecoration) {
744-
const disposables: IDisposable[] = [];
745-
disposables.push(marker);
746-
disposables.push(findResultDecoration.onRender((e) => this._applyStyles(e, options.matchBorder, false)));
747-
disposables.push(findResultDecoration.onDispose(() => dispose(disposables)));
748-
}
749-
return findResultDecoration;
759+
}
760+
761+
return decorations.length === 0 ? undefined : decorations;
750762
}
751763
}

0 commit comments

Comments
 (0)