diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 30a06931cd7..218e5ec9fd5 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -444,8 +444,7 @@ export class SnippetSession { return false; } - let ranges: Range[] = []; - let placeholderIndex: number = -1; + let allPossibleSelections: Map; for (const snippet of this._snippets) { const possibleSelections = snippet.computePossibleSelections(); @@ -453,49 +452,58 @@ export class SnippetSession { // for the first snippet find the placeholder (and its ranges) // that contain at least one selection. for all remaining snippets // the same placeholder (and their ranges) must be used. - if (placeholderIndex < 0) { + if (!allPossibleSelections) { + allPossibleSelections = new Map(); possibleSelections.forEach((ranges, index) => { - if (placeholderIndex >= 0) { - return; - } + ranges.sort(Range.compareRangesUsingStarts); for (const selection of selections) { if (ranges[0].containsRange(selection)) { - placeholderIndex = index; + allPossibleSelections.set(index, []); break; } } }); } - if (placeholderIndex < 0) { + if (allPossibleSelections.size === 0) { // return false if we couldn't associate a selection to // this (the first) snippet return false; } - ranges.push(...possibleSelections.get(placeholderIndex)); + // add selections from 'this' snippet so that we know all + // selections for this placeholder + allPossibleSelections.forEach((array, index) => { + array.push(...possibleSelections.get(index)); + }); } - if (selections.length !== ranges.length) { - // this means we started at a placeholder with N - // ranges and new have M (N > M) selections. - // So (at least) one placeholder is without selection -> cancel - return false; - } - - // also sort (placeholder)-ranges. then walk both arrays and - // make sure the placeholder-ranges contain the corresponding + // sort selections (and later placeholder-ranges). then walk both + // arrays and make sure the placeholder-ranges contain the corresponding // selection selections.sort(Range.compareRangesUsingStarts); - ranges.sort(Range.compareRangesUsingStarts); - for (let i = 0; i < ranges.length; i++) { - if (!ranges[i].containsRange(selections[i])) { - return false; + allPossibleSelections.forEach((ranges, index) => { + + if (ranges.length !== selections.length) { + allPossibleSelections.delete(index); + return; } - } - return true; + ranges.sort(Range.compareRangesUsingStarts); + + for (let i = 0; i < ranges.length; i++) { + if (!ranges[i].containsRange(selections[i])) { + allPossibleSelections.delete(index); + return; + } + } + }); + + // from all possible selections we have deleted those + // that don't match with the current selection. if we don't + // have any left, we don't have a selection anymore + return allPossibleSelections.size > 0; } } diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts index 0bcbb92e579..526dcb3dc1a 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts @@ -303,4 +303,18 @@ suite('SnippetController2', function () { assertSelections(editor, new Selection(2, 9, 2, 9), new Selection(1, 7, 1, 7)); assertContextKeys(contextKeys, true, false, true); }); + + test('“Nested” snippets terminating abruptly in VSCode 1.19.2. #42012', function () { + + const ctrl = new SnippetController2(editor, logService, contextKeys); + model.setValue(''); + editor.setSelection(new Selection(1, 1, 1, 1)); + ctrl.insert('var ${2:${1:name}} = ${1:name} + 1;${0}'); + + assertSelections(editor, new Selection(1, 5, 1, 9), new Selection(1, 12, 1, 16)); + assertContextKeys(contextKeys, true, false, true); + + ctrl.next(); + assertContextKeys(contextKeys, true, true, true); + }); });