UNPKG

monaco-editor

Version:
736 lines (735 loc) • 33.1 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); } return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); import * as nls from '../../../nls.js'; import { onUnexpectedError } from '../../../base/common/errors.js'; import { Emitter } from '../../../base/common/event.js'; import * as strings from '../../../base/common/strings.js'; import { CursorCollection } from './cursorCollection.js'; import { CursorColumns, CursorConfiguration, CursorContext, CursorState, EditOperationResult } from './cursorCommon.js'; import { DeleteOperations } from './cursorDeleteOperations.js'; import { TypeOperations } from './cursorTypeOperations.js'; import { Range } from '../core/range.js'; import { Selection } from '../core/selection.js'; import * as editorCommon from '../editorCommon.js'; import * as viewEvents from '../view/viewEvents.js'; function containsLineMappingChanged(events) { for (var i = 0, len = events.length; i < len; i++) { if (events[i].type === 6 /* ViewLineMappingChanged */) { return true; } } return false; } var CursorStateChangedEvent = /** @class */ (function () { function CursorStateChangedEvent(selections, source, reason) { this.selections = selections; this.source = source; this.reason = reason; } return CursorStateChangedEvent; }()); export { CursorStateChangedEvent }; /** * A snapshot of the cursor and the model state */ var CursorModelState = /** @class */ (function () { function CursorModelState(model, cursor) { this.modelVersionId = model.getVersionId(); this.cursorState = cursor.getAll(); } CursorModelState.prototype.equals = function (other) { if (!other) { return false; } if (this.modelVersionId !== other.modelVersionId) { return false; } if (this.cursorState.length !== other.cursorState.length) { return false; } for (var i = 0, len = this.cursorState.length; i < len; i++) { if (!this.cursorState[i].equals(other.cursorState[i])) { return false; } } return true; }; return CursorModelState; }()); export { CursorModelState }; var Cursor = /** @class */ (function (_super) { __extends(Cursor, _super); function Cursor(configuration, model, viewModel) { var _this = _super.call(this) || this; _this._onDidReachMaxCursorCount = _this._register(new Emitter()); _this.onDidReachMaxCursorCount = _this._onDidReachMaxCursorCount.event; _this._onDidAttemptReadOnlyEdit = _this._register(new Emitter()); _this.onDidAttemptReadOnlyEdit = _this._onDidAttemptReadOnlyEdit.event; _this._onDidChange = _this._register(new Emitter()); _this.onDidChange = _this._onDidChange.event; _this._configuration = configuration; _this._model = model; _this._knownModelVersionId = _this._model.getVersionId(); _this._viewModel = viewModel; _this.context = new CursorContext(_this._configuration, _this._model, _this._viewModel); _this._cursors = new CursorCollection(_this.context); _this._isHandling = false; _this._isDoingComposition = false; _this._columnSelectData = null; _this._prevEditOperationType = 0 /* Other */; _this._register(_this._model.onDidChangeRawContent(function (e) { _this._knownModelVersionId = e.versionId; if (_this._isHandling) { return; } var hadFlushEvent = e.containsEvent(1 /* Flush */); _this._onModelContentChanged(hadFlushEvent); })); _this._register(viewModel.addEventListener(function (events) { if (!containsLineMappingChanged(events)) { return; } if (_this._knownModelVersionId !== _this._model.getVersionId()) { // There are model change events that I didn't yet receive. // // This can happen when editing the model, and the view model receives the change events first, // and the view model emits line mapping changed events, all before the cursor gets a chance to // recover from markers. // // The model change listener above will be called soon and we'll ensure a valid cursor state there. return; } // Ensure valid state _this.setStates('viewModel', 0 /* NotSet */, _this.getAll()); })); var updateCursorContext = function () { _this.context = new CursorContext(_this._configuration, _this._model, _this._viewModel); _this._cursors.updateContext(_this.context); }; _this._register(_this._model.onDidChangeLanguage(function (e) { updateCursorContext(); })); _this._register(_this._model.onDidChangeLanguageConfiguration(function () { updateCursorContext(); })); _this._register(_this._model.onDidChangeOptions(function () { updateCursorContext(); })); _this._register(_this._configuration.onDidChange(function (e) { if (CursorConfiguration.shouldRecreate(e)) { updateCursorContext(); } })); return _this; } Cursor.prototype.dispose = function () { this._cursors.dispose(); _super.prototype.dispose.call(this); }; // ------ some getters/setters Cursor.prototype.getPrimaryCursor = function () { return this._cursors.getPrimaryCursor(); }; Cursor.prototype.getLastAddedCursorIndex = function () { return this._cursors.getLastAddedCursorIndex(); }; Cursor.prototype.getAll = function () { return this._cursors.getAll(); }; Cursor.prototype.setStates = function (source, reason, states) { if (states !== null && states.length > Cursor.MAX_CURSOR_COUNT) { states = states.slice(0, Cursor.MAX_CURSOR_COUNT); this._onDidReachMaxCursorCount.fire(void 0); } var oldState = new CursorModelState(this._model, this); this._cursors.setStates(states); this._cursors.normalize(); this._columnSelectData = null; this._emitStateChangedIfNecessary(source, reason, oldState); }; Cursor.prototype.setColumnSelectData = function (columnSelectData) { this._columnSelectData = columnSelectData; }; Cursor.prototype.reveal = function (horizontal, target, scrollType) { this._revealRange(target, 0 /* Simple */, horizontal, scrollType); }; Cursor.prototype.revealRange = function (revealHorizontal, viewRange, verticalType, scrollType) { this.emitCursorRevealRange(viewRange, verticalType, revealHorizontal, scrollType); }; Cursor.prototype.scrollTo = function (desiredScrollTop) { this._viewModel.viewLayout.setScrollPositionSmooth({ scrollTop: desiredScrollTop }); }; Cursor.prototype.saveState = function () { var result = []; var selections = this._cursors.getSelections(); for (var i = 0, len = selections.length; i < len; i++) { var selection = selections[i]; result.push({ inSelectionMode: !selection.isEmpty(), selectionStart: { lineNumber: selection.selectionStartLineNumber, column: selection.selectionStartColumn, }, position: { lineNumber: selection.positionLineNumber, column: selection.positionColumn, } }); } return result; }; Cursor.prototype.restoreState = function (states) { var desiredSelections = []; for (var i = 0, len = states.length; i < len; i++) { var state = states[i]; var positionLineNumber = 1; var positionColumn = 1; // Avoid missing properties on the literal if (state.position && state.position.lineNumber) { positionLineNumber = state.position.lineNumber; } if (state.position && state.position.column) { positionColumn = state.position.column; } var selectionStartLineNumber = positionLineNumber; var selectionStartColumn = positionColumn; // Avoid missing properties on the literal if (state.selectionStart && state.selectionStart.lineNumber) { selectionStartLineNumber = state.selectionStart.lineNumber; } if (state.selectionStart && state.selectionStart.column) { selectionStartColumn = state.selectionStart.column; } desiredSelections.push({ selectionStartLineNumber: selectionStartLineNumber, selectionStartColumn: selectionStartColumn, positionLineNumber: positionLineNumber, positionColumn: positionColumn }); } this.setStates('restoreState', 0 /* NotSet */, CursorState.fromModelSelections(desiredSelections)); this.reveal(true, 0 /* Primary */, 1 /* Immediate */); }; Cursor.prototype._onModelContentChanged = function (hadFlushEvent) { this._prevEditOperationType = 0 /* Other */; if (hadFlushEvent) { // a model.setValue() was called this._cursors.dispose(); this._cursors = new CursorCollection(this.context); this._emitStateChangedIfNecessary('model', 1 /* ContentFlush */, null); } else { var selectionsFromMarkers = this._cursors.readSelectionFromMarkers(); this.setStates('modelChange', 2 /* RecoverFromMarkers */, CursorState.fromModelSelections(selectionsFromMarkers)); } }; Cursor.prototype.getSelection = function () { return this._cursors.getPrimaryCursor().modelState.selection; }; Cursor.prototype.getColumnSelectData = function () { if (this._columnSelectData) { return this._columnSelectData; } var primaryCursor = this._cursors.getPrimaryCursor(); var primaryPos = primaryCursor.viewState.position; return { toViewLineNumber: primaryPos.lineNumber, toViewVisualColumn: CursorColumns.visibleColumnFromColumn2(this.context.config, this.context.viewModel, primaryPos) }; }; Cursor.prototype.getSelections = function () { return this._cursors.getSelections(); }; Cursor.prototype.getViewSelections = function () { return this._cursors.getViewSelections(); }; Cursor.prototype.getPosition = function () { return this._cursors.getPrimaryCursor().modelState.position; }; Cursor.prototype.setSelections = function (source, selections) { this.setStates(source, 0 /* NotSet */, CursorState.fromModelSelections(selections)); }; Cursor.prototype.getPrevEditOperationType = function () { return this._prevEditOperationType; }; Cursor.prototype.setPrevEditOperationType = function (type) { this._prevEditOperationType = type; }; // ------ auxiliary handling logic Cursor.prototype._executeEditOperation = function (opResult) { if (!opResult) { // Nothing to execute return; } if (opResult.shouldPushStackElementBefore) { this._model.pushStackElement(); } var result = CommandExecutor.executeCommands(this._model, this._cursors.getSelections(), opResult.commands); if (result) { // The commands were applied correctly this._interpretCommandResult(result); this._prevEditOperationType = opResult.type; } if (opResult.shouldPushStackElementAfter) { this._model.pushStackElement(); } }; Cursor.prototype._interpretCommandResult = function (cursorState) { if (!cursorState || cursorState.length === 0) { cursorState = this._cursors.readSelectionFromMarkers(); } this._columnSelectData = null; this._cursors.setSelections(cursorState); this._cursors.normalize(); }; // ----------------------------------------------------------------------------------------------------------- // ----- emitting events Cursor.prototype._emitStateChangedIfNecessary = function (source, reason, oldState) { var newState = new CursorModelState(this._model, this); if (newState.equals(oldState)) { return false; } var selections = this._cursors.getSelections(); var viewSelections = this._cursors.getViewSelections(); // Let the view get the event first. try { var eventsCollector = this._beginEmit(); eventsCollector.emit(new viewEvents.ViewCursorStateChangedEvent(viewSelections)); } finally { this._endEmit(); } // Only after the view has been notified, let the rest of the world know... if (!oldState || oldState.cursorState.length !== newState.cursorState.length || newState.cursorState.some(function (newCursorState, i) { return !newCursorState.modelState.equals(oldState.cursorState[i].modelState); })) { this._onDidChange.fire(new CursorStateChangedEvent(selections, source || 'keyboard', reason)); } return true; }; Cursor.prototype._revealRange = function (revealTarget, verticalType, revealHorizontal, scrollType) { var viewPositions = this._cursors.getViewPositions(); var viewPosition = viewPositions[0]; if (revealTarget === 1 /* TopMost */) { for (var i = 1; i < viewPositions.length; i++) { if (viewPositions[i].isBefore(viewPosition)) { viewPosition = viewPositions[i]; } } } else if (revealTarget === 2 /* BottomMost */) { for (var i = 1; i < viewPositions.length; i++) { if (viewPosition.isBeforeOrEqual(viewPositions[i])) { viewPosition = viewPositions[i]; } } } else { if (viewPositions.length > 1) { // no revealing! return; } } var viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column); this.emitCursorRevealRange(viewRange, verticalType, revealHorizontal, scrollType); }; Cursor.prototype.emitCursorRevealRange = function (viewRange, verticalType, revealHorizontal, scrollType) { try { var eventsCollector = this._beginEmit(); eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(viewRange, verticalType, revealHorizontal, scrollType)); } finally { this._endEmit(); } }; // ----------------------------------------------------------------------------------------------------------- // ----- handlers beyond this point Cursor.prototype.trigger = function (source, handlerId, payload) { var H = editorCommon.Handler; if (handlerId === H.CompositionStart) { this._isDoingComposition = true; return; } if (handlerId === H.CompositionEnd) { this._isDoingComposition = false; } if (this._configuration.editor.readOnly) { // All the remaining handlers will try to edit the model, // but we cannot edit when read only... this._onDidAttemptReadOnlyEdit.fire(void 0); return; } var oldState = new CursorModelState(this._model, this); var cursorChangeReason = 0 /* NotSet */; if (handlerId !== H.Undo && handlerId !== H.Redo) { // TODO@Alex: if the undo/redo stack contains non-null selections // it would also be OK to stop tracking selections here this._cursors.stopTrackingSelections(); } // ensure valid state on all cursors this._cursors.ensureValidState(); this._isHandling = true; try { switch (handlerId) { case H.Type: this._type(source, payload.text); break; case H.ReplacePreviousChar: this._replacePreviousChar(payload.text, payload.replaceCharCnt); break; case H.Paste: cursorChangeReason = 4 /* Paste */; this._paste(payload.text, payload.pasteOnNewLine, payload.multicursorText); break; case H.Cut: this._cut(); break; case H.Undo: cursorChangeReason = 5 /* Undo */; this._interpretCommandResult(this._model.undo()); break; case H.Redo: cursorChangeReason = 6 /* Redo */; this._interpretCommandResult(this._model.redo()); break; case H.ExecuteCommand: this._externalExecuteCommand(payload); break; case H.ExecuteCommands: this._externalExecuteCommands(payload); break; case H.CompositionEnd: this._interpretCompositionEnd(source); break; } } catch (err) { onUnexpectedError(err); } this._isHandling = false; if (handlerId !== H.Undo && handlerId !== H.Redo) { this._cursors.startTrackingSelections(); } if (this._emitStateChangedIfNecessary(source, cursorChangeReason, oldState)) { this._revealRange(0 /* Primary */, 0 /* Simple */, true, 0 /* Smooth */); } }; Cursor.prototype._interpretCompositionEnd = function (source) { if (!this._isDoingComposition && source === 'keyboard') { // composition finishes, let's check if we need to auto complete if necessary. this._executeEditOperation(TypeOperations.compositionEndWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections())); } }; Cursor.prototype._type = function (source, text) { if (!this._isDoingComposition && source === 'keyboard') { // If this event is coming straight from the keyboard, look for electric characters and enter for (var i = 0, len = text.length; i < len; i++) { var charCode = text.charCodeAt(i); var chr = void 0; if (strings.isHighSurrogate(charCode) && i + 1 < len) { chr = text.charAt(i) + text.charAt(i + 1); i++; } else { chr = text.charAt(i); } // Here we must interpret each typed character individually, that's why we create a new context this._executeEditOperation(TypeOperations.typeWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), chr)); } } else { this._executeEditOperation(TypeOperations.typeWithoutInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), text)); } }; Cursor.prototype._replacePreviousChar = function (text, replaceCharCnt) { this._executeEditOperation(TypeOperations.replacePreviousChar(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), text, replaceCharCnt)); }; Cursor.prototype._paste = function (text, pasteOnNewLine, multicursorText) { this._executeEditOperation(TypeOperations.paste(this.context.config, this.context.model, this.getSelections(), text, pasteOnNewLine, multicursorText)); }; Cursor.prototype._cut = function () { this._executeEditOperation(DeleteOperations.cut(this.context.config, this.context.model, this.getSelections())); }; Cursor.prototype._externalExecuteCommand = function (command) { this._cursors.killSecondaryCursors(); this._executeEditOperation(new EditOperationResult(0 /* Other */, [command], { shouldPushStackElementBefore: false, shouldPushStackElementAfter: false })); }; Cursor.prototype._externalExecuteCommands = function (commands) { this._executeEditOperation(new EditOperationResult(0 /* Other */, commands, { shouldPushStackElementBefore: false, shouldPushStackElementAfter: false })); }; Cursor.MAX_CURSOR_COUNT = 10000; return Cursor; }(viewEvents.ViewEventEmitter)); export { Cursor }; var CommandExecutor = /** @class */ (function () { function CommandExecutor() { } CommandExecutor.executeCommands = function (model, selectionsBefore, commands) { var ctx = { model: model, selectionsBefore: selectionsBefore, trackedRanges: [], trackedRangesDirection: [] }; var result = this._innerExecuteCommands(ctx, commands); for (var i = 0, len = ctx.trackedRanges.length; i < len; i++) { ctx.model._setTrackedRange(ctx.trackedRanges[i], null, 0 /* AlwaysGrowsWhenTypingAtEdges */); } return result; }; CommandExecutor._innerExecuteCommands = function (ctx, commands) { if (this._arrayIsEmpty(commands)) { return null; } var commandsData = this._getEditOperations(ctx, commands); if (commandsData.operations.length === 0) { return null; } var rawOperations = commandsData.operations; var loserCursorsMap = this._getLoserCursorMap(rawOperations); if (loserCursorsMap.hasOwnProperty('0')) { // These commands are very messed up console.warn('Ignoring commands'); return null; } // Remove operations belonging to losing cursors var filteredOperations = []; for (var i = 0, len = rawOperations.length; i < len; i++) { if (!loserCursorsMap.hasOwnProperty(rawOperations[i].identifier.major.toString())) { filteredOperations.push(rawOperations[i]); } } // TODO@Alex: find a better way to do this. // give the hint that edit operations are tracked to the model if (commandsData.hadTrackedEditOperation && filteredOperations.length > 0) { filteredOperations[0]._isTracked = true; } var selectionsAfter = ctx.model.pushEditOperations(ctx.selectionsBefore, filteredOperations, function (inverseEditOperations) { var groupedInverseEditOperations = []; for (var i = 0; i < ctx.selectionsBefore.length; i++) { groupedInverseEditOperations[i] = []; } for (var i = 0; i < inverseEditOperations.length; i++) { var op = inverseEditOperations[i]; if (!op.identifier) { // perhaps auto whitespace trim edits continue; } groupedInverseEditOperations[op.identifier.major].push(op); } var minorBasedSorter = function (a, b) { return a.identifier.minor - b.identifier.minor; }; var cursorSelections = []; var _loop_1 = function (i) { if (groupedInverseEditOperations[i].length > 0) { groupedInverseEditOperations[i].sort(minorBasedSorter); cursorSelections[i] = commands[i].computeCursorState(ctx.model, { getInverseEditOperations: function () { return groupedInverseEditOperations[i]; }, getTrackedSelection: function (id) { var idx = parseInt(id, 10); var range = ctx.model._getTrackedRange(ctx.trackedRanges[idx]); if (ctx.trackedRangesDirection[idx] === 0 /* LTR */) { return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn); } return new Selection(range.endLineNumber, range.endColumn, range.startLineNumber, range.startColumn); } }); } else { cursorSelections[i] = ctx.selectionsBefore[i]; } }; for (var i = 0; i < ctx.selectionsBefore.length; i++) { _loop_1(i); } return cursorSelections; }); if (!selectionsAfter) { selectionsAfter = ctx.selectionsBefore; } // Extract losing cursors var losingCursors = []; for (var losingCursorIndex in loserCursorsMap) { if (loserCursorsMap.hasOwnProperty(losingCursorIndex)) { losingCursors.push(parseInt(losingCursorIndex, 10)); } } // Sort losing cursors descending losingCursors.sort(function (a, b) { return b - a; }); // Remove losing cursors for (var i = 0; i < losingCursors.length; i++) { selectionsAfter.splice(losingCursors[i], 1); } return selectionsAfter; }; CommandExecutor._arrayIsEmpty = function (commands) { for (var i = 0, len = commands.length; i < len; i++) { if (commands[i]) { return false; } } return true; }; CommandExecutor._getEditOperations = function (ctx, commands) { var operations = []; var hadTrackedEditOperation = false; for (var i = 0, len = commands.length; i < len; i++) { var command = commands[i]; if (command) { var r = this._getEditOperationsFromCommand(ctx, i, command); operations = operations.concat(r.operations); hadTrackedEditOperation = hadTrackedEditOperation || r.hadTrackedEditOperation; } } return { operations: operations, hadTrackedEditOperation: hadTrackedEditOperation }; }; CommandExecutor._getEditOperationsFromCommand = function (ctx, majorIdentifier, command) { // This method acts as a transaction, if the command fails // everything it has done is ignored var operations = []; var operationMinor = 0; var addEditOperation = function (selection, text) { if (selection.isEmpty() && text === '') { // This command wants to add a no-op => no thank you return; } operations.push({ identifier: { major: majorIdentifier, minor: operationMinor++ }, range: selection, text: text, forceMoveMarkers: false, isAutoWhitespaceEdit: command.insertsAutoWhitespace }); }; var hadTrackedEditOperation = false; var addTrackedEditOperation = function (selection, text) { hadTrackedEditOperation = true; addEditOperation(selection, text); }; var trackSelection = function (selection, trackPreviousOnEmpty) { var stickiness; if (selection.isEmpty()) { if (typeof trackPreviousOnEmpty === 'boolean') { if (trackPreviousOnEmpty) { stickiness = 2 /* GrowsOnlyWhenTypingBefore */; } else { stickiness = 3 /* GrowsOnlyWhenTypingAfter */; } } else { // Try to lock it with surrounding text var maxLineColumn = ctx.model.getLineMaxColumn(selection.startLineNumber); if (selection.startColumn === maxLineColumn) { stickiness = 2 /* GrowsOnlyWhenTypingBefore */; } else { stickiness = 3 /* GrowsOnlyWhenTypingAfter */; } } } else { stickiness = 1 /* NeverGrowsWhenTypingAtEdges */; } var l = ctx.trackedRanges.length; var id = ctx.model._setTrackedRange(null, selection, stickiness); ctx.trackedRanges[l] = id; ctx.trackedRangesDirection[l] = selection.getDirection(); return l.toString(); }; var editOperationBuilder = { addEditOperation: addEditOperation, addTrackedEditOperation: addTrackedEditOperation, trackSelection: trackSelection }; try { command.getEditOperations(ctx.model, editOperationBuilder); } catch (e) { e.friendlyMessage = nls.localize('corrupt.commands', "Unexpected exception while executing command."); onUnexpectedError(e); return { operations: [], hadTrackedEditOperation: false }; } return { operations: operations, hadTrackedEditOperation: hadTrackedEditOperation }; }; CommandExecutor._getLoserCursorMap = function (operations) { // This is destructive on the array operations = operations.slice(0); // Sort operations with last one first operations.sort(function (a, b) { // Note the minus! return -(Range.compareRangesUsingEnds(a.range, b.range)); }); // Operations can not overlap! var loserCursorsMap = {}; for (var i = 1; i < operations.length; i++) { var previousOp = operations[i - 1]; var currentOp = operations[i]; if (previousOp.range.getStartPosition().isBefore(currentOp.range.getEndPosition())) { var loserMajor = void 0; if (previousOp.identifier.major > currentOp.identifier.major) { // previousOp loses the battle loserMajor = previousOp.identifier.major; } else { loserMajor = currentOp.identifier.major; } loserCursorsMap[loserMajor.toString()] = true; for (var j = 0; j < operations.length; j++) { if (operations[j].identifier.major === loserMajor) { operations.splice(j, 1); if (j < i) { i--; } j--; } } if (i > 0) { i--; } } } return loserCursorsMap; }; return CommandExecutor; }());