SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,84 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Range } from 'vs/editor/common/core/range';
import { Selection, SelectionDirection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
export class CopyLinesCommand implements editorCommon.ICommand {
private _selection: Selection;
private _isCopyingDown: boolean;
private _selectionDirection: SelectionDirection;
private _selectionId: string;
private _startLineNumberDelta: number;
private _endLineNumberDelta: number;
constructor(selection: Selection, isCopyingDown: boolean) {
this._selection = selection;
this._isCopyingDown = isCopyingDown;
}
public getEditOperations(model: editorCommon.ITokenizedModel, builder: editorCommon.IEditOperationBuilder): void {
var s = this._selection;
this._startLineNumberDelta = 0;
this._endLineNumberDelta = 0;
if (s.startLineNumber < s.endLineNumber && s.endColumn === 1) {
this._endLineNumberDelta = 1;
s = s.setEndPosition(s.endLineNumber - 1, model.getLineMaxColumn(s.endLineNumber - 1));
}
var sourceLines: string[] = [];
for (var i = s.startLineNumber; i <= s.endLineNumber; i++) {
sourceLines.push(model.getLineContent(i));
}
var sourceText = sourceLines.join('\n');
if (sourceText === '') {
// Duplicating empty line
if (this._isCopyingDown) {
this._startLineNumberDelta++;
this._endLineNumberDelta++;
}
}
if (!this._isCopyingDown) {
builder.addEditOperation(new Range(s.endLineNumber, model.getLineMaxColumn(s.endLineNumber), s.endLineNumber, model.getLineMaxColumn(s.endLineNumber)), '\n' + sourceText);
} else {
builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), sourceText + '\n');
}
this._selectionId = builder.trackSelection(s);
this._selectionDirection = this._selection.getDirection();
}
public computeCursorState(model: editorCommon.ITokenizedModel, helper: editorCommon.ICursorStateComputerData): Selection {
var result = helper.getTrackedSelection(this._selectionId);
if (this._startLineNumberDelta !== 0 || this._endLineNumberDelta !== 0) {
var startLineNumber = result.startLineNumber,
startColumn = result.startColumn,
endLineNumber = result.endLineNumber,
endColumn = result.endColumn;
if (this._startLineNumberDelta !== 0) {
startLineNumber = startLineNumber + this._startLineNumberDelta;
startColumn = 1;
}
if (this._endLineNumberDelta !== 0) {
endLineNumber = endLineNumber + this._endLineNumberDelta;
endColumn = 1;
}
result = Selection.createWithDirection(startLineNumber, startColumn, endLineNumber, endColumn, this._selectionDirection);
}
return result;
}
}

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ICommand, ICursorStateComputerData, IEditOperationBuilder, ITokenizedModel } from 'vs/editor/common/editorCommon';
export class DeleteLinesCommand implements ICommand {
public static createFromSelection(selection: Selection): DeleteLinesCommand {
var endLineNumber = selection.endLineNumber;
if (selection.startLineNumber < selection.endLineNumber && selection.endColumn === 1) {
endLineNumber -= 1;
}
return new DeleteLinesCommand(selection.startLineNumber, endLineNumber, selection.positionColumn);
}
private startLineNumber: number;
private endLineNumber: number;
private restoreCursorToColumn: number;
constructor(startLineNumber: number, endLineNumber: number, restoreCursorToColumn: number) {
this.startLineNumber = startLineNumber;
this.endLineNumber = endLineNumber;
this.restoreCursorToColumn = restoreCursorToColumn;
}
public getEditOperations(model: ITokenizedModel, builder: IEditOperationBuilder): void {
if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
// Model is empty
return;
}
var startLineNumber = this.startLineNumber;
var endLineNumber = this.endLineNumber;
var startColumn = 1;
var endColumn = model.getLineMaxColumn(endLineNumber);
if (endLineNumber < model.getLineCount()) {
endLineNumber += 1;
endColumn = 1;
} else if (startLineNumber > 1) {
startLineNumber -= 1;
startColumn = model.getLineMaxColumn(startLineNumber);
}
builder.addTrackedEditOperation(new Range(startLineNumber, startColumn, endLineNumber, endColumn), null);
}
public computeCursorState(model: ITokenizedModel, helper: ICursorStateComputerData): Selection {
var inverseEditOperations = helper.getInverseEditOperations();
var srcRange = inverseEditOperations[0].range;
return new Selection(
srcRange.endLineNumber,
this.restoreCursorToColumn,
srcRange.endLineNumber,
this.restoreCursorToColumn
);
}
}

View File

@@ -0,0 +1,797 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
import { SortLinesCommand } from 'vs/editor/contrib/linesOperations/common/sortLinesCommand';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { TrimTrailingWhitespaceCommand } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand';
import { ICommand, ICommonCodeEditor, IIdentifiedSingleEditOperation } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ReplaceCommand, ReplaceCommandThatPreservesSelection } from 'vs/editor/common/commands/replaceCommand';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { editorAction, ServicesAccessor, IActionOptions, EditorAction } from 'vs/editor/common/editorCommonExtensions';
import { CopyLinesCommand } from './copyLinesCommand';
import { DeleteLinesCommand } from './deleteLinesCommand';
import { MoveLinesCommand } from './moveLinesCommand';
import { TypeOperations } from 'vs/editor/common/controller/cursorTypeOperations';
import { CoreEditingCommands } from 'vs/editor/common/controller/coreCommands';
// copy lines
abstract class AbstractCopyLinesAction extends EditorAction {
private down: boolean;
constructor(down: boolean, opts: IActionOptions) {
super(opts);
this.down = down;
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
var commands: ICommand[] = [];
var selections = editor.getSelections();
for (var i = 0; i < selections.length; i++) {
commands.push(new CopyLinesCommand(selections[i], this.down));
}
editor.pushUndoStop();
editor.executeCommands(this.id, commands);
editor.pushUndoStop();
}
}
@editorAction
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.textFocus,
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow }
}
});
}
}
@editorAction
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.textFocus,
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow,
linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow }
}
});
}
}
// move lines
abstract class AbstractMoveLinesAction extends EditorAction {
private down: boolean;
constructor(down: boolean, opts: IActionOptions) {
super(opts);
this.down = down;
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
var commands: ICommand[] = [];
var selections = editor.getSelections();
var autoIndent = editor.getConfiguration().autoIndent;
for (var i = 0; i < selections.length; i++) {
commands.push(new MoveLinesCommand(selections[i], this.down, autoIndent));
}
editor.pushUndoStop();
editor.executeCommands(this.id, commands);
editor.pushUndoStop();
}
}
@editorAction
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.textFocus,
primary: KeyMod.Alt | KeyCode.UpArrow,
linux: { primary: KeyMod.Alt | KeyCode.UpArrow }
}
});
}
}
@editorAction
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.textFocus,
primary: KeyMod.Alt | KeyCode.DownArrow,
linux: { primary: KeyMod.Alt | KeyCode.DownArrow }
}
});
}
}
abstract class AbstractSortLinesAction extends EditorAction {
private descending: boolean;
constructor(descending: boolean, opts: IActionOptions) {
super(opts);
this.descending = descending;
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
if (!SortLinesCommand.canRun(editor.getModel(), editor.getSelection(), this.descending)) {
return;
}
var command = new SortLinesCommand(editor.getSelection(), this.descending);
editor.pushUndoStop();
editor.executeCommands(this.id, [command]);
editor.pushUndoStop();
}
}
@editorAction
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
});
}
}
@editorAction
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
});
}
}
@editorAction
export class TrimTrailingWhitespaceAction extends EditorAction {
public static 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.textFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_X)
}
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
var command = new TrimTrailingWhitespaceCommand(editor.getSelection());
editor.pushUndoStop();
editor.executeCommands(this.id, [command]);
editor.pushUndoStop();
}
}
// delete lines
interface IDeleteLinesOperation {
startLineNumber: number;
endLineNumber: number;
positionColumn: number;
}
abstract class AbstractRemoveLinesAction extends EditorAction {
_getLinesToRemove(editor: ICommonCodeEditor): IDeleteLinesOperation[] {
// Construct delete operations
var operations: IDeleteLinesOperation[] = editor.getSelections().map((s) => {
var endLineNumber = s.endLineNumber;
if (s.startLineNumber < s.endLineNumber && s.endColumn === 1) {
endLineNumber -= 1;
}
return {
startLineNumber: s.startLineNumber,
endLineNumber: endLineNumber,
positionColumn: s.positionColumn
};
});
// Sort delete operations
operations.sort((a, b) => {
return a.startLineNumber - b.startLineNumber;
});
// Merge delete operations on consecutive lines
var mergedOperations: IDeleteLinesOperation[] = [];
var previousOperation = operations[0];
for (var 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;
}
}
@editorAction
class DeleteLinesAction extends AbstractRemoveLinesAction {
constructor() {
super({
id: 'editor.action.deleteLines',
label: nls.localize('lines.delete', "Delete Line"),
alias: 'Delete Line',
precondition: EditorContextKeys.writable,
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_K
}
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
var ops = this._getLinesToRemove(editor);
// Finally, construct the delete lines commands
var commands: ICommand[] = ops.map((op) => {
return new DeleteLinesCommand(op.startLineNumber, op.endLineNumber, op.positionColumn);
});
editor.pushUndoStop();
editor.executeCommands(this.id, commands);
editor.pushUndoStop();
}
}
@editorAction
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.textFocus,
primary: KeyMod.CtrlCmd | KeyCode.US_CLOSE_SQUARE_BRACKET
}
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
editor.pushUndoStop();
editor.executeCommands(this.id, TypeOperations.indent(editor._getCursorConfiguration(), editor.getModel(), editor.getSelections()));
editor.pushUndoStop();
}
}
@editorAction
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.textFocus,
primary: KeyMod.CtrlCmd | KeyCode.US_OPEN_SQUARE_BRACKET
}
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
CoreEditingCommands.Outdent.runEditorCommand(null, editor, null);
}
}
@editorAction
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.textFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter
}
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
editor.pushUndoStop();
editor.executeCommands(this.id, TypeOperations.lineInsertBefore(editor._getCursorConfiguration(), editor.getModel(), editor.getSelections()));
}
}
@editorAction
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.textFocus,
primary: KeyMod.CtrlCmd | KeyCode.Enter
}
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
editor.pushUndoStop();
editor.executeCommands(this.id, TypeOperations.lineInsertAfter(editor._getCursorConfiguration(), editor.getModel(), editor.getSelections()));
}
}
export abstract class AbstractDeleteAllToBoundaryAction extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
const primaryCursor = editor.getSelection();
let rangesToDelete = this._getRangesToDelete(editor);
// merge overlapping selections
let effectiveRanges: Range[] = [];
for (let i = 0, count = rangesToDelete.length - 1; i < count; i++) {
let range = rangesToDelete[i];
let 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]);
let endCursorState = this._getEndCursorState(primaryCursor, effectiveRanges);
let edits: IIdentifiedSingleEditOperation[] = effectiveRanges.map(range => {
endCursorState.push(new Selection(range.startLineNumber, range.startColumn, range.startLineNumber, range.startColumn));
return EditOperation.replace(range, '');
});
editor.executeEdits(this.id, edits, endCursorState);
}
/**
* Compute the cursor state after the edit operations were applied.
*/
protected abstract _getEndCursorState(primaryCursor: Range, rangesToDelete: Range[]): Selection[];
protected abstract _getRangesToDelete(editor: ICommonCodeEditor): Range[];
}
@editorAction
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.textFocus,
primary: null,
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }
}
});
}
_getEndCursorState(primaryCursor: Range, rangesToDelete: Range[]): Selection[] {
let endPrimaryCursor: Selection;
let endCursorState: Selection[] = [];
for (let i = 0, len = rangesToDelete.length; i < len; i++) {
let range = rangesToDelete[i];
let endCursor = new Selection(rangesToDelete[i].startLineNumber, rangesToDelete[i].startColumn, rangesToDelete[i].startLineNumber, rangesToDelete[i].startColumn);
if (range.intersectRanges(primaryCursor)) {
endPrimaryCursor = endCursor;
} else {
endCursorState.push(endCursor);
}
}
if (endPrimaryCursor) {
endCursorState.unshift(endPrimaryCursor);
}
return endCursorState;
}
_getRangesToDelete(editor: ICommonCodeEditor): Range[] {
let rangesToDelete: Range[] = editor.getSelections();
rangesToDelete.sort(Range.compareRangesUsingStarts);
rangesToDelete = rangesToDelete.map(selection => {
if (selection.isEmpty()) {
return new Range(selection.startLineNumber, 1, selection.startLineNumber, selection.startColumn);
} else {
return selection;
}
});
return rangesToDelete;
}
}
@editorAction
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.textFocus,
primary: null,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_K, secondary: [KeyMod.CtrlCmd | KeyCode.Delete] }
}
});
}
_getEndCursorState(primaryCursor: Range, rangesToDelete: Range[]): Selection[] {
let endPrimaryCursor: Selection;
let endCursorState: Selection[] = [];
for (let i = 0, len = rangesToDelete.length, offset = 0; i < len; i++) {
let range = rangesToDelete[i];
let 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: ICommonCodeEditor): Range[] {
let model = editor.getModel();
let rangesToDelete: Range[] = editor.getSelections().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;
}
}
@editorAction
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.textFocus,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_J }
}
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
let selections = editor.getSelections();
let primaryCursor = editor.getSelection();
selections.sort(Range.compareRangesUsingStarts);
let reducedSelections: Selection[] = [];
let 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);
let model = editor.getModel();
let edits = [];
let endCursorState = [];
let endPrimaryCursor = primaryCursor;
let lineOffset = 0;
for (let i = 0, len = reducedSelections.length; i < len; i++) {
let selection = reducedSelections[i];
let startLineNumber = selection.startLineNumber;
let startColumn = 1;
let endLineNumber: number,
endColumn: number,
columnDeltaOffset: number;
let selectionEndPositionOffset = model.getLineContent(selection.endLineNumber).length - selection.endColumn;
if (selection.isEmpty() || selection.startLineNumber === selection.endLineNumber) {
let 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++) {
let lineText = model.getLineContent(i);
let 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, ' ');
}
let lineTextWithoutIndent = lineText.substr(firstNonWhitespaceIdx - 1);
trimmedLinesContent += (insertSpace ? ' ' : '') + lineTextWithoutIndent;
if (insertSpace) {
columnDeltaOffset = lineTextWithoutIndent.length + 1;
} else {
columnDeltaOffset = lineTextWithoutIndent.length;
}
} else {
columnDeltaOffset = 0;
}
}
let deleteSelection = new Range(startLineNumber, startColumn, endLineNumber, endColumn);
if (!deleteSelection.isEmpty()) {
let resultSelection: Selection;
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.executeEdits(this.id, edits, endCursorState);
}
}
@editorAction
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
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
let selections = editor.getSelections();
let model = editor.getModel();
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
let selection = selections[i];
if (!selection.isEmpty()) {
continue;
}
let cursor = selection.getStartPosition();
let 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.
let deleteSelection = new Range(cursor.lineNumber, Math.max(1, cursor.column - 1), cursor.lineNumber + 1, 1);
let 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 {
let deleteSelection = new Range(cursor.lineNumber, Math.max(1, cursor.column - 1), cursor.lineNumber, cursor.column + 1);
let 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 abstract class AbstractCaseAction extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
let selections = editor.getSelections();
let model = editor.getModel();
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
let selection = selections[i];
if (selection.isEmpty()) {
let cursor = selection.getStartPosition();
let word = model.getWordAtPosition(cursor);
if (!word) {
continue;
}
let wordRange = new Range(cursor.lineNumber, word.startColumn, cursor.lineNumber, word.endColumn);
let text = model.getValueInRange(wordRange);
commands.push(new ReplaceCommandThatPreservesSelection(wordRange, this._modifyText(text),
new Selection(cursor.lineNumber, cursor.column, cursor.lineNumber, cursor.column)));
} else {
let text = model.getValueInRange(selection);
commands.push(new ReplaceCommandThatPreservesSelection(selection, this._modifyText(text), selection));
}
}
editor.pushUndoStop();
editor.executeCommands(this.id, commands);
editor.pushUndoStop();
}
protected abstract _modifyText(text: string): string;
}
@editorAction
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
});
}
protected _modifyText(text: string): string {
return text.toLocaleUpperCase();
}
}
@editorAction
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
});
}
protected _modifyText(text: string): string {
return text.toLocaleLowerCase();
}
}

View File

@@ -0,0 +1,356 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as strings from 'vs/base/common/strings';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ICommand, ICursorStateComputerData, IEditOperationBuilder, ITokenizedModel } from 'vs/editor/common/editorCommon';
import { LanguageConfigurationRegistry, IIndentConverter } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand';
import * as IndentUtil from 'vs/editor/contrib/indentation/common/indentUtils';
import { IndentAction } from 'vs/editor/common/modes/languageConfiguration';
import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules';
export class MoveLinesCommand implements ICommand {
private _selection: Selection;
private _isMovingDown: boolean;
private _autoIndent: boolean;
private _selectionId: string;
private _moveEndPositionDown: boolean;
private _moveEndLineSelectionShrink: boolean;
constructor(selection: Selection, isMovingDown: boolean, autoIndent: boolean) {
this._selection = selection;
this._isMovingDown = isMovingDown;
this._autoIndent = autoIndent;
this._moveEndLineSelectionShrink = false;
}
public getEditOperations(model: ITokenizedModel, builder: IEditOperationBuilder): void {
var modelLineCount = model.getLineCount();
if (this._isMovingDown && this._selection.endLineNumber === modelLineCount) {
return;
}
if (!this._isMovingDown && this._selection.startLineNumber === 1) {
return;
}
this._moveEndPositionDown = false;
var 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));
}
let tabSize = model.getOptions().tabSize;
let insertSpaces = model.getOptions().insertSpaces;
let indentConverter = this.buildIndentConverter(tabSize);
let virtualModel = {
getLineTokens: (lineNumber: number) => {
return model.getLineTokens(lineNumber);
},
getLanguageIdentifier: () => {
return model.getLanguageIdentifier();
},
getLanguageIdAtPosition: (lineNumber: number, column: number) => {
return model.getLanguageIdAtPosition(lineNumber, column);
},
getLineContent: null
};
if (s.startLineNumber === s.endLineNumber && model.getLineMaxColumn(s.startLineNumber) === 1) {
// Current line is empty
var lineNumber = s.startLineNumber;
var 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 {
var movingLineNumber: number,
movingLineText: string;
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 + IndentUtil.getSpaceCnt(oldIndentation, tabSize);
let newIndentation = IndentUtil.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(virtualModel, model.getLanguageIdAtPosition(
movingLineNumber, 1), s.startLineNumber, indentConverter);
if (indentOfMovingLine !== null) {
let oldIndentation = strings.getLeadingWhitespace(model.getLineContent(movingLineNumber));
let newSpaceCnt = IndentUtil.getSpaceCnt(indentOfMovingLine, tabSize);
let oldSpaceCnt = IndentUtil.getSpaceCnt(oldIndentation, tabSize);
if (newSpaceCnt !== oldSpaceCnt) {
let newIndentation = IndentUtil.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.matchEnterRule(model, indentConverter, tabSize, s.startLineNumber, s.startLineNumber, 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(virtualModel, model.getLanguageIdAtPosition(
movingLineNumber, 1), s.startLineNumber + 1, indentConverter);
if (newIndentatOfMovingBlock !== null) {
const oldIndentation = strings.getLeadingWhitespace(model.getLineContent(s.startLineNumber));
const newSpaceCnt = IndentUtil.getSpaceCnt(newIndentatOfMovingBlock, tabSize);
const oldSpaceCnt = IndentUtil.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: number) => {
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(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 = IndentUtil.getSpaceCnt(indentOfFirstLine, tabSize);
let oldSpaceCnt = IndentUtil.getSpaceCnt(oldIndent, tabSize);
if (newSpaceCnt !== oldSpaceCnt) {
let spaceCntOffset = newSpaceCnt - oldSpaceCnt;
this.getIndentEditsOfMovingBlock(model, builder, s, tabSize, insertSpaces, spaceCntOffset);
}
}
}
}
}
}
this._selectionId = builder.trackSelection(s);
}
private buildIndentConverter(tabSize: number): IIndentConverter {
return {
shiftIndent: (indentation) => {
let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + 1, tabSize);
let newIndentation = '';
for (let i = 0; i < desiredIndentCount; i++) {
newIndentation += '\t';
}
return newIndentation;
},
unshiftIndent: (indentation) => {
let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + 1, tabSize);
let newIndentation = '';
for (let i = 0; i < desiredIndentCount; i++) {
newIndentation += '\t';
}
return newIndentation;
}
};
}
private matchEnterRule(model: ITokenizedModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, oneLineAboveText?: string) {
let validPrecedingLine = oneLineAbove;
while (validPrecedingLine >= 1) {
// ship empty lines as empty lines just inherit indentation
let lineContent;
if (validPrecedingLine === oneLineAbove && oneLineAboveText !== undefined) {
lineContent = oneLineAboveText;
} 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(model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn));
if (enter) {
let enterPrefix = enter.indentation;
let enterAction = enter.enterAction;
if (enterAction.indentAction === IndentAction.None) {
enterPrefix = enter.indentation + enterAction.appendText;
} else if (enterAction.indentAction === IndentAction.Indent) {
enterPrefix = enter.indentation + enterAction.appendText;
} else if (enterAction.indentAction === IndentAction.IndentOutdent) {
enterPrefix = enter.indentation;
} else if (enterAction.indentAction === IndentAction.Outdent) {
enterPrefix = indentConverter.unshiftIndent(enter.indentation) + enterAction.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 & IndentConsts.DECREASE_MASK) {
newIndentation = indentConverter.unshiftIndent(newIndentation);
}
let newSpaceCnt = IndentUtil.getSpaceCnt(newIndentation, tabSize);
let oldSpaceCnt = IndentUtil.getSpaceCnt(oldIndentation, tabSize);
return newSpaceCnt - oldSpaceCnt;
}
}
return null;
}
private trimLeft(str: string) {
return str.replace(/^\s+/, '');
}
private shouldAutoIndent(model: ITokenizedModel, selection: Selection) {
if (!this._autoIndent) {
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;
}
private getIndentEditsOfMovingBlock(model: ITokenizedModel, builder: IEditOperationBuilder, s: Selection, tabSize: number, insertSpaces: boolean, offset: number) {
for (let i = s.startLineNumber; i <= s.endLineNumber; i++) {
let lineContent = model.getLineContent(i);
let originalIndent = strings.getLeadingWhitespace(lineContent);
let originalSpacesCnt = IndentUtil.getSpaceCnt(originalIndent, tabSize);
let newSpacesCnt = originalSpacesCnt + offset;
let newIndent = IndentUtil.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;
}
}
}
}
public computeCursorState(model: ITokenizedModel, helper: ICursorStateComputerData): Selection {
var 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;
}
}

View File

@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
export class SortLinesCommand implements editorCommon.ICommand {
private selection: Selection;
private selectionId: string;
private descending: boolean;
constructor(selection: Selection, descending: boolean) {
this.selection = selection;
this.descending = descending;
}
public getEditOperations(model: editorCommon.ITokenizedModel, builder: editorCommon.IEditOperationBuilder): void {
let op = sortLines(model, this.selection, this.descending);
if (op) {
builder.addEditOperation(op.range, op.text);
}
this.selectionId = builder.trackSelection(this.selection);
}
public computeCursorState(model: editorCommon.ITokenizedModel, helper: editorCommon.ICursorStateComputerData): Selection {
return helper.getTrackedSelection(this.selectionId);
}
public static canRun(model: editorCommon.ITextModel, selection: Selection, descending: boolean): boolean {
let data = getSortData(model, selection, descending);
if (!data) {
return false;
}
for (let i = 0, len = data.before.length; i < len; i++) {
if (data.before[i] !== data.after[i]) {
return true;
}
}
return false;
}
}
function getSortData(model: editorCommon.ITextModel, selection: Selection, descending: boolean) {
let startLineNumber = selection.startLineNumber;
let endLineNumber = selection.endLineNumber;
if (selection.endColumn === 1) {
endLineNumber--;
}
// Nothing to sort if user didn't select anything.
if (startLineNumber >= endLineNumber) {
return null;
}
let linesToSort = [];
// Get the contents of the selection to be sorted.
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
linesToSort.push(model.getLineContent(lineNumber));
}
let sorted = linesToSort.slice(0);
sorted.sort((a, b) => {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
// If descending, reverse the order.
if (descending === true) {
sorted = sorted.reverse();
}
return {
startLineNumber: startLineNumber,
endLineNumber: endLineNumber,
before: linesToSort,
after: sorted
};
}
/**
* Generate commands for sorting lines on a model.
*/
function sortLines(model: editorCommon.ITextModel, selection: Selection, descending: boolean): editorCommon.IIdentifiedSingleEditOperation {
let data = getSortData(model, selection, descending);
if (!data) {
return null;
}
return EditOperation.replace(
new Range(data.startLineNumber, 1, data.endLineNumber, model.getLineMaxColumn(data.endLineNumber)),
data.after.join('\n')
);
}

View File

@@ -0,0 +1,200 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Selection } from 'vs/editor/common/core/selection';
import { CopyLinesCommand } from 'vs/editor/contrib/linesOperations/common/copyLinesCommand';
import { testCommand } from 'vs/editor/test/common/commands/commandTestUtils';
function testCopyLinesDownCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, null, selection, (sel) => new CopyLinesCommand(sel, true), expectedLines, expectedSelection);
}
function testCopyLinesUpCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, null, selection, (sel) => new CopyLinesCommand(sel, false), expectedLines, expectedSelection);
}
suite('Editor Contrib - Copy Lines Command', () => {
test('copy first line down', function () {
testCopyLinesDownCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 3, 1, 1),
[
'first',
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(2, 3, 2, 1)
);
});
test('copy first line up', function () {
testCopyLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 3, 1, 1),
[
'first',
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 3, 1, 1)
);
});
test('copy last line down', function () {
testCopyLinesDownCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(5, 3, 5, 1),
[
'first',
'second line',
'third line',
'fourth line',
'fifth',
'fifth'
],
new Selection(6, 3, 6, 1)
);
});
test('copy last line up', function () {
testCopyLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(5, 3, 5, 1),
[
'first',
'second line',
'third line',
'fourth line',
'fifth',
'fifth'
],
new Selection(5, 3, 5, 1)
);
});
test('issue #1322: copy line up', function () {
testCopyLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(3, 11, 3, 11),
[
'first',
'second line',
'third line',
'third line',
'fourth line',
'fifth'
],
new Selection(3, 11, 3, 11)
);
});
test('issue #1322: copy last line up', function () {
testCopyLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(5, 6, 5, 6),
[
'first',
'second line',
'third line',
'fourth line',
'fifth',
'fifth'
],
new Selection(5, 6, 5, 6)
);
});
test('copy many lines up', function () {
testCopyLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(4, 3, 2, 1),
[
'first',
'second line',
'third line',
'fourth line',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(4, 3, 2, 1)
);
});
test('ignore empty selection', function () {
testCopyLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(2, 1, 1, 1),
[
'first',
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(2, 1, 1, 1)
);
});
});

View File

@@ -0,0 +1,194 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Selection } from 'vs/editor/common/core/selection';
import { DeleteLinesCommand } from 'vs/editor/contrib/linesOperations/common/deleteLinesCommand';
import { testCommand } from 'vs/editor/test/common/commands/commandTestUtils';
function testDeleteLinesCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, null, selection, (sel) => DeleteLinesCommand.createFromSelection(sel), expectedLines, expectedSelection);
}
suite('Editor Contrib - Delete Lines Command', () => {
test('empty selection in middle of lines', function () {
testDeleteLinesCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(2, 3, 2, 3),
[
'first',
'third line',
'fourth line',
'fifth'
],
new Selection(2, 3, 2, 3)
);
});
test('empty selection at top of lines', function () {
testDeleteLinesCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 5, 1, 5),
[
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 5, 1, 5)
);
});
test('empty selection at end of lines', function () {
testDeleteLinesCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(5, 2, 5, 2),
[
'first',
'second line',
'third line',
'fourth line'
],
new Selection(4, 2, 4, 2)
);
});
test('with selection in middle of lines', function () {
testDeleteLinesCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(3, 3, 2, 2),
[
'first',
'fourth line',
'fifth'
],
new Selection(2, 2, 2, 2)
);
});
test('with selection at top of lines', function () {
testDeleteLinesCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 4, 1, 5),
[
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 5, 1, 5)
);
});
test('with selection at end of lines', function () {
testDeleteLinesCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(5, 1, 5, 2),
[
'first',
'second line',
'third line',
'fourth line'
],
new Selection(4, 2, 4, 2)
);
});
test('with full line selection in middle of lines', function () {
testDeleteLinesCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(4, 1, 2, 1),
[
'first',
'fourth line',
'fifth'
],
new Selection(2, 1, 2, 1)
);
});
test('with full line selection at top of lines', function () {
testDeleteLinesCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(2, 1, 1, 5),
[
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 5, 1, 5)
);
});
test('with full line selection at end of lines', function () {
testDeleteLinesCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(4, 1, 5, 2),
[
'first',
'second line',
'third line'
],
new Selection(3, 2, 3, 2)
);
});
});

View File

@@ -0,0 +1,607 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { Selection } from 'vs/editor/common/core/selection';
import { Position } from 'vs/editor/common/core/position';
import { Handler, IModel, DefaultEndOfLine } from 'vs/editor/common/editorCommon';
import { withMockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor';
import { DeleteAllLeftAction, JoinLinesAction, TransposeAction, UpperCaseAction, LowerCaseAction, DeleteAllRightAction, InsertLineBeforeAction, InsertLineAfterAction, IndentLinesAction } from 'vs/editor/contrib/linesOperations/common/linesOperations';
import { Cursor } from 'vs/editor/common/controller/cursor';
import { Model } from 'vs/editor/common/model/model';
import { CoreEditingCommands } from 'vs/editor/common/controller/coreCommands';
suite('Editor Contrib - Line Operations', () => {
suite('DeleteAllLeftAction', () => {
test('should delete to the left of the cursor', function () {
withMockCodeEditor(
[
'one',
'two',
'three'
], {}, (editor, cursor) => {
let model = editor.getModel();
let deleteAllLeftAction = new DeleteAllLeftAction();
editor.setSelection(new Selection(1, 2, 1, 2));
deleteAllLeftAction.run(null, editor);
assert.equal(model.getLineContent(1), 'ne', '001');
editor.setSelections([new Selection(2, 2, 2, 2), new Selection(3, 2, 3, 2)]);
deleteAllLeftAction.run(null, editor);
assert.equal(model.getLineContent(2), 'wo', '002');
assert.equal(model.getLineContent(3), 'hree', '003');
});
});
test('should work in multi cursor mode', function () {
withMockCodeEditor(
[
'hello',
'world',
'hello world',
'hello',
'bonjour',
'hola',
'world',
'hello world',
], {}, (editor, cursor) => {
let model = editor.getModel();
let deleteAllLeftAction = new DeleteAllLeftAction();
editor.setSelections([new Selection(1, 2, 1, 2), new Selection(1, 4, 1, 4)]);
deleteAllLeftAction.run(null, editor);
assert.equal(model.getLineContent(1), 'lo', '001');
editor.setSelections([new Selection(2, 2, 2, 2), new Selection(2, 4, 2, 5)]);
deleteAllLeftAction.run(null, editor);
assert.equal(model.getLineContent(2), 'ord', '002');
editor.setSelections([new Selection(3, 2, 3, 5), new Selection(3, 7, 3, 7)]);
deleteAllLeftAction.run(null, editor);
assert.equal(model.getLineContent(3), 'world', '003');
editor.setSelections([new Selection(4, 3, 4, 3), new Selection(4, 5, 5, 4)]);
deleteAllLeftAction.run(null, editor);
assert.equal(model.getLineContent(4), 'lljour', '004');
editor.setSelections([new Selection(5, 3, 6, 3), new Selection(6, 5, 7, 5), new Selection(7, 7, 7, 7)]);
deleteAllLeftAction.run(null, editor);
assert.equal(model.getLineContent(5), 'horlworld', '005');
});
});
});
suite('JoinLinesAction', () => {
test('should join lines and insert space if necessary', function () {
withMockCodeEditor(
[
'hello',
'world',
'hello ',
'world',
'hello ',
' world',
'hello ',
' world',
'',
'',
'hello world'
], {}, (editor, cursor) => {
let model = editor.getModel();
let joinLinesAction = new JoinLinesAction();
editor.setSelection(new Selection(1, 2, 1, 2));
joinLinesAction.run(null, editor);
assert.equal(model.getLineContent(1), 'hello world', '001');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 6, 1, 6).toString(), '002');
editor.setSelection(new Selection(2, 2, 2, 2));
joinLinesAction.run(null, editor);
assert.equal(model.getLineContent(2), 'hello world', '003');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 7, 2, 7).toString(), '004');
editor.setSelection(new Selection(3, 2, 3, 2));
joinLinesAction.run(null, editor);
assert.equal(model.getLineContent(3), 'hello world', '005');
assert.deepEqual(editor.getSelection().toString(), new Selection(3, 7, 3, 7).toString(), '006');
editor.setSelection(new Selection(4, 2, 5, 3));
joinLinesAction.run(null, editor);
assert.equal(model.getLineContent(4), 'hello world', '007');
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 2, 4, 8).toString(), '008');
editor.setSelection(new Selection(5, 1, 7, 3));
joinLinesAction.run(null, editor);
assert.equal(model.getLineContent(5), 'hello world', '009');
assert.deepEqual(editor.getSelection().toString(), new Selection(5, 1, 5, 3).toString(), '010');
});
});
test('should work in multi cursor mode', function () {
withMockCodeEditor(
[
'hello',
'world',
'hello ',
'world',
'hello ',
' world',
'hello ',
' world',
'',
'',
'hello world'
], {}, (editor, cursor) => {
let model = editor.getModel();
let joinLinesAction = new JoinLinesAction();
editor.setSelections([
/** primary cursor */
new Selection(5, 2, 5, 2),
new Selection(1, 2, 1, 2),
new Selection(3, 2, 4, 2),
new Selection(5, 4, 6, 3),
new Selection(7, 5, 8, 4),
new Selection(10, 1, 10, 1)
]);
joinLinesAction.run(null, editor);
assert.equal(model.getLinesContent().join('\n'), 'hello world\nhello world\nhello world\nhello world\n\nhello world', '001');
assert.deepEqual(editor.getSelections().toString(), [
/** primary cursor */
new Selection(3, 4, 3, 8),
new Selection(1, 6, 1, 6),
new Selection(2, 2, 2, 8),
new Selection(4, 5, 4, 9),
new Selection(6, 1, 6, 1)
].toString(), '002');
/** primary cursor */
assert.deepEqual(editor.getSelection().toString(), new Selection(3, 4, 3, 8).toString(), '003');
});
});
});
test('transpose', function () {
withMockCodeEditor(
[
'hello world',
'',
'',
' ',
], {}, (editor, cursor) => {
let model = editor.getModel();
let transposeAction = new TransposeAction();
editor.setSelection(new Selection(1, 1, 1, 1));
transposeAction.run(null, editor);
assert.equal(model.getLineContent(1), 'hello world', '001');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 2, 1, 2).toString(), '002');
editor.setSelection(new Selection(1, 6, 1, 6));
transposeAction.run(null, editor);
assert.equal(model.getLineContent(1), 'hell oworld', '003');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 7, 1, 7).toString(), '004');
editor.setSelection(new Selection(1, 12, 1, 12));
transposeAction.run(null, editor);
assert.equal(model.getLineContent(1), 'hell oworl', '005');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 2, 2, 2).toString(), '006');
editor.setSelection(new Selection(3, 1, 3, 1));
transposeAction.run(null, editor);
assert.equal(model.getLineContent(3), '', '007');
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 1, 4, 1).toString(), '008');
editor.setSelection(new Selection(4, 2, 4, 2));
transposeAction.run(null, editor);
assert.equal(model.getLineContent(4), ' ', '009');
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 3, 4, 3).toString(), '010');
}
);
// fix #16633
withMockCodeEditor(
[
'',
'',
'hello',
'world',
'',
'hello world',
'',
'hello world'
], {}, (editor, cursor) => {
let model = editor.getModel();
let transposeAction = new TransposeAction();
editor.setSelection(new Selection(1, 1, 1, 1));
transposeAction.run(null, editor);
assert.equal(model.getLineContent(2), '', '011');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 1, 2, 1).toString(), '012');
editor.setSelection(new Selection(3, 6, 3, 6));
transposeAction.run(null, editor);
assert.equal(model.getLineContent(4), 'oworld', '013');
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 2, 4, 2).toString(), '014');
editor.setSelection(new Selection(6, 12, 6, 12));
transposeAction.run(null, editor);
assert.equal(model.getLineContent(7), 'd', '015');
assert.deepEqual(editor.getSelection().toString(), new Selection(7, 2, 7, 2).toString(), '016');
editor.setSelection(new Selection(8, 12, 8, 12));
transposeAction.run(null, editor);
assert.equal(model.getLineContent(8), 'hello world', '019');
assert.deepEqual(editor.getSelection().toString(), new Selection(8, 12, 8, 12).toString(), '020');
}
);
});
test('toggle case', function () {
withMockCodeEditor(
[
'hello world',
'öçşğü'
], {}, (editor, cursor) => {
let model = editor.getModel();
let uppercaseAction = new UpperCaseAction();
let lowercaseAction = new LowerCaseAction();
editor.setSelection(new Selection(1, 1, 1, 12));
uppercaseAction.run(null, editor);
assert.equal(model.getLineContent(1), 'HELLO WORLD', '001');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 12).toString(), '002');
editor.setSelection(new Selection(1, 1, 1, 12));
lowercaseAction.run(null, editor);
assert.equal(model.getLineContent(1), 'hello world', '003');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 12).toString(), '004');
editor.setSelection(new Selection(1, 3, 1, 3));
uppercaseAction.run(null, editor);
assert.equal(model.getLineContent(1), 'HELLO world', '005');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 3, 1, 3).toString(), '006');
editor.setSelection(new Selection(1, 4, 1, 4));
lowercaseAction.run(null, editor);
assert.equal(model.getLineContent(1), 'hello world', '007');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 4, 1, 4).toString(), '008');
editor.setSelection(new Selection(2, 1, 2, 6));
uppercaseAction.run(null, editor);
assert.equal(model.getLineContent(2), 'ÖÇŞĞÜ', '009');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 1, 2, 6).toString(), '010');
editor.setSelection(new Selection(2, 1, 2, 6));
lowercaseAction.run(null, editor);
assert.equal(model.getLineContent(2), 'öçşğü', '011');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 1, 2, 6).toString(), '012');
}
);
withMockCodeEditor(
[
'',
' '
], {}, (editor, cursor) => {
let model = editor.getModel();
let uppercaseAction = new UpperCaseAction();
let lowercaseAction = new LowerCaseAction();
editor.setSelection(new Selection(1, 1, 1, 1));
uppercaseAction.run(null, editor);
assert.equal(model.getLineContent(1), '', '013');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 1).toString(), '014');
editor.setSelection(new Selection(1, 1, 1, 1));
lowercaseAction.run(null, editor);
assert.equal(model.getLineContent(1), '', '015');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 1).toString(), '016');
editor.setSelection(new Selection(2, 2, 2, 2));
uppercaseAction.run(null, editor);
assert.equal(model.getLineContent(2), ' ', '017');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 2, 2, 2).toString(), '018');
editor.setSelection(new Selection(2, 2, 2, 2));
lowercaseAction.run(null, editor);
assert.equal(model.getLineContent(2), ' ', '019');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 2, 2, 2).toString(), '020');
}
);
});
suite('DeleteAllRightAction', () => {
test('should be noop on empty', () => {
withMockCodeEditor([''], {}, (editor, cursor) => {
const model = editor.getModel();
const action = new DeleteAllRightAction();
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
editor.setSelection(new Selection(1, 1, 1, 1));
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(1, 1, 1, 1), new Selection(1, 1, 1, 1)]);
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
});
});
test('should delete selected range', () => {
withMockCodeEditor([
'hello',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel();
const action = new DeleteAllRightAction();
editor.setSelection(new Selection(1, 2, 1, 5));
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['ho', 'world']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 2, 1, 2)]);
editor.setSelection(new Selection(1, 1, 2, 4));
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['ld']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
editor.setSelection(new Selection(1, 1, 1, 3));
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
});
});
test('should delete to the right of the cursor', () => {
withMockCodeEditor([
'hello',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel();
const action = new DeleteAllRightAction();
editor.setSelection(new Selection(1, 3, 1, 3));
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['he', 'world']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 3, 1, 3)]);
editor.setSelection(new Selection(2, 1, 2, 1));
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['he', '']);
assert.deepEqual(editor.getSelections(), [new Selection(2, 1, 2, 1)]);
});
});
test('should join two lines, if at the end of the line', () => {
withMockCodeEditor([
'hello',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel();
const action = new DeleteAllRightAction();
editor.setSelection(new Selection(1, 6, 1, 6));
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['helloworld']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
editor.setSelection(new Selection(1, 6, 1, 6));
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['hello']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
editor.setSelection(new Selection(1, 6, 1, 6));
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['hello']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
});
});
test('should work with multiple cursors', () => {
withMockCodeEditor([
'hello',
'there',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel();
const action = new DeleteAllRightAction();
editor.setSelections([
new Selection(1, 3, 1, 3),
new Selection(1, 6, 1, 6),
new Selection(3, 4, 3, 4),
]);
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['hethere', 'wor']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(2, 4, 2, 4)
]);
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['he', 'wor']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(2, 4, 2, 4)
]);
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['hewor']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(1, 6, 1, 6)
]);
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['he']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3)
]);
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['he']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3)
]);
});
});
test('should work with undo/redo', () => {
withMockCodeEditor([
'hello',
'there',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel();
const action = new DeleteAllRightAction();
editor.setSelections([
new Selection(1, 3, 1, 3),
new Selection(1, 6, 1, 6),
new Selection(3, 4, 3, 4),
]);
action.run(null, editor);
assert.deepEqual(model.getLinesContent(), ['hethere', 'wor']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(2, 4, 2, 4)
]);
editor.trigger('tests', Handler.Undo, {});
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(1, 6, 1, 6),
new Selection(3, 4, 3, 4)
]);
editor.trigger('tests', Handler.Redo, {});
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(2, 4, 2, 4)
]);
});
});
});
test('InsertLineBeforeAction', function () {
function testInsertLineBefore(lineNumber: number, column: number, callback: (model: IModel, cursor: Cursor) => void): void {
const TEXT = [
'First line',
'Second line',
'Third line'
];
withMockCodeEditor(TEXT, {}, (editor, cursor) => {
editor.setPosition(new Position(lineNumber, column));
let insertLineBeforeAction = new InsertLineBeforeAction();
insertLineBeforeAction.run(null, editor);
callback(editor.getModel(), cursor);
});
}
testInsertLineBefore(1, 3, (model, cursor) => {
assert.deepEqual(cursor.getSelection(), new Selection(1, 1, 1, 1));
assert.equal(model.getLineContent(1), '');
assert.equal(model.getLineContent(2), 'First line');
assert.equal(model.getLineContent(3), 'Second line');
assert.equal(model.getLineContent(4), 'Third line');
});
testInsertLineBefore(2, 3, (model, cursor) => {
assert.deepEqual(cursor.getSelection(), new Selection(2, 1, 2, 1));
assert.equal(model.getLineContent(1), 'First line');
assert.equal(model.getLineContent(2), '');
assert.equal(model.getLineContent(3), 'Second line');
assert.equal(model.getLineContent(4), 'Third line');
});
testInsertLineBefore(3, 3, (model, cursor) => {
assert.deepEqual(cursor.getSelection(), new Selection(3, 1, 3, 1));
assert.equal(model.getLineContent(1), 'First line');
assert.equal(model.getLineContent(2), 'Second line');
assert.equal(model.getLineContent(3), '');
assert.equal(model.getLineContent(4), 'Third line');
});
});
test('InsertLineAfterAction', () => {
function testInsertLineAfter(lineNumber: number, column: number, callback: (model: IModel, cursor: Cursor) => void): void {
const TEXT = [
'First line',
'Second line',
'Third line'
];
withMockCodeEditor(TEXT, {}, (editor, cursor) => {
editor.setPosition(new Position(lineNumber, column));
let insertLineAfterAction = new InsertLineAfterAction();
insertLineAfterAction.run(null, editor);
callback(editor.getModel(), cursor);
});
}
testInsertLineAfter(1, 3, (model, cursor) => {
assert.deepEqual(cursor.getSelection(), new Selection(2, 1, 2, 1));
assert.equal(model.getLineContent(1), 'First line');
assert.equal(model.getLineContent(2), '');
assert.equal(model.getLineContent(3), 'Second line');
assert.equal(model.getLineContent(4), 'Third line');
});
testInsertLineAfter(2, 3, (model, cursor) => {
assert.deepEqual(cursor.getSelection(), new Selection(3, 1, 3, 1));
assert.equal(model.getLineContent(1), 'First line');
assert.equal(model.getLineContent(2), 'Second line');
assert.equal(model.getLineContent(3), '');
assert.equal(model.getLineContent(4), 'Third line');
});
testInsertLineAfter(3, 3, (model, cursor) => {
assert.deepEqual(cursor.getSelection(), new Selection(4, 1, 4, 1));
assert.equal(model.getLineContent(1), 'First line');
assert.equal(model.getLineContent(2), 'Second line');
assert.equal(model.getLineContent(3), 'Third line');
assert.equal(model.getLineContent(4), '');
});
});
test('Bug 18276:[editor] Indentation broken when selection is empty', () => {
let model = Model.createFromString(
[
'function baz() {'
].join('\n'),
{
defaultEOL: DefaultEndOfLine.LF,
detectIndentation: false,
insertSpaces: false,
tabSize: 4,
trimAutoWhitespace: true
}
);
withMockCodeEditor(null, { model: model }, (editor, cursor) => {
let indentLinesAction = new IndentLinesAction();
editor.setPosition(new Position(1, 2));
indentLinesAction.run(null, editor);
assert.equal(model.getLineContent(1), '\tfunction baz() {');
assert.deepEqual(editor.getSelection(), new Selection(1, 3, 1, 3));
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), '\tf\tunction baz() {');
});
model.dispose();
});
});

View File

@@ -0,0 +1,353 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Selection } from 'vs/editor/common/core/selection';
import { MoveLinesCommand } from 'vs/editor/contrib/linesOperations/common/moveLinesCommand';
import { testCommand } from 'vs/editor/test/common/commands/commandTestUtils';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { IndentationRule } from 'vs/editor/common/modes/languageConfiguration';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
function testMoveLinesDownCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, true, false), expectedLines, expectedSelection);
}
function testMoveLinesUpCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, false, false), expectedLines, expectedSelection);
}
function testMoveLinesDownWithIndentCommand(languageId: LanguageIdentifier, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, true, true), expectedLines, expectedSelection);
}
function testMoveLinesUpWithIndentCommand(languageId: LanguageIdentifier, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, false, true), expectedLines, expectedSelection);
}
suite('Editor Contrib - Move Lines Command', () => {
test('move first up / last down disabled', function () {
testMoveLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 1, 1, 1),
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 1, 1, 1)
);
testMoveLinesDownCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(5, 1, 5, 1),
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(5, 1, 5, 1)
);
});
test('move first line down', function () {
testMoveLinesDownCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 4, 1, 1),
[
'second line',
'first',
'third line',
'fourth line',
'fifth'
],
new Selection(2, 4, 2, 1)
);
});
test('move 2nd line up', function () {
testMoveLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(2, 1, 2, 1),
[
'second line',
'first',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 1, 1, 1)
);
});
test('issue #1322a: move 2nd line up', function () {
testMoveLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(2, 12, 2, 12),
[
'second line',
'first',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 12, 1, 12)
);
});
test('issue #1322b: move last line up', function () {
testMoveLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(5, 6, 5, 6),
[
'first',
'second line',
'third line',
'fifth',
'fourth line'
],
new Selection(4, 6, 4, 6)
);
});
test('issue #1322c: move last line selected up', function () {
testMoveLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(5, 6, 5, 1),
[
'first',
'second line',
'third line',
'fifth',
'fourth line'
],
new Selection(4, 6, 4, 1)
);
});
test('move last line up', function () {
testMoveLinesUpCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(5, 1, 5, 1),
[
'first',
'second line',
'third line',
'fifth',
'fourth line'
],
new Selection(4, 1, 4, 1)
);
});
test('move 4th line down', function () {
testMoveLinesDownCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(4, 1, 4, 1),
[
'first',
'second line',
'third line',
'fifth',
'fourth line'
],
new Selection(5, 1, 5, 1)
);
});
test('move multiple lines down', function () {
testMoveLinesDownCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(4, 4, 2, 2),
[
'first',
'fifth',
'second line',
'third line',
'fourth line'
],
new Selection(5, 4, 3, 2)
);
});
test('invisible selection is ignored', function () {
testMoveLinesDownCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(2, 1, 1, 1),
[
'second line',
'first',
'third line',
'fourth line',
'fifth'
],
new Selection(3, 1, 2, 1)
);
});
});
class IndentRulesMode extends MockMode {
private static _id = new LanguageIdentifier('moveLinesIndentMode', 7);
constructor(indentationRules: IndentationRule) {
super(IndentRulesMode._id);
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
indentationRules: indentationRules
}));
}
}
suite('Editor contrib - Move Lines Command honors Indentation Rules', () => {
let indentRules = {
decreaseIndentPattern: /^\s*((?!\S.*\/[*]).*[*]\/\s*)?[})\]]|^\s*(case\b.*|default):\s*(\/\/.*|\/[*].*[*]\/\s*)?$/,
increaseIndentPattern: /(\{[^}"'`]*|\([^)"']*|\[[^\]"']*|^\s*(\{\}|\(\)|\[\]|(case\b.*|default):))\s*(\/\/.*|\/[*].*[*]\/\s*)?$/,
indentNextLinePattern: /^\s*(for|while|if|else)\b(?!.*[;{}]\s*(\/\/.*|\/[*].*[*]\/\s*)?$)/,
unIndentedLinePattern: /^(?!.*([;{}]|\S:)\s*(\/\/.*|\/[*].*[*]\/\s*)?$)(?!.*(\{[^}"']*|\([^)"']*|\[[^\]"']*|^\s*(\{\}|\(\)|\[\]|(case\b.*|default):))\s*(\/\/.*|\/[*].*[*]\/\s*)?$)(?!^\s*((?!\S.*\/[*]).*[*]\/\s*)?[})\]]|^\s*(case\b.*|default):\s*(\/\/.*|\/[*].*[*]\/\s*)?$)(?!^\s*(for|while|if|else)\b(?!.*[;{}]\s*(\/\/.*|\/[*].*[*]\/\s*)?$))/
};
// https://github.com/Microsoft/vscode/issues/28552#issuecomment-307862797
test('first line indentation adjust to 0', () => {
let mode = new IndentRulesMode(indentRules);
testMoveLinesUpWithIndentCommand(
mode.getLanguageIdentifier(),
[
'class X {',
'\tz = 2',
'}'
],
new Selection(2, 1, 2, 1),
[
'z = 2',
'class X {',
'}'
],
new Selection(1, 1, 1, 1)
);
mode.dispose();
});
// https://github.com/Microsoft/vscode/issues/28552#issuecomment-307867717
test('move lines across block', () => {
let mode = new IndentRulesMode(indentRules);
testMoveLinesDownWithIndentCommand(
mode.getLanguageIdentifier(),
[
'const value = 2;',
'const standardLanguageDescriptions = [',
' {',
' diagnosticSource: \'js\',',
' }',
'];'
],
new Selection(1, 1, 1, 1),
[
'const standardLanguageDescriptions = [',
' const value = 2;',
' {',
' diagnosticSource: \'js\',',
' }',
'];'
],
new Selection(2, 5, 2, 5)
);
mode.dispose();
});
test('move line should still work as before if there is no indentation rules', () => {
testMoveLinesUpWithIndentCommand(
null,
[
'if (true) {',
' var task = new Task(() => {',
' var work = 1234;',
' });',
'}'
],
new Selection(3, 1, 3, 1),
[
'if (true) {',
' var work = 1234;',
' var task = new Task(() => {',
' });',
'}'
],
new Selection(2, 1, 2, 1)
);
});
});

View File

@@ -0,0 +1,167 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Selection } from 'vs/editor/common/core/selection';
import { SortLinesCommand } from 'vs/editor/contrib/linesOperations/common/sortLinesCommand';
import { testCommand } from 'vs/editor/test/common/commands/commandTestUtils';
function testSortLinesAscendingCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, null, selection, (sel) => new SortLinesCommand(sel, false), expectedLines, expectedSelection);
}
function testSortLinesDescendingCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, null, selection, (sel) => new SortLinesCommand(sel, true), expectedLines, expectedSelection);
}
suite('Editor Contrib - Sort Lines Command', () => {
test('no op unless at least two lines selected 1', function () {
testSortLinesAscendingCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 3, 1, 1),
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 3, 1, 1)
);
});
test('no op unless at least two lines selected 2', function () {
testSortLinesAscendingCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 3, 2, 1),
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 3, 2, 1)
);
});
test('sorting two lines ascending', function () {
testSortLinesAscendingCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(3, 3, 4, 2),
[
'first',
'second line',
'fourth line',
'third line',
'fifth'
],
new Selection(3, 3, 4, 2)
);
});
test('sorting first 4 lines ascending', function () {
testSortLinesAscendingCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 1, 5, 1),
[
'first',
'fourth line',
'second line',
'third line',
'fifth'
],
new Selection(1, 1, 5, 1)
);
});
test('sorting all lines ascending', function () {
testSortLinesAscendingCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 1, 5, 6),
[
'fifth',
'first',
'fourth line',
'second line',
'third line',
],
new Selection(1, 1, 5, 6)
);
});
test('sorting first 4 lines desscending', function () {
testSortLinesDescendingCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 1, 5, 1),
[
'third line',
'second line',
'fourth line',
'first',
'fifth'
],
new Selection(1, 1, 5, 1)
);
});
test('sorting all lines descending', function () {
testSortLinesDescendingCommand(
[
'first',
'second line',
'third line',
'fourth line',
'fifth'
],
new Selection(1, 1, 5, 6),
[
'third line',
'second line',
'fourth line',
'first',
'fifth',
],
new Selection(1, 1, 5, 6)
);
});
});