From dff025af9f8663ef43560259f24bbdc5bbc5fcf6 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Wed, 10 Dec 2025 18:14:25 +0100 Subject: [PATCH] fix freeze --- .../common/viewLayout/lineDecorations.ts | 2 +- .../browser/view/ghostText/ghostTextView.ts | 68 +++++++++++++------ 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/common/viewLayout/lineDecorations.ts b/src/vs/editor/common/viewLayout/lineDecorations.ts index 7641e62c859..3439b945aac 100644 --- a/src/vs/editor/common/viewLayout/lineDecorations.ts +++ b/src/vs/editor/common/viewLayout/lineDecorations.ts @@ -28,7 +28,7 @@ export class LineDecoration { ); } - public static equalsArr(a: LineDecoration[], b: LineDecoration[]): boolean { + public static equalsArr(a: readonly LineDecoration[], b: readonly LineDecoration[]): boolean { const aLen = a.length; const bLen = b.length; if (aLen !== bLen) { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts index ca352ca8e03..0782ad3a748 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts @@ -8,7 +8,7 @@ import { renderIcon } from '../../../../../../base/browser/ui/iconLabel/iconLabe import { Codicon } from '../../../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; -import { IObservable, autorun, autorunWithStore, constObservable, derived, observableSignalFromEvent, observableValue } from '../../../../../../base/common/observable.js'; +import { IObservable, autorun, autorunWithStore, constObservable, derived, derivedOpts, observableSignalFromEvent, observableValue } from '../../../../../../base/common/observable.js'; import * as strings from '../../../../../../base/common/strings.js'; import { applyFontInfo } from '../../../../../browser/config/domFontInfo.js'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidgetPosition, IViewZoneChangeAccessor, MouseTargetType } from '../../../../../browser/editorBrowser.js'; @@ -34,7 +34,8 @@ import { CodeEditorWidget } from '../../../../../browser/widget/codeEditor/codeE import { TokenWithTextArray } from '../../../../../common/tokens/tokenWithTextArray.js'; import { InlineCompletionViewData } from '../inlineEdits/inlineEditsViewInterface.js'; import { InlineDecorationType } from '../../../../../common/viewModel/inlineDecorations.js'; -import { sum } from '../../../../../../base/common/arrays.js'; +import { equals, sum } from '../../../../../../base/common/arrays.js'; +import { equalsIfDefined, IEquatable, itemEquals } from '../../../../../../base/common/equals.js'; export interface IGhostTextWidgetData { readonly ghostText: GhostText | GhostTextReplacement; @@ -102,14 +103,14 @@ export class GhostTextView extends Disposable { this._additionalLinesWidget = this._register( new AdditionalLinesWidget( this._editor, - derived(reader => { + derivedOpts({ owner: this, equalsFn: equalsIfDefined(itemEquals()) }, reader => { /** @description lines */ const uiState = this._state.read(reader); - return uiState ? { - lineNumber: uiState.lineNumber, - additionalLines: uiState.additionalLines, - minReservedLineCount: uiState.additionalReservedLineCount, - } : undefined; + return uiState ? new AdditionalLinesData( + uiState.lineNumber, + uiState.additionalLines, + uiState.additionalReservedLineCount, + ) : undefined; }), this._shouldKeepCursorStable, this._isClickable @@ -243,10 +244,10 @@ export class GhostTextView extends Disposable { const existingContent = t.slice(additionalLinesOriginalSuffix.columnRange.toZeroBasedOffsetRange()); content = TokenWithTextArray.fromLineTokens(content).append(existingContent).toLineTokens(content.languageIdCodec); } - return { + return new LineData( content, - decorations: l.decorations, - }; + l.decorations, + ); }); const cursorColumn = this._editor.getSelection()?.getStartPosition().column!; @@ -420,6 +421,24 @@ function computeGhostTextViewData(ghostText: GhostText | GhostTextReplacement, t }; } +class AdditionalLinesData implements IEquatable { + constructor( + public readonly lineNumber: number, + public readonly additionalLines: readonly LineData[], + public readonly minReservedLineCount: number, + ) { } + + equals(other: AdditionalLinesData): boolean { + if (this.lineNumber !== other.lineNumber) { + return false; + } + if (this.minReservedLineCount !== other.minReservedLineCount) { + return false; + } + return equals(this.additionalLines, other.additionalLines, itemEquals()); + } +} + export class AdditionalLinesWidget extends Disposable { private _viewZoneInfo: { viewZoneId: string; heightInLines: number; lineNumber: number } | undefined; public get viewZoneId(): string | undefined { return this._viewZoneInfo?.viewZoneId; } @@ -440,11 +459,7 @@ export class AdditionalLinesWidget extends Disposable { constructor( private readonly _editor: ICodeEditor, - private readonly _lines: IObservable<{ - lineNumber: number; - additionalLines: LineData[]; - minReservedLineCount: number; - } | undefined>, + private readonly _lines: IObservable, private readonly _shouldKeepCursorStable: boolean, private readonly _isClickable: boolean, ) { @@ -500,7 +515,7 @@ export class AdditionalLinesWidget extends Disposable { }); } - private updateLines(lineNumber: number, additionalLines: LineData[], minReservedLineCount: number): void { + private updateLines(lineNumber: number, additionalLines: readonly LineData[], minReservedLineCount: number): void { const textModel = this._editor.getModel(); if (!textModel) { return; @@ -581,12 +596,21 @@ function isTargetGhostText(target: EventTarget | null): boolean { return isHTMLElement(target) && target.classList.contains(GHOST_TEXT_CLASS_NAME); } -export interface LineData { - content: LineTokens; // Must not contain a linebreak! - decorations: LineDecoration[]; +export class LineData implements IEquatable { + constructor( + public readonly content: LineTokens, // Must not contain a linebreak! + public readonly decorations: readonly LineDecoration[] + ) { } + + equals(other: LineData): boolean { + if (!this.content.equals(other.content)) { + return false; + } + return LineDecoration.equalsArr(this.decorations, other.decorations); + } } -function renderLines(domNode: HTMLElement, tabSize: number, lines: LineData[], opts: IComputedEditorOptions, isClickable: boolean): void { +function renderLines(domNode: HTMLElement, tabSize: number, lines: readonly LineData[], opts: IComputedEditorOptions, isClickable: boolean): void { const disableMonospaceOptimizations = opts.get(EditorOption.disableMonospaceOptimizations); const stopRenderingLineAfter = opts.get(EditorOption.stopRenderingLineAfter); // 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 containsRTL, 0, lineTokens, - lineData.decorations, + lineData.decorations.slice(), tabSize, 0, fontInfo.spaceWidth,