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

Consolidate data tracked over an async completion session #59553

Merged
merged 9 commits into from
Feb 17, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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 @@ -105,14 +105,14 @@ public AsyncCompletionData.CommitResult TryCommit(
return CommitResultUnhandled;
}

if (!item.Properties.TryGetProperty(CompletionSource.RoslynItem, out RoslynCompletionItem roslynItem))
if (!CompletionItemData.TryGetData(item, out var itemData))
{
// Roslyn should not be called if the item committing was not provided by Roslyn.
return CommitResultUnhandled;
}

var filterText = session.ApplicableToSpan.GetText(session.ApplicableToSpan.TextBuffer.CurrentSnapshot) + typeChar;
if (Helpers.IsFilterCharacter(roslynItem, typeChar, filterText))
if (Helpers.IsFilterCharacter(itemData.RoslynItem, typeChar, filterText))
{
// Returning Cancel means we keep the current session and consider the character for further filtering.
return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.CancelCommit);
Expand All @@ -124,34 +124,35 @@ public AsyncCompletionData.CommitResult TryCommit(
// We can be called before for ShouldCommitCompletion. However, that call does not provide rules applied for the completion item.
// Now we check for the commit character in the context of Rules that could change the list of commit characters.

if (!Helpers.IsStandardCommitCharacter(typeChar) && !IsCommitCharacter(serviceRules, roslynItem, typeChar))
if (!Helpers.IsStandardCommitCharacter(typeChar) && !IsCommitCharacter(serviceRules, itemData.RoslynItem, typeChar))
{
// Returning None means we complete the current session with a void commit.
// The Editor then will try to trigger a new completion session for the character.
return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None);
}

if (!item.Properties.TryGetProperty(CompletionSource.TriggerLocation, out SnapshotPoint triggerLocation))
if (!itemData.TriggerLocation.HasValue)
{
// Need the trigger snapshot to calculate the span when the commit changes to be applied.
// They should always be available from items provided by Roslyn CompletionSource.
// Just to be defensive, if it's not found here, Roslyn should not make a commit.
return CommitResultUnhandled;
}

if (!session.Properties.TryGetProperty(CompletionSource.CompletionListSpan, out TextSpan completionListSpan))
var triggerDocument = itemData.TriggerLocation.Value.Snapshot.GetOpenDocumentInCurrentContextWithChanges();
if (triggerDocument == null)
{
return CommitResultUnhandled;
}

var triggerDocument = triggerLocation.Snapshot.GetOpenDocumentInCurrentContextWithChanges();
if (triggerDocument == null)
var sessionData = CompletionSessionData.GetOrCreateSessionData(session);
if (!sessionData.CompletionListSpan.HasValue)
{
return CommitResultUnhandled;
}

// Telemetry
if (session.TextView.Properties.TryGetProperty(CompletionSource.TargetTypeFilterExperimentEnabled, out bool isExperimentEnabled) && isExperimentEnabled)
if (sessionData.TargetTypeFilterExperimentEnabled)
{
// Capture the % of committed completion items that would have appeared in the "Target type matches" filter
// (regardless of whether that filter button was active at the time of commit).
Expand All @@ -166,7 +167,7 @@ public AsyncCompletionData.CommitResult TryCommit(
var commitChar = typeChar == '\0' ? null : (char?)typeChar;
return Commit(
session, triggerDocument, completionService, subjectBuffer,
roslynItem, completionListSpan, commitChar, triggerLocation.Snapshot, serviceRules,
itemData.RoslynItem, sessionData.CompletionListSpan.Value, commitChar, itemData.TriggerLocation.Value.Snapshot, serviceRules,
filterText, cancellationToken);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using Microsoft.VisualStudio.Text;
using RoslynCompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem;

namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion
{
internal sealed record class CompletionItemData(RoslynCompletionItem RoslynItem, SnapshotPoint? TriggerLocation)
{
private const string RoslynCompletionItemData = nameof(RoslynCompletionItemData);

public bool IsProvidedByRoslynCompletionSource => TriggerLocation.HasValue;

public static bool TryGetData(CompletionItem vsCompletionitem, out CompletionItemData data)
=> vsCompletionitem.Properties.TryGetProperty(RoslynCompletionItemData, out data);

public static RoslynCompletionItem GetOrAddDummyRoslynItem(CompletionItem vsItem)
{
if (TryGetData(vsItem, out var data))
return data.RoslynItem;

var roslynItem = CreateDummyRoslynItem(vsItem);
AddData(vsItem, roslynItem, triggerLocation: null);

return roslynItem;
}

public static void AddData(CompletionItem vsCompletionitem, RoslynCompletionItem roslynItem, SnapshotPoint? triggerLocation)
=> vsCompletionitem.Properties[RoslynCompletionItemData] = new CompletionItemData(roslynItem, triggerLocation);

private static RoslynCompletionItem CreateDummyRoslynItem(CompletionItem vsItem)
=> RoslynCompletionItem.Create(
displayText: vsItem.DisplayText,
filterText: vsItem.FilterText,
sortText: vsItem.SortText,
displayTextSuffix: vsItem.Suffix);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using Microsoft.VisualStudio.Text;
using RoslynCompletionList = Microsoft.CodeAnalysis.Completion.CompletionList;

namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion
{
/// <summary>
/// Contains data need to be tracked over an entire IAsyncCompletionSession completion
/// session for various operations.
/// </summary>
internal sealed class CompletionSessionData
{
private const string RoslynCompletionSessionData = nameof(RoslynCompletionSessionData);
public bool TargetTypeFilterExperimentEnabled { get; set; }
public bool TargetTypeFilterSelected { get; set; }
public bool HasSuggestionItemOptions { get; set; }

public SnapshotPoint? ExpandedItemTriggerLocation { get; set; }
public TextSpan? CompletionListSpan { get; set; }
public ImmutableArray<CompletionItem>? CombinedSortedList { get; set; }
public Task<(CompletionContext, RoslynCompletionList)>? ExpandedItemsTask { get; set; }

private CompletionSessionData()
{
}

public static CompletionSessionData GetOrCreateSessionData(IAsyncCompletionSession session)
=> session.Properties.GetOrCreateSingletonProperty(RoslynCompletionSessionData, static () => new CompletionSessionData());
}
}
Loading