123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import * as strings from '../../../base/common/strings.js';
- import { ShiftCommand } from '../../common/commands/shiftCommand.js';
- import { Range } from '../../common/core/range.js';
- import { Selection } from '../../common/core/selection.js';
- import { IndentAction } from '../../common/modes/languageConfiguration.js';
- import { LanguageConfigurationRegistry } from '../../common/modes/languageConfigurationRegistry.js';
- import * as indentUtils from '../indentation/indentUtils.js';
- export class MoveLinesCommand {
- constructor(selection, isMovingDown, autoIndent) {
- this._selection = selection;
- this._isMovingDown = isMovingDown;
- this._autoIndent = autoIndent;
- this._selectionId = null;
- this._moveEndLineSelectionShrink = false;
- }
- getEditOperations(model, builder) {
- let modelLineCount = model.getLineCount();
- if (this._isMovingDown && this._selection.endLineNumber === modelLineCount) {
- this._selectionId = builder.trackSelection(this._selection);
- return;
- }
- if (!this._isMovingDown && this._selection.startLineNumber === 1) {
- this._selectionId = builder.trackSelection(this._selection);
- return;
- }
- this._moveEndPositionDown = false;
- let s = this._selection;
- if (s.startLineNumber < s.endLineNumber && s.endColumn === 1) {
- this._moveEndPositionDown = true;
- s = s.setEndPosition(s.endLineNumber - 1, model.getLineMaxColumn(s.endLineNumber - 1));
- }
- const { tabSize, indentSize, insertSpaces } = model.getOptions();
- let indentConverter = this.buildIndentConverter(tabSize, indentSize, insertSpaces);
- let virtualModel = {
- getLineTokens: (lineNumber) => {
- return model.getLineTokens(lineNumber);
- },
- getLanguageId: () => {
- return model.getLanguageId();
- },
- getLanguageIdAtPosition: (lineNumber, column) => {
- return model.getLanguageIdAtPosition(lineNumber, column);
- },
- getLineContent: null,
- };
- if (s.startLineNumber === s.endLineNumber && model.getLineMaxColumn(s.startLineNumber) === 1) {
- // Current line is empty
- let lineNumber = s.startLineNumber;
- let otherLineNumber = (this._isMovingDown ? lineNumber + 1 : lineNumber - 1);
- if (model.getLineMaxColumn(otherLineNumber) === 1) {
- // Other line number is empty too, so no editing is needed
- // Add a no-op to force running by the model
- builder.addEditOperation(new Range(1, 1, 1, 1), null);
- }
- else {
- // Type content from other line number on line number
- builder.addEditOperation(new Range(lineNumber, 1, lineNumber, 1), model.getLineContent(otherLineNumber));
- // Remove content from other line number
- builder.addEditOperation(new Range(otherLineNumber, 1, otherLineNumber, model.getLineMaxColumn(otherLineNumber)), null);
- }
- // Track selection at the other line number
- s = new Selection(otherLineNumber, 1, otherLineNumber, 1);
- }
- else {
- let movingLineNumber;
- let movingLineText;
- if (this._isMovingDown) {
- movingLineNumber = s.endLineNumber + 1;
- movingLineText = model.getLineContent(movingLineNumber);
- // Delete line that needs to be moved
- builder.addEditOperation(new Range(movingLineNumber - 1, model.getLineMaxColumn(movingLineNumber - 1), movingLineNumber, model.getLineMaxColumn(movingLineNumber)), null);
- let insertingText = movingLineText;
- if (this.shouldAutoIndent(model, s)) {
- let movingLineMatchResult = this.matchEnterRule(model, indentConverter, tabSize, movingLineNumber, s.startLineNumber - 1);
- // if s.startLineNumber - 1 matches onEnter rule, we still honor that.
- if (movingLineMatchResult !== null) {
- let oldIndentation = strings.getLeadingWhitespace(model.getLineContent(movingLineNumber));
- let newSpaceCnt = movingLineMatchResult + indentUtils.getSpaceCnt(oldIndentation, tabSize);
- let newIndentation = indentUtils.generateIndent(newSpaceCnt, tabSize, insertSpaces);
- insertingText = newIndentation + this.trimLeft(movingLineText);
- }
- else {
- // no enter rule matches, let's check indentatin rules then.
- virtualModel.getLineContent = (lineNumber) => {
- if (lineNumber === s.startLineNumber) {
- return model.getLineContent(movingLineNumber);
- }
- else {
- return model.getLineContent(lineNumber);
- }
- };
- let indentOfMovingLine = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(movingLineNumber, 1), s.startLineNumber, indentConverter);
- if (indentOfMovingLine !== null) {
- let oldIndentation = strings.getLeadingWhitespace(model.getLineContent(movingLineNumber));
- let newSpaceCnt = indentUtils.getSpaceCnt(indentOfMovingLine, tabSize);
- let oldSpaceCnt = indentUtils.getSpaceCnt(oldIndentation, tabSize);
- if (newSpaceCnt !== oldSpaceCnt) {
- let newIndentation = indentUtils.generateIndent(newSpaceCnt, tabSize, insertSpaces);
- insertingText = newIndentation + this.trimLeft(movingLineText);
- }
- }
- }
- // add edit operations for moving line first to make sure it's executed after we make indentation change
- // to s.startLineNumber
- builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), insertingText + '\n');
- let ret = this.matchEnterRuleMovingDown(model, indentConverter, tabSize, s.startLineNumber, movingLineNumber, insertingText);
- // check if the line being moved before matches onEnter rules, if so let's adjust the indentation by onEnter rules.
- if (ret !== null) {
- if (ret !== 0) {
- this.getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, ret);
- }
- }
- else {
- // it doesn't match onEnter rules, let's check indentation rules then.
- virtualModel.getLineContent = (lineNumber) => {
- if (lineNumber === s.startLineNumber) {
- return insertingText;
- }
- else if (lineNumber >= s.startLineNumber + 1 && lineNumber <= s.endLineNumber + 1) {
- return model.getLineContent(lineNumber - 1);
- }
- else {
- return model.getLineContent(lineNumber);
- }
- };
- let newIndentatOfMovingBlock = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(movingLineNumber, 1), s.startLineNumber + 1, indentConverter);
- if (newIndentatOfMovingBlock !== null) {
- const oldIndentation = strings.getLeadingWhitespace(model.getLineContent(s.startLineNumber));
- const newSpaceCnt = indentUtils.getSpaceCnt(newIndentatOfMovingBlock, tabSize);
- const oldSpaceCnt = indentUtils.getSpaceCnt(oldIndentation, tabSize);
- if (newSpaceCnt !== oldSpaceCnt) {
- const spaceCntOffset = newSpaceCnt - oldSpaceCnt;
- this.getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, spaceCntOffset);
- }
- }
- }
- }
- else {
- // Insert line that needs to be moved before
- builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), insertingText + '\n');
- }
- }
- else {
- movingLineNumber = s.startLineNumber - 1;
- movingLineText = model.getLineContent(movingLineNumber);
- // Delete line that needs to be moved
- builder.addEditOperation(new Range(movingLineNumber, 1, movingLineNumber + 1, 1), null);
- // Insert line that needs to be moved after
- builder.addEditOperation(new Range(s.endLineNumber, model.getLineMaxColumn(s.endLineNumber), s.endLineNumber, model.getLineMaxColumn(s.endLineNumber)), '\n' + movingLineText);
- if (this.shouldAutoIndent(model, s)) {
- virtualModel.getLineContent = (lineNumber) => {
- if (lineNumber === movingLineNumber) {
- return model.getLineContent(s.startLineNumber);
- }
- else {
- return model.getLineContent(lineNumber);
- }
- };
- let ret = this.matchEnterRule(model, indentConverter, tabSize, s.startLineNumber, s.startLineNumber - 2);
- // check if s.startLineNumber - 2 matches onEnter rules, if so adjust the moving block by onEnter rules.
- if (ret !== null) {
- if (ret !== 0) {
- this.getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, ret);
- }
- }
- else {
- // it doesn't match any onEnter rule, let's check indentation rules then.
- let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(s.startLineNumber, 1), movingLineNumber, indentConverter);
- if (indentOfFirstLine !== null) {
- // adjust the indentation of the moving block
- let oldIndent = strings.getLeadingWhitespace(model.getLineContent(s.startLineNumber));
- let newSpaceCnt = indentUtils.getSpaceCnt(indentOfFirstLine, tabSize);
- let oldSpaceCnt = indentUtils.getSpaceCnt(oldIndent, tabSize);
- if (newSpaceCnt !== oldSpaceCnt) {
- let spaceCntOffset = newSpaceCnt - oldSpaceCnt;
- this.getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, spaceCntOffset);
- }
- }
- }
- }
- }
- }
- this._selectionId = builder.trackSelection(s);
- }
- buildIndentConverter(tabSize, indentSize, insertSpaces) {
- return {
- shiftIndent: (indentation) => {
- return ShiftCommand.shiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces);
- },
- unshiftIndent: (indentation) => {
- return ShiftCommand.unshiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces);
- }
- };
- }
- parseEnterResult(model, indentConverter, tabSize, line, enter) {
- if (enter) {
- let enterPrefix = enter.indentation;
- if (enter.indentAction === IndentAction.None) {
- enterPrefix = enter.indentation + enter.appendText;
- }
- else if (enter.indentAction === IndentAction.Indent) {
- enterPrefix = enter.indentation + enter.appendText;
- }
- else if (enter.indentAction === IndentAction.IndentOutdent) {
- enterPrefix = enter.indentation;
- }
- else if (enter.indentAction === IndentAction.Outdent) {
- enterPrefix = indentConverter.unshiftIndent(enter.indentation) + enter.appendText;
- }
- let movingLineText = model.getLineContent(line);
- if (this.trimLeft(movingLineText).indexOf(this.trimLeft(enterPrefix)) >= 0) {
- let oldIndentation = strings.getLeadingWhitespace(model.getLineContent(line));
- let newIndentation = strings.getLeadingWhitespace(enterPrefix);
- let indentMetadataOfMovelingLine = LanguageConfigurationRegistry.getIndentMetadata(model, line);
- if (indentMetadataOfMovelingLine !== null && indentMetadataOfMovelingLine & 2 /* DECREASE_MASK */) {
- newIndentation = indentConverter.unshiftIndent(newIndentation);
- }
- let newSpaceCnt = indentUtils.getSpaceCnt(newIndentation, tabSize);
- let oldSpaceCnt = indentUtils.getSpaceCnt(oldIndentation, tabSize);
- return newSpaceCnt - oldSpaceCnt;
- }
- }
- return null;
- }
- /**
- *
- * @param model
- * @param indentConverter
- * @param tabSize
- * @param line the line moving down
- * @param futureAboveLineNumber the line which will be at the `line` position
- * @param futureAboveLineText
- */
- matchEnterRuleMovingDown(model, indentConverter, tabSize, line, futureAboveLineNumber, futureAboveLineText) {
- if (strings.lastNonWhitespaceIndex(futureAboveLineText) >= 0) {
- // break
- let maxColumn = model.getLineMaxColumn(futureAboveLineNumber);
- let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(futureAboveLineNumber, maxColumn, futureAboveLineNumber, maxColumn));
- return this.parseEnterResult(model, indentConverter, tabSize, line, enter);
- }
- else {
- // go upwards, starting from `line - 1`
- let validPrecedingLine = line - 1;
- while (validPrecedingLine >= 1) {
- let lineContent = model.getLineContent(validPrecedingLine);
- let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineContent);
- if (nonWhitespaceIdx >= 0) {
- break;
- }
- validPrecedingLine--;
- }
- if (validPrecedingLine < 1 || line > model.getLineCount()) {
- return null;
- }
- let maxColumn = model.getLineMaxColumn(validPrecedingLine);
- let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn));
- return this.parseEnterResult(model, indentConverter, tabSize, line, enter);
- }
- }
- matchEnterRule(model, indentConverter, tabSize, line, oneLineAbove, previousLineText) {
- let validPrecedingLine = oneLineAbove;
- while (validPrecedingLine >= 1) {
- // ship empty lines as empty lines just inherit indentation
- let lineContent;
- if (validPrecedingLine === oneLineAbove && previousLineText !== undefined) {
- lineContent = previousLineText;
- }
- else {
- lineContent = model.getLineContent(validPrecedingLine);
- }
- let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineContent);
- if (nonWhitespaceIdx >= 0) {
- break;
- }
- validPrecedingLine--;
- }
- if (validPrecedingLine < 1 || line > model.getLineCount()) {
- return null;
- }
- let maxColumn = model.getLineMaxColumn(validPrecedingLine);
- let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn));
- return this.parseEnterResult(model, indentConverter, tabSize, line, enter);
- }
- trimLeft(str) {
- return str.replace(/^\s+/, '');
- }
- shouldAutoIndent(model, selection) {
- if (this._autoIndent < 4 /* Full */) {
- return false;
- }
- // if it's not easy to tokenize, we stop auto indent.
- if (!model.isCheapToTokenize(selection.startLineNumber)) {
- return false;
- }
- let languageAtSelectionStart = model.getLanguageIdAtPosition(selection.startLineNumber, 1);
- let languageAtSelectionEnd = model.getLanguageIdAtPosition(selection.endLineNumber, 1);
- if (languageAtSelectionStart !== languageAtSelectionEnd) {
- return false;
- }
- if (LanguageConfigurationRegistry.getIndentRulesSupport(languageAtSelectionStart) === null) {
- return false;
- }
- return true;
- }
- getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, offset) {
- for (let i = s.startLineNumber; i <= s.endLineNumber; i++) {
- let lineContent = model.getLineContent(i);
- let originalIndent = strings.getLeadingWhitespace(lineContent);
- let originalSpacesCnt = indentUtils.getSpaceCnt(originalIndent, tabSize);
- let newSpacesCnt = originalSpacesCnt + offset;
- let newIndent = indentUtils.generateIndent(newSpacesCnt, tabSize, insertSpaces);
- if (newIndent !== originalIndent) {
- builder.addEditOperation(new Range(i, 1, i, originalIndent.length + 1), newIndent);
- if (i === s.endLineNumber && s.endColumn <= originalIndent.length + 1 && newIndent === '') {
- // as users select part of the original indent white spaces
- // when we adjust the indentation of endLine, we should adjust the cursor position as well.
- this._moveEndLineSelectionShrink = true;
- }
- }
- }
- }
- computeCursorState(model, helper) {
- let result = helper.getTrackedSelection(this._selectionId);
- if (this._moveEndPositionDown) {
- result = result.setEndPosition(result.endLineNumber + 1, 1);
- }
- if (this._moveEndLineSelectionShrink && result.startLineNumber < result.endLineNumber) {
- result = result.setEndPosition(result.endLineNumber, 2);
- }
- return result;
- }
- }
|