From 9e285220e4bf564c199de75a915707646b5445db Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Fri, 10 Mar 2023 12:13:09 -0800 Subject: [PATCH] Don't hide expanded items if defaults are provided --- .../AsyncCompletion/ItemManager.cs | 3 +- .../CSharpCompletionCommandHandlerTests.vb | 169 -------------- ...etionCommandHandlerTests_DefaultsSource.vb | 206 ++++++++++++++++++ 3 files changed, 208 insertions(+), 170 deletions(-) diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/ItemManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/ItemManager.cs index 4ab7870e5ab8a..578aa768b5c16 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/ItemManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/ItemManager.cs @@ -127,8 +127,9 @@ private static SegmentedList SortCompletionItems(AsyncCompleti } } - // Don't try to hide expanded items if filter text is reasonably long or the list is short. + // Don't try to hide expanded items if filter text length > 1 and defaults is not empty (which might suggest an expanded item). var hideExpandedItems = sessionData.AttemptHidingExpandedItems + && data.Defaults.IsEmpty && session.ApplicableToSpan.GetText(data.Snapshot).Length <= MaximumFilterTextLengthToExcludeExpandedItems; // Don't hide expanded items in the session once it's included diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index e7073905ab15f..1057e5227e90a 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -9,7 +9,6 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Completion.Providers Imports Microsoft.CodeAnalysis.CSharp -Imports Microsoft.CodeAnalysis.CSharp.Completion Imports Microsoft.CodeAnalysis.CSharp.ExternalAccess.Pythia.Api Imports Microsoft.CodeAnalysis.CSharp.Formatting Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion @@ -18,7 +17,6 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.PooledObjects -Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Tags Imports Microsoft.CodeAnalysis.Text Imports Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion @@ -26,7 +24,6 @@ Imports Microsoft.VisualStudio.Text Imports Microsoft.VisualStudio.Text.Editor Imports Microsoft.VisualStudio.Text.Operations Imports Microsoft.VisualStudio.Text.Projection -Imports Roslyn.Utilities Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense @@ -11261,172 +11258,6 @@ class Program End Using End Function - - Public Async Function TestAutoHidingExpandedItems(responsiveTypingEnabled As Boolean) As Task - Using state = TestStateFactory.CreateCSharpTestState( - -$$ - , - excludedTypes:={GetType(CSharpCompletionService.Factory)}.ToList(), - extraExportedTypes:={GetType(MockCompletionServiceFactory)}.ToList()) - - Dim workspace = state.Workspace - workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) - - ' we don't auto hide expanded item if responsive completion is disabled - state.TextView.Options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, responsiveTypingEnabled) - - Dim completionService = DirectCast(workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService(Of CompletionService)(), MockCompletionServiceFactory.Service) - - If responsiveTypingEnabled Then - ' we blocked the expanded item task, so only regular items in the list - Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() - state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=False) - Dim items = state.GetCompletionItems() - Assert.Equal(completionService.RegularCount, items.Count) - Assert.False(items.Any(Function(i) i.Flags.IsExpanded())) - - ' make sure expanded item task completes - completionService.ExpandedItemsCheckpoint.Release() - Dim session = Await state.GetCompletionSession() - Dim sessionData = CompletionSessionData.GetOrCreateSessionData(session) - Assert.NotNull(sessionData.ExpandedItemsTask) - Await sessionData.ExpandedItemsTask - Else - ' we don't try to separate regular and expanded items when responsive completion is enabled, - ' so we expect everything to be provided - completionService.ExpandedItemsCheckpoint.Release() - - Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() - state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True) - Dim items = state.GetCompletionItems() - Assert.Equal(completionService.RegularCount + completionService.ExpandedCount, items.Count) - Assert.Equal(completionService.ExpandedCount, items.Where(Function(x) x.Flags.IsExpanded()).Count()) - End If - - Dim typedChars = "Item" - For i = 0 To typedChars.Length - 1 - Await state.SendTypeCharsAndWaitForUiRenderAsync(typedChars(i)) - Dim items = state.GetCompletionItems() - Dim expandedCount As Integer - - If (responsiveTypingEnabled And i <= ItemManager.MaximumFilterTextLengthToExcludeExpandedItems - 1) Then - expandedCount = 0 - Else - expandedCount = completionService.ExpandedCount - End If - - Assert.True((completionService.RegularCount + expandedCount) = items.Count, $"Typed char: '{typedChars(i)}', expected: {completionService.RegularCount + expandedCount}, actual: {items.Count} ") - Assert.Equal(expandedCount, items.Where(Function(x) x.Flags.IsExpanded()).Count()) - Next - - ' once we show expanded items, we will not hide it in the same session, even if filter text became shorter - For i = 1 To 4 - state.SendBackspace() - Dim items = state.GetCompletionItems() - Assert.True((completionService.RegularCount + completionService.ExpandedCount) = items.Count, $"Backspace number: {i}expected: {completionService.RegularCount + completionService.ExpandedCount}, actual: {items.Count} ") - Assert.Equal(completionService.ExpandedCount, items.Where(Function(x) x.Flags.IsExpanded()).Count()) - Next - End Using - End Function - - - Private Class MockCompletionServiceFactory - Implements ILanguageServiceFactory - - Private ReadOnly _listenerProvider As IAsynchronousOperationListenerProvider - - - - Public Sub New(listenerProvider As IAsynchronousOperationListenerProvider) - _listenerProvider = listenerProvider - End Sub - - Public Function CreateLanguageService(languageServices As CodeAnalysis.Host.HostLanguageServices) As CodeAnalysis.Host.ILanguageService Implements ILanguageServiceFactory.CreateLanguageService - Return New Service(languageServices.LanguageServices.SolutionServices, _listenerProvider) - End Function - - Public Class Service - Inherits CompletionService - - Public Property RegularCount As Integer = 10 - Public Property ExpandedCount As Integer = 10 - - Public ExpandedItemsCheckpoint As New Checkpoint() - - Public Sub New(services As CodeAnalysis.Host.SolutionServices, listenerProvider As IAsynchronousOperationListenerProvider) - MyBase.New(services, listenerProvider) - End Sub - - Public Overrides ReadOnly Property Language As String - Get - Return LanguageNames.CSharp - End Get - End Property - - Friend Overrides Function GetRules(options As CompletionOptions) As CompletionRules - Return CompletionRules.Default - End Function - - Friend Overrides Async Function GetCompletionsAsync(document As Document, - caretPosition As Integer, - options As CompletionOptions, - passThroughOptions As OptionSet, - Optional trigger As CompletionTrigger = Nothing, - Optional roles As ImmutableHashSet(Of String) = Nothing, - Optional cancellationToken As CancellationToken = Nothing) As Task(Of CompletionList) - - Dim text = Await document.GetTextAsync(cancellationToken).ConfigureAwait(False) - Dim defaultItemSpan = GetDefaultCompletionListSpan(text, caretPosition) - - Dim builder = ArrayBuilder(Of CompletionItem).GetInstance(RegularCount + ExpandedCount) - If (options.ExpandedCompletionBehavior = ExpandedCompletionMode.AllItems) Then - CreateRegularItems(builder, RegularCount) - Await CreateExpandedItems(builder, ExpandedCount) - ElseIf (options.ExpandedCompletionBehavior = ExpandedCompletionMode.ExpandedItemsOnly) Then - Await CreateExpandedItems(builder, ExpandedCount) - Else - CreateRegularItems(builder, RegularCount) - End If - - Return CompletionList.Create(defaultItemSpan, builder.ToImmutableAndFree()) - End Function - - Friend Overrides Function ShouldTriggerCompletion(project As Project, - languageServices As CodeAnalysis.Host.LanguageServices, - text As SourceText, - caretPosition As Integer, - trigger As CompletionTrigger, - options As CompletionOptions, - passThroughOptions As OptionSet, - Optional roles As ImmutableHashSet(Of String) = Nothing) As Boolean - Return True - End Function - - Private Async Function CreateExpandedItems(builder As ArrayBuilder(Of CompletionItem), count As Integer) As Task - Await ExpandedItemsCheckpoint.Task - For i = 1 To count - Dim item = ImportCompletionItem.Create( - $"ItemExpanded{i}", - arity:=0, - containingNamespace:="NS", - glyph:=Glyph.ClassPublic, - genericTypeSuffix:=String.Empty, - flags:=CompletionItemFlags.Expanded, - extensionMethodData:=Nothing, - includedInTargetTypeCompletion:=True) - builder.Add(item) - Next - End Function - - Private Shared Sub CreateRegularItems(builder As ArrayBuilder(Of CompletionItem), count As Integer) - For i = 1 To count - builder.Add(CompletionItem.Create($"ItemRegular{i}")) - Next - End Sub - End Class - End Class - Public Async Function CompletionOffOfNullableLambdaParameter(showCompletionInArgumentLists As Boolean) As Task Using state = TestStateFactory.CreateCSharpTestState( diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_DefaultsSource.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_DefaultsSource.vb index 32467d086f0fd..1c28105e59967 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_DefaultsSource.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_DefaultsSource.vb @@ -4,11 +4,16 @@ Imports System.Collections.Immutable Imports System.Composition +Imports System.Threading Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.Shared.TestHooks +Imports Microsoft.CodeAnalysis.PooledObjects +Imports Microsoft.CodeAnalysis.CSharp.Completion +Imports Microsoft.CodeAnalysis.Completion.Providers Imports Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion Imports Microsoft.VisualStudio.Text.Editor @@ -469,5 +474,206 @@ class Test Assert.Contains("if", committedLine, StringComparison.Ordinal) End Using End Function + + + Public Async Function TestAutoHidingExpandedItems(responsiveTypingEnabled As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + +$$ + , + excludedTypes:={GetType(CSharpCompletionService.Factory)}.ToList(), + extraExportedTypes:={GetType(MockCompletionServiceFactory)}.ToList()) + + Dim workspace = state.Workspace + workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) + + ' we don't auto hide expanded item if responsive completion is disabled + state.TextView.Options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, responsiveTypingEnabled) + + Dim completionService = DirectCast(workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService(Of CompletionService)(), MockCompletionServiceFactory.Service) + + If responsiveTypingEnabled Then + ' we blocked the expanded item task, so only regular items in the list + Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() + state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=False) + Dim items = state.GetCompletionItems() + Assert.Equal(completionService.RegularCount, items.Count) + Assert.False(items.Any(Function(i) i.Flags.IsExpanded())) + + ' make sure expanded item task completes + completionService.ExpandedItemsCheckpoint.Release() + Dim session = Await state.GetCompletionSession() + Dim sessionData = CompletionSessionData.GetOrCreateSessionData(session) + Assert.NotNull(sessionData.ExpandedItemsTask) + Await sessionData.ExpandedItemsTask + Else + ' we don't try to separate regular and expanded items when responsive completion is enabled, + ' so we expect everything to be provided + completionService.ExpandedItemsCheckpoint.Release() + + Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() + state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True) + Dim items = state.GetCompletionItems() + Assert.Equal(completionService.RegularCount + completionService.ExpandedCount, items.Count) + Assert.Equal(completionService.ExpandedCount, items.Where(Function(x) x.Flags.IsExpanded()).Count()) + End If + + Dim typedChars = "Item" + For i = 0 To typedChars.Length - 1 + Await state.SendTypeCharsAndWaitForUiRenderAsync(typedChars(i)) + Dim items = state.GetCompletionItems() + Dim expandedCount As Integer + + If (responsiveTypingEnabled And i <= ItemManager.MaximumFilterTextLengthToExcludeExpandedItems - 1) Then + expandedCount = 0 + Else + expandedCount = completionService.ExpandedCount + End If + + Assert.True((completionService.RegularCount + expandedCount) = items.Count, $"Typed char: '{typedChars(i)}', expected: {completionService.RegularCount + expandedCount}, actual: {items.Count} ") + Assert.Equal(expandedCount, items.Where(Function(x) x.Flags.IsExpanded()).Count()) + Next + + ' once we show expanded items, we will not hide it in the same session, even if filter text became shorter + For i = 1 To 4 + state.SendBackspace() + Dim items = state.GetCompletionItems() + Assert.True((completionService.RegularCount + completionService.ExpandedCount) = items.Count, $"Backspace number: {i}expected: {completionService.RegularCount + completionService.ExpandedCount}, actual: {items.Count} ") + Assert.Equal(completionService.ExpandedCount, items.Where(Function(x) x.Flags.IsExpanded()).Count()) + Next + End Using + End Function + + + Public Async Function TestAutoHidingExpandedItemsWithDefaults() As Task + Using state = TestStateFactory.CreateCSharpTestState( + +$$ + , + excludedTypes:={GetType(CSharpCompletionService.Factory)}.ToList(), + extraExportedTypes:={GetType(MockCompletionServiceFactory), GetType(MockDefaultSource)}.ToList()) + + Dim workspace = state.Workspace + workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) + state.TextView.Options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, True) + + Dim completionService = DirectCast(workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService(Of CompletionService)(), MockCompletionServiceFactory.Service) + MockDefaultSource.Defaults = ImmutableArray.Create("ItemExpanded1") + + ' we blocked the expanded item task, so only regular items in the list + Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() + state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=False) + Dim items = state.GetCompletionItems() + Assert.Equal(completionService.RegularCount, items.Count) + Assert.False(items.Any(Function(i) i.Flags.IsExpanded())) + + ' make sure expanded item task completes + completionService.ExpandedItemsCheckpoint.Release() + Dim session = Await state.GetCompletionSession() + Dim sessionData = CompletionSessionData.GetOrCreateSessionData(session) + Assert.NotNull(sessionData.ExpandedItemsTask) + Await sessionData.ExpandedItemsTask + + Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() + Await state.AssertSelectedCompletionItem("★ ItemExpanded1", isHardSelected:=False) + End Using + End Function + + + Private Class MockCompletionServiceFactory + Implements ILanguageServiceFactory + + Private ReadOnly _listenerProvider As IAsynchronousOperationListenerProvider + + + + Public Sub New(listenerProvider As IAsynchronousOperationListenerProvider) + _listenerProvider = listenerProvider + End Sub + + Public Function CreateLanguageService(languageServices As CodeAnalysis.Host.HostLanguageServices) As CodeAnalysis.Host.ILanguageService Implements ILanguageServiceFactory.CreateLanguageService + Return New Service(languageServices.LanguageServices.SolutionServices, _listenerProvider) + End Function + + Public Class Service + Inherits CompletionService + + Public Property RegularCount As Integer = 10 + Public Property ExpandedCount As Integer = 10 + + Public ExpandedItemsCheckpoint As New Checkpoint() + + Public Sub New(services As CodeAnalysis.Host.SolutionServices, listenerProvider As IAsynchronousOperationListenerProvider) + MyBase.New(services, listenerProvider) + End Sub + + Public Overrides ReadOnly Property Language As String + Get + Return LanguageNames.CSharp + End Get + End Property + + Friend Overrides Function GetRules(options As CompletionOptions) As CompletionRules + Return CompletionRules.Default + End Function + + Friend Overrides Async Function GetCompletionsAsync(document As Document, + caretPosition As Integer, + options As CompletionOptions, + passThroughOptions As OptionSet, + Optional trigger As CompletionTrigger = Nothing, + Optional roles As ImmutableHashSet(Of String) = Nothing, + Optional cancellationToken As CancellationToken = Nothing) As Task(Of CompletionList) + + Dim text = Await document.GetTextAsync(cancellationToken).ConfigureAwait(False) + Dim defaultItemSpan = GetDefaultCompletionListSpan(text, caretPosition) + + Dim builder = ArrayBuilder(Of CompletionItem).GetInstance(RegularCount + ExpandedCount) + If (options.ExpandedCompletionBehavior = ExpandedCompletionMode.AllItems) Then + CreateRegularItems(builder, RegularCount) + Await CreateExpandedItems(builder, ExpandedCount) + ElseIf (options.ExpandedCompletionBehavior = ExpandedCompletionMode.ExpandedItemsOnly) Then + Await CreateExpandedItems(builder, ExpandedCount) + Else + CreateRegularItems(builder, RegularCount) + End If + + Return CompletionList.Create(defaultItemSpan, builder.ToImmutableAndFree()) + End Function + + Friend Overrides Function ShouldTriggerCompletion(project As Project, + languageServices As CodeAnalysis.Host.LanguageServices, + text As SourceText, + caretPosition As Integer, + trigger As CompletionTrigger, + options As CompletionOptions, + passThroughOptions As OptionSet, + Optional roles As ImmutableHashSet(Of String) = Nothing) As Boolean + Return True + End Function + + Private Async Function CreateExpandedItems(builder As ArrayBuilder(Of CompletionItem), count As Integer) As Task + Await ExpandedItemsCheckpoint.Task + For i = 1 To count + Dim item = ImportCompletionItem.Create( + $"ItemExpanded{i}", + arity:=0, + containingNamespace:="NS", + glyph:=Glyph.ClassPublic, + genericTypeSuffix:=String.Empty, + flags:=CompletionItemFlags.Expanded, + extensionMethodData:=Nothing, + includedInTargetTypeCompletion:=True) + builder.Add(item) + Next + End Function + + Private Shared Sub CreateRegularItems(builder As ArrayBuilder(Of CompletionItem), count As Integer) + For i = 1 To count + builder.Add(CompletionItem.Create($"ItemRegular{i}")) + Next + End Sub + End Class + End Class End Class End Namespace