From 6b515aea3244d547c15f5b2e2ea3fe31d3897a4e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Oct 2016 12:34:36 +0200 Subject: [PATCH] stable tabstop at snippet line start, fixes #11890 --- .../editor/contrib/snippet/common/snippet.ts | 52 ++++++++++------- .../snippet/test/common/snippet.test.ts | 58 +++++++++++++++++++ .../test/common/snippetController.test.ts | 24 ++++++++ 3 files changed, 113 insertions(+), 21 deletions(-) diff --git a/src/vs/editor/contrib/snippet/common/snippet.ts b/src/vs/editor/contrib/snippet/common/snippet.ts index 6bfbd8ba6e7..4245228ea5d 100644 --- a/src/vs/editor/contrib/snippet/common/snippet.ts +++ b/src/vs/editor/contrib/snippet/common/snippet.ts @@ -60,43 +60,53 @@ export class CodeSnippet implements ICodeSnippet { } public bind(referenceLine: string, deltaLine: number, firstLineDeltaColumn: number, config: IIndentationNormalizer): ICodeSnippet { - var resultLines: string[] = []; - var resultPlaceHolders: IPlaceHolder[] = []; + const resultLines: string[] = []; + const resultPlaceHolders: IPlaceHolder[] = []; - var referenceIndentation = this.extractLineIndentation(referenceLine, firstLineDeltaColumn + 1); - var originalLine: string, originalLineIndentation: string, remainingLine: string, indentation: string; - var i: number, len: number, j: number, lenJ: number; + const referenceIndentation = this.extractLineIndentation(referenceLine, firstLineDeltaColumn + 1); // Compute resultLines & keep deltaColumns as a reference for adjusting placeholders - var deltaColumns: number[] = []; - for (i = 0, len = this.lines.length; i < len; i++) { - originalLine = this.lines[i]; + const deltaColumns: number[] = []; + + for (let i = 0, len = this.lines.length; i < len; i++) { + let originalLine = this.lines[i]; if (i === 0) { deltaColumns[i + 1] = firstLineDeltaColumn; resultLines[i] = originalLine; } else { - originalLineIndentation = this.extractLineIndentation(originalLine); - remainingLine = originalLine.substr(originalLineIndentation.length); - indentation = config.normalizeIndentation(referenceIndentation + originalLineIndentation); + let originalLineIndentation = this.extractLineIndentation(originalLine); + let remainingLine = originalLine.substr(originalLineIndentation.length); + let indentation = config.normalizeIndentation(referenceIndentation + originalLineIndentation); deltaColumns[i + 1] = indentation.length - originalLineIndentation.length; resultLines[i] = indentation + remainingLine; } } // Compute resultPlaceHolders - var originalPlaceHolder: IPlaceHolder, originalOccurence: editorCommon.IRange, resultOccurences: editorCommon.IRange[]; - for (i = 0, len = this.placeHolders.length; i < len; i++) { - originalPlaceHolder = this.placeHolders[i]; + for (const originalPlaceHolder of this.placeHolders) { + let resultOccurences = []; - resultOccurences = []; - for (j = 0, lenJ = originalPlaceHolder.occurences.length; j < lenJ; j++) { - originalOccurence = originalPlaceHolder.occurences[j]; + for (let {startLineNumber, startColumn, endLineNumber, endColumn} of originalPlaceHolder.occurences) { + + if (startColumn > 1) { + // placeholders that aren't at the beginning of the snippet line + // will be moved by how many characters the indentation has been + // adjusted + startColumn = startColumn + deltaColumns[startLineNumber]; + endColumn = endColumn + deltaColumns[endLineNumber]; + + } else { + // placeholders at the beginning of the snippet line + // will be indented by the reference indentation + startColumn += referenceIndentation.length; + endColumn += referenceIndentation.length; + } resultOccurences.push({ - startLineNumber: originalOccurence.startLineNumber + deltaLine, - startColumn: originalOccurence.startColumn + deltaColumns[originalOccurence.startLineNumber], - endLineNumber: originalOccurence.endLineNumber + deltaLine, - endColumn: originalOccurence.endColumn + deltaColumns[originalOccurence.endLineNumber] + startLineNumber: startLineNumber + deltaLine, + startColumn, + endLineNumber: endLineNumber + deltaLine, + endColumn }); } diff --git a/src/vs/editor/contrib/snippet/test/common/snippet.test.ts b/src/vs/editor/contrib/snippet/test/common/snippet.test.ts index b18834b9b0b..cdb521c40e6 100644 --- a/src/vs/editor/contrib/snippet/test/common/snippet.test.ts +++ b/src/vs/editor/contrib/snippet/test/common/snippet.test.ts @@ -179,5 +179,63 @@ suite('Editor Contrib - Snippets', () => { assert.equal(snippet.placeHolders.length, 0); assert.deepEqual(snippet.lines, ['', '$scope.$broadcast(\'scroll.infiniteScrollComplete\');', '']); }); + + test('bind, adjust indentation', () => { + + // don't move placeholder at the beginning of the line + let snippet = CodeSnippet.fromTextmate([ + 'afterEach((done) => {', + '\t${1}test${2}', + '})' + ].join('\n')); + + // replace tab-stop with two spaces + let boundSnippet = snippet.bind('', 0, 0, { + normalizeIndentation(str: string): string { + return str.replace(/\t/g, ' '); + } + }); + let [first, second] = boundSnippet.placeHolders; + assert.equal(first.occurences.length, 1); + assert.equal(first.occurences[0].startColumn, 3); + assert.equal(second.occurences.length, 1); + assert.equal(second.occurences[0].startColumn, 7); + + // keep tab-stop, identity + boundSnippet = snippet.bind('', 0, 0, { + normalizeIndentation(str: string): string { + return str; + } + }); + [first, second] = boundSnippet.placeHolders; + assert.equal(first.occurences.length, 1); + assert.equal(first.occurences[0].startColumn, 2); + assert.equal(second.occurences.length, 1); + assert.equal(second.occurences[0].startColumn, 6); + }); + + + test('issue #11890: Bad cursor position', () => { + + let snippet = CodeSnippet.fromTextmate([ + 'afterEach((done) => {', + '${1}\ttest${2}', + '})' + ].join('\n')); + + let boundSnippet = snippet.bind('', 0, 0, { + normalizeIndentation(str: string): string { + return str.replace(/\t/g, ' '); + } + }); + + assert.equal(boundSnippet.lines[1], ' test'); + assert.equal(boundSnippet.placeHolders.length, 2); + let [first, second] = boundSnippet.placeHolders; + assert.equal(first.occurences.length, 1); + assert.equal(first.occurences[0].startColumn, 1); + assert.equal(second.occurences.length, 1); + assert.equal(second.occurences[0].startColumn, 7); + }); }); diff --git a/src/vs/editor/contrib/snippet/test/common/snippetController.test.ts b/src/vs/editor/contrib/snippet/test/common/snippetController.test.ts index 5f9eb9a3647..e405b6ae79f 100644 --- a/src/vs/editor/contrib/snippet/test/common/snippetController.test.ts +++ b/src/vs/editor/contrib/snippet/test/common/snippetController.test.ts @@ -408,6 +408,30 @@ suite('SnippetController', () => { }, ['af', '\taf']); }); + test('Final tabstop, #11890 stay at the beginning', () => { + + snippetTest((editor, cursor, codeSnippet, controller) => { + + editor.setSelections([ + new Selection(1, 5, 1, 5) + ]); + + codeSnippet = CodeSnippet.fromTextmate([ + 'afterEach((done) => {', + '${1}\ttest', + '});' + ].join('\n')); + + controller.run(codeSnippet, 2, 0, true); + + assert.equal(editor.getSelections().length, 1); + const [first] = editor.getSelections(); + + assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 3, endLineNumber: 2, endColumn: 3 }), first.toString()); + + }, [' af']); + }); + test('Final tabstop, no tabstop', () => { snippetTest((editor, cursor, codeSnippet, controller) => {