Skip to content

Commit

Permalink
fixes #144
Browse files Browse the repository at this point in the history
  • Loading branch information
alexdima committed Nov 24, 2015
1 parent 991b63f commit 616b9ad
Show file tree
Hide file tree
Showing 3 changed files with 363 additions and 29 deletions.
62 changes: 33 additions & 29 deletions src/vs/editor/contrib/format/common/formatCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ export class EditOperationsCommand implements EditorCommon.ICommand {
public getEditOperations(model: EditorCommon.ITokenizedModel, builder: EditorCommon.IEditOperationBuilder): void {
this._edits
// We know that this edit.range comes from the mirror model, so it should only contain \n and no \r's
.map((edit) => this.fixLineTerminators(edit, model) )
.map((edit) => this.trimEdit(edit, model))
.map((edit) => EditOperationsCommand.trimEdit(edit, model))
.filter((edit) => edit !== null) // produced above in case the edit.text is identical to the existing text
.forEach((edit) => builder.addEditOperation(Range.lift(edit.range), edit.text));

Expand All @@ -50,9 +49,8 @@ export class EditOperationsCommand implements EditorCommon.ICommand {
return helper.getTrackedSelection(this._selectionId);
}

private fixLineTerminators(edit: EditorCommon.ISingleEditOperation, model: EditorCommon.ITokenizedModel) : EditorCommon.ISingleEditOperation {
static fixLineTerminators(edit: EditorCommon.ISingleEditOperation, model: EditorCommon.ITokenizedModel): void {
edit.text = edit.text.replace(/\r\n|\r|\n/g, model.getEOL());
return edit;
}

/**
Expand All @@ -63,42 +61,48 @@ export class EditOperationsCommand implements EditorCommon.ICommand {
* bug #15108. There the cursor was jumping since the tracked selection was in the middle of the range edit
* and was lost.
*/
private trimEdit(edit:EditorCommon.ISingleEditOperation, model: EditorCommon.ITokenizedModel): EditorCommon.ISingleEditOperation {
static trimEdit(edit:EditorCommon.ISingleEditOperation, model: EditorCommon.ITokenizedModel): EditorCommon.ISingleEditOperation {

var currentText = model.getValueInRange(edit.range);
this.fixLineTerminators(edit, model);

return this._trimEdit(model.validateRange(edit.range), edit.text, edit.forceMoveMarkers, model);
}

static _trimEdit(editRange:Range, editText:string, editForceMoveMarkers:boolean, model: EditorCommon.ITokenizedModel): EditorCommon.ISingleEditOperation {

let currentText = model.getValueInRange(editRange);

// Find the equal characters in the front
var commonPrefixLength = Strings.commonPrefixLength(edit.text, currentText);
let commonPrefixLength = Strings.commonPrefixLength(editText, currentText);

// If the two strings are identical, return no edit
if (commonPrefixLength === currentText.length && commonPrefixLength === edit.text.length) {
// If the two strings are identical, return no edit (no-op)
if (commonPrefixLength === currentText.length && commonPrefixLength === editText.length) {
return null;
}

// Only compute a common suffix if none of the strings is already fully contained in the prefix
var commonSuffixLength = 0;
if (commonPrefixLength !== currentText.length && commonPrefixLength !== edit.text.length) {
commonSuffixLength = Strings.commonSuffixLength(edit.text, currentText);
if (commonPrefixLength > 0) {
// Apply front trimming
let newStartPosition = model.modifyPosition(editRange.getStartPosition(), commonPrefixLength);
editRange = new Range(newStartPosition.lineNumber, newStartPosition.column, editRange.endLineNumber, editRange.endColumn);
editText = editText.substring(commonPrefixLength);
currentText = currentText.substr(commonPrefixLength);
}

// Adjust start position
var newStartPosition = new Position(edit.range.startLineNumber, edit.range.startColumn);
newStartPosition = model.modifyPosition(newStartPosition, commonPrefixLength);

// Adjust end position
var newEndPosition = new Position(edit.range.endLineNumber, edit.range.endColumn);
newEndPosition = model.modifyPosition(newEndPosition, -commonSuffixLength);
// Find the equal characters in the rear
let commonSuffixLength = Strings.commonSuffixLength(editText, currentText);

//Trim the text
var newText = edit.text.slice(commonPrefixLength, edit.text.length - commonSuffixLength);
if (commonSuffixLength > 0) {
// Apply rear trimming
let newEndPosition = model.modifyPosition(editRange.getEndPosition(), -commonSuffixLength);
editRange = new Range(editRange.startLineNumber, editRange.startColumn, newEndPosition.lineNumber, newEndPosition.column);
editText = editText.substring(0, editText.length - commonSuffixLength);
currentText = currentText.substring(0, currentText.length - commonSuffixLength);
}

return {
text: newText,
range: {
startLineNumber:newStartPosition.lineNumber,
startColumn:newStartPosition.column,
endLineNumber:newEndPosition.lineNumber,
endColumn: newEndPosition.column
}};
text: editText,
range: editRange,
forceMoveMarkers: editForceMoveMarkers
};
}
}
295 changes: 295 additions & 0 deletions src/vs/editor/contrib/format/test/common/formatCommand.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
/*---------------------------------------------------------------------------------------------
* 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 assert = require('assert');
import {Range} from 'vs/editor/common/core/range';
import TU = require('vs/editor/test/common/commands/commandTestUtils');
import {EditOperationsCommand} from 'vs/editor/contrib/format/common/formatCommand';
import {Selection} from 'vs/editor/common/core/selection';
import {Model} from 'vs/editor/common/model/model';
import EditorCommon = require('vs/editor/common/editorCommon');

function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text:string[]): EditorCommon.ISingleEditOperation {
return {
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
text: text.join('\n'),
forceMoveMarkers: false
};
}

suite('FormatCommand.trimEdit', () => {
function testTrimEdit(lines: string[], edit:EditorCommon.ISingleEditOperation, expected:EditorCommon.ISingleEditOperation): void {
let model = new Model(lines.join('\n'), null);
let actual = EditOperationsCommand.trimEdit(edit, model);
assert.deepEqual(actual, expected);
model.dispose();
}

test('single-line no-op', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1,1,1,10, [
'some text'
]),
null
)
});

test('multi-line no-op 1', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1,1,2,16, [
'some text',
'some other text'
]),
null
)
});

test('multi-line no-op 2', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1,1,2,1, [
'some text',
''
]),
null
)
});

test('simple prefix, no suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1,1,1,10, [
'some interesting thing'
]),
editOp(1,6,1,10, [
'interesting thing'
])
)
});

test('whole line prefix, no suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1,1,1,10, [
'some text',
'interesting thing'
]),
editOp(1,10,1,10, [
'',
'interesting thing'
])
)
});

test('multi-line prefix, no suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1,1,2,16, [
'some text',
'some other interesting thing'
]),
editOp(2,12,2,16, [
'interesting thing'
])
)
});

test('no prefix, simple suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1,1,1,10, [
'interesting text'
]),
editOp(1,1,1,5, [
'interesting'
])
)
});

test('no prefix, whole line suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1,1,1,10, [
'interesting thing',
'some text'
]),
editOp(1,1,1,1, [
'interesting thing',
''
])
)
});

test('no prefix, multi-line suffix', () => {
testTrimEdit(
[
'some text',
'some other text'
],
editOp(1,1,2,16, [
'interesting thing text',
'some other text'
]),
editOp(1,1,1,5, [
'interesting thing'
])
)
});

test('no overlapping prefix & suffix', () => {
testTrimEdit(
[
'some cool text'
],
editOp(1,1,1,15, [
'some interesting text'
]),
editOp(1,6,1,10, [
'interesting'
])
)
});

test('overlapping prefix & suffix 1', () => {
testTrimEdit(
[
'some cool text'
],
editOp(1,1,1,15, [
'some cool cool text'
]),
editOp(1,11,1,11, [
'cool '
])
)
});

test('overlapping prefix & suffix 2', () => {
testTrimEdit(
[
'some cool cool text'
],
editOp(1,1,1,29, [
'some cool text'
]),
editOp(1,11,1,16, [
''
])
)
});
});

suite('FormatCommand', () => {
function testFormatCommand(lines: string[], selection: Selection, edits:EditorCommon.ISingleEditOperation[], expectedLines: string[], expectedSelection: Selection): void {
TU.testCommand(lines, null, selection, (sel) => new EditOperationsCommand(edits, sel), expectedLines, expectedSelection);
}

test('no-op', () => {
testFormatCommand(
[
'some text',
'some other text'
],
new Selection(2,1,2,5),
[
editOp(1, 1, 2, 16, [
'some text',
'some other text'
])
],
[
'some text',
'some other text'
],
new Selection(2,1,2,5)
);
});

test('trim beginning', () => {
testFormatCommand(
[
'some text',
'some other text'
],
new Selection(2,1,2,5),
[
editOp(1, 1, 2, 16, [
'some text',
'some new other text'
])
],
[
'some text',
'some new other text'
],
new Selection(2,1,2,5)
);
});

test('issue #144', () => {
testFormatCommand(
[
'package caddy',
'',
'func main() {',
'\tfmt.Println("Hello World! :)")',
'}',
''
],
new Selection(1,1,1,1),
[
editOp(1, 1, 6, 1, [
'package caddy',
'',
'import "fmt"',
'',
'func main() {',
'\tfmt.Println("Hello World! :)")',
'}',
''
])
],
[
'package caddy',
'',
'import "fmt"',
'',
'func main() {',
'\tfmt.Println("Hello World! :)")',
'}',
''
],
new Selection(1,1,1,1)
);
});

});
Loading

0 comments on commit 616b9ad

Please sign in to comment.