diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 1794938bdc145595ab6327a7b3a47810f5d9c840..aa72b601f39409a598bb9b1e1b9f4c044910d630 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -272,7 +272,7 @@ export class SnippetSession { const snippets: OneSnippet[] = []; const modelBasedVariableResolver = new ModelBasedVariableResolver(model); - const clipboardVariableResolver = new ClipboardBasedVariableResolver(editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional))); + const clipboardService = editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional)); let delta = 0; @@ -286,11 +286,11 @@ export class SnippetSession { // the original index. that allows you to create correct // offset-based selection logic without changing the // primary selection - const indexedSelection = editor.getSelections() + const indexedSelections = editor.getSelections() .map((selection, idx) => ({ selection, idx })) .sort((a, b) => Range.compareRangesUsingStarts(a.selection, b.selection)); - for (const { selection, idx } of indexedSelection) { + for (const { selection, idx } of indexedSelections) { // extend selection with the `overwriteBefore` and `overwriteAfter` and then // compare if this matches the extensions of the primary selection @@ -317,7 +317,7 @@ export class SnippetSession { .parse(adjustedTemplate, true, enforceFinalTabstop) .resolveVariables(new CompositeSnippetVariableResolver([ modelBasedVariableResolver, - clipboardVariableResolver, + new ClipboardBasedVariableResolver(clipboardService, idx, indexedSelections.length), new SelectionBasedVariableResolver(model, selection) ])); diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index d6103e266cd68cde1d552f70939fc2bc99cf45b9..eac5593f75aa7c0c1ad18cdc3ebed8029b26854c 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -9,7 +9,7 @@ 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, Text } from 'vs/editor/contrib/snippet/snippetParser'; -import { getLeadingWhitespace, commonPrefixLength } from 'vs/base/common/strings'; +import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace } from 'vs/base/common/strings'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; export const KnownSnippetVariableNames = Object.freeze({ @@ -145,14 +145,28 @@ export class ModelBasedVariableResolver implements VariableResolver { export class ClipboardBasedVariableResolver implements VariableResolver { constructor( - private readonly _clipboardService: IClipboardService + private readonly _clipboardService: IClipboardService, + private readonly _selectionIdx: number, + private readonly _selectionCount: number ) { // } resolve(variable: Variable): string { - return (variable.name === 'CLIPBOARD' && this._clipboardService) - ? this._clipboardService.readText() || undefined - : undefined; + if (variable.name !== 'CLIPBOARD' || !this._clipboardService) { + return undefined; + } + + const text = this._clipboardService.readText(); + if (!text) { + return undefined; + } + + const lines = text.split(/\r\n|\n|\r/).filter(s => !isFalsyOrWhitespace(s)); + if (lines.length === this._selectionCount) { + return lines[this._selectionIdx]; + } else { + return text; + } } } diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index 7920178fe02d1f71582287e3102e52a0c690f1fe..f98043d0c20b9f1ae9a8ea57367edcae7c5bbc38 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -208,15 +208,17 @@ suite('Snippet Variables Resolver', function () { }); test('Add variable to insert value from clipboard to a snippet #40153', function () { + let readTextResult: string; - let _throw = () => { throw new Error(); }; - let resolver = new ClipboardBasedVariableResolver(new class implements IClipboardService { + const clipboardService = new class implements IClipboardService { _serviceBrand: any; readText(): string { return readTextResult; } - writeText = _throw; - readFindText = _throw; - writeFindText = _throw; - }); + _throw = () => { throw new Error(); }; + writeText = this._throw; + readFindText = this._throw; + writeFindText = this._throw; + }; + let resolver = new ClipboardBasedVariableResolver(clipboardService, 1, 0); readTextResult = undefined; assertVariableResolve(resolver, 'CLIPBOARD', undefined); @@ -233,4 +235,30 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve(resolver, 'foo', undefined); assertVariableResolve(resolver, 'cLIPBOARD', undefined); }); + + test('Add variable to insert value from clipboard to a snippet #40153', function () { + + let readTextResult: string; + let resolver: VariableResolver; + const clipboardService = new class implements IClipboardService { + _serviceBrand: any; + readText(): string { return readTextResult; } + _throw = () => { throw new Error(); }; + writeText = this._throw; + readFindText = this._throw; + writeFindText = this._throw; + }; + + resolver = new ClipboardBasedVariableResolver(clipboardService, 1, 2); + readTextResult = 'line1'; + assertVariableResolve(resolver, 'CLIPBOARD', 'line1'); + readTextResult = 'line1\nline2\nline3'; + assertVariableResolve(resolver, 'CLIPBOARD', 'line1\nline2\nline3'); + + readTextResult = 'line1\nline2'; + assertVariableResolve(resolver, 'CLIPBOARD', 'line2'); + readTextResult = 'line1\nline2'; + resolver = new ClipboardBasedVariableResolver(clipboardService, 0, 2); + assertVariableResolve(resolver, 'CLIPBOARD', 'line1'); + }); });