Skip to content

Commit

Permalink
Keep track of trigger location for individual CompletionItem
Browse files Browse the repository at this point in the history
Fix committing completion item and expander in projection scenarios
  • Loading branch information
genlu committed Jul 23, 2021
1 parent ebbae5b commit fff91e2
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,11 @@ public AsyncCompletionData.CommitResult TryCommit(
return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None);
}

if (!Helpers.TryGetInitialTriggerLocation(session, out var triggerLocation))
if (!item.Properties.TryGetProperty(CompletionSource.TriggerLocation, out SnapshotPoint triggerLocation))
{
// Need the trigger snapshot to calculate the span when the commit changes to be applied.
// They should always be available from VS. Just to be defensive, if it's not found here, Roslyn should not make a commit.
// 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal sealed class CompletionSource : ForegroundThreadAffinitizedObject, IAsy
{
internal const string RoslynItem = nameof(RoslynItem);
internal const string TriggerLocation = nameof(TriggerLocation);
internal const string ExpandedItemTriggerLocation = nameof(ExpandedItemTriggerLocation);
internal const string CompletionListSpan = nameof(CompletionListSpan);
internal const string InsertionText = nameof(InsertionText);
internal const string HasSuggestionItemOptions = nameof(HasSuggestionItemOptions);
Expand Down Expand Up @@ -219,7 +220,6 @@ private bool TryInvokeSnippetCompletion(
if (session is null)
throw new ArgumentNullException(nameof(session));

session.Properties[TriggerLocation] = triggerLocation;
return GetCompletionContextWorkerAsync(session, trigger, triggerLocation, isExpanded: false, cancellationToken);
}

Expand All @@ -231,12 +231,9 @@ private bool TryInvokeSnippetCompletion(
CancellationToken cancellationToken)
{
// We only want to provide expanded items for Roslyn's expander.
if ((object)expander == FilterSet.Expander)
if ((object)expander == FilterSet.Expander && session.Properties.TryGetProperty(ExpandedItemTriggerLocation, out SnapshotPoint initialTriggerLocation))
{
if (Helpers.TryGetInitialTriggerLocation(session, out var initialTriggerLocation))
{
return await GetCompletionContextWorkerAsync(session, intialTrigger, initialTriggerLocation, isExpanded: true, cancellationToken).ConfigureAwait(false);
}
return await GetCompletionContextWorkerAsync(session, intialTrigger, initialTriggerLocation, isExpanded: true, cancellationToken).ConfigureAwait(false);
}

return AsyncCompletionData.CompletionContext.Empty;
Expand Down Expand Up @@ -297,7 +294,7 @@ private bool TryInvokeSnippetCompletion(
foreach (var roslynItem in completionList.Items)
{
cancellationToken.ThrowIfCancellationRequested();
var item = Convert(document, roslynItem, filterSet);
var item = Convert(document, roslynItem, filterSet, triggerLocation);
itemsBuilder.Add(item);
}

Expand Down Expand Up @@ -336,6 +333,16 @@ private bool TryInvokeSnippetCompletion(
}
}

// We need to remember the trigger location for when a completion service claims expanded items are available
// since the intial trigger we are able to get from IAsyncCompletionSession might not be the same (e.g. in projection scenarios,)
// so when they are requested via expander later, we can retrieve it,.
// Techinically we should save the trigger location for each individual service that made such claim, but in reality , only Roslyn's completion service uses
// expander, so we can get away with not making such distinction.
if (!isExpanded && expandItemsAvailable)
{
session.Properties[ExpandedItemTriggerLocation] = triggerLocation;
}

// It's possible that some providers can provide expanded items, in which case we will need to show expander as unselected.
return new AsyncCompletionData.CompletionContext(
items,
Expand All @@ -354,7 +361,7 @@ private bool TryInvokeSnippetCompletion(
throw new ArgumentNullException(nameof(item));

if (!item.Properties.TryGetProperty(RoslynItem, out RoslynCompletionItem roslynItem) ||
!Helpers.TryGetInitialTriggerLocation(session, out var triggerLocation))
!item.Properties.TryGetProperty(TriggerLocation, out SnapshotPoint triggerLocation))
{
return null;
}
Expand Down Expand Up @@ -427,7 +434,8 @@ public VSCompletionItemData(
private VSCompletionItem Convert(
Document document,
RoslynCompletionItem roslynItem,
FilterSet filterSet)
FilterSet filterSet,
SnapshotPoint initialTriggerLocation)
{
VSCompletionItemData itemData;

Expand Down Expand Up @@ -481,6 +489,7 @@ private VSCompletionItem Convert(
attributeIcons: itemData.AttributeIcons);

item.Properties.AddProperty(RoslynItem, roslynItem);
item.Properties.AddProperty(TriggerLocation, initialTriggerLocation);

return item;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.VisualStudio.Text;
using EditorAsyncCompletion = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
Expand Down Expand Up @@ -141,9 +142,6 @@ internal static bool TextTypedSoFarMatchesItem(RoslynCompletionItem item, string
internal static bool IsStandardCommitCharacter(char c)
=> c == '\t' || c == '\n' || c == '\0';

internal static bool TryGetInitialTriggerLocation(EditorAsyncCompletion.IAsyncCompletionSession session, out SnapshotPoint initialTriggerLocation)
=> session.Properties.TryGetProperty(CompletionSource.TriggerLocation, out initialTriggerLocation);

// This is a temporarily method to support preference of IntelliCode items comparing to non-IntelliCode items.
// We expect that Editor will introduce this support and we will get rid of relying on the "★" then.
// We check both the display text and the display text prefix to account for IntelliCode item providers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,14 @@ private static readonly ObjectPool<List<MatchResult>> s_listOfMatchResultPool

var filterReason = Helpers.GetFilterReason(data.Trigger);

// If the session was created/maintained out of Roslyn, e.g. in debugger; no properties are set and we should use data.Snapshot.
// However, we prefer using the original snapshot in some projection scenarios.
var snapshotForDocument = Helpers.TryGetInitialTriggerLocation(session, out var triggerLocation)
? triggerLocation.Snapshot
: data.Snapshot;

var document = snapshotForDocument.TextBuffer.AsTextContainer().GetOpenDocumentInCurrentContext();
// We prefer using the original snapshot, which should always be available from items provided by Roslyn CompletionSource.
// Just to be defensive, if it's not found then use data.Snapshot instead.
var snapshotForDocument = data.InitialSortedList.FirstOrDefault(item => item.Properties.ContainsProperty(CompletionSource.TriggerLocation)) is VSCompletionItem firstItem &&
firstItem.Properties.GetProperty(CompletionSource.TriggerLocation) is SnapshotPoint triggerLocation
? triggerLocation.Snapshot
: data.Snapshot;

var document = snapshotForDocument?.TextBuffer.AsTextContainer().GetOpenDocumentInCurrentContext();
var completionService = document?.GetLanguageService<CompletionService>();
var completionRules = completionService?.GetRules() ?? CompletionRules.Default;
var completionHelper = document != null ? CompletionHelper.GetHelper(document) : _defaultCompletionHelper;
Expand Down

0 comments on commit fff91e2

Please sign in to comment.