From e3283df6737d4dc1c3be1feb72984eeef6c2eb68 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 17 Jun 2016 17:37:33 -0700 Subject: [PATCH 1/6] Allow languages to specify if they want to support completion on backspace or not. --- .../Completion/CSharpCompletionService.cs | 12 ++------- .../Portable/Completion/CompletionOptions.cs | 4 ++- .../CompletionServiceWithProviders.cs | 14 +++++++++- .../VisualBasicCompletionService.vb | 21 +++++---------- .../CSharp/Impl/CSharpVSResources.Designer.cs | 27 ++++++++++++------- .../CSharp/Impl/CSharpVSResources.resx | 9 ++++--- .../CSharpSettingsManagerOptionSerializer.cs | 23 ++++++++++++++++ .../IntelliSenseOptionPageControl.xaml | 11 +++++--- .../IntelliSenseOptionPageControl.xaml.cs | 20 +++++++++++++- .../Options/IntelliSenseOptionPageStrings.cs | 9 ++++--- .../Impl/BasicVSResources.Designer.vb | 18 +++++++++++++ .../VisualBasic/Impl/BasicVSResources.resx | 6 +++++ .../IntelliSenseOptionPageControl.xaml | 11 ++++++++ .../IntelliSenseOptionPageControl.xaml.vb | 17 ++++++++++++ .../Options/IntelliSenseOptionPageStrings.vb | 6 +++++ ...ualBasicSettingsManagerOptionSerializer.vb | 21 ++++++++++++++- 16 files changed, 181 insertions(+), 48 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs b/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs index 2844ef145b3dd..87218bb9ae642 100644 --- a/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs +++ b/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs @@ -1,18 +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 System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Completion.SuggestionMode; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.Completion @@ -57,10 +52,7 @@ public CSharpCompletionService( _workspace = workspace; } - public override string Language - { - get { return LanguageNames.CSharp; } - } + public override string Language => LanguageNames.CSharp; protected override ImmutableArray GetBuiltInProviders() { @@ -95,4 +87,4 @@ public override CompletionRules GetRules() return newRules; } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Completion/CompletionOptions.cs b/src/Features/Core/Portable/Completion/CompletionOptions.cs index 907da69a6914a..5aafb2597191a 100644 --- a/src/Features/Core/Portable/Completion/CompletionOptions.cs +++ b/src/Features/Core/Portable/Completion/CompletionOptions.cs @@ -12,7 +12,9 @@ internal static class CompletionOptions public static readonly PerLanguageOption HideAdvancedMembers = new PerLanguageOption(FeatureName, "HideAdvancedMembers", defaultValue: false); public static readonly PerLanguageOption IncludeKeywords = new PerLanguageOption(FeatureName, "IncludeKeywords", defaultValue: true); public static readonly PerLanguageOption TriggerOnTyping = new PerLanguageOption(FeatureName, "TriggerOnTyping", defaultValue: true); - public static readonly PerLanguageOption TriggerOnTypingLetters = new PerLanguageOption(FeatureName, "TriggerOnTypingLetters", defaultValue: true); + + public static readonly PerLanguageOption TriggerOnTypingLetters = new PerLanguageOption(FeatureName, nameof(TriggerOnTypingLetters), defaultValue: true); + public static readonly PerLanguageOption TriggerOnDeletion = new PerLanguageOption(FeatureName, nameof(TriggerOnDeletion), defaultValue: null); public static readonly PerLanguageOption EnterKeyBehavior = new PerLanguageOption(FeatureName, nameof(EnterKeyBehavior), defaultValue: EnterKeyRule.Default); diff --git a/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs b/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs index 8f02612b213e4..eb5fd893548e1 100644 --- a/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs +++ b/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs @@ -401,7 +401,8 @@ private async Task GetContextAsync( } } - public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, ImmutableHashSet roles = null, OptionSet options = null) + public override bool ShouldTriggerCompletion( + SourceText text, int caretPosition, CompletionTrigger trigger, ImmutableHashSet roles = null, OptionSet options = null) { options = options ?? _workspace.Options; if (!options.GetOption(CompletionOptions.TriggerOnTyping, this.Language)) @@ -409,10 +410,21 @@ public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, return false; } + if (trigger.Kind == CompletionTriggerKind.Deletion && this.SupportsTriggerOnDeletion(options)) + { + return Char.IsLetterOrDigit(trigger.Character) || trigger.Character == '.'; + } + var providers = this.GetProviders(roles, CompletionTrigger.Default); return providers.Any(p => p.ShouldTriggerCompletion(text, caretPosition, trigger, options)); } + internal virtual bool SupportsTriggerOnDeletion(OptionSet options) + { + var opt = options.GetOption(CompletionOptions.TriggerOnDeletion, this.Language); + return opt == true; + } + public override async Task GetChangeAsync( Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken) { diff --git a/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb b/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb index 1fa284df94934..ed66986a83554 100644 --- a/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb +++ b/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb @@ -2,10 +2,8 @@ Imports System.Collections.Immutable Imports System.Composition -Imports System.Globalization Imports System.Threading Imports Microsoft.CodeAnalysis.Completion -Imports Microsoft.CodeAnalysis.Completion.Providers Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Text @@ -151,18 +149,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion Return CompletionUtilities.GetCompletionItemSpan(text, caretPosition) End Function - Public Overrides Function ShouldTriggerCompletion(text As SourceText, position As Integer, trigger As CompletionTrigger, Optional roles As ImmutableHashSet(Of String) = Nothing, Optional options As OptionSet = Nothing) As Boolean - options = If(options, _workspace.Options) - - If Not options.GetOption(CompletionOptions.TriggerOnTyping, Me.Language) Then - Return False - End If - - If trigger.Kind = CompletionTriggerKind.Deletion AndAlso (Char.IsLetterOrDigit(trigger.Character) OrElse trigger.Character = "."c) Then - Return True - Else - Return MyBase.ShouldTriggerCompletion(text, position, trigger, roles, options) - End If + Friend Overrides Function SupportsTriggerOnDeletion(options As OptionSet) As Boolean + ' If the option is null (i.e. default) or 'true', then we want to trigger completion. + ' Only if the option is false do we not want to trigger. + Dim opt = options.GetOption(CompletionOptions.TriggerOnDeletion, Me.Language) + Return If(opt = False, False, True) End Function End Class -End Namespace +End Namespace \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs b/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs index e124ac3be1a91..c18cdec807096 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs @@ -402,15 +402,6 @@ internal static string Option_Always_add_new_line_on_enter { } } - /// - /// Looks up a localized string similar to _Show completion list after a character is typed. - /// - internal static string Option_BringUpOnIdentifier { - get { - return ResourceManager.GetString("Option_BringUpOnIdentifier", resourceCulture); - } - } - /// /// Looks up a localized string similar to Enable full solution _analysis. /// @@ -636,6 +627,24 @@ internal static string Option_Show_completion_item_filters { } } + /// + /// Looks up a localized string similar to Show completion list after a character is _deleted. + /// + internal static string Option_Show_completion_list_after_a_character_is_deleted { + get { + return ResourceManager.GetString("Option_Show_completion_list_after_a_character_is_deleted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _Show completion list after a character is typed. + /// + internal static string Option_Show_completion_list_after_a_character_is_typed { + get { + return ResourceManager.GetString("Option_Show_completion_list_after_a_character_is_typed", resourceCulture); + } + } + /// /// Looks up a localized string similar to Place _keywords in completion lists. /// diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx index 2886264b8e516..50dced64eac93 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx @@ -378,7 +378,7 @@ _Place 'System' directives first when sorting usings - + _Show completion list after a character is typed @@ -468,10 +468,13 @@ _Only add new line on enter after end of fully typed word - + _Always add new line on enter - + _Never add new line on enter + + Show completion list after a character is _deleted + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Impl/Options/CSharpSettingsManagerOptionSerializer.cs b/src/VisualStudio/CSharp/Impl/Options/CSharpSettingsManagerOptionSerializer.cs index 72ed0fac23cbe..34c90be2ba566 100644 --- a/src/VisualStudio/CSharp/Impl/Options/CSharpSettingsManagerOptionSerializer.cs +++ b/src/VisualStudio/CSharp/Impl/Options/CSharpSettingsManagerOptionSerializer.cs @@ -82,6 +82,7 @@ protected override ImmutableDictionary CreateStorageKeyToOption { new KeyValuePair(GetStorageKeyForOption(CompletionOptions.IncludeKeywords), CompletionOptions.IncludeKeywords), new KeyValuePair(GetStorageKeyForOption(CompletionOptions.TriggerOnTypingLetters), CompletionOptions.TriggerOnTypingLetters), + new KeyValuePair(GetStorageKeyForOption(CompletionOptions.TriggerOnDeletion), CompletionOptions.TriggerOnDeletion), new KeyValuePair(GetStorageKeyForOption(CompletionOptions.ShowCompletionItemFilters), CompletionOptions.ShowCompletionItemFilters), new KeyValuePair(GetStorageKeyForOption(CompletionOptions.HighlightMatchingPortionsOfCompletionListItems), CompletionOptions.HighlightMatchingPortionsOfCompletionListItems), }); @@ -146,6 +147,7 @@ protected override bool SupportsOption(IOption option, string languageName) { if (option == CompletionOptions.IncludeKeywords || option == CompletionOptions.TriggerOnTypingLetters || + option == CompletionOptions.TriggerOnDeletion || option == CompletionOptions.ShowCompletionItemFilters || option == CompletionOptions.HighlightMatchingPortionsOfCompletionListItems || option == CompletionOptions.EnterKeyBehavior || @@ -272,9 +274,30 @@ public override bool TryFetch(OptionKey optionKey, out object value) return FetchEnterKeyBehavior(optionKey, out value); } + if (optionKey.Option == CompletionOptions.TriggerOnDeletion) + { + return FetchTriggerOnDeletion(optionKey, out value); + } + return base.TryFetch(optionKey, out value); } + private bool FetchTriggerOnDeletion(OptionKey optionKey, out object value) + { + if (!base.TryFetch(optionKey, out value)) + { + return false; + } + + if (value == null) + { + // The default behavior for c# is to not trigger completion on deletion. + value = (bool?)false; + } + + return true; + } + private bool FetchStyleBool(string settingName, out object value) { var typeStyleValue = Manager.GetValueOrDefault(settingName); diff --git a/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml index fef4bfc5e01c6..725ed7a7d5e46 100644 --- a/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml +++ b/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml @@ -15,9 +15,9 @@ - @@ -27,6 +27,11 @@ + + CSharpVSResources.Option_Show_completion_list_after_a_character_is_typed; + + public static string Option_Show_completion_list_after_a_character_is_deleted => + CSharpVSResources.Option_Show_completion_list_after_a_character_is_deleted; public static string Option_CompletionLists { diff --git a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.Designer.vb b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.Designer.vb index 745daece03034..eabfac302c769 100644 --- a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.Designer.vb +++ b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.Designer.vb @@ -388,6 +388,24 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic End Get End Property + ''' + ''' Looks up a localized string similar to Show completion list after a character is _deleted. + ''' + Friend Shared ReadOnly Property Option_Show_completion_list_after_a_character_is_deleted() As String + Get + Return ResourceManager.GetString("Option_Show_completion_list_after_a_character_is_deleted", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to _Show completion list after a character is typed. + ''' + Friend Shared ReadOnly Property Option_Show_completion_list_after_a_character_is_typed() As String + Get + Return ResourceManager.GetString("Option_Show_completion_list_after_a_character_is_typed", resourceCulture) + End Get + End Property + ''' ''' Looks up a localized string similar to Suggest imports for types in _NuGet packages. ''' diff --git a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx index cdd50c3b7f550..2c528241bb342 100644 --- a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx +++ b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx @@ -258,4 +258,10 @@ _Never add new line on enter + + Show completion list after a character is _deleted + + + _Show completion list after a character is typed + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml index 6871862ce7eb6..b842572ded3e3 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml +++ b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml @@ -15,6 +15,17 @@ + + + + + diff --git a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb index 18dc0611a44d6..9f108056369d1 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageControl.xaml.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.Windows Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.VisualStudio.LanguageServices.Implementation.Options @@ -12,6 +13,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options MyBase.New(serviceProvider) InitializeComponent() + BindToOption(Show_completion_list_after_a_character_is_typed, CompletionOptions.TriggerOnTypingLetters, LanguageNames.VisualBasic) + Show_completion_list_after_a_character_is_deleted.IsChecked = Me.OptionService.GetOption( + CompletionOptions.TriggerOnDeletion, LanguageNames.VisualBasic) <> False + BindToOption(Show_completion_item_filters, CompletionOptions.ShowCompletionItemFilters, LanguageNames.VisualBasic) BindToOption(Highlight_matching_portions_of_completion_list_items, CompletionOptions.HighlightMatchingPortionsOfCompletionListItems, LanguageNames.VisualBasic) @@ -19,5 +24,17 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options BindToOption(Only_add_new_line_on_enter_with_whole_word, CompletionOptions.EnterKeyBehavior, EnterKeyRule.AfterFullyTypedWord, LanguageNames.VisualBasic) BindToOption(Always_add_new_line_on_enter, CompletionOptions.EnterKeyBehavior, EnterKeyRule.Always, LanguageNames.VisualBasic) End Sub + + Private Sub Show_completion_list_after_a_character_is_deleted_Checked(sender As Object, e As RoutedEventArgs) + Me.OptionService.SetOptions( + Me.OptionService.GetOptions().WithChangedOption( + CompletionOptions.TriggerOnDeletion, LanguageNames.VisualBasic, value:=True)) + End Sub + + Private Sub Show_completion_list_after_a_character_is_deleted_Unchecked(sender As Object, e As RoutedEventArgs) + Me.OptionService.SetOptions( + Me.OptionService.GetOptions().WithChangedOption( + CompletionOptions.TriggerOnDeletion, LanguageNames.VisualBasic, value:=False)) + End Sub End Class End Namespace \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb index cb5613263810c..092a0849adbc6 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/IntelliSenseOptionPageStrings.vb @@ -5,6 +5,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options Public ReadOnly Property Option_CompletionLists As String = BasicVSResources.Option_CompletionLists + Public ReadOnly Property Option_Show_completion_list_after_a_character_is_typed As String = + BasicVSResources.Option_Show_completion_list_after_a_character_is_typed + + Public ReadOnly Property Option_Show_completion_list_after_a_character_is_deleted As String = + BasicVSResources.Option_Show_completion_list_after_a_character_is_deleted + Public ReadOnly Property Option_Highlight_matching_portions_of_completion_list_items As String = BasicVSResources.Option_Highlight_matching_portions_of_completion_list_items diff --git a/src/VisualStudio/VisualBasic/Impl/Options/VisualBasicSettingsManagerOptionSerializer.vb b/src/VisualStudio/VisualBasic/Impl/Options/VisualBasicSettingsManagerOptionSerializer.vb index 5114dfaf33f13..9fe3ed81ecbe1 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/VisualBasicSettingsManagerOptionSerializer.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/VisualBasicSettingsManagerOptionSerializer.vb @@ -86,7 +86,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options End Property Protected Overrides Function SupportsOption([option] As IOption, languageName As String) As Boolean - If [option].Name = CompletionOptions.EnterKeyBehavior.Name Then + If [option].Name = CompletionOptions.EnterKeyBehavior.Name OrElse + [option].Name = CompletionOptions.TriggerOnTypingLetters.Name OrElse + [option].Name = CompletionOptions.TriggerOnDeletion.Name Then Return True ElseIf languageName = LanguageNames.VisualBasic Then @@ -165,9 +167,26 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options Return FetchEnterKeyBehavior(optionKey, value) End If + If optionKey.Option Is CompletionOptions.TriggerOnDeletion Then + Return FetchTriggerOnDeletion(optionKey, value) + End If + Return MyBase.TryFetch(optionKey, value) End Function + Private Function FetchTriggerOnDeletion(optionKey As OptionKey, ByRef value As Object) As Boolean + If MyBase.TryFetch(optionKey, value) Then + If value Is Nothing Then + ' The default behavior for VB is to trigger completion on deletion. + value = CType(True, Boolean?) + End If + + Return True + End If + + Return False + End Function + Private Function FetchEnterKeyBehavior(optionKey As OptionKey, ByRef value As Object) As Boolean If MyBase.TryFetch(optionKey, value) Then If value.Equals(EnterKeyRule.Default) Then From da05dbe10e1935ae2c9c230663181627c58a731b Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 17 Jun 2016 17:40:31 -0700 Subject: [PATCH 2/6] Rename parameter. --- .../Completion/Controller.Session_FilterModel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs index 8d6ad949cddcb..7bbea0247011c 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs @@ -289,7 +289,7 @@ private bool IsHardSelection( Model model, PresentationItem bestFilterMatch, ITextSnapshot textSnapshot, - CompletionHelper completionRules, + CompletionHelper completionHelper, CompletionTrigger trigger, CompletionFilterReason reason) { @@ -317,7 +317,7 @@ private bool IsHardSelection( var viewSpan = model.GetViewBufferSpan(bestFilterMatch.Item.Span); var fullFilterText = model.GetCurrentTextInSnapshot(viewSpan, textSnapshot, endPoint: null); - var shouldSoftSelect = completionRules.ShouldSoftSelectItem(bestFilterMatch.Item, fullFilterText, trigger); + var shouldSoftSelect = completionHelper.ShouldSoftSelectItem(bestFilterMatch.Item, fullFilterText, trigger); if (shouldSoftSelect) { return false; @@ -325,7 +325,7 @@ private bool IsHardSelection( // If the user moved the caret left after they started typing, the 'best' match may not match at all // against the full text span that this item would be replacing. - if (!completionRules.MatchesFilterText(bestFilterMatch.Item, fullFilterText, trigger, reason, this.Controller.GetRecentItems())) + if (!completionHelper.MatchesFilterText(bestFilterMatch.Item, fullFilterText, trigger, reason, this.Controller.GetRecentItems())) { return false; } From 15c647c714eb175de4b4d9c18d673f620aad890c Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 17 Jun 2016 18:20:49 -0700 Subject: [PATCH 3/6] Make the VB deletion behavior the default for all languages. --- .../InternalUtilities/StringExtensions.cs | 14 ++++++++++- .../Completion/CompletionHelper.cs | 11 --------- .../Controller.Session_FilterModel.cs | 23 +++++++++++++++++-- .../Completion/VisualBasicCompletionHelper.vb | 16 ++----------- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/Compilers/Core/Portable/InternalUtilities/StringExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/StringExtensions.cs index b0c1469a12176..9574c95884db3 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/StringExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/StringExtensions.cs @@ -266,5 +266,17 @@ internal static bool All(this string arg, Predicate predicate) return true; } + + public static int GetCaseInsensitivePrefixLength(this string string1, string string2) + { + int x = 0; + while (x < string1.Length && x < string2.Length && + char.ToUpper(string1[x]) == char.ToUpper(string2[x])) + { + x++; + } + + return x; + } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs index c5a183ec94163..f3aa84ee50a48 100644 --- a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs +++ b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs @@ -235,17 +235,6 @@ protected static bool IsEnumMemberItem(CompletionItem item) return item.Tags.Contains(CompletionTags.EnumMember); } - protected int GetPrefixLength(string text, string pattern) - { - int x = 0; - while (x < text.Length && x < pattern.Length && char.ToUpper(text[x]) == char.ToUpper(pattern[x])) - { - x++; - } - - return x; - } - protected int CompareMatches(PatternMatch match1, PatternMatch match2, CompletionItem item1, CompletionItem item2) { // First see how the two items compare in a case insensitive fashion. Matches that diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs index 7bbea0247011c..6588ba88b12df 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs @@ -152,7 +152,7 @@ private Model FilterModelInBackgroundWorker( } var filterText = model.GetCurrentTextInSnapshot(currentItem.Item.Span, textSnapshot, textSpanToText); - var matchesFilterText = helper.MatchesFilterText(currentItem.Item, filterText, model.Trigger, filterReason, recentItems); + var matchesFilterText = MatchesFilterText(helper, currentItem.Item, filterText, model.Trigger, filterReason, recentItems); itemToFilterText[currentItem.Item] = filterText; if (matchesFilterText) @@ -256,6 +256,25 @@ private Model FilterModelInBackgroundWorker( return result; } + private static bool MatchesFilterText( + CompletionHelper helper, CompletionItem item, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray recentItems) + { + // For the deletion we bake in the core logic for how matching should work. + // This way deletion feels the same across all languages that opt into deletion + // as a completion trigger. + + // Specifically, to avoid being too aggressive when matching an item during + // completion, we require that the current filter text be a prefix of the + // item in the list. + if (filterReason == CompletionFilterReason.BackspaceOrDelete && + trigger.Kind == CompletionTriggerKind.Deletion) + { + return item.FilterText.GetCaseInsensitivePrefixLength(filterText) > 0; + } + + return helper.MatchesFilterText(item, filterText, trigger, filterReason, recentItems); + } + private bool ItemIsFilteredOut( CompletionItem item, ImmutableDictionary filterState) @@ -325,7 +344,7 @@ private bool IsHardSelection( // If the user moved the caret left after they started typing, the 'best' match may not match at all // against the full text span that this item would be replacing. - if (!completionHelper.MatchesFilterText(bestFilterMatch.Item, fullFilterText, trigger, reason, this.Controller.GetRecentItems())) + if (!MatchesFilterText(completionHelper, bestFilterMatch.Item, fullFilterText, trigger, reason, this.Controller.GetRecentItems())) { return false; } diff --git a/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb b/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb index b17ae351520aa..0fb933438427c 100644 --- a/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb +++ b/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb @@ -1,6 +1,5 @@ Imports System.Collections.Immutable Imports System.Composition -Imports System.Globalization Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Host.Mef @@ -41,22 +40,11 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Completion End Get End Property - Public Overrides Function MatchesFilterText(item As CompletionItem, filterText As String, trigger As CompletionTrigger, filterReason As CompletionFilterReason, Optional recentItems As ImmutableArray(Of String) = Nothing) As Boolean - ' If this Is a session started on backspace, we use a much looser prefix match check - ' to see if an item matches - - If filterReason = CompletionFilterReason.BackspaceOrDelete AndAlso trigger.Kind = CompletionTriggerKind.Deletion Then - Return GetPrefixLength(item.FilterText, filterText) > 0 - End If - - Return MyBase.MatchesFilterText(item, filterText, trigger, filterReason, recentItems) - End Function - Public Overrides Function IsBetterFilterMatch(item1 As CompletionItem, item2 As CompletionItem, filterText As String, trigger As CompletionTrigger, filterReason As CompletionFilterReason, Optional recentItems As ImmutableArray(Of String) = Nothing) As Boolean If filterReason = CompletionFilterReason.BackspaceOrDelete Then - Dim prefixLength1 = GetPrefixLength(item1.FilterText, filterText) - Dim prefixLength2 = GetPrefixLength(item2.FilterText, filterText) + Dim prefixLength1 = item1.FilterText.GetCaseInsensitivePrefixLength(filterText) + Dim prefixLength2 = item2.FilterText.GetCaseInsensitivePrefixLength(filterText) Return prefixLength1 > prefixLength2 OrElse ((item1.Rules.MatchPriority > MatchPriority.Default AndAlso Not item2.Rules.MatchPriority > MatchPriority.Default) AndAlso Not IsEnumMemberItem(item1)) End If From 0e4d6a3bd2ada2568c049ff18a412aeca29b53be Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Sun, 19 Jun 2016 00:46:41 -0700 Subject: [PATCH 4/6] Make more deletion code non-VB specific. --- .../Controller.Session_FilterModel.cs | 37 ++++++++++++++++++- .../Completion/VisualBasicCompletionHelper.vb | 7 ---- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs index d6a1c08ca58a2..141cf208e7085 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs @@ -163,7 +163,7 @@ private Model FilterModelInBackgroundWorker( // If we have no best match, or this match is better than the last match, // then the current item is the best filter match. if (bestFilterMatch == null || - helper.IsBetterFilterMatch(currentItem.Item, bestFilterMatch.Item, filterText, model.Trigger, filterReason, recentItems)) + IsBetterFilterMatch(helper, currentItem.Item, bestFilterMatch.Item, filterText, model.Trigger, filterReason, recentItems)) { bestFilterMatch = currentItem; } @@ -257,8 +257,41 @@ private Model FilterModelInBackgroundWorker( return result; } + private static bool IsBetterFilterMatch( + CompletionHelper helper, CompletionItem item1, CompletionItem item2, + string filterText, CompletionTrigger trigger, + CompletionFilterReason filterReason, ImmutableArray recentItems) + { + // For the deletion we bake in the core logic for how betterness should work. + // This way deletion feels the same across all languages that opt into deletion + // as a completion trigger. + if (filterReason == CompletionFilterReason.BackspaceOrDelete) + { + var prefixLength1 = item1.FilterText.GetCaseInsensitivePrefixLength(filterText); + var prefixLength2 = item2.FilterText.GetCaseInsensitivePrefixLength(filterText); + + // Prefer the item that matches a longer prefix of the filter text. + if (prefixLength1 > prefixLength2) + { + return true; + } + + // If the lengths are the same, prefer the one with the higher match priority. + if (item1.Rules.MatchPriority > item2.Rules.MatchPriority) + { + return true; + } + + return false; + } + + return helper.IsBetterFilterMatch(item1, item2, filterText, trigger, filterReason, recentItems); + } + private static bool MatchesFilterText( - CompletionHelper helper, CompletionItem item, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray recentItems) + CompletionHelper helper, CompletionItem item, + string filterText, CompletionTrigger trigger, + CompletionFilterReason filterReason, ImmutableArray recentItems) { // For the deletion we bake in the core logic for how matching should work. // This way deletion feels the same across all languages that opt into deletion diff --git a/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb b/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb index 61db270a59981..cdf848542bcf4 100644 --- a/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb +++ b/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb @@ -41,13 +41,6 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Completion End Property Public Overrides Function IsBetterFilterMatch(item1 As CompletionItem, item2 As CompletionItem, filterText As String, trigger As CompletionTrigger, filterReason As CompletionFilterReason, Optional recentItems As ImmutableArray(Of String) = Nothing) As Boolean - - If filterReason = CompletionFilterReason.BackspaceOrDelete Then - Dim prefixLength1 = item1.FilterText.GetCaseInsensitivePrefixLength(filterText) - Dim prefixLength2 = item2.FilterText.GetCaseInsensitivePrefixLength(filterText) - Return prefixLength1 > prefixLength2 OrElse ((item1.Rules.MatchPriority > MatchPriority.Default AndAlso Not item2.Rules.MatchPriority > MatchPriority.Default) AndAlso Not IsEnumMemberItem(item1)) - End If - If IsEnumMemberItem(item2) Then Dim match1 = GetMatch(item1, filterText) Dim match2 = GetMatch(item2, filterText) From 403afb884e33611738e2a2118bc24b2ceeccee92 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Sun, 19 Jun 2016 00:59:22 -0700 Subject: [PATCH 5/6] Remove unnecessary parameter. --- .../Core/Extensibility/Completion/CompletionHelper.cs | 6 ++++-- .../Completion/Controller.Session_FilterModel.cs | 4 ++-- .../Test2/IntelliSense/CompletionRulesTests.vb | 4 ++-- .../VisualBasic/Completion/VisualBasicCompletionHelper.vb | 7 +++++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs index e5d8edd55d391..54c461815d541 100644 --- a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs +++ b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs @@ -64,7 +64,9 @@ public IReadOnlyList GetHighlightedSpans(CompletionItem completionItem /// iff the completion item matches and should be included in the filtered completion /// results, or false if it should not be. /// - public virtual bool MatchesFilterText(CompletionItem item, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray recentItems = default(ImmutableArray)) + public virtual bool MatchesFilterText( + CompletionItem item, string filterText, + CompletionTrigger trigger, ImmutableArray recentItems) { // If the user hasn't typed anything, and this item was preselected, or was in the // MRU list, then we definitely want to include it. @@ -166,7 +168,7 @@ private PatternMatcher GetEnUSPatternMatcher(string value) /// Returns true if item1 is a better completion item than item2 given the provided filter /// text, or false if it is not better. /// - public virtual bool IsBetterFilterMatch(CompletionItem item1, CompletionItem item2, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray recentItems = default(ImmutableArray)) + public virtual bool IsBetterFilterMatch(CompletionItem item1, CompletionItem item2, string filterText, CompletionTrigger trigger, ImmutableArray recentItems) { var match1 = GetMatch(item1, filterText); var match2 = GetMatch(item2, filterText); diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs index 141cf208e7085..470f042db6001 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs @@ -285,7 +285,7 @@ private static bool IsBetterFilterMatch( return false; } - return helper.IsBetterFilterMatch(item1, item2, filterText, trigger, filterReason, recentItems); + return helper.IsBetterFilterMatch(item1, item2, filterText, trigger, recentItems); } private static bool MatchesFilterText( @@ -306,7 +306,7 @@ private static bool MatchesFilterText( return item.FilterText.GetCaseInsensitivePrefixLength(filterText) > 0; } - return helper.MatchesFilterText(item, filterText, trigger, filterReason, recentItems); + return helper.MatchesFilterText(item, filterText, trigger, recentItems); } private bool ItemIsFilteredOut( diff --git a/src/EditorFeatures/Test2/IntelliSense/CompletionRulesTests.vb b/src/EditorFeatures/Test2/IntelliSense/CompletionRulesTests.vb index 63a174dea0432..ca87b99404f97 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CompletionRulesTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CompletionRulesTests.vb @@ -51,7 +51,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Dim helper = CompletionHelper.GetHelper(workspace, LanguageNames.CSharp) 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}") + Assert.True(helper.MatchesFilterText(item, v, CompletionTrigger.Default, Nothing), $"Expected item {word} does not match {v}") Next End Using End Sub @@ -62,7 +62,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Dim helper = CompletionHelper.GetHelper(workspace, LanguageNames.CSharp) 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}") + Assert.False(helper.MatchesFilterText(item, v, CompletionTrigger.Default, Nothing), $"Unexpected item {word} matches {v}") Next End Using diff --git a/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb b/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb index cdf848542bcf4..6ed5c34fc3a8b 100644 --- a/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb +++ b/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb @@ -40,7 +40,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Completion End Get End Property - Public Overrides Function IsBetterFilterMatch(item1 As CompletionItem, item2 As CompletionItem, filterText As String, trigger As CompletionTrigger, filterReason As CompletionFilterReason, Optional recentItems As ImmutableArray(Of String) = Nothing) As Boolean + Public Overrides Function IsBetterFilterMatch( + item1 As CompletionItem, item2 As CompletionItem, + filterText As String, trigger As CompletionTrigger, + recentItems As ImmutableArray(Of String)) As Boolean If IsEnumMemberItem(item2) Then Dim match1 = GetMatch(item1, filterText) Dim match2 = GetMatch(item2, filterText) @@ -59,7 +62,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Completion End If End If - Return MyBase.IsBetterFilterMatch(item1, item2, filterText, trigger, filterReason, recentItems) + Return MyBase.IsBetterFilterMatch(item1, item2, filterText, trigger, recentItems) End Function End Class End Namespace \ No newline at end of file From ebc4ef3adf82800a028450cc09fbd2ae8de970ac Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Sun, 19 Jun 2016 01:38:58 -0700 Subject: [PATCH 6/6] Don't preselect softly selected items. --- .../Completion/Controller.Session_FilterModel.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs index 470f042db6001..7a8212ed454eb 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs @@ -277,7 +277,14 @@ private static bool IsBetterFilterMatch( } // If the lengths are the same, prefer the one with the higher match priority. - if (item1.Rules.MatchPriority > item2.Rules.MatchPriority) + // But only if it's an item that would have been hard selected. We don't want + // to aggressively select an item that was only going to be softly offered. + var item1Priority = item1.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection + ? item1.Rules.MatchPriority : MatchPriority.Default; + var item2Priority = item2.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection + ? item2.Rules.MatchPriority : MatchPriority.Default; + + if (item1Priority > item2Priority) { return true; }