Skip to content

Commit

Permalink
Fix
Browse files Browse the repository at this point in the history
  • Loading branch information
genlu committed Jan 31, 2022
1 parent f06e2ab commit 217ae3b
Show file tree
Hide file tree
Showing 10 changed files with 50 additions and 81 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 @@ -188,6 +188,6 @@ public ImmutableArray<CompletionFilterWithState> GetFilterStatesInSet()
return builder.ToImmutableAndFree();
}

private readonly record struct FilterWithMask (CompletionFilter Filter, int Mask);
private readonly record struct FilterWithMask(CompletionFilter Filter, int Mask);
}
}
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 217ae3b

Please sign in to comment.