diff --git a/src/vs/editor/contrib/linesOperations/common/linesOperations.ts b/src/vs/editor/contrib/linesOperations/common/linesOperations.ts index 3ebab02652f..933212cb321 100644 --- a/src/vs/editor/contrib/linesOperations/common/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/common/linesOperations.ts @@ -7,6 +7,7 @@ import * as nls from 'vs/nls'; import {KeyCode, KeyMod} from 'vs/base/common/keyCodes'; import {TPromise} from 'vs/base/common/winjs.base'; +import {SortLinesCommand} from 'vs/editor/contrib/linesOperations/common/sortLinesCommand'; import {TrimTrailingWhitespaceCommand} from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; import {EditorAction, HandlerEditorAction} from 'vs/editor/common/editorAction'; import {Handler, ICommand, ICommonCodeEditor, IEditorActionDescriptorData} from 'vs/editor/common/editorCommon'; @@ -100,6 +101,40 @@ class MoveLinesDownAction extends MoveLinesAction { } } +class SortLinesAction extends EditorAction { + private descending:boolean; + + constructor(descriptor:IEditorActionDescriptorData, editor:ICommonCodeEditor, descending:boolean) { + super(descriptor, editor); + this.descending = descending; + } + + public run():TPromise { + + var command = new SortLinesCommand(this.editor.getSelection(), this.descending); + + this.editor.executeCommands(this.id, [command]); + + return TPromise.as(true); + } +} + +class SortLinesAscendingAction extends SortLinesAction { + static ID ='editor.action.sortLinesAscending'; + + constructor(descriptor:IEditorActionDescriptorData, editor:ICommonCodeEditor) { + super(descriptor, editor, false); + } +} + +class SortLinesDescendingAction extends SortLinesAction { + static ID ='editor.action.sortLinesDescending'; + + constructor(descriptor:IEditorActionDescriptorData, editor:ICommonCodeEditor) { + super(descriptor, editor, true); + } +} + class TrimTrailingWhitespaceAction extends EditorAction { static ID = 'editor.action.trimTrailingWhitespace'; @@ -234,6 +269,14 @@ CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(DeleteLines context: ContextKey.EditorTextFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_K })); +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(SortLinesAscendingAction, SortLinesAscendingAction.ID, nls.localize('lines.sortAscending', "Sort Lines Ascending"), { + context: ContextKey.EditorTextFocus, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_2 +})); +CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(SortLinesDescendingAction, SortLinesDescendingAction.ID, nls.localize('lines.sortDescending', "Sort Lines Descending"), { + context: ContextKey.EditorTextFocus, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_3 +})); CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(TrimTrailingWhitespaceAction, TrimTrailingWhitespaceAction.ID, nls.localize('lines.trimTrailingWhitespace', "Trim Trailing Whitespace"), { context: ContextKey.EditorTextFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X @@ -274,5 +317,3 @@ CommonEditorRegistry.registerEditorAction(new EditorActionDescriptor(InsertLineA context: ContextKey.EditorTextFocus, primary: KeyMod.CtrlCmd | KeyCode.Enter })); - - diff --git a/src/vs/editor/contrib/linesOperations/common/sortLinesCommand.ts b/src/vs/editor/contrib/linesOperations/common/sortLinesCommand.ts new file mode 100644 index 00000000000..0d0f5156f69 --- /dev/null +++ b/src/vs/editor/contrib/linesOperations/common/sortLinesCommand.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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'; + +export class SortLinesCommand implements editorCommon.ICommand { + + private selection:editorCommon.IEditorSelection; + private selectionId:string; + private descending:boolean; + + constructor(selection:editorCommon.IEditorSelection, 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):editorCommon.IEditorSelection { + return helper.getTrackedSelection(this.selectionId); + } +} + +/** + * Generate commands for sorting lines on a model. + */ +export function sortLines(model:editorCommon.ITextModel, selection:editorCommon.IEditorSelection, descending:boolean): editorCommon.IIdentifiedSingleEditOperation { + 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.sort((a, b) => { + return a.toLowerCase().localeCompare(b.toLowerCase()); + }); + + // If descending, reverse the order. + if (descending === true) { + sorted = sorted.reverse(); + } + + return EditOperation.replace( + new Range(startLineNumber, 1, endLineNumber, model.getLineMaxColumn(endLineNumber)), + sorted.join('\n') + ); +} diff --git a/src/vs/editor/contrib/linesOperations/test/common/sortLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/common/sortLinesCommand.test.ts new file mode 100644 index 00000000000..3456ab97d94 --- /dev/null +++ b/src/vs/editor/contrib/linesOperations/test/common/sortLinesCommand.test.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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) + ); + }); +});