Skip to content

Commit 302ff6a

Browse files
authored
Merge pull request #282539 from microsoft/benibenj/fixFreeze
Fix freeze issue by updating data structures
2 parents 618725e + 8bf2202 commit 302ff6a

File tree

2 files changed

+47
-23
lines changed

2 files changed

+47
-23
lines changed

src/vs/editor/common/viewLayout/lineDecorations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class LineDecoration {
2828
);
2929
}
3030

31-
public static equalsArr(a: LineDecoration[], b: LineDecoration[]): boolean {
31+
public static equalsArr(a: readonly LineDecoration[], b: readonly LineDecoration[]): boolean {
3232
const aLen = a.length;
3333
const bLen = b.length;
3434
if (aLen !== bLen) {

src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { renderIcon } from '../../../../../../base/browser/ui/iconLabel/iconLabe
88
import { Codicon } from '../../../../../../base/common/codicons.js';
99
import { Emitter, Event } from '../../../../../../base/common/event.js';
1010
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js';
11-
import { IObservable, autorun, autorunWithStore, constObservable, derived, observableSignalFromEvent, observableValue } from '../../../../../../base/common/observable.js';
11+
import { IObservable, autorun, autorunWithStore, constObservable, derived, derivedOpts, observableSignalFromEvent, observableValue } from '../../../../../../base/common/observable.js';
1212
import * as strings from '../../../../../../base/common/strings.js';
1313
import { applyFontInfo } from '../../../../../browser/config/domFontInfo.js';
1414
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidgetPosition, IViewZoneChangeAccessor, MouseTargetType } from '../../../../../browser/editorBrowser.js';
@@ -34,7 +34,8 @@ import { CodeEditorWidget } from '../../../../../browser/widget/codeEditor/codeE
3434
import { TokenWithTextArray } from '../../../../../common/tokens/tokenWithTextArray.js';
3535
import { InlineCompletionViewData } from '../inlineEdits/inlineEditsViewInterface.js';
3636
import { InlineDecorationType } from '../../../../../common/viewModel/inlineDecorations.js';
37-
import { sum } from '../../../../../../base/common/arrays.js';
37+
import { equals, sum } from '../../../../../../base/common/arrays.js';
38+
import { equalsIfDefined, IEquatable, itemEquals } from '../../../../../../base/common/equals.js';
3839

3940
export interface IGhostTextWidgetData {
4041
readonly ghostText: GhostText | GhostTextReplacement;
@@ -102,14 +103,14 @@ export class GhostTextView extends Disposable {
102103
this._additionalLinesWidget = this._register(
103104
new AdditionalLinesWidget(
104105
this._editor,
105-
derived(reader => {
106+
derivedOpts({ owner: this, equalsFn: equalsIfDefined(itemEquals()) }, reader => {
106107
/** @description lines */
107108
const uiState = this._state.read(reader);
108-
return uiState ? {
109-
lineNumber: uiState.lineNumber,
110-
additionalLines: uiState.additionalLines,
111-
minReservedLineCount: uiState.additionalReservedLineCount,
112-
} : undefined;
109+
return uiState ? new AdditionalLinesData(
110+
uiState.lineNumber,
111+
uiState.additionalLines,
112+
uiState.additionalReservedLineCount,
113+
) : undefined;
113114
}),
114115
this._shouldKeepCursorStable,
115116
this._isClickable
@@ -243,10 +244,10 @@ export class GhostTextView extends Disposable {
243244
const existingContent = t.slice(additionalLinesOriginalSuffix.columnRange.toZeroBasedOffsetRange());
244245
content = TokenWithTextArray.fromLineTokens(content).append(existingContent).toLineTokens(content.languageIdCodec);
245246
}
246-
return {
247+
return new LineData(
247248
content,
248-
decorations: l.decorations,
249-
};
249+
l.decorations,
250+
);
250251
});
251252

252253
const cursorColumn = this._editor.getSelection()?.getStartPosition().column!;
@@ -420,6 +421,24 @@ function computeGhostTextViewData(ghostText: GhostText | GhostTextReplacement, t
420421
};
421422
}
422423

424+
class AdditionalLinesData implements IEquatable<AdditionalLinesData> {
425+
constructor(
426+
public readonly lineNumber: number,
427+
public readonly additionalLines: readonly LineData[],
428+
public readonly minReservedLineCount: number,
429+
) { }
430+
431+
equals(other: AdditionalLinesData): boolean {
432+
if (this.lineNumber !== other.lineNumber) {
433+
return false;
434+
}
435+
if (this.minReservedLineCount !== other.minReservedLineCount) {
436+
return false;
437+
}
438+
return equals(this.additionalLines, other.additionalLines, itemEquals());
439+
}
440+
}
441+
423442
export class AdditionalLinesWidget extends Disposable {
424443
private _viewZoneInfo: { viewZoneId: string; heightInLines: number; lineNumber: number } | undefined;
425444
public get viewZoneId(): string | undefined { return this._viewZoneInfo?.viewZoneId; }
@@ -440,11 +459,7 @@ export class AdditionalLinesWidget extends Disposable {
440459

441460
constructor(
442461
private readonly _editor: ICodeEditor,
443-
private readonly _lines: IObservable<{
444-
lineNumber: number;
445-
additionalLines: LineData[];
446-
minReservedLineCount: number;
447-
} | undefined>,
462+
private readonly _lines: IObservable<AdditionalLinesData | undefined>,
448463
private readonly _shouldKeepCursorStable: boolean,
449464
private readonly _isClickable: boolean,
450465
) {
@@ -500,7 +515,7 @@ export class AdditionalLinesWidget extends Disposable {
500515
});
501516
}
502517

503-
private updateLines(lineNumber: number, additionalLines: LineData[], minReservedLineCount: number): void {
518+
private updateLines(lineNumber: number, additionalLines: readonly LineData[], minReservedLineCount: number): void {
504519
const textModel = this._editor.getModel();
505520
if (!textModel) {
506521
return;
@@ -581,12 +596,21 @@ function isTargetGhostText(target: EventTarget | null): boolean {
581596
return isHTMLElement(target) && target.classList.contains(GHOST_TEXT_CLASS_NAME);
582597
}
583598

584-
export interface LineData {
585-
content: LineTokens; // Must not contain a linebreak!
586-
decorations: LineDecoration[];
599+
export class LineData implements IEquatable<LineData> {
600+
constructor(
601+
public readonly content: LineTokens, // Must not contain a linebreak!
602+
public readonly decorations: readonly LineDecoration[]
603+
) { }
604+
605+
equals(other: LineData): boolean {
606+
if (!this.content.equals(other.content)) {
607+
return false;
608+
}
609+
return LineDecoration.equalsArr(this.decorations, other.decorations);
610+
}
587611
}
588612

589-
function renderLines(domNode: HTMLElement, tabSize: number, lines: LineData[], opts: IComputedEditorOptions, isClickable: boolean): void {
613+
function renderLines(domNode: HTMLElement, tabSize: number, lines: readonly LineData[], opts: IComputedEditorOptions, isClickable: boolean): void {
590614
const disableMonospaceOptimizations = opts.get(EditorOption.disableMonospaceOptimizations);
591615
const stopRenderingLineAfter = opts.get(EditorOption.stopRenderingLineAfter);
592616
// To avoid visual confusion, we don't want to render visible whitespace
@@ -625,7 +649,7 @@ function renderLines(domNode: HTMLElement, tabSize: number, lines: LineData[], o
625649
containsRTL,
626650
0,
627651
lineTokens,
628-
lineData.decorations,
652+
lineData.decorations.slice(),
629653
tabSize,
630654
0,
631655
fontInfo.spaceWidth,

0 commit comments

Comments
 (0)