UNPKG

monaco-editor

Version:
216 lines (215 loc) • 10.9 kB
/*--------------------------------------------------------------------------------------------- * 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 { ReplaceCommand } from '../commands/replaceCommand.js'; import { EditOperationResult, isQuote } from '../cursorCommon.js'; import { CursorColumns } from '../core/cursorColumns.js'; import { MoveOperations } from './cursorMoveOperations.js'; import { Range } from '../core/range.js'; import { Position } from '../core/position.js'; export class DeleteOperations { static deleteRight(prevEditOperationType, config, model, selections) { const commands = []; let shouldPushStackElementBefore = (prevEditOperationType !== 3 /* EditOperationType.DeletingRight */); for (let i = 0, len = selections.length; i < len; i++) { const selection = selections[i]; let deleteSelection = selection; if (deleteSelection.isEmpty()) { const position = selection.getPosition(); const rightOfPosition = MoveOperations.right(config, model, position); deleteSelection = new Range(rightOfPosition.lineNumber, rightOfPosition.column, position.lineNumber, position.column); } if (deleteSelection.isEmpty()) { // Probably at end of file => ignore commands[i] = null; continue; } if (deleteSelection.startLineNumber !== deleteSelection.endLineNumber) { shouldPushStackElementBefore = true; } commands[i] = new ReplaceCommand(deleteSelection, ''); } return [shouldPushStackElementBefore, commands]; } static isAutoClosingPairDelete(autoClosingDelete, autoClosingBrackets, autoClosingQuotes, autoClosingPairsOpen, model, selections, autoClosedCharacters) { if (autoClosingBrackets === 'never' && autoClosingQuotes === 'never') { return false; } if (autoClosingDelete === 'never') { return false; } for (let i = 0, len = selections.length; i < len; i++) { const selection = selections[i]; const position = selection.getPosition(); if (!selection.isEmpty()) { return false; } const lineText = model.getLineContent(position.lineNumber); if (position.column < 2 || position.column >= lineText.length + 1) { return false; } const character = lineText.charAt(position.column - 2); const autoClosingPairCandidates = autoClosingPairsOpen.get(character); if (!autoClosingPairCandidates) { return false; } if (isQuote(character)) { if (autoClosingQuotes === 'never') { return false; } } else { if (autoClosingBrackets === 'never') { return false; } } const afterCharacter = lineText.charAt(position.column - 1); let foundAutoClosingPair = false; for (const autoClosingPairCandidate of autoClosingPairCandidates) { if (autoClosingPairCandidate.open === character && autoClosingPairCandidate.close === afterCharacter) { foundAutoClosingPair = true; } } if (!foundAutoClosingPair) { return false; } // Must delete the pair only if it was automatically inserted by the editor if (autoClosingDelete === 'auto') { let found = false; for (let j = 0, lenJ = autoClosedCharacters.length; j < lenJ; j++) { const autoClosedCharacter = autoClosedCharacters[j]; if (position.lineNumber === autoClosedCharacter.startLineNumber && position.column === autoClosedCharacter.startColumn) { found = true; break; } } if (!found) { return false; } } } return true; } static _runAutoClosingPairDelete(config, model, selections) { const commands = []; for (let i = 0, len = selections.length; i < len; i++) { const position = selections[i].getPosition(); const deleteSelection = new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column + 1); commands[i] = new ReplaceCommand(deleteSelection, ''); } return [true, commands]; } static deleteLeft(prevEditOperationType, config, model, selections, autoClosedCharacters) { if (this.isAutoClosingPairDelete(config.autoClosingDelete, config.autoClosingBrackets, config.autoClosingQuotes, config.autoClosingPairs.autoClosingPairsOpenByEnd, model, selections, autoClosedCharacters)) { return this._runAutoClosingPairDelete(config, model, selections); } const commands = []; let shouldPushStackElementBefore = (prevEditOperationType !== 2 /* EditOperationType.DeletingLeft */); for (let i = 0, len = selections.length; i < len; i++) { const deleteRange = DeleteOperations.getDeleteRange(selections[i], model, config); // Ignore empty delete ranges, as they have no effect // They happen if the cursor is at the beginning of the file. if (deleteRange.isEmpty()) { commands[i] = null; continue; } if (deleteRange.startLineNumber !== deleteRange.endLineNumber) { shouldPushStackElementBefore = true; } commands[i] = new ReplaceCommand(deleteRange, ''); } return [shouldPushStackElementBefore, commands]; } static getDeleteRange(selection, model, config) { if (!selection.isEmpty()) { return selection; } const position = selection.getPosition(); // Unintend when using tab stops and cursor is within indentation if (config.useTabStops && position.column > 1) { const lineContent = model.getLineContent(position.lineNumber); const firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent); const lastIndentationColumn = (firstNonWhitespaceIndex === -1 ? /* entire string is whitespace */ lineContent.length + 1 : firstNonWhitespaceIndex + 1); if (position.column <= lastIndentationColumn) { const fromVisibleColumn = config.visibleColumnFromColumn(model, position); const toVisibleColumn = CursorColumns.prevIndentTabStop(fromVisibleColumn, config.indentSize); const toColumn = config.columnFromVisibleColumn(model, position.lineNumber, toVisibleColumn); return new Range(position.lineNumber, toColumn, position.lineNumber, position.column); } } return Range.fromPositions(DeleteOperations.getPositionAfterDeleteLeft(position, model), position); } static getPositionAfterDeleteLeft(position, model) { if (position.column > 1) { // Convert 1-based columns to 0-based offsets and back. const idx = strings.getLeftDeleteOffset(position.column - 1, model.getLineContent(position.lineNumber)); return position.with(undefined, idx + 1); } else if (position.lineNumber > 1) { const newLine = position.lineNumber - 1; return new Position(newLine, model.getLineMaxColumn(newLine)); } else { return position; } } static cut(config, model, selections) { const commands = []; let lastCutRange = null; selections.sort((a, b) => Position.compare(a.getStartPosition(), b.getEndPosition())); for (let i = 0, len = selections.length; i < len; i++) { const selection = selections[i]; if (selection.isEmpty()) { if (config.emptySelectionClipboard) { // This is a full line cut const position = selection.getPosition(); let startLineNumber, startColumn, endLineNumber, endColumn; if (position.lineNumber < model.getLineCount()) { // Cutting a line in the middle of the model startLineNumber = position.lineNumber; startColumn = 1; endLineNumber = position.lineNumber + 1; endColumn = 1; } else if (position.lineNumber > 1 && (lastCutRange === null || lastCutRange === void 0 ? void 0 : lastCutRange.endLineNumber) !== position.lineNumber) { // Cutting the last line & there are more than 1 lines in the model & a previous cut operation does not touch the current cut operation startLineNumber = position.lineNumber - 1; startColumn = model.getLineMaxColumn(position.lineNumber - 1); endLineNumber = position.lineNumber; endColumn = model.getLineMaxColumn(position.lineNumber); } else { // Cutting the single line that the model contains startLineNumber = position.lineNumber; startColumn = 1; endLineNumber = position.lineNumber; endColumn = model.getLineMaxColumn(position.lineNumber); } const deleteSelection = new Range(startLineNumber, startColumn, endLineNumber, endColumn); lastCutRange = deleteSelection; if (!deleteSelection.isEmpty()) { commands[i] = new ReplaceCommand(deleteSelection, ''); } else { commands[i] = null; } } else { // Cannot cut empty selection commands[i] = null; } } else { commands[i] = new ReplaceCommand(selection, ''); } } return new EditOperationResult(0 /* EditOperationType.Other */, commands, { shouldPushStackElementBefore: true, shouldPushStackElementAfter: true }); } }