Path: blob/main/src/vs/editor/common/viewModel/viewModelImpl.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ArrayQueue } from '../../../base/common/arrays.js';6import { RunOnceScheduler } from '../../../base/common/async.js';7import { Color } from '../../../base/common/color.js';8import { Event } from '../../../base/common/event.js';9import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';10import * as platform from '../../../base/common/platform.js';11import * as strings from '../../../base/common/strings.js';12import { ConfigurationChangedEvent, EditorOption, EDITOR_FONT_DEFAULTS, filterValidationDecorations, filterFontDecorations } from '../config/editorOptions.js';13import { CursorsController } from '../cursor/cursor.js';14import { CursorConfiguration, CursorState, EditOperationType, IColumnSelectData, PartialCursorState } from '../cursorCommon.js';15import { CursorChangeReason } from '../cursorEvents.js';16import { IPosition, Position } from '../core/position.js';17import { Range } from '../core/range.js';18import { ISelection, Selection } from '../core/selection.js';19import { ICommand, ICursorState, IViewState, ScrollType } from '../editorCommon.js';20import { IEditorConfiguration } from '../config/editorConfiguration.js';21import { EndOfLinePreference, IAttachedView, ICursorStateComputer, IGlyphMarginLanesModel, IIdentifiedSingleEditOperation, ITextModel, PositionAffinity, TextDirection, TrackedRangeStickiness } from '../model.js';22import { IActiveIndentGuideInfo, BracketGuideOptions, IndentGuide } from '../textModelGuides.js';23import { ModelDecorationMinimapOptions, ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from '../model/textModel.js';24import * as textModelEvents from '../textModelEvents.js';25import { TokenizationRegistry } from '../languages.js';26import { ColorId } from '../encodedTokenAttributes.js';27import { ILanguageConfigurationService } from '../languages/languageConfigurationRegistry.js';28import { PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js';29import { tokenizeLineToHTML } from '../languages/textToHtmlTokenizer.js';30import { EditorTheme } from '../editorTheme.js';31import * as viewEvents from '../viewEvents.js';32import { ViewLayout } from '../viewLayout/viewLayout.js';33import { MinimapTokensColorTracker } from './minimapTokensColorTracker.js';34import { ILineBreaksComputer, ILineBreaksComputerFactory, InjectedText } from '../modelLineProjectionData.js';35import { ViewEventHandler } from '../viewEventHandler.js';36import { ILineHeightChangeAccessor, IViewModel, IWhitespaceChangeAccessor, MinimapLinesRenderingData, OverviewRulerDecorationsGroup, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from '../viewModel.js';37import { ViewModelDecorations } from './viewModelDecorations.js';38import { FocusChangedEvent, HiddenAreasChangedEvent, ModelContentChangedEvent, ModelDecorationsChangedEvent, ModelFontChangedEvent, ModelLanguageChangedEvent, ModelLanguageConfigurationChangedEvent, ModelLineHeightChangedEvent, ModelOptionsChangedEvent, ModelTokensChangedEvent, OutgoingViewModelEvent, ReadOnlyEditAttemptEvent, ScrollChangedEvent, ViewModelEventDispatcher, ViewModelEventsCollector, ViewZonesChangedEvent, WidgetFocusChangedEvent } from '../viewModelEventDispatcher.js';39import { IViewModelLines, ViewModelLinesFromModelAsIs, ViewModelLinesFromProjectedModel } from './viewModelLines.js';40import { IThemeService } from '../../../platform/theme/common/themeService.js';41import { GlyphMarginLanesModel } from './glyphLanesModel.js';42import { ICustomLineHeightData } from '../viewLayout/lineHeights.js';43import { TextModelEditSource } from '../textModelEditSource.js';44import { InlineDecoration } from './inlineDecorations.js';45import { ICoordinatesConverter } from '../coordinatesConverter.js';4647const USE_IDENTITY_LINES_COLLECTION = true;4849export class ViewModel extends Disposable implements IViewModel {5051private readonly _editorId: number;52private readonly _configuration: IEditorConfiguration;53public readonly model: ITextModel;54private readonly _eventDispatcher: ViewModelEventDispatcher;55public readonly onEvent: Event<OutgoingViewModelEvent>;56public cursorConfig: CursorConfiguration;57private readonly _updateConfigurationViewLineCount: RunOnceScheduler;58private _hasFocus: boolean;59private readonly _viewportStart: ViewportStart;60private readonly _lines: IViewModelLines;61public readonly coordinatesConverter: ICoordinatesConverter;62public readonly viewLayout: ViewLayout;63private readonly _cursor: CursorsController;64private readonly _decorations: ViewModelDecorations;65public readonly glyphLanes: IGlyphMarginLanesModel;6667constructor(68editorId: number,69configuration: IEditorConfiguration,70model: ITextModel,71domLineBreaksComputerFactory: ILineBreaksComputerFactory,72monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory,73scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable,74private readonly languageConfigurationService: ILanguageConfigurationService,75private readonly _themeService: IThemeService,76private readonly _attachedView: IAttachedView,77private readonly _transactionalTarget: IBatchableTarget,78) {79super();8081this._editorId = editorId;82this._configuration = configuration;83this.model = model;84this._eventDispatcher = new ViewModelEventDispatcher();85this.onEvent = this._eventDispatcher.onEvent;86this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService);87this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0));88this._hasFocus = false;89this._viewportStart = ViewportStart.create(this.model);90this.glyphLanes = new GlyphMarginLanesModel(0);9192if (USE_IDENTITY_LINES_COLLECTION && this.model.isTooLargeForTokenization()) {9394this._lines = new ViewModelLinesFromModelAsIs(this.model);9596} else {97const options = this._configuration.options;98const fontInfo = options.get(EditorOption.fontInfo);99const wrappingStrategy = options.get(EditorOption.wrappingStrategy);100const wrappingInfo = options.get(EditorOption.wrappingInfo);101const wrappingIndent = options.get(EditorOption.wrappingIndent);102const wordBreak = options.get(EditorOption.wordBreak);103const wrapOnEscapedLineFeeds = options.get(EditorOption.wrapOnEscapedLineFeeds);104105this._lines = new ViewModelLinesFromProjectedModel(106this._editorId,107this.model,108domLineBreaksComputerFactory,109monospaceLineBreaksComputerFactory,110fontInfo,111this.model.getOptions().tabSize,112wrappingStrategy,113wrappingInfo.wrappingColumn,114wrappingIndent,115wordBreak,116wrapOnEscapedLineFeeds117);118}119120this.coordinatesConverter = this._lines.createCoordinatesConverter();121122this._cursor = this._register(new CursorsController(model, this, this.coordinatesConverter, this.cursorConfig));123124this.viewLayout = this._register(new ViewLayout(this._configuration, this.getLineCount(), this._getCustomLineHeights(), scheduleAtNextAnimationFrame));125126this._register(this.viewLayout.onDidScroll((e) => {127if (e.scrollTopChanged) {128this._handleVisibleLinesChanged();129}130if (e.scrollTopChanged) {131this._viewportStart.invalidate();132}133this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewScrollChangedEvent(e));134this._eventDispatcher.emitOutgoingEvent(new ScrollChangedEvent(135e.oldScrollWidth, e.oldScrollLeft, e.oldScrollHeight, e.oldScrollTop,136e.scrollWidth, e.scrollLeft, e.scrollHeight, e.scrollTop137));138}));139140this._register(this.viewLayout.onDidContentSizeChange((e) => {141this._eventDispatcher.emitOutgoingEvent(e);142}));143144this._decorations = new ViewModelDecorations(this._editorId, this.model, this._configuration, this._lines, this.coordinatesConverter);145146this._registerModelEvents();147148this._register(this._configuration.onDidChangeFast((e) => {149try {150const eventsCollector = this._eventDispatcher.beginEmitViewEvents();151this._onConfigurationChanged(eventsCollector, e);152} finally {153this._eventDispatcher.endEmitViewEvents();154}155}));156157this._register(MinimapTokensColorTracker.getInstance().onDidChange(() => {158this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewTokensColorsChangedEvent());159}));160161this._register(this._themeService.onDidColorThemeChange((theme) => {162this._invalidateDecorationsColorCache();163this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewThemeChangedEvent(theme));164}));165166this._updateConfigurationViewLineCountNow();167}168169public override dispose(): void {170// First remove listeners, as disposing the lines might end up sending171// model decoration changed events ... and we no longer care about them ...172super.dispose();173this._decorations.dispose();174this._lines.dispose();175this._viewportStart.dispose();176this._eventDispatcher.dispose();177}178179public createLineBreaksComputer(): ILineBreaksComputer {180return this._lines.createLineBreaksComputer();181}182183public addViewEventHandler(eventHandler: ViewEventHandler): void {184this._eventDispatcher.addViewEventHandler(eventHandler);185}186187public removeViewEventHandler(eventHandler: ViewEventHandler): void {188this._eventDispatcher.removeViewEventHandler(eventHandler);189}190191private _getCustomLineHeights(): ICustomLineHeightData[] {192const allowVariableLineHeights = this._configuration.options.get(EditorOption.allowVariableLineHeights);193if (!allowVariableLineHeights) {194return [];195}196const decorations = this.model.getCustomLineHeightsDecorations(this._editorId);197return decorations.map((d) => {198const lineNumber = d.range.startLineNumber;199const viewRange = this.coordinatesConverter.convertModelRangeToViewRange(new Range(lineNumber, 1, lineNumber, this.model.getLineMaxColumn(lineNumber)));200return {201decorationId: d.id,202startLineNumber: viewRange.startLineNumber,203endLineNumber: viewRange.endLineNumber,204lineHeight: d.options.lineHeight || 0205};206});207}208209private _updateConfigurationViewLineCountNow(): void {210this._configuration.setViewLineCount(this._lines.getViewLineCount());211}212213private getModelVisibleRanges(): Range[] {214const linesViewportData = this.viewLayout.getLinesViewportData();215const viewVisibleRange = new Range(216linesViewportData.startLineNumber,217this.getLineMinColumn(linesViewportData.startLineNumber),218linesViewportData.endLineNumber,219this.getLineMaxColumn(linesViewportData.endLineNumber)220);221const modelVisibleRanges = this._toModelVisibleRanges(viewVisibleRange);222return modelVisibleRanges;223}224225public visibleLinesStabilized(): void {226const modelVisibleRanges = this.getModelVisibleRanges();227this._attachedView.setVisibleLines(modelVisibleRanges, true);228}229230private _handleVisibleLinesChanged(): void {231const modelVisibleRanges = this.getModelVisibleRanges();232this._attachedView.setVisibleLines(modelVisibleRanges, false);233}234235public setHasFocus(hasFocus: boolean): void {236this._hasFocus = hasFocus;237this._cursor.setHasFocus(hasFocus);238this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewFocusChangedEvent(hasFocus));239this._eventDispatcher.emitOutgoingEvent(new FocusChangedEvent(!hasFocus, hasFocus));240}241242public setHasWidgetFocus(hasWidgetFocus: boolean): void {243this._eventDispatcher.emitOutgoingEvent(new WidgetFocusChangedEvent(!hasWidgetFocus, hasWidgetFocus));244}245246public onCompositionStart(): void {247this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionStartEvent());248}249250public onCompositionEnd(): void {251this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionEndEvent());252}253254private _captureStableViewport(): StableViewport {255// We might need to restore the current start view range, so save it (if available)256// But only if the scroll position is not at the top of the file257if (this._viewportStart.isValid && this.viewLayout.getCurrentScrollTop() > 0) {258const previousViewportStartViewPosition = new Position(this._viewportStart.viewLineNumber, this.getLineMinColumn(this._viewportStart.viewLineNumber));259const previousViewportStartModelPosition = this.coordinatesConverter.convertViewPositionToModelPosition(previousViewportStartViewPosition);260return new StableViewport(previousViewportStartModelPosition, this._viewportStart.startLineDelta);261}262return new StableViewport(null, 0);263}264265private _onConfigurationChanged(eventsCollector: ViewModelEventsCollector, e: ConfigurationChangedEvent): void {266const stableViewport = this._captureStableViewport();267const options = this._configuration.options;268const fontInfo = options.get(EditorOption.fontInfo);269const wrappingStrategy = options.get(EditorOption.wrappingStrategy);270const wrappingInfo = options.get(EditorOption.wrappingInfo);271const wrappingIndent = options.get(EditorOption.wrappingIndent);272const wordBreak = options.get(EditorOption.wordBreak);273274if (this._lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent, wordBreak)) {275eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent());276eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent());277eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));278this._cursor.onLineMappingChanged(eventsCollector);279this._decorations.onLineMappingChanged();280this.viewLayout.onFlushed(this.getLineCount(), this._getCustomLineHeights());281282this._updateConfigurationViewLineCount.schedule();283}284285if (e.hasChanged(EditorOption.readOnly)) {286// Must read again all decorations due to readOnly filtering287this._decorations.reset();288eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));289}290291if (e.hasChanged(EditorOption.renderValidationDecorations)) {292this._decorations.reset();293eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));294}295296eventsCollector.emitViewEvent(new viewEvents.ViewConfigurationChangedEvent(e));297this.viewLayout.onConfigurationChanged(e);298299stableViewport.recoverViewportStart(this.coordinatesConverter, this.viewLayout);300301if (CursorConfiguration.shouldRecreate(e)) {302this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService);303this._cursor.updateConfiguration(this.cursorConfig);304}305}306307private _registerModelEvents(): void {308309this._register(this.model.onDidChangeContentOrInjectedText((e) => {310try {311const eventsCollector = this._eventDispatcher.beginEmitViewEvents();312313let hadOtherModelChange = false;314let hadModelLineChangeThatChangedLineMapping = false;315316const changes = (e instanceof textModelEvents.InternalModelContentChangeEvent ? e.rawContentChangedEvent.changes : e.changes);317const versionId = (e instanceof textModelEvents.InternalModelContentChangeEvent ? e.rawContentChangedEvent.versionId : null);318319// Do a first pass to compute line mappings, and a second pass to actually interpret them320const lineBreaksComputer = this._lines.createLineBreaksComputer();321for (const change of changes) {322switch (change.changeType) {323case textModelEvents.RawContentChangedType.LinesInserted: {324for (let lineIdx = 0; lineIdx < change.detail.length; lineIdx++) {325const line = change.detail[lineIdx];326let injectedText = change.injectedTexts[lineIdx];327if (injectedText) {328injectedText = injectedText.filter(element => (!element.ownerId || element.ownerId === this._editorId));329}330lineBreaksComputer.addRequest(line, injectedText, null);331}332break;333}334case textModelEvents.RawContentChangedType.LineChanged: {335let injectedText: textModelEvents.LineInjectedText[] | null = null;336if (change.injectedText) {337injectedText = change.injectedText.filter(element => (!element.ownerId || element.ownerId === this._editorId));338}339lineBreaksComputer.addRequest(change.detail, injectedText, null);340break;341}342}343}344const lineBreaks = lineBreaksComputer.finalize();345const lineBreakQueue = new ArrayQueue(lineBreaks);346347for (const change of changes) {348switch (change.changeType) {349case textModelEvents.RawContentChangedType.Flush: {350this._lines.onModelFlushed();351eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent());352this._decorations.reset();353this.viewLayout.onFlushed(this.getLineCount(), this._getCustomLineHeights());354hadOtherModelChange = true;355break;356}357case textModelEvents.RawContentChangedType.LinesDeleted: {358const linesDeletedEvent = this._lines.onModelLinesDeleted(versionId, change.fromLineNumber, change.toLineNumber);359if (linesDeletedEvent !== null) {360eventsCollector.emitViewEvent(linesDeletedEvent);361this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber);362}363hadOtherModelChange = true;364break;365}366case textModelEvents.RawContentChangedType.LinesInserted: {367const insertedLineBreaks = lineBreakQueue.takeCount(change.detail.length);368const linesInsertedEvent = this._lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks);369if (linesInsertedEvent !== null) {370eventsCollector.emitViewEvent(linesInsertedEvent);371this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);372}373hadOtherModelChange = true;374break;375}376case textModelEvents.RawContentChangedType.LineChanged: {377const changedLineBreakData = lineBreakQueue.dequeue()!;378const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] =379this._lines.onModelLineChanged(versionId, change.lineNumber, changedLineBreakData);380hadModelLineChangeThatChangedLineMapping = lineMappingChanged;381if (linesChangedEvent) {382eventsCollector.emitViewEvent(linesChangedEvent);383}384if (linesInsertedEvent) {385eventsCollector.emitViewEvent(linesInsertedEvent);386this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);387}388if (linesDeletedEvent) {389eventsCollector.emitViewEvent(linesDeletedEvent);390this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber);391}392break;393}394case textModelEvents.RawContentChangedType.EOLChanged: {395// Nothing to do. The new version will be accepted below396break;397}398}399}400401if (versionId !== null) {402this._lines.acceptVersionId(versionId);403}404this.viewLayout.onHeightMaybeChanged();405406if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) {407eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent());408eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));409this._cursor.onLineMappingChanged(eventsCollector);410this._decorations.onLineMappingChanged();411}412} finally {413this._eventDispatcher.endEmitViewEvents();414}415416// Update the configuration and reset the centered view line417const viewportStartWasValid = this._viewportStart.isValid;418this._viewportStart.invalidate();419this._configuration.setModelLineCount(this.model.getLineCount());420this._updateConfigurationViewLineCountNow();421422// Recover viewport423if (!this._hasFocus && this.model.getAttachedEditorCount() >= 2 && viewportStartWasValid) {424const modelRange = this.model._getTrackedRange(this._viewportStart.modelTrackedRange);425if (modelRange) {426const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelRange.getStartPosition());427const viewPositionTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber);428this.viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this._viewportStart.startLineDelta }, ScrollType.Immediate);429}430}431432try {433const eventsCollector = this._eventDispatcher.beginEmitViewEvents();434if (e instanceof textModelEvents.InternalModelContentChangeEvent) {435eventsCollector.emitOutgoingEvent(new ModelContentChangedEvent(e.contentChangedEvent));436}437this._cursor.onModelContentChanged(eventsCollector, e);438} finally {439this._eventDispatcher.endEmitViewEvents();440}441442this._handleVisibleLinesChanged();443}));444445const allowVariableLineHeights = this._configuration.options.get(EditorOption.allowVariableLineHeights);446if (allowVariableLineHeights) {447this._register(this.model.onDidChangeLineHeight((e) => {448const filteredChanges = e.changes.filter((change) => change.ownerId === this._editorId || change.ownerId === 0);449450this.viewLayout.changeSpecialLineHeights((accessor: ILineHeightChangeAccessor) => {451for (const change of filteredChanges) {452const { decorationId, lineNumber, lineHeight } = change;453const viewRange = this.coordinatesConverter.convertModelRangeToViewRange(new Range(lineNumber, 1, lineNumber, this.model.getLineMaxColumn(lineNumber)));454if (lineHeight !== null) {455accessor.insertOrChangeCustomLineHeight(decorationId, viewRange.startLineNumber, viewRange.endLineNumber, lineHeight);456} else {457accessor.removeCustomLineHeight(decorationId);458}459}460});461462// recreate the model event using the filtered changes463if (filteredChanges.length > 0) {464const filteredEvent = new textModelEvents.ModelLineHeightChangedEvent(filteredChanges);465this._eventDispatcher.emitOutgoingEvent(new ModelLineHeightChangedEvent(filteredEvent));466}467}));468}469470const allowVariableFonts = this._configuration.options.get(EditorOption.effectiveAllowVariableFonts);471if (allowVariableFonts) {472this._register(this.model.onDidChangeFont((e) => {473const filteredChanges = e.changes.filter((change) => change.ownerId === this._editorId || change.ownerId === 0);474// recreate the model event using the filtered changes475if (filteredChanges.length > 0) {476const filteredEvent = new textModelEvents.ModelFontChangedEvent(filteredChanges);477this._eventDispatcher.emitOutgoingEvent(new ModelFontChangedEvent(filteredEvent));478}479}));480}481482this._register(this.model.onDidChangeTokens((e) => {483const viewRanges: { fromLineNumber: number; toLineNumber: number }[] = [];484for (let j = 0, lenJ = e.ranges.length; j < lenJ; j++) {485const modelRange = e.ranges[j];486const viewStartLineNumber = this.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.fromLineNumber, 1)).lineNumber;487const viewEndLineNumber = this.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.toLineNumber, this.model.getLineMaxColumn(modelRange.toLineNumber))).lineNumber;488viewRanges[j] = {489fromLineNumber: viewStartLineNumber,490toLineNumber: viewEndLineNumber491};492}493this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewTokensChangedEvent(viewRanges));494this._eventDispatcher.emitOutgoingEvent(new ModelTokensChangedEvent(e));495}));496497this._register(this.model.onDidChangeLanguageConfiguration((e) => {498this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewLanguageConfigurationEvent());499this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService);500this._cursor.updateConfiguration(this.cursorConfig);501this._eventDispatcher.emitOutgoingEvent(new ModelLanguageConfigurationChangedEvent(e));502}));503504this._register(this.model.onDidChangeLanguage((e) => {505this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService);506this._cursor.updateConfiguration(this.cursorConfig);507this._eventDispatcher.emitOutgoingEvent(new ModelLanguageChangedEvent(e));508}));509510this._register(this.model.onDidChangeOptions((e) => {511// A tab size change causes a line mapping changed event => all view parts will repaint OK, no further event needed here512if (this._lines.setTabSize(this.model.getOptions().tabSize)) {513try {514const eventsCollector = this._eventDispatcher.beginEmitViewEvents();515eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent());516eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent());517eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));518this._cursor.onLineMappingChanged(eventsCollector);519this._decorations.onLineMappingChanged();520this.viewLayout.onFlushed(this.getLineCount(), this._getCustomLineHeights());521} finally {522this._eventDispatcher.endEmitViewEvents();523}524this._updateConfigurationViewLineCount.schedule();525}526527this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService);528this._cursor.updateConfiguration(this.cursorConfig);529530this._eventDispatcher.emitOutgoingEvent(new ModelOptionsChangedEvent(e));531}));532533this._register(this.model.onDidChangeDecorations((e) => {534this._decorations.onModelDecorationsChanged();535this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewDecorationsChangedEvent(e));536this._eventDispatcher.emitOutgoingEvent(new ModelDecorationsChangedEvent(e));537}));538}539540private readonly hiddenAreasModel = new HiddenAreasModel();541private previousHiddenAreas: readonly Range[] = [];542543public getFontSizeAtPosition(position: IPosition): string | null {544const allowVariableFonts = this._configuration.options.get(EditorOption.effectiveAllowVariableFonts);545if (!allowVariableFonts) {546return null;547}548const fontDecorations = this.model.getFontDecorationsInRange(Range.fromPositions(position), this._editorId);549let fontSize: string = this._configuration.options.get(EditorOption.fontInfo).fontSize + 'px';550for (const fontDecoration of fontDecorations) {551if (fontDecoration.options.fontSize) {552fontSize = fontDecoration.options.fontSize;553break;554}555}556return fontSize;557}558559/**560* @param forceUpdate If true, the hidden areas will be updated even if the new ranges are the same as the previous ranges.561* This is because the model might have changed, which resets the hidden areas, but not the last cached value.562* This needs a better fix in the future.563*/564public setHiddenAreas(ranges: Range[], source?: unknown, forceUpdate?: boolean): void {565this.hiddenAreasModel.setHiddenAreas(source, ranges);566const mergedRanges = this.hiddenAreasModel.getMergedRanges();567if (mergedRanges === this.previousHiddenAreas && !forceUpdate) {568return;569}570571this.previousHiddenAreas = mergedRanges;572573const stableViewport = this._captureStableViewport();574575let lineMappingChanged = false;576try {577const eventsCollector = this._eventDispatcher.beginEmitViewEvents();578lineMappingChanged = this._lines.setHiddenAreas(mergedRanges);579if (lineMappingChanged) {580eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent());581eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent());582eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));583this._cursor.onLineMappingChanged(eventsCollector);584this._decorations.onLineMappingChanged();585this.viewLayout.onFlushed(this.getLineCount(), this._getCustomLineHeights());586this.viewLayout.onHeightMaybeChanged();587}588589const firstModelLineInViewPort = stableViewport.viewportStartModelPosition?.lineNumber;590const firstModelLineIsHidden = firstModelLineInViewPort && mergedRanges.some(range => range.startLineNumber <= firstModelLineInViewPort && firstModelLineInViewPort <= range.endLineNumber);591if (!firstModelLineIsHidden) {592stableViewport.recoverViewportStart(this.coordinatesConverter, this.viewLayout);593}594} finally {595this._eventDispatcher.endEmitViewEvents();596}597this._updateConfigurationViewLineCount.schedule();598599if (lineMappingChanged) {600this._eventDispatcher.emitOutgoingEvent(new HiddenAreasChangedEvent());601}602}603604public getVisibleRangesPlusViewportAboveBelow(): Range[] {605const layoutInfo = this._configuration.options.get(EditorOption.layoutInfo);606const lineHeight = this._configuration.options.get(EditorOption.lineHeight);607const linesAround = Math.max(20, Math.round(layoutInfo.height / lineHeight));608const partialData = this.viewLayout.getLinesViewportData();609const startViewLineNumber = Math.max(1, partialData.completelyVisibleStartLineNumber - linesAround);610const endViewLineNumber = Math.min(this.getLineCount(), partialData.completelyVisibleEndLineNumber + linesAround);611612return this._toModelVisibleRanges(new Range(613startViewLineNumber, this.getLineMinColumn(startViewLineNumber),614endViewLineNumber, this.getLineMaxColumn(endViewLineNumber)615));616}617618public getVisibleRanges(): Range[] {619const visibleViewRange = this.getCompletelyVisibleViewRange();620return this._toModelVisibleRanges(visibleViewRange);621}622623public getHiddenAreas(): Range[] {624return this._lines.getHiddenAreas();625}626627private _toModelVisibleRanges(visibleViewRange: Range): Range[] {628const visibleRange = this.coordinatesConverter.convertViewRangeToModelRange(visibleViewRange);629const hiddenAreas = this._lines.getHiddenAreas();630631if (hiddenAreas.length === 0) {632return [visibleRange];633}634635const result: Range[] = [];636let resultLen = 0;637let startLineNumber = visibleRange.startLineNumber;638let startColumn = visibleRange.startColumn;639const endLineNumber = visibleRange.endLineNumber;640const endColumn = visibleRange.endColumn;641for (let i = 0, len = hiddenAreas.length; i < len; i++) {642const hiddenStartLineNumber = hiddenAreas[i].startLineNumber;643const hiddenEndLineNumber = hiddenAreas[i].endLineNumber;644645if (hiddenEndLineNumber < startLineNumber) {646continue;647}648if (hiddenStartLineNumber > endLineNumber) {649continue;650}651652if (startLineNumber < hiddenStartLineNumber) {653result[resultLen++] = new Range(654startLineNumber, startColumn,655hiddenStartLineNumber - 1, this.model.getLineMaxColumn(hiddenStartLineNumber - 1)656);657}658startLineNumber = hiddenEndLineNumber + 1;659startColumn = 1;660}661662if (startLineNumber < endLineNumber || (startLineNumber === endLineNumber && startColumn < endColumn)) {663result[resultLen++] = new Range(664startLineNumber, startColumn,665endLineNumber, endColumn666);667}668669return result;670}671672public getCompletelyVisibleViewRange(): Range {673const partialData = this.viewLayout.getLinesViewportData();674const startViewLineNumber = partialData.completelyVisibleStartLineNumber;675const endViewLineNumber = partialData.completelyVisibleEndLineNumber;676677return new Range(678startViewLineNumber, this.getLineMinColumn(startViewLineNumber),679endViewLineNumber, this.getLineMaxColumn(endViewLineNumber)680);681}682683public getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range {684const partialData = this.viewLayout.getLinesViewportDataAtScrollTop(scrollTop);685const startViewLineNumber = partialData.completelyVisibleStartLineNumber;686const endViewLineNumber = partialData.completelyVisibleEndLineNumber;687688return new Range(689startViewLineNumber, this.getLineMinColumn(startViewLineNumber),690endViewLineNumber, this.getLineMaxColumn(endViewLineNumber)691);692}693694public saveState(): IViewState {695const compatViewState = this.viewLayout.saveState();696697const scrollTop = compatViewState.scrollTop;698const firstViewLineNumber = this.viewLayout.getLineNumberAtVerticalOffset(scrollTop);699const firstPosition = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(firstViewLineNumber, this.getLineMinColumn(firstViewLineNumber)));700const firstPositionDeltaTop = this.viewLayout.getVerticalOffsetForLineNumber(firstViewLineNumber) - scrollTop;701702return {703scrollLeft: compatViewState.scrollLeft,704firstPosition: firstPosition,705firstPositionDeltaTop: firstPositionDeltaTop706};707}708709public reduceRestoreState(state: IViewState): { scrollLeft: number; scrollTop: number } {710if (typeof state.firstPosition === 'undefined') {711// This is a view state serialized by an older version712return this._reduceRestoreStateCompatibility(state);713}714715const modelPosition = this.model.validatePosition(state.firstPosition);716const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);717const scrollTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber) - state.firstPositionDeltaTop;718return {719scrollLeft: state.scrollLeft,720scrollTop: scrollTop721};722}723724private _reduceRestoreStateCompatibility(state: IViewState): { scrollLeft: number; scrollTop: number } {725return {726scrollLeft: state.scrollLeft,727scrollTop: state.scrollTopWithoutViewZones!728};729}730731private getTabSize(): number {732return this.model.getOptions().tabSize;733}734735public getLineCount(): number {736return this._lines.getViewLineCount();737}738739/**740* Gives a hint that a lot of requests are about to come in for these line numbers.741*/742public setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void {743this._viewportStart.update(this, startLineNumber);744}745746public getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {747return this._lines.getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber);748}749750public getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[] {751return this._lines.getViewLinesIndentGuides(startLineNumber, endLineNumber);752}753754public getBracketGuidesInRangeByLine(startLineNumber: number, endLineNumber: number, activePosition: IPosition | null, options: BracketGuideOptions): IndentGuide[][] {755return this._lines.getViewLinesBracketGuides(startLineNumber, endLineNumber, activePosition, options);756}757758public getLineContent(lineNumber: number): string {759return this._lines.getViewLineContent(lineNumber);760}761762public getLineLength(lineNumber: number): number {763return this._lines.getViewLineLength(lineNumber);764}765766public getLineMinColumn(lineNumber: number): number {767return this._lines.getViewLineMinColumn(lineNumber);768}769770public getLineMaxColumn(lineNumber: number): number {771return this._lines.getViewLineMaxColumn(lineNumber);772}773774public getLineFirstNonWhitespaceColumn(lineNumber: number): number {775const result = strings.firstNonWhitespaceIndex(this.getLineContent(lineNumber));776if (result === -1) {777return 0;778}779return result + 1;780}781782public getLineLastNonWhitespaceColumn(lineNumber: number): number {783const result = strings.lastNonWhitespaceIndex(this.getLineContent(lineNumber));784if (result === -1) {785return 0;786}787return result + 2;788}789790public getMinimapDecorationsInRange(range: Range): ViewModelDecoration[] {791return this._decorations.getMinimapDecorationsInRange(range);792}793794public getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[] {795return this._decorations.getDecorationsViewportData(visibleRange).decorations;796}797798public getInjectedTextAt(viewPosition: Position): InjectedText | null {799return this._lines.getInjectedTextAt(viewPosition);800}801802private _getTextDirection(lineNumber: number, decorations: ViewModelDecoration[]): TextDirection {803let rtlCount = 0;804805for (const decoration of decorations) {806const range = decoration.range;807if (range.startLineNumber > lineNumber || range.endLineNumber < lineNumber) {808continue;809}810const textDirection = decoration.options.textDirection;811if (textDirection === TextDirection.RTL) {812rtlCount++;813} else if (textDirection === TextDirection.LTR) {814rtlCount--;815}816}817818return rtlCount > 0 ? TextDirection.RTL : TextDirection.LTR;819}820821public getTextDirection(lineNumber: number): TextDirection {822const decorationsCollection = this._decorations.getDecorationsOnLine(lineNumber);823return this._getTextDirection(lineNumber, decorationsCollection.decorations);824}825826public getViewportViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData {827const viewportDecorationsCollection = this._decorations.getDecorationsViewportData(visibleRange);828const inlineDecorations = viewportDecorationsCollection.inlineDecorations[lineNumber - visibleRange.startLineNumber];829return this._getViewLineRenderingData(lineNumber, inlineDecorations, viewportDecorationsCollection.hasVariableFonts, viewportDecorationsCollection.decorations);830}831832public getViewLineRenderingData(lineNumber: number): ViewLineRenderingData {833const decorationsCollection = this._decorations.getDecorationsOnLine(lineNumber);834return this._getViewLineRenderingData(lineNumber, decorationsCollection.inlineDecorations[0], decorationsCollection.hasVariableFonts, decorationsCollection.decorations);835}836837private _getViewLineRenderingData(lineNumber: number, inlineDecorations: InlineDecoration[], hasVariableFonts: boolean, decorations: ViewModelDecoration[]): ViewLineRenderingData {838const mightContainRTL = this.model.mightContainRTL();839const mightContainNonBasicASCII = this.model.mightContainNonBasicASCII();840const tabSize = this.getTabSize();841const lineData = this._lines.getViewLineData(lineNumber);842843if (lineData.inlineDecorations) {844inlineDecorations = [845...inlineDecorations,846...lineData.inlineDecorations.map(d =>847d.toInlineDecoration(lineNumber)848)849];850}851852return new ViewLineRenderingData(853lineData.minColumn,854lineData.maxColumn,855lineData.content,856lineData.continuesWithWrappedLine,857mightContainRTL,858mightContainNonBasicASCII,859lineData.tokens,860inlineDecorations,861tabSize,862lineData.startVisibleColumn,863this._getTextDirection(lineNumber, decorations),864hasVariableFonts865);866}867868public getViewLineData(lineNumber: number): ViewLineData {869return this._lines.getViewLineData(lineNumber);870}871872public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData {873const result = this._lines.getViewLinesData(startLineNumber, endLineNumber, needed);874return new MinimapLinesRenderingData(875this.getTabSize(),876result877);878}879880public getAllOverviewRulerDecorations(theme: EditorTheme): OverviewRulerDecorationsGroup[] {881const decorations = this.model.getOverviewRulerDecorations(this._editorId, filterValidationDecorations(this._configuration.options), filterFontDecorations(this._configuration.options));882const result = new OverviewRulerDecorations();883for (const decoration of decorations) {884const decorationOptions = <ModelDecorationOptions>decoration.options;885const opts = decorationOptions.overviewRuler;886if (!opts) {887continue;888}889const lane = <number>opts.position;890if (lane === 0) {891continue;892}893const color = opts.getColor(theme.value);894const viewStartLineNumber = this.coordinatesConverter.getViewLineNumberOfModelPosition(decoration.range.startLineNumber, decoration.range.startColumn);895const viewEndLineNumber = this.coordinatesConverter.getViewLineNumberOfModelPosition(decoration.range.endLineNumber, decoration.range.endColumn);896897result.accept(color, decorationOptions.zIndex, viewStartLineNumber, viewEndLineNumber, lane);898}899return result.asArray;900}901902private _invalidateDecorationsColorCache(): void {903const decorations = this.model.getOverviewRulerDecorations();904for (const decoration of decorations) {905const opts1 = <ModelDecorationOverviewRulerOptions>decoration.options.overviewRuler;906opts1?.invalidateCachedColor();907const opts2 = <ModelDecorationMinimapOptions>decoration.options.minimap;908opts2?.invalidateCachedColor();909}910}911912public getValueInRange(range: Range, eol: EndOfLinePreference): string {913const modelRange = this.coordinatesConverter.convertViewRangeToModelRange(range);914return this.model.getValueInRange(modelRange, eol);915}916917public getValueLengthInRange(range: Range, eol: EndOfLinePreference): number {918const modelRange = this.coordinatesConverter.convertViewRangeToModelRange(range);919return this.model.getValueLengthInRange(modelRange, eol);920}921922public modifyPosition(position: Position, offset: number): Position {923const modelPosition = this.coordinatesConverter.convertViewPositionToModelPosition(position);924const resultModelPosition = this.model.modifyPosition(modelPosition, offset);925return this.coordinatesConverter.convertModelPositionToViewPosition(resultModelPosition);926}927928public deduceModelPositionRelativeToViewPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position {929const modelAnchor = this.coordinatesConverter.convertViewPositionToModelPosition(viewAnchorPosition);930if (this.model.getEOL().length === 2) {931// This model uses CRLF, so the delta must take that into account932if (deltaOffset < 0) {933deltaOffset -= lineFeedCnt;934} else {935deltaOffset += lineFeedCnt;936}937}938939const modelAnchorOffset = this.model.getOffsetAt(modelAnchor);940const resultOffset = modelAnchorOffset + deltaOffset;941return this.model.getPositionAt(resultOffset);942}943944public getPlainTextToCopy(modelRanges: Range[], emptySelectionClipboard: boolean, forceCRLF: boolean): string | string[] {945const newLineCharacter = forceCRLF ? '\r\n' : this.model.getEOL();946947modelRanges = modelRanges.slice(0);948modelRanges.sort(Range.compareRangesUsingStarts);949950let hasEmptyRange = false;951let hasNonEmptyRange = false;952for (const range of modelRanges) {953if (range.isEmpty()) {954hasEmptyRange = true;955} else {956hasNonEmptyRange = true;957}958}959960if (!hasNonEmptyRange) {961// all ranges are empty962if (!emptySelectionClipboard) {963return '';964}965966const modelLineNumbers = modelRanges.map((r) => r.startLineNumber);967968let result = '';969for (let i = 0; i < modelLineNumbers.length; i++) {970if (i > 0 && modelLineNumbers[i - 1] === modelLineNumbers[i]) {971continue;972}973result += this.model.getLineContent(modelLineNumbers[i]) + newLineCharacter;974}975return result;976}977978if (hasEmptyRange && emptySelectionClipboard) {979// mixed empty selections and non-empty selections980const result: string[] = [];981let prevModelLineNumber = 0;982for (const modelRange of modelRanges) {983const modelLineNumber = modelRange.startLineNumber;984if (modelRange.isEmpty()) {985if (modelLineNumber !== prevModelLineNumber) {986result.push(this.model.getLineContent(modelLineNumber));987}988} else {989result.push(this.model.getValueInRange(modelRange, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined));990}991prevModelLineNumber = modelLineNumber;992}993return result.length === 1 ? result[0] : result;994}995996const result: string[] = [];997for (const modelRange of modelRanges) {998if (!modelRange.isEmpty()) {999result.push(this.model.getValueInRange(modelRange, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined));1000}1001}1002return result.length === 1 ? result[0] : result;1003}10041005public getRichTextToCopy(modelRanges: Range[], emptySelectionClipboard: boolean): { html: string; mode: string } | null {1006const languageId = this.model.getLanguageId();1007if (languageId === PLAINTEXT_LANGUAGE_ID) {1008return null;1009}10101011if (modelRanges.length !== 1) {1012// no multiple selection support at this time1013return null;1014}10151016let range = modelRanges[0];1017if (range.isEmpty()) {1018if (!emptySelectionClipboard) {1019// nothing to copy1020return null;1021}1022const lineNumber = range.startLineNumber;1023range = new Range(lineNumber, this.model.getLineMinColumn(lineNumber), lineNumber, this.model.getLineMaxColumn(lineNumber));1024}10251026const fontInfo = this._configuration.options.get(EditorOption.fontInfo);1027const colorMap = this._getColorMap();1028const hasBadChars = (/[:;\\\/<>]/.test(fontInfo.fontFamily));1029const useDefaultFontFamily = (hasBadChars || fontInfo.fontFamily === EDITOR_FONT_DEFAULTS.fontFamily);1030let fontFamily: string;1031if (useDefaultFontFamily) {1032fontFamily = EDITOR_FONT_DEFAULTS.fontFamily;1033} else {1034fontFamily = fontInfo.fontFamily;1035fontFamily = fontFamily.replace(/"/g, '\'');1036const hasQuotesOrIsList = /[,']/.test(fontFamily);1037if (!hasQuotesOrIsList) {1038const needsQuotes = /[+ ]/.test(fontFamily);1039if (needsQuotes) {1040fontFamily = `'${fontFamily}'`;1041}1042}1043fontFamily = `${fontFamily}, ${EDITOR_FONT_DEFAULTS.fontFamily}`;1044}10451046return {1047mode: languageId,1048html: (1049`<div style="`1050+ `color: ${colorMap[ColorId.DefaultForeground]};`1051+ `background-color: ${colorMap[ColorId.DefaultBackground]};`1052+ `font-family: ${fontFamily};`1053+ `font-weight: ${fontInfo.fontWeight};`1054+ `font-size: ${fontInfo.fontSize}px;`1055+ `line-height: ${fontInfo.lineHeight}px;`1056+ `white-space: pre;`1057+ `">`1058+ this._getHTMLToCopy(range, colorMap)1059+ '</div>'1060)1061};1062}10631064private _getHTMLToCopy(modelRange: Range, colorMap: string[]): string {1065const startLineNumber = modelRange.startLineNumber;1066const startColumn = modelRange.startColumn;1067const endLineNumber = modelRange.endLineNumber;1068const endColumn = modelRange.endColumn;10691070const tabSize = this.getTabSize();10711072let result = '';10731074for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {1075const lineTokens = this.model.tokenization.getLineTokens(lineNumber);1076const lineContent = lineTokens.getLineContent();1077const startOffset = (lineNumber === startLineNumber ? startColumn - 1 : 0);1078const endOffset = (lineNumber === endLineNumber ? endColumn - 1 : lineContent.length);10791080if (lineContent === '') {1081result += '<br>';1082} else {1083result += tokenizeLineToHTML(lineContent, lineTokens.inflate(), colorMap, startOffset, endOffset, tabSize, platform.isWindows);1084}1085}10861087return result;1088}10891090private _getColorMap(): string[] {1091const colorMap = TokenizationRegistry.getColorMap();1092const result: string[] = ['#000000'];1093if (colorMap) {1094for (let i = 1, len = colorMap.length; i < len; i++) {1095result[i] = Color.Format.CSS.formatHex(colorMap[i]);1096}1097}1098return result;1099}11001101//#region cursor operations11021103public getPrimaryCursorState(): CursorState {1104return this._cursor.getPrimaryCursorState();1105}1106public getLastAddedCursorIndex(): number {1107return this._cursor.getLastAddedCursorIndex();1108}1109public getCursorStates(): CursorState[] {1110return this._cursor.getCursorStates();1111}1112public setCursorStates(source: string | null | undefined, reason: CursorChangeReason, states: PartialCursorState[] | null): boolean {1113return this._withViewEventsCollector(eventsCollector => this._cursor.setStates(eventsCollector, source, reason, states));1114}1115public getCursorColumnSelectData(): IColumnSelectData {1116return this._cursor.getCursorColumnSelectData();1117}1118public getCursorAutoClosedCharacters(): Range[] {1119return this._cursor.getAutoClosedCharacters();1120}1121public setCursorColumnSelectData(columnSelectData: IColumnSelectData): void {1122this._cursor.setCursorColumnSelectData(columnSelectData);1123}1124public getPrevEditOperationType(): EditOperationType {1125return this._cursor.getPrevEditOperationType();1126}1127public setPrevEditOperationType(type: EditOperationType): void {1128this._cursor.setPrevEditOperationType(type);1129}1130public getSelection(): Selection {1131return this._cursor.getSelection();1132}1133public getSelections(): Selection[] {1134return this._cursor.getSelections();1135}1136public getPosition(): Position {1137return this._cursor.getPrimaryCursorState().modelState.position;1138}1139public setSelections(source: string | null | undefined, selections: readonly ISelection[], reason = CursorChangeReason.NotSet): void {1140this._withViewEventsCollector(eventsCollector => this._cursor.setSelections(eventsCollector, source, selections, reason));1141}1142public saveCursorState(): ICursorState[] {1143return this._cursor.saveState();1144}1145public restoreCursorState(states: ICursorState[]): void {1146this._withViewEventsCollector(eventsCollector => this._cursor.restoreState(eventsCollector, states));1147}11481149private _executeCursorEdit(callback: (eventsCollector: ViewModelEventsCollector) => void): void {1150if (this._cursor.context.cursorConfig.readOnly) {1151// we cannot edit when read only...1152this._eventDispatcher.emitOutgoingEvent(new ReadOnlyEditAttemptEvent());1153return;1154}1155this._withViewEventsCollector(callback);1156}1157public executeEdits(source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer, reason: TextModelEditSource): void {1158this._executeCursorEdit(eventsCollector => this._cursor.executeEdits(eventsCollector, source, edits, cursorStateComputer, reason));1159}1160public startComposition(): void {1161this._executeCursorEdit(eventsCollector => this._cursor.startComposition(eventsCollector));1162}1163public endComposition(source?: string | null | undefined): void {1164this._executeCursorEdit(eventsCollector => this._cursor.endComposition(eventsCollector, source));1165}1166public type(text: string, source?: string | null | undefined): void {1167this._executeCursorEdit(eventsCollector => this._cursor.type(eventsCollector, text, source));1168}1169public compositionType(text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number, source?: string | null | undefined): void {1170this._executeCursorEdit(eventsCollector => this._cursor.compositionType(eventsCollector, text, replacePrevCharCnt, replaceNextCharCnt, positionDelta, source));1171}1172public paste(text: string, pasteOnNewLine: boolean, multicursorText?: string[] | null | undefined, source?: string | null | undefined): void {1173this._executeCursorEdit(eventsCollector => this._cursor.paste(eventsCollector, text, pasteOnNewLine, multicursorText, source));1174}1175public cut(source?: string | null | undefined): void {1176this._executeCursorEdit(eventsCollector => this._cursor.cut(eventsCollector, source));1177}1178public executeCommand(command: ICommand, source?: string | null | undefined): void {1179this._executeCursorEdit(eventsCollector => this._cursor.executeCommand(eventsCollector, command, source));1180}1181public executeCommands(commands: ICommand[], source?: string | null | undefined): void {1182this._executeCursorEdit(eventsCollector => this._cursor.executeCommands(eventsCollector, commands, source));1183}1184public revealAllCursors(source: string | null | undefined, revealHorizontal: boolean, minimalReveal: boolean = false): void {1185this._withViewEventsCollector(eventsCollector => this._cursor.revealAll(eventsCollector, source, minimalReveal, viewEvents.VerticalRevealType.Simple, revealHorizontal, ScrollType.Smooth));1186}1187public revealPrimaryCursor(source: string | null | undefined, revealHorizontal: boolean, minimalReveal: boolean = false): void {1188this._withViewEventsCollector(eventsCollector => this._cursor.revealPrimary(eventsCollector, source, minimalReveal, viewEvents.VerticalRevealType.Simple, revealHorizontal, ScrollType.Smooth));1189}1190public revealTopMostCursor(source: string | null | undefined): void {1191const viewPosition = this._cursor.getTopMostViewPosition();1192const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column);1193this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, false, viewRange, null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Smooth)));1194}1195public revealBottomMostCursor(source: string | null | undefined): void {1196const viewPosition = this._cursor.getBottomMostViewPosition();1197const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column);1198this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, false, viewRange, null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Smooth)));1199}1200public revealRange(source: string | null | undefined, revealHorizontal: boolean, viewRange: Range, verticalType: viewEvents.VerticalRevealType, scrollType: ScrollType): void {1201this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, false, viewRange, null, verticalType, revealHorizontal, scrollType)));1202}12031204//#endregion12051206//#region viewLayout1207public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => void): void {1208const hadAChange = this.viewLayout.changeWhitespace(callback);1209if (hadAChange) {1210this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewZonesChangedEvent());1211this._eventDispatcher.emitOutgoingEvent(new ViewZonesChangedEvent());1212}1213}1214//#endregion12151216private _withViewEventsCollector<T>(callback: (eventsCollector: ViewModelEventsCollector) => T): T {1217return this._transactionalTarget.batchChanges(() => {1218try {1219const eventsCollector = this._eventDispatcher.beginEmitViewEvents();1220return callback(eventsCollector);1221} finally {1222this._eventDispatcher.endEmitViewEvents();1223}1224});1225}12261227public batchEvents(callback: () => void): void {1228this._withViewEventsCollector(() => { callback(); });1229}12301231normalizePosition(position: Position, affinity: PositionAffinity): Position {1232return this._lines.normalizePosition(position, affinity);1233}12341235/**1236* Gets the column at which indentation stops at a given line.1237* @internal1238*/1239getLineIndentColumn(lineNumber: number): number {1240return this._lines.getLineIndentColumn(lineNumber);1241}1242}12431244export interface IBatchableTarget {1245/**1246* Allows the target to apply the changes introduced by the callback in a batch.1247*/1248batchChanges<T>(cb: () => T): T;1249}12501251class ViewportStart implements IDisposable {12521253public static create(model: ITextModel): ViewportStart {1254const viewportStartLineTrackedRange = model._setTrackedRange(null, new Range(1, 1, 1, 1), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);1255return new ViewportStart(model, 1, false, viewportStartLineTrackedRange, 0);1256}12571258public get viewLineNumber(): number {1259return this._viewLineNumber;1260}12611262public get isValid(): boolean {1263return this._isValid;1264}12651266public get modelTrackedRange(): string {1267return this._modelTrackedRange;1268}12691270public get startLineDelta(): number {1271return this._startLineDelta;1272}12731274private constructor(1275private readonly _model: ITextModel,1276private _viewLineNumber: number,1277private _isValid: boolean,1278private _modelTrackedRange: string,1279private _startLineDelta: number,1280) { }12811282public dispose(): void {1283this._model._setTrackedRange(this._modelTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);1284}12851286public update(viewModel: IViewModel, startLineNumber: number): void {1287const position = viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(startLineNumber, viewModel.getLineMinColumn(startLineNumber)));1288const viewportStartLineTrackedRange = viewModel.model._setTrackedRange(this._modelTrackedRange, new Range(position.lineNumber, position.column, position.lineNumber, position.column), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);1289const viewportStartLineTop = viewModel.viewLayout.getVerticalOffsetForLineNumber(startLineNumber);1290const scrollTop = viewModel.viewLayout.getCurrentScrollTop();12911292this._viewLineNumber = startLineNumber;1293this._isValid = true;1294this._modelTrackedRange = viewportStartLineTrackedRange;1295this._startLineDelta = scrollTop - viewportStartLineTop;1296}12971298public invalidate(): void {1299this._isValid = false;1300}1301}13021303class OverviewRulerDecorations {13041305private readonly _asMap: { [color: string]: OverviewRulerDecorationsGroup } = Object.create(null);1306readonly asArray: OverviewRulerDecorationsGroup[] = [];13071308public accept(color: string, zIndex: number, startLineNumber: number, endLineNumber: number, lane: number): void {1309const prevGroup = this._asMap[color];13101311if (prevGroup) {1312const prevData = prevGroup.data;1313const prevLane = prevData[prevData.length - 3];1314const prevEndLineNumber = prevData[prevData.length - 1];1315if (prevLane === lane && prevEndLineNumber + 1 >= startLineNumber) {1316// merge into prev1317if (endLineNumber > prevEndLineNumber) {1318prevData[prevData.length - 1] = endLineNumber;1319}1320return;1321}13221323// push1324prevData.push(lane, startLineNumber, endLineNumber);1325} else {1326const group = new OverviewRulerDecorationsGroup(color, zIndex, [lane, startLineNumber, endLineNumber]);1327this._asMap[color] = group;1328this.asArray.push(group);1329}1330}1331}13321333class HiddenAreasModel {1334private readonly hiddenAreas = new Map<unknown, Range[]>();1335private shouldRecompute = false;1336private ranges: Range[] = [];13371338setHiddenAreas(source: unknown, ranges: Range[]): void {1339const existing = this.hiddenAreas.get(source);1340if (existing && rangeArraysEqual(existing, ranges)) {1341return;1342}1343this.hiddenAreas.set(source, ranges);1344this.shouldRecompute = true;1345}13461347/**1348* The returned array is immutable.1349*/1350getMergedRanges(): readonly Range[] {1351if (!this.shouldRecompute) {1352return this.ranges;1353}1354this.shouldRecompute = false;1355const newRanges = Array.from(this.hiddenAreas.values()).reduce((r, hiddenAreas) => mergeLineRangeArray(r, hiddenAreas), []);1356if (rangeArraysEqual(this.ranges, newRanges)) {1357return this.ranges;1358}1359this.ranges = newRanges;1360return this.ranges;1361}1362}13631364function mergeLineRangeArray(arr1: Range[], arr2: Range[]): Range[] {1365const result: Range[] = [];1366let i = 0;1367let j = 0;1368while (i < arr1.length && j < arr2.length) {1369const item1 = arr1[i];1370const item2 = arr2[j];13711372if (item1.endLineNumber < item2.startLineNumber - 1) {1373result.push(arr1[i++]);1374} else if (item2.endLineNumber < item1.startLineNumber - 1) {1375result.push(arr2[j++]);1376} else {1377const startLineNumber = Math.min(item1.startLineNumber, item2.startLineNumber);1378const endLineNumber = Math.max(item1.endLineNumber, item2.endLineNumber);1379result.push(new Range(startLineNumber, 1, endLineNumber, 1));1380i++;1381j++;1382}1383}1384while (i < arr1.length) {1385result.push(arr1[i++]);1386}1387while (j < arr2.length) {1388result.push(arr2[j++]);1389}1390return result;1391}13921393function rangeArraysEqual(arr1: Range[], arr2: Range[]): boolean {1394if (arr1.length !== arr2.length) {1395return false;1396}1397for (let i = 0; i < arr1.length; i++) {1398if (!arr1[i].equalsRange(arr2[i])) {1399return false;1400}1401}1402return true;1403}14041405/**1406* Maintain a stable viewport by trying to keep the first line in the viewport constant.1407*/1408class StableViewport {1409constructor(1410public readonly viewportStartModelPosition: Position | null,1411public readonly startLineDelta: number1412) { }14131414public recoverViewportStart(coordinatesConverter: ICoordinatesConverter, viewLayout: ViewLayout): void {1415if (!this.viewportStartModelPosition) {1416return;1417}1418const viewPosition = coordinatesConverter.convertModelPositionToViewPosition(this.viewportStartModelPosition);1419const viewPositionTop = viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber);1420viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this.startLineDelta }, ScrollType.Immediate);1421}1422}142314241425