Path: blob/main/src/vs/editor/common/viewModel/viewModelImpl.ts
5226 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, filterValidationDecorations, filterFontDecorations, FindComputedEditorOptionValueById } from '../config/editorOptions.js';13import { EDITOR_FONT_DEFAULTS } from '../config/fontInfo.js';14import { CursorsController } from '../cursor/cursor.js';15import { CursorConfiguration, CursorState, EditOperationType, IColumnSelectData, PartialCursorState } from '../cursorCommon.js';16import { CursorChangeReason } from '../cursorEvents.js';17import { IPosition, Position } from '../core/position.js';18import { Range } from '../core/range.js';19import { ISelection, Selection } from '../core/selection.js';20import { ICommand, ICursorState, IViewState, ScrollType } from '../editorCommon.js';21import { IEditorConfiguration } from '../config/editorConfiguration.js';22import { EndOfLinePreference, IAttachedView, ICursorStateComputer, IGlyphMarginLanesModel, IIdentifiedSingleEditOperation, ITextModel, PositionAffinity, TextDirection, TrackedRangeStickiness } from '../model.js';23import { IActiveIndentGuideInfo, BracketGuideOptions, IndentGuide } from '../textModelGuides.js';24import { ModelDecorationMinimapOptions, ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from '../model/textModel.js';25import * as textModelEvents from '../textModelEvents.js';26import { TokenizationRegistry } from '../languages.js';27import { ColorId } from '../encodedTokenAttributes.js';28import { ILanguageConfigurationService } from '../languages/languageConfigurationRegistry.js';29import { PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js';30import { tokenizeLineToHTML } from '../languages/textToHtmlTokenizer.js';31import { EditorTheme } from '../editorTheme.js';32import * as viewEvents from '../viewEvents.js';33import { ViewLayout } from '../viewLayout/viewLayout.js';34import { MinimapTokensColorTracker } from './minimapTokensColorTracker.js';35import { ILineBreaksComputer, ILineBreaksComputerFactory, InjectedText } from '../modelLineProjectionData.js';36import { ViewEventHandler } from '../viewEventHandler.js';37import { ILineHeightChangeAccessor, IViewModel, IWhitespaceChangeAccessor, MinimapLinesRenderingData, OverviewRulerDecorationsGroup, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from '../viewModel.js';38import { ViewModelDecorations } from './viewModelDecorations.js';39import { FocusChangedEvent, HiddenAreasChangedEvent, ModelContentChangedEvent, ModelDecorationsChangedEvent, ModelFontChangedEvent, ModelLanguageChangedEvent, ModelLanguageConfigurationChangedEvent, ModelLineHeightChangedEvent, ModelOptionsChangedEvent, ModelTokensChangedEvent, OutgoingViewModelEvent, ReadOnlyEditAttemptEvent, ScrollChangedEvent, ViewModelEventDispatcher, ViewModelEventsCollector, ViewZonesChangedEvent, WidgetFocusChangedEvent } from '../viewModelEventDispatcher.js';40import { IViewModelLines, ViewModelLinesFromModelAsIs, ViewModelLinesFromProjectedModel } from './viewModelLines.js';41import { IThemeService } from '../../../platform/theme/common/themeService.js';42import { GlyphMarginLanesModel } from './glyphLanesModel.js';43import { ICustomLineHeightData } from '../viewLayout/lineHeights.js';44import { TextModelEditSource } from '../textModelEditSource.js';45import { InlineDecoration } from './inlineDecorations.js';46import { ICoordinatesConverter } from '../coordinatesConverter.js';4748const USE_IDENTITY_LINES_COLLECTION = true;4950export class ViewModel extends Disposable implements IViewModel {5152private readonly _editorId: number;53private readonly _configuration: IEditorConfiguration;54public readonly model: ITextModel;55private readonly _eventDispatcher: ViewModelEventDispatcher;56public readonly onEvent: Event<OutgoingViewModelEvent>;57public cursorConfig: CursorConfiguration;58private readonly _updateConfigurationViewLineCount: RunOnceScheduler;59private _hasFocus: boolean;60private readonly _viewportStart: ViewportStart;61private readonly _lines: IViewModelLines;62public readonly coordinatesConverter: ICoordinatesConverter;63public readonly viewLayout: ViewLayout;64private readonly _cursor: CursorsController;65private readonly _decorations: ViewModelDecorations;66public readonly glyphLanes: IGlyphMarginLanesModel;6768constructor(69editorId: number,70configuration: IEditorConfiguration,71model: ITextModel,72domLineBreaksComputerFactory: ILineBreaksComputerFactory,73monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory,74scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable,75private readonly languageConfigurationService: ILanguageConfigurationService,76private readonly _themeService: IThemeService,77private readonly _attachedView: IAttachedView,78private readonly _transactionalTarget: IBatchableTarget,79) {80super();8182this._editorId = editorId;83this._configuration = configuration;84this.model = model;85this._eventDispatcher = new ViewModelEventDispatcher();86this.onEvent = this._eventDispatcher.onEvent;87this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService);88this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0));89this._hasFocus = false;90this._viewportStart = ViewportStart.create(this.model);91this.glyphLanes = new GlyphMarginLanesModel(0);9293if (USE_IDENTITY_LINES_COLLECTION && this.model.isTooLargeForTokenization()) {9495this._lines = new ViewModelLinesFromModelAsIs(this.model);9697} else {98const options = this._configuration.options;99const fontInfo = options.get(EditorOption.fontInfo);100const wrappingStrategy = options.get(EditorOption.wrappingStrategy);101const wrappingInfo = options.get(EditorOption.wrappingInfo);102const wrappingIndent = options.get(EditorOption.wrappingIndent);103const wordBreak = options.get(EditorOption.wordBreak);104const wrapOnEscapedLineFeeds = options.get(EditorOption.wrapOnEscapedLineFeeds);105106this._lines = new ViewModelLinesFromProjectedModel(107this._editorId,108this.model,109domLineBreaksComputerFactory,110monospaceLineBreaksComputerFactory,111fontInfo,112this.model.getOptions().tabSize,113wrappingStrategy,114wrappingInfo.wrappingColumn,115wrappingIndent,116wordBreak,117wrapOnEscapedLineFeeds118);119}120121this.coordinatesConverter = this._lines.createCoordinatesConverter();122123this._cursor = this._register(new CursorsController(model, this, this.coordinatesConverter, this.cursorConfig));124125this.viewLayout = this._register(new ViewLayout(this._configuration, this.getLineCount(), this._getCustomLineHeights(), scheduleAtNextAnimationFrame));126127this._register(this.viewLayout.onDidScroll((e) => {128if (e.scrollTopChanged) {129this._handleVisibleLinesChanged();130}131if (e.scrollTopChanged) {132this._viewportStart.invalidate();133}134this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewScrollChangedEvent(e));135this._eventDispatcher.emitOutgoingEvent(new ScrollChangedEvent(136e.oldScrollWidth, e.oldScrollLeft, e.oldScrollHeight, e.oldScrollTop,137e.scrollWidth, e.scrollLeft, e.scrollHeight, e.scrollTop138));139}));140141this._register(this.viewLayout.onDidContentSizeChange((e) => {142this._eventDispatcher.emitOutgoingEvent(e);143}));144145this._decorations = new ViewModelDecorations(this._editorId, this.model, this._configuration, this._lines, this.coordinatesConverter);146147this._registerModelEvents();148149this._register(this._configuration.onDidChangeFast((e) => {150try {151const eventsCollector = this._eventDispatcher.beginEmitViewEvents();152this._onConfigurationChanged(eventsCollector, e);153} finally {154this._eventDispatcher.endEmitViewEvents();155}156}));157158this._register(MinimapTokensColorTracker.getInstance().onDidChange(() => {159this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewTokensColorsChangedEvent());160}));161162this._register(this._themeService.onDidColorThemeChange((theme) => {163this._invalidateDecorationsColorCache();164this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewThemeChangedEvent(theme));165}));166167this._updateConfigurationViewLineCountNow();168this.model.registerViewModel(this);169}170171public override dispose(): void {172// First remove listeners, as disposing the lines might end up sending173// model decoration changed events ... and we no longer care about them ...174super.dispose();175this._decorations.dispose();176this._lines.dispose();177this._viewportStart.dispose();178this._eventDispatcher.dispose();179this.model.unregisterViewModel(this);180}181182public getEditorOption<T extends EditorOption>(id: T): FindComputedEditorOptionValueById<T> {183return this._configuration.options.get(id);184}185186public createLineBreaksComputer(): ILineBreaksComputer {187return this._lines.createLineBreaksComputer();188}189190public addViewEventHandler(eventHandler: ViewEventHandler): void {191this._eventDispatcher.addViewEventHandler(eventHandler);192}193194public removeViewEventHandler(eventHandler: ViewEventHandler): void {195this._eventDispatcher.removeViewEventHandler(eventHandler);196}197198private _getCustomLineHeights(): ICustomLineHeightData[] {199const allowVariableLineHeights = this._configuration.options.get(EditorOption.allowVariableLineHeights);200if (!allowVariableLineHeights) {201return [];202}203const defaultLineHeight = this._configuration.options.get(EditorOption.lineHeight);204const decorations = this.model.getCustomLineHeightsDecorations(this._editorId);205return decorations.map((d) => {206const lineNumber = d.range.startLineNumber;207const viewRange = this.coordinatesConverter.convertModelRangeToViewRange(new Range(lineNumber, 1, lineNumber, this.model.getLineMaxColumn(lineNumber)));208return {209decorationId: d.id,210startLineNumber: viewRange.startLineNumber,211endLineNumber: viewRange.endLineNumber,212lineHeight: d.options.lineHeight ? d.options.lineHeight * defaultLineHeight : 0213};214});215}216217private _updateConfigurationViewLineCountNow(): void {218this._configuration.setViewLineCount(this._lines.getViewLineCount());219}220221private getModelVisibleRanges(): Range[] {222const linesViewportData = this.viewLayout.getLinesViewportData();223const viewVisibleRange = new Range(224linesViewportData.startLineNumber,225this.getLineMinColumn(linesViewportData.startLineNumber),226linesViewportData.endLineNumber,227this.getLineMaxColumn(linesViewportData.endLineNumber)228);229const modelVisibleRanges = this._toModelVisibleRanges(viewVisibleRange);230return modelVisibleRanges;231}232233public visibleLinesStabilized(): void {234const modelVisibleRanges = this.getModelVisibleRanges();235this._attachedView.setVisibleLines(modelVisibleRanges, true);236}237238private _handleVisibleLinesChanged(): void {239const modelVisibleRanges = this.getModelVisibleRanges();240this._attachedView.setVisibleLines(modelVisibleRanges, false);241}242243public setHasFocus(hasFocus: boolean): void {244this._hasFocus = hasFocus;245this._cursor.setHasFocus(hasFocus);246this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewFocusChangedEvent(hasFocus));247this._eventDispatcher.emitOutgoingEvent(new FocusChangedEvent(!hasFocus, hasFocus));248}249250public setHasWidgetFocus(hasWidgetFocus: boolean): void {251this._eventDispatcher.emitOutgoingEvent(new WidgetFocusChangedEvent(!hasWidgetFocus, hasWidgetFocus));252}253254public onCompositionStart(): void {255this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionStartEvent());256}257258public onCompositionEnd(): void {259this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionEndEvent());260}261262private _captureStableViewport(): StableViewport {263// We might need to restore the current start view range, so save it (if available)264// But only if the scroll position is not at the top of the file265if (this._viewportStart.isValid && this.viewLayout.getCurrentScrollTop() > 0) {266const previousViewportStartViewPosition = new Position(this._viewportStart.viewLineNumber, this.getLineMinColumn(this._viewportStart.viewLineNumber));267const previousViewportStartModelPosition = this.coordinatesConverter.convertViewPositionToModelPosition(previousViewportStartViewPosition);268return new StableViewport(previousViewportStartModelPosition, this._viewportStart.startLineDelta);269}270return new StableViewport(null, 0);271}272273private _onConfigurationChanged(eventsCollector: ViewModelEventsCollector, e: ConfigurationChangedEvent): void {274const stableViewport = this._captureStableViewport();275const options = this._configuration.options;276const fontInfo = options.get(EditorOption.fontInfo);277const wrappingStrategy = options.get(EditorOption.wrappingStrategy);278const wrappingInfo = options.get(EditorOption.wrappingInfo);279const wrappingIndent = options.get(EditorOption.wrappingIndent);280const wordBreak = options.get(EditorOption.wordBreak);281282if (this._lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent, wordBreak)) {283eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent());284eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent());285eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));286this._cursor.onLineMappingChanged(eventsCollector);287this._decorations.onLineMappingChanged();288this.viewLayout.onFlushed(this.getLineCount(), this._getCustomLineHeights());289290this._updateConfigurationViewLineCount.schedule();291}292293if (e.hasChanged(EditorOption.readOnly)) {294// Must read again all decorations due to readOnly filtering295this._decorations.reset();296eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));297}298299if (e.hasChanged(EditorOption.renderValidationDecorations)) {300this._decorations.reset();301eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));302}303304eventsCollector.emitViewEvent(new viewEvents.ViewConfigurationChangedEvent(e));305this.viewLayout.onConfigurationChanged(e);306307stableViewport.recoverViewportStart(this.coordinatesConverter, this.viewLayout);308309if (CursorConfiguration.shouldRecreate(e)) {310this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService);311this._cursor.updateConfiguration(this.cursorConfig);312}313}314315/**316* Gets called directly by the text model.317*/318onDidChangeContentOrInjectedText(e: textModelEvents.InternalModelContentChangeEvent | textModelEvents.ModelInjectedTextChangedEvent): void {319320try {321const eventsCollector = this._eventDispatcher.beginEmitViewEvents();322323let hadOtherModelChange = false;324let hadModelLineChangeThatChangedLineMapping = false;325326const changes = (e instanceof textModelEvents.InternalModelContentChangeEvent ? e.rawContentChangedEvent.changes : e.changes);327const versionId = (e instanceof textModelEvents.InternalModelContentChangeEvent ? e.rawContentChangedEvent.versionId : null);328329// Do a first pass to compute line mappings, and a second pass to actually interpret them330const lineBreaksComputer = this._lines.createLineBreaksComputer();331for (const change of changes) {332switch (change.changeType) {333case textModelEvents.RawContentChangedType.LinesInserted: {334for (let lineIdx = 0; lineIdx < change.detail.length; lineIdx++) {335const line = change.detail[lineIdx];336let injectedText = change.injectedTexts[lineIdx];337if (injectedText) {338injectedText = injectedText.filter(element => (!element.ownerId || element.ownerId === this._editorId));339}340lineBreaksComputer.addRequest(line, injectedText, null);341}342break;343}344case textModelEvents.RawContentChangedType.LineChanged: {345let injectedText: textModelEvents.LineInjectedText[] | null = null;346if (change.injectedText) {347injectedText = change.injectedText.filter(element => (!element.ownerId || element.ownerId === this._editorId));348}349lineBreaksComputer.addRequest(change.detail, injectedText, null);350break;351}352}353}354const lineBreaks = lineBreaksComputer.finalize();355const lineBreakQueue = new ArrayQueue(lineBreaks);356357for (const change of changes) {358switch (change.changeType) {359case textModelEvents.RawContentChangedType.Flush: {360this._lines.onModelFlushed();361eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent());362this._decorations.reset();363this.viewLayout.onFlushed(this.getLineCount(), this._getCustomLineHeights());364hadOtherModelChange = true;365break;366}367case textModelEvents.RawContentChangedType.LinesDeleted: {368const linesDeletedEvent = this._lines.onModelLinesDeleted(versionId, change.fromLineNumber, change.toLineNumber);369if (linesDeletedEvent !== null) {370eventsCollector.emitViewEvent(linesDeletedEvent);371this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber);372}373hadOtherModelChange = true;374break;375}376case textModelEvents.RawContentChangedType.LinesInserted: {377const insertedLineBreaks = lineBreakQueue.takeCount(change.detail.length);378const linesInsertedEvent = this._lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks);379if (linesInsertedEvent !== null) {380eventsCollector.emitViewEvent(linesInsertedEvent);381this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);382}383hadOtherModelChange = true;384break;385}386case textModelEvents.RawContentChangedType.LineChanged: {387const changedLineBreakData = lineBreakQueue.dequeue()!;388const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] =389this._lines.onModelLineChanged(versionId, change.lineNumber, changedLineBreakData);390hadModelLineChangeThatChangedLineMapping = lineMappingChanged;391if (linesChangedEvent) {392eventsCollector.emitViewEvent(linesChangedEvent);393}394if (linesInsertedEvent) {395eventsCollector.emitViewEvent(linesInsertedEvent);396this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);397}398if (linesDeletedEvent) {399eventsCollector.emitViewEvent(linesDeletedEvent);400this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber);401}402break;403}404case textModelEvents.RawContentChangedType.EOLChanged: {405// Nothing to do. The new version will be accepted below406break;407}408}409}410411if (versionId !== null) {412this._lines.acceptVersionId(versionId);413}414this.viewLayout.onHeightMaybeChanged();415416if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) {417eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent());418eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));419this._cursor.onLineMappingChanged(eventsCollector);420this._decorations.onLineMappingChanged();421}422} finally {423this._eventDispatcher.endEmitViewEvents();424}425426// Update the configuration and reset the centered view line427const viewportStartWasValid = this._viewportStart.isValid;428this._viewportStart.invalidate();429this._configuration.setModelLineCount(this.model.getLineCount());430this._updateConfigurationViewLineCountNow();431432// Recover viewport433if (!this._hasFocus && this.model.getAttachedEditorCount() >= 2 && viewportStartWasValid) {434const modelRange = this.model._getTrackedRange(this._viewportStart.modelTrackedRange);435if (modelRange) {436const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelRange.getStartPosition());437const viewPositionTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber);438this.viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this._viewportStart.startLineDelta }, ScrollType.Immediate);439}440}441442this._handleVisibleLinesChanged();443}444445/**446* Gets called directly by the text model.447*/448emitContentChangeEvent(e: textModelEvents.InternalModelContentChangeEvent | textModelEvents.ModelInjectedTextChangedEvent): void {449this._emitViewEvent((eventsCollector) => {450if (e instanceof textModelEvents.InternalModelContentChangeEvent) {451eventsCollector.emitOutgoingEvent(new ModelContentChangedEvent(e.contentChangedEvent));452}453this._cursor.onModelContentChanged(eventsCollector, e);454});455}456457private _registerModelEvents(): void {458459const allowVariableLineHeights = this._configuration.options.get(EditorOption.allowVariableLineHeights);460if (allowVariableLineHeights) {461this._register(this.model.onDidChangeLineHeight((e) => {462const filteredChanges = e.changes.filter((change) => change.ownerId === this._editorId || change.ownerId === 0);463464this.viewLayout.changeSpecialLineHeights((accessor: ILineHeightChangeAccessor) => {465for (const change of filteredChanges) {466const { decorationId, lineNumber, lineHeightMultiplier } = change;467const viewRange = this.coordinatesConverter.convertModelRangeToViewRange(new Range(lineNumber, 1, lineNumber, this.model.getLineMaxColumn(lineNumber)));468if (lineHeightMultiplier !== null) {469accessor.insertOrChangeCustomLineHeight(decorationId, viewRange.startLineNumber, viewRange.endLineNumber, lineHeightMultiplier * this._configuration.options.get(EditorOption.lineHeight));470} else {471accessor.removeCustomLineHeight(decorationId);472}473}474});475476// recreate the model event using the filtered changes477if (filteredChanges.length > 0) {478const filteredEvent = new textModelEvents.ModelLineHeightChangedEvent(filteredChanges);479this._eventDispatcher.emitOutgoingEvent(new ModelLineHeightChangedEvent(filteredEvent));480}481}));482}483484const allowVariableFonts = this._configuration.options.get(EditorOption.effectiveAllowVariableFonts);485if (allowVariableFonts) {486this._register(this.model.onDidChangeFont((e) => {487const filteredChanges = e.changes.filter((change) => change.ownerId === this._editorId || change.ownerId === 0);488// recreate the model event using the filtered changes489if (filteredChanges.length > 0) {490const filteredEvent = new textModelEvents.ModelFontChangedEvent(filteredChanges);491this._eventDispatcher.emitOutgoingEvent(new ModelFontChangedEvent(filteredEvent));492}493}));494}495496this._register(this.model.onDidChangeTokens((e) => {497const viewRanges: { fromLineNumber: number; toLineNumber: number }[] = [];498for (let j = 0, lenJ = e.ranges.length; j < lenJ; j++) {499const modelRange = e.ranges[j];500const viewStartLineNumber = this.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.fromLineNumber, 1)).lineNumber;501const viewEndLineNumber = this.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.toLineNumber, this.model.getLineMaxColumn(modelRange.toLineNumber))).lineNumber;502viewRanges[j] = {503fromLineNumber: viewStartLineNumber,504toLineNumber: viewEndLineNumber505};506}507this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewTokensChangedEvent(viewRanges));508this._eventDispatcher.emitOutgoingEvent(new ModelTokensChangedEvent(e));509}));510511this._register(this.model.onDidChangeLanguageConfiguration((e) => {512this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewLanguageConfigurationEvent());513this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService);514this._cursor.updateConfiguration(this.cursorConfig);515this._eventDispatcher.emitOutgoingEvent(new ModelLanguageConfigurationChangedEvent(e));516}));517518this._register(this.model.onDidChangeLanguage((e) => {519this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService);520this._cursor.updateConfiguration(this.cursorConfig);521this._eventDispatcher.emitOutgoingEvent(new ModelLanguageChangedEvent(e));522}));523524this._register(this.model.onDidChangeOptions((e) => {525// A tab size change causes a line mapping changed event => all view parts will repaint OK, no further event needed here526if (this._lines.setTabSize(this.model.getOptions().tabSize)) {527try {528const eventsCollector = this._eventDispatcher.beginEmitViewEvents();529eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent());530eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent());531eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));532this._cursor.onLineMappingChanged(eventsCollector);533this._decorations.onLineMappingChanged();534this.viewLayout.onFlushed(this.getLineCount(), this._getCustomLineHeights());535} finally {536this._eventDispatcher.endEmitViewEvents();537}538this._updateConfigurationViewLineCount.schedule();539}540541this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService);542this._cursor.updateConfiguration(this.cursorConfig);543544this._eventDispatcher.emitOutgoingEvent(new ModelOptionsChangedEvent(e));545}));546547this._register(this.model.onDidChangeDecorations((e) => {548this._decorations.onModelDecorationsChanged();549this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewDecorationsChangedEvent(e));550this._eventDispatcher.emitOutgoingEvent(new ModelDecorationsChangedEvent(e));551}));552}553554private readonly hiddenAreasModel = new HiddenAreasModel();555private previousHiddenAreas: readonly Range[] = [];556557public getFontSizeAtPosition(position: IPosition): string | null {558const allowVariableFonts = this._configuration.options.get(EditorOption.effectiveAllowVariableFonts);559if (!allowVariableFonts) {560return null;561}562const fontDecorations = this.model.getFontDecorationsInRange(Range.fromPositions(position), this._editorId);563let fontSize: string = this._configuration.options.get(EditorOption.fontInfo).fontSize + 'px';564for (const fontDecoration of fontDecorations) {565if (fontDecoration.options.fontSize) {566fontSize = fontDecoration.options.fontSize;567break;568}569}570return fontSize;571}572573/**574* @param forceUpdate If true, the hidden areas will be updated even if the new ranges are the same as the previous ranges.575* This is because the model might have changed, which resets the hidden areas, but not the last cached value.576* This needs a better fix in the future.577*/578public setHiddenAreas(ranges: Range[], source?: unknown, forceUpdate?: boolean): void {579this.hiddenAreasModel.setHiddenAreas(source, ranges);580const mergedRanges = this.hiddenAreasModel.getMergedRanges();581if (mergedRanges === this.previousHiddenAreas && !forceUpdate) {582return;583}584585this.previousHiddenAreas = mergedRanges;586587const stableViewport = this._captureStableViewport();588589let lineMappingChanged = false;590try {591const eventsCollector = this._eventDispatcher.beginEmitViewEvents();592lineMappingChanged = this._lines.setHiddenAreas(mergedRanges);593if (lineMappingChanged) {594eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent());595eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent());596eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));597this._cursor.onLineMappingChanged(eventsCollector);598this._decorations.onLineMappingChanged();599this.viewLayout.onFlushed(this.getLineCount(), this._getCustomLineHeights());600this.viewLayout.onHeightMaybeChanged();601}602603const firstModelLineInViewPort = stableViewport.viewportStartModelPosition?.lineNumber;604const firstModelLineIsHidden = firstModelLineInViewPort && mergedRanges.some(range => range.startLineNumber <= firstModelLineInViewPort && firstModelLineInViewPort <= range.endLineNumber);605if (!firstModelLineIsHidden) {606stableViewport.recoverViewportStart(this.coordinatesConverter, this.viewLayout);607}608} finally {609this._eventDispatcher.endEmitViewEvents();610}611this._updateConfigurationViewLineCount.schedule();612613if (lineMappingChanged) {614this._eventDispatcher.emitOutgoingEvent(new HiddenAreasChangedEvent());615}616}617618public getVisibleRangesPlusViewportAboveBelow(): Range[] {619const layoutInfo = this._configuration.options.get(EditorOption.layoutInfo);620const lineHeight = this._configuration.options.get(EditorOption.lineHeight);621const linesAround = Math.max(20, Math.round(layoutInfo.height / lineHeight));622const partialData = this.viewLayout.getLinesViewportData();623const startViewLineNumber = Math.max(1, partialData.completelyVisibleStartLineNumber - linesAround);624const endViewLineNumber = Math.min(this.getLineCount(), partialData.completelyVisibleEndLineNumber + linesAround);625626return this._toModelVisibleRanges(new Range(627startViewLineNumber, this.getLineMinColumn(startViewLineNumber),628endViewLineNumber, this.getLineMaxColumn(endViewLineNumber)629));630}631632public getVisibleRanges(): Range[] {633const visibleViewRange = this.getCompletelyVisibleViewRange();634return this._toModelVisibleRanges(visibleViewRange);635}636637public getHiddenAreas(): Range[] {638return this._lines.getHiddenAreas();639}640641private _toModelVisibleRanges(visibleViewRange: Range): Range[] {642const visibleRange = this.coordinatesConverter.convertViewRangeToModelRange(visibleViewRange);643const hiddenAreas = this._lines.getHiddenAreas();644645if (hiddenAreas.length === 0) {646return [visibleRange];647}648649const result: Range[] = [];650let resultLen = 0;651let startLineNumber = visibleRange.startLineNumber;652let startColumn = visibleRange.startColumn;653const endLineNumber = visibleRange.endLineNumber;654const endColumn = visibleRange.endColumn;655for (let i = 0, len = hiddenAreas.length; i < len; i++) {656const hiddenStartLineNumber = hiddenAreas[i].startLineNumber;657const hiddenEndLineNumber = hiddenAreas[i].endLineNumber;658659if (hiddenEndLineNumber < startLineNumber) {660continue;661}662if (hiddenStartLineNumber > endLineNumber) {663continue;664}665666if (startLineNumber < hiddenStartLineNumber) {667result[resultLen++] = new Range(668startLineNumber, startColumn,669hiddenStartLineNumber - 1, this.model.getLineMaxColumn(hiddenStartLineNumber - 1)670);671}672startLineNumber = hiddenEndLineNumber + 1;673startColumn = 1;674}675676if (startLineNumber < endLineNumber || (startLineNumber === endLineNumber && startColumn < endColumn)) {677result[resultLen++] = new Range(678startLineNumber, startColumn,679endLineNumber, endColumn680);681}682683return result;684}685686public getCompletelyVisibleViewRange(): Range {687const partialData = this.viewLayout.getLinesViewportData();688const startViewLineNumber = partialData.completelyVisibleStartLineNumber;689const endViewLineNumber = partialData.completelyVisibleEndLineNumber;690691return new Range(692startViewLineNumber, this.getLineMinColumn(startViewLineNumber),693endViewLineNumber, this.getLineMaxColumn(endViewLineNumber)694);695}696697public getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range {698const partialData = this.viewLayout.getLinesViewportDataAtScrollTop(scrollTop);699const startViewLineNumber = partialData.completelyVisibleStartLineNumber;700const endViewLineNumber = partialData.completelyVisibleEndLineNumber;701702return new Range(703startViewLineNumber, this.getLineMinColumn(startViewLineNumber),704endViewLineNumber, this.getLineMaxColumn(endViewLineNumber)705);706}707708/**709* Applies `cursorSurroundingLines` and `stickyScroll` padding to the given view range.710*/711public getViewRangeWithCursorPadding(viewRange: Range): Range {712const options = this._configuration.options;713const cursorSurroundingLines = options.get(EditorOption.cursorSurroundingLines);714const stickyScroll = options.get(EditorOption.stickyScroll);715716let { startLineNumber, endLineNumber } = viewRange;717const padding = Math.min(718Math.max(cursorSurroundingLines, stickyScroll.enabled ? stickyScroll.maxLineCount : 0),719Math.floor((endLineNumber - startLineNumber + 1) / 2));720721startLineNumber += padding;722endLineNumber -= Math.max(0, padding - 1);723724if (padding === 0 || startLineNumber > endLineNumber) {725return viewRange;726}727728return new Range(729startLineNumber, this.getLineMinColumn(startLineNumber),730endLineNumber, this.getLineMaxColumn(endLineNumber)731);732}733734public saveState(): IViewState {735const compatViewState = this.viewLayout.saveState();736737const scrollTop = compatViewState.scrollTop;738const firstViewLineNumber = this.viewLayout.getLineNumberAtVerticalOffset(scrollTop);739const firstPosition = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(firstViewLineNumber, this.getLineMinColumn(firstViewLineNumber)));740const firstPositionDeltaTop = this.viewLayout.getVerticalOffsetForLineNumber(firstViewLineNumber) - scrollTop;741742return {743scrollLeft: compatViewState.scrollLeft,744firstPosition: firstPosition,745firstPositionDeltaTop: firstPositionDeltaTop746};747}748749public reduceRestoreState(state: IViewState): { scrollLeft: number; scrollTop: number } {750if (typeof state.firstPosition === 'undefined') {751// This is a view state serialized by an older version752return this._reduceRestoreStateCompatibility(state);753}754755const modelPosition = this.model.validatePosition(state.firstPosition);756const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);757const scrollTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber) - state.firstPositionDeltaTop;758return {759scrollLeft: state.scrollLeft,760scrollTop: scrollTop761};762}763764private _reduceRestoreStateCompatibility(state: IViewState): { scrollLeft: number; scrollTop: number } {765return {766scrollLeft: state.scrollLeft,767scrollTop: state.scrollTopWithoutViewZones!768};769}770771private getTabSize(): number {772return this.model.getOptions().tabSize;773}774775public getLineCount(): number {776return this._lines.getViewLineCount();777}778779/**780* Gives a hint that a lot of requests are about to come in for these line numbers.781*/782public setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void {783if (this._lines.getViewLineCount() === 0) {784// No visible lines to set viewport on785return;786}787this._viewportStart.update(this, startLineNumber);788}789790public getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {791return this._lines.getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber);792}793794public getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[] {795return this._lines.getViewLinesIndentGuides(startLineNumber, endLineNumber);796}797798public getBracketGuidesInRangeByLine(startLineNumber: number, endLineNumber: number, activePosition: IPosition | null, options: BracketGuideOptions): IndentGuide[][] {799return this._lines.getViewLinesBracketGuides(startLineNumber, endLineNumber, activePosition, options);800}801802public getLineContent(lineNumber: number): string {803return this._lines.getViewLineContent(lineNumber);804}805806public getLineLength(lineNumber: number): number {807return this._lines.getViewLineLength(lineNumber);808}809810public getLineMinColumn(lineNumber: number): number {811return this._lines.getViewLineMinColumn(lineNumber);812}813814public getLineMaxColumn(lineNumber: number): number {815return this._lines.getViewLineMaxColumn(lineNumber);816}817818public getLineFirstNonWhitespaceColumn(lineNumber: number): number {819const result = strings.firstNonWhitespaceIndex(this.getLineContent(lineNumber));820if (result === -1) {821return 0;822}823return result + 1;824}825826public getLineLastNonWhitespaceColumn(lineNumber: number): number {827const result = strings.lastNonWhitespaceIndex(this.getLineContent(lineNumber));828if (result === -1) {829return 0;830}831return result + 2;832}833834public getMinimapDecorationsInRange(range: Range): ViewModelDecoration[] {835return this._decorations.getMinimapDecorationsInRange(range);836}837838public getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[] {839return this._decorations.getDecorationsViewportData(visibleRange).decorations;840}841842public getInjectedTextAt(viewPosition: Position): InjectedText | null {843return this._lines.getInjectedTextAt(viewPosition);844}845846private _getTextDirection(lineNumber: number, decorations: ViewModelDecoration[]): TextDirection {847let rtlCount = 0;848849for (const decoration of decorations) {850const range = decoration.range;851if (range.startLineNumber > lineNumber || range.endLineNumber < lineNumber) {852continue;853}854const textDirection = decoration.options.textDirection;855if (textDirection === TextDirection.RTL) {856rtlCount++;857} else if (textDirection === TextDirection.LTR) {858rtlCount--;859}860}861862return rtlCount > 0 ? TextDirection.RTL : TextDirection.LTR;863}864865public getTextDirection(lineNumber: number): TextDirection {866const decorationsCollection = this._decorations.getDecorationsOnLine(lineNumber);867return this._getTextDirection(lineNumber, decorationsCollection.decorations);868}869870public getViewportViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData {871const viewportDecorationsCollection = this._decorations.getDecorationsViewportData(visibleRange);872const relativeLineNumber = lineNumber - visibleRange.startLineNumber;873const inlineDecorations = viewportDecorationsCollection.inlineDecorations[relativeLineNumber];874const hasVariableFonts = viewportDecorationsCollection.hasVariableFonts[relativeLineNumber];875return this._getViewLineRenderingData(lineNumber, inlineDecorations, hasVariableFonts, viewportDecorationsCollection.decorations);876}877878public getViewLineRenderingData(lineNumber: number): ViewLineRenderingData {879const decorationsCollection = this._decorations.getDecorationsOnLine(lineNumber);880return this._getViewLineRenderingData(lineNumber, decorationsCollection.inlineDecorations[0], decorationsCollection.hasVariableFonts[0], decorationsCollection.decorations);881}882883private _getViewLineRenderingData(lineNumber: number, inlineDecorations: InlineDecoration[], hasVariableFonts: boolean, decorations: ViewModelDecoration[]): ViewLineRenderingData {884const mightContainRTL = this.model.mightContainRTL();885const mightContainNonBasicASCII = this.model.mightContainNonBasicASCII();886const tabSize = this.getTabSize();887const lineData = this._lines.getViewLineData(lineNumber);888889if (lineData.inlineDecorations) {890inlineDecorations = [891...inlineDecorations,892...lineData.inlineDecorations.map(d =>893d.toInlineDecoration(lineNumber)894)895];896}897898return new ViewLineRenderingData(899lineData.minColumn,900lineData.maxColumn,901lineData.content,902lineData.continuesWithWrappedLine,903mightContainRTL,904mightContainNonBasicASCII,905lineData.tokens,906inlineDecorations,907tabSize,908lineData.startVisibleColumn,909this._getTextDirection(lineNumber, decorations),910hasVariableFonts911);912}913914public getViewLineData(lineNumber: number): ViewLineData {915return this._lines.getViewLineData(lineNumber);916}917918public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData {919const result = this._lines.getViewLinesData(startLineNumber, endLineNumber, needed);920return new MinimapLinesRenderingData(921this.getTabSize(),922result923);924}925926public getAllOverviewRulerDecorations(theme: EditorTheme): OverviewRulerDecorationsGroup[] {927const decorations = this.model.getOverviewRulerDecorations(this._editorId, filterValidationDecorations(this._configuration.options), filterFontDecorations(this._configuration.options));928const result = new OverviewRulerDecorations();929for (const decoration of decorations) {930const decorationOptions = <ModelDecorationOptions>decoration.options;931const opts = decorationOptions.overviewRuler;932if (!opts) {933continue;934}935const lane = <number>opts.position;936if (lane === 0) {937continue;938}939const color = opts.getColor(theme.value);940const viewStartLineNumber = this.coordinatesConverter.getViewLineNumberOfModelPosition(decoration.range.startLineNumber, decoration.range.startColumn);941const viewEndLineNumber = this.coordinatesConverter.getViewLineNumberOfModelPosition(decoration.range.endLineNumber, decoration.range.endColumn);942943result.accept(color, decorationOptions.zIndex, viewStartLineNumber, viewEndLineNumber, lane);944}945return result.asArray;946}947948private _invalidateDecorationsColorCache(): void {949const decorations = this.model.getOverviewRulerDecorations();950for (const decoration of decorations) {951const opts1 = <ModelDecorationOverviewRulerOptions>decoration.options.overviewRuler;952opts1?.invalidateCachedColor();953const opts2 = <ModelDecorationMinimapOptions>decoration.options.minimap;954opts2?.invalidateCachedColor();955}956}957958public getValueInRange(range: Range, eol: EndOfLinePreference): string {959const modelRange = this.coordinatesConverter.convertViewRangeToModelRange(range);960return this.model.getValueInRange(modelRange, eol);961}962963public getValueLengthInRange(range: Range, eol: EndOfLinePreference): number {964const modelRange = this.coordinatesConverter.convertViewRangeToModelRange(range);965return this.model.getValueLengthInRange(modelRange, eol);966}967968public modifyPosition(position: Position, offset: number): Position {969const modelPosition = this.coordinatesConverter.convertViewPositionToModelPosition(position);970const resultModelPosition = this.model.modifyPosition(modelPosition, offset);971return this.coordinatesConverter.convertModelPositionToViewPosition(resultModelPosition);972}973974public deduceModelPositionRelativeToViewPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position {975const modelAnchor = this.coordinatesConverter.convertViewPositionToModelPosition(viewAnchorPosition);976if (this.model.getEOL().length === 2) {977// This model uses CRLF, so the delta must take that into account978if (deltaOffset < 0) {979deltaOffset -= lineFeedCnt;980} else {981deltaOffset += lineFeedCnt;982}983}984985const modelAnchorOffset = this.model.getOffsetAt(modelAnchor);986const resultOffset = modelAnchorOffset + deltaOffset;987return this.model.getPositionAt(resultOffset);988}989990public getPlainTextToCopy(modelRanges: Range[], emptySelectionClipboard: boolean, forceCRLF: boolean): { sourceRanges: Range[]; sourceText: string | string[] } {991const newLineCharacter = forceCRLF ? '\r\n' : this.model.getEOL();992993modelRanges = modelRanges.slice(0);994modelRanges.sort(Range.compareRangesUsingStarts);995996let hasEmptyRange = false;997let hasNonEmptyRange = false;998for (const range of modelRanges) {999if (range.isEmpty()) {1000hasEmptyRange = true;1001} else {1002hasNonEmptyRange = true;1003}1004}10051006if (!hasNonEmptyRange && !emptySelectionClipboard) {1007// all ranges are empty1008return { sourceRanges: [], sourceText: '' };1009}10101011const ranges: Range[] = [];1012const result: string[] = [];1013const pushRange = (modelRange: Range, append: string = '') => {1014ranges.push(modelRange);1015result.push(this.model.getValueInRange(modelRange, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined) + append);1016};10171018if (hasEmptyRange && emptySelectionClipboard) {1019// some (maybe all) empty selections1020let prevModelLineNumber = 0;1021for (const modelRange of modelRanges) {1022const modelLineNumber = modelRange.startLineNumber;1023if (modelRange.isEmpty()) {1024if (modelLineNumber !== prevModelLineNumber) {1025pushRange(new Range(modelLineNumber, this.model.getLineMinColumn(modelLineNumber), modelLineNumber, this.model.getLineMaxColumn(modelLineNumber)), newLineCharacter);1026}1027} else {1028pushRange(modelRange);1029}1030prevModelLineNumber = modelLineNumber;1031}1032} else {1033for (const modelRange of modelRanges) {1034if (!modelRange.isEmpty()) {1035pushRange(modelRange);1036}1037}1038}10391040return { sourceRanges: ranges, sourceText: result.length === 1 ? result[0] : result };1041}10421043public getRichTextToCopy(modelRanges: Range[], emptySelectionClipboard: boolean): { html: string; mode: string } | null {1044const languageId = this.model.getLanguageId();1045if (languageId === PLAINTEXT_LANGUAGE_ID) {1046return null;1047}10481049if (modelRanges.length !== 1) {1050// no multiple selection support at this time1051return null;1052}10531054let range = modelRanges[0];1055if (range.isEmpty()) {1056if (!emptySelectionClipboard) {1057// nothing to copy1058return null;1059}1060const lineNumber = range.startLineNumber;1061range = new Range(lineNumber, this.model.getLineMinColumn(lineNumber), lineNumber, this.model.getLineMaxColumn(lineNumber));1062}10631064const fontInfo = this._configuration.options.get(EditorOption.fontInfo);1065const colorMap = this._getColorMap();1066const hasBadChars = (/[:;\\\/<>]/.test(fontInfo.fontFamily));1067const useDefaultFontFamily = (hasBadChars || fontInfo.fontFamily === EDITOR_FONT_DEFAULTS.fontFamily);1068let fontFamily: string;1069if (useDefaultFontFamily) {1070fontFamily = EDITOR_FONT_DEFAULTS.fontFamily;1071} else {1072fontFamily = fontInfo.fontFamily;1073fontFamily = fontFamily.replace(/"/g, '\'');1074const hasQuotesOrIsList = /[,']/.test(fontFamily);1075if (!hasQuotesOrIsList) {1076const needsQuotes = /[+ ]/.test(fontFamily);1077if (needsQuotes) {1078fontFamily = `'${fontFamily}'`;1079}1080}1081fontFamily = `${fontFamily}, ${EDITOR_FONT_DEFAULTS.fontFamily}`;1082}10831084return {1085mode: languageId,1086html: (1087`<div style="`1088+ `color: ${colorMap[ColorId.DefaultForeground]};`1089+ `background-color: ${colorMap[ColorId.DefaultBackground]};`1090+ `font-family: ${fontFamily};`1091+ `font-weight: ${fontInfo.fontWeight};`1092+ `font-size: ${fontInfo.fontSize}px;`1093+ `line-height: ${fontInfo.lineHeight}px;`1094+ `white-space: pre;`1095+ `">`1096+ this._getHTMLToCopy(range, colorMap)1097+ '</div>'1098)1099};1100}11011102private _getHTMLToCopy(modelRange: Range, colorMap: string[]): string {1103const startLineNumber = modelRange.startLineNumber;1104const startColumn = modelRange.startColumn;1105const endLineNumber = modelRange.endLineNumber;1106const endColumn = modelRange.endColumn;11071108const tabSize = this.getTabSize();11091110let result = '';11111112for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {1113const lineTokens = this.model.tokenization.getLineTokens(lineNumber);1114const lineContent = lineTokens.getLineContent();1115const startOffset = (lineNumber === startLineNumber ? startColumn - 1 : 0);1116const endOffset = (lineNumber === endLineNumber ? endColumn - 1 : lineContent.length);11171118if (lineContent === '') {1119result += '<br>';1120} else {1121result += tokenizeLineToHTML(lineContent, lineTokens.inflate(), colorMap, startOffset, endOffset, tabSize, platform.isWindows);1122}1123}11241125return result;1126}11271128private _getColorMap(): string[] {1129const colorMap = TokenizationRegistry.getColorMap();1130const result: string[] = ['#000000'];1131if (colorMap) {1132for (let i = 1, len = colorMap.length; i < len; i++) {1133result[i] = Color.Format.CSS.formatHex(colorMap[i]);1134}1135}1136return result;1137}11381139//#region cursor operations11401141public getPrimaryCursorState(): CursorState {1142return this._cursor.getPrimaryCursorState();1143}1144public getLastAddedCursorIndex(): number {1145return this._cursor.getLastAddedCursorIndex();1146}1147public getCursorStates(): CursorState[] {1148return this._cursor.getCursorStates();1149}1150public setCursorStates(source: string | null | undefined, reason: CursorChangeReason, states: PartialCursorState[] | null): boolean {1151return this._withViewEventsCollector(eventsCollector => this._cursor.setStates(eventsCollector, source, reason, states));1152}1153public getCursorColumnSelectData(): IColumnSelectData {1154return this._cursor.getCursorColumnSelectData();1155}1156public getCursorAutoClosedCharacters(): Range[] {1157return this._cursor.getAutoClosedCharacters();1158}1159public setCursorColumnSelectData(columnSelectData: IColumnSelectData): void {1160this._cursor.setCursorColumnSelectData(columnSelectData);1161}1162public getPrevEditOperationType(): EditOperationType {1163return this._cursor.getPrevEditOperationType();1164}1165public setPrevEditOperationType(type: EditOperationType): void {1166this._cursor.setPrevEditOperationType(type);1167}1168public getSelection(): Selection {1169return this._cursor.getSelection();1170}1171public getSelections(): Selection[] {1172return this._cursor.getSelections();1173}1174public getPosition(): Position {1175return this._cursor.getPrimaryCursorState().modelState.position;1176}1177public setSelections(source: string | null | undefined, selections: readonly ISelection[], reason = CursorChangeReason.NotSet): void {1178this._withViewEventsCollector(eventsCollector => this._cursor.setSelections(eventsCollector, source, selections, reason));1179}1180public saveCursorState(): ICursorState[] {1181return this._cursor.saveState();1182}1183public restoreCursorState(states: ICursorState[]): void {1184this._withViewEventsCollector(eventsCollector => this._cursor.restoreState(eventsCollector, states));1185}11861187private _executeCursorEdit(callback: (eventsCollector: ViewModelEventsCollector) => void): void {1188if (this._cursor.context.cursorConfig.readOnly) {1189// we cannot edit when read only...1190this._eventDispatcher.emitOutgoingEvent(new ReadOnlyEditAttemptEvent());1191return;1192}1193this._withViewEventsCollector(callback);1194}1195public executeEdits(source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer, reason: TextModelEditSource): void {1196this._executeCursorEdit(eventsCollector => this._cursor.executeEdits(eventsCollector, source, edits, cursorStateComputer, reason));1197}1198public startComposition(): void {1199this._executeCursorEdit(eventsCollector => this._cursor.startComposition(eventsCollector));1200}1201public endComposition(source?: string | null | undefined): void {1202this._executeCursorEdit(eventsCollector => this._cursor.endComposition(eventsCollector, source));1203}1204public type(text: string, source?: string | null | undefined): void {1205this._executeCursorEdit(eventsCollector => this._cursor.type(eventsCollector, text, source));1206}1207public compositionType(text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number, source?: string | null | undefined): void {1208this._executeCursorEdit(eventsCollector => this._cursor.compositionType(eventsCollector, text, replacePrevCharCnt, replaceNextCharCnt, positionDelta, source));1209}1210public paste(text: string, pasteOnNewLine: boolean, multicursorText?: string[] | null | undefined, source?: string | null | undefined): void {1211this._executeCursorEdit(eventsCollector => this._cursor.paste(eventsCollector, text, pasteOnNewLine, multicursorText, source));1212}1213public cut(source?: string | null | undefined): void {1214this._executeCursorEdit(eventsCollector => this._cursor.cut(eventsCollector, source));1215}1216public executeCommand(command: ICommand, source?: string | null | undefined): void {1217this._executeCursorEdit(eventsCollector => this._cursor.executeCommand(eventsCollector, command, source));1218}1219public executeCommands(commands: ICommand[], source?: string | null | undefined): void {1220this._executeCursorEdit(eventsCollector => this._cursor.executeCommands(eventsCollector, commands, source));1221}1222public revealAllCursors(source: string | null | undefined, revealHorizontal: boolean, minimalReveal: boolean = false): void {1223this._withViewEventsCollector(eventsCollector => this._cursor.revealAll(eventsCollector, source, minimalReveal, viewEvents.VerticalRevealType.Simple, revealHorizontal, ScrollType.Smooth));1224}1225public revealPrimaryCursor(source: string | null | undefined, revealHorizontal: boolean, minimalReveal: boolean = false): void {1226this._withViewEventsCollector(eventsCollector => this._cursor.revealPrimary(eventsCollector, source, minimalReveal, viewEvents.VerticalRevealType.Simple, revealHorizontal, ScrollType.Smooth));1227}1228public revealTopMostCursor(source: string | null | undefined): void {1229const viewPosition = this._cursor.getTopMostViewPosition();1230const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column);1231this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, false, viewRange, null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Smooth)));1232}1233public revealBottomMostCursor(source: string | null | undefined): void {1234const viewPosition = this._cursor.getBottomMostViewPosition();1235const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column);1236this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, false, viewRange, null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Smooth)));1237}1238public revealRange(source: string | null | undefined, revealHorizontal: boolean, viewRange: Range, verticalType: viewEvents.VerticalRevealType, scrollType: ScrollType): void {1239this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, false, viewRange, null, verticalType, revealHorizontal, scrollType)));1240}12411242//#endregion12431244//#region viewLayout1245public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => void): void {1246const hadAChange = this.viewLayout.changeWhitespace(callback);1247if (hadAChange) {1248this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewZonesChangedEvent());1249this._eventDispatcher.emitOutgoingEvent(new ViewZonesChangedEvent());1250}1251}1252//#endregion12531254private _withViewEventsCollector<T>(callback: (eventsCollector: ViewModelEventsCollector) => T): T {1255return this._transactionalTarget.batchChanges(() => {1256return this._emitViewEvent(callback);1257});1258}12591260private _emitViewEvent<T>(callback: (eventsCollector: ViewModelEventsCollector) => T): T {1261try {1262const eventsCollector = this._eventDispatcher.beginEmitViewEvents();1263return callback(eventsCollector);1264} finally {1265this._eventDispatcher.endEmitViewEvents();1266}1267}12681269public batchEvents(callback: () => void): void {1270this._withViewEventsCollector(() => { callback(); });1271}12721273normalizePosition(position: Position, affinity: PositionAffinity): Position {1274return this._lines.normalizePosition(position, affinity);1275}12761277/**1278* Gets the column at which indentation stops at a given line.1279* @internal1280*/1281getLineIndentColumn(lineNumber: number): number {1282return this._lines.getLineIndentColumn(lineNumber);1283}1284}12851286export interface IBatchableTarget {1287/**1288* Allows the target to apply the changes introduced by the callback in a batch.1289*/1290batchChanges<T>(cb: () => T): T;1291}12921293class ViewportStart implements IDisposable {12941295public static create(model: ITextModel): ViewportStart {1296const viewportStartLineTrackedRange = model._setTrackedRange(null, new Range(1, 1, 1, 1), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);1297return new ViewportStart(model, 1, false, viewportStartLineTrackedRange, 0);1298}12991300public get viewLineNumber(): number {1301return this._viewLineNumber;1302}13031304public get isValid(): boolean {1305return this._isValid;1306}13071308public get modelTrackedRange(): string {1309return this._modelTrackedRange;1310}13111312public get startLineDelta(): number {1313return this._startLineDelta;1314}13151316private constructor(1317private readonly _model: ITextModel,1318private _viewLineNumber: number,1319private _isValid: boolean,1320private _modelTrackedRange: string,1321private _startLineDelta: number,1322) { }13231324public dispose(): void {1325this._model._setTrackedRange(this._modelTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);1326}13271328public update(viewModel: IViewModel, startLineNumber: number): void {1329const position = viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(startLineNumber, viewModel.getLineMinColumn(startLineNumber)));1330const viewportStartLineTrackedRange = viewModel.model._setTrackedRange(this._modelTrackedRange, new Range(position.lineNumber, position.column, position.lineNumber, position.column), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);1331const viewportStartLineTop = viewModel.viewLayout.getVerticalOffsetForLineNumber(startLineNumber);1332const scrollTop = viewModel.viewLayout.getCurrentScrollTop();13331334this._viewLineNumber = startLineNumber;1335this._isValid = true;1336this._modelTrackedRange = viewportStartLineTrackedRange;1337this._startLineDelta = scrollTop - viewportStartLineTop;1338}13391340public invalidate(): void {1341this._isValid = false;1342}1343}13441345class OverviewRulerDecorations {13461347private readonly _asMap: { [color: string]: OverviewRulerDecorationsGroup } = Object.create(null);1348readonly asArray: OverviewRulerDecorationsGroup[] = [];13491350public accept(color: string, zIndex: number, startLineNumber: number, endLineNumber: number, lane: number): void {1351const prevGroup = this._asMap[color];13521353if (prevGroup) {1354const prevData = prevGroup.data;1355const prevLane = prevData[prevData.length - 3];1356const prevEndLineNumber = prevData[prevData.length - 1];1357if (prevLane === lane && prevEndLineNumber + 1 >= startLineNumber) {1358// merge into prev1359if (endLineNumber > prevEndLineNumber) {1360prevData[prevData.length - 1] = endLineNumber;1361}1362return;1363}13641365// push1366prevData.push(lane, startLineNumber, endLineNumber);1367} else {1368const group = new OverviewRulerDecorationsGroup(color, zIndex, [lane, startLineNumber, endLineNumber]);1369this._asMap[color] = group;1370this.asArray.push(group);1371}1372}1373}13741375class HiddenAreasModel {1376private readonly hiddenAreas = new Map<unknown, Range[]>();1377private shouldRecompute = false;1378private ranges: Range[] = [];13791380setHiddenAreas(source: unknown, ranges: Range[]): void {1381const existing = this.hiddenAreas.get(source);1382if (existing && rangeArraysEqual(existing, ranges)) {1383return;1384}1385this.hiddenAreas.set(source, ranges);1386this.shouldRecompute = true;1387}13881389/**1390* The returned array is immutable.1391*/1392getMergedRanges(): readonly Range[] {1393if (!this.shouldRecompute) {1394return this.ranges;1395}1396this.shouldRecompute = false;1397const newRanges = Array.from(this.hiddenAreas.values()).reduce((r, hiddenAreas) => mergeLineRangeArray(r, hiddenAreas), []);1398if (rangeArraysEqual(this.ranges, newRanges)) {1399return this.ranges;1400}1401this.ranges = newRanges;1402return this.ranges;1403}1404}14051406function mergeLineRangeArray(arr1: Range[], arr2: Range[]): Range[] {1407const result: Range[] = [];1408let i = 0;1409let j = 0;1410while (i < arr1.length && j < arr2.length) {1411const item1 = arr1[i];1412const item2 = arr2[j];14131414if (item1.endLineNumber < item2.startLineNumber - 1) {1415result.push(arr1[i++]);1416} else if (item2.endLineNumber < item1.startLineNumber - 1) {1417result.push(arr2[j++]);1418} else {1419const startLineNumber = Math.min(item1.startLineNumber, item2.startLineNumber);1420const endLineNumber = Math.max(item1.endLineNumber, item2.endLineNumber);1421result.push(new Range(startLineNumber, 1, endLineNumber, 1));1422i++;1423j++;1424}1425}1426while (i < arr1.length) {1427result.push(arr1[i++]);1428}1429while (j < arr2.length) {1430result.push(arr2[j++]);1431}1432return result;1433}14341435function rangeArraysEqual(arr1: Range[], arr2: Range[]): boolean {1436if (arr1.length !== arr2.length) {1437return false;1438}1439for (let i = 0; i < arr1.length; i++) {1440if (!arr1[i].equalsRange(arr2[i])) {1441return false;1442}1443}1444return true;1445}14461447/**1448* Maintain a stable viewport by trying to keep the first line in the viewport constant.1449*/1450class StableViewport {1451constructor(1452public readonly viewportStartModelPosition: Position | null,1453public readonly startLineDelta: number1454) { }14551456public recoverViewportStart(coordinatesConverter: ICoordinatesConverter, viewLayout: ViewLayout): void {1457if (!this.viewportStartModelPosition) {1458return;1459}1460const viewPosition = coordinatesConverter.convertModelPositionToViewPosition(this.viewportStartModelPosition);1461const viewPositionTop = viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber);1462viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this.startLineDelta }, ScrollType.Immediate);1463}1464}146514661467