From bca2236bd5d91d197917defbdb2e3ce6f63988e4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 25 Jul 2017 14:55:42 +0200 Subject: [PATCH] normalize selected text variable when resolving it, #31124 --- .../snippet/browser/snippetVariables.ts | 32 ++++++++++- .../test/browser/snippetSession.test.ts | 48 +++++++++++++++++ .../test/browser/snippetVariables.test.ts | 53 +++++++++++-------- 3 files changed, 110 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/contrib/snippet/browser/snippetVariables.ts b/src/vs/editor/contrib/snippet/browser/snippetVariables.ts index 15d6173631b..25780d7dbcd 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetVariables.ts @@ -8,7 +8,8 @@ import { basename, dirname } from 'vs/base/common/paths'; import { IModel } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; -import { VariableResolver, Variable } from 'vs/editor/contrib/snippet/browser/snippetParser'; +import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/browser/snippetParser'; +import { getLeadingWhitespace, commonPrefixLength } from 'vs/base/common/strings'; export class EditorSnippetVariableResolver implements VariableResolver { @@ -36,7 +37,34 @@ export class EditorSnippetVariableResolver implements VariableResolver { const { name } = variable; if (name === 'SELECTION' || name === 'TM_SELECTED_TEXT') { - return this._model.getValueInRange(this._selection) || undefined; + let value = this._model.getValueInRange(this._selection) || undefined; + if (value && this._selection.startLineNumber !== this._selection.endLineNumber) { + // Selection is a multiline string which we indentation we now + // need to adjust. We compare the indentation of this variable + // with the indentation at the editor position and add potential + // extra indentation to the value + + const line = this._model.getLineContent(this._selection.startLineNumber); + const lineLeadingWhitespace = getLeadingWhitespace(line, 0, this._selection.startColumn - 1); + + let varLeadingWhitespace = lineLeadingWhitespace; + variable.snippet.walk(marker => { + if (marker === variable) { + return false; + } + if (marker instanceof Text) { + varLeadingWhitespace = getLeadingWhitespace(marker.value.split(/\r\n|\r|\n/).pop()); + } + return true; + }); + const whitespaceCommonLength = commonPrefixLength(varLeadingWhitespace, lineLeadingWhitespace); + + value = value.replace( + /(\r\n|\r|\n)(.*)/g, + (m, newline, rest) => `${newline}${varLeadingWhitespace.substr(whitespaceCommonLength)}${rest}` + ); + } + return value; } else if (name === 'TM_CURRENT_LINE') { return this._model.getLineContent(this._selection.positionLineNumber); diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts index 62f148f8ff8..26e401d6f6f 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts @@ -462,5 +462,53 @@ suite('SnippetSession', function () { assert.equal(editor.getModel().getValue(), 'test 1\ntest 2\ntest 3\ntest 4\n'); }); + + test('Snippet variable text isn\'t whitespace normalised, #31124', function () { + editor.getModel().setValue([ + 'start', + '\t\t-one', + '\t\t-two', + 'end' + ].join('\n')); + + editor.getModel().updateOptions({ insertSpaces: false }); + editor.setSelection(new Selection(2, 2, 3, 7)); + + new SnippetSession(editor, '
\n\t$TM_SELECTED_TEXT\n
$0').insert(); + + let expected = [ + 'start', + '\t
', + '\t\t\t-one', + '\t\t\t-two', + '\t
', + 'end' + ].join('\n'); + + assert.equal(editor.getModel().getValue(), expected); + + editor.getModel().setValue([ + 'start', + '\t\t-one', + '\t-two', + 'end' + ].join('\n')); + + editor.getModel().updateOptions({ insertSpaces: false }); + editor.setSelection(new Selection(2, 2, 3, 7)); + + new SnippetSession(editor, '
\n\t$TM_SELECTED_TEXT\n
$0').insert(); + + expected = [ + 'start', + '\t
', + '\t\t\t-one', + '\t\t-two', + '\t
', + 'end' + ].join('\n'); + + assert.equal(editor.getModel().getValue(), expected); + }); }); diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts index a21516d37d8..07c264b307f 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts @@ -31,59 +31,70 @@ suite('Snippet Variables Resolver', function () { model.dispose(); }); + function assertVariableResolve(resolver: EditorSnippetVariableResolver, varName: string, expected: string) { + const snippet = new SnippetParser().parse(`$${varName}`); + const variable = snippet.children[0]; + variable.resolve(resolver); + if (variable.children.length === 0) { + assert.equal(undefined, expected); + } else { + assert.equal(variable.toString(), expected); + } + } + test('editor variables, basics', function () { - assert.equal(resolver.resolve(new Variable('TM_FILENAME')), 'text.txt'); - assert.equal(resolver.resolve(new Variable('something')), undefined); + assertVariableResolve(resolver, 'TM_FILENAME', 'text.txt'); + assertVariableResolve(resolver, 'something', undefined); }); test('editor variables, file/dir', function () { - assert.equal(resolver.resolve(new Variable('TM_FILENAME')), 'text.txt'); + assertVariableResolve(resolver, 'TM_FILENAME', 'text.txt'); if (!isWindows) { - assert.equal(resolver.resolve(new Variable('TM_DIRECTORY')), '/foo/files'); - assert.equal(resolver.resolve(new Variable('TM_FILEPATH')), '/foo/files/text.txt'); + assertVariableResolve(resolver, 'TM_DIRECTORY', '/foo/files'); + assertVariableResolve(resolver, 'TM_FILEPATH', '/foo/files/text.txt'); } resolver = new EditorSnippetVariableResolver( Model.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')), new Selection(1, 1, 1, 1) ); - assert.equal(resolver.resolve(new Variable('TM_FILENAME')), 'ghi'); + assertVariableResolve(resolver, 'TM_FILENAME', 'ghi'); if (!isWindows) { - assert.equal(resolver.resolve(new Variable('TM_DIRECTORY')), '/abc/def'); - assert.equal(resolver.resolve(new Variable('TM_FILEPATH')), '/abc/def/ghi'); + assertVariableResolve(resolver, 'TM_DIRECTORY', '/abc/def'); + assertVariableResolve(resolver, 'TM_FILEPATH', '/abc/def/ghi'); } resolver = new EditorSnippetVariableResolver( Model.createFromString('', undefined, undefined, URI.parse('mem:fff.ts')), new Selection(1, 1, 1, 1) ); - assert.equal(resolver.resolve(new Variable('TM_DIRECTORY')), ''); - assert.equal(resolver.resolve(new Variable('TM_FILEPATH')), 'fff.ts'); + assertVariableResolve(resolver, 'TM_DIRECTORY', ''); + assertVariableResolve(resolver, 'TM_FILEPATH', 'fff.ts'); }); test('editor variables, selection', function () { resolver = new EditorSnippetVariableResolver(model, new Selection(1, 2, 2, 3)); - assert.equal(resolver.resolve(new Variable('TM_SELECTED_TEXT')), 'his is line one\nth'); - assert.equal(resolver.resolve(new Variable('TM_CURRENT_LINE')), 'this is line two'); - assert.equal(resolver.resolve(new Variable('TM_LINE_INDEX')), '1'); - assert.equal(resolver.resolve(new Variable('TM_LINE_NUMBER')), '2'); + assertVariableResolve(resolver, 'TM_SELECTED_TEXT', 'his is line one\nth'); + assertVariableResolve(resolver, 'TM_CURRENT_LINE', 'this is line two'); + assertVariableResolve(resolver, 'TM_LINE_INDEX', '1'); + assertVariableResolve(resolver, 'TM_LINE_NUMBER', '2'); resolver = new EditorSnippetVariableResolver(model, new Selection(2, 3, 1, 2)); - assert.equal(resolver.resolve(new Variable('TM_SELECTED_TEXT')), 'his is line one\nth'); - assert.equal(resolver.resolve(new Variable('TM_CURRENT_LINE')), 'this is line one'); - assert.equal(resolver.resolve(new Variable('TM_LINE_INDEX')), '0'); - assert.equal(resolver.resolve(new Variable('TM_LINE_NUMBER')), '1'); + assertVariableResolve(resolver, 'TM_SELECTED_TEXT', 'his is line one\nth'); + assertVariableResolve(resolver, 'TM_CURRENT_LINE', 'this is line one'); + assertVariableResolve(resolver, 'TM_LINE_INDEX', '0'); + assertVariableResolve(resolver, 'TM_LINE_NUMBER', '1'); resolver = new EditorSnippetVariableResolver(model, new Selection(1, 2, 1, 2)); - assert.equal(resolver.resolve(new Variable('TM_SELECTED_TEXT')), undefined); + assertVariableResolve(resolver, 'TM_SELECTED_TEXT', undefined); - assert.equal(resolver.resolve(new Variable('TM_CURRENT_WORD')), 'this'); + assertVariableResolve(resolver, 'TM_CURRENT_WORD', 'this'); resolver = new EditorSnippetVariableResolver(model, new Selection(3, 1, 3, 1)); - assert.equal(resolver.resolve(new Variable('TM_CURRENT_WORD')), undefined); + assertVariableResolve(resolver, 'TM_CURRENT_WORD', undefined); }); -- GitLab