提交 9bccbd0d 编写于 作者: D David Barbet

Merge remote-tracking branch 'upstream/features/lspSupport' into merge_lsp_to_master

......@@ -118,6 +118,8 @@ internal static bool IsValidClrNamespaceName(this string name)
return lastChar != '.';
}
private const string AttributeSuffix = "Attribute";
internal static string GetWithSingleAttributeSuffix(
this string name,
bool isCaseSensitive)
......@@ -128,7 +130,7 @@ internal static bool IsValidClrNamespaceName(this string name)
name = cleaned;
}
return name + "Attribute";
return name + AttributeSuffix;
}
internal static bool TryGetWithoutAttributeSuffix(
......@@ -150,9 +152,7 @@ internal static bool IsValidClrNamespaceName(this string name)
bool isCaseSensitive,
out string result)
{
const string AttributeSuffix = "Attribute";
var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
if (name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison))
if (name.HasAttributeSuffix(isCaseSensitive))
{
result = name.Substring(0, name.Length - AttributeSuffix.Length);
return true;
......@@ -162,6 +162,12 @@ internal static bool IsValidClrNamespaceName(this string name)
return false;
}
internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive)
{
var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
return name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison);
}
internal static bool IsValidUnicodeString(this string str)
{
int i = 0;
......
......@@ -907,6 +907,278 @@ class Bat
AssertRelativeOrder(new List<string>() { "SomeType", "SomeTypeWithLongerName" }, completionList.Items);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task AttributeTypeInAttributeNameContext()
{
var file1 = @"
namespace Foo
{
public class MyAttribute : System.Attribute { }
public class MyAttributeWithoutSuffix : System.Attribute { }
public class MyClass { }
}";
var file2 = @"
namespace Test
{
[$$
class Program { }
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyTypeImportItemExistsAsync(markup, "My", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttribute");
await VerifyTypeImportItemIsAbsentAsync(markup, "MyAttributeWithoutSuffix", inlineDescription: "Foo"); // We intentionally ignore attribute types without proper suffix for perf reason
await VerifyTypeImportItemIsAbsentAsync(markup, "MyAttribute", inlineDescription: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "MyClass", inlineDescription: "Foo");
}
[InlineData(SourceCodeKind.Regular)]
[InlineData(SourceCodeKind.Script)]
[WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task CommitAttributeTypeInAttributeNameContext(SourceCodeKind kind)
{
var file1 = @"
namespace Foo
{
public class MyAttribute : System.Attribute { }
}";
var file2 = @"
namespace Test
{
[$$
class Program { }
}";
string expectedCodeAfterCommit = @"
using Foo;
namespace Test
{
[My$$
class Program { }
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyCustomCommitProviderAsync(markup, "My", expectedCodeAfterCommit, sourceCodeKind: kind);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task AttributeTypeInNonAttributeNameContext()
{
var file1 = @"
namespace Foo
{
public class MyAttribute : System.Attribute { }
public class MyAttributeWithoutSuffix : System.Attribute { }
public class MyClass { }
}";
var file2 = @"
namespace Test
{
class Program
{
$$
}
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyTypeImportItemExistsAsync(markup, "MyAttribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttribute");
await VerifyTypeImportItemExistsAsync(markup, "MyAttributeWithoutSuffix", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttributeWithoutSuffix");
await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyClass");
}
[InlineData(SourceCodeKind.Regular)]
[InlineData(SourceCodeKind.Script)]
[WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task CommitAttributeTypeInNonAttributeNameContext(SourceCodeKind kind)
{
var file1 = @"
namespace Foo
{
public class MyAttribute : System.Attribute { }
}";
var file2 = @"
namespace Test
{
class Program
{
$$
}
}";
string expectedCodeAfterCommit = @"
using Foo;
namespace Test
{
class Program
{
MyAttribute$$
}
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyCustomCommitProviderAsync(markup, "MyAttribute", expectedCodeAfterCommit, sourceCodeKind: kind);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task AttributeTypeWithoutSuffixInAttributeNameContext()
{
// attribute suffix isn't capitalized
var file1 = @"
namespace Foo
{
public class Myattribute : System.Attribute { }
public class MyClass { }
}";
var file2 = @"
namespace Test
{
[$$
class Program { }
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute");
await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "MyClass", inlineDescription: "Foo");
}
[InlineData(SourceCodeKind.Regular)]
[InlineData(SourceCodeKind.Script)]
[WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task CommitAttributeTypeWithoutSuffixInAttributeNameContext(SourceCodeKind kind)
{
// attribute suffix isn't capitalized
var file1 = @"
namespace Foo
{
public class Myattribute : System.Attribute { }
}";
var file2 = @"
namespace Test
{
[$$
class Program { }
}";
string expectedCodeAfterCommit = @"
using Foo;
namespace Test
{
[Myattribute$$
class Program { }
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyCustomCommitProviderAsync(markup, "Myattribute", expectedCodeAfterCommit, sourceCodeKind: kind);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task AttributeTypeWithoutSuffixInNonAttributeNameContext()
{
// attribute suffix isn't capitalized
var file1 = @"
namespace Foo
{
public class Myattribute : System.Attribute { }
public class MyClass { }
}";
var file2 = @"
namespace Test
{
class Program
{
$$
}
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute");
await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyClass");
}
[InlineData(SourceCodeKind.Regular)]
[InlineData(SourceCodeKind.Script)]
[WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task CommitAttributeTypeWithoutSuffixInNonAttributeNameContext(SourceCodeKind kind)
{
// attribute suffix isn't capitalized
var file1 = @"
namespace Foo
{
public class Myattribute : System.Attribute { }
}";
var file2 = @"
namespace Test
{
class Program
{
$$
}
}";
string expectedCodeAfterCommit = @"
using Foo;
namespace Test
{
class Program
{
Myattribute$$
}
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyCustomCommitProviderAsync(markup, "Myattribute", expectedCodeAfterCommit, sourceCodeKind: kind);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
[WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")]
public async Task VBAttributeTypeWithoutSuffixInAttributeNameContext()
{
var file1 = @"
Namespace Foo
Public Class Myattribute
Inherits System.Attribute
End Class
Public Class MyVBClass
End Class
End Namespace";
var file2 = @"
namespace Test
{
[$$
class Program
{
}
}";
var markup = CreateMarkupForProjecWithProjectReference(file2, file1, LanguageNames.CSharp, LanguageNames.VisualBasic);
await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute");
await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "MyVBClass", inlineDescription: "Foo");
}
private static void AssertRelativeOrder(List<string> expectedTypesInRelativeOrder, ImmutableArray<CompletionItem> allCompletionItems)
{
var hashset = new HashSet<string>(expectedTypesInRelativeOrder);
......@@ -919,9 +1191,9 @@ private static void AssertRelativeOrder(List<string> expectedTypesInRelativeOrde
}
}
private Task VerifyTypeImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null)
private Task VerifyTypeImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null, string expectedDescriptionOrNull = null)
{
return VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription);
return VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull);
}
......
......@@ -172,7 +172,7 @@ internal CommitManager(ITextView textView, RecentItemsManager recentItemsManager
roslynItem, completionListSpan, commitChar, triggerSnapshot, serviceRules,
filterText, cancellationToken);
_recentItemsManager.MakeMostRecentItem(roslynItem.DisplayText);
_recentItemsManager.MakeMostRecentItem(roslynItem.FilterText);
return new AsyncCompletionData.CommitResult(isHandled: true, commitBehavior);
}
......
......@@ -4,6 +4,8 @@
using Microsoft.VisualStudio.Text;
using AsyncCompletionData = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using RoslynTrigger = Microsoft.CodeAnalysis.Completion.CompletionTrigger;
using RoslynCompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem;
using VSCompletionItem = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionItem;
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion
{
......@@ -63,7 +65,7 @@ internal static CompletionFilterReason GetFilterReason(AsyncCompletionData.Compl
}
}
internal static bool IsFilterCharacter(CompletionItem item, char ch, string textTypedSoFar)
internal static bool IsFilterCharacter(RoslynCompletionItem item, char ch, string textTypedSoFar)
{
// First see if the item has any specific filter rules it wants followed.
foreach (var rule in item.Rules.FilterCharacterRules)
......@@ -97,5 +99,15 @@ internal static bool IsFilterCharacter(CompletionItem item, char ch, string text
return false;
}
// This is a temporarily method to support preference of IntelliCode items comparing to non-IntelliCode items.
// We expect that Editor will intorduce this support and we will get rid of relying on the "★" then.
internal static bool IsPreferredItem(this RoslynCompletionItem completionItem)
=> completionItem.DisplayText.StartsWith("★");
// This is a temporarily method to support preference of IntelliCode items comparing to non-IntelliCode items.
// We expect that Editor will intorduce this support and we will get rid of relying on the "★" then.
internal static bool IsPreferredItem(this VSCompletionItem completionItem)
=> completionItem.DisplayText.StartsWith("★");
}
}
......@@ -293,12 +293,12 @@ private static bool IsAfterDot(ITextSnapshot snapshot, ITrackingSpan applicableT
if (bestItem != null)
{
selectedItemIndex = itemsInList.IndexOf(i => Equals(i.FilterResult.CompletionItem, bestItem));
var deduplicatedList = matchingItems.Where(r => !r.DisplayText.StartsWith("★"));
var deduplicatedListCount = matchingItems.Where(r => !r.IsPreferredItem()).Count();
if (selectedItemIndex > -1 &&
deduplicatedList.Count() == 1 &&
deduplicatedListCount == 1 &&
filterText.Length > 0)
{
var uniqueItemIndex = itemsInList.IndexOf(i => Equals(i.FilterResult.CompletionItem, deduplicatedList.First()));
var uniqueItemIndex = itemsInList.IndexOf(i => Equals(i.FilterResult.CompletionItem, bestItem));
uniqueItem = highlightedList[uniqueItemIndex].CompletionItem;
}
}
......@@ -391,7 +391,7 @@ private static bool IsAfterDot(ITextSnapshot snapshot, ITrackingSpan applicableT
hardSelect = false;
}
var deduplicatedListCount = matchingItems.Where(r => !r.VSCompletionItem.DisplayText.StartsWith("★")).Count();
var deduplicatedListCount = matchingItems.Where(r => !r.VSCompletionItem.IsPreferredItem()).Count();
return new FilteredCompletionModel(
highlightedList, index, filters,
......@@ -493,7 +493,8 @@ private static bool ShouldBeFilteredOutOfCompletionList(VSCompletionItem item, I
var mruIndex1 = GetRecentItemIndex(recentItems, bestItem);
var mruIndex2 = GetRecentItemIndex(recentItems, chosenItem);
if (mruIndex2 < mruIndex1)
if ((mruIndex2 < mruIndex1) ||
(mruIndex2 == mruIndex1 && !bestItem.IsPreferredItem() && chosenItem.IsPreferredItem()))
{
bestItem = chosenItem;
}
......@@ -513,7 +514,8 @@ private static bool ShouldBeFilteredOutOfCompletionList(VSCompletionItem item, I
var bestItemPriority = bestItem.Rules.MatchPriority;
var currentItemPriority = chosenItem.Rules.MatchPriority;
if (currentItemPriority > bestItemPriority)
if ((currentItemPriority > bestItemPriority) ||
((currentItemPriority == bestItemPriority) && !bestItem.IsPreferredItem() && chosenItem.IsPreferredItem()))
{
bestItem = chosenItem;
}
......@@ -524,7 +526,7 @@ private static bool ShouldBeFilteredOutOfCompletionList(VSCompletionItem item, I
internal static int GetRecentItemIndex(ImmutableArray<string> recentItems, RoslynCompletionItem item)
{
var index = recentItems.IndexOf(item.DisplayText);
var index = recentItems.IndexOf(item.FilterText);
return -index;
}
......@@ -565,7 +567,13 @@ internal static bool IsBetterDeletionMatch(FilterResult result1, FilterResult re
{
return true;
}
if (result1.CompletionItem.IsPreferredItem() && !result2.CompletionItem.IsPreferredItem())
{
return true;
}
}
return false;
}
......
......@@ -5057,13 +5057,112 @@ class C
state.SendTypeChars(".len")
Await state.AssertCompletionSession()
state.AssertCompletionItemsContainAll({"Length", "★ Length3", "★ Length2"})
state.AssertCompletionItemsContainAll({"Length", "★ Length", "★ Length2"})
state.SendCommitUniqueCompletionListItem()
Await state.AssertNoCompletionSession()
Assert.Contains("s.Length", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal)
End Using
End Function
' Implementation for the Modern completion only
<InlineData(CompletionImplementation.Modern)>
<WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function IntelliCodeItemPreferredAfterCommitingIntelliCodeItem(completionImplementation As CompletionImplementation) As Task
Dim provider = New IntelliCodeMockProvider()
Using state = TestStateFactory.CreateCSharpTestState(completionImplementation,
<Document>
class C
{
void Method()
{
var s = "";
s$$
}
}
</Document>, {provider})
state.Workspace.Options = state.Workspace.Options.WithChangedOption(
CompletionOptions.TriggerOnDeletion, LanguageNames.CSharp, True)
state.SendTypeChars(".nor")
Await state.AssertCompletionSession()
state.AssertCompletionItemsContainAll({"Normalize", "★ Normalize"})
Await state.AssertSelectedCompletionItem("★ Normalize", displayTextSuffix:="()")
state.SendTab()
Await state.AssertNoCompletionSession()
Assert.Contains("s.Normalize", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal)
For i = 1 To "ze".Length
state.SendBackspace()
Next
Await state.AssertSelectedCompletionItem("★ Normalize", displayTextSuffix:="()")
state.SendEscape()
For i = 1 To "Normali".Length
state.SendBackspace()
Next
state.SendEscape()
Assert.Contains("s.", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal)
state.SendInvokeCompletionList()
Await state.AssertSelectedCompletionItem("★ Normalize", displayTextSuffix:="()")
state.SendEscape()
state.SendTypeChars("n")
Await state.AssertSelectedCompletionItem("★ Normalize", displayTextSuffix:="()")
End Using
End Function
' Implementation for the Modern completion only
<InlineData(CompletionImplementation.Modern)>
<WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function IntelliCodeItemPreferredAfterCommitingNonIntelliCodeItem(completionImplementation As CompletionImplementation) As Task
Dim provider = New IntelliCodeMockProvider()
Using state = TestStateFactory.CreateCSharpTestState(completionImplementation,
<Document>
class C
{
void Method()
{
var s = "";
s$$
}
}
</Document>, {provider})
state.Workspace.Options = state.Workspace.Options.WithChangedOption(
CompletionOptions.TriggerOnDeletion, LanguageNames.CSharp, True)
state.SendTypeChars(".nor")
Await state.AssertCompletionSession()
state.AssertCompletionItemsContainAll({"Normalize", "★ Normalize"})
Await state.AssertSelectedCompletionItem("★ Normalize", displayTextSuffix:="()")
state.NavigateToDisplayText("Normalize")
state.SendTab()
Await state.AssertNoCompletionSession()
Assert.Contains("s.Normalize", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal)
For i = 1 To "ze".Length
state.SendBackspace()
Next
Await state.AssertSelectedCompletionItem("★ Normalize", displayTextSuffix:="()")
state.SendEscape()
For i = 1 To "Normali".Length
state.SendBackspace()
Next
state.SendEscape()
Assert.Contains("s.", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal)
state.SendInvokeCompletionList()
Await state.AssertSelectedCompletionItem("★ Normalize", displayTextSuffix:="()")
state.SendEscape()
state.SendTypeChars("n")
Await state.AssertSelectedCompletionItem("★ Normalize", displayTextSuffix:="()")
End Using
End Function
<WorkItem(35614, "https://github.com/dotnet/roslyn/issues/35614")>
<MemberData(NameOf(AllCompletionImplementations))>
<WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)>
......@@ -5451,33 +5550,37 @@ class C
Public Overrides Function ProvideCompletionsAsync(context As CompletionContext) As Task
context.AddItem(CompletionItem.Create(displayText:="★ Length", filterText:="Length"))
context.AddItem(CompletionItem.Create(displayText:="★ Normalize", filterText:="Normalize"))
context.AddItem(CompletionItem.Create(displayText:="★ Normalize", filterText:="Normalize", displayTextSuffix:="()"))
context.AddItem(CompletionItem.Create(displayText:="Length", filterText:="Length"))
context.AddItem(CompletionItem.Create(displayText:="ToString()", filterText:="ToString"))
context.AddItem(CompletionItem.Create(displayText:="First()", filterText:="First"))
context.AddItem(CompletionItem.Create(displayText:="ToString", filterText:="ToString", displayTextSuffix:="()"))
context.AddItem(CompletionItem.Create(displayText:="First", filterText:="First", displayTextSuffix:="()"))
Return Task.CompletedTask
End Function
Public Overrides Function ShouldTriggerCompletion(text As SourceText, caretPosition As Integer, trigger As CompletionTrigger, options As OptionSet) As Boolean
Return True
End Function
Public Overrides Function GetChangeAsync(document As Document, item As CompletionItem, commitKey As Char?, cancellationToken As CancellationToken) As Task(Of CompletionChange)
Dim commitText = item.DisplayText
If commitText.StartsWith("★") Then
' remove the star and the following space
commitText = commitText.Substring(2)
End If
Return Task.FromResult(CompletionChange.Create(New TextChange(item.Span, commitText)))
End Function
End Class
' Simulates a situation where IntelliCode provides items not included into the Rolsyn original list.
' We want to ignore these items in CommitIfUnique.
' This situation should not happen. Tests with this provider were added to cover protective scenarios.
Private Class IntelliCodeMockWeirdProvider
Inherits CompletionProvider
Inherits IntelliCodeMockProvider
Public Overrides Function ProvideCompletionsAsync(context As CompletionContext) As Task
context.AddItem(CompletionItem.Create(displayText:="★ Length2", filterText:="Length2"))
context.AddItem(CompletionItem.Create(displayText:="Length", filterText:="Length"))
context.AddItem(CompletionItem.Create(displayText:="★ Length3", filterText:="Length3"))
Return Task.CompletedTask
End Function
Public Overrides Function ShouldTriggerCompletion(text As SourceText, caretPosition As Integer, trigger As CompletionTrigger, options As OptionSet) As Boolean
Return True
Public Overrides Async Function ProvideCompletionsAsync(context As CompletionContext) As Task
Await MyBase.ProvideCompletionsAsync(context).ConfigureAwait(False)
context.AddItem(CompletionItem.Create(displayText:="★ Length2", filterText:="Length"))
End Function
End Class
End Class
......
......@@ -9,7 +9,6 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.LanguageServices;
......
......@@ -153,7 +153,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Dim completionItems = session.GetComputedItems(CancellationToken.None)
' During the computation we can explicitly dismiss the session or we can return no items.
' Each of these conditions mean that there is no active completion.
Assert.True(session.IsDismissed OrElse completionItems.Items.Count() = 0)
Assert.True(session.IsDismissed OrElse completionItems.Items.Count() = 0, "AssertNoCompletionSession")
End Function
Public Overrides Sub AssertNoCompletionSessionWithNoBlock()
......@@ -188,7 +188,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Await WaitForAsynchronousOperationsAsync()
Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(view)
Assert.NotNull(session)
Assert.True(session IsNot Nothing, "AssertCompletionSession")
End Function
Public Overrides Async Function AssertCompletionSessionAfterTypingHash() As Task
......@@ -291,15 +291,6 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
End If
End Sub
Private Function GetRoslynCompletionItemOpt(editorCompletionItem As Data.CompletionItem) As CompletionItem
Dim roslynCompletionItem As CompletionItem = Nothing
If editorCompletionItem?.Properties.TryGetProperty(RoslynItem, roslynCompletionItem) Then
Return roslynCompletionItem
End If
Return Nothing
End Function
Public Overrides Function GetCompletionItems() As IList(Of CompletionItem)
WaitForAsynchronousOperationsAsync()
Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView)
......
......@@ -259,6 +259,31 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Public MustOverride Function WaitForUIRenderedAsync() As Task
Public Sub NavigateToDisplayText(targetText As String)
Dim currentText = GetSelectedItem().DisplayText
' GetComputedItems provided by the Editor for tests does not guarantee that
' the order of items match the order of items actually displayed in the completion popup.
' For example, they put starred items (intellicode) below non-starred ones.
' And the order they display those items in the UI is opposite.
' Therefore, we do the full traverse: down to the bottom and if not found up to the top.
Do While currentText <> targetText
SendDownKey()
Dim newText = GetSelectedItem().DisplayText
If currentText = newText Then
' Nothing found on going down. Try going up
Do While currentText <> targetText
SendUpKey()
newText = GetSelectedItem().DisplayText
Assert.True(newText <> currentText, "Reached the bottom, then the top and didn't find the match")
currentText = newText
Loop
End If
currentText = newText
Loop
End Sub
#End Region
#Region "Signature Help Operations"
......
' 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.Completion
Imports Microsoft.CodeAnalysis.Editor.UnitTests
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Experiments
Imports Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Imports Microsoft.VisualStudio.Composition
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.CompletionProviders
<UseExportProvider>
Public Class TypeImportCompletionProviderTests
Inherits AbstractVisualBasicCompletionProviderTests
Public Sub New(workspaceFixture As VisualBasicTestWorkspaceFixture)
MyBase.New(workspaceFixture)
End Sub
Private Property ShowImportCompletionItemsOptionValue As Boolean = True
Protected Overrides Sub SetWorkspaceOptions(workspace As TestWorkspace)
workspace.Options = workspace.Options.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic, ShowImportCompletionItemsOptionValue)
End Sub
Protected Overrides Function GetExportProvider() As ExportProvider
Return ExportProviderCache.GetOrCreateExportProviderFactory(TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic.WithPart(GetType(TestExperimentationService))).CreateExportProvider()
End Function
Friend Overrides Function CreateCompletionProvider() As CompletionProvider
Return New TypeImportCompletionProvider()
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
<WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")>
Public Async Function AttributeTypeInAttributeNameContext() As Task
Dim file1 = <Text>
Namespace Foo
Public Class MyAttribute
Inherits System.Attribute
End Class
Public Class MyVBClass
End Class
Public Class MyAttributeWithoutSuffix
Inherits System.Attribute
End Class
End Namespace</Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
<$$
Sub Main()
End Sub
End Class]]></Text>.Value
Dim markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.VisualBasic)
Await VerifyItemExistsAsync(markup, "My", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.MyAttribute")
Await VerifyItemIsAbsentAsync(markup, "MyAttributeWithoutSuffix", inlineDescription:="Foo") ' We intentionally ignore attribute types without proper suffix for perf reason
Await VerifyItemIsAbsentAsync(markup, "MyAttribute", inlineDescription:="Foo")
Await VerifyItemIsAbsentAsync(markup, "MyVBClass", inlineDescription:="Foo")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
<WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")>
Public Async Function AttributeTypeInNonAttributeNameContext() As Task
Dim file1 = <Text>
Namespace Foo
Public Class MyAttribute
Inherits System.Attribute
End Class
Public Class MyVBClass
End Class
Public Class MyAttributeWithoutSuffix
Inherits System.Attribute
End Namespace</Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
Sub Main()
Dim x As $$
End Sub
End Class]]></Text>.Value
Dim markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.VisualBasic)
Await VerifyItemExistsAsync(markup, "MyAttribute", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.MyAttribute")
Await VerifyItemExistsAsync(markup, "MyAttributeWithoutSuffix", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.MyAttributeWithoutSuffix")
Await VerifyItemExistsAsync(markup, "MyVBClass", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.MyVBClass")
Await VerifyItemIsAbsentAsync(markup, "My", inlineDescription:="Foo")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
<WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")>
Public Async Function AttributeTypeInAttributeNameContext2() As Task
' attribute suffix isn't capitalized
Dim file1 = <Text>
Namespace Foo
Public Class Myattribute
Inherits System.Attribute
End Class
End Namespace</Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
<$$
Sub Main()
End Sub
End Class]]></Text>.Value
Dim markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.VisualBasic)
Await VerifyItemExistsAsync(markup, "My", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.Myattribute")
Await VerifyItemIsAbsentAsync(markup, "Myattribute", inlineDescription:="Foo")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
<WorkItem(35540, "https://github.com/dotnet/roslyn/issues/35540")>
Public Async Function CSharpAttributeTypeWithoutSuffixInAttributeNameContext() As Task
' attribute suffix isn't capitalized
Dim file1 = <Text>
namespace Foo
{
public class Myattribute : System.Attribute { }
}</Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
<$$
Sub Main()
End Sub
End Class]]></Text>.Value
Dim markup = CreateMarkupForProjecWithProjectReference(file2, file1, LanguageNames.VisualBasic, LanguageNames.CSharp)
Await VerifyItemExistsAsync(markup, "My", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", expectedDescriptionOrNull:="Class Foo.Myattribute")
Await VerifyItemIsAbsentAsync(markup, "Myattribute", inlineDescription:="Foo")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
<WorkItem(35124, "https://github.com/dotnet/roslyn/issues/35124")>
Public Async Function GenericTypeShouldDisplayProperVBSyntax() As Task
Dim file1 = <Text>
Namespace Foo
Public Class MyGenericClass(Of T)
End Class
End Namespace</Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
Sub Main()
Dim x As $$
End Sub
End Class]]></Text>.Value
Dim markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.VisualBasic)
Await VerifyItemExistsAsync(markup, "MyGenericClass", glyph:=Glyph.ClassPublic, inlineDescription:="Foo", displayTextSuffix:="(Of ...)", expectedDescriptionOrNull:="Class Foo.MyGenericClass(Of T)")
End Function
End Class
End Namespace
// 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.Composition;
using Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
[ExportLanguageServiceFactory(typeof(ITypeImportCompletionService), LanguageNames.CSharp), Shared]
internal sealed class CSharpTypeImportCompletionServiceFactory : ILanguageServiceFactory
{
[ImportingConstructor]
public CSharpTypeImportCompletionServiceFactory()
{
}
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
return new CSharpTypeImportCompletionService(languageServices.WorkspaceServices.Workspace);
}
private class CSharpTypeImportCompletionService : AbstractTypeImportCompletionService
{
public CSharpTypeImportCompletionService(Workspace workspace)
: base(workspace)
{
}
protected override string GenericTypeSuffix
=> "<>";
protected override bool IsCaseSensitive => true;
}
}
}
......@@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImports;
using Microsoft.CodeAnalysis.Completion.Log;
using Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion;
using Microsoft.CodeAnalysis.Debugging;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Experiments;
......@@ -78,7 +79,7 @@ private async Task AddCompletionItemsAsync(CompletionContext completionContext,
var document = completionContext.Document;
var project = document.Project;
var workspace = project.Solution.Workspace;
var typeImportCompletionService = workspace.Services.GetService<ITypeImportCompletionService>();
var typeImportCompletionService = document.GetLanguageService<ITypeImportCompletionService>();
// Find all namespaces in scope at current cursor location,
// which will be used to filter so the provider only returns out-of-scope types.
......@@ -86,7 +87,7 @@ private async Task AddCompletionItemsAsync(CompletionContext completionContext,
// Get completion items from current project.
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAndInternalItem, cancellationToken)
await typeImportCompletionService.GetTopLevelTypesAsync(project, syntaxContext, HandlePublicAndInternalItem, cancellationToken)
.ConfigureAwait(false);
// Get declarations from directly referenced projects and PEs
......@@ -105,6 +106,7 @@ await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAnd
{
await typeImportCompletionService.GetTopLevelTypesAsync(
assemblyProject,
syntaxContext,
GetHandler(compilation.Assembly, assembly),
cancellationToken).ConfigureAwait(false);
}
......@@ -114,6 +116,7 @@ await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAnd
project.Solution,
compilation,
peReference,
syntaxContext,
GetHandler(compilation.Assembly, assembly),
cancellationToken);
}
......@@ -125,24 +128,24 @@ await typeImportCompletionService.GetTopLevelTypesAsync(project, HandlePublicAnd
return;
// Decide which item handler to use based on IVT
Action<TypeImportCompletionItemInfo> GetHandler(IAssemblySymbol assembly, IAssemblySymbol referencedAssembly)
Action<CompletionItem, bool> GetHandler(IAssemblySymbol assembly, IAssemblySymbol referencedAssembly)
=> assembly.IsSameAssemblyOrHasFriendAccessTo(referencedAssembly)
? (Action<TypeImportCompletionItemInfo>)HandlePublicAndInternalItem
? (Action<CompletionItem, bool>)HandlePublicAndInternalItem
: HandlePublicItem;
// Add only public types to completion list
void HandlePublicItem(TypeImportCompletionItemInfo itemInfo)
=> AddItems(itemInfo, isInternalsVisible: false, completionContext, namespacesInScope, telemetryCounter);
void HandlePublicItem(CompletionItem item, bool isPublic)
=> AddItems(item, isPublic, isInternalsVisible: false, completionContext, namespacesInScope, telemetryCounter);
// Add both public and internal types to completion list
void HandlePublicAndInternalItem(TypeImportCompletionItemInfo itemInfo)
=> AddItems(itemInfo, isInternalsVisible: true, completionContext, namespacesInScope, telemetryCounter);
void HandlePublicAndInternalItem(CompletionItem item, bool isPublic)
=> AddItems(item, isPublic, isInternalsVisible: true, completionContext, namespacesInScope, telemetryCounter);
static void AddItems(TypeImportCompletionItemInfo itemInfo, bool isInternalsVisible, CompletionContext completionContext, HashSet<string> namespacesInScope, TelemetryCounter counter)
static void AddItems(CompletionItem item, bool isPublic, bool isInternalsVisible, CompletionContext completionContext, HashSet<string> namespacesInScope, TelemetryCounter counter)
{
if (itemInfo.IsPublic || isInternalsVisible)
if (isPublic || isInternalsVisible)
{
var containingNamespace = TypeImportCompletionItem.GetContainingNamespace(itemInfo.Item);
var containingNamespace = TypeImportCompletionItem.GetContainingNamespace(item);
if (!namespacesInScope.Contains(containingNamespace))
{
// We can return cached item directly, item's span will be fixed by completion service.
......@@ -150,7 +153,7 @@ static void AddItems(TypeImportCompletionItemInfo itemInfo, bool isInternalsVisi
// On the other hand, because of this (i.e. mutating the span of cached item for each run),
// the provider can not be used as a service by components that might be run in parallel
// with completion, which would be a race.
completionContext.AddItem(itemInfo.Item);
completionContext.AddItem(item);
counter.ItemsCount++; ;
}
}
......
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal abstract partial class AbstractTypeImportCompletionService : ITypeImportCompletionService
{
private ITypeImportCompletionCacheService CacheService { get; }
protected abstract string GenericTypeSuffix { get; }
protected abstract bool IsCaseSensitive { get; }
internal AbstractTypeImportCompletionService(Workspace workspace)
{
CacheService = workspace.Services.GetService<ITypeImportCompletionCacheService>();
}
public async Task GetTopLevelTypesAsync(
Project project,
SyntaxContext syntaxContext,
Action<CompletionItem, bool> handleItem,
CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
{
throw new ArgumentException(nameof(project));
}
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
// Since we only need top level types from source, therefore we only care if source symbol checksum changes.
var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false);
GetAccessibleTopLevelTypesWorker(
project.Id,
compilation.Assembly,
checksum,
syntaxContext,
handleItem,
CacheService.ProjectItemsCache,
cancellationToken);
}
public void GetTopLevelTypesFromPEReference(
Solution solution,
Compilation compilation,
PortableExecutableReference peReference,
SyntaxContext syntaxContext,
Action<CompletionItem, bool> handleItem,
CancellationToken cancellationToken)
{
var key = GetReferenceKey(peReference);
if (key == null)
{
// Can't cache items for reference with null key. We don't want risk potential perf regression by
// making those items repeatedly, so simply not returning anything from this assembly, until
// we have a better understanding on this sceanrio.
// TODO: Add telemetry
return;
}
if (!(compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol assemblySymbol))
{
return;
}
var checksum = SymbolTreeInfo.GetMetadataChecksum(solution, peReference, cancellationToken);
GetAccessibleTopLevelTypesWorker(
key,
assemblySymbol,
checksum,
syntaxContext,
handleItem,
CacheService.PEItemsCache,
cancellationToken);
return;
static string GetReferenceKey(PortableExecutableReference reference)
=> reference.FilePath ?? reference.Display;
}
private void GetAccessibleTopLevelTypesWorker<TKey>(
TKey key,
IAssemblySymbol assembly,
Checksum checksum,
SyntaxContext syntaxContext,
Action<CompletionItem, bool> handleItem,
IDictionary<TKey, ReferenceCacheEntry> cache,
CancellationToken cancellationToken)
{
var language = syntaxContext.SemanticModel.Language;
// Cache miss, create all requested items.
if (!cache.TryGetValue(key, out var cacheEntry) ||
cacheEntry.Checksum != checksum)
{
var builder = new ReferenceCacheEntry.Builder(checksum, language, GenericTypeSuffix);
GetCompletionItemsForTopLevelTypeDeclarations(assembly.GlobalNamespace, builder, cancellationToken);
cacheEntry = builder.ToReferenceCacheEntry();
cache[key] = cacheEntry;
}
foreach (var itemInfo in cacheEntry.GetItemsForContext(language, GenericTypeSuffix, syntaxContext.IsAttributeNameContext, IsCaseSensitive))
{
handleItem(itemInfo.Item, itemInfo.IsPublic);
}
}
private static void GetCompletionItemsForTopLevelTypeDeclarations(
INamespaceSymbol rootNamespaceSymbol,
ReferenceCacheEntry.Builder builder,
CancellationToken cancellationToken)
{
VisitNamespace(rootNamespaceSymbol, containingNamespace: null, builder, cancellationToken);
return;
static void VisitNamespace(
INamespaceSymbol symbol,
string containingNamespace,
ReferenceCacheEntry.Builder builder,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
containingNamespace = ConcatNamespace(containingNamespace, symbol.Name);
foreach (var memberNamespace in symbol.GetNamespaceMembers())
{
VisitNamespace(memberNamespace, containingNamespace, builder, cancellationToken);
}
var overloads = PooledDictionary<string, TypeOverloadInfo>.GetInstance();
var types = symbol.GetTypeMembers();
// Iterate over all top level internal and public types, keep track of "type overloads".
foreach (var type in types)
{
// No need to check accessibility here, since top level types can only be internal or public.
if (type.CanBeReferencedByName)
{
overloads.TryGetValue(type.Name, out var overloadInfo);
overloads[type.Name] = overloadInfo.Aggregate(type);
}
}
foreach (var pair in overloads)
{
var overloadInfo = pair.Value;
// Create CompletionItem for non-generic type overload, if exists.
if (overloadInfo.NonGenericOverload != null)
{
builder.AddItem(
overloadInfo.NonGenericOverload,
containingNamespace,
overloadInfo.NonGenericOverload.DeclaredAccessibility == Accessibility.Public);
}
// Create one CompletionItem for all generic type overloads, if there's any.
// For simplicity, we always show the type symbol with lowest arity in CompletionDescription
// and without displaying the total number of overloads.
if (overloadInfo.BestGenericOverload != null)
{
// If any of the generic overloads is public, then the completion item is considered public.
builder.AddItem(
overloadInfo.BestGenericOverload,
containingNamespace,
overloadInfo.ContainsPublicGenericOverload);
}
}
overloads.Free();
}
}
private static string ConcatNamespace(string containingNamespace, string name)
{
Debug.Assert(name != null);
if (string.IsNullOrEmpty(containingNamespace))
{
return name;
}
return containingNamespace + "." + name;
}
private readonly struct TypeOverloadInfo
{
public TypeOverloadInfo(INamedTypeSymbol nonGenericOverload, INamedTypeSymbol bestGenericOverload, bool containsPublicGenericOverload)
{
NonGenericOverload = nonGenericOverload;
BestGenericOverload = bestGenericOverload;
ContainsPublicGenericOverload = containsPublicGenericOverload;
}
public INamedTypeSymbol NonGenericOverload { get; }
// Generic with fewest type parameters is considered best symbol to show in description.
public INamedTypeSymbol BestGenericOverload { get; }
public bool ContainsPublicGenericOverload { get; }
public TypeOverloadInfo Aggregate(INamedTypeSymbol type)
{
if (type.Arity == 0)
{
return new TypeOverloadInfo(nonGenericOverload: type, BestGenericOverload, ContainsPublicGenericOverload);
}
// We consider generic with fewer type parameters better symbol to show in description
var newBestGenericOverload = BestGenericOverload == null || type.Arity < BestGenericOverload.Arity
? type
: BestGenericOverload;
var newContainsPublicGenericOverload = type.DeclaredAccessibility >= Accessibility.Public || ContainsPublicGenericOverload;
return new TypeOverloadInfo(NonGenericOverload, newBestGenericOverload, newContainsPublicGenericOverload);
}
}
private readonly struct ReferenceCacheEntry
{
public class Builder
{
private readonly string _language;
private readonly string _genericTypeSuffix;
private readonly Checksum _checksum;
public Builder(Checksum checksum, string language, string genericTypeSuffix)
{
_checksum = checksum;
_language = language;
_genericTypeSuffix = genericTypeSuffix;
_itemsBuilder = ArrayBuilder<TypeImportCompletionItemInfo>.GetInstance();
}
private ArrayBuilder<TypeImportCompletionItemInfo> _itemsBuilder;
public ReferenceCacheEntry ToReferenceCacheEntry()
{
return new ReferenceCacheEntry(
_checksum,
_language,
_itemsBuilder.ToImmutableAndFree());
}
public void AddItem(INamedTypeSymbol symbol, string containingNamespace, bool isPublic)
{
var isGeneric = symbol.Arity > 0;
// Need to determine if a type is an attribute up front since we want to filter out
// non-attribute types when in attribute context. We can't do this lazily since we don't hold
// on to symbols. However, the cost of calling `IsAttribute` on every top-level type symbols
// is prohibitively high, so we opt for the heuristic that would do the simple textual "Attribute"
// suffix check first, then the more expensive symbolic check. As a result, all unimported
// attribute types that don't have "Attribute" suffix would be filtered out when in attribute context.
var isAttribute = symbol.Name.HasAttributeSuffix(isCaseSensitive: false) && symbol.IsAttribute();
var item = TypeImportCompletionItem.Create(symbol, containingNamespace, _genericTypeSuffix);
item.IsCached = true;
_itemsBuilder.Add(new TypeImportCompletionItemInfo(item, isPublic, isGeneric, isAttribute));
return;
}
}
private ReferenceCacheEntry(
Checksum checksum,
string language,
ImmutableArray<TypeImportCompletionItemInfo> items)
{
Checksum = checksum;
Language = language;
ItemInfos = items;
}
public string Language { get; }
public Checksum Checksum { get; }
private ImmutableArray<TypeImportCompletionItemInfo> ItemInfos { get; }
public ImmutableArray<TypeImportCompletionItemInfo> GetItemsForContext(string language, string genericTypeSuffix, bool isAttributeContext, bool isCaseSensitive)
{
var isSameLanguage = Language == language;
if (isSameLanguage && !isAttributeContext)
{
return ItemInfos;
}
var builder = ArrayBuilder<TypeImportCompletionItemInfo>.GetInstance();
foreach (var info in ItemInfos)
{
CompletionItem item = info.Item;
if (isAttributeContext)
{
if (!info.IsAttribute)
{
continue;
}
item = GetAppropriateAttributeItem(info.Item, isCaseSensitive);
}
if (!isSameLanguage && info.IsGeneric)
{
// We don't want to cache this item.
item = TypeImportCompletionItem.CreateItemWithGenericDisplaySuffix(item, genericTypeSuffix);
}
builder.Add(info.WithItem(item));
}
return builder.ToImmutableAndFree();
static CompletionItem GetAppropriateAttributeItem(CompletionItem attributeItem, bool isCaseSensitive)
{
if (attributeItem.DisplayText.TryGetWithoutAttributeSuffix(isCaseSensitive: isCaseSensitive, out var attributeNameWithoutSuffix))
{
// We don't want to cache this item.
return TypeImportCompletionItem.CreateAttributeItemWithoutSuffix(attributeItem, attributeNameWithoutSuffix);
}
return attributeItem;
}
}
}
private readonly struct TypeImportCompletionItemInfo
{
private readonly ItemPropertyKind _properties;
public TypeImportCompletionItemInfo(CompletionItem item, bool isPublic, bool isGeneric, bool isAttribute)
{
Item = item;
_properties = (isPublic ? ItemPropertyKind.IsPublic : 0)
| (isGeneric ? ItemPropertyKind.IsGeneric : 0)
| (isAttribute ? ItemPropertyKind.IsAttribute : 0);
}
public CompletionItem Item { get; }
public bool IsPublic
=> (_properties & ItemPropertyKind.IsPublic) != 0;
public bool IsGeneric
=> (_properties & ItemPropertyKind.IsGeneric) != 0;
public bool IsAttribute
=> (_properties & ItemPropertyKind.IsAttribute) != 0;
public TypeImportCompletionItemInfo WithItem(CompletionItem item)
{
return new TypeImportCompletionItemInfo(item, IsPublic, IsGeneric, IsAttribute);
}
[Flags]
private enum ItemPropertyKind : byte
{
IsPublic = 0x1,
IsGeneric = 0x2,
IsAttribute = 0x4,
}
}
}
}
// 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal partial class AbstractTypeImportCompletionService
{
private interface ITypeImportCompletionCacheService : IWorkspaceService
{
// PE references are keyed on assembly path.
IDictionary<string, ReferenceCacheEntry> PEItemsCache { get; }
IDictionary<ProjectId, ReferenceCacheEntry> ProjectItemsCache { get; }
}
[ExportWorkspaceServiceFactory(typeof(ITypeImportCompletionCacheService), ServiceLayer.Editor), Shared]
private class TypeImportCompletionCacheServiceFactory : IWorkspaceServiceFactory
{
private readonly ConcurrentDictionary<string, ReferenceCacheEntry> _peItemsCache
= new ConcurrentDictionary<string, ReferenceCacheEntry>();
private readonly ConcurrentDictionary<ProjectId, ReferenceCacheEntry> _projectItemsCache
= new ConcurrentDictionary<ProjectId, ReferenceCacheEntry>();
[ImportingConstructor]
public TypeImportCompletionCacheServiceFactory()
{
}
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
var workspace = workspaceServices.Workspace;
if (workspace.Kind == WorkspaceKind.Host)
{
var cacheService = workspaceServices.GetService<IWorkspaceCacheService>();
if (cacheService != null)
{
cacheService.CacheFlushRequested += OnCacheFlushRequested;
}
}
return new TypeImportCompletionCacheService(_peItemsCache, _projectItemsCache);
}
private void OnCacheFlushRequested(object sender, EventArgs e)
{
_peItemsCache.Clear();
_projectItemsCache.Clear();
}
private class TypeImportCompletionCacheService : ITypeImportCompletionCacheService
{
public IDictionary<string, ReferenceCacheEntry> PEItemsCache { get; }
public IDictionary<ProjectId, ReferenceCacheEntry> ProjectItemsCache { get; }
public TypeImportCompletionCacheService(
ConcurrentDictionary<string, ReferenceCacheEntry> peCache,
ConcurrentDictionary<ProjectId, ReferenceCacheEntry> projectCache)
{
PEItemsCache = peCache;
ProjectItemsCache = projectCache;
}
}
}
}
}
// 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 System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal interface ITypeImportCompletionService : ILanguageService
{
/// <summary>
/// Get all the top level types from given project. This method is intended to be used for
/// getting types from source only, so the project must support compilation.
/// For getting types from PE, use <see cref="GetTopLevelTypesFromPEReference"/>.
/// </summary>
Task GetTopLevelTypesAsync(
Project project,
SyntaxContext syntaxContext,
Action<CompletionItem, bool> handleItem,
CancellationToken cancellationToken);
void GetTopLevelTypesFromPEReference(
Solution solution,
Compilation compilation,
PortableExecutableReference peReference,
SyntaxContext syntaxContext,
Action<CompletionItem, bool> handleItem,
CancellationToken cancellationToken);
}
}
......@@ -16,8 +16,9 @@ internal static class TypeImportCompletionItem
private static readonly string[] s_aritySuffixesOneToNine = { "`1", "`2", "`3", "`4", "`5", "`6", "`7", "`8", "`9" };
private const string TypeAritySuffixName = nameof(TypeAritySuffixName);
private const string AttributeFullName = nameof(AttributeFullName);
public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containingNamespace)
public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containingNamespace, string genericTypeSuffix)
{
PooledDictionary<string, string> propertyBuilder = null;
......@@ -27,17 +28,13 @@ public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containi
propertyBuilder.Add(TypeAritySuffixName, GetAritySuffix(typeSymbol.Arity));
}
// Hack: add tildes (ASCII: 126) to name and namespace as sort text:
// Add tildes (ASCII: 126) to name and namespace as sort text:
// 1. '~' before type name makes import items show after in-scope items
// 2. ' ' before namespace makes types with identical type name but from different namespace all show up in the list,
// it also makes sure type with shorter name shows first, e.g. 'SomeType` before 'SomeTypeWithLongerName'.
var sortTextBuilder = PooledStringBuilder.GetInstance();
sortTextBuilder.Builder.AppendFormat(SortTextFormat, typeSymbol.Name, containingNamespace);
// TODO:
// 1. Suffix should be language specific, i.e. `(Of ...)` if triggered from VB.
// 2. Sort the import items to be after in-scope symbols in a less hacky way.
// 3. Editor support for resolving item text conflicts?
return CompletionItem.Create(
displayText: typeSymbol.Name,
filterText: typeSymbol.Name,
......@@ -46,10 +43,37 @@ public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containi
tags: GlyphTags.GetTags(typeSymbol.GetGlyph()),
rules: CompletionItemRules.Default,
displayTextPrefix: null,
displayTextSuffix: typeSymbol.Arity == 0 ? string.Empty : "<>",
displayTextSuffix: typeSymbol.Arity == 0 ? string.Empty : genericTypeSuffix,
inlineDescription: containingNamespace);
}
public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem attributeItem, string attributeNameWithoutSuffix)
{
Debug.Assert(!attributeItem.Properties.ContainsKey(AttributeFullName));
// Remember the full type name so we can get the symbol when description is displayed.
var newProperties = attributeItem.Properties.Add(AttributeFullName, attributeItem.DisplayText);
var sortTextBuilder = PooledStringBuilder.GetInstance();
sortTextBuilder.Builder.AppendFormat(SortTextFormat, attributeNameWithoutSuffix, attributeItem.InlineDescription);
return CompletionItem.Create(
displayText: attributeNameWithoutSuffix,
filterText: attributeNameWithoutSuffix,
sortText: sortTextBuilder.ToStringAndFree(),
properties: newProperties,
tags: attributeItem.Tags,
rules: attributeItem.Rules,
displayTextPrefix: attributeItem.DisplayTextPrefix,
displayTextSuffix: attributeItem.DisplayTextSuffix,
inlineDescription: attributeItem.InlineDescription);
}
public static CompletionItem CreateItemWithGenericDisplaySuffix(CompletionItem item, string genericTypeSuffix)
{
return item.WithDisplayTextSuffix(genericTypeSuffix);
}
public static string GetContainingNamespace(CompletionItem item)
=> item.InlineDescription;
......@@ -95,7 +119,8 @@ private static string GetFullyQualifiedName(string namespaceName, string typeNam
private static string GetMetadataName(CompletionItem item)
{
var containingNamespace = GetContainingNamespace(item);
var fullyQualifiedName = GetFullyQualifiedName(containingNamespace, item.DisplayText);
var typeName = item.Properties.TryGetValue(AttributeFullName, out var attributeFullName) ? attributeFullName : item.DisplayText;
var fullyQualifiedName = GetFullyQualifiedName(containingNamespace, typeName);
if (item.Properties.TryGetValue(TypeAritySuffixName, out var aritySuffix))
{
return fullyQualifiedName + aritySuffix;
......
// 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.Collections.Concurrent;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal interface ITypeImportCompletionService : IWorkspaceService
{
/// <summary>
/// Get all the top level types from given project. This method is intended to be used for
/// getting types from source only, so the project must support compilation.
/// For getting types from PE, use <see cref="GetTopLevelTypesFromPEReference"/>.
/// </summary>
Task GetTopLevelTypesAsync(
Project project,
Action<TypeImportCompletionItemInfo> handleItem,
CancellationToken cancellationToken);
void GetTopLevelTypesFromPEReference(
Solution solution,
Compilation compilation,
PortableExecutableReference peReference,
Action<TypeImportCompletionItemInfo> handleItem,
CancellationToken cancellationToken);
}
internal readonly struct TypeImportCompletionItemInfo
{
public TypeImportCompletionItemInfo(CompletionItem item, bool isPublic)
{
Item = item;
IsPublic = isPublic;
}
public CompletionItem Item { get; }
public bool IsPublic { get; }
}
[ExportWorkspaceServiceFactory(typeof(ITypeImportCompletionService), ServiceLayer.Editor), Shared]
internal sealed class TypeImportCompletionService : IWorkspaceServiceFactory
{
private readonly ConcurrentDictionary<string, ReferenceCacheEntry> _peItemsCache
= new ConcurrentDictionary<string, ReferenceCacheEntry>();
private readonly ConcurrentDictionary<ProjectId, ReferenceCacheEntry> _projectItemsCache
= new ConcurrentDictionary<ProjectId, ReferenceCacheEntry>();
[ImportingConstructor]
public TypeImportCompletionService()
{
}
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
var workspace = workspaceServices.Workspace;
if (workspace.Kind == WorkspaceKind.Host)
{
var cacheService = workspaceServices.GetService<IWorkspaceCacheService>();
if (cacheService != null)
{
cacheService.CacheFlushRequested += OnCacheFlushRequested;
}
}
return new Service(workspace, _peItemsCache, _projectItemsCache);
}
private void OnCacheFlushRequested(object sender, EventArgs e)
{
_peItemsCache.Clear();
_projectItemsCache.Clear();
}
private class Service : ITypeImportCompletionService
{
// PE references are keyed on assembly path.
private readonly ConcurrentDictionary<string, ReferenceCacheEntry> _peItemsCache;
private readonly ConcurrentDictionary<ProjectId, ReferenceCacheEntry> _projectItemsCache;
public Service(
Workspace workspace,
ConcurrentDictionary<string, ReferenceCacheEntry> peReferenceCache,
ConcurrentDictionary<ProjectId, ReferenceCacheEntry> projectReferenceCache)
{
_peItemsCache = peReferenceCache;
_projectItemsCache = projectReferenceCache;
workspace.WorkspaceChanged += OnWorkspaceChanged;
}
public void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
{
switch (e.Kind)
{
case WorkspaceChangeKind.SolutionCleared:
case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.SolutionRemoved:
_peItemsCache.Clear();
_projectItemsCache.Clear();
break;
case WorkspaceChangeKind.ProjectRemoved:
case WorkspaceChangeKind.ProjectReloaded:
_projectItemsCache.TryRemove(e.ProjectId, out _);
break;
}
}
public async Task GetTopLevelTypesAsync(
Project project,
Action<TypeImportCompletionItemInfo> handleItem,
CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
{
throw new ArgumentException(nameof(project));
}
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
// Since we only need top level types from source, therefore we only care if source symbol checksum changes.
var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false);
GetAccessibleTopLevelTypesWorker(
project.Id,
compilation.Assembly,
checksum,
handleItem,
_projectItemsCache,
cancellationToken);
}
public void GetTopLevelTypesFromPEReference(
Solution solution,
Compilation compilation,
PortableExecutableReference peReference,
Action<TypeImportCompletionItemInfo> handleItem,
CancellationToken cancellationToken)
{
var key = GetReferenceKey(peReference);
if (key == null)
{
// Can't cache items for reference with null key. We don't want risk potential perf regression by
// making those items repeatedly, so simply not returning anything from this assembly, until
// we have a better understanding on this sceanrio.
// TODO: Add telemetry
return;
}
if (!(compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol assemblySymbol))
{
return;
}
var checksum = SymbolTreeInfo.GetMetadataChecksum(solution, peReference, cancellationToken);
GetAccessibleTopLevelTypesWorker(
key,
assemblySymbol,
checksum,
handleItem,
_peItemsCache,
cancellationToken);
return;
static string GetReferenceKey(PortableExecutableReference reference)
=> reference.FilePath ?? reference.Display;
}
private static void GetAccessibleTopLevelTypesWorker<TKey>(
TKey key,
IAssemblySymbol assembly,
Checksum checksum,
Action<TypeImportCompletionItemInfo> handleItem,
ConcurrentDictionary<TKey, ReferenceCacheEntry> cache,
CancellationToken cancellationToken)
{
// Cache miss, create all requested items.
if (!cache.TryGetValue(key, out var cacheEntry) ||
cacheEntry.Checksum != checksum)
{
var items = GetCompletionItemsForTopLevelTypeDeclarations(assembly.GlobalNamespace, cancellationToken);
cacheEntry = new ReferenceCacheEntry(checksum, items);
cache[key] = cacheEntry;
}
foreach (var item in cacheEntry.CachedItems)
{
handleItem(item);
}
}
private static ImmutableArray<TypeImportCompletionItemInfo> GetCompletionItemsForTopLevelTypeDeclarations(
INamespaceSymbol rootNamespaceSymbol,
CancellationToken cancellationToken)
{
var builder = ArrayBuilder<TypeImportCompletionItemInfo>.GetInstance();
VisitNamespace(rootNamespaceSymbol, containingNamespace: null, builder, cancellationToken);
return builder.ToImmutableAndFree();
static void VisitNamespace(
INamespaceSymbol symbol,
string containingNamespace,
ArrayBuilder<TypeImportCompletionItemInfo> builder,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
containingNamespace = ConcatNamespace(containingNamespace, symbol.Name);
foreach (var memberNamespace in symbol.GetNamespaceMembers())
{
VisitNamespace(memberNamespace, containingNamespace, builder, cancellationToken);
}
var overloads = PooledDictionary<string, TypeOverloadInfo>.GetInstance();
var types = symbol.GetTypeMembers();
// Iterate over all top level internal and public types, keep track of "type overloads".
foreach (var type in types)
{
// No need to check accessibility here, since top level types can only be internal or public.
if (type.CanBeReferencedByName)
{
overloads.TryGetValue(type.Name, out var overloadInfo);
overloads[type.Name] = overloadInfo.Aggregate(type);
}
}
foreach (var pair in overloads)
{
var overloadInfo = pair.Value;
// Create CompletionItem for non-generic type overload, if exists.
if (overloadInfo.NonGenericOverload != null)
{
var item = TypeImportCompletionItem.Create(overloadInfo.NonGenericOverload, containingNamespace);
var isPublic = overloadInfo.NonGenericOverload.DeclaredAccessibility == Accessibility.Public;
item.IsCached = true;
builder.Add(new TypeImportCompletionItemInfo(item, isPublic));
}
// Create one CompletionItem for all generic type overloads, if there's any.
// For simplicity, we always show the type symbol with lowest arity in CompletionDescription
// and without displaying the total number of overloads.
if (overloadInfo.BestGenericOverload != null)
{
// If any of the generic overloads is public, then the completion item is considered public.
var item = TypeImportCompletionItem.Create(overloadInfo.BestGenericOverload, containingNamespace);
var isPublic = overloadInfo.ContainsPublicGenericOverload;
item.IsCached = true;
builder.Add(new TypeImportCompletionItemInfo(item, isPublic));
}
}
overloads.Free();
}
}
}
private static string ConcatNamespace(string containingNamespace, string name)
{
Debug.Assert(name != null);
if (string.IsNullOrEmpty(containingNamespace))
{
return name;
}
return containingNamespace + "." + name;
}
private readonly struct TypeOverloadInfo
{
public TypeOverloadInfo(INamedTypeSymbol nonGenericOverload, INamedTypeSymbol bestGenericOverload, bool containsPublicGenericOverload)
{
NonGenericOverload = nonGenericOverload;
BestGenericOverload = bestGenericOverload;
ContainsPublicGenericOverload = containsPublicGenericOverload;
}
public INamedTypeSymbol NonGenericOverload { get; }
// Generic with fewest type parameters is considered best symbol to show in description.
public INamedTypeSymbol BestGenericOverload { get; }
public bool ContainsPublicGenericOverload { get; }
public TypeOverloadInfo Aggregate(INamedTypeSymbol type)
{
if (type.Arity == 0)
{
return new TypeOverloadInfo(nonGenericOverload: type, BestGenericOverload, ContainsPublicGenericOverload);
}
// We consider generic with fewer type parameters better symbol to show in description
var newBestGenericOverload = BestGenericOverload == null || type.Arity < BestGenericOverload.Arity
? type
: BestGenericOverload;
var newContainsPublicGenericOverload = type.DeclaredAccessibility >= Accessibility.Public || ContainsPublicGenericOverload;
return new TypeOverloadInfo(NonGenericOverload, newBestGenericOverload, newContainsPublicGenericOverload);
}
}
private readonly struct ReferenceCacheEntry
{
public ReferenceCacheEntry(
Checksum checksum,
ImmutableArray<TypeImportCompletionItemInfo> cachedItems)
{
Checksum = checksum;
CachedItems = cachedItems;
}
public Checksum Checksum { get; }
public ImmutableArray<TypeImportCompletionItemInfo> CachedItems { get; }
}
}
}
' 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.Composition
Imports Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
Imports Microsoft.CodeAnalysis.Host
Imports Microsoft.CodeAnalysis.Host.Mef
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
<ExportLanguageServiceFactory(GetType(ITypeImportCompletionService), LanguageNames.VisualBasic), [Shared]>
Friend NotInheritable Class BasicTypeImportCompletionServiceFactory
Implements ILanguageServiceFactory
<ImportingConstructor>
Public Sub New()
End Sub
Public Function CreateLanguageService(languageServices As HostLanguageServices) As ILanguageService Implements ILanguageServiceFactory.CreateLanguageService
Return New BasicTypeImportCompletionService(languageServices.WorkspaceServices.Workspace)
End Function
Private Class BasicTypeImportCompletionService
Inherits AbstractTypeImportCompletionService
Public Sub New(workspace As Workspace)
MyBase.New(workspace)
End Sub
Protected Overrides ReadOnly Property GenericTypeSuffix As String
Get
Return "(Of ...)"
End Get
End Property
Protected Overrides ReadOnly Property IsCaseSensitive As Boolean
Get
Return False
End Get
End Property
End Class
End Class
End Namespace
......@@ -3,7 +3,6 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.FlowAnalysis;
using Microsoft.CodeAnalysis.Operations;
......@@ -61,7 +60,7 @@ public static ValueUsageInfo GetValueUsageInfo(this IOperation operation)
//
return ValueUsageInfo.Write;
case IOperation iop when iop.GetType().GetInterfaces().Any(i => i.Name == "IRecursivePatternOperation"):
case IRecursivePatternOperation _:
// A declaration pattern within a recursive pattern is a
// write for the declared local.
// For example, 'x' is defined and assigned the value from 'obj' below:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册