monaco-editor-core
Version:
A browser based code editor
1,106 lines (1,105 loc) • 47.4 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 { KeyChord } from '../../../../base/common/keyCodes.js';
import { CoreEditingCommands } from '../../../browser/coreCommands.js';
import { EditorAction, registerEditorAction } from '../../../browser/editorExtensions.js';
import { ReplaceCommand, ReplaceCommandThatPreservesSelection, ReplaceCommandThatSelectsText } from '../../../common/commands/replaceCommand.js';
import { TrimTrailingWhitespaceCommand } from '../../../common/commands/trimTrailingWhitespaceCommand.js';
import { TypeOperations } from '../../../common/cursor/cursorTypeOperations.js';
import { EnterOperation } from '../../../common/cursor/cursorTypeEditOperations.js';
import { EditOperation } from '../../../common/core/editOperation.js';
import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
import { Selection } from '../../../common/core/selection.js';
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
import { CopyLinesCommand } from './copyLinesCommand.js';
import { MoveLinesCommand } from './moveLinesCommand.js';
import { SortLinesCommand } from './sortLinesCommand.js';
import * as nls from '../../../../nls.js';
import { MenuId } from '../../../../platform/actions/common/actions.js';
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
// copy lines
class AbstractCopyLinesAction extends EditorAction {
constructor(down, opts) {
super(opts);
this.down = down;
}
run(_accessor, editor) {
if (!editor.hasModel()) {
return;
}
const selections = editor.getSelections().map((selection, index) => ({ selection, index, ignore: false }));
selections.sort((a, b) => Range.compareRangesUsingStarts(a.selection, b.selection));
// Remove selections that would result in copying the same line
let prev = selections[0];
for (let i = 1; i < selections.length; i++) {
const curr = selections[i];
if (prev.selection.endLineNumber === curr.selection.startLineNumber) {
// these two selections would copy the same line
if (prev.index < curr.index) {
// prev wins
curr.ignore = true;
}
else {
// curr wins
prev.ignore = true;
prev = curr;
}
}
}
const commands = [];
for (const selection of selections) {
commands.push(new CopyLinesCommand(selection.selection, this.down, selection.ignore));
}
editor.pushUndoStop();
editor.executeCommands(this.id, commands);
editor.pushUndoStop();
}
}
class CopyLinesUpAction extends AbstractCopyLinesAction {
constructor() {
super(false, {
id: 'editor.action.copyLinesUpAction',
label: nls.localize('lines.copyUp', "Copy Line Up"),
alias: 'Copy Line Up',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: 512 /* KeyMod.Alt */ | 1024 /* KeyMod.Shift */ | 16 /* KeyCode.UpArrow */,
linux: { primary: 2048 /* KeyMod.CtrlCmd */ | 512 /* KeyMod.Alt */ | 1024 /* KeyMod.Shift */ | 16 /* KeyCode.UpArrow */ },
weight: 100 /* KeybindingWeight.EditorContrib */
},
menuOpts: {
menuId: MenuId.MenubarSelectionMenu,
group: '2_line',
title: nls.localize({ key: 'miCopyLinesUp', comment: ['&& denotes a mnemonic'] }, "&&Copy Line Up"),
order: 1
}
});
}
}
class CopyLinesDownAction extends AbstractCopyLinesAction {
constructor() {
super(true, {
id: 'editor.action.copyLinesDownAction',
label: nls.localize('lines.copyDown', "Copy Line Down"),
alias: 'Copy Line Down',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: 512 /* KeyMod.Alt */ | 1024 /* KeyMod.Shift */ | 18 /* KeyCode.DownArrow */,
linux: { primary: 2048 /* KeyMod.CtrlCmd */ | 512 /* KeyMod.Alt */ | 1024 /* KeyMod.Shift */ | 18 /* KeyCode.DownArrow */ },
weight: 100 /* KeybindingWeight.EditorContrib */
},
menuOpts: {
menuId: MenuId.MenubarSelectionMenu,
group: '2_line',
title: nls.localize({ key: 'miCopyLinesDown', comment: ['&& denotes a mnemonic'] }, "Co&&py Line Down"),
order: 2
}
});
}
}
export class DuplicateSelectionAction extends EditorAction {
constructor() {
super({
id: 'editor.action.duplicateSelection',
label: nls.localize('duplicateSelection', "Duplicate Selection"),
alias: 'Duplicate Selection',
precondition: EditorContextKeys.writable,
menuOpts: {
menuId: MenuId.MenubarSelectionMenu,
group: '2_line',
title: nls.localize({ key: 'miDuplicateSelection', comment: ['&& denotes a mnemonic'] }, "&&Duplicate Selection"),
order: 5
}
});
}
run(accessor, editor, args) {
if (!editor.hasModel()) {
return;
}
const commands = [];
const selections = editor.getSelections();
const model = editor.getModel();
for (const selection of selections) {
if (selection.isEmpty()) {
commands.push(new CopyLinesCommand(selection, true));
}
else {
const insertSelection = new Selection(selection.endLineNumber, selection.endColumn, selection.endLineNumber, selection.endColumn);
commands.push(new ReplaceCommandThatSelectsText(insertSelection, model.getValueInRange(selection)));
}
}
editor.pushUndoStop();
editor.executeCommands(this.id, commands);
editor.pushUndoStop();
}
}
// move lines
class AbstractMoveLinesAction extends EditorAction {
constructor(down, opts) {
super(opts);
this.down = down;
}
run(accessor, editor) {
const languageConfigurationService = accessor.get(ILanguageConfigurationService);
const commands = [];
const selections = editor.getSelections() || [];
const autoIndent = editor.getOption(12 /* EditorOption.autoIndent */);
for (const selection of selections) {
commands.push(new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService));
}
editor.pushUndoStop();
editor.executeCommands(this.id, commands);
editor.pushUndoStop();
}
}
class MoveLinesUpAction extends AbstractMoveLinesAction {
constructor() {
super(false, {
id: 'editor.action.moveLinesUpAction',
label: nls.localize('lines.moveUp', "Move Line Up"),
alias: 'Move Line Up',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: 512 /* KeyMod.Alt */ | 16 /* KeyCode.UpArrow */,
linux: { primary: 512 /* KeyMod.Alt */ | 16 /* KeyCode.UpArrow */ },
weight: 100 /* KeybindingWeight.EditorContrib */
},
menuOpts: {
menuId: MenuId.MenubarSelectionMenu,
group: '2_line',
title: nls.localize({ key: 'miMoveLinesUp', comment: ['&& denotes a mnemonic'] }, "Mo&&ve Line Up"),
order: 3
}
});
}
}
class MoveLinesDownAction extends AbstractMoveLinesAction {
constructor() {
super(true, {
id: 'editor.action.moveLinesDownAction',
label: nls.localize('lines.moveDown', "Move Line Down"),
alias: 'Move Line Down',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: 512 /* KeyMod.Alt */ | 18 /* KeyCode.DownArrow */,
linux: { primary: 512 /* KeyMod.Alt */ | 18 /* KeyCode.DownArrow */ },
weight: 100 /* KeybindingWeight.EditorContrib */
},
menuOpts: {
menuId: MenuId.MenubarSelectionMenu,
group: '2_line',
title: nls.localize({ key: 'miMoveLinesDown', comment: ['&& denotes a mnemonic'] }, "Move &&Line Down"),
order: 4
}
});
}
}
export class AbstractSortLinesAction extends EditorAction {
constructor(descending, opts) {
super(opts);
this.descending = descending;
}
run(_accessor, editor) {
if (!editor.hasModel()) {
return;
}
const model = editor.getModel();
let selections = editor.getSelections();
if (selections.length === 1 && selections[0].isEmpty()) {
// Apply to whole document.
selections = [new Selection(1, 1, model.getLineCount(), model.getLineMaxColumn(model.getLineCount()))];
}
for (const selection of selections) {
if (!SortLinesCommand.canRun(editor.getModel(), selection, this.descending)) {
return;
}
}
const commands = [];
for (let i = 0, len = selections.length; i < len; i++) {
commands[i] = new SortLinesCommand(selections[i], this.descending);
}
editor.pushUndoStop();
editor.executeCommands(this.id, commands);
editor.pushUndoStop();
}
}
export class SortLinesAscendingAction extends AbstractSortLinesAction {
constructor() {
super(false, {
id: 'editor.action.sortLinesAscending',
label: nls.localize('lines.sortAscending', "Sort Lines Ascending"),
alias: 'Sort Lines Ascending',
precondition: EditorContextKeys.writable
});
}
}
export class SortLinesDescendingAction extends AbstractSortLinesAction {
constructor() {
super(true, {
id: 'editor.action.sortLinesDescending',
label: nls.localize('lines.sortDescending', "Sort Lines Descending"),
alias: 'Sort Lines Descending',
precondition: EditorContextKeys.writable
});
}
}
export class DeleteDuplicateLinesAction extends EditorAction {
constructor() {
super({
id: 'editor.action.removeDuplicateLines',
label: nls.localize('lines.deleteDuplicates', "Delete Duplicate Lines"),
alias: 'Delete Duplicate Lines',
precondition: EditorContextKeys.writable
});
}
run(_accessor, editor) {
if (!editor.hasModel()) {
return;
}
const model = editor.getModel();
if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
return;
}
const edits = [];
const endCursorState = [];
let linesDeleted = 0;
let updateSelection = true;
let selections = editor.getSelections();
if (selections.length === 1 && selections[0].isEmpty()) {
// Apply to whole document.
selections = [new Selection(1, 1, model.getLineCount(), model.getLineMaxColumn(model.getLineCount()))];
updateSelection = false;
}
for (const selection of selections) {
const uniqueLines = new Set();
const lines = [];
for (let i = selection.startLineNumber; i <= selection.endLineNumber; i++) {
const line = model.getLineContent(i);
if (uniqueLines.has(line)) {
continue;
}
lines.push(line);
uniqueLines.add(line);
}
const selectionToReplace = new Selection(selection.startLineNumber, 1, selection.endLineNumber, model.getLineMaxColumn(selection.endLineNumber));
const adjustedSelectionStart = selection.startLineNumber - linesDeleted;
const finalSelection = new Selection(adjustedSelectionStart, 1, adjustedSelectionStart + lines.length - 1, lines[lines.length - 1].length);
edits.push(EditOperation.replace(selectionToReplace, lines.join('\n')));
endCursorState.push(finalSelection);
linesDeleted += (selection.endLineNumber - selection.startLineNumber + 1) - lines.length;
}
editor.pushUndoStop();
editor.executeEdits(this.id, edits, updateSelection ? endCursorState : undefined);
editor.pushUndoStop();
}
}
export class TrimTrailingWhitespaceAction extends EditorAction {
static { this.ID = 'editor.action.trimTrailingWhitespace'; }
constructor() {
super({
id: TrimTrailingWhitespaceAction.ID,
label: nls.localize('lines.trimTrailingWhitespace', "Trim Trailing Whitespace"),
alias: 'Trim Trailing Whitespace',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyChord(2048 /* KeyMod.CtrlCmd */ | 41 /* KeyCode.KeyK */, 2048 /* KeyMod.CtrlCmd */ | 54 /* KeyCode.KeyX */),
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
run(_accessor, editor, args) {
let cursors = [];
if (args.reason === 'auto-save') {
// See https://github.com/editorconfig/editorconfig-vscode/issues/47
// It is very convenient for the editor config extension to invoke this action.
// So, if we get a reason:'auto-save' passed in, let's preserve cursor positions.
cursors = (editor.getSelections() || []).map(s => new Position(s.positionLineNumber, s.positionColumn));
}
const selection = editor.getSelection();
if (selection === null) {
return;
}
const config = _accessor.get(IConfigurationService);
const model = editor.getModel();
const trimInRegexAndStrings = config.getValue('files.trimTrailingWhitespaceInRegexAndStrings', { overrideIdentifier: model?.getLanguageId(), resource: model?.uri });
const command = new TrimTrailingWhitespaceCommand(selection, cursors, trimInRegexAndStrings);
editor.pushUndoStop();
editor.executeCommands(this.id, [command]);
editor.pushUndoStop();
}
}
export class DeleteLinesAction extends EditorAction {
constructor() {
super({
id: 'editor.action.deleteLines',
label: nls.localize('lines.delete', "Delete Line"),
alias: 'Delete Line',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.textInputFocus,
primary: 2048 /* KeyMod.CtrlCmd */ | 1024 /* KeyMod.Shift */ | 41 /* KeyCode.KeyK */,
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
run(_accessor, editor) {
if (!editor.hasModel()) {
return;
}
const ops = this._getLinesToRemove(editor);
const model = editor.getModel();
if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
// Model is empty
return;
}
let linesDeleted = 0;
const edits = [];
const cursorState = [];
for (let i = 0, len = ops.length; i < len; i++) {
const op = ops[i];
let startLineNumber = op.startLineNumber;
let endLineNumber = op.endLineNumber;
let startColumn = 1;
let endColumn = model.getLineMaxColumn(endLineNumber);
if (endLineNumber < model.getLineCount()) {
endLineNumber += 1;
endColumn = 1;
}
else if (startLineNumber > 1) {
startLineNumber -= 1;
startColumn = model.getLineMaxColumn(startLineNumber);
}
edits.push(EditOperation.replace(new Selection(startLineNumber, startColumn, endLineNumber, endColumn), ''));
cursorState.push(new Selection(startLineNumber - linesDeleted, op.positionColumn, startLineNumber - linesDeleted, op.positionColumn));
linesDeleted += (op.endLineNumber - op.startLineNumber + 1);
}
editor.pushUndoStop();
editor.executeEdits(this.id, edits, cursorState);
editor.pushUndoStop();
}
_getLinesToRemove(editor) {
// Construct delete operations
const operations = editor.getSelections().map((s) => {
let endLineNumber = s.endLineNumber;
if (s.startLineNumber < s.endLineNumber && s.endColumn === 1) {
endLineNumber -= 1;
}
return {
startLineNumber: s.startLineNumber,
selectionStartColumn: s.selectionStartColumn,
endLineNumber: endLineNumber,
positionColumn: s.positionColumn
};
});
// Sort delete operations
operations.sort((a, b) => {
if (a.startLineNumber === b.startLineNumber) {
return a.endLineNumber - b.endLineNumber;
}
return a.startLineNumber - b.startLineNumber;
});
// Merge delete operations which are adjacent or overlapping
const mergedOperations = [];
let previousOperation = operations[0];
for (let i = 1; i < operations.length; i++) {
if (previousOperation.endLineNumber + 1 >= operations[i].startLineNumber) {
// Merge current operations into the previous one
previousOperation.endLineNumber = operations[i].endLineNumber;
}
else {
// Push previous operation
mergedOperations.push(previousOperation);
previousOperation = operations[i];
}
}
// Push the last operation
mergedOperations.push(previousOperation);
return mergedOperations;
}
}
export class IndentLinesAction extends EditorAction {
constructor() {
super({
id: 'editor.action.indentLines',
label: nls.localize('lines.indent', "Indent Line"),
alias: 'Indent Line',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: 2048 /* KeyMod.CtrlCmd */ | 94 /* KeyCode.BracketRight */,
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
run(_accessor, editor) {
const viewModel = editor._getViewModel();
if (!viewModel) {
return;
}
editor.pushUndoStop();
editor.executeCommands(this.id, TypeOperations.indent(viewModel.cursorConfig, editor.getModel(), editor.getSelections()));
editor.pushUndoStop();
}
}
class OutdentLinesAction extends EditorAction {
constructor() {
super({
id: 'editor.action.outdentLines',
label: nls.localize('lines.outdent', "Outdent Line"),
alias: 'Outdent Line',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: 2048 /* KeyMod.CtrlCmd */ | 92 /* KeyCode.BracketLeft */,
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
run(_accessor, editor) {
CoreEditingCommands.Outdent.runEditorCommand(_accessor, editor, null);
}
}
export class InsertLineBeforeAction extends EditorAction {
constructor() {
super({
id: 'editor.action.insertLineBefore',
label: nls.localize('lines.insertBefore', "Insert Line Above"),
alias: 'Insert Line Above',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: 2048 /* KeyMod.CtrlCmd */ | 1024 /* KeyMod.Shift */ | 3 /* KeyCode.Enter */,
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
run(_accessor, editor) {
const viewModel = editor._getViewModel();
if (!viewModel) {
return;
}
editor.pushUndoStop();
editor.executeCommands(this.id, EnterOperation.lineInsertBefore(viewModel.cursorConfig, editor.getModel(), editor.getSelections()));
}
}
export class InsertLineAfterAction extends EditorAction {
constructor() {
super({
id: 'editor.action.insertLineAfter',
label: nls.localize('lines.insertAfter', "Insert Line Below"),
alias: 'Insert Line Below',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: 2048 /* KeyMod.CtrlCmd */ | 3 /* KeyCode.Enter */,
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
run(_accessor, editor) {
const viewModel = editor._getViewModel();
if (!viewModel) {
return;
}
editor.pushUndoStop();
editor.executeCommands(this.id, EnterOperation.lineInsertAfter(viewModel.cursorConfig, editor.getModel(), editor.getSelections()));
}
}
export class AbstractDeleteAllToBoundaryAction extends EditorAction {
run(_accessor, editor) {
if (!editor.hasModel()) {
return;
}
const primaryCursor = editor.getSelection();
const rangesToDelete = this._getRangesToDelete(editor);
// merge overlapping selections
const effectiveRanges = [];
for (let i = 0, count = rangesToDelete.length - 1; i < count; i++) {
const range = rangesToDelete[i];
const nextRange = rangesToDelete[i + 1];
if (Range.intersectRanges(range, nextRange) === null) {
effectiveRanges.push(range);
}
else {
rangesToDelete[i + 1] = Range.plusRange(range, nextRange);
}
}
effectiveRanges.push(rangesToDelete[rangesToDelete.length - 1]);
const endCursorState = this._getEndCursorState(primaryCursor, effectiveRanges);
const edits = effectiveRanges.map(range => {
return EditOperation.replace(range, '');
});
editor.pushUndoStop();
editor.executeEdits(this.id, edits, endCursorState);
editor.pushUndoStop();
}
}
export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction {
constructor() {
super({
id: 'deleteAllLeft',
label: nls.localize('lines.deleteAllLeft', "Delete All Left"),
alias: 'Delete All Left',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: { primary: 2048 /* KeyMod.CtrlCmd */ | 1 /* KeyCode.Backspace */ },
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
_getEndCursorState(primaryCursor, rangesToDelete) {
let endPrimaryCursor = null;
const endCursorState = [];
let deletedLines = 0;
rangesToDelete.forEach(range => {
let endCursor;
if (range.endColumn === 1 && deletedLines > 0) {
const newStartLine = range.startLineNumber - deletedLines;
endCursor = new Selection(newStartLine, range.startColumn, newStartLine, range.startColumn);
}
else {
endCursor = new Selection(range.startLineNumber, range.startColumn, range.startLineNumber, range.startColumn);
}
deletedLines += range.endLineNumber - range.startLineNumber;
if (range.intersectRanges(primaryCursor)) {
endPrimaryCursor = endCursor;
}
else {
endCursorState.push(endCursor);
}
});
if (endPrimaryCursor) {
endCursorState.unshift(endPrimaryCursor);
}
return endCursorState;
}
_getRangesToDelete(editor) {
const selections = editor.getSelections();
if (selections === null) {
return [];
}
let rangesToDelete = selections;
const model = editor.getModel();
if (model === null) {
return [];
}
rangesToDelete.sort(Range.compareRangesUsingStarts);
rangesToDelete = rangesToDelete.map(selection => {
if (selection.isEmpty()) {
if (selection.startColumn === 1) {
const deleteFromLine = Math.max(1, selection.startLineNumber - 1);
const deleteFromColumn = selection.startLineNumber === 1 ? 1 : model.getLineLength(deleteFromLine) + 1;
return new Range(deleteFromLine, deleteFromColumn, selection.startLineNumber, 1);
}
else {
return new Range(selection.startLineNumber, 1, selection.startLineNumber, selection.startColumn);
}
}
else {
return new Range(selection.startLineNumber, 1, selection.endLineNumber, selection.endColumn);
}
});
return rangesToDelete;
}
}
export class DeleteAllRightAction extends AbstractDeleteAllToBoundaryAction {
constructor() {
super({
id: 'deleteAllRight',
label: nls.localize('lines.deleteAllRight', "Delete All Right"),
alias: 'Delete All Right',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: { primary: 256 /* KeyMod.WinCtrl */ | 41 /* KeyCode.KeyK */, secondary: [2048 /* KeyMod.CtrlCmd */ | 20 /* KeyCode.Delete */] },
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
_getEndCursorState(primaryCursor, rangesToDelete) {
let endPrimaryCursor = null;
const endCursorState = [];
for (let i = 0, len = rangesToDelete.length, offset = 0; i < len; i++) {
const range = rangesToDelete[i];
const endCursor = new Selection(range.startLineNumber - offset, range.startColumn, range.startLineNumber - offset, range.startColumn);
if (range.intersectRanges(primaryCursor)) {
endPrimaryCursor = endCursor;
}
else {
endCursorState.push(endCursor);
}
}
if (endPrimaryCursor) {
endCursorState.unshift(endPrimaryCursor);
}
return endCursorState;
}
_getRangesToDelete(editor) {
const model = editor.getModel();
if (model === null) {
return [];
}
const selections = editor.getSelections();
if (selections === null) {
return [];
}
const rangesToDelete = selections.map((sel) => {
if (sel.isEmpty()) {
const maxColumn = model.getLineMaxColumn(sel.startLineNumber);
if (sel.startColumn === maxColumn) {
return new Range(sel.startLineNumber, sel.startColumn, sel.startLineNumber + 1, 1);
}
else {
return new Range(sel.startLineNumber, sel.startColumn, sel.startLineNumber, maxColumn);
}
}
return sel;
});
rangesToDelete.sort(Range.compareRangesUsingStarts);
return rangesToDelete;
}
}
export class JoinLinesAction extends EditorAction {
constructor() {
super({
id: 'editor.action.joinLines',
label: nls.localize('lines.joinLines', "Join Lines"),
alias: 'Join Lines',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: 0,
mac: { primary: 256 /* KeyMod.WinCtrl */ | 40 /* KeyCode.KeyJ */ },
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
run(_accessor, editor) {
const selections = editor.getSelections();
if (selections === null) {
return;
}
let primaryCursor = editor.getSelection();
if (primaryCursor === null) {
return;
}
selections.sort(Range.compareRangesUsingStarts);
const reducedSelections = [];
const lastSelection = selections.reduce((previousValue, currentValue) => {
if (previousValue.isEmpty()) {
if (previousValue.endLineNumber === currentValue.startLineNumber) {
if (primaryCursor.equalsSelection(previousValue)) {
primaryCursor = currentValue;
}
return currentValue;
}
if (currentValue.startLineNumber > previousValue.endLineNumber + 1) {
reducedSelections.push(previousValue);
return currentValue;
}
else {
return new Selection(previousValue.startLineNumber, previousValue.startColumn, currentValue.endLineNumber, currentValue.endColumn);
}
}
else {
if (currentValue.startLineNumber > previousValue.endLineNumber) {
reducedSelections.push(previousValue);
return currentValue;
}
else {
return new Selection(previousValue.startLineNumber, previousValue.startColumn, currentValue.endLineNumber, currentValue.endColumn);
}
}
});
reducedSelections.push(lastSelection);
const model = editor.getModel();
if (model === null) {
return;
}
const edits = [];
const endCursorState = [];
let endPrimaryCursor = primaryCursor;
let lineOffset = 0;
for (let i = 0, len = reducedSelections.length; i < len; i++) {
const selection = reducedSelections[i];
const startLineNumber = selection.startLineNumber;
const startColumn = 1;
let columnDeltaOffset = 0;
let endLineNumber, endColumn;
const selectionEndPositionOffset = model.getLineLength(selection.endLineNumber) - selection.endColumn;
if (selection.isEmpty() || selection.startLineNumber === selection.endLineNumber) {
const position = selection.getStartPosition();
if (position.lineNumber < model.getLineCount()) {
endLineNumber = startLineNumber + 1;
endColumn = model.getLineMaxColumn(endLineNumber);
}
else {
endLineNumber = position.lineNumber;
endColumn = model.getLineMaxColumn(position.lineNumber);
}
}
else {
endLineNumber = selection.endLineNumber;
endColumn = model.getLineMaxColumn(endLineNumber);
}
let trimmedLinesContent = model.getLineContent(startLineNumber);
for (let i = startLineNumber + 1; i <= endLineNumber; i++) {
const lineText = model.getLineContent(i);
const firstNonWhitespaceIdx = model.getLineFirstNonWhitespaceColumn(i);
if (firstNonWhitespaceIdx >= 1) {
let insertSpace = true;
if (trimmedLinesContent === '') {
insertSpace = false;
}
if (insertSpace && (trimmedLinesContent.charAt(trimmedLinesContent.length - 1) === ' ' ||
trimmedLinesContent.charAt(trimmedLinesContent.length - 1) === '\t')) {
insertSpace = false;
trimmedLinesContent = trimmedLinesContent.replace(/[\s\uFEFF\xA0]+$/g, ' ');
}
const lineTextWithoutIndent = lineText.substr(firstNonWhitespaceIdx - 1);
trimmedLinesContent += (insertSpace ? ' ' : '') + lineTextWithoutIndent;
if (insertSpace) {
columnDeltaOffset = lineTextWithoutIndent.length + 1;
}
else {
columnDeltaOffset = lineTextWithoutIndent.length;
}
}
else {
columnDeltaOffset = 0;
}
}
const deleteSelection = new Range(startLineNumber, startColumn, endLineNumber, endColumn);
if (!deleteSelection.isEmpty()) {
let resultSelection;
if (selection.isEmpty()) {
edits.push(EditOperation.replace(deleteSelection, trimmedLinesContent));
resultSelection = new Selection(deleteSelection.startLineNumber - lineOffset, trimmedLinesContent.length - columnDeltaOffset + 1, startLineNumber - lineOffset, trimmedLinesContent.length - columnDeltaOffset + 1);
}
else {
if (selection.startLineNumber === selection.endLineNumber) {
edits.push(EditOperation.replace(deleteSelection, trimmedLinesContent));
resultSelection = new Selection(selection.startLineNumber - lineOffset, selection.startColumn, selection.endLineNumber - lineOffset, selection.endColumn);
}
else {
edits.push(EditOperation.replace(deleteSelection, trimmedLinesContent));
resultSelection = new Selection(selection.startLineNumber - lineOffset, selection.startColumn, selection.startLineNumber - lineOffset, trimmedLinesContent.length - selectionEndPositionOffset);
}
}
if (Range.intersectRanges(deleteSelection, primaryCursor) !== null) {
endPrimaryCursor = resultSelection;
}
else {
endCursorState.push(resultSelection);
}
}
lineOffset += deleteSelection.endLineNumber - deleteSelection.startLineNumber;
}
endCursorState.unshift(endPrimaryCursor);
editor.pushUndoStop();
editor.executeEdits(this.id, edits, endCursorState);
editor.pushUndoStop();
}
}
export class TransposeAction extends EditorAction {
constructor() {
super({
id: 'editor.action.transpose',
label: nls.localize('editor.transpose', "Transpose Characters around the Cursor"),
alias: 'Transpose Characters around the Cursor',
precondition: EditorContextKeys.writable
});
}
run(_accessor, editor) {
const selections = editor.getSelections();
if (selections === null) {
return;
}
const model = editor.getModel();
if (model === null) {
return;
}
const commands = [];
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
if (!selection.isEmpty()) {
continue;
}
const cursor = selection.getStartPosition();
const maxColumn = model.getLineMaxColumn(cursor.lineNumber);
if (cursor.column >= maxColumn) {
if (cursor.lineNumber === model.getLineCount()) {
continue;
}
// The cursor is at the end of current line and current line is not empty
// then we transpose the character before the cursor and the line break if there is any following line.
const deleteSelection = new Range(cursor.lineNumber, Math.max(1, cursor.column - 1), cursor.lineNumber + 1, 1);
const chars = model.getValueInRange(deleteSelection).split('').reverse().join('');
commands.push(new ReplaceCommand(new Selection(cursor.lineNumber, Math.max(1, cursor.column - 1), cursor.lineNumber + 1, 1), chars));
}
else {
const deleteSelection = new Range(cursor.lineNumber, Math.max(1, cursor.column - 1), cursor.lineNumber, cursor.column + 1);
const chars = model.getValueInRange(deleteSelection).split('').reverse().join('');
commands.push(new ReplaceCommandThatPreservesSelection(deleteSelection, chars, new Selection(cursor.lineNumber, cursor.column + 1, cursor.lineNumber, cursor.column + 1)));
}
}
editor.pushUndoStop();
editor.executeCommands(this.id, commands);
editor.pushUndoStop();
}
}
export class AbstractCaseAction extends EditorAction {
run(_accessor, editor) {
const selections = editor.getSelections();
if (selections === null) {
return;
}
const model = editor.getModel();
if (model === null) {
return;
}
const wordSeparators = editor.getOption(132 /* EditorOption.wordSeparators */);
const textEdits = [];
for (const selection of selections) {
if (selection.isEmpty()) {
const cursor = selection.getStartPosition();
const word = editor.getConfiguredWordAtPosition(cursor);
if (!word) {
continue;
}
const wordRange = new Range(cursor.lineNumber, word.startColumn, cursor.lineNumber, word.endColumn);
const text = model.getValueInRange(wordRange);
textEdits.push(EditOperation.replace(wordRange, this._modifyText(text, wordSeparators)));
}
else {
const text = model.getValueInRange(selection);
textEdits.push(EditOperation.replace(selection, this._modifyText(text, wordSeparators)));
}
}
editor.pushUndoStop();
editor.executeEdits(this.id, textEdits);
editor.pushUndoStop();
}
}
export class UpperCaseAction extends AbstractCaseAction {
constructor() {
super({
id: 'editor.action.transformToUppercase',
label: nls.localize('editor.transformToUppercase', "Transform to Uppercase"),
alias: 'Transform to Uppercase',
precondition: EditorContextKeys.writable
});
}
_modifyText(text, wordSeparators) {
return text.toLocaleUpperCase();
}
}
export class LowerCaseAction extends AbstractCaseAction {
constructor() {
super({
id: 'editor.action.transformToLowercase',
label: nls.localize('editor.transformToLowercase', "Transform to Lowercase"),
alias: 'Transform to Lowercase',
precondition: EditorContextKeys.writable
});
}
_modifyText(text, wordSeparators) {
return text.toLocaleLowerCase();
}
}
class BackwardsCompatibleRegExp {
constructor(_pattern, _flags) {
this._pattern = _pattern;
this._flags = _flags;
this._actual = null;
this._evaluated = false;
}
get() {
if (!this._evaluated) {
this._evaluated = true;
try {
this._actual = new RegExp(this._pattern, this._flags);
}
catch (err) {
// this browser does not support this regular expression
}
}
return this._actual;
}
isSupported() {
return (this.get() !== null);
}
}
export class TitleCaseAction extends AbstractCaseAction {
static { this.titleBoundary = new BackwardsCompatibleRegExp('(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}', 'gmu'); }
constructor() {
super({
id: 'editor.action.transformToTitlecase',
label: nls.localize('editor.transformToTitlecase', "Transform to Title Case"),
alias: 'Transform to Title Case',
precondition: EditorContextKeys.writable
});
}
_modifyText(text, wordSeparators) {
const titleBoundary = TitleCaseAction.titleBoundary.get();
if (!titleBoundary) {
// cannot support this
return text;
}
return text
.toLocaleLowerCase()
.replace(titleBoundary, (b) => b.toLocaleUpperCase());
}
}
export class SnakeCaseAction extends AbstractCaseAction {
static { this.caseBoundary = new BackwardsCompatibleRegExp('(\\p{Ll})(\\p{Lu})', 'gmu'); }
static { this.singleLetters = new BackwardsCompatibleRegExp('(\\p{Lu}|\\p{N})(\\p{Lu})(\\p{Ll})', 'gmu'); }
constructor() {
super({
id: 'editor.action.transformToSnakecase',
label: nls.localize('editor.transformToSnakecase', "Transform to Snake Case"),
alias: 'Transform to Snake Case',
precondition: EditorContextKeys.writable
});
}
_modifyText(text, wordSeparators) {
const caseBoundary = SnakeCaseAction.caseBoundary.get();
const singleLetters = SnakeCaseAction.singleLetters.get();
if (!caseBoundary || !singleLetters) {
// cannot support this
return text;
}
return (text
.replace(caseBoundary, '$1_$2')
.replace(singleLetters, '$1_$2$3')
.toLocaleLowerCase());
}
}
export class CamelCaseAction extends AbstractCaseAction {
static { this.wordBoundary = new BackwardsCompatibleRegExp('[_\\s-]', 'gm'); }
constructor() {
super({
id: 'editor.action.transformToCamelcase',
label: nls.localize('editor.transformToCamelcase', "Transform to Camel Case"),
alias: 'Transform to Camel Case',
precondition: EditorContextKeys.writable
});
}
_modifyText(text, wordSeparators) {
const wordBoundary = CamelCaseAction.wordBoundary.get();
if (!wordBoundary) {
// cannot support this
return text;
}
const words = text.split(wordBoundary);
const firstWord = words.shift();
return firstWord + words.map((word) => word.substring(0, 1).toLocaleUpperCase() + word.substring(1))
.join('');
}
}
export class PascalCaseAction extends AbstractCaseAction {
static { this.wordBoundary = new BackwardsCompatibleRegExp('[_\\s-]', 'gm'); }
static { this.wordBoundaryToMaintain = new BackwardsCompatibleRegExp('(?<=\\.)', 'gm'); }
constructor() {
super({
id: 'editor.action.transformToPascalcase',
label: nls.localize('editor.transformToPascalcase', "Transform to Pascal Case"),
alias: 'Transform to Pascal Case',
precondition: EditorContextKeys.writable
});
}
_modifyText(text, wordSeparators) {
const wordBoundary = PascalCaseAction.wordBoundary.get();
const wordBoundaryToMaintain = PascalCaseAction.wordBoundaryToMaintain.get();
if (!wordBoundary || !wordBoundaryToMaintain) {
// cannot support this
return text;
}
const wordsWithMaintainBoundaries = text.split(wordBoundaryToMaintain);
const words = wordsWithMaintainBoundaries.map((word) => word.split(wordBoundary)).flat();
return words.map((word) => word.substring(0, 1).toLocaleUpperCase() + word.substring(1))
.join('');
}
}
export class KebabCaseAction extends AbstractCaseAction {
static isSupported() {
const areAllRegexpsSupported = [
this.caseBoundary,
this.singleLetters,
this.underscoreBoundary,
].every((regexp) => regexp.isSupported());
return areAllRegexpsSupported;
}
static { this.caseBoundary = new BackwardsCompatibleRegExp('(\\p{Ll})(\\p{Lu})', 'gmu'); }
static { this.singleLetters = new BackwardsCompatibleRegExp('(\\p{Lu}|\\p{N})(\\p{Lu}\\p{Ll})', 'gmu'); }
static { this.underscoreBoundary = new BackwardsCompatibleRegExp('(\\S)(_)(\\S)', 'gm'); }
constructor() {
super({
id: 'editor.action.transformToKebabcase',
label: nls.localize('editor.transformToKebabcase', 'Transform to Kebab Case'),
alias: 'Transform to Kebab Case',
precondition: EditorContextKeys.writable
});
}
_modifyText(text, _) {
const caseBoundary = KebabCaseAction.caseBoundary.get();
const singleLetters = KebabCaseAction.singleLetters.get();
const underscoreBoundary = KebabCaseAction.underscoreBoundary.get();
if (!caseBoundary || !singleLetters || !underscoreBoundary) {
// one or more regexps aren't supported
return text;
}
return text
.replace(underscoreBoundary, '$1-$3')
.replace(caseBoundary, '$1-$2')
.replace(singleLetters, '$1-$2')
.toLocaleLowerCase();
}
}
registerEditorAction(CopyLinesUpAction);
registerEditorAction(CopyLinesDownAction);
registerEditorAction(DuplicateSelectionAction);
registerEditorAction(MoveLinesUpAction);
registerEditorAction(MoveLinesDownAction);
registerEditorAction(SortLinesAscendingAction);
registerEditorAction(SortLinesDescendingAction);
registerEditorAction(DeleteDuplicateLinesAction);
registerEditorAction(TrimTrailingWhitespaceAction);
registerEditorAction(DeleteLinesAction);
registerEditorAction(IndentLinesAction);
registerEditorAction(OutdentLinesAction);
registerEditorAction(InsertLineBeforeAction);
registerEditorAction(InsertLineAfterAction);
registerEditorAction(DeleteAllLeftAction);
registerEditorAction(DeleteAllRightAction);
registerEditorAction(JoinLinesAction);
registerEditorAction(TransposeAction);
registerEditorAction(UpperCaseAction);
registerEditorAction(LowerCaseAction);
if (SnakeCaseAction.caseBoundary.isSupported() && SnakeCaseAction.singleLetters.isSupported()) {
registerEditorAction(SnakeCaseAction);
}
if (CamelCaseAction.wordBoundary.isSupported()) {
registerEditorAction(CamelCaseAction);
}
if (PascalCaseAction.wordBoundary.isSupported()) {
registerEditorAction(PascalCaseAction);
}
if (TitleCaseAction.titleBoundary.isSupported()) {
registerEditorAction(TitleCaseAction);
}
if (KebabCaseAction.isSupported()) {
registerEditorAction(KebabCaseAction);
}