diff --git a/build/Rulesets/NonShippingProjectRules.ruleset b/build/Rulesets/NonShippingProjectRules.ruleset index 9c1648b78fa59..f176857b22284 100644 --- a/build/Rulesets/NonShippingProjectRules.ruleset +++ b/build/Rulesets/NonShippingProjectRules.ruleset @@ -15,5 +15,6 @@ + \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs index f64bb00ec5080..379668ac18f33 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; @@ -8,7 +9,6 @@ using Microsoft.CodeAnalysis.CSharp.Completion; using Microsoft.CodeAnalysis.Editor.UnitTests.Completion; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; using Xunit; @@ -20,6 +20,12 @@ protected AbstractCSharpCompletionProviderTests(CSharpTestWorkspaceFixture works { } + internal override CompletionServiceWithProviders CreateCompletionService( + Workspace workspace, ImmutableArray exclusiveProviders) + { + return new CSharpCompletionService(workspace, exclusiveProviders); + } + protected override async Task VerifyWorkerAsync(string code, int position, string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, bool experimental, int? glyph) { await VerifyAtPositionAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, experimental, glyph); @@ -138,14 +144,14 @@ protected async Task VerifySendEnterThroughToEnterAsync(string initialMarkup, st var document = workspace.CurrentSolution.GetDocument(documentId); var position = hostDocument.CursorPosition.Value; - var completionList = await GetCompletionListAsync(document, position, CompletionTrigger.Default); - var item = completionList.Items.First(i => i.DisplayText.StartsWith(textTypedSoFar)); + workspace.Options = workspace.Options.WithChangedOption( + CSharpCompletionOptions.AddNewLineOnEnterAfterFullyTypedWord, sendThroughEnterEnabled); - var optionService = workspace.Services.GetService(); - var options = optionService.GetOptions().WithChangedOption(CSharpCompletionOptions.AddNewLineOnEnterAfterFullyTypedWord, sendThroughEnterEnabled); - optionService.SetOptions(options); + var service = GetCompletionService(workspace); + var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Default); + var item = completionList.Items.First(i => i.DisplayText.StartsWith(textTypedSoFar)); - var completionRules = CompletionHelper.GetHelper(document); + var completionRules = CompletionHelper.GetHelper(document, service); Assert.Equal(expected, completionRules.SendEnterThroughToEditor(item, textTypedSoFar, workspace.Options)); } } @@ -166,7 +172,8 @@ private async Task VerifyTextualTriggerCharacterWorkerAsync(string markup, bool var options = workspace.Options.WithChangedOption(CompletionOptions.TriggerOnTypingLetters, LanguageNames.CSharp, triggerOnLetter); var trigger = CompletionTrigger.CreateInsertionTrigger(text[position]); - var isTextualTriggerCharacterResult = CompletionProvider.ShouldTriggerCompletion(text, position + 1, trigger, options); + var service = GetCompletionService(workspace); + var isTextualTriggerCharacterResult = service.ShouldTriggerCompletion(text, position + 1, trigger, options: options); if (expectedTriggerCharacter) { @@ -205,10 +212,11 @@ protected async Task VerifyCommitCharactersAsync(string initialMarkup, string te var document = workspace.CurrentSolution.GetDocument(documentId); var position = hostDocument.CursorPosition.Value; - var completionList = await GetCompletionListAsync(document, position, CompletionTrigger.Default); + var service = GetCompletionService(workspace); + var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Default); var item = completionList.Items.First(i => i.DisplayText.StartsWith(textTypedSoFar)); - var completionRules = CompletionHelper.GetHelper(document); + var completionRules = CompletionHelper.GetHelper(document, service); foreach (var ch in validChars) { diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs index fec8c5ee77426..d8c08578cf234 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs @@ -434,7 +434,8 @@ class C var provider = new CrefCompletionProvider(); var hostDocument = workspace.DocumentWithCursor; var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); - var completionList = await GetCompletionListAsync(provider, document, hostDocument.CursorPosition.Value, CompletionTrigger.Default); + var service = GetCompletionService(workspace); + var completionList = await GetCompletionListAsync(service, document, hostDocument.CursorPosition.Value, CompletionTrigger.Default); } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs index 8cd5559bdfd2a..253078d572e48 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; @@ -576,10 +575,11 @@ void foo() var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); var triggerInfo = CompletionTrigger.CreateInsertionTrigger('a'); - var completionList = await GetCompletionListAsync(document, position, triggerInfo); + var service = GetCompletionService(workspace); + var completionList = await GetCompletionListAsync(service, document, position, triggerInfo); var item = completionList.Items.First(); - var completionRules = CompletionHelper.GetHelper(document); + var completionRules = CompletionHelper.GetHelper(document, service); Assert.False(completionRules.SendEnterThroughToEditor(item, string.Empty, workspace.Options), "Expected false from SendEnterThroughToEditor()"); } @@ -777,7 +777,8 @@ private async Task VerifyExclusiveAsync(string markup, bool exclusive) var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); var triggerInfo = CompletionTrigger.CreateInsertionTrigger('a'); - var completionList = await GetCompletionListContextAsync(document, position, triggerInfo); + var service = GetCompletionService(workspace); + var completionList = await GetCompletionListAsync(service, document, position, triggerInfo); if (completionList != null) { diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs index caadb2e62ee55..3af70e51beaa9 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs @@ -2126,10 +2126,11 @@ public override void set_Bar(int bay, int value) var document = solution.GetDocument(documentId); var triggerInfo = CompletionTrigger.Default; - var completionList = await GetCompletionListAsync(document, position, triggerInfo); + var service = GetCompletionService(testWorkspace); + var completionList = await GetCompletionListAsync(service, document, position, triggerInfo); var completionItem = completionList.Items.First(i => CompareItems(i.DisplayText, "Bar[int bay]")); - var customCommitCompletionProvider = CompletionProvider as ICustomCommitCompletionProvider; + var customCommitCompletionProvider = service.ExclusiveProviders?[0] as ICustomCommitCompletionProvider; if (customCommitCompletionProvider != null) { var textView = testWorkspace.GetTestDocument(documentId).GetTextView(); @@ -2385,10 +2386,11 @@ public override bool Equals(object obj) var document = solution.GetDocument(documentId); var triggerInfo = CompletionTrigger.Default; - var completionList = await GetCompletionListAsync(document, position, triggerInfo); + var service = GetCompletionService(testWorkspace); + var completionList = await GetCompletionListAsync(service, document, position, triggerInfo); var completionItem = completionList.Items.First(i => CompareItems(i.DisplayText, "Equals(object obj)")); - var customCommitCompletionProvider = CompletionProvider as ICustomCommitCompletionProvider; + var customCommitCompletionProvider = service.ExclusiveProviders?[0] as ICustomCommitCompletionProvider; if (customCommitCompletionProvider != null) { var textView = testWorkspace.GetTestDocument(documentId).GetTextView(); @@ -2443,10 +2445,11 @@ public override bool Equals(object obj) var document = solution.GetDocument(documentId); var triggerInfo = CompletionTrigger.Default; - var completionList = await GetCompletionListAsync(document, cursorPosition, triggerInfo); + var service = GetCompletionService(testWorkspace); + var completionList = await GetCompletionListAsync(service, document, cursorPosition, triggerInfo); var completionItem = completionList.Items.First(i => CompareItems(i.DisplayText, "Equals(object obj)")); - var customCommitCompletionProvider = CompletionProvider as ICustomCommitCompletionProvider; + var customCommitCompletionProvider = service.ExclusiveProviders?[0] as ICustomCommitCompletionProvider; if (customCommitCompletionProvider != null) { var textView = testWorkspace.GetTestDocument(documentId).GetTextView(); @@ -2541,20 +2544,24 @@ static void Main(string[] args) override $$ } }"; - var workspace = await TestWorkspace.CreateAsync(LanguageNames.CSharp, new CSharpCompilationOptions(OutputKind.ConsoleApplication), new CSharpParseOptions(), text); - var provider = new OverrideCompletionProvider(); - var testDocument = workspace.Documents.Single(); - var document = workspace.CurrentSolution.GetDocument(testDocument.Id); - var completionList = await GetCompletionListAsync(provider, document, testDocument.CursorPosition.Value, CompletionTrigger.Default); + using (var workspace = await TestWorkspace.CreateAsync(LanguageNames.CSharp, new CSharpCompilationOptions(OutputKind.ConsoleApplication), new CSharpParseOptions(), text)) + { + var provider = new OverrideCompletionProvider(); + var testDocument = workspace.Documents.Single(); + var document = workspace.CurrentSolution.GetDocument(testDocument.Id); + + var service = GetCompletionService(workspace); + var completionList = await GetCompletionListAsync(service, document, testDocument.CursorPosition.Value, CompletionTrigger.Default); - var oldTree = await document.GetSyntaxTreeAsync(); + var oldTree = await document.GetSyntaxTreeAsync(); - var commit = await provider.GetChangeAsync(document, completionList.Items.First(i => i.DisplayText == "ToString()"), ' '); - var changes = commit.TextChanges; + var commit = await provider.GetChangeAsync(document, completionList.Items.First(i => i.DisplayText == "ToString()"), ' '); + var changes = commit.TextChanges; - // If we left the trailing trivia of the close curly of Main alone, - // there should only be one change: the replacement of "override " with a method. - Assert.Equal(changes.Single().Span, TextSpan.FromBounds(136, 145)); + // If we left the trailing trivia of the close curly of Main alone, + // there should only be one change: the replacement of "override " with a method. + Assert.Equal(changes.Single().Span, TextSpan.FromBounds(136, 145)); + } } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs index 21b16d334bc01..9883a440fc191 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs @@ -580,7 +580,10 @@ private async Task VerifyWorkerAsync(string markup, bool isBuilder) private async Task CheckResultsAsync(Document document, int position, bool isBuilder) { var triggerInfo = CompletionTrigger.CreateInsertionTrigger('a'); - var completionList = await GetCompletionListAsync(document, position, triggerInfo); + var service = GetCompletionService(document.Project.Solution.Workspace); + var completionList = await service.GetContextAsync( + service.ExclusiveProviders?[0], document, position, triggerInfo, + options: null, cancellationToken: CancellationToken.None); if (isBuilder) { diff --git a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs index 40db2eba6da43..8a8d48d7ff597 100644 --- a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs +++ b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs @@ -29,7 +29,8 @@ protected CompletionHelper(CompletionService completionService) _rules = CompletionService.GetRules(); } - public static CompletionHelper GetHelper(Workspace workspace, string language) + public static CompletionHelper GetHelper( + Workspace workspace, string language, CompletionService completionService) { var ls = workspace.Services.GetLanguageServices(language); if (ls != null) @@ -37,10 +38,9 @@ public static CompletionHelper GetHelper(Workspace workspace, string language) var factory = ls.GetService(); if (factory != null) { - return factory.CreateCompletionHelper(); + return factory.CreateCompletionHelper(completionService); } - var completionService = ls.GetService(); if (completionService != null) { return new CompletionHelper(completionService); @@ -50,9 +50,9 @@ public static CompletionHelper GetHelper(Workspace workspace, string language) return null; } - public static CompletionHelper GetHelper(Document document) + public static CompletionHelper GetHelper(Document document, CompletionService service) { - return GetHelper(document.Project.Solution.Workspace, document.Project.Language); + return GetHelper(document.Project.Solution.Workspace, document.Project.Language, service); } public IReadOnlyList GetHighlightedSpans(CompletionItem completionItem, string filterText) @@ -446,9 +446,10 @@ protected bool IsObjectCreationItem(CompletionItem item) return item.Tags.Contains(CompletionTags.ObjectCreation); } - public static async Task GetTextChangeAsync(Document document, CompletionItem item, char? commitKey = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task GetTextChangeAsync( + CompletionService service, Document document, CompletionItem item, + char? commitKey = null, CancellationToken cancellationToken = default(CancellationToken)) { - var service = CompletionService.GetService(document); var change = await service.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false); // normally the items that produce multiple changes are not expecting to trigger the behaviors that rely on looking at the text diff --git a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelperFactory.cs b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelperFactory.cs index 51b4dd76db51a..b4e70192d448c 100644 --- a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelperFactory.cs +++ b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelperFactory.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis.Editor { internal abstract class CompletionHelperFactory : ILanguageService { - public abstract CompletionHelper CreateCompletionHelper(); + public abstract CompletionHelper CreateCompletionHelper(CompletionService completionService); } } \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_ReturnKey.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_ReturnKey.cs index c7958b00b1c4e..687f399a46987 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_ReturnKey.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_ReturnKey.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Threading; using Microsoft.CodeAnalysis.Editor.Commands; -using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion { diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs index bb5da8ee21d19..d393f3daa829e 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs @@ -13,6 +13,7 @@ using Microsoft.VisualStudio.Text; using Roslyn.Utilities; using System.Threading; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion { @@ -273,7 +274,8 @@ private CompletionHelper GetCompletionHelper() var document = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document != null) { - return CompletionHelper.GetHelper(document); + return CompletionHelper.GetHelper( + document, document.GetLanguageService()); } return null; diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/DescriptionModifyingPresentationItem.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/DescriptionModifyingPresentationItem.cs index 04cc58b940ab4..5562af6653c7c 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/DescriptionModifyingPresentationItem.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/DescriptionModifyingPresentationItem.cs @@ -36,7 +36,7 @@ public override async Task GetDescriptionAsync(Document d var description = await this.CompletionService.GetDescriptionAsync(document, this.Item, cancellationToken).ConfigureAwait(false); var parts = description.TaggedParts; - var change = await CompletionHelper.GetTextChangeAsync(document, this.Item, '\t').ConfigureAwait(false); + var change = await CompletionHelper.GetTextChangeAsync(this.CompletionService, document, this.Item, '\t').ConfigureAwait(false); var insertionText = change.NewText; var note = string.Empty; diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Presentation/RoslynCompletionSet.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Presentation/RoslynCompletionSet.cs index a30f1b0f3bdbd..acdd8dc727043 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Presentation/RoslynCompletionSet.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Presentation/RoslynCompletionSet.cs @@ -12,7 +12,6 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using VSCompletion = Microsoft.VisualStudio.Language.Intellisense.Completion; -using Microsoft.CodeAnalysis.Snippets; namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.Presentation { @@ -205,7 +204,8 @@ private CompletionHelper GetCompletionHelper() var document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document != null) { - _completionHelper = CompletionHelper.GetHelper(document); + _completionHelper = CompletionHelper.GetHelper(document, + document.Project.LanguageServices.GetService()); } } diff --git a/src/EditorFeatures/Test/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/Test/Completion/AbstractCompletionProviderTests.cs index 381c10db4dd9f..76abd17ffaefa 100644 --- a/src/EditorFeatures/Test/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/Test/Completion/AbstractCompletionProviderTests.cs @@ -28,7 +28,6 @@ public abstract class AbstractCompletionProviderTests : TestB where TWorkspaceFixture : TestWorkspaceFixture, new() { protected readonly Mock MockCompletionSession; - internal CompletionProvider CompletionProvider; protected TWorkspaceFixture WorkspaceFixture; protected AbstractCompletionProviderTests(TWorkspaceFixture workspaceFixture) @@ -36,7 +35,6 @@ protected AbstractCompletionProviderTests(TWorkspaceFixture workspaceFixture) MockCompletionSession = new Mock(MockBehavior.Strict); this.WorkspaceFixture = workspaceFixture; - this.CompletionProvider = CreateCompletionProvider(); } public override void Dispose() @@ -53,20 +51,28 @@ protected static async Task CanUseSpeculativeSemanticModelAsync(Document d return !service.GetMemberBodySpanForSpeculativeBinding(node).IsEmpty; } - internal static CompletionService GetCompletionService(Document document) + internal CompletionServiceWithProviders GetCompletionService(Workspace workspace) { - return CompletionService.GetService(document); + return CreateCompletionService(workspace, ImmutableArray.Create(CreateCompletionProvider())); } - internal static CompletionHelper GetCompletionHelper(Document document) + internal abstract CompletionServiceWithProviders CreateCompletionService( + Workspace workspace, ImmutableArray exclusiveProviders); + + internal static CompletionHelper GetCompletionHelper(Document document, CompletionService service) { - return CompletionHelper.GetHelper(document); + return CompletionHelper.GetHelper(document, service); } - internal static async Task GetCompletionListContextAsync(CompletionProvider provider, Document document, int position, CompletionTrigger triggerInfo, OptionSet options = null) + internal static async Task GetCompletionListContextAsync( + CompletionService service, + CompletionProvider provider, + Document document, + int position, + CompletionTrigger triggerInfo, + OptionSet options = null) { options = options ?? document.Project.Solution.Workspace.Options; - var service = document.Project.LanguageServices.GetService(); var text = await document.GetTextAsync(); var span = service.GetDefaultItemSpan(text, position); var context = new CompletionContext(provider, document, position, span, triggerInfo, options, CancellationToken.None); @@ -74,26 +80,15 @@ internal static async Task GetCompletionListContextAsync(Comp return context; } - internal Task GetCompletionListContextAsync(Document document, int position, CompletionTrigger triggerInfo, OptionSet options = null) - { - return GetCompletionListContextAsync(this.CompletionProvider, document, position, triggerInfo, options); - } - - internal static async Task GetCompletionListAsync(CompletionProvider provider, Document document, int position, CompletionTrigger triggerInfo, OptionSet options = null) - { - var service = GetCompletionService(document); - var context = await GetCompletionListContextAsync(provider, document, position, triggerInfo, options); - var text = await document.GetTextAsync(); - var span = service.GetDefaultItemSpan(text, position); - return CompletionList.Create(span, context.Items.ToImmutableArray(), service.GetRules(), context.SuggestionModeItem); - } - - internal Task GetCompletionListAsync(Document document, int position, CompletionTrigger triggerInfo, OptionSet options = null) + internal Task GetCompletionListAsync( + CompletionService service, + Document document, int position, CompletionTrigger triggerInfo, OptionSet options = null) { - return GetCompletionListAsync(this.CompletionProvider, document, position, triggerInfo, options); + return service.GetCompletionsAsync(document, position, triggerInfo, options: options); } - private async Task CheckResultsAsync(Document document, int position, string expectedItemOrNull, string expectedDescriptionOrNull, bool usePreviousCharAsTrigger, bool checkForAbsence, Glyph? glyph) + private async Task CheckResultsAsync( + Document document, int position, string expectedItemOrNull, string expectedDescriptionOrNull, bool usePreviousCharAsTrigger, bool checkForAbsence, Glyph? glyph) { var code = (await document.GetTextAsync()).ToString(); @@ -104,8 +99,8 @@ private async Task CheckResultsAsync(Document document, int position, string exp trigger = CompletionTrigger.CreateInsertionTrigger(insertedCharacter: code.ElementAt(position - 1)); } - var completionList = await GetCompletionListAsync(document, position, trigger); - var completionService = document.Project.LanguageServices.GetService(); + var completionService = GetCompletionService(document.Project.Solution.Workspace); + var completionList = await GetCompletionListAsync(completionService, document, position, trigger); var items = completionList == null ? default(ImmutableArray) : completionList.Items; if (checkForAbsence) @@ -306,25 +301,28 @@ protected virtual async Task VerifyCustomCommitProviderWorkerAsync(string codeBe private async Task VerifyCustomCommitProviderCheckResultsAsync(Document document, string codeBeforeCommit, int position, string itemToCommit, string expectedCodeAfterCommit, char? commitChar) { - var textBuffer = (await WorkspaceFixture.GetWorkspaceAsync()).Documents.Single().TextBuffer; + var workspace = await WorkspaceFixture.GetWorkspaceAsync(); + var textBuffer = workspace.Documents.Single().TextBuffer; - var items = (await GetCompletionListAsync(document, position, CompletionTrigger.Default)).Items; + var service = GetCompletionService(workspace); + var items = (await GetCompletionListAsync(service, document, position, CompletionTrigger.Default)).Items; var firstItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); - var customCommitCompletionProvider = CompletionProvider as ICustomCommitCompletionProvider; + var customCommitCompletionProvider = service.ExclusiveProviders?[0] as ICustomCommitCompletionProvider; if (customCommitCompletionProvider != null) { - var completionRules = GetCompletionHelper(document); + var completionRules = GetCompletionHelper(document, service); var textView = (await WorkspaceFixture.GetWorkspaceAsync()).Documents.Single().GetTextView(); VerifyCustomCommitWorker(customCommitCompletionProvider, firstItem, completionRules, textView, textBuffer, codeBeforeCommit, expectedCodeAfterCommit, commitChar); } else { - await VerifyCustomCommitWorkerAsync(document, firstItem, codeBeforeCommit, expectedCodeAfterCommit, commitChar); + await VerifyCustomCommitWorkerAsync(service, document, firstItem, codeBeforeCommit, expectedCodeAfterCommit, commitChar); } } - internal virtual async Task VerifyCustomCommitWorkerAsync( + internal async Task VerifyCustomCommitWorkerAsync( + CompletionServiceWithProviders service, Document document, CompletionItem completionItem, string codeBeforeCommit, @@ -335,7 +333,7 @@ internal virtual async Task VerifyCustomCommitWorkerAsync( string actualExpectedCode = null; MarkupTestFile.GetPosition(expectedCodeAfterCommit, out actualExpectedCode, out expectedCaretPosition); - CompletionHelper completionRules = GetCompletionHelper(document); + CompletionHelper completionRules = GetCompletionHelper(document, service); if (commitChar.HasValue && !completionRules.IsCommitCharacter(completionItem, commitChar.Value, string.Empty)) { @@ -343,10 +341,9 @@ internal virtual async Task VerifyCustomCommitWorkerAsync( return; } - var service = CompletionService.GetService(document); - var commit = service.GetChangeAsync(document, completionItem, commitChar).Result; + var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); - var text = document.GetTextAsync().Result; + var text = await document.GetTextAsync(); var newText = text.WithChanges(commit.TextChanges); var newDoc = document.WithText(newText); document.Project.Solution.Workspace.TryApplyChanges(newDoc.Project.Solution); @@ -412,20 +409,22 @@ protected virtual async Task VerifyProviderCommitWorkerAsync(string codeBeforeCo private async Task VerifyProviderCommitCheckResultsAsync(Document document, int position, string itemToCommit, string expectedCodeAfterCommit, char? commitCharOpt, string textTypedSoFar) { - var textBuffer = (await WorkspaceFixture.GetWorkspaceAsync()).Documents.Single().TextBuffer; + var workspace = await WorkspaceFixture.GetWorkspaceAsync(); + var textBuffer = workspace.Documents.Single().TextBuffer; var textSnapshot = textBuffer.CurrentSnapshot.AsText(); - var items = (await GetCompletionListAsync(document, position, CompletionTrigger.Default)).Items; + var service = GetCompletionService(workspace); + var items = (await GetCompletionListAsync(service, document, position, CompletionTrigger.Default)).Items; var firstItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); - var completionRules = GetCompletionHelper(document); + var completionRules = GetCompletionHelper(document, service); var commitChar = commitCharOpt ?? '\t'; var text = await document.GetTextAsync(); if (commitChar == '\t' || completionRules.IsCommitCharacter(firstItem, commitChar, textTypedSoFar)) { - var textChange = CompletionHelper.GetTextChangeAsync(document, firstItem, commitChar).Result; + var textChange = CompletionHelper.GetTextChangeAsync(service, document, firstItem, commitChar).Result; // Adjust TextChange to include commit character, so long as it isn't TAB. if (commitChar != '\t') @@ -445,11 +444,10 @@ private async Task VerifyProviderCommitCheckResultsAsync(Document document, int Assert.Equal(expectedCodeAfterCommit, text.ToString()); } - protected async Task VerifyItemInEditorBrowsableContextsAsync(string markup, string referencedCode, string item, int expectedSymbolsSameSolution, int expectedSymbolsMetadataReference, - string sourceLanguage, string referencedLanguage, bool hideAdvancedMembers = false) + protected async Task VerifyItemInEditorBrowsableContextsAsync( + string markup, string referencedCode, string item, int expectedSymbolsSameSolution, int expectedSymbolsMetadataReference, + string sourceLanguage, string referencedLanguage, bool hideAdvancedMembers = false) { - CompletionProvider = CreateCompletionProvider(); - await VerifyItemWithMetadataReferenceAsync(markup, referencedCode, item, expectedSymbolsMetadataReference, sourceLanguage, referencedLanguage, hideAdvancedMembers); await VerifyItemWithProjectReferenceAsync(markup, referencedCode, item, expectedSymbolsSameSolution, sourceLanguage, referencedLanguage, hideAdvancedMembers); @@ -538,7 +536,8 @@ private Task VerifyItemInSameProjectAsync(string markup, string referencedCode, return VerifyItemWithReferenceWorkerAsync(xmlString, expectedItem, expectedSymbols, hideAdvancedMembers); } - private async Task VerifyItemWithReferenceWorkerAsync(string xmlString, string expectedItem, int expectedSymbols, bool hideAdvancedMembers) + private async Task VerifyItemWithReferenceWorkerAsync( + string xmlString, string expectedItem, int expectedSymbols, bool hideAdvancedMembers) { using (var testWorkspace = await TestWorkspace.CreateAsync(xmlString)) { @@ -552,8 +551,8 @@ private async Task VerifyItemWithReferenceWorkerAsync(string xmlString, string e var triggerInfo = CompletionTrigger.Default; - var completionList = await GetCompletionListAsync(document, position, triggerInfo); - var completionService = document.Project.LanguageServices.GetService(); + var completionService = GetCompletionService(testWorkspace); + var completionList = await GetCompletionListAsync(completionService, document, position, triggerInfo); if (expectedSymbols >= 1) { @@ -596,7 +595,8 @@ protected Task VerifyItemWithMscorlib45Async(string markup, string expectedItem, return VerifyItemWithMscorlib45WorkerAsync(xmlString, expectedItem, expectedDescription); } - private async Task VerifyItemWithMscorlib45WorkerAsync(string xmlString, string expectedItem, string expectedDescription) + private async Task VerifyItemWithMscorlib45WorkerAsync( + string xmlString, string expectedItem, string expectedDescription) { using (var testWorkspace = await TestWorkspace.CreateAsync(xmlString)) { @@ -606,8 +606,8 @@ private async Task VerifyItemWithMscorlib45WorkerAsync(string xmlString, string var document = solution.GetDocument(documentId); var triggerInfo = CompletionTrigger.Default; - var completionList = await GetCompletionListAsync(document, position, triggerInfo); - var completionService = document.Project.LanguageServices.GetService(); + var completionService = GetCompletionService(testWorkspace); + var completionList = await GetCompletionListAsync(completionService, document, position, triggerInfo); var item = completionList.Items.FirstOrDefault(i => i.DisplayText == expectedItem); Assert.Equal(expectedDescription, (await completionService.GetDescriptionAsync(document, item)).Text); @@ -638,8 +638,8 @@ protected async Task VerifyItemInLinkedFilesAsync(string xmlString, string expec var document = solution.GetDocument(currentContextDocumentId); var triggerInfo = CompletionTrigger.Default; - var completionList = await GetCompletionListAsync(document, position, triggerInfo); - var completionService = document.Project.LanguageServices.GetService(); + var completionService = GetCompletionService(testWorkspace); + var completionList = await GetCompletionListAsync(completionService, document, position, triggerInfo); var item = completionList.Items.Single(c => c.DisplayText == expectedItem); Assert.NotNull(item); diff --git a/src/EditorFeatures/Test2/IntelliSense/CompletionRulesTests.vb b/src/EditorFeatures/Test2/IntelliSense/CompletionRulesTests.vb index 56536d16a0a25..d0c49dfc4d1e6 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CompletionRulesTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CompletionRulesTests.vb @@ -47,7 +47,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Private Sub TestMatches(v As String, wordsToMatch() As String) Using New CultureContext("tr-TR") - Dim helper = CompletionHelper.GetHelper(New TestWorkspace, LanguageNames.CSharp) + Dim workspace = New TestWorkspace + Dim helper = CompletionHelper.GetHelper(workspace, LanguageNames.CSharp, + workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetService(Of CompletionService)) For Each word In wordsToMatch Dim item = CompletionItem.Create(word) Assert.True(helper.MatchesFilterText(item, v, CompletionTrigger.Default, CompletionFilterReason.TypeChar), $"Expected item {word} does not match {v}") @@ -57,7 +59,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Private Sub TestNotMatches(v As String, wordsToNotMatch() As String) Using New CultureContext("tr-TR") - Dim helper = CompletionHelper.GetHelper(New TestWorkspace, LanguageNames.CSharp) + Dim workspace = New TestWorkspace + Dim helper = CompletionHelper.GetHelper(workspace, LanguageNames.CSharp, + workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetService(Of CompletionService)) For Each word In wordsToNotMatch Dim item = CompletionItem.Create(word) Assert.False(helper.MatchesFilterText(item, v, CompletionTrigger.Default, CompletionFilterReason.TypeChar), $"Unexpected item {word} matches {v}") diff --git a/src/EditorFeatures/Test2/IntelliSense/TestState.vb b/src/EditorFeatures/Test2/IntelliSense/TestState.vb index 8f0457712c27d..cadacdf933159 100644 --- a/src/EditorFeatures/Test2/IntelliSense/TestState.vb +++ b/src/EditorFeatures/Test2/IntelliSense/TestState.vb @@ -52,7 +52,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Dim languageServices = Me.Workspace.CurrentSolution.Projects.First().LanguageServices Dim language = languageServices.Language - If (extraCompletionProviders IsNot Nothing) Then + If extraCompletionProviders IsNot Nothing Then Dim completionService = DirectCast(languageServices.GetService(Of CompletionService), CompletionServiceWithProviders) completionService.SetTestProviders(extraCompletionProviders.Select(Function(lz) lz.Value).ToList()) End If diff --git a/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb b/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb index ab8d2986551d8..5924ec733076d 100644 --- a/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb +++ b/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb @@ -13,21 +13,17 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Completion Implements ILanguageServiceFactory Public Function CreateLanguageService(languageServices As HostLanguageServices) As ILanguageService Implements ILanguageServiceFactory.CreateLanguageService - Dim completionService = languageServices.GetService(Of CompletionService) - Return New VisualBasicCompletionHelperFactory(completionService) + Return New VisualBasicCompletionHelperFactory() End Function Private Class VisualBasicCompletionHelperFactory Inherits CompletionHelperFactory - Private _completionService As CompletionService - - Public Sub New(completionService As CompletionService) - _completionService = completionService + Public Sub New() End Sub - Public Overrides Function CreateCompletionHelper() As CompletionHelper - Return New VisualBasicCompletionHelper(_completionService) + Public Overrides Function CreateCompletionHelper(service As CompletionService) As CompletionHelper + Return New VisualBasicCompletionHelper(service) End Function End Class End Class diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb index e2a8e1c2a0ae0..64a4c2013f5f5 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb @@ -1,10 +1,12 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Editor.UnitTests.Completion Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Completion Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.CompletionProviders Public MustInherit Class AbstractVisualBasicCompletionProviderTests @@ -14,6 +16,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet MyBase.New(workspaceFixture) End Sub + Friend Overrides Function CreateCompletionService(workspace As Workspace, exclusiveProviders As ImmutableArray(Of CompletionProvider)) As CompletionServiceWithProviders + Return New VisualBasicCompletionService(workspace, exclusiveProviders) + End Function + Protected Overrides Async Function VerifyWorkerAsync(code As String, position As Integer, expectedItemOrNull As String, expectedDescriptionOrNull As String, sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, experimental As Boolean, glyph As Integer?) As Threading.Tasks.Task ' Script/interactive support removed for now. ' TODO: Re-enable these when interactive is back in the product. @@ -73,7 +79,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet Await MyBase.VerifyWorkerAsync(code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, experimental:=experimental, glyph:=glyph) End Function - Private Function VerifyAtEndOfFileAsync(code As String, position As Integer, expectedItemOrNull As String, expectedDescriptionOrNull As String, sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, glyph As Integer?, experimental As Boolean) As Threading.Tasks.Task + Protected Function VerifyAtEndOfFileAsync(code As String, position As Integer, expectedItemOrNull As String, expectedDescriptionOrNull As String, sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, glyph As Integer?, experimental As Boolean) As Threading.Tasks.Task Return VerifyAtEndOfFileAsync(code, position, String.Empty, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, experimental) End Function @@ -111,10 +117,11 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet Dim document = workspace.CurrentSolution.GetDocument(documentId) Dim position = hostDocument.CursorPosition.Value - Dim completionList = Await GetCompletionListAsync(document, position, CompletionTrigger.Default) + Dim service = GetCompletionService(workspace) + Dim completionList = Await GetCompletionListAsync(service, document, position, CompletionTrigger.Default) Dim item = completionList.Items.First(Function(i) i.DisplayText.StartsWith(textTypedSoFar)) - Dim helper = CompletionHelper.GetHelper(document) + Dim helper = CompletionHelper.GetHelper(document, service) Assert.Equal(expected, helper.SendEnterThroughToEditor(item, textTypedSoFar, workspace.Options)) End Using End Function @@ -131,10 +138,11 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet Dim document = workspace.CurrentSolution.GetDocument(documentId) Dim position = hostDocument.CursorPosition.Value - Dim completionList = Await GetCompletionListAsync(document, position, CompletionTrigger.Default) + Dim service = GetCompletionService(workspace) + Dim completionList = Await GetCompletionListAsync(service, document, position, CompletionTrigger.Default) Dim item = completionList.Items.First() - Dim helper = CompletionHelper.GetHelper(document) + Dim helper = CompletionHelper.GetHelper(document, service) For Each ch In chars Assert.True(helper.IsCommitCharacter(item, ch, textTypedSoFar), $"Expected '{ch}' to be a commit character") @@ -197,7 +205,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet Dim text = document.TextBuffer.CurrentSnapshot.AsText() Dim options = workspace.Options.WithChangedOption(CompletionOptions.TriggerOnTypingLetters, LanguageNames.VisualBasic, triggerOnLetter) Dim trigger = CompletionTrigger.CreateInsertionTrigger(text(position)) - Dim isTextualTriggerCharacterResult = CompletionProvider.ShouldTriggerCompletion(text, position + 1, trigger, options) + + Dim completionService = GetCompletionService(workspace) + Dim isTextualTriggerCharacterResult = completionService.ShouldTriggerCompletion( + text, position + 1, trigger, options:=options) If expectedTriggerCharacter Then Dim assertText = "'" & text.ToString(New TextSpan(position, 1)) & "' expected to be textual trigger character" diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb index a0feab3b63da5..4d27b806a1c4f 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb @@ -429,10 +429,10 @@ End Class]]>.Value.NormalizeLineEndings() ' This verifies that the provider is asking for a speculative SemanticModel ' by walking to the node the documentation is attached to. - Dim provider = New CrefCompletionProvider() Dim hostDocument = workspace.DocumentWithCursor Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id) - Dim completionList = Await GetCompletionListAsync(provider, document, hostDocument.CursorPosition.Value, CompletionTrigger.Default) + Dim service = GetCompletionService(workspace) + Dim completionList = Await GetCompletionListAsync(service, document, hostDocument.CursorPosition.Value, CompletionTrigger.Default) End Using End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ImplementsClauseCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ImplementsClauseCompletionProviderTests.vb index 510ed86253dda..15f211222ab30 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ImplementsClauseCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ImplementsClauseCompletionProviderTests.vb @@ -643,10 +643,9 @@ End Interface Using workspace = Await TestWorkspace.CreateAsync(element) Dim position = workspace.Documents.Single().CursorPosition.Value Dim document = workspace.CurrentSolution.GetDocument(workspace.Documents.Single().Id) - - Dim completionList = Await GetCompletionListAsync(document, position, CompletionTrigger.Default) + Dim service = GetCompletionService(workspace) + Dim completionList = Await GetCompletionListAsync(service, document, position, CompletionTrigger.Default) AssertEx.Any(completionList.Items, Function(c) c.DisplayText = "Workcover") - End Using End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb index 6a279ceb071ac..b25bcf393e7aa 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb @@ -406,8 +406,8 @@ End Program Dim hostDocument = workspace.Documents.First() Dim caretPosition = hostDocument.CursorPosition.Value Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id) - - Dim completionList = Await GetCompletionListContextAsync(document, caretPosition, CompletionTrigger.Default) + Dim service = GetCompletionService(workspace) + Dim completionList = Await GetCompletionListAsync(service, document, caretPosition, CompletionTrigger.Default) Assert.True(completionList Is Nothing OrElse completionList.IsExclusive, "Expected always exclusive") End Using End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb index 8c1a3787a77e1..3281d3122a9ef 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb @@ -1746,7 +1746,8 @@ public class C Dim caretPosition = hostDocument.CursorPosition.Value Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id) - Dim completionList = Await GetCompletionListAsync(document, caretPosition, CompletionTrigger.Default) + Dim service = GetCompletionService(workspace) + Dim completionList = Await GetCompletionListAsync(service, document, caretPosition, CompletionTrigger.Default) Assert.False(completionList.Items.Any(Function(c) c.DisplayText = "e")) End Using End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb index 100b9562e6450..553ad04a246ef 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb @@ -1,5 +1,6 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports System.Threading Imports System.Threading.Tasks Imports System.Xml.Linq Imports Microsoft.CodeAnalysis.Completion @@ -328,20 +329,21 @@ End Class Await CheckResultsAsync(document2, position, isBuilder, triggerInfo, options) End If End Using - End Function Private Async Function CheckResultsAsync(document As Document, position As Integer, isBuilder As Boolean, triggerInfo As CompletionTrigger?, options As OptionSet) As Task triggerInfo = If(triggerInfo, CompletionTrigger.CreateInsertionTrigger("a"c)) - Dim completionList = Await GetCompletionListAsync(document, position, triggerInfo.Value, options) + Dim service = GetCompletionService(document.Project.Solution.Workspace) + Dim context = Await service.GetContextAsync( + service.ExclusiveProviders?(0), document, position, triggerInfo.Value, options, CancellationToken.None) If isBuilder Then - Assert.NotNull(completionList) - Assert.NotNull(completionList.SuggestionModeItem) + Assert.NotNull(context) + Assert.NotNull(context.SuggestionModeItem) Else - If completionList IsNot Nothing Then - Assert.True(completionList.SuggestionModeItem Is Nothing, "group.Builder = " & If(completionList.SuggestionModeItem IsNot Nothing, completionList.SuggestionModeItem.DisplayText, "null")) + If context IsNot Nothing Then + Assert.True(context.SuggestionModeItem Is Nothing, "group.Builder = " & If(context.SuggestionModeItem IsNot Nothing, context.SuggestionModeItem.DisplayText, "null")) End If End If End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb index ae628ac4cbbb8..8a2e53afba789 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb @@ -1,5 +1,6 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.CompletionProviders @@ -17,6 +18,11 @@ Namespace Tests Return New XmlDocCommentCompletionProvider() End Function + Protected Overrides Async Function VerifyWorkerAsync(code As String, position As Integer, expectedItemOrNull As String, expectedDescriptionOrNull As String, sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, experimental As Boolean, glyph As Integer?) As Task + Await VerifyAtPositionAsync(code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, experimental) + Await VerifyAtEndOfFileAsync(code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, experimental) + End Function + Private Async Function VerifyItemsExistAsync(markup As String, ParamArray items() As String) As Task For Each item In items Await VerifyItemExistsAsync(markup, item) @@ -340,7 +346,9 @@ Class C End Class " - Await VerifyItemExistsAsync(text, "foo", usePreviousCharAsTrigger:=True) + Await VerifyItemExistsAsync( + text, "foo", + usePreviousCharAsTrigger:=True) End Function diff --git a/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs b/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs index 32850090de92e..7db3c864ebb40 100644 --- a/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs +++ b/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs @@ -50,8 +50,9 @@ internal class CSharpCompletionService : CommonCompletionService private readonly Workspace _workspace; - public CSharpCompletionService(Workspace workspace) - : base(workspace) + public CSharpCompletionService( + Workspace workspace, ImmutableArray? exclusiveProviders = null) + : base(workspace, exclusiveProviders) { _workspace = workspace; } diff --git a/src/Features/CSharp/Portable/Completion/SuggestionMode/CSharpSuggestionModeCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/SuggestionMode/CSharpSuggestionModeCompletionProvider.cs index f85ffb392a787..6838be4de173e 100644 --- a/src/Features/CSharp/Portable/Completion/SuggestionMode/CSharpSuggestionModeCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/SuggestionMode/CSharpSuggestionModeCompletionProvider.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/src/Features/Core/Portable/Completion/CommonCompletionService.cs b/src/Features/Core/Portable/Completion/CommonCompletionService.cs index 1e7be956cfe6a..ea20236f5f7b9 100644 --- a/src/Features/Core/Portable/Completion/CommonCompletionService.cs +++ b/src/Features/Core/Portable/Completion/CommonCompletionService.cs @@ -20,8 +20,10 @@ namespace Microsoft.CodeAnalysis.Completion { internal abstract partial class CommonCompletionService : CompletionServiceWithProviders { - protected CommonCompletionService(Workspace workspace) - : base(workspace) + protected CommonCompletionService( + Workspace workspace, + ImmutableArray? exclusiveProviders) + : base(workspace, exclusiveProviders) { } diff --git a/src/Features/Core/Portable/Completion/CompletionList.cs b/src/Features/Core/Portable/Completion/CompletionList.cs index 903e787a39148..3484135463e62 100644 --- a/src/Features/Core/Portable/Completion/CompletionList.cs +++ b/src/Features/Core/Portable/Completion/CompletionList.cs @@ -36,12 +36,23 @@ public sealed class CompletionList /// public CompletionItem SuggestionModeItem { get; } - private CompletionList(TextSpan defaultSpan, ImmutableArray items, CompletionRules rules, CompletionItem suggestionModeItem) + /// + /// For testing purposes only. + /// + internal bool IsExclusive { get; } + + private CompletionList( + TextSpan defaultSpan, + ImmutableArray items, + CompletionRules rules, + CompletionItem suggestionModeItem, + bool isExclusive) { this.DefaultSpan = defaultSpan; this.Items = items.IsDefault ? ImmutableArray.Empty : items; this.Rules = rules ?? CompletionRules.Default; this.SuggestionModeItem = suggestionModeItem; + this.IsExclusive = isExclusive; } /// @@ -58,7 +69,18 @@ public static CompletionList Create( CompletionRules rules = null, CompletionItem suggestionModeItem = null) { - return new CompletionList(defaultSpan, FixItemSpans(items, defaultSpan), rules, suggestionModeItem); + return Create(defaultSpan, items, rules, suggestionModeItem, isExclusive: false); + } + + internal static CompletionList Create( + TextSpan defaultSpan, + ImmutableArray items, + CompletionRules rules, + CompletionItem suggestionModeItem, + bool isExclusive) + { + return new CompletionList( + defaultSpan, FixItemSpans(items, defaultSpan), rules, suggestionModeItem, isExclusive); } private static ImmutableArray FixItemSpans(ImmutableArray items, TextSpan defaultSpan) @@ -130,7 +152,8 @@ public CompletionList WithSuggestionModeItem(CompletionItem suggestionModeItem) /// /// The default returned when no items are found to populate the list. /// - public static readonly CompletionList Empty - = new CompletionList(default(TextSpan), default(ImmutableArray), CompletionRules.Default, null); + public static readonly CompletionList Empty = new CompletionList( + default(TextSpan), default(ImmutableArray), CompletionRules.Default, + suggestionModeItem: null, isExclusive: false); } } diff --git a/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs b/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs index 457c8b69490dd..34416504a9d37 100644 --- a/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs +++ b/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs @@ -20,15 +20,38 @@ namespace Microsoft.CodeAnalysis.Completion /// /// A subtype of that aggregates completions from one or more s. /// - public abstract class CompletionServiceWithProviders : CompletionService + public abstract class CompletionServiceWithProviders : CompletionService, IEqualityComparer> { private static readonly Func> s_createList = _ => new List(); - private IEnumerable> _importedProviders; + + private readonly object _gate = new object(); + + private readonly Dictionary _nameToProvider = new Dictionary(); + private readonly Dictionary, ImmutableArray> _rolesToProviders; + private readonly Func, ImmutableArray> _createRoleProviders; + private readonly Workspace _workspace; + /// + /// Internal for testing purposes. + /// + internal readonly ImmutableArray? ExclusiveProviders; + + private IEnumerable> _importedProviders; + protected CompletionServiceWithProviders(Workspace workspace) + : this(workspace, exclusiveProviders: null) + { + } + + internal CompletionServiceWithProviders( + Workspace workspace, + ImmutableArray? exclusiveProviders = null) { _workspace = workspace; + ExclusiveProviders = exclusiveProviders; + _rolesToProviders = new Dictionary, ImmutableArray>(this); + _createRoleProviders = CreateRoleProviders; } public override CompletionRules GetRules() @@ -64,45 +87,53 @@ private IEnumerable> GetImp } private ImmutableArray _testProviders = ImmutableArray.Empty; + private object p; internal void SetTestProviders(IEnumerable testProviders) { - _testProviders = testProviders != null ? testProviders.ToImmutableArray() : ImmutableArray.Empty; - _lazyNameToProviderMap = null; + lock (_gate) + { + _testProviders = testProviders != null ? testProviders.ToImmutableArray() : ImmutableArray.Empty; + _rolesToProviders.Clear(); + _nameToProvider.Clear(); + } } - private class RoleProviders + private ImmutableArray CreateRoleProviders(ImmutableHashSet roles) { - public ImmutableArray Providers; + var providers = GetAllProviders(roles); + + foreach (var provider in providers) + { + _nameToProvider[provider.Name] = provider; + } + + return providers; } - private readonly ConditionalWeakTable, RoleProviders> _roleProviders - = new ConditionalWeakTable, RoleProviders>(); + private ImmutableArray GetAllProviders(ImmutableHashSet roles) + { + if (ExclusiveProviders.HasValue) + { + return ExclusiveProviders.Value; + } + + var builtin = GetBuiltInProviders(); + var imported = GetImportedProviders() + .Where(lz => lz.Metadata.Roles == null || lz.Metadata.Roles.Length == 0 || roles.Overlaps(lz.Metadata.Roles)) + .Select(lz => lz.Value); + + var providers = builtin.Concat(imported).Concat(_testProviders); + return providers.ToImmutableArray(); + } protected ImmutableArray GetProviders(ImmutableHashSet roles) { roles = roles ?? ImmutableHashSet.Empty; - RoleProviders providers; - if (!_roleProviders.TryGetValue(roles, out providers)) - { - providers = _roleProviders.GetValue(roles, _ => - { - var builtin = GetBuiltInProviders(); - var imported = GetImportedProviders() - .Where(lz => lz.Metadata.Roles == null || lz.Metadata.Roles.Length == 0 || roles.Overlaps(lz.Metadata.Roles)) - .Select(lz => lz.Value); - return new RoleProviders { Providers = builtin.Concat(imported).ToImmutableArray() }; - }); - } - - if (_testProviders.Length > 0) + lock (_gate) { - return providers.Providers.Concat(_testProviders); - } - else - { - return providers.Providers; + return _rolesToProviders.GetOrAdd(roles, _createRoleProviders); } } @@ -118,47 +149,20 @@ protected virtual ImmutableArray GetProviders(ImmutableHashS } } - private ImmutableDictionary _lazyNameToProviderMap = null; - private ImmutableDictionary NameToProviderMap - { - get - { - if (_lazyNameToProviderMap == null) - { - Interlocked.CompareExchange(ref _lazyNameToProviderMap, CreateNameToProviderMap(), null); - } - - return _lazyNameToProviderMap; - } - } - - private ImmutableDictionary CreateNameToProviderMap() - { - var map = ImmutableDictionary.Empty; - - foreach (var provider in GetBuiltInProviders().Concat(GetImportedProviders().Select(lz => lz.Value)).Concat(_testProviders)) - { - if (!map.ContainsKey(provider.Name)) - { - map = map.Add(provider.Name, provider); - } - } - - return map; - } - internal protected CompletionProvider GetProvider(CompletionItem item) { string name; - CompletionProvider provider; + CompletionProvider provider = null; - if (item.Properties.TryGetValue("Provider", out name) - && this.NameToProviderMap.TryGetValue(name, out provider)) + if (item.Properties.TryGetValue("Provider", out name)) { - return provider; + lock (_gate) + { + _nameToProvider.TryGetValue(name, out provider); + } } - return null; + return provider; } public override async Task GetCompletionsAsync( @@ -201,7 +205,9 @@ public override async Task GetCompletionsAsync( var completionLists = new List(); foreach (var provider in triggeredProviders) { - var completionList = await GetProviderCompletionsAsync(provider, document, caretPosition, defaultItemSpan, trigger, options, cancellationToken).ConfigureAwait(false); + var completionList = await GetContextAsync( + provider, document, caretPosition, trigger, + options, defaultItemSpan, cancellationToken).ConfigureAwait(false); if (completionList != null) { completionLists.Add(completionList); @@ -214,7 +220,9 @@ public override async Task GetCompletionsAsync( if (firstExclusiveList != null) { - return MergeAndPruneCompletionLists(SpecializedCollections.SingletonEnumerable(firstExclusiveList), defaultItemSpan); + return MergeAndPruneCompletionLists( + SpecializedCollections.SingletonEnumerable(firstExclusiveList), defaultItemSpan, + isExclusive: true); } // If no exclusive providers provided anything, then go through the remaining @@ -234,7 +242,7 @@ public override async Task GetCompletionsAsync( var nonUsedNonExclusiveLists = new List(); foreach (var provider in nonUsedProviders) { - var completionList = await GetProviderCompletionsAsync(provider, document, caretPosition, defaultItemSpan, trigger, options, cancellationToken).ConfigureAwait(false); + var completionList = await GetContextAsync(provider, document, caretPosition, trigger, options, defaultItemSpan, cancellationToken).ConfigureAwait(false); if (completionList != null && !completionList.IsExclusive) { nonUsedNonExclusiveLists.Add(completionList); @@ -251,10 +259,11 @@ public override async Task GetCompletionsAsync( // groups are properly ordered based on the original providers. allProvidersAndLists.Sort((p1, p2) => completionProviderToIndex[p1.Provider] - completionProviderToIndex[p2.Provider]); - return MergeAndPruneCompletionLists(allProvidersAndLists, defaultItemSpan); + return MergeAndPruneCompletionLists(allProvidersAndLists, defaultItemSpan, isExclusive: false); } - private CompletionList MergeAndPruneCompletionLists(IEnumerable completionLists, TextSpan contextSpan) + private CompletionList MergeAndPruneCompletionLists( + IEnumerable completionLists, TextSpan contextSpan, bool isExclusive) { var displayNameToItemsMap = new Dictionary>(); CompletionItem suggestionModeItem = null; @@ -282,7 +291,9 @@ private CompletionList MergeAndPruneCompletionLists(IEnumerable GetCompletionProviderToIndex(IEnumer return result; } - private static async Task GetProviderCompletionsAsync( + // Internal for testing purposes only. + internal async Task GetContextAsync( CompletionProvider provider, Document document, int position, - TextSpan defaultFilterSpan, CompletionTrigger triggerInfo, OptionSet options, CancellationToken cancellationToken) { - var context = new CompletionContext(provider, document, position, defaultFilterSpan, triggerInfo, options, cancellationToken); + return await GetContextAsync( + provider, document, position, triggerInfo, + options, defaultSpan: null, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + private async Task GetContextAsync( + CompletionProvider provider, + Document document, + int position, + CompletionTrigger triggerInfo, + OptionSet options, + TextSpan? defaultSpan, + CancellationToken cancellationToken) + { + options = options ?? document.Project.Solution.Workspace.Options; + + if (defaultSpan == null) + { + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + defaultSpan = this.GetDefaultItemSpan(text, position); + } + + var context = new CompletionContext(provider, document, position, defaultSpan.Value, triggerInfo, options, cancellationToken); await provider.ProvideCompletionsAsync(context).ConfigureAwait(false); return context; } @@ -386,7 +419,8 @@ public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, return providers.Any(p => p.ShouldTriggerCompletion(text, caretPosition, trigger, options)); } - public override async Task GetChangeAsync(Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken) + public override async Task GetChangeAsync( + Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken) { var provider = GetProvider(item); if (provider != null) @@ -398,5 +432,39 @@ public override async Task GetChangeAsync(Document document, C return CompletionChange.Create(ImmutableArray.Create(new TextChange(item.Span, item.DisplayText))); } } + + bool IEqualityComparer>.Equals(ImmutableHashSet x, ImmutableHashSet y) + { + if (x == y) + { + return true; + } + + if (x.Count != y.Count) + { + return false; + } + + foreach (var v in x) + { + if (!y.Contains(v)) + { + return false; + } + } + + return true; + } + + int IEqualityComparer>.GetHashCode(ImmutableHashSet obj) + { + var hash = 0; + foreach (var o in obj) + { + hash += o.GetHashCode(); + } + + return hash; + } } } diff --git a/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb b/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb index caaecdc830a80..54620ed7486fe 100644 --- a/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb +++ b/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb @@ -45,8 +45,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion Private ReadOnly _workspace As Workspace - Public Sub New(workspace As Workspace) - MyBase.New(workspace) + Public Sub New(workspace As Workspace, + Optional exclusiveProviders As ImmutableArray(Of CompletionProvider) ? = Nothing) + MyBase.New(workspace, exclusiveProviders) _workspace = workspace End Sub diff --git a/src/Interactive/EditorFeatures/Core/Completion/AbstractReferenceDirectiveCompletionProvider.cs b/src/Interactive/EditorFeatures/Core/Completion/AbstractReferenceDirectiveCompletionProvider.cs index 8d333c310fb60..d8962aea2ec56 100644 --- a/src/Interactive/EditorFeatures/Core/Completion/AbstractReferenceDirectiveCompletionProvider.cs +++ b/src/Interactive/EditorFeatures/Core/Completion/AbstractReferenceDirectiveCompletionProvider.cs @@ -40,7 +40,8 @@ private static ICurrentWorkingDirectoryDiscoveryService GetFileSystemDiscoverySe private static readonly ImmutableArray s_filterRules = ImmutableArray.Empty; - private static readonly CompletionItemRules s_rules = CompletionItemRules.Create(filterCharacterRules: s_filterRules, commitCharacterRules: s_commitRules); + private static readonly CompletionItemRules s_rules = CompletionItemRules.Create( + filterCharacterRules: s_filterRules, commitCharacterRules: s_commitRules, enterKeyRule: EnterKeyRule.Never); public override async Task ProvideCompletionsAsync(CompletionContext context) { diff --git a/src/VisualStudio/Core/Test/DebuggerIntelliSense/TestState.vb b/src/VisualStudio/Core/Test/DebuggerIntelliSense/TestState.vb index 70c30b08a44a3..7142961d70ddd 100644 --- a/src/VisualStudio/Core/Test/DebuggerIntelliSense/TestState.vb +++ b/src/VisualStudio/Core/Test/DebuggerIntelliSense/TestState.vb @@ -57,7 +57,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.DebuggerIntelliSense Dim languageServices = Me.Workspace.CurrentSolution.Projects.First().LanguageServices Dim language = languageServices.Language - If (extraCompletionProviders IsNot Nothing) Then + If extraCompletionProviders IsNot Nothing Then Dim completionService = DirectCast(languageServices.GetService(Of CompletionService), CommonCompletionService) completionService.SetTestProviders(extraCompletionProviders.Select(Function(lz) lz.Value).ToList()) End If