Skip to content

Comments

Queue all modifications to the line heights and apply all of them only when reading from the data structure#296963

Draft
alexdima wants to merge 5 commits intomainfrom
alexd/dominant-turkey
Draft

Queue all modifications to the line heights and apply all of them only when reading from the data structure#296963
alexdima wants to merge 5 commits intomainfrom
alexd/dominant-turkey

Conversation

@alexdima
Copy link
Member

No description provided.

Copilot AI review requested due to automatic review settings February 23, 2026 11:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the LineHeightsManager to implement lazy commit semantics: modifications are queued and automatically applied when data is read, eliminating the need for explicit commit() calls while maintaining backward compatibility.

Changes:

  • Introduced a queue-based change tracking system with auto-commit on read
  • Refactored commit logic to handle interleaved decoration and structural changes correctly
  • Simplified test setup by removing explicit commit() calls where no longer needed

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/vs/editor/common/viewLayout/lineHeights.ts Implemented pending changes queue with PendingChange discriminated union, refactored commit logic into _commitPendingChanges with proper staging for decoration changes, made commit() a no-op, added _commitIfNeeded() calls to read methods
src/vs/editor/test/common/viewLayout/lineHeights.test.ts Simplified existing test to use range-based decoration, added comprehensive auto-commit test suite with 20 new tests covering edge cases and interleaved operations

@aiday-mar
Copy link
Contributor

aiday-mar commented Feb 23, 2026

On a file with 700 lines:

File

/*---------------------------------------------------------------------------------------------

  • Copyright (c) Microsoft Corporation. All rights reserved.
  • Licensed under the MIT License. See License.txt in the project root for license information.
    --------------------------------------------------------------------------------------------/

import '../../services/markerDecorations.js';
import * as dom from '../../../../base/browser/dom.js';
import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
import { IMouseWheelEvent } from '../../../../base/browser/mouseEvent.js';
import { Color } from '../../../../base/common/color.js';
import { onUnexpectedError } from '../../../../base/common/errors.js';
import { Emitter, EmitterOptions, Event, EventDeliveryQueue, createEventDeliveryQueue } from '../../../../base/common/event.js';
import { hash } from '../../../../base/common/hash.js';
import { Disposable, DisposableStore, IDisposable, dispose } from '../../../../base/common/lifecycle.js';
import { Schemas } from '../../../../base/common/network.js';
import './editor.css';
import { applyFontInfo } from '../../config/domFontInfo.js';
import { EditorConfiguration, IEditorConstructionOptions } from '../../config/editorConfiguration.js';
import { TabFocus } from '../../config/tabFocus.js';
import * as editorBrowser from '../../editorBrowser.js';
import { EditorExtensionsRegistry, IEditorContributionDescription } from '../../editorExtensions.js';
import { ICodeEditorService } from '../../services/codeEditorService.js';
import { IContentWidgetData, IGlyphMarginWidgetData, IOverlayWidgetData, View } from '../../view.js';
import { DOMLineBreaksComputerFactory } from '../../view/domLineBreaksComputer.js';
import { ICommandDelegate } from '../../view/viewController.js';
import { ViewUserInputEvents } from '../../view/viewUserInputEvents.js';
import { CodeEditorContributions } from './codeEditorContributions.js';
import { IEditorConfiguration } from '../../../common/config/editorConfiguration.js';
import { ConfigurationChangedEvent, EditorLayoutInfo, EditorOption, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, filterFontDecorations, filterValidationDecorations } from '../../../common/config/editorOptions.js';
import { CursorColumns } from '../../../common/core/cursorColumns.js';
import { IDimension } from '../../../common/core/2d/dimension.js';
import { editorUnnecessaryCodeOpacity } from '../../../common/core/editorColorRegistry.js';
import { IPosition, Position } from '../../../common/core/position.js';
import { IRange, Range } from '../../../common/core/range.js';
import { ISelection, Selection } from '../../../common/core/selection.js';
import { IWordAtPosition } from '../../../common/core/wordHelper.js';
import { WordOperations } from '../../../common/cursor/cursorWordOperations.js';
import { CursorChangeReason, ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from '../../../common/cursorEvents.js';
import { InternalEditorAction } from '../../../common/editorAction.js';
import * as editorCommon from '../../../common/editorCommon.js';
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
import { EndOfLinePreference, IAttachedView, ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from '../../../common/model.js';
import { ClassName } from '../../../common/model/intervalTree.js';
import { ModelDecorationOptions } from '../../../common/model/textModel.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent, ModelFontChangedEvent, ModelLineHeightChangedEvent } from '../../../common/textModelEvents.js';
import { VerticalRevealType } from '../../../common/viewEvents.js';
import { IEditorWhitespace, IViewModel } from '../../../common/viewModel.js';
import { MonospaceLineBreaksComputerFactory } from '../../../common/viewModel/monospaceLineBreaksComputer.js';
import { ViewModel } from '../../../common/viewModel/viewModelImpl.js';
import { OutgoingViewModelEventKind } from '../../../common/viewModelEventDispatcher.js';
import * as nls from '../../../../nls.js';
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { ContextKeyValue, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
import { editorErrorForeground, editorHintForeground, editorInfoForeground, editorWarningForeground } from '../../../../platform/theme/common/colorRegistry.js';
import { IThemeService, registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
import { MenuId } from '../../../../platform/actions/common/actions.js';
import { TextModelEditReason, EditReasons } from '../../../common/textModelEditReason.js';
import { TextEdit } from '../../../common/core/edits/textEdit.js';

/**

  • CodeEditorWidget provides a rich code editor experience, supporting events, decorations,

  • widgets, and context management. It is highly extensible and integrates with various services.
    */
    export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeEditor {

    private static readonly dropIntoEditorDecorationOptions = ModelDecorationOptions.register({
    description: 'workbench-dnd-target',
    className: 'dnd-target'
    });

    //#region Eventing

    private readonly _deliveryQueue = createEventDeliveryQueue();
    protected readonly _contributions: CodeEditorContributions = this._register(new CodeEditorContributions());

    private readonly _onDidDispose: Emitter = this._register(new Emitter());
    public readonly onDidDispose: Event = this._onDidDispose.event;

    private readonly _onDidChangeModelContent: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeModelContent: Event = this._onDidChangeModelContent.event;

    private readonly _onDidChangeModelLanguage: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeModelLanguage: Event = this._onDidChangeModelLanguage.event;

    private readonly _onDidChangeModelLanguageConfiguration: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeModelLanguageConfiguration: Event = this._onDidChangeModelLanguageConfiguration.event;

    private readonly _onDidChangeModelOptions: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeModelOptions: Event = this._onDidChangeModelOptions.event;

    private readonly _onDidChangeModelDecorations: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeModelDecorations: Event = this._onDidChangeModelDecorations.event;

    private readonly _onDidChangeLineHeight: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeLineHeight: Event = this._onDidChangeLineHeight.event;

    private readonly _onDidChangeFont: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeFont: Event = this._onDidChangeFont.event;

    private readonly _onDidChangeModelTokens: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeModelTokens: Event = this._onDidChangeModelTokens.event;

    private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event;

    protected readonly _onWillChangeModel: Emitter<editorCommon.IModelChangedEvent> = this._register(new Emitter<editorCommon.IModelChangedEvent>({ deliveryQueue: this._deliveryQueue }));
    public readonly onWillChangeModel: Event<editorCommon.IModelChangedEvent> = this._onWillChangeModel.event;

    protected readonly _onDidChangeModel: Emitter<editorCommon.IModelChangedEvent> = this._register(new Emitter<editorCommon.IModelChangedEvent>({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeModel: Event<editorCommon.IModelChangedEvent> = this._onDidChangeModel.event;

    private readonly _onDidChangeCursorPosition: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeCursorPosition: Event = this._onDidChangeCursorPosition.event;

    private readonly _onDidChangeCursorSelection: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeCursorSelection: Event = this._onDidChangeCursorSelection.event;

    private readonly _onDidAttemptReadOnlyEdit: Emitter = this._register(new InteractionEmitter(this._contributions, this._deliveryQueue));
    public readonly onDidAttemptReadOnlyEdit: Event = this._onDidAttemptReadOnlyEdit.event;

    private readonly _onDidLayoutChange: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidLayoutChange: Event = this._onDidLayoutChange.event;

    private readonly _editorTextFocus: BooleanEventEmitter = this._register(new BooleanEventEmitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidFocusEditorText: Event = this._editorTextFocus.onDidChangeToTrue;
    public readonly onDidBlurEditorText: Event = this._editorTextFocus.onDidChangeToFalse;

    private readonly _editorWidgetFocus: BooleanEventEmitter = this._register(new BooleanEventEmitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidFocusEditorWidget: Event = this._editorWidgetFocus.onDidChangeToTrue;
    public readonly onDidBlurEditorWidget: Event = this._editorWidgetFocus.onDidChangeToFalse;

    private readonly _onWillType: Emitter = this._register(new InteractionEmitter(this._contributions, this._deliveryQueue));
    public readonly onWillType = this._onWillType.event;

    private readonly _onDidType: Emitter = this._register(new InteractionEmitter(this._contributions, this._deliveryQueue));
    public readonly onDidType = this._onDidType.event;

    private readonly _onDidCompositionStart: Emitter = this._register(new InteractionEmitter(this._contributions, this._deliveryQueue));
    public readonly onDidCompositionStart = this._onDidCompositionStart.event;

    private readonly _onDidCompositionEnd: Emitter = this._register(new InteractionEmitter(this._contributions, this._deliveryQueue));
    public readonly onDidCompositionEnd = this._onDidCompositionEnd.event;

    private readonly _onDidPaste: Emitter<editorBrowser.IPasteEvent> = this._register(new InteractionEmitter<editorBrowser.IPasteEvent>(this._contributions, this._deliveryQueue));
    public readonly onDidPaste = this._onDidPaste.event;

    private readonly _onMouseUp: Emitter<editorBrowser.IEditorMouseEvent> = this._register(new InteractionEmitter<editorBrowser.IEditorMouseEvent>(this._contributions, this._deliveryQueue));
    public readonly onMouseUp: Event<editorBrowser.IEditorMouseEvent> = this._onMouseUp.event;

    private readonly _onMouseDown: Emitter<editorBrowser.IEditorMouseEvent> = this._register(new InteractionEmitter<editorBrowser.IEditorMouseEvent>(this._contributions, this._deliveryQueue));
    public readonly onMouseDown: Event<editorBrowser.IEditorMouseEvent> = this._onMouseDown.event;

    private readonly _onMouseDrag: Emitter<editorBrowser.IEditorMouseEvent> = this._register(new InteractionEmitter<editorBrowser.IEditorMouseEvent>(this._contributions, this._deliveryQueue));
    public readonly onMouseDrag: Event<editorBrowser.IEditorMouseEvent> = this._onMouseDrag.event;

    private readonly _onMouseDrop: Emitter<editorBrowser.IPartialEditorMouseEvent> = this._register(new InteractionEmitter<editorBrowser.IPartialEditorMouseEvent>(this._contributions, this._deliveryQueue));
    public readonly onMouseDrop: Event<editorBrowser.IPartialEditorMouseEvent> = this._onMouseDrop.event;

    private readonly _onMouseDropCanceled: Emitter = this._register(new InteractionEmitter(this._contributions, this._deliveryQueue));
    public readonly onMouseDropCanceled: Event = this._onMouseDropCanceled.event;

    private readonly _onDropIntoEditor = this._register(new InteractionEmitter<{ readonly position: IPosition; readonly event: DragEvent }>(this._contributions, this._deliveryQueue));
    public readonly onDropIntoEditor = this._onDropIntoEditor.event;

    private readonly _onContextMenu: Emitter<editorBrowser.IEditorMouseEvent> = this._register(new InteractionEmitter<editorBrowser.IEditorMouseEvent>(this._contributions, this._deliveryQueue));
    public readonly onContextMenu: Event<editorBrowser.IEditorMouseEvent> = this._onContextMenu.event;

    private readonly _onMouseMove: Emitter<editorBrowser.IEditorMouseEvent> = this._register(new InteractionEmitter<editorBrowser.IEditorMouseEvent>(this._contributions, this._deliveryQueue));
    public readonly onMouseMove: Event<editorBrowser.IEditorMouseEvent> = this._onMouseMove.event;

    private readonly _onMouseLeave: Emitter<editorBrowser.IPartialEditorMouseEvent> = this._register(new InteractionEmitter<editorBrowser.IPartialEditorMouseEvent>(this._contributions, this._deliveryQueue));
    public readonly onMouseLeave: Event<editorBrowser.IPartialEditorMouseEvent> = this._onMouseLeave.event;

    private readonly _onMouseWheel: Emitter = this._register(new InteractionEmitter(this._contributions, this._deliveryQueue));
    public readonly onMouseWheel: Event = this._onMouseWheel.event;

    private readonly _onKeyUp: Emitter = this._register(new InteractionEmitter(this._contributions, this._deliveryQueue));
    public readonly onKeyUp: Event = this._onKeyUp.event;

    private readonly _onKeyDown: Emitter = this._register(new InteractionEmitter(this._contributions, this._deliveryQueue));
    public readonly onKeyDown: Event = this._onKeyDown.event;

    private readonly _onDidContentSizeChange: Emitter<editorCommon.IContentSizeChangedEvent> = this._register(new Emitter<editorCommon.IContentSizeChangedEvent>({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidContentSizeChange: Event<editorCommon.IContentSizeChangedEvent> = this._onDidContentSizeChange.event;

    private readonly _onDidScrollChange: Emitter<editorCommon.IScrollEvent> = this._register(new Emitter<editorCommon.IScrollEvent>({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidScrollChange: Event<editorCommon.IScrollEvent> = this._onDidScrollChange.event;

    private readonly _onDidChangeViewZones: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeViewZones: Event = this._onDidChangeViewZones.event;

    private readonly _onDidChangeHiddenAreas: Emitter = this._register(new Emitter({ deliveryQueue: this._deliveryQueue }));
    public readonly onDidChangeHiddenAreas: Event = this._onDidChangeHiddenAreas.event;

    private _updateCounter = 0;

    private readonly _onWillTriggerEditorOperationEvent: Emitter<editorCommon.ITriggerEditorOperationEvent> = this._register(new Emitter<editorCommon.ITriggerEditorOperationEvent>());
    public readonly onWillTriggerEditorOperationEvent: Event<editorCommon.ITriggerEditorOperationEvent> = this._onWillTriggerEditorOperationEvent.event;

    private readonly _onBeginUpdate: Emitter = this._register(new Emitter());
    public readonly onBeginUpdate: Event = this._onBeginUpdate.event;

    private readonly _onEndUpdate: Emitter = this._register(new Emitter());
    public readonly onEndUpdate: Event = this._onEndUpdate.event;

    private readonly _onBeforeExecuteEdit = this._register(new Emitter<{ source: string | undefined }>());
    public readonly onBeforeExecuteEdit = this._onBeforeExecuteEdit.event;

    //#endregion

    public get isSimpleWidget(): boolean {
    return this._configuration.isSimpleWidget;
    }

    public get contextMenuId(): MenuId {
    return this._configuration.contextMenuId;
    }

    private readonly _telemetryData?: object;

    private readonly _domElement: HTMLElement;
    private readonly _overflowWidgetsDomNode: HTMLElement | undefined;
    private readonly _id: number;
    private readonly _configuration: IEditorConfiguration;
    private _contributionsDisposable: IDisposable | undefined;

    protected readonly _actions = new Map<string, editorCommon.IEditorAction>();

    // --- Members logically associated to a model
    protected _modelData: ModelData | null;

    protected readonly _instantiationService: IInstantiationService;
    protected readonly _contextKeyService: IContextKeyService;
    get contextKeyService() { return this._contextKeyService; }
    private readonly _notificationService: INotificationService;
    protected readonly _codeEditorService: ICodeEditorService;
    private readonly _commandService: ICommandService;
    private readonly _themeService: IThemeService;

    private _contentWidgets: { [key: string]: IContentWidgetData };
    private _overlayWidgets: { [key: string]: IOverlayWidgetData };
    private _glyphMarginWidgets: { [key: string]: IGlyphMarginWidgetData };

    /**

    • map from "parent" decoration type to live decoration ids.
      */
      private _decorationTypeKeysToIds: { [decorationTypeKey: string]: string[] };
      private _decorationTypeSubtypes: { [decorationTypeKey: string]: { [subtype: string]: boolean } };

    private _bannerDomNode: HTMLElement | null = null;

    private _dropIntoEditorDecorations: EditorDecorationsCollection = this.createDecorationsCollection();

    public inComposition: boolean = false;

    constructor(
    domElement: HTMLElement,
    _options: Readonly,
    codeEditorWidgetOptions: ICodeEditorWidgetOptions,
    @IInstantiationService instantiationService: IInstantiationService,
    @ICodeEditorService codeEditorService: ICodeEditorService,
    @ICommandService commandService: ICommandService,
    @IContextKeyService contextKeyService: IContextKeyService,
    @IThemeService themeService: IThemeService,
    @INotificationService notificationService: INotificationService,
    @IAccessibilityService accessibilityService: IAccessibilityService,
    @ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService,
    @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
    ) {
    super();
    codeEditorService.willCreateCodeEditor();

     const options = { ..._options };
    
     this._domElement = domElement;
     this._overflowWidgetsDomNode = options.overflowWidgetsDomNode;
     delete options.overflowWidgetsDomNode;
     this._id = (++EDITOR_ID);
     this._decorationTypeKeysToIds = {};
     this._decorationTypeSubtypes = {};
     this._telemetryData = codeEditorWidgetOptions.telemetryData;
    
     this._configuration = this._register(this._createConfiguration(codeEditorWidgetOptions.isSimpleWidget || false,
         codeEditorWidgetOptions.contextMenuId ?? (codeEditorWidgetOptions.isSimpleWidget ? MenuId.SimpleEditorContext : MenuId.EditorContext),
         options, accessibilityService));
     this._register(this._configuration.onDidChange((e) => {
         this._onDidChangeConfiguration.fire(e);
    
         const options = this._configuration.options;
         if (e.hasChanged(EditorOption.layoutInfo)) {
             const layoutInfo = options.get(EditorOption.layoutInfo);
             this._onDidLayoutChange.fire(layoutInfo);
         }
     }));
    
     this._contextKeyService = this._register(contextKeyService.createScoped(this._domElement));
     if (codeEditorWidgetOptions.contextKeyValues) {
         for (const [key, value] of Object.entries(codeEditorWidgetOptions.contextKeyValues)) {
             this._contextKeyService.createKey(key, value);
         }
     }
     this._notificationService = notificationService;
     this._codeEditorService = codeEditorService;
     this._commandService = commandService;
     this._themeService = themeService;
     this._register(new EditorContextKeysManager(this, this._contextKeyService));
     this._register(new EditorModeContext(this, this._contextKeyService, languageFeaturesService));
    
     this._instantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService])));
    
     this._modelData = null;
    
     this._contentWidgets = {};
     this._overlayWidgets = {};
     this._glyphMarginWidgets = {};
    
     let contributions: IEditorContributionDescription[];
     if (Array.isArray(codeEditorWidgetOptions.contributions)) {
         contributions = codeEditorWidgetOptions.contributions;
     } else {
         contributions = EditorExtensionsRegistry.getEditorContributions();
     }
     this._contributions.initialize(this, contributions, this._instantiationService);
    
     for (const action of EditorExtensionsRegistry.getEditorActions()) {
         if (this._actions.has(action.id)) {
             onUnexpectedError(new Error(`Cannot have two actions with the same id ${action.id}`));
             continue;
         }
         const internalAction = new InternalEditorAction(
             action.id,
             action.label,
             action.alias,
             action.metadata,
             action.precondition ?? undefined,
             (args: unknown): Promise<void> => {
                 return this._instantiationService.invokeFunction((accessor) => {
                     return Promise.resolve(action.runEditorCommand(accessor, this, args));
                 });
             },
             this._contextKeyService
         );
         this._actions.set(internalAction.id, internalAction);
     }
    
     const isDropIntoEnabled = () => {
         return !this._configuration.options.get(EditorOption.readOnly)
             && this._configuration.options.get(EditorOption.dropIntoEditor).enabled;
     };
    
     this._register(new dom.DragAndDropObserver(this._domElement, {
         onDragOver: e => {
             if (!isDropIntoEnabled()) {
                 return;
             }
    
             const target = this.getTargetAtClientPoint(e.clientX, e.clientY);
             if (target?.position) {
                 this.showDropIndicatorAt(target.position);
             }
         },
         onDrop: async e => {
             if (!isDropIntoEnabled()) {
                 return;
             }
    
             this.removeDropIndicator();
    
             if (!e.dataTransfer) {
                 return;
             }
    
             const target = this.getTargetAtClientPoint(e.clientX, e.clientY);
             if (target?.position) {
                 this._onDropIntoEditor.fire({ position: target.position, event: e });
             }
         },
         onDragLeave: () => {
             this.removeDropIndicator();
         },
         onDragEnd: () => {
             this.removeDropIndicator();
         },
     }));
    
     this._codeEditorService.addCodeEditor(this);
    

    }

    public writeScreenReaderContent(reason: string): void {
    this._modelData?.view.writeScreenReaderContent(reason);
    }

    protected _createConfiguration(isSimpleWidget: boolean, contextMenuId: MenuId, options: Readonly, accessibilityService: IAccessibilityService): EditorConfiguration {
    return new EditorConfiguration(isSimpleWidget, contextMenuId, options, this._domElement, accessibilityService);
    }

    public getId(): string {
    return this.getEditorType() + ':' + this._id;
    }

    public getEditorType(): string {
    return editorCommon.EditorType.ICodeEditor;
    }

    public override dispose(): void {
    this._codeEditorService.removeCodeEditor(this);

     this._actions.clear();
     this._contentWidgets = {};
     this._overlayWidgets = {};
    
     this._removeDecorationTypes();
     this._postDetachModelCleanup(this._detachModel());
    
     this._onDidDispose.fire();
    
     super.dispose();
    

    }

    public invokeWithinContext(fn: (accessor: ServicesAccessor) => T): T {
    return this._instantiationService.invokeFunction(fn);
    }

    public updateOptions(newOptions: Readonly | undefined): void {
    this._configuration.updateOptions(newOptions || {});
    }

    public getOptions(): IComputedEditorOptions {
    return this._configuration.options;
    }

    public getOption(id: T): FindComputedEditorOptionValueById {
    return this._configuration.options.get(id);
    }

    public getRawOptions(): IEditorOptions {
    return this._configuration.getRawOptions();
    }

    public getOverflowWidgetsDomNode(): HTMLElement | undefined {
    return this._overflowWidgetsDomNode;
    }

    public getConfiguredWordAtPosition(position: Position): IWordAtPosition | null {
    if (!this._modelData) {
    return null;
    }
    return WordOperations.getWordAtPosition(this._modelData.model, this._configuration.options.get(EditorOption.wordSeparators), this._configuration.options.get(EditorOption.wordSegmenterLocales), position);
    }

    public getValue(options: { preserveBOM: boolean; lineEnding: string } | null = null): string {
    if (!this._modelData) {
    return '';
    }

     const preserveBOM: boolean = (options && options.preserveBOM) ? true : false;
     let eolPreference = EndOfLinePreference.TextDefined;
     if (options && options.lineEnding && options.lineEnding === '\n') {
         eolPreference = EndOfLinePreference.LF;
     } else if (options && options.lineEnding && options.lineEnding === '\r\n') {
         eolPreference = EndOfLinePreference.CRLF;
     }
     return this._modelData.model.getValue(eolPreference, preserveBOM);
    

    }

    public setValue(newValue: string): void {
    try {
    this._beginUpdate();
    if (!this._modelData) {
    return;
    }
    this._modelData.model.setValue(newValue);
    } finally {
    this._endUpdate();
    }
    }

    public getModel(): ITextModel | null {
    if (!this._modelData) {
    return null;
    }
    return this._modelData.model;
    }

    public setModel(_model: ITextModel | editorCommon.IDiffEditorModel | editorCommon.IDiffEditorViewModel | null = null): void {
    try {
    this._beginUpdate();
    const model = <ITextModel | null>_model;
    if (this._modelData === null && model === null) {
    // Current model is the new model
    return;
    }
    if (this._modelData && this._modelData.model === model) {
    // Current model is the new model
    return;
    }

         const e: editorCommon.IModelChangedEvent = {
             oldModelUrl: this._modelData?.model.uri || null,
             newModelUrl: model?.uri || null
         };
         this._onWillChangeModel.fire(e);
    
         const hasTextFocus = this.hasTextFocus();
         const detachedModel = this._detachModel();
         this._attachModel(model);
         if (this.hasModel()) {
             // we have a new model (with a new view)!
             if (hasTextFocus) {
                 this.focus();
             }
         } else {
             // we have no model (and no view) anymore
             // make sure the outside world knows we are not focused
             this._editorTextFocus.setValue(false);
             this._editorWidgetFocus.setValue(false);
         }
    
         this._removeDecorationTypes();
         this._onDidChangeModel.fire(e);
         this._postDetachModelCleanup(detachedModel);
    
         this._contributionsDisposable = this._contributions.onAfterModelAttached();
     } finally {
         this._endUpdate();
     }
    

    }

    private _removeDecorationTypes(): void {
    this._decorationTypeKeysToIds = {};
    if (this._decorationTypeSubtypes) {
    for (const decorationType in this._decorationTypeSubtypes) {
    const subTypes = this._decorationTypeSubtypes[decorationType];
    for (const subType in subTypes) {
    this._removeDecorationType(decorationType + '-' + subType);
    }
    }
    this._decorationTypeSubtypes = {};
    }
    }

    public getVisibleRanges(): Range[] {
    if (!this._modelData) {
    return [];
    }
    return this._modelData.viewModel.getVisibleRanges();
    }

    public getVisibleRangesPlusViewportAboveBelow(): Range[] {
    if (!this._modelData) {
    return [];
    }
    return this._modelData.viewModel.getVisibleRangesPlusViewportAboveBelow();
    }

    public getWhitespaces(): IEditorWhitespace[] {
    if (!this._modelData) {
    return [];
    }
    return this._modelData.viewModel.viewLayout.getWhitespaces();
    }

    private static _getVerticalOffsetAfterPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number, includeViewZones: boolean): number {
    const modelPosition = modelData.model.validatePosition({
    lineNumber: modelLineNumber,
    column: modelColumn
    });
    const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
    return modelData.viewModel.viewLayout.getVerticalOffsetAfterLineNumber(viewPosition.lineNumber, includeViewZones);
    }

    public getTopForLineNumber(lineNumber: number, includeViewZones: boolean = false): number {
    if (!this._modelData) {
    return -1;
    }
    return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, 1, includeViewZones);
    }

    public getTopForPosition(lineNumber: number, column: number): number {
    if (!this._modelData) {
    return -1;
    }
    return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, column, false);
    }

    private static _getVerticalOffsetForPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number, includeViewZones: boolean = false): number {
    const modelPosition = modelData.model.validatePosition({
    lineNumber: modelLineNumber,
    column: modelColumn
    });
    const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
    return modelData.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber, includeViewZones);
    }

    public getBottomForLineNumber(lineNumber: number, includeViewZones: boolean = false): number {
    if (!this._modelData) {
    return -1;
    }
    const maxCol = this._modelData.model.getLineMaxColumn(lineNumber);
    return CodeEditorWidget._getVerticalOffsetAfterPosition(this._modelData, lineNumber, maxCol, includeViewZones);
    }

    public getLineHeightForPosition(position: IPosition): number {
    if (!this._modelData) {
    return -1;
    }
    const viewModel = this._modelData.viewModel;
    const coordinatesConverter = viewModel.coordinatesConverter;
    const pos = Position.lift(position);
    if (coordinatesConverter.modelPositionIsVisible(pos)) {
    const viewPosition = coordinatesConverter.convertModelPositionToViewPosition(pos);
    return viewModel.viewLayout.getLineHeightForLineNumber(viewPosition.lineNumber);
    }
    return 0;
    }

    public setHiddenAreas(ranges: IRange[], source?: unknown, forceUpdate?: boolean): void {
    this._modelData?.viewModel.setHiddenAreas(ranges.map(r => Range.lift(r)), source, forceUpdate);
    }

    public getVisibleColumnFromPosition(rawPosition: IPosition): number {
    if (!this._modelData) {
    return rawPosition.column;
    }

     const position = this._modelData.model.validatePosition(rawPosition);
     const tabSize = this._modelData.model.getOptions().tabSize;
    
     return CursorColumns.visibleColumnFromColumn(this._modelData.model.getLineContent(position.lineNumber), position.column, tabSize) + 1;
    

    }

    public getStatusbarColumn(rawPosition: IPosition): number {
    if (!this._modelData) {
    return rawPosition.column;
    }

     const position = this._modelData.model.validatePosition(rawPosition);
     const tabSize = this._modelData.model.getOptions().tabSize;
    
     return CursorColumns.toStatusbarColumn(this._modelData.model.getLineContent(position.lineNumber), position.column, tabSize);
    

    }

    public getPosition(): Position | null {
    if (!this._modelData) {
    return null;
    }
    return this._modelData.viewModel.getPosition();
    }

    public setPosition(position: IPosition, source: string = 'api'): void {
    if (!this._modelData) {
    return;
    }
    if (!Position.isIPosition(position)) {
    throw new Error('Invalid arguments');
    }
    this._modelData.viewModel.setSelections(source, [{
    selectionStartLineNumber: position.lineNumber,
    selectionStartColumn: position.column,
    positionLineNumber: position.lineNumber,
    positionColumn: position.column
    }]);
    }

    private _sendRevealRange(modelRange: Range, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void {
    if (!this._modelData) {
    return;
    }
    if (!Range.isIRange(modelRange)) {
    throw new Error('Invalid arguments');
    }
    const validatedModelRange = this._modelData.model.validateRange(modelRange);
    const viewRange = this._modelData.viewModel.coordinatesConverter.convertModelRangeToViewRange(validatedModelRange);

     this._modelData.viewModel.revealRange('api', revealHorizontal, viewRange, verticalType, scrollType);
    

    }

    public revealLine(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
    this._revealLine(lineNumber, VerticalRevealType.Simple, scrollType);
    }

    public revealLineInCenter(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
    this._revealLine(lineNumber, VerticalRevealType.Center, scrollType);
    }

    public revealLineInCenterIfOutsideViewport(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
    this._revealLine(lineNumber, VerticalRevealType.CenterIfOutsideViewport, scrollType);
    }

    public revealLineNearTop(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
    this._revealLine(lineNumber, VerticalRevealType.NearTop, scrollType);
    }

    private _revealLine(lineNumber: number, revealType: VerticalRevealType, scrollType: editorCommon.ScrollType): void {
    if (typeof lineNumber !== 'number') {
    throw new Error('Invalid arguments');
    }

     this._sendRevealRange(
         new Range(lineNumber, 1, lineNumber, 1),
         revealType,
         false,
         scrollType
     );
    

    }

    public revealPosition(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
    console.log('revealPosition : ', position);
    this._revealPosition(
    position,
    VerticalRevealType.Simple,
    true,
    scrollType
    );
    }

    public revealPositionInCenter(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
    this._revealPosition(
    position,
    VerticalRevealType.Center,
    true,
    scrollType
    );
    }

    public revealPositionInCenterIfOutsideViewport(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
    this._revealPosition(
    position,
    VerticalRevealType.CenterIfOutsideViewport,
    true,
    scrollType
    );
    }

    public revealPositionNearTop(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
    this._revealPosition(
    position,
    VerticalRevealType.NearTop,
    true,
    scrollType
    );
    }
    }

If you copy paste the above code until you get 50'000 lines, when inserting a new line on the first CodeEditorWidget instance:

  • with main it takes a total for the commit() calls after 10 seconds (search for uniqueCommitName in trace)
    • 8.45 ms
    • 9.67 ms
    • 9.55 ms
  • with alexd/dominant-turkey it takes a total for the commit() calls after 10 seconds (search for _flushStagedDecorationChanges in trace)
    • 8.72 ms
    • 8.66 ms
    • 8.17 ms

It would seem overall like the optimized implementation is slightly quicker. The optimized implementation has a total average time of 8.5 ms. The initial implementation had a total average time of 9.2 ms. So it would seem that the optimized implementation is around 10% faster.

Trace-20260223T173431.json.gz
Trace-20260223T173712.json.gz
Trace-20260223T173853.json.gz
Trace-20260223T183704.json.gz
Trace-20260223T183839.json.gz
Trace-20260223T184044.json.gz

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants