/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';
var __extends = (this && this.__extends) || (function () {
    var 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 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';
import * as strings from '../../../base/common/strings';
import { onUnexpectedError } from '../../../base/common/errors';
import { CursorCollection } from './cursorCollection';
import { Range } from '../core/range';
import { Selection, SelectionDirection } from '../core/selection';
import * as editorCommon from '../editorCommon';
import { CursorColumns, CursorConfiguration, EditOperationResult, CursorContext, CursorState } from './cursorCommon';
import { DeleteOperations } from './cursorDeleteOperations';
import { TypeOperations } from './cursorTypeOperations';
import { CursorChangeReason } from './cursorEvents';
import * as viewEvents from '../view/viewEvents';
import { Emitter } from '../../../base/common/event';
import { TrackedRangeStickiness } from '../model';
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._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', CursorChangeReason.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) {
        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', CursorChangeReason.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', CursorChangeReason.ContentFlush, null);
        }
        else {
            var selectionsFromMarkers = this._cursors.readSelectionFromMarkers();
            this.setStates('modelChange', CursorChangeReason.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, CursorChangeReason.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) {
            return;
        }
        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;
            return;
        }
        if (this._configuration.editor.readOnly) {
            // All the remaining handlers will try to edit the model,
            // but we cannot edit when read only...
            return;
        }
        var oldState = new CursorModelState(this._model, this);
        var cursorChangeReason = CursorChangeReason.NotSet;
        // 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 = CursorChangeReason.Paste;
                    this._paste(payload.text, payload.pasteOnNewLine, payload.multicursorText);
                    break;
                case H.Cut:
                    this._cut();
                    break;
                case H.Undo:
                    cursorChangeReason = CursorChangeReason.Undo;
                    this._interpretCommandResult(this._model.undo());
                    break;
                case H.Redo:
                    cursorChangeReason = CursorChangeReason.Redo;
                    this._interpretCommandResult(this._model.redo());
                    break;
                case H.ExecuteCommand:
                    this._externalExecuteCommand(payload);
                    break;
                case H.ExecuteCommands:
                    this._externalExecuteCommands(payload);
                    break;
            }
        }
        catch (err) {
            onUnexpectedError(err);
        }
        this._isHandling = false;
        if (this._emitStateChangedIfNecessary(source, cursorChangeReason, oldState)) {
            this._revealRange(0 /* Primary */, 0 /* Simple */, true, 0 /* Smooth */);
        }
    };
    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
        }));
    };
    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, TrackedRangeStickiness.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] === SelectionDirection.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;
        });
        // 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++) {
            if (commands[i]) {
                var r = this._getEditOperationsFromCommand(ctx, i, commands[i]);
                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 = TrackedRangeStickiness.GrowsOnlyWhenTypingBefore;
                    }
                    else {
                        stickiness = TrackedRangeStickiness.GrowsOnlyWhenTypingAfter;
                    }
                }
                else {
                    // Try to lock it with surrounding text
                    var maxLineColumn = ctx.model.getLineMaxColumn(selection.startLineNumber);
                    if (selection.startColumn === maxLineColumn) {
                        stickiness = TrackedRangeStickiness.GrowsOnlyWhenTypingBefore;
                    }
                    else {
                        stickiness = TrackedRangeStickiness.GrowsOnlyWhenTypingAfter;
                    }
                }
            }
            else {
                stickiness = TrackedRangeStickiness.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;
}());
