Skip to content

Commit bfb6730

Browse files
authored
Merge pull request microsoft#138974 from microsoft/joh/fix/138707
be more strict when suggesting snippets at word ends
2 parents 8e7b278 + 6d1c824 commit bfb6730

File tree

2 files changed

+80
-3
lines changed

2 files changed

+80
-3
lines changed

src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/sn
1717
import { isPatternInWord } from 'vs/base/common/filters';
1818
import { StopWatch } from 'vs/base/common/stopwatch';
1919
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
20+
import { getWordAtText } from 'vs/editor/common/model/wordHelper';
2021

2122
export class SnippetCompletion implements CompletionItem {
2223

@@ -70,6 +71,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider {
7071
const snippets = new Set(await this._snippets.getSnippets(languageId));
7172

7273
const lineContentLow = model.getLineContent(position.lineNumber).toLowerCase();
74+
const wordUntil = model.getWordUntilPosition(position).word.toLowerCase();
7375

7476
const suggestions: SnippetCompletion[] = [];
7577
const columnOffset = position.column - 1;
@@ -78,6 +80,13 @@ export class SnippetCompletionProvider implements CompletionItemProvider {
7880

7981
for (const snippet of snippets) {
8082

83+
const snippetPrefixWord = getWordAtText(1, LanguageConfigurationRegistry.getWordDefinition(languageId), snippet.prefixLow, 0);
84+
85+
if (wordUntil && snippetPrefixWord && !isPatternInWord(wordUntil, 0, wordUntil.length, snippet.prefixLow, 0, snippet.prefixLow.length)) {
86+
// when at a word the snippet prefix must match
87+
continue;
88+
}
89+
8190
for (let pos = Math.max(0, columnOffset - snippet.prefixLow.length); pos < lineContentLow.length; pos++) {
8291

8392
if (context.triggerKind === CompletionTriggerKind.TriggerCharacter && !snippet.prefixLow.startsWith(triggerCharacterLow)) {

src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as assert from 'assert';
7-
import { SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser/snippetCompletionProvider';
7+
import { SnippetCompletion, SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser/snippetCompletionProvider';
88
import { Position } from 'vs/editor/common/core/position';
99
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
1010
import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
@@ -91,12 +91,17 @@ suite('SnippetsService', function () {
9191
});
9292
});
9393

94-
test('snippet completions - simple 2', function () {
94+
test('snippet completions - simple 2', async function () {
9595

9696
const provider = new SnippetCompletionProvider(modeService, snippetService);
9797
const model = disposables.add(createTextModel('hello ', undefined, 'fooLang'));
9898

99-
return provider.provideCompletionItems(model, new Position(1, 6), context)!.then(result => {
99+
await provider.provideCompletionItems(model, new Position(1, 6) /* hello| */, context)!.then(result => {
100+
assert.strictEqual(result.incomplete, undefined);
101+
assert.strictEqual(result.suggestions.length, 0);
102+
});
103+
104+
await provider.provideCompletionItems(model, new Position(1, 7) /* hello |*/, context)!.then(result => {
100105
assert.strictEqual(result.incomplete, undefined);
101106
assert.strictEqual(result.suggestions.length, 2);
102107
});
@@ -640,4 +645,67 @@ suite('SnippetsService', function () {
640645
assert.strictEqual(result.suggestions.length, 1);
641646
model.dispose();
642647
});
648+
649+
test('Snippet suggestions are too eager #138707 (word)', async function () {
650+
snippetService = new SimpleSnippetService([
651+
new Snippet(['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User),
652+
new Snippet(['fooLang'], 'hell_or_tell', 'hell_or_tell', '', 'value', '', SnippetSource.User),
653+
new Snippet(['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User),
654+
]);
655+
656+
const provider = new SnippetCompletionProvider(modeService, snippetService);
657+
let model = createTextModel('\'hellot\'', undefined, 'fooLang');
658+
659+
let result = await provider.provideCompletionItems(
660+
model,
661+
new Position(1, 8),
662+
{ triggerKind: CompletionTriggerKind.Invoke }
663+
)!;
664+
665+
assert.strictEqual(result.suggestions.length, 2);
666+
assert.strictEqual((<SnippetCompletion>result.suggestions[0]).label.label, '^y');
667+
assert.strictEqual((<SnippetCompletion>result.suggestions[1]).label.label, 'hell_or_tell');
668+
model.dispose();
669+
});
670+
671+
test('Snippet suggestions are too eager #138707 (no word)', async function () {
672+
snippetService = new SimpleSnippetService([
673+
new Snippet(['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User),
674+
new Snippet(['fooLang'], 't', 't', '', 'value', '', SnippetSource.User),
675+
new Snippet(['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User),
676+
]);
677+
678+
const provider = new SnippetCompletionProvider(modeService, snippetService);
679+
let model = createTextModel(')*&^', undefined, 'fooLang');
680+
681+
let result = await provider.provideCompletionItems(
682+
model,
683+
new Position(1, 5),
684+
{ triggerKind: CompletionTriggerKind.Invoke }
685+
)!;
686+
687+
assert.strictEqual(result.suggestions.length, 1);
688+
assert.strictEqual((<SnippetCompletion>result.suggestions[0]).label.label, '^y');
689+
model.dispose();
690+
});
691+
692+
test('Snippet suggestions are too eager #138707 (word/word)', async function () {
693+
snippetService = new SimpleSnippetService([
694+
new Snippet(['fooLang'], 'async arrow function', 'async arrow function', '', 'value', '', SnippetSource.User),
695+
new Snippet(['fooLang'], 'foobarrrrrr', 'foobarrrrrr', '', 'value', '', SnippetSource.User),
696+
]);
697+
698+
const provider = new SnippetCompletionProvider(modeService, snippetService);
699+
let model = createTextModel('foobar', undefined, 'fooLang');
700+
701+
let result = await provider.provideCompletionItems(
702+
model,
703+
new Position(1, 7),
704+
{ triggerKind: CompletionTriggerKind.Invoke }
705+
)!;
706+
707+
assert.strictEqual(result.suggestions.length, 1);
708+
assert.strictEqual((<SnippetCompletion>result.suggestions[0]).label.label, 'foobarrrrrr');
709+
model.dispose();
710+
});
643711
});

0 commit comments

Comments
 (0)