Skip to content

Commit

Permalink
Fix
Browse files Browse the repository at this point in the history
  • Loading branch information
genlu committed Jan 29, 2022
1 parent f06e2ab commit a83e943
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,7 @@ private async Task VerifyExclusiveAsync(string markup, bool exclusive)
var service = GetCompletionService(document.Project);
var completionList = await GetCompletionListAsync(service, document, position, triggerInfo);

if (completionList != null)
if (!completionList.IsEmpty)
{
Assert.True(exclusive == completionList.GetTestAccessor().IsExclusive, "group.IsExclusive == " + completionList.GetTestAccessor().IsExclusive);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void Method() {
var caretPosition = workspace.DocumentWithCursor.CursorPosition ?? throw new InvalidOperationException();
var completions = await completionService.GetCompletionsAsync(document, caretPosition, CompletionOptions.Default);

Assert.NotNull(completions);
Assert.False(completions.IsEmpty);
var item = Assert.Single(completions.Items.Where(item => item.ProviderName == typeof(DebugAssertTestCompletionProvider).FullName));
Assert.Equal("Assertion failed", item.DisplayText);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Dim list = Await completionService.GetCompletionsAsync(
document, caretPosition:=0, options:=CompletionOptions.Default, trigger:=CompletionTrigger.Invoke)

Assert.NotNull(list)
Assert.NotEmpty(list.Items)
Assert.True(list.Items.Length = 1, "Completion list contained more than one item")
Assert.Equal("Completion Item From Test Completion Provider", list.Items.First.DisplayText)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Dim list = Await completionService.GetCompletionsAsync(
document, caretPosition:=0, options:=CompletionOptions.Default, trigger:=CompletionTrigger.Invoke)

Assert.NotNull(list)
Assert.NotEmpty(list.Items)
Assert.True(list.Items.Length = 2, "Completion List does not contain exactly two items.")
Assert.Equal(String.Format(CompletionItemExclusive, 2), list.Items.First.DisplayText)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ private protected async Task CheckResultsAsync(
var displayOptions = SymbolDescriptionOptions.From(document.Project);
var completionService = GetCompletionService(document.Project);
var completionList = await GetCompletionListAsync(completionService, document, position, trigger, options);
var items = completionList == null ? ImmutableArray<RoslynCompletion.CompletionItem>.Empty : completionList.Items;
var items = completionList.Items;

if (hasSuggestionModeItem != null)
{
Expand Down Expand Up @@ -1114,7 +1114,7 @@ protected async Task VerifyCommitCharactersAsync(string initialMarkup, string te
var completionService = GetCompletionService(document.Project);
var completionList = await GetCompletionListAsync(completionService, document, position, trigger);

return completionList == null ? ImmutableArray<RoslynCompletion.CompletionItem>.Empty : completionList.Items;
return completionList.Items;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ End Program</Document>
Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id)
Dim service = GetCompletionService(document.Project)
Dim completionList = Await GetCompletionListAsync(service, document, caretPosition, RoslynCompletion.CompletionTrigger.Invoke)
Assert.True(completionList Is Nothing OrElse completionList.GetTestAccessor().IsExclusive, "Expected always exclusive")
Assert.True(completionList.IsEmpty OrElse completionList.GetTestAccessor().IsExclusive, "Expected always exclusive")
End Using
End Function

Expand Down
19 changes: 18 additions & 1 deletion src/Features/Core/Portable/Completion/CompletionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
Expand All @@ -20,6 +21,7 @@ public sealed class CompletionContext

private CompletionItem? _suggestionModeItem;
private OptionSet? _lazyOptionSet;
private bool _isExclusive;

internal CompletionProvider Provider { get; }

Expand Down Expand Up @@ -71,8 +73,23 @@ public sealed class CompletionContext

/// <summary>
/// Set to true if the items added here should be the only items presented to the user.
/// Expand items should never be exclusive.
/// </summary>
public bool IsExclusive { get; set; }
public bool IsExclusive
{
get
{
return _isExclusive && !Provider.IsExpandItemProvider;
}

set
{
if (value)
Debug.Assert(!Provider.IsExpandItemProvider);

_isExclusive = value;
}
}

/// <summary>
/// Creates a <see cref="CompletionContext"/> instance.
Expand Down
2 changes: 2 additions & 0 deletions src/Features/Core/Portable/Completion/CompletionList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ public CompletionList WithSuggestionModeItem(CompletionItem suggestionModeItem)
default, default, CompletionRules.Default,
suggestionModeItem: null, isExclusive: false);

internal bool IsEmpty => Items.IsEmpty && SuggestionModeItem is null;

internal TestAccessor GetTestAccessor()
=> new(this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,14 @@ private protected async Task<CompletionList> GetCompletionsWithAvailabilityOfExp
document, caretPosition, trigger, options, defaultItemSpan, triggeredProviders, cancellationToken).ConfigureAwait(false);

// Nothing to do if we didn't even get any regular items back (i.e. 0 items or suggestion item only.)
if (!triggeredContexts.ContainsNonSuggestionModeItem)
if (!triggeredContexts.Any(cc => cc.Items.Count > 0))
return CompletionList.Empty;

// See if there were completion contexts provided that were exclusive. If so, then that's all we'll return.
if (triggeredContexts.ContainsExclusiveContext)
return MergeAndPruneCompletionLists(triggeredContexts.GetExclusiveContexts(), defaultItemSpan, options, isExclusive: true);
// See if there were completion contexts provided that were exclusive. If so, then
// that's all we'll return.
var exclusiveContexts = triggeredContexts.Where(t => t.IsExclusive).ToImmutableArray();
if (!exclusiveContexts.IsEmpty)
return MergeAndPruneCompletionLists(exclusiveContexts, defaultItemSpan, options, isExclusive: true);

// Great! We had some items. Now we want to see if any of the other providers
// would like to augment the completion list. For example, we might trigger
Expand All @@ -148,31 +150,31 @@ private protected async Task<CompletionList> GetCompletionsWithAvailabilityOfExp
// Providers are ordered, but we processed them in our own order. Ensure that the
// groups are properly ordered based on the original providers.
var completionProviderToIndex = GetCompletionProviderToIndex(providers);
var allContexts = triggeredContexts.NonEmptyContexts.Concat(augmentingContexts.NonEmptyContexts)
var allContexts = triggeredContexts.Concat(augmentingContexts)
.Sort((p1, p2) => completionProviderToIndex[p1.Provider] - completionProviderToIndex[p2.Provider]);

return MergeAndPruneCompletionLists(allContexts, defaultItemSpan, options, isExclusive: false);

ImmutableArray<CompletionProvider> GetTriggeredProviders(
Document document, ConcatImmutableArray<CompletionProvider> allProviders, int caretPosition, CompletionOptions options, CompletionTrigger trigger, ImmutableHashSet<string>? roles, SourceText text)
Document document, ConcatImmutableArray<CompletionProvider> providers, int caretPosition, CompletionOptions options, CompletionTrigger trigger, ImmutableHashSet<string>? roles, SourceText text)
{
switch (trigger.Kind)
{
case CompletionTriggerKind.Insertion:
case CompletionTriggerKind.Deletion:
var shouldTrigger = ShouldTypingTriggerCompletionWithoutAskingProviders(trigger, options);
if (shouldTrigger.HasValue && !shouldTrigger.Value)
return ImmutableArray<CompletionProvider>.Empty;

var triggeredProviders = allProviders.Where(p => p.ShouldTriggerCompletion(document.Project.LanguageServices, text, caretPosition, trigger, options)).ToImmutableArrayOrEmpty();
Debug.Assert(ValidatePossibleTriggerCharacterSet(trigger.Kind, triggeredProviders, document, text, caretPosition, options));
if (ShouldTriggerCompletion(document.Project, document.Project.LanguageServices, text, caretPosition, trigger, options, roles))
{
var triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(document.Project.LanguageServices, text, caretPosition, trigger, options)).ToImmutableArrayOrEmpty();

Debug.Assert(ValidatePossibleTriggerCharacterSet(trigger.Kind, triggeredProviders, document, text, caretPosition, options));
return triggeredProviders.IsEmpty ? providers.ToImmutableArray() : triggeredProviders;
}

return triggeredProviders.Length == 0
? allProviders.ToImmutableArray()
: triggeredProviders;
return ImmutableArray<CompletionProvider>.Empty;

default:
return allProviders.ToImmutableArray();
return providers.ToImmutableArray();
}
}

Expand Down Expand Up @@ -206,11 +208,8 @@ public sealed override bool ShouldTriggerCompletion(SourceText text, int caretPo
return ShouldTriggerCompletion(document?.Project, languageServices, text, caretPosition, trigger, completionOptions, roles);
}

/// <summary>
/// A preliminary quick check to see if we can decide whether to trigger completion without asking each individual providers.
/// Returning null means the decision needs to be made by providers.
/// </summary>
private bool? ShouldTypingTriggerCompletionWithoutAskingProviders(CompletionTrigger trigger, CompletionOptions options)
internal sealed override bool ShouldTriggerCompletion(
Project? project, HostLanguageServices languageServices, SourceText text, int caretPosition, CompletionTrigger trigger, CompletionOptions options, ImmutableHashSet<string>? roles = null)
{
if (!options.TriggerOnTyping)
{
Expand All @@ -222,16 +221,6 @@ public sealed override bool ShouldTriggerCompletion(SourceText text, int caretPo
return char.IsLetterOrDigit(trigger.Character) || trigger.Character == '.';
}

return null;
}

internal sealed override bool ShouldTriggerCompletion(
Project? project, HostLanguageServices languageServices, SourceText text, int caretPosition, CompletionTrigger trigger, CompletionOptions options, ImmutableHashSet<string>? roles = null)
{
var shouldTrigger = ShouldTypingTriggerCompletionWithoutAskingProviders(trigger, options);
if (shouldTrigger.HasValue)
return shouldTrigger.Value;

var providers = _providerManager.GetFilteredProviders(project, roles, trigger, options);
return providers.Any(p => p.ShouldTriggerCompletion(languageServices, text, caretPosition, trigger, options));
}
Expand Down Expand Up @@ -278,7 +267,10 @@ private static bool ValidatePossibleTriggerCharacterSet(CompletionTriggerKind co
return true;
}

private static async Task<AggregatedCompletionContextsData> ComputeNonEmptyCompletionContextsAsync(
private static bool HasAnyItems(CompletionContext cc)
=> cc.Items.Count > 0 || cc.SuggestionModeItem != null;

private static async Task<ImmutableArray<CompletionContext>> ComputeNonEmptyCompletionContextsAsync(
Document document, int caretPosition, CompletionTrigger trigger,
CompletionOptions options, TextSpan defaultItemSpan,
ImmutableArray<CompletionProvider> providers,
Expand All @@ -293,7 +285,7 @@ private static async Task<AggregatedCompletionContextsData> ComputeNonEmptyCompl
}

var completionContexts = await Task.WhenAll(completionContextTasks).ConfigureAwait(false);
return new(completionContexts);
return completionContexts.Where(HasAnyItems).ToImmutableArray();
}

private CompletionList MergeAndPruneCompletionLists(
Expand Down Expand Up @@ -510,46 +502,6 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
}
}

private readonly struct AggregatedCompletionContextsData
{
public ImmutableArray<CompletionContext> NonEmptyContexts { get; }

public bool ContainsNonSuggestionModeItem { get; }
public bool ContainsExclusiveContext { get; }

public bool IsEmpty => NonEmptyContexts.Length == 0;

public AggregatedCompletionContextsData(CompletionContext[] allContexts)
{
var containsNonSuggestionModeItem = false;
var containsExclusiveContext = false;
var builder = ArrayBuilder<CompletionContext>.GetInstance(allContexts.Length);

foreach (var context in allContexts)
{
if (context.Items.Count > 0)
{
containsNonSuggestionModeItem = true;
builder.Add(context);
}
else if (context.SuggestionModeItem != null)
{
builder.Add(context);
}

if (context.IsExclusive)
containsExclusiveContext = true;
}

NonEmptyContexts = builder.ToImmutableAndFree();
ContainsNonSuggestionModeItem = containsNonSuggestionModeItem;
ContainsExclusiveContext = containsExclusiveContext;
}

public ImmutableArray<CompletionContext> GetExclusiveContexts()
=> ContainsExclusiveContext ? NonEmptyContexts.WhereAsArray(c => c.IsExclusive) : ImmutableArray<CompletionContext>.Empty;
}

internal TestAccessor GetTestAccessor()
=> new(this);

Expand Down

0 comments on commit a83e943

Please sign in to comment.