/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs
1 740 строк
72 KB
Todd Grunke
Dev/toddgrun/fix lsp completion suggestion mode empty filter (#83438)
28 апр 2026, 20:28
Не верифицирован
28 апр 2026, 20:28
ac6f3ea
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; using LSP = Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Completion; public sealed class CompletionTests : AbstractLanguageServerProtocolTests { private static readonly LSP.VSInternalClientCapabilities s_vsCompletionCapabilities = CreateCoreCompletionCapabilities(); private static LSP.VSInternalClientCapabilities CreateCoreCompletionCapabilities() => new() { SupportsVisualStudioExtensions = true, TextDocument = new() { Completion = new VSInternalCompletionSetting() { CompletionListSetting = new CompletionListSetting() { ItemDefaults = [CompletionCapabilityHelper.EditRangePropertyName], }, CompletionItemKind = new(), CompletionList = new VSInternalCompletionListSetting() { CommitCharacters = true, } }, }, }; public CompletionTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } [Theory, CombinatorialData] public async Task TestGetCompletionsAsync_PromotesCommitCharactersToListAsync(bool mutatingLspWorkspace, bool isPublicDefaultCommitChars) { var itemDefaultArray = isPublicDefaultCommitChars ? new string[] { CompletionCapabilityHelper.EditRangePropertyName, CompletionCapabilityHelper.CommitCharactersPropertyName } : [CompletionCapabilityHelper.EditRangePropertyName]; var clientCapabilities = new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true, TextDocument = new LSP.TextDocumentClientCapabilities { Completion = new LSP.VSInternalCompletionSetting { CompletionListSetting = new LSP.CompletionListSetting { ItemDefaults = itemDefaultArray }, CompletionList = isPublicDefaultCommitChars ? null : new LSP.VSInternalCompletionListSetting { CommitCharacters = true } }, } }; var markup = """ class A { void M() { {|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, clientCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync(label: "A", kind: LSP.CompletionItemKind.Class, tags: ["Class", "Internal"], request: completionParams, document: document, commitCharacters: CompletionRules.Default.DefaultCommitCharacters, sortText: "0000").ConfigureAwait(false); var expectedCommitCharacters = expected.CommitCharacters; // Null out the commit characters since we're expecting the commit characters will be lifted onto the completion list. expected.CommitCharacters = null; var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); var vsCompletionList = Assert.IsAssignableFrom<LSP.VSInternalCompletionList>(results); if (isPublicDefaultCommitChars) Assert.Equal(expectedCommitCharacters, vsCompletionList.ItemDefaults.CommitCharacters); else Assert.Equal(expectedCommitCharacters, vsCompletionList.CommitCharacters.Value.First); } [Theory, CombinatorialData] public async Task TestGetCompletions_PromotesNothingWhenNoCommitCharactersAsync(bool mutatingLspWorkspace) { var clientCapabilities = new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true, TextDocument = new LSP.TextDocumentClientCapabilities { Completion = new LSP.VSInternalCompletionSetting { CompletionListSetting = new LSP.CompletionListSetting { ItemDefaults = [CompletionCapabilityHelper.EditRangePropertyName] }, CompletionList = new LSP.VSInternalCompletionListSetting { CommitCharacters = true, } }, } }; var markup = """ namespace M {{|caret:|} } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, clientCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync(label: "A", kind: LSP.CompletionItemKind.Class, tags: ["Class", "Internal"], request: completionParams, document: document, commitCharacters: CompletionRules.Default.DefaultCommitCharacters).ConfigureAwait(false); var expectedCommitCharacters = expected.CommitCharacters; // Null out the commit characters since we're expecting the commit characters will be lifted onto the completion list. expected.CommitCharacters = null; var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.All(results.Items, item => Assert.Null(item.CommitCharacters)); var vsCompletionList = Assert.IsAssignableFrom<LSP.VSInternalCompletionList>(results); Assert.Equal(expectedCommitCharacters, vsCompletionList.CommitCharacters.Value.First); } [Theory, CombinatorialData] public async Task TestGetCompletionsAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { {|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync(label: "A", kind: LSP.CompletionItemKind.Class, tags: ["Class", "Internal"], request: completionParams, document: document, commitCharacters: null, sortText: "0000").ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); Assert.NotNull(results.ItemDefaults.EditRange); } [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1777096")] public async Task TestGetExtensionMethodCoreLsp(bool mutatingLspWorkspace) { var markup = """ class A { void M(A a) { a.{|caret:|} } } static class Extensions { public static void Goo(this A a) { } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync(label: "Goo", kind: LSP.CompletionItemKind.Method, tags: ["ExtensionMethod", "Public"], request: completionParams, document: document, commitCharacters: null, sortText: "0003").ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.Single(i => i.Label == "Goo")); Assert.NotNull(results.ItemDefaults.EditRange); } [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1777096")] public async Task TestGetExtensionMethodCoreVSLsp(bool mutatingLspWorkspace) { var markup = """ class A { void M(A a) { a.{|caret:|} } } static class Extensions { public static void Goo(this A a) { } } """; // If the client supports more completion kinds, then we can give a more precise answer. var capabilities = CreateCoreCompletionCapabilities(); capabilities.TextDocument.Completion.CompletionItemKind.ValueSet = [LSP.CompletionItemKind.ExtensionMethod]; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, capabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync(label: "Goo", kind: LSP.CompletionItemKind.ExtensionMethod, tags: ["ExtensionMethod", "Public"], request: completionParams, document: document, commitCharacters: null, sortText: "0003").ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.Single(i => i.Label == "Goo")); Assert.NotNull(results.ItemDefaults.EditRange); } [Theory, CombinatorialData] public async Task TestGetCompletionsTypingAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { A{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "A", triggerKind: LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync(label: "A", kind: LSP.CompletionItemKind.Class, tags: ["Class", "Internal"], request: completionParams, document: document, commitCharacters: null, sortText: "0000").ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); } [Theory, CombinatorialData] public async Task TestGetCompletionsDoesNotIncludeUnimportedTypesAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { {|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var solution = testLspServer.TestWorkspace.CurrentSolution; // Make sure the unimported types option is on testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, true); testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, true); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams); Assert.False(results.Items.Any(item => "Console" == item.Label)); } [Theory, CombinatorialData] public async Task TestGetCompletionsUsesSnippetOptionAsync(bool mutatingLspWorkspace) { var markup = """ class A { {|caret:|} } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.CSharp, SnippetsRule.NeverInclude); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams); Assert.False(results.Items.Any(item => "ctor" == item.Label)); } [Theory, CombinatorialData] public async Task TestGetCompletionsWithPreselectAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { A classA = new {|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync("A", LSP.CompletionItemKind.Class, ["Class", "Internal"], completionParams, document, preselect: true, commitCharacters: ImmutableArray.Create(' ', '(', '[', '{', ';', '.'), sortText: "0000", matchPriority: MatchPriority.Preselect).ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); } [Theory, CombinatorialData] public async Task TestGetCompletionsIsInSuggestionMode(bool mutatingLspWorkspace) { var markup = """ using System.Collections.Generic; using System.Linq; namespace M { class Item { void M() { var items = new List<Item>(); items.Count(i{|caret:|} } } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "i", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = (LSP.VSInternalCompletionList)await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.True(results.Items.Any()); Assert.True(results.SuggestionMode); } [Theory, CombinatorialData] public async Task TestDotTriggerDoesNotSetSuggestionMode(bool mutatingLspWorkspace) { // Typing '.' triggers completion with empty filter text. IsAllPunctuation("") returns true // vacuously, but empty text should not be treated as "all punctuation" — it should produce // hard selection and a complete list, not suggestion mode with isIncomplete. var markup = """ using System; class C { void M() { Console.{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: ".", triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); var results = (LSP.VSInternalCompletionList)await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.NotEmpty(results.Items); Assert.False(results.SuggestionMode); Assert.False(results.IsIncomplete); } [Theory, CombinatorialData] public async Task TestUnderscoreTriggerSetsSuggestionModeAndIncomplete(bool mutatingLspWorkspace) { // Typing '_' (punctuation) should set suggestion mode and mark the list as incomplete, // so the server gets called back when the user types a non-punctuation character. var markup = """ class C { private int _value; void M() { _{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "_", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = (LSP.VSInternalCompletionList)await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.NotEmpty(results.Items); Assert.True(results.SuggestionMode); Assert.True(results.IsIncomplete); } [ConditionalTheory(typeof(IsEnglishLocal)), CombinatorialData] public async Task TestGetDateAndTimeCompletionsAsync(bool mutatingLspWorkspace) { var markup = """ using System; class A { void M() { DateTime.Now.ToString("{|caret:|}); } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: """ " """, triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync( label: "d", kind: LSP.CompletionItemKind.Text, tags: ["Text"], request: completionParams, document: document, sortText: "00000000", labelDetails: new() { Description = "shortdate" }).ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/59453")] public async Task TestGetDateAndTimeCompletionOnGuid(bool mutatingLspWorkspace) { var markup = """ using System; class A { void M() { Guid.NewGuid().ToString("{|caret:|}); } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: """ " """, triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Null(results); } [ConditionalTheory(typeof(IsEnglishLocal)), CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/50964")] public async Task TestGetRegexCompletionsAsync(bool mutatingLspWorkspace) { var markup = """ using System.Text.RegularExpressions; class A { void M() { new Regex("{|caret:|}"); } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var solution = testLspServer.GetCurrentSolution(); var document = solution.Projects.First().Documents.First(); var defaultRange = new LSP.Range { Start = new LSP.Position { Line = 5, Character = 19 }, End = new LSP.Position { Line = 5, Character = 19 } }; var expected = await CreateCompletionItemAsync( label: @"\A", kind: LSP.CompletionItemKind.Text, tags: ["Text"], request: completionParams, document: document, textEditText: @"\\A", sortText: "00000000", labelDetails: new() { Description = "startofstringonly" }).ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); Assert.Equal(defaultRange, results.ItemDefaults.EditRange); } [ConditionalTheory(typeof(IsEnglishLocal)), CombinatorialData] public async Task TestGetRegexLiteralCompletionsAsync(bool mutatingLspWorkspace) { var markup = """ using System.Text.RegularExpressions; class A { void M() { new Regex(@"\{|caret:|}"); } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\\", triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); var solution = testLspServer.GetCurrentSolution(); var document = solution.Projects.First().Documents.First(); var defaultRange = new LSP.Range { Start = new LSP.Position { Line = 5, Character = 21 }, End = new LSP.Position { Line = 5, Character = 21 } }; var expected = await CreateCompletionItemAsync( label: @"\A", kind: LSP.CompletionItemKind.Text, tags: ["Text"], request: completionParams, document: document, sortText: "00000000", vsResolveTextEditOnCommit: true, labelDetails: new() { Description = "startofstringonly" }).ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); Assert.Equal(defaultRange, results.ItemDefaults.EditRange); } [ConditionalTheory(typeof(IsEnglishLocal)), CombinatorialData] public async Task TestGetRegexCompletionsReplaceTextAsync(bool mutatingLspWorkspace) { var markup = """ using System.Text.RegularExpressions; class A { void M() { Regex r = new("\\{|caret:|}"); } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "\\", triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); var solution = testLspServer.GetCurrentSolution(); var document = solution.Projects.First().Documents.First(); var defaultRange = new LSP.Range { Start = new LSP.Position { Line = 5, Character = 25 }, End = new LSP.Position { Line = 5, Character = 25 } }; var expected = await CreateCompletionItemAsync( label: @"\A", kind: LSP.CompletionItemKind.Text, tags: ["Text"], request: completionParams, document: document, sortText: "00000000", vsResolveTextEditOnCommit: true, labelDetails: new() { Description = "startofstringonly" }).ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); Assert.Equal(defaultRange, results.ItemDefaults.EditRange); } [ConditionalTheory(typeof(IsEnglishLocal)), CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/50964")] public async Task TestGetRegexCompletionsWithoutItemDefaultSupportAsync(bool mutatingLspWorkspace) { var clientCapabilities = new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true, TextDocument = new LSP.TextDocumentClientCapabilities { Completion = new LSP.VSInternalCompletionSetting { CompletionListSetting = new LSP.CompletionListSetting { ItemDefaults = null, }, CompletionList = new VSInternalCompletionListSetting { CommitCharacters = true, } }, } }; var markup = """ using System.Text.RegularExpressions; class A { void M() { new Regex("{|caret:|}"); } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, clientCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var solution = testLspServer.GetCurrentSolution(); var document = solution.Projects.First().Documents.First(); var textEdit = GenerateTextEdit(@"\\A", startLine: 5, startChar: 19, endLine: 5, endChar: 19); var expected = await CreateCompletionItemAsync( label: @"\A", kind: LSP.CompletionItemKind.Text, tags: ["Text"], request: completionParams, document: document, textEdit: textEdit, sortText: "00000000", labelDetails: new() { Description = "startofstringonly" }).ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); Assert.Null(results.ItemDefaults); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/46694")] public async Task TestCompletionListCacheAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { {|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var cache = GetCompletionListCache(testLspServer); Assert.NotNull(cache); var testAccessor = cache.GetTestAccessor(); // This test assumes that the maximum cache size is 3, and will have to modified if this number changes. Assert.True(testAccessor.MaximumCacheSize == 3); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); // 1 item in cache await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); var completionList = cache.GetCachedEntry(0).CompletionList; Assert.NotNull(completionList); Assert.Single(testAccessor.GetCacheContents()); // 2 items in cache await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); completionList = cache.GetCachedEntry(0).CompletionList; Assert.NotNull(completionList); completionList = cache.GetCachedEntry(1).CompletionList; Assert.NotNull(completionList); Assert.True(testAccessor.GetCacheContents().Count == 2); // 3 items in cache await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); completionList = cache.GetCachedEntry(0).CompletionList; Assert.NotNull(completionList); completionList = cache.GetCachedEntry(1).CompletionList; Assert.NotNull(completionList); completionList = cache.GetCachedEntry(2).CompletionList; Assert.NotNull(completionList); Assert.True(testAccessor.GetCacheContents().Count == 3); // Maximum size of cache (3) should not be exceeded - oldest item should be ejected await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); var cacheEntry = cache.GetCachedEntry(0); Assert.Null(cacheEntry); completionList = cache.GetCachedEntry(1).CompletionList; Assert.NotNull(completionList); completionList = cache.GetCachedEntry(2).CompletionList; Assert.NotNull(completionList); completionList = cache.GetCachedEntry(3).CompletionList; Assert.NotNull(completionList); Assert.True(testAccessor.GetCacheContents().Count == 3); } [Theory, CombinatorialData] public async Task TestGetCompletionsWithDeletionInvokeKindAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { {|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Deletion, triggerCharacter: "M", triggerKind: LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync("A", LSP.CompletionItemKind.Class, ["Class", "Internal"], completionParams, document, commitCharacters: CompletionRules.Default.DefaultCommitCharacters).ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); // By default, completion doesn't trigger on deletion. Assert.Null(results); } [Theory, CombinatorialData] public async Task TestDoNotProvideOverrideTextEditsOrInsertTextAsync(bool mutatingLspWorkspace) { var markup = """ abstract class A { public abstract void M(); } class B : A { override {|caret:|} } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Null(results.Items.First().TextEdit); Assert.Null(results.Items.First().InsertText); Assert.True(((LSP.VSInternalCompletionItem)results.Items.First()).VsResolveTextEditOnCommit); } [Theory, CombinatorialData] public async Task TestDoNotProvidePartialMethodTextEditsOrInsertTextAsync(bool mutatingLspWorkspace) { var markup = """ partial class C { partial void Method(); } partial class C { partial {|caret:|} } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Null(results.Items.First().TextEdit); Assert.Null(results.Items.First().InsertText); } [Theory, CombinatorialData] public async Task TestSoftSelectedItemsHaveNoCommitCharactersWithoutVSCapabilityAsync(bool mutatingLspWorkspace) { var markup = """ using System.Text.RegularExpressions; class A { void M() { new Regex("[{|caret:|}") } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "[", triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.NotNull(results); Assert.NotEmpty(results.Items); Assert.All(results.Items, (item) => Assert.Empty(item.CommitCharacters)); } [Theory, CombinatorialData] public async Task TestLargeCompletionListIsMarkedIncompleteAsync(bool mutatingLspWorkspace) { var markup = """ using System; using System.Buffers; using System.Buffers.Binary; using System.Buffers.Text; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.Design; using System.Configuration; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Dynamic; using System.Globalization; using System.IO; using System.Linq; using System.Media; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text; using System.Threading; using System.Threading.Tasks; class A { void M() { T{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "T", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Equal(1000, results.Items.Length); Assert.True(results.IsIncomplete); } [Theory, CombinatorialData] public async Task TestIncompleteCompletionListContainsPreselectedItemAsync(bool mutatingLspWorkspace) { var markup = """ using System; using System.Buffers; using System.Buffers.Binary; using System.Buffers.Text; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.Design; using System.Configuration; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Dynamic; using System.Globalization; using System.IO; using System.Linq; using System.Media; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text; using System.Threading; using System.Threading.Tasks; class A { class W { } void M() { W someW = new {|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var caretLocation = testLspServer.GetLocations("caret").Single(); var completionParams = CreateCompletionParams( caretLocation, invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: " ", triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Equal(1000, results.Items.Length); Assert.True(results.IsIncomplete); var itemW = results.Items.Single(item => item.Label == "W"); Assert.True(itemW.Preselect); } [Theory, CombinatorialData] public async Task TestRequestForIncompleteListIsFilteredDownAsync(bool mutatingLspWorkspace) { var markup = """ using System; using System.Buffers; using System.Buffers.Binary; using System.Buffers.Text; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.Design; using System.Configuration; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Dynamic; using System.Globalization; using System.IO; using System.Linq; using System.Media; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text; using System.Threading; using System.Threading.Tasks; class A { void M() { T{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var caretLocation = testLspServer.GetLocations("caret").Single(); await testLspServer.OpenDocumentAsync(caretLocation.DocumentUri); var completionParams = CreateCompletionParams( caretLocation, invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "T", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Equal(1000, results.Items.Length); Assert.True(results.IsIncomplete); Assert.Equal("T", results.Items.First().Label); await testLspServer.InsertTextAsync(caretLocation.DocumentUri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, "a")); completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "a", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.True(results.IsIncomplete); Assert.True(results.Items.Length < 1000); Assert.Contains("ta", results.Items.First().Label, StringComparison.OrdinalIgnoreCase); } [Theory, CombinatorialData] public async Task TestIncompleteCompletionListFiltersWithPatternMatchingAsync(bool mutatingLspWorkspace) { var markup = """ using System; using System.Buffers; using System.Buffers.Binary; using System.Buffers.Text; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.Design; using System.Configuration; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Dynamic; using System.Globalization; using System.IO; using System.Linq; using System.Media; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text; using System.Threading; using System.Threading.Tasks; class A { void M() { T{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var caretLocation = testLspServer.GetLocations("caret").Single(); await testLspServer.OpenDocumentAsync(caretLocation.DocumentUri); var completionParams = CreateCompletionParams( caretLocation, invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "T", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Equal(1000, results.Items.Length); Assert.True(results.IsIncomplete); Assert.Equal("T", results.Items.First().Label); await testLspServer.InsertTextAsync(caretLocation.DocumentUri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, "C")); completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "C", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.True(results.IsIncomplete); Assert.True(results.Items.Length < 1000); Assert.Equal("TaiwanCalendar", results.Items.First().Label); } [Theory, CombinatorialData] public async Task TestIncompleteCompletionListWithDeletionAsync(bool mutatingLspWorkspace) { var markup = """ using System; using System.Buffers; using System.Buffers.Binary; using System.Buffers.Text; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.Design; using System.Configuration; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Dynamic; using System.Globalization; using System.IO; using System.Linq; using System.Media; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text; using System.Threading; using System.Threading.Tasks; class A { void M() { T{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var caretLocation = testLspServer.GetLocations("caret").Single(); await testLspServer.OpenDocumentAsync(caretLocation.DocumentUri); // Insert 'T' to make 'T' and trigger completion. var completionParams = CreateCompletionParams( caretLocation, invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "T", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Equal(1000, results.Items.Length); Assert.True(results.IsIncomplete); Assert.Equal("T", results.Items.First().Label); // Insert 'ask' to make 'Task' and trigger completion. await testLspServer.InsertTextAsync(caretLocation.DocumentUri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, "ask")); completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "k", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.True(results.IsIncomplete); Assert.True(results.Items.Length < 1000); Assert.Equal("Task", results.Items.First().Label); // Delete 'ask' to make 'T' and trigger completion on deletion. await testLspServer.DeleteTextAsync(caretLocation.DocumentUri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, caretLocation.Range.End.Line, caretLocation.Range.End.Character + 3)); completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Deletion, triggerCharacter: "a", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.True(results.IsIncomplete); Assert.Equal(1000, results.Items.Length); Assert.True(results.IsIncomplete); Assert.Equal("T", results.Items.First().Label); // Insert 'i' to make 'Ti' and trigger completion. await testLspServer.InsertTextAsync(caretLocation.DocumentUri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, "i")); completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "i", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.True(results.IsIncomplete); Assert.True(results.Items.Length < 1000); Assert.Equal("Timeout", results.Items.First().Label); } [Theory, CombinatorialData] public async Task TestNewCompletionRequestDoesNotUseIncompleteListAsync(bool mutatingLspWorkspace) { var markup = """ using System; using System.Buffers; using System.Buffers.Binary; using System.Buffers.Text; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.Design; using System.Configuration; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Dynamic; using System.Globalization; using System.IO; using System.Linq; using System.Media; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text; using System.Threading; using System.Threading.Tasks; class A { void M() { T{|firstCaret:|} } void M2() { Console.WH{|secondCaret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var firstCaret = testLspServer.GetLocations("firstCaret").Single(); await testLspServer.OpenDocumentAsync(firstCaret.DocumentUri); // Make a completion request that returns an incomplete list. var completionParams = CreateCompletionParams( firstCaret, invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "T", triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Equal(1000, results.Items.Length); Assert.True(results.IsIncomplete); Assert.Contains(results.Items, i => i.Label == "T"); // It's client's responsibility to sort, so we can't assume the best match is the first item. // Make a second completion request, but not for the original incomplete list. completionParams = CreateCompletionParams( testLspServer.GetLocations("secondCaret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "H", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.False(results.IsIncomplete); Assert.True(results.Items.Length < 1000); Assert.Contains(results.Items, i => i.Label == "WindowHeight"); // It's client's responsibility to sort, so we can't assume the best match is the first item. } [Theory, CombinatorialData] public async Task TestRequestForIncompleteListWhenMissingCachedListAsync(bool mutatingLspWorkspace) { var markup = """ using System; using System.Buffers; using System.Buffers.Binary; using System.Buffers.Text; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.Design; using System.Configuration; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Dynamic; using System.Globalization; using System.IO; using System.Linq; using System.Media; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text; using System.Threading; using System.Threading.Tasks; class A { void M() { Ta{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var caretLocation = testLspServer.GetLocations("caret").Single(); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "a", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.True(results.IsIncomplete); Assert.True(results.Items.Length < 1000); Assert.Contains("ta", results.Items.First().Label, StringComparison.OrdinalIgnoreCase); } [Theory, CombinatorialData] public async Task TestRequestForIncompleteListUsesCorrectCachedListAsync(bool mutatingLspWorkspace) { var markup = """ using System; using System.Buffers; using System.Buffers.Binary; using System.Buffers.Text; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.Design; using System.Configuration; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Dynamic; using System.Globalization; using System.IO; using System.Linq; using System.Media; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text; using System.Threading; using System.Threading.Tasks; class A { void M1() { int Taaa = 1; T{|firstCaret:|} } void M2() { int Saaa = 1; {|secondCaret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var firstCaretLocation = testLspServer.GetLocations("firstCaret").Single(); await testLspServer.OpenDocumentAsync(firstCaretLocation.DocumentUri); // Create request to on insertion of 'T' var completionParams = CreateCompletionParams( firstCaretLocation, invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "T", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Equal(1000, results.Items.Length); Assert.True(results.IsIncomplete); Assert.Equal("T", results.Items.First().Label); Assert.Single(results.Items, item => item.Label == "Taaa"); // Insert 'S' at the second caret var secondCaretLocation = testLspServer.GetLocations("secondCaret").Single(); await testLspServer.InsertTextAsync(secondCaretLocation.DocumentUri, (secondCaretLocation.Range.End.Line, secondCaretLocation.Range.End.Character, "S")); // Trigger completion on 'S' var triggerLocation = GetLocationPlusOne(secondCaretLocation); completionParams = CreateCompletionParams( triggerLocation, invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "S", triggerKind: LSP.CompletionTriggerKind.Invoked); results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Equal(1000, results.Items.Length); Assert.True(results.IsIncomplete); Assert.Equal("Saaa", results.Items.First().Label); // Now type 'a' in M1 after 'T' await testLspServer.InsertTextAsync(firstCaretLocation.DocumentUri, (firstCaretLocation.Range.End.Line, firstCaretLocation.Range.End.Character, "a")); // Trigger completion on 'a' (using incomplete as we previously returned incomplete completions from 'T'). triggerLocation = GetLocationPlusOne(firstCaretLocation); completionParams = CreateCompletionParams( triggerLocation, invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "a", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); // Verify we get completions for 'Ta' and not from the 'S' location in M2 Assert.True(results.IsIncomplete); Assert.True(results.Items.Length < 1000); Assert.DoesNotContain(results.Items, item => item.Label == "Saaa"); Assert.Contains(results.Items, item => item.Label == "Taaa"); } [Theory, CombinatorialData] public async Task TestCompletionRequestRespectsListSizeOptionAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { {|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var globalOptions = testLspServer.TestWorkspace.GetService<IGlobalOptionService>(); var listMaxSize = 1; globalOptions.SetGlobalOption(LspOptionsStorage.MaxCompletionListSize, listMaxSize); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.True(results.IsIncomplete); Assert.Equal(listMaxSize, results.Items.Length); } [Theory, CombinatorialData] public async Task TestRequestForIncompleteListFiltersDownToEmptyAsync(bool mutatingLspWorkspace) { var markup = """ using System; using System.Buffers; using System.Buffers.Binary; using System.Buffers.Text; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.Design; using System.Configuration; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Dynamic; using System.Globalization; using System.IO; using System.Linq; using System.Media; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text; using System.Threading; using System.Threading.Tasks; class A { void M() { T{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var caretLocation = testLspServer.GetLocations("caret").Single(); await testLspServer.OpenDocumentAsync(caretLocation.DocumentUri); var completionParams = CreateCompletionParams( caretLocation, invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "T", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Equal(1000, results.Items.Length); Assert.True(results.IsIncomplete); Assert.Equal("T", results.Items.First().Label); await testLspServer.InsertTextAsync(caretLocation.DocumentUri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, "z")); completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "z", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.True(results.IsIncomplete); Assert.Empty(results.Items); } [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1755138")] public async Task TestHasSuggestionModeItemAsync(bool mutatingLspWorkspace) { var markup = """ using System.Threading.Tasks; class A { void M() { Task.Run(abcdefg{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "g", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); var list = (LSP.VSInternalCompletionList)results; Assert.False(list.IsIncomplete); Assert.NotEmpty(list.Items); // it client's responsibility to filter, server should return all items available regardless of the filter text (unless item counts exceeds the limit) Assert.True(list.SuggestionMode); } [Theory, CombinatorialData] public async Task EditRangeShouldNotEndAtCursorPosition(bool mutatingLspWorkspace) { var markup = """ public class C1 {} pub{|caret:|}class """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var caret = testLspServer.GetLocations("caret").Single(); testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.CSharp, SnippetsRule.NeverInclude); var completionParams = CreateCompletionParams( caret, invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams); Assert.NotNull(results); Assert.NotEmpty(results.Items); Assert.Equal(new() { Start = new(2, 0), End = new(2, 8) }, results.ItemDefaults.EditRange.Value.First); } [Theory, CombinatorialData] public async Task TestHasInsertTextModeIfSupportedAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { {|caret:|} } } """; var capabilities = CreateCoreCompletionCapabilities(); capabilities.TextDocument.Completion.CompletionItem = new LSP.CompletionItemSetting { InsertTextModeSupport = new LSP.InsertTextModeSupportSetting { ValueSet = [LSP.InsertTextMode.AsIs] } }; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, capabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Equal(LSP.InsertTextMode.AsIs, results.ItemDefaults.InsertTextMode); } [Theory, CombinatorialData] public async Task TestDoesNotHaveInsertTextModeIfNotSupportedAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { {|caret:|} } } """; var capabilities = CreateCoreCompletionCapabilities(); capabilities.TextDocument.Completion.CompletionItem = new LSP.CompletionItemSetting { InsertTextModeSupport = new LSP.InsertTextModeSupportSetting { ValueSet = [] } }; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, capabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit, triggerCharacter: "\0", triggerKind: LSP.CompletionTriggerKind.Invoked); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.Null(results.ItemDefaults.InsertTextMode); } [Theory, CombinatorialData] public async Task TestGetCompletions_MatchPriority_SetForVSClient(bool mutatingLspWorkspace) { var markup = """ class C { void M() { var x = nu{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "u", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); var nullItem = results.Items.SingleOrDefault(i => i.Label == "null"); var nuintItem = results.Items.SingleOrDefault(i => i.Label == "nuint"); Assert.NotNull(nullItem); Assert.NotNull(nuintItem); var vsNullItem = (LSP.VSInternalCompletionItem)nullItem; var vsNuintItem = (LSP.VSInternalCompletionItem)nuintItem; // null has default MatchPriority (0), nuint has MatchPriority.Default - 1 (-1) Assert.True(vsNullItem.MatchPriority > vsNuintItem.MatchPriority, $"Expected null's MatchPriority ({vsNullItem.MatchPriority}) to be greater than nuint's ({vsNuintItem.MatchPriority})"); } [Theory, CombinatorialData] public async Task TestGetCompletions_MatchPriority_DefaultIsZero(bool mutatingLspWorkspace) { // Verify that items with default MatchPriority have value 0. var markup = """ class C { void M() { int{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, s_vsCompletionCapabilities); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, triggerCharacter: "t", triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); // Find a standard keyword like "int" — it should have default (0) MatchPriority var intItem = results.Items.SingleOrDefault(i => i.Label == "int"); Assert.NotNull(intItem); var vsIntItem = (LSP.VSInternalCompletionItem)intItem; Assert.Equal(0, vsIntItem.MatchPriority); } internal static Task<LSP.CompletionList> RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams) { return testLspServer.ExecuteRequestAsync<LSP.CompletionParams, LSP.CompletionList>(LSP.Methods.TextDocumentCompletionName, completionParams, CancellationToken.None); } private static CompletionListCache GetCompletionListCache(TestLspServer testLspServer) { var cache = testLspServer.GetRequiredLspService<CompletionListCache>(); return cache; } }