Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make more deletion code non-VB specific. #12093

Merged
merged 7 commits into from
Jun 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -266,5 +266,17 @@ internal static bool All(this string arg, Predicate<char> 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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ public IReadOnlyList<TextSpan> GetHighlightedSpans(CompletionItem completionItem
/// iff the completion item matches and should be included in the filtered completion
/// results, or false if it should not be.
/// </summary>
public virtual bool MatchesFilterText(CompletionItem item, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray<string> recentItems = default(ImmutableArray<string>))
public virtual bool MatchesFilterText(
CompletionItem item, string filterText,
CompletionTrigger trigger, ImmutableArray<string> 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.
Expand Down Expand Up @@ -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.
/// </summary>
public virtual bool IsBetterFilterMatch(CompletionItem item1, CompletionItem item2, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray<string> recentItems = default(ImmutableArray<string>))
public virtual bool IsBetterFilterMatch(CompletionItem item1, CompletionItem item2, string filterText, CompletionTrigger trigger, ImmutableArray<string> recentItems)
{
var match1 = GetMatch(item1, filterText);
var match2 = GetMatch(item2, filterText);
Expand Down Expand Up @@ -236,17 +238,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,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)
Expand All @@ -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;
}
Expand Down Expand Up @@ -257,6 +257,65 @@ private Model FilterModelInBackgroundWorker(
return result;
}

private static bool IsBetterFilterMatch(
CompletionHelper helper, CompletionItem item1, CompletionItem item2,
string filterText, CompletionTrigger trigger,
CompletionFilterReason filterReason, ImmutableArray<string> 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.
// 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;
}

return false;
}

return helper.IsBetterFilterMatch(item1, item2, filterText, trigger, recentItems);
}

private static bool MatchesFilterText(
CompletionHelper helper, CompletionItem item,
string filterText, CompletionTrigger trigger,
CompletionFilterReason filterReason, ImmutableArray<string> 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, recentItems);
}

private bool ItemIsFilteredOut(
CompletionItem item,
ImmutableDictionary<CompletionItemFilter, bool> filterState)
Expand Down Expand Up @@ -290,7 +349,7 @@ private bool IsHardSelection(
Model model,
PresentationItem bestFilterMatch,
ITextSnapshot textSnapshot,
CompletionHelper completionRules,
CompletionHelper completionHelper,
CompletionTrigger trigger,
CompletionFilterReason reason)
{
Expand Down Expand Up @@ -326,7 +385,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 (!MatchesFilterText(completionHelper, bestFilterMatch.Item, fullFilterText, trigger, reason, this.Controller.GetRecentItems()))
{
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/EditorFeatures/Test2/IntelliSense/CompletionRulesTests.vb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -41,25 +40,10 @@ 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)
Return prefixLength1 > prefixLength2 OrElse ((item1.Rules.MatchPriority > MatchPriority.Default AndAlso Not item2.Rules.MatchPriority > MatchPriority.Default) AndAlso Not IsEnumMemberItem(item1))
End If

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)
Expand All @@ -78,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
12 changes: 2 additions & 10 deletions src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<CompletionProvider> GetBuiltInProviders()
{
Expand Down Expand Up @@ -95,4 +87,4 @@ public override CompletionRules GetRules()
return newRules;
}
}
}
}
4 changes: 3 additions & 1 deletion src/Features/Core/Portable/Completion/CompletionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ internal static class CompletionOptions
public static readonly PerLanguageOption<bool> HideAdvancedMembers = new PerLanguageOption<bool>(FeatureName, "HideAdvancedMembers", defaultValue: false);
public static readonly PerLanguageOption<bool> IncludeKeywords = new PerLanguageOption<bool>(FeatureName, "IncludeKeywords", defaultValue: true);
public static readonly PerLanguageOption<bool> TriggerOnTyping = new PerLanguageOption<bool>(FeatureName, "TriggerOnTyping", defaultValue: true);
public static readonly PerLanguageOption<bool> TriggerOnTypingLetters = new PerLanguageOption<bool>(FeatureName, "TriggerOnTypingLetters", defaultValue: true);

public static readonly PerLanguageOption<bool> TriggerOnTypingLetters = new PerLanguageOption<bool>(FeatureName, nameof(TriggerOnTypingLetters), defaultValue: true);
public static readonly PerLanguageOption<bool?> TriggerOnDeletion = new PerLanguageOption<bool?>(FeatureName, nameof(TriggerOnDeletion), defaultValue: null);

public static readonly PerLanguageOption<EnterKeyRule> EnterKeyBehavior =
new PerLanguageOption<EnterKeyRule>(FeatureName, nameof(EnterKeyBehavior), defaultValue: EnterKeyRule.Default);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,18 +401,30 @@ private async Task<CompletionContext> GetContextAsync(
}
}

public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, ImmutableHashSet<string> roles = null, OptionSet options = null)
public override bool ShouldTriggerCompletion(
SourceText text, int caretPosition, CompletionTrigger trigger, ImmutableHashSet<string> roles = null, OptionSet options = null)
{
options = options ?? _workspace.Options;
if (!options.GetOption(CompletionOptions.TriggerOnTyping, this.Language))
{
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<CompletionChange> GetChangeAsync(
Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
27 changes: 18 additions & 9 deletions src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading