monaco-editor-core
Version:
A browser based code editor
892 lines (891 loc) • 61.7 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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 { filterValidationDecorations, filterFontDecorations } from '../config/editorOptions.js';
import { EDITOR_FONT_DEFAULTS } from '../config/fontInfo.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 { TextDirection } from '../model.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, ModelFontChangedEvent, ModelLanguageChangedEvent, ModelLanguageConfigurationChangedEvent, ModelLineHeightChangedEvent, ModelOptionsChangedEvent, ModelTokensChangedEvent, ReadOnlyEditAttemptEvent, ScrollChangedEvent, ViewModelEventDispatcher, ViewZonesChangedEvent, WidgetFocusChangedEvent } 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(59 /* EditorOption.fontInfo */);
const wrappingStrategy = options.get(156 /* EditorOption.wrappingStrategy */);
const wrappingInfo = options.get(166 /* EditorOption.wrappingInfo */);
const wrappingIndent = options.get(155 /* EditorOption.wrappingIndent */);
const wordBreak = options.get(146 /* EditorOption.wordBreak */);
const wrapOnEscapedLineFeeds = options.get(160 /* EditorOption.wrapOnEscapedLineFeeds */);
this._lines = new ViewModelLinesFromProjectedModel(this._editorId, this.model, domLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, fontInfo, this.model.getOptions().tabSize, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent, wordBreak, wrapOnEscapedLineFeeds);
}
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(), this._getCustomLineHeights(), 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);
}
_getCustomLineHeights() {
const allowVariableLineHeights = this._configuration.options.get(5 /* EditorOption.allowVariableLineHeights */);
if (!allowVariableLineHeights) {
return [];
}
const decorations = this.model.getCustomLineHeightsDecorations(this._editorId);
return decorations.map((d) => {
const lineNumber = d.range.startLineNumber;
const viewRange = this.coordinatesConverter.convertModelRangeToViewRange(new Range(lineNumber, 1, lineNumber, this.model.getLineMaxColumn(lineNumber)));
return {
decorationId: d.id,
startLineNumber: viewRange.startLineNumber,
endLineNumber: viewRange.endLineNumber,
lineHeight: d.options.lineHeight || 0
};
});
}
_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));
}
setHasWidgetFocus(hasWidgetFocus) {
this._eventDispatcher.emitOutgoingEvent(new WidgetFocusChangedEvent(!hasWidgetFocus, hasWidgetFocus));
}
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(59 /* EditorOption.fontInfo */);
const wrappingStrategy = options.get(156 /* EditorOption.wrappingStrategy */);
const wrappingInfo = options.get(166 /* EditorOption.wrappingInfo */);
const wrappingIndent = options.get(155 /* EditorOption.wrappingIndent */);
const wordBreak = options.get(146 /* 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._getCustomLineHeights());
this._updateConfigurationViewLineCount.schedule();
}
if (e.hasChanged(104 /* EditorOption.readOnly */)) {
// Must read again all decorations due to readOnly filtering
this._decorations.reset();
eventsCollector.emitViewEvent(new viewEvents.ViewDecorationsChangedEvent(null));
}
if (e.hasChanged(112 /* 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(), this._getCustomLineHeights());
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();
}));
const allowVariableLineHeights = this._configuration.options.get(5 /* EditorOption.allowVariableLineHeights */);
if (allowVariableLineHeights) {
this._register(this.model.onDidChangeLineHeight((e) => {
const filteredChanges = e.changes.filter((change) => change.ownerId === this._editorId || change.ownerId === 0);
this.viewLayout.changeSpecialLineHeights((accessor) => {
for (const change of filteredChanges) {
const { decorationId, lineNumber, lineHeight } = change;
const viewRange = this.coordinatesConverter.convertModelRangeToViewRange(new Range(lineNumber, 1, lineNumber, this.model.getLineMaxColumn(lineNumber)));
if (lineHeight !== null) {
accessor.insertOrChangeCustomLineHeight(decorationId, viewRange.startLineNumber, viewRange.endLineNumber, lineHeight);
}
else {
accessor.removeCustomLineHeight(decorationId);
}
}
});
// recreate the model event using the filtered changes
if (filteredChanges.length > 0) {
const filteredEvent = new textModelEvents.ModelLineHeightChangedEvent(filteredChanges);
this._eventDispatcher.emitOutgoingEvent(new ModelLineHeightChangedEvent(filteredEvent));
}
}));
}
const allowVariableFonts = this._configuration.options.get(172 /* EditorOption.effectiveAllowVariableFonts */);
if (allowVariableFonts) {
this._register(this.model.onDidChangeFont((e) => {
const filteredChanges = e.changes.filter((change) => change.ownerId === this._editorId || change.ownerId === 0);
// recreate the model event using the filtered changes
if (filteredChanges.length > 0) {
const filteredEvent = new textModelEvents.ModelFontChangedEvent(filteredChanges);
this._eventDispatcher.emitOutgoingEvent(new ModelFontChangedEvent(filteredEvent));
}
}));
}
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(), this._getCustomLineHeights());
}
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));
}));
}
getFontSizeAtPosition(position) {
const allowVariableFonts = this._configuration.options.get(172 /* EditorOption.effectiveAllowVariableFonts */);
if (!allowVariableFonts) {
return null;
}
const fontDecorations = this.model.getFontDecorationsInRange(Range.fromPositions(position), this._editorId);
let fontSize = this._configuration.options.get(59 /* EditorOption.fontInfo */).fontSize + 'px';
for (const fontDecoration of fontDecorations) {
if (fontDecoration.options.fontSize) {
fontSize = fontDecoration.options.fontSize;
break;
}
}
return fontSize;
}
/**
* @param forceUpdate If true, the hidden areas will be updated even if the new ranges are the same as the previous ranges.
* This is because the model might have changed, which resets the hidden areas, but not the last cached value.
* This needs a better fix in the future.
*/
setHiddenAreas(ranges, source, forceUpdate) {
this.hiddenAreasModel.setHiddenAreas(source, ranges);
const mergedRanges = this.hiddenAreasModel.getMergedRanges();
if (mergedRanges === this.previousHiddenAreas && !forceUpdate) {
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._getCustomLineHeights());
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(165 /* EditorOption.layoutInfo */);
const lineHeight = this._configuration.options.get(75 /* 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);
}
_getTextDirection(lineNumber, decorations) {
let rtlCount = 0;
for (const decoration of decorations) {
const range = decoration.range;
if (range.startLineNumber > lineNumber || range.endLineNumber < lineNumber) {
continue;
}
const textDirection = decoration.options.textDirection;
if (textDirection === TextDirection.RTL) {
rtlCount++;
}
else if (textDirection === TextDirection.LTR) {
rtlCount--;
}
}
return rtlCount > 0 ? TextDirection.RTL : TextDirection.LTR;
}
getTextDirection(lineNumber) {
const decorationsCollection = this._decorations.getDecorationsOnLine(lineNumber);
return this._getTextDirection(lineNumber, decorationsCollection.decorations);
}
getViewportViewLineRenderingData(visibleRange, lineNumber) {
const viewportDecorationsCollection = this._decorations.getDecorationsViewportData(visibleRange);
const inlineDecorations = viewportDecorationsCollection.inlineDecorations[lineNumber - visibleRange.startLineNumber];
return this._getViewLineRenderingData(lineNumber, inlineDecorations, viewportDecorationsCollection.hasVariableFonts, viewportDecorationsCollection.decorations);
}
getViewLineRenderingData(lineNumber) {
const decorationsCollection = this._decorations.getDecorationsOnLine(lineNumber);
return this._getViewLineRenderingData(lineNumber, decorationsCollection.inlineDecorations[0], decorationsCollection.hasVariableFonts, decorationsCollection.decorations);
}
_getViewLineRenderingData(lineNumber, inlineDecorations, hasVariableFonts, decorations) {
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, this._getTextDirection(lineNumber, decorations), hasVariableFonts);
}
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), filterFontDecorations(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(59 /* 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);