UNPKG

monaco-editor-core

Version:

A browser based code editor

911 lines • 55.7 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { ArrayQueue } from '../../../base/common/arrays.js'; import { RunOnceScheduler } from '../../../base/common/async.js'; import { Color } from '../../../base/common/color.js'; import { Disposable } from '../../../base/common/lifecycle.js'; import * as platform from '../../../base/common/platform.js'; import * as strings from '../../../base/common/strings.js'; import { EDITOR_FONT_DEFAULTS, filterValidationDecorations } from '../config/editorOptions.js'; import { CursorsController } from '../cursor/cursor.js'; import { CursorConfiguration } from '../cursorCommon.js'; import { Position } from '../core/position.js'; import { Range } from '../core/range.js'; import * as textModelEvents from '../textModelEvents.js'; import { TokenizationRegistry } from '../languages.js'; import { PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js'; import { tokenizeLineToHTML } from '../languages/textToHtmlTokenizer.js'; import * as viewEvents from '../viewEvents.js'; import { ViewLayout } from '../viewLayout/viewLayout.js'; import { MinimapTokensColorTracker } from './minimapTokensColorTracker.js'; import { MinimapLinesRenderingData, OverviewRulerDecorationsGroup, ViewLineRenderingData } from '../viewModel.js'; import { ViewModelDecorations } from './viewModelDecorations.js'; import { FocusChangedEvent, HiddenAreasChangedEvent, ModelContentChangedEvent, ModelDecorationsChangedEvent, ModelLanguageChangedEvent, ModelLanguageConfigurationChangedEvent, ModelOptionsChangedEvent, ModelTokensChangedEvent, ReadOnlyEditAttemptEvent, ScrollChangedEvent, ViewModelEventDispatcher, ViewZonesChangedEvent } from '../viewModelEventDispatcher.js'; import { ViewModelLinesFromModelAsIs, ViewModelLinesFromProjectedModel } from './viewModelLines.js'; import { GlyphMarginLanesModel } from './glyphLanesModel.js'; const USE_IDENTITY_LINES_COLLECTION = true; export class ViewModel extends Disposable { constructor(editorId, configuration, model, domLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, scheduleAtNextAnimationFrame, languageConfigurationService, _themeService, _attachedView, _transactionalTarget) { super(); this.languageConfigurationService = languageConfigurationService; this._themeService = _themeService; this._attachedView = _attachedView; this._transactionalTarget = _transactionalTarget; this.hiddenAreasModel = new HiddenAreasModel(); this.previousHiddenAreas = []; this._editorId = editorId; this._configuration = configuration; this.model = model; this._eventDispatcher = new ViewModelEventDispatcher(); this.onEvent = this._eventDispatcher.onEvent; this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService); this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0)); this._hasFocus = false; this._viewportStart = ViewportStart.create(this.model); this.glyphLanes = new GlyphMarginLanesModel(0); if (USE_IDENTITY_LINES_COLLECTION && this.model.isTooLargeForTokenization()) { this._lines = new ViewModelLinesFromModelAsIs(this.model); } else { const options = this._configuration.options; const fontInfo = options.get(50 /* EditorOption.fontInfo */); const wrappingStrategy = options.get(140 /* EditorOption.wrappingStrategy */); const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */); const wrappingIndent = options.get(139 /* EditorOption.wrappingIndent */); const wordBreak = options.get(130 /* EditorOption.wordBreak */); this._lines = new ViewModelLinesFromProjectedModel(this._editorId, this.model, domLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, fontInfo, this.model.getOptions().tabSize, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent, wordBreak); } this.coordinatesConverter = this._lines.createCoordinatesConverter(); this._cursor = this._register(new CursorsController(model, this, this.coordinatesConverter, this.cursorConfig)); this.viewLayout = this._register(new ViewLayout(this._configuration, this.getLineCount(), scheduleAtNextAnimationFrame)); this._register(this.viewLayout.onDidScroll((e) => { if (e.scrollTopChanged) { this._handleVisibleLinesChanged(); } if (e.scrollTopChanged) { this._viewportStart.invalidate(); } this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewScrollChangedEvent(e)); this._eventDispatcher.emitOutgoingEvent(new ScrollChangedEvent(e.oldScrollWidth, e.oldScrollLeft, e.oldScrollHeight, e.oldScrollTop, e.scrollWidth, e.scrollLeft, e.scrollHeight, e.scrollTop)); })); this._register(this.viewLayout.onDidContentSizeChange((e) => { this._eventDispatcher.emitOutgoingEvent(e); })); this._decorations = new ViewModelDecorations(this._editorId, this.model, this._configuration, this._lines, this.coordinatesConverter); this._registerModelEvents(); this._register(this._configuration.onDidChangeFast((e) => { try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); this._onConfigurationChanged(eventsCollector, e); } finally { this._eventDispatcher.endEmitViewEvents(); } })); this._register(MinimapTokensColorTracker.getInstance().onDidChange(() => { this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewTokensColorsChangedEvent()); })); this._register(this._themeService.onDidColorThemeChange((theme) => { this._invalidateDecorationsColorCache(); this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewThemeChangedEvent(theme)); })); this._updateConfigurationViewLineCountNow(); } dispose() { // First remove listeners, as disposing the lines might end up sending // model decoration changed events ... and we no longer care about them ... super.dispose(); this._decorations.dispose(); this._lines.dispose(); this._viewportStart.dispose(); this._eventDispatcher.dispose(); } createLineBreaksComputer() { return this._lines.createLineBreaksComputer(); } addViewEventHandler(eventHandler) { this._eventDispatcher.addViewEventHandler(eventHandler); } removeViewEventHandler(eventHandler) { this._eventDispatcher.removeViewEventHandler(eventHandler); } _updateConfigurationViewLineCountNow() { this._configuration.setViewLineCount(this._lines.getViewLineCount()); } getModelVisibleRanges() { const linesViewportData = this.viewLayout.getLinesViewportData(); const viewVisibleRange = new Range(linesViewportData.startLineNumber, this.getLineMinColumn(linesViewportData.startLineNumber), linesViewportData.endLineNumber, this.getLineMaxColumn(linesViewportData.endLineNumber)); const modelVisibleRanges = this._toModelVisibleRanges(viewVisibleRange); return modelVisibleRanges; } visibleLinesStabilized() { const modelVisibleRanges = this.getModelVisibleRanges(); this._attachedView.setVisibleLines(modelVisibleRanges, true); } _handleVisibleLinesChanged() { const modelVisibleRanges = this.getModelVisibleRanges(); this._attachedView.setVisibleLines(modelVisibleRanges, false); } setHasFocus(hasFocus) { this._hasFocus = hasFocus; this._cursor.setHasFocus(hasFocus); this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewFocusChangedEvent(hasFocus)); this._eventDispatcher.emitOutgoingEvent(new FocusChangedEvent(!hasFocus, hasFocus)); } onCompositionStart() { this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionStartEvent()); } onCompositionEnd() { this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionEndEvent()); } _captureStableViewport() { // We might need to restore the current start view range, so save it (if available) // But only if the scroll position is not at the top of the file if (this._viewportStart.isValid && this.viewLayout.getCurrentScrollTop() > 0) { const previousViewportStartViewPosition = new Position(this._viewportStart.viewLineNumber, this.getLineMinColumn(this._viewportStart.viewLineNumber)); const previousViewportStartModelPosition = this.coordinatesConverter.convertViewPositionToModelPosition(previousViewportStartViewPosition); return new StableViewport(previousViewportStartModelPosition, this._viewportStart.startLineDelta); } return new StableViewport(null, 0); } _onConfigurationChanged(eventsCollector, e) { const stableViewport = this._captureStableViewport(); const options = this._configuration.options; const fontInfo = options.get(50 /* EditorOption.fontInfo */); const wrappingStrategy = options.get(140 /* EditorOption.wrappingStrategy */); const wrappingInfo = options.get(147 /* EditorOption.wrappingInfo */); const wrappingIndent = options.get(139 /* EditorOption.wrappingIndent */); const wordBreak = options.get(130 /* EditorOption.wordBreak */); if (this._lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent, wordBreak)) { eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); this._cursor.onLineMappingChanged(eventsCollector); this._decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); this._updateConfigurationViewLineCount.schedule(); } if (e.hasChanged(92 /* EditorOption.readOnly */)) { // Must read again all decorations due to readOnly filtering this._decorations.reset(); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); } if (e.hasChanged(99 /* EditorOption.renderValidationDecorations */)) { this._decorations.reset(); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); } eventsCollector.emitViewEvent(new viewEvents.ViewConfigurationChangedEvent(e)); this.viewLayout.onConfigurationChanged(e); stableViewport.recoverViewportStart(this.coordinatesConverter, this.viewLayout); if (CursorConfiguration.shouldRecreate(e)) { this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService); this._cursor.updateConfiguration(this.cursorConfig); } } _registerModelEvents() { this._register(this.model.onDidChangeContentOrInjectedText((e) => { try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); let hadOtherModelChange = false; let hadModelLineChangeThatChangedLineMapping = false; const changes = (e instanceof textModelEvents.InternalModelContentChangeEvent ? e.rawContentChangedEvent.changes : e.changes); const versionId = (e instanceof textModelEvents.InternalModelContentChangeEvent ? e.rawContentChangedEvent.versionId : null); // Do a first pass to compute line mappings, and a second pass to actually interpret them const lineBreaksComputer = this._lines.createLineBreaksComputer(); for (const change of changes) { switch (change.changeType) { case 4 /* textModelEvents.RawContentChangedType.LinesInserted */: { for (let lineIdx = 0; lineIdx < change.detail.length; lineIdx++) { const line = change.detail[lineIdx]; let injectedText = change.injectedTexts[lineIdx]; if (injectedText) { injectedText = injectedText.filter(element => (!element.ownerId || element.ownerId === this._editorId)); } lineBreaksComputer.addRequest(line, injectedText, null); } break; } case 2 /* textModelEvents.RawContentChangedType.LineChanged */: { let injectedText = null; if (change.injectedText) { injectedText = change.injectedText.filter(element => (!element.ownerId || element.ownerId === this._editorId)); } lineBreaksComputer.addRequest(change.detail, injectedText, null); break; } } } const lineBreaks = lineBreaksComputer.finalize(); const lineBreakQueue = new ArrayQueue(lineBreaks); for (const change of changes) { switch (change.changeType) { case 1 /* textModelEvents.RawContentChangedType.Flush */: { this._lines.onModelFlushed(); eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); this._decorations.reset(); this.viewLayout.onFlushed(this.getLineCount()); hadOtherModelChange = true; break; } case 3 /* textModelEvents.RawContentChangedType.LinesDeleted */: { const linesDeletedEvent = this._lines.onModelLinesDeleted(versionId, change.fromLineNumber, change.toLineNumber); if (linesDeletedEvent !== null) { eventsCollector.emitViewEvent(linesDeletedEvent); this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber); } hadOtherModelChange = true; break; } case 4 /* textModelEvents.RawContentChangedType.LinesInserted */: { const insertedLineBreaks = lineBreakQueue.takeCount(change.detail.length); const linesInsertedEvent = this._lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks); if (linesInsertedEvent !== null) { eventsCollector.emitViewEvent(linesInsertedEvent); this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); } hadOtherModelChange = true; break; } case 2 /* textModelEvents.RawContentChangedType.LineChanged */: { const changedLineBreakData = lineBreakQueue.dequeue(); const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this._lines.onModelLineChanged(versionId, change.lineNumber, changedLineBreakData); hadModelLineChangeThatChangedLineMapping = lineMappingChanged; if (linesChangedEvent) { eventsCollector.emitViewEvent(linesChangedEvent); } if (linesInsertedEvent) { eventsCollector.emitViewEvent(linesInsertedEvent); this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); } if (linesDeletedEvent) { eventsCollector.emitViewEvent(linesDeletedEvent); this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber); } break; } case 5 /* textModelEvents.RawContentChangedType.EOLChanged */: { // Nothing to do. The new version will be accepted below break; } } } if (versionId !== null) { this._lines.acceptVersionId(versionId); } this.viewLayout.onHeightMaybeChanged(); if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) { eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); this._cursor.onLineMappingChanged(eventsCollector); this._decorations.onLineMappingChanged(); } } finally { this._eventDispatcher.endEmitViewEvents(); } // Update the configuration and reset the centered view line const viewportStartWasValid = this._viewportStart.isValid; this._viewportStart.invalidate(); this._configuration.setModelLineCount(this.model.getLineCount()); this._updateConfigurationViewLineCountNow(); // Recover viewport if (!this._hasFocus && this.model.getAttachedEditorCount() >= 2 && viewportStartWasValid) { const modelRange = this.model._getTrackedRange(this._viewportStart.modelTrackedRange); if (modelRange) { const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelRange.getStartPosition()); const viewPositionTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber); this.viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this._viewportStart.startLineDelta }, 1 /* ScrollType.Immediate */); } } try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); if (e instanceof textModelEvents.InternalModelContentChangeEvent) { eventsCollector.emitOutgoingEvent(new ModelContentChangedEvent(e.contentChangedEvent)); } this._cursor.onModelContentChanged(eventsCollector, e); } finally { this._eventDispatcher.endEmitViewEvents(); } this._handleVisibleLinesChanged(); })); this._register(this.model.onDidChangeTokens((e) => { const viewRanges = []; for (let j = 0, lenJ = e.ranges.length; j < lenJ; j++) { const modelRange = e.ranges[j]; const viewStartLineNumber = this.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.fromLineNumber, 1)).lineNumber; const viewEndLineNumber = this.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.toLineNumber, this.model.getLineMaxColumn(modelRange.toLineNumber))).lineNumber; viewRanges[j] = { fromLineNumber: viewStartLineNumber, toLineNumber: viewEndLineNumber }; } this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewTokensChangedEvent(viewRanges)); this._eventDispatcher.emitOutgoingEvent(new ModelTokensChangedEvent(e)); })); this._register(this.model.onDidChangeLanguageConfiguration((e) => { this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewLanguageConfigurationEvent()); this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService); this._cursor.updateConfiguration(this.cursorConfig); this._eventDispatcher.emitOutgoingEvent(new ModelLanguageConfigurationChangedEvent(e)); })); this._register(this.model.onDidChangeLanguage((e) => { this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService); this._cursor.updateConfiguration(this.cursorConfig); this._eventDispatcher.emitOutgoingEvent(new ModelLanguageChangedEvent(e)); })); this._register(this.model.onDidChangeOptions((e) => { // A tab size change causes a line mapping changed event => all view parts will repaint OK, no further event needed here if (this._lines.setTabSize(this.model.getOptions().tabSize)) { try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); this._cursor.onLineMappingChanged(eventsCollector); this._decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); } finally { this._eventDispatcher.endEmitViewEvents(); } this._updateConfigurationViewLineCount.schedule(); } this.cursorConfig = new CursorConfiguration(this.model.getLanguageId(), this.model.getOptions(), this._configuration, this.languageConfigurationService); this._cursor.updateConfiguration(this.cursorConfig); this._eventDispatcher.emitOutgoingEvent(new ModelOptionsChangedEvent(e)); })); this._register(this.model.onDidChangeDecorations((e) => { this._decorations.onModelDecorationsChanged(); this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewDecorationsChangedEvent(e)); this._eventDispatcher.emitOutgoingEvent(new ModelDecorationsChangedEvent(e)); })); } setHiddenAreas(ranges, source) { this.hiddenAreasModel.setHiddenAreas(source, ranges); const mergedRanges = this.hiddenAreasModel.getMergedRanges(); if (mergedRanges === this.previousHiddenAreas) { return; } this.previousHiddenAreas = mergedRanges; const stableViewport = this._captureStableViewport(); let lineMappingChanged = false; try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); lineMappingChanged = this._lines.setHiddenAreas(mergedRanges); if (lineMappingChanged) { eventsCollector.emitViewEvent(new viewEvents.ViewFlushedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null)); this._cursor.onLineMappingChanged(eventsCollector); this._decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); this.viewLayout.onHeightMaybeChanged(); } const firstModelLineInViewPort = stableViewport.viewportStartModelPosition?.lineNumber; const firstModelLineIsHidden = firstModelLineInViewPort && mergedRanges.some(range => range.startLineNumber <= firstModelLineInViewPort && firstModelLineInViewPort <= range.endLineNumber); if (!firstModelLineIsHidden) { stableViewport.recoverViewportStart(this.coordinatesConverter, this.viewLayout); } } finally { this._eventDispatcher.endEmitViewEvents(); } this._updateConfigurationViewLineCount.schedule(); if (lineMappingChanged) { this._eventDispatcher.emitOutgoingEvent(new HiddenAreasChangedEvent()); } } getVisibleRangesPlusViewportAboveBelow() { const layoutInfo = this._configuration.options.get(146 /* EditorOption.layoutInfo */); const lineHeight = this._configuration.options.get(67 /* EditorOption.lineHeight */); const linesAround = Math.max(20, Math.round(layoutInfo.height / lineHeight)); const partialData = this.viewLayout.getLinesViewportData(); const startViewLineNumber = Math.max(1, partialData.completelyVisibleStartLineNumber - linesAround); const endViewLineNumber = Math.min(this.getLineCount(), partialData.completelyVisibleEndLineNumber + linesAround); return this._toModelVisibleRanges(new Range(startViewLineNumber, this.getLineMinColumn(startViewLineNumber), endViewLineNumber, this.getLineMaxColumn(endViewLineNumber))); } getVisibleRanges() { const visibleViewRange = this.getCompletelyVisibleViewRange(); return this._toModelVisibleRanges(visibleViewRange); } getHiddenAreas() { return this._lines.getHiddenAreas(); } _toModelVisibleRanges(visibleViewRange) { const visibleRange = this.coordinatesConverter.convertViewRangeToModelRange(visibleViewRange); const hiddenAreas = this._lines.getHiddenAreas(); if (hiddenAreas.length === 0) { return [visibleRange]; } const result = []; let resultLen = 0; let startLineNumber = visibleRange.startLineNumber; let startColumn = visibleRange.startColumn; const endLineNumber = visibleRange.endLineNumber; const endColumn = visibleRange.endColumn; for (let i = 0, len = hiddenAreas.length; i < len; i++) { const hiddenStartLineNumber = hiddenAreas[i].startLineNumber; const hiddenEndLineNumber = hiddenAreas[i].endLineNumber; if (hiddenEndLineNumber < startLineNumber) { continue; } if (hiddenStartLineNumber > endLineNumber) { continue; } if (startLineNumber < hiddenStartLineNumber) { result[resultLen++] = new Range(startLineNumber, startColumn, hiddenStartLineNumber - 1, this.model.getLineMaxColumn(hiddenStartLineNumber - 1)); } startLineNumber = hiddenEndLineNumber + 1; startColumn = 1; } if (startLineNumber < endLineNumber || (startLineNumber === endLineNumber && startColumn < endColumn)) { result[resultLen++] = new Range(startLineNumber, startColumn, endLineNumber, endColumn); } return result; } getCompletelyVisibleViewRange() { const partialData = this.viewLayout.getLinesViewportData(); const startViewLineNumber = partialData.completelyVisibleStartLineNumber; const endViewLineNumber = partialData.completelyVisibleEndLineNumber; return new Range(startViewLineNumber, this.getLineMinColumn(startViewLineNumber), endViewLineNumber, this.getLineMaxColumn(endViewLineNumber)); } getCompletelyVisibleViewRangeAtScrollTop(scrollTop) { const partialData = this.viewLayout.getLinesViewportDataAtScrollTop(scrollTop); const startViewLineNumber = partialData.completelyVisibleStartLineNumber; const endViewLineNumber = partialData.completelyVisibleEndLineNumber; return new Range(startViewLineNumber, this.getLineMinColumn(startViewLineNumber), endViewLineNumber, this.getLineMaxColumn(endViewLineNumber)); } saveState() { const compatViewState = this.viewLayout.saveState(); const scrollTop = compatViewState.scrollTop; const firstViewLineNumber = this.viewLayout.getLineNumberAtVerticalOffset(scrollTop); const firstPosition = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(firstViewLineNumber, this.getLineMinColumn(firstViewLineNumber))); const firstPositionDeltaTop = this.viewLayout.getVerticalOffsetForLineNumber(firstViewLineNumber) - scrollTop; return { scrollLeft: compatViewState.scrollLeft, firstPosition: firstPosition, firstPositionDeltaTop: firstPositionDeltaTop }; } reduceRestoreState(state) { if (typeof state.firstPosition === 'undefined') { // This is a view state serialized by an older version return this._reduceRestoreStateCompatibility(state); } const modelPosition = this.model.validatePosition(state.firstPosition); const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); const scrollTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber) - state.firstPositionDeltaTop; return { scrollLeft: state.scrollLeft, scrollTop: scrollTop }; } _reduceRestoreStateCompatibility(state) { return { scrollLeft: state.scrollLeft, scrollTop: state.scrollTopWithoutViewZones }; } getTabSize() { return this.model.getOptions().tabSize; } getLineCount() { return this._lines.getViewLineCount(); } /** * Gives a hint that a lot of requests are about to come in for these line numbers. */ setViewport(startLineNumber, endLineNumber, centeredLineNumber) { this._viewportStart.update(this, startLineNumber); } getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber) { return this._lines.getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber); } getLinesIndentGuides(startLineNumber, endLineNumber) { return this._lines.getViewLinesIndentGuides(startLineNumber, endLineNumber); } getBracketGuidesInRangeByLine(startLineNumber, endLineNumber, activePosition, options) { return this._lines.getViewLinesBracketGuides(startLineNumber, endLineNumber, activePosition, options); } getLineContent(lineNumber) { return this._lines.getViewLineContent(lineNumber); } getLineLength(lineNumber) { return this._lines.getViewLineLength(lineNumber); } getLineMinColumn(lineNumber) { return this._lines.getViewLineMinColumn(lineNumber); } getLineMaxColumn(lineNumber) { return this._lines.getViewLineMaxColumn(lineNumber); } getLineFirstNonWhitespaceColumn(lineNumber) { const result = strings.firstNonWhitespaceIndex(this.getLineContent(lineNumber)); if (result === -1) { return 0; } return result + 1; } getLineLastNonWhitespaceColumn(lineNumber) { const result = strings.lastNonWhitespaceIndex(this.getLineContent(lineNumber)); if (result === -1) { return 0; } return result + 2; } getMinimapDecorationsInRange(range) { return this._decorations.getMinimapDecorationsInRange(range); } getDecorationsInViewport(visibleRange) { return this._decorations.getDecorationsViewportData(visibleRange).decorations; } getInjectedTextAt(viewPosition) { return this._lines.getInjectedTextAt(viewPosition); } getViewportViewLineRenderingData(visibleRange, lineNumber) { const allInlineDecorations = this._decorations.getDecorationsViewportData(visibleRange).inlineDecorations; const inlineDecorations = allInlineDecorations[lineNumber - visibleRange.startLineNumber]; return this._getViewLineRenderingData(lineNumber, inlineDecorations); } getViewLineRenderingData(lineNumber) { const inlineDecorations = this._decorations.getInlineDecorationsOnLine(lineNumber); return this._getViewLineRenderingData(lineNumber, inlineDecorations); } _getViewLineRenderingData(lineNumber, inlineDecorations) { const mightContainRTL = this.model.mightContainRTL(); const mightContainNonBasicASCII = this.model.mightContainNonBasicASCII(); const tabSize = this.getTabSize(); const lineData = this._lines.getViewLineData(lineNumber); if (lineData.inlineDecorations) { inlineDecorations = [ ...inlineDecorations, ...lineData.inlineDecorations.map(d => d.toInlineDecoration(lineNumber)) ]; } return new ViewLineRenderingData(lineData.minColumn, lineData.maxColumn, lineData.content, lineData.continuesWithWrappedLine, mightContainRTL, mightContainNonBasicASCII, lineData.tokens, inlineDecorations, tabSize, lineData.startVisibleColumn); } getViewLineData(lineNumber) { return this._lines.getViewLineData(lineNumber); } getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed) { const result = this._lines.getViewLinesData(startLineNumber, endLineNumber, needed); return new MinimapLinesRenderingData(this.getTabSize(), result); } getAllOverviewRulerDecorations(theme) { const decorations = this.model.getOverviewRulerDecorations(this._editorId, filterValidationDecorations(this._configuration.options)); const result = new OverviewRulerDecorations(); for (const decoration of decorations) { const decorationOptions = decoration.options; const opts = decorationOptions.overviewRuler; if (!opts) { continue; } const lane = opts.position; if (lane === 0) { continue; } const color = opts.getColor(theme.value); const viewStartLineNumber = this.coordinatesConverter.getViewLineNumberOfModelPosition(decoration.range.startLineNumber, decoration.range.startColumn); const viewEndLineNumber = this.coordinatesConverter.getViewLineNumberOfModelPosition(decoration.range.endLineNumber, decoration.range.endColumn); result.accept(color, decorationOptions.zIndex, viewStartLineNumber, viewEndLineNumber, lane); } return result.asArray; } _invalidateDecorationsColorCache() { const decorations = this.model.getOverviewRulerDecorations(); for (const decoration of decorations) { const opts1 = decoration.options.overviewRuler; opts1?.invalidateCachedColor(); const opts2 = decoration.options.minimap; opts2?.invalidateCachedColor(); } } getValueInRange(range, eol) { const modelRange = this.coordinatesConverter.convertViewRangeToModelRange(range); return this.model.getValueInRange(modelRange, eol); } getValueLengthInRange(range, eol) { const modelRange = this.coordinatesConverter.convertViewRangeToModelRange(range); return this.model.getValueLengthInRange(modelRange, eol); } modifyPosition(position, offset) { const modelPosition = this.coordinatesConverter.convertViewPositionToModelPosition(position); const resultModelPosition = this.model.modifyPosition(modelPosition, offset); return this.coordinatesConverter.convertModelPositionToViewPosition(resultModelPosition); } deduceModelPositionRelativeToViewPosition(viewAnchorPosition, deltaOffset, lineFeedCnt) { const modelAnchor = this.coordinatesConverter.convertViewPositionToModelPosition(viewAnchorPosition); if (this.model.getEOL().length === 2) { // This model uses CRLF, so the delta must take that into account if (deltaOffset < 0) { deltaOffset -= lineFeedCnt; } else { deltaOffset += lineFeedCnt; } } const modelAnchorOffset = this.model.getOffsetAt(modelAnchor); const resultOffset = modelAnchorOffset + deltaOffset; return this.model.getPositionAt(resultOffset); } getPlainTextToCopy(modelRanges, emptySelectionClipboard, forceCRLF) { const newLineCharacter = forceCRLF ? '\r\n' : this.model.getEOL(); modelRanges = modelRanges.slice(0); modelRanges.sort(Range.compareRangesUsingStarts); let hasEmptyRange = false; let hasNonEmptyRange = false; for (const range of modelRanges) { if (range.isEmpty()) { hasEmptyRange = true; } else { hasNonEmptyRange = true; } } if (!hasNonEmptyRange) { // all ranges are empty if (!emptySelectionClipboard) { return ''; } const modelLineNumbers = modelRanges.map((r) => r.startLineNumber); let result = ''; for (let i = 0; i < modelLineNumbers.length; i++) { if (i > 0 && modelLineNumbers[i - 1] === modelLineNumbers[i]) { continue; } result += this.model.getLineContent(modelLineNumbers[i]) + newLineCharacter; } return result; } if (hasEmptyRange && emptySelectionClipboard) { // mixed empty selections and non-empty selections const result = []; let prevModelLineNumber = 0; for (const modelRange of modelRanges) { const modelLineNumber = modelRange.startLineNumber; if (modelRange.isEmpty()) { if (modelLineNumber !== prevModelLineNumber) { result.push(this.model.getLineContent(modelLineNumber)); } } else { result.push(this.model.getValueInRange(modelRange, forceCRLF ? 2 /* EndOfLinePreference.CRLF */ : 0 /* EndOfLinePreference.TextDefined */)); } prevModelLineNumber = modelLineNumber; } return result.length === 1 ? result[0] : result; } const result = []; for (const modelRange of modelRanges) { if (!modelRange.isEmpty()) { result.push(this.model.getValueInRange(modelRange, forceCRLF ? 2 /* EndOfLinePreference.CRLF */ : 0 /* EndOfLinePreference.TextDefined */)); } } return result.length === 1 ? result[0] : result; } getRichTextToCopy(modelRanges, emptySelectionClipboard) { const languageId = this.model.getLanguageId(); if (languageId === PLAINTEXT_LANGUAGE_ID) { return null; } if (modelRanges.length !== 1) { // no multiple selection support at this time return null; } let range = modelRanges[0]; if (range.isEmpty()) { if (!emptySelectionClipboard) { // nothing to copy return null; } const lineNumber = range.startLineNumber; range = new Range(lineNumber, this.model.getLineMinColumn(lineNumber), lineNumber, this.model.getLineMaxColumn(lineNumber)); } const fontInfo = this._configuration.options.get(50 /* EditorOption.fontInfo */); const colorMap = this._getColorMap(); const hasBadChars = (/[:;\\\/<>]/.test(fontInfo.fontFamily)); const useDefaultFontFamily = (hasBadChars || fontInfo.fontFamily === EDITOR_FONT_DEFAULTS.fontFamily); let fontFamily; if (useDefaultFontFamily) { fontFamily = EDITOR_FONT_DEFAULTS.fontFamily; } else { fontFamily = fontInfo.fontFamily; fontFamily = fontFamily.replace(/"/g, '\''); const hasQuotesOrIsList = /[,']/.test(fontFamily); if (!hasQuotesOrIsList) { const needsQuotes = /[+ ]/.test(fontFamily); if (needsQuotes) { fontFamily = `'${fontFamily}'`; } } fontFamily = `${fontFamily}, ${EDITOR_FONT_DEFAULTS.fontFamily}`; } return { mode: languageId, html: (`<div style="` + `color: ${colorMap[1 /* ColorId.DefaultForeground */]};` + `background-color: ${colorMap[2 /* ColorId.DefaultBackground */]};` + `font-family: ${fontFamily};` + `font-weight: ${fontInfo.fontWeight};` + `font-size: ${fontInfo.fontSize}px;` + `line-height: ${fontInfo.lineHeight}px;` + `white-space: pre;` + `">` + this._getHTMLToCopy(range, colorMap) + '</div>') }; } _getHTMLToCopy(modelRange, colorMap) { const startLineNumber = modelRange.startLineNumber; const startColumn = modelRange.startColumn; const endLineNumber = modelRange.endLineNumber; const endColumn = modelRange.endColumn; const tabSize = this.getTabSize(); let result = ''; for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { const lineTokens = this.model.tokenization.getLineTokens(lineNumber); const lineContent = lineTokens.getLineContent(); const startOffset = (lineNumber === startLineNumber ? startColumn - 1 : 0); const endOffset = (lineNumber === endLineNumber ? endColumn - 1 : lineContent.length); if (lineContent === '') { result += '<br>'; } else { result += tokenizeLineToHTML(lineContent, lineTokens.inflate(), colorMap, startOffset, endOffset, tabSize, platform.isWindows); } } return result; } _getColorMap() { const colorMap = TokenizationRegistry.getColorMap(); const result = ['#000000']; if (colorMap) { for (let i = 1, len = colorMap.length; i < len; i++) { result[i] = Color.Format.CSS.formatHex(colorMap[i]); } } return result; } //#region cursor operations getPrimaryCursorState() { return this._cursor.getPrimaryCursorState(); } getLastAddedCursorIndex() { return this._cursor.getLastAddedCursorIndex(); } getCursorStates() { return this._cursor.getCursorStates(); } setCursorStates(source, reason, states) { return this._withViewEventsCollector(eventsCollector => this._cursor.setStates(eventsCollector, source, reason, states)); } getCursorColumnSelectData() { return this._cursor.getCursorColumnSelectData(); } getCursorAutoClosedCharacters() { return this._cursor.getAutoClosedCharacters(); } setCursorColumnSelectData(columnSelectData) { this._cursor.setCursorColumnSelectData(columnSelectData); } getPrevEditOperationType() { return this._cursor.getPrevEditOperationType(); } setPrevEditOperationType(type) { this._cursor.setPrevEditOperationType(type); } getSelection() { return this._cursor.getSelection(); } getSelections() { return this._cursor.getSelections(); } getPosition() { return this._cursor.getPrimaryCursorState().modelState.position; } setSelections(source, selections, reason = 0 /* CursorChangeReason.NotSet */) { this._withViewEventsCollector(eventsCollector => this._cursor.setSelections(eventsCollector, source, selections, reason)); } saveCursorState() { return this._cursor.saveState(); } restoreCursorState(states) { this._withViewEventsCollector(eventsCollector => this._cursor.restoreState(eventsCollector, states)); } _executeCursorEdit(callback) { if (this._cursor.context.cursorConfig.readOnly) { // we cannot edit when read only... this._eventDispatcher.emitOutgoingEvent(new ReadOnlyEditAttemptEvent()); return; } this._withViewEventsCollector(callback); } executeEdits(source, edits, cursorStateComputer) { this._executeCursorEdit(eventsCollector => this._cursor.executeEdits(eventsCollector, source, edits, cursorStateComputer)); } startComposition() { this._executeCursorEdit(eventsCollector => this._cursor.startComposition(eventsCollector)); } endComposition(source) { this._executeCursorEdit(eventsCollector => this._cursor.endComposition(eventsCollector, source)); } type(text, source) { this._executeCursorEdit(eventsCollector => this._cursor.type(eventsCollector, text, source)); } compositionType(text, replacePrevCharCnt, replaceNextCharCnt, positionDelta, source) { this._executeCursorEdit(eventsCollector => this._cursor.compositionType(eventsCollector, text, replacePrevCharCnt, replaceNextCharCnt, positionDelta, source)); } paste(text, pasteOnNewLine, multicursorText, source) { this._executeCursorEdit(eventsCollector => this._cursor.paste(eventsCollector, text, pasteOnNewLine, multicursorText, source)); } cut(source) { this._executeCursorEdit(eventsCollector => this._cursor.cut(eventsCollector, source)); } executeCommand(command, source) { this._executeCursorEdit(eventsCollector => this._cursor.executeCommand(eventsCollector, command, source)); } executeCommands(commands, source) { this._executeCursorEdit(eventsCollector => this._cursor.executeCommands(eventsCollector, commands, source)); } revealAllCursors(source, revealHorizontal, minimalReveal = false) { this._withViewEventsCollector(eventsCollector => this._cursor.revealAll(eventsCollector, source, minimalReveal, 0 /* viewEvents.VerticalRevealType.Simple */, revealHorizontal, 0 /* ScrollType.Smooth */)); } revealPrimaryCursor(source, revealHorizontal, minimalReveal = false) { this._withViewEventsCollector(eventsCollector => this._cursor.revealPrimary(eventsCollector, source, minimalReveal, 0 /* viewEvents.VerticalRevealType.Simple */, revealHorizontal, 0 /* ScrollType.Smooth */)); } revealTopMostCursor(source) { const viewPosition = this._cursor.getTopMostViewPosition(); const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column); this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, false, viewRange, null, 0 /* viewEvents.VerticalRevealType.Simple */, true, 0 /* ScrollType.Smooth */))); } revealBottomMostCursor(source) { const viewPosition = this._cursor.getBottomMostViewPosition(); const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column); this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, false, viewRange, null, 0 /* viewEvents.VerticalRevealType.Simple */, true, 0 /* ScrollType.Smooth */))); } revealRange(source, revealHorizontal, viewRange, verticalType, scrollType) { this._withViewEventsCollector(eventsCollector => eventsCollector.emitViewEvent(new viewEvents.ViewRevealRangeRequestEvent(source, false, viewRange, null, verticalType, revealHorizontal, scrollType))); } //#endregion //#region viewLayout changeWhitespace(callback) { const hadAChange = this.viewLayout.changeWhitespace(callback); if (hadAChange) { this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewZonesChangedEvent()); this._eventDispatcher.emitOutgoingEvent(new ViewZonesChangedEvent()); } } //#endregion _withViewEventsCollector(callback) { return this._transactionalTarget.batchChanges(() => { try { const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); return callback(eventsCollector); } finally { this._eventDispatcher.endEmitViewEvents(); } }); } batchEvents(callback) { this._withViewEventsCollector(() => { callback(); }); } normalizePosition(position, affinity) { return this._lines.normalizePosition(position, affinity); } /** * Gets the column at which indentation stops at a given line. * @internal */ getLineIndentColumn(lineNumber) { return this._lines.getLineIndentColumn(lineNumber); } } class ViewportStart { static create(model) { const viewportStartLineTrackedRange = model._setTrackedRange(null, new Range(1, 1, 1, 1), 1 /* TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges */); retur