monaco-editor-core
Version:
A browser based code editor
911 lines • 55.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 { 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