diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CommitManager.cs
index 192290e3d57bf..1059605bec8ed 100644
--- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CommitManager.cs
+++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CommitManager.cs
@@ -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);
@@ -124,14 +124,14 @@ 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.
@@ -139,19 +139,20 @@ public AsyncCompletionData.CommitResult TryCommit(
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).
@@ -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);
}
diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionItemData.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionItemData.cs
new file mode 100644
index 0000000000000..cba26a8302f54
--- /dev/null
+++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionItemData.cs
@@ -0,0 +1,40 @@
+// 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 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;
+
+ // TriggerLocation is null for items provided by non-roslyn completion source
+ 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);
+ }
+}
diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSessionData.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSessionData.cs
new file mode 100644
index 0000000000000..98729d3730fe5
--- /dev/null
+++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSessionData.cs
@@ -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
+{
+ ///
+ /// Contains data need to be tracked over an entire IAsyncCompletionSession completion
+ /// session for various operations.
+ ///
+ 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? 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());
+ }
+}
diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs
index 9244dd9679f20..5603b0d71f2be 100644
--- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs
+++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs
@@ -10,9 +10,9 @@
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
-using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
@@ -29,6 +29,7 @@
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Text.Editor;
+using Roslyn.Utilities;
using AsyncCompletionData = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using RoslynCompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem;
using VSCompletionContext = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionContext;
@@ -39,18 +40,11 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncComplet
{
internal sealed class CompletionSource : ForegroundThreadAffinitizedObject, IAsyncExpandingCompletionSource
{
- 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);
- internal const string Description = nameof(Description);
internal const string PotentialCommitCharacters = nameof(PotentialCommitCharacters);
- internal const string ExcludedCommitCharacters = nameof(ExcludedCommitCharacters);
internal const string NonBlockingCompletion = nameof(NonBlockingCompletion);
- internal const string TargetTypeFilterExperimentEnabled = nameof(TargetTypeFilterExperimentEnabled);
- internal const string ExpandedItemsTask = nameof(ExpandedItemsTask);
+
+ // Don't change this property! Editor code currently has a dependency on it.
+ internal const string ExcludedCommitCharacters = nameof(ExcludedCommitCharacters);
private static readonly ImmutableArray s_warningImageAttributeImagesArray =
ImmutableArray.Create(new ImageElement(Glyph.CompletionWarning.GetImageId(), EditorFeaturesResources.Warning_image_element));
@@ -133,9 +127,6 @@ public AsyncCompletionData.CompletionStartData InitializeCompletion(
// Set it later if met the condition.
_snippetCompletionTriggeredIndirectly = false;
- // For telemetry reporting purpose
- _textView.Properties[TargetTypeFilterExperimentEnabled] = options.TargetTypedCompletionFilter;
-
var sourceText = document.GetTextSynchronously(cancellationToken);
return ShouldTriggerCompletion(trigger, triggerLocation, sourceText, document, service, options)
@@ -264,6 +255,10 @@ public async Task GetCompletionContextAsync(
// to still provide those items later before they are truly required.
var options = _globalOptions.GetCompletionOptions(document.Project.Language);
+ var sessionData = CompletionSessionData.GetOrCreateSessionData(session);
+
+ // For telemetry reporting purpose
+ sessionData.TargetTypeFilterExperimentEnabled = options.TargetTypedCompletionFilter;
if (!options.ShouldShowItemsFromUnimportNamspaces())
{
@@ -271,7 +266,7 @@ public async Task GetCompletionContextAsync(
var (context, list) = await GetCompletionContextWorkerAsync(document, trigger, triggerLocation,
options with { ExpandedCompletionBehavior = ExpandedCompletionMode.NonExpandedItemsOnly }, cancellationToken).ConfigureAwait(false);
- AddPropertiesToSession(session, list, triggerLocation);
+ UpdateSessionData(session, sessionData, list, triggerLocation);
return context;
}
else if (!_responsiveCompletionEnabled)
@@ -281,7 +276,7 @@ public async Task GetCompletionContextAsync(
var (context, list) = await GetCompletionContextWorkerAsync(document, trigger, triggerLocation,
options with { ExpandedCompletionBehavior = ExpandedCompletionMode.AllItems }, cancellationToken).ConfigureAwait(false);
- AddPropertiesToSession(session, list, triggerLocation);
+ UpdateSessionData(session, sessionData, list, triggerLocation);
AsyncCompletionLogger.LogImportCompletionGetContext(isBlocking: true, delayed: false);
return context;
}
@@ -309,13 +304,13 @@ public async Task GetCompletionContextAsync(
// Now trigger and wait for core providers to return;
var (nonExpandedContext, nonExpandedCompletionList) = await GetCompletionContextWorkerAsync(document, trigger, triggerLocation,
options with { ExpandedCompletionBehavior = ExpandedCompletionMode.NonExpandedItemsOnly }, cancellationToken).ConfigureAwait(false);
- AddPropertiesToSession(session, nonExpandedCompletionList, triggerLocation);
+ UpdateSessionData(session, sessionData, nonExpandedCompletionList, triggerLocation);
if (expandedItemsTask.IsCompleted)
{
// the task of expanded item is completed, get the result and combine it with result of non-expanded items.
var (expandedContext, expandedCompletionList) = await expandedItemsTask.ConfigureAwait(false);
- AddPropertiesToSession(session, expandedCompletionList, triggerLocation);
+ UpdateSessionData(session, sessionData, expandedCompletionList, triggerLocation);
AsyncCompletionLogger.LogImportCompletionGetContext(isBlocking: false, delayed: false);
return CombineCompletionContext(nonExpandedContext, expandedContext);
@@ -327,7 +322,7 @@ public async Task GetCompletionContextAsync(
// after core providers completed (instead of how long it takes end-to-end).
stopwatch.Start();
- session.Properties[ExpandedItemsTask] = expandedItemsTask;
+ sessionData.ExpandedItemsTask = expandedItemsTask;
AsyncCompletionLogger.LogImportCompletionGetContext(isBlocking: false, delayed: true);
return nonExpandedContext;
@@ -362,20 +357,25 @@ public async Task GetExpandedCompletionContextAsync(
SnapshotSpan applicableToSpan,
CancellationToken cancellationToken)
{
+ var sessionData = CompletionSessionData.GetOrCreateSessionData(session);
+
// We only want to provide expanded items for Roslyn's expander.
- if (expander == FilterSet.Expander && session.Properties.TryGetProperty(ExpandedItemTriggerLocation, out SnapshotPoint initialTriggerLocation))
+ if (expander == FilterSet.Expander && sessionData.ExpandedItemTriggerLocation.HasValue)
{
+ var initialTriggerLocation = sessionData.ExpandedItemTriggerLocation.Value;
AsyncCompletionLogger.LogExpanderUsage();
// It's possible we didn't provide expanded items at the beginning of completion session because it was slow even if the feature is enabled.
// ExpandedItemsTask would be available in this case, so we just need to return its result.
- if (session.Properties.TryGetProperty(ExpandedItemsTask, out Task<(VSCompletionContext, CompletionList)> expandedItemsTask))
+ if (sessionData.ExpandedItemsTask != null)
{
// Make sure the task is removed when returning expanded items,
// so duplicated items won't be added in subsequent list updates.
- session.Properties.RemoveProperty(ExpandedItemsTask);
- var (expandedContext, expandedCompletionList) = await expandedItemsTask.ConfigureAwait(false);
- AddPropertiesToSession(session, expandedCompletionList, initialTriggerLocation);
+ var task = sessionData.ExpandedItemsTask;
+ sessionData.ExpandedItemsTask = null;
+
+ var (expandedContext, expandedCompletionList) = await task.ConfigureAwait(false);
+ UpdateSessionData(session, sessionData, expandedCompletionList, initialTriggerLocation);
return expandedContext;
}
@@ -393,7 +393,7 @@ public async Task GetExpandedCompletionContextAsync(
};
var (context, completionList) = await GetCompletionContextWorkerAsync(document, intialTrigger, initialTriggerLocation, options, cancellationToken).ConfigureAwait(false);
- AddPropertiesToSession(session, completionList, initialTriggerLocation);
+ UpdateSessionData(session, sessionData, completionList, initialTriggerLocation);
return context;
}
@@ -444,25 +444,22 @@ public async Task GetExpandedCompletionContextAsync(
var suggestionItemOptions = new AsyncCompletionData.SuggestionItemOptions(
completionList.SuggestionModeItem.DisplayText,
- completionList.SuggestionModeItem.Properties.TryGetValue(Description, out var description) ? description : string.Empty);
+ completionList.SuggestionModeItem.Properties.TryGetValue(CommonCompletionItem.DescriptionProperty, out var description) ? description : string.Empty);
return (new(items, suggestionItemOptions, selectionHint: AsyncCompletionData.InitialSelectionHint.SoftSelection, filters), completionList);
}
- private static void AddPropertiesToSession(IAsyncCompletionSession session, CompletionList completionList, SnapshotPoint triggerLocation)
+ private static void UpdateSessionData(IAsyncCompletionSession session, CompletionSessionData sessionData, CompletionList completionList, SnapshotPoint triggerLocation)
{
// Store around the span this completion list applies to. We'll use this later
// to pass this value in when we're committing a completion list item.
// It's OK to overwrite this value when expanded items are requested.
- session.Properties[CompletionListSpan] = completionList.Span;
+ sessionData.CompletionListSpan = completionList.Span;
// This is a code supporting original completion scenarios:
// Controller.Session_ComputeModel: if completionList.SuggestionModeItem != null, then suggestionMode = true
// If there are suggestionItemOptions, then later HandleNormalFiltering should set selection to SoftSelection.
- if (!session.Properties.TryGetProperty(HasSuggestionItemOptions, out bool hasSuggestionItemOptionsBefore) || !hasSuggestionItemOptionsBefore)
- {
- session.Properties[HasSuggestionItemOptions] = completionList.SuggestionModeItem != null;
- }
+ sessionData.HasSuggestionItemOptions |= completionList.SuggestionModeItem != null;
var excludedCommitCharacters = GetExcludedCommitCharacters(completionList.Items);
if (excludedCommitCharacters.Length > 0)
@@ -480,9 +477,9 @@ private static void AddPropertiesToSession(IAsyncCompletionSession session, Comp
// so when they are requested via expander later, we can retrieve it.
// Technically 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 (!session.Properties.ContainsProperty(ExpandedItemTriggerLocation))
+ if (!sessionData.ExpandedItemTriggerLocation.HasValue)
{
- session.Properties[ExpandedItemTriggerLocation] = triggerLocation;
+ sessionData.ExpandedItemTriggerLocation = triggerLocation;
}
}
@@ -493,13 +490,10 @@ private static void AddPropertiesToSession(IAsyncCompletionSession session, Comp
if (item is null)
throw new ArgumentNullException(nameof(item));
- if (!item.Properties.TryGetProperty(RoslynItem, out RoslynCompletionItem roslynItem) ||
- !item.Properties.TryGetProperty(TriggerLocation, out SnapshotPoint triggerLocation))
- {
+ if (!CompletionItemData.TryGetData(item, out var itemData) || !itemData.TriggerLocation.HasValue)
return null;
- }
- var document = triggerLocation.Snapshot.GetOpenDocumentInCurrentContextWithChanges();
+ var document = itemData.TriggerLocation.Value.Snapshot.GetOpenDocumentInCurrentContextWithChanges();
if (document == null)
return null;
@@ -509,7 +503,7 @@ private static void AddPropertiesToSession(IAsyncCompletionSession session, Comp
var completionOptions = _globalOptions.GetCompletionOptions(document.Project.Language);
var displayOptions = _globalOptions.GetSymbolDescriptionOptions(document.Project.Language);
- var description = await service.GetDescriptionAsync(document, roslynItem, completionOptions, displayOptions, cancellationToken).ConfigureAwait(false);
+ var description = await service.GetDescriptionAsync(document, itemData.RoslynItem, completionOptions, displayOptions, cancellationToken).ConfigureAwait(false);
if (description == null)
return null;
@@ -564,7 +558,7 @@ private VSCompletionItem Convert(
// roslynItem generated by providers can contain an insertionText in a property bag.
// We will not use it but other providers may need it.
// We actually will calculate the insertion text once again when called TryCommit.
- if (!roslynItem.Properties.TryGetValue(InsertionText, out var insertionText))
+ if (!roslynItem.Properties.TryGetValue(CommonCompletionItem.InsertionTextProperty, out var insertionText))
{
insertionText = roslynItem.DisplayText;
}
@@ -600,9 +594,7 @@ private VSCompletionItem Convert(
automationText: roslynItem.AutomationText ?? roslynItem.DisplayText,
attributeIcons: itemData.AttributeIcons);
- item.Properties.AddProperty(RoslynItem, roslynItem);
- item.Properties.AddProperty(TriggerLocation, initialTriggerLocation);
-
+ CompletionItemData.AddData(item, roslynItem, initialTriggerLocation);
return item;
}
diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.CompletionListUpdater.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.CompletionListUpdater.cs
index 030692e4ad810..5bcbf0c447000 100644
--- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.CompletionListUpdater.cs
+++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.CompletionListUpdater.cs
@@ -33,10 +33,11 @@ internal partial class ItemManager
///
private sealed class CompletionListUpdater
{
- private readonly IAsyncCompletionSession _session;
- private readonly AsyncCompletionSessionDataSnapshot _data;
+ private readonly CompletionSessionData _sessionData;
+ private readonly AsyncCompletionSessionDataSnapshot _snapshotData;
private readonly RecentItemsManager _recentItemsManager;
+ private readonly ITrackingSpan _applicableToSpan;
private readonly bool _hasSuggestedItemOptions;
private readonly string _filterText;
private readonly Document? _document;
@@ -48,8 +49,8 @@ private sealed class CompletionListUpdater
private readonly Func, string, ImmutableArray> _filterMethod;
- private CompletionTriggerReason InitialTriggerReason => _data.InitialTrigger.Reason;
- private CompletionTriggerReason UpdateTriggerReason => _data.Trigger.Reason;
+ private CompletionTriggerReason InitialTriggerReason => _snapshotData.InitialTrigger.Reason;
+ private CompletionTriggerReason UpdateTriggerReason => _snapshotData.Trigger.Reason;
// We might need to handle large amount of items with import completion enabled,
// so use a dedicated pool to minimize/avoid array allocations (especially in LOH)
@@ -59,31 +60,26 @@ private sealed class CompletionListUpdater
private static readonly ObjectPool>> s_listOfMatchResultPool = new(factory: () => new(), size: 1);
public CompletionListUpdater(
- IAsyncCompletionSession session,
- AsyncCompletionSessionDataSnapshot data,
+ ITrackingSpan applicableToSpan,
+ CompletionSessionData sessionData,
+ AsyncCompletionSessionDataSnapshot snapshotData,
RecentItemsManager recentItemsManager,
IGlobalOptionService globalOptions)
{
- _session = session;
- _data = data;
+ _sessionData = sessionData;
+ _snapshotData = snapshotData;
_recentItemsManager = recentItemsManager;
- _filterText = _session.ApplicableToSpan.GetText(_data.Snapshot);
+ _applicableToSpan = applicableToSpan;
+ _filterText = applicableToSpan.GetText(_snapshotData.Snapshot);
- if (!_session.Properties.TryGetProperty(CompletionSource.HasSuggestionItemOptions, out bool hasSuggestedItemOptions))
- {
- // This is the scenario when the session is created out of Roslyn, in some other provider, e.g. in Debugger.
- // For now, the default hasSuggestedItemOptions is false.
- hasSuggestedItemOptions = false;
- }
-
- _hasSuggestedItemOptions = hasSuggestedItemOptions || _data.DisplaySuggestionItem;
+ _hasSuggestedItemOptions = _sessionData.HasSuggestionItemOptions || _snapshotData.DisplaySuggestionItem;
// We prefer using the original snapshot, which should always be available from items provided by Roslyn's CompletionSource.
// Only use data.Snapshot in the theoretically possible but rare case when all items we are handling are from some non-Roslyn CompletionSource.
- var snapshotForDocument = TryGetInitialTriggerLocation(_data, out var intialTriggerLocation)
+ var snapshotForDocument = TryGetInitialTriggerLocation(_snapshotData, out var intialTriggerLocation)
? intialTriggerLocation.Snapshot
- : _data.Snapshot;
+ : _snapshotData.Snapshot;
_document = snapshotForDocument?.TextBuffer.AsTextContainer().GetOpenDocumentInCurrentContext();
if (_document != null)
@@ -184,7 +180,7 @@ private bool ShouldDismissCompletionListImmediately()
//
// We'll bring up the completion list here (as VB has completion on ).
// If the user then types '3', we don't want to match against Int32.
- if (_filterText.Length > 0 && char.IsNumber(_filterText[0]) && !IsAfterDot(_data.Snapshot, _session.ApplicableToSpan))
+ if (_filterText.Length > 0 && char.IsNumber(_filterText[0]) && !IsAfterDot(_snapshotData.Snapshot, _applicableToSpan))
{
// Dismiss the session.
return true;
@@ -213,8 +209,8 @@ static bool IsAfterDot(ITextSnapshot snapshot, ITrackingSpan applicableToSpan)
private void AddCompletionItems(List> list, CancellationToken cancellationToken)
{
// FilterStateHelper is used to decide whether a given item should be included in the list based on the state of filter/expander buttons.
- var filterHelper = new FilterStateHelper(_data.SelectedFilters);
- filterHelper.LogTargetTypeFilterTelemetry(_session);
+ var filterHelper = new FilterStateHelper(_snapshotData.SelectedFilters);
+ filterHelper.LogTargetTypeFilterTelemetry(_sessionData);
// We want to sort the items by pattern matching results while preserving the original alphabetical order for items with
// same pattern match score, but `List.Sort` isn't stable. Therefore we have to add a monotonically increasing integer
@@ -227,20 +223,28 @@ private void AddCompletionItems(List> list, Cancel
var roslynFilterReason = Helpers.GetFilterReason(UpdateTriggerReason);
// Filter items based on the selected filters and matching.
- foreach (var item in _data.InitialSortedList)
+ foreach (var item in _snapshotData.InitialSortedList)
{
cancellationToken.ThrowIfCancellationRequested();
if (filterHelper.ShouldBeFilteredOut(item))
continue;
- var roslynItem = GetOrAddRoslynCompletionItem(item);
- if (CompletionHelper.TryCreateMatchResult(_completionHelper, roslynItem, item, _filterText,
+ if (CompletionItemData.TryGetData(item, out var itemData))
+ {
+ if (CompletionHelper.TryCreateMatchResult(_completionHelper, itemData.RoslynItem, item, _filterText,
roslynInitialTriggerKind, roslynFilterReason, _recentItemsManager.RecentItems, _highlightMatchingPortions, currentIndex,
out var matchResult))
+ {
+ list.Add(matchResult);
+ currentIndex++;
+ }
+ }
+ else
{
- list.Add(matchResult);
- currentIndex++;
+ // All items passed in should contain a CompletionItemData object in the property bag,
+ // which is guaranteed in `ItemManager.SortCompletionListAsync`.
+ throw ExceptionUtilities.Unreachable;
}
}
@@ -314,7 +318,7 @@ private void AddCompletionItems(List> list, Cancel
}
}
- var typedChar = _data.Trigger.Character;
+ var typedChar = _snapshotData.Trigger.Character;
// Check that it is a filter symbol. We can be called for a non-filter symbol.
// If inserting a non-filter character (neither IsPotentialFilterCharacter, nor Helpers.IsFilterCharacter),
@@ -475,7 +479,7 @@ static Span GetOffsetSpan(TextSpan span, RoslynCompletionItem item)
// selection.
return new FilteredCompletionModel(
items: ImmutableArray.Empty, selectedItemIndex: 0,
- filters: _data.SelectedFilters, selectionHint: UpdateSelectionHint.SoftSelected, centerSelection: true, uniqueItem: null);
+ filters: _snapshotData.SelectedFilters, selectionHint: UpdateSelectionHint.SoftSelected, centerSelection: true, uniqueItem: null);
}
private ImmutableArray GetUpdatedFilters(IReadOnlyList> items, CancellationToken cancellationToken)
@@ -493,7 +497,7 @@ private ImmutableArray GetUpdatedFilters(IReadOnlyLis
// When no items are available for a given filter, it becomes unavailable.
// Expanders always appear available as long as it's presented.
- return _data.SelectedFilters.SelectAsArray(n => n.WithAvailability(n.Filter is CompletionExpander || filters.Contains(n.Filter)));
+ return _snapshotData.SelectedFilters.SelectAsArray(n => n.WithAvailability(n.Filter is CompletionExpander || filters.Contains(n.Filter)));
}
///
@@ -553,10 +557,13 @@ static int GetRecentItemIndex(ImmutableArray recentItems, RoslynCompleti
private static bool TryGetInitialTriggerLocation(AsyncCompletionSessionDataSnapshot data, out SnapshotPoint intialTriggerLocation)
{
- var firstItem = data.InitialSortedList.FirstOrDefault(static item => item.Properties.ContainsProperty(CompletionSource.TriggerLocation));
- if (firstItem != null)
+ foreach (var item in data.InitialSortedList)
{
- return firstItem.Properties.TryGetProperty(CompletionSource.TriggerLocation, out intialTriggerLocation);
+ if (CompletionItemData.TryGetData(item, out var itemData) && itemData.TriggerLocation.HasValue)
+ {
+ intialTriggerLocation = itemData.TriggerLocation.Value;
+ return true;
+ }
}
intialTriggerLocation = default;
@@ -656,7 +663,7 @@ private static bool IsPotentialFilterCharacter(char c)
private ItemSelection UpdateSelectionBasedOnSuggestedDefaults(IReadOnlyList> items, ItemSelection itemSelection, CancellationToken cancellationToken)
{
// Editor doesn't provide us a list of "default" items.
- if (_data.Defaults.IsDefaultOrEmpty)
+ if (_snapshotData.Defaults.IsDefaultOrEmpty)
return itemSelection;
// "Preselect" is only used when we have high confidence with the selection, so don't override it.
@@ -713,7 +720,7 @@ private ItemSelection GetDefaultsMatch(IReadOnlyList filtersWithState)
{
// The filter state list contains two kinds of "filters": regular filter and expander.
@@ -796,19 +800,19 @@ private bool ShouldBeFilteredOutOfExpandedCompletionList(VSCompletionItem item)
return associatedWithUnselectedExpander;
}
- public void LogTargetTypeFilterTelemetry(IAsyncCompletionSession session)
+ public void LogTargetTypeFilterTelemetry(CompletionSessionData sessionData)
{
- if (session.TextView.Properties.TryGetProperty(CompletionSource.TargetTypeFilterExperimentEnabled, out bool isExperimentEnabled) && isExperimentEnabled)
+ if (sessionData.TargetTypeFilterExperimentEnabled)
{
// Telemetry: Want to know % of sessions with the "Target type matches" filter where that filter is actually enabled
if (_needToFilter &&
- !session.Properties.ContainsProperty(_targetTypeCompletionFilterChosenMarker) &&
+ !sessionData.TargetTypeFilterSelected &&
_selectedNonExpanderFilters.Any(f => f.DisplayText == FeaturesResources.Target_type_matches))
{
AsyncCompletionLogger.LogTargetTypeFilterChosenInSession();
// Make sure we only record one enabling of the filter per session
- session.Properties.AddProperty(_targetTypeCompletionFilterChosenMarker, _targetTypeCompletionFilterChosenMarker);
+ sessionData.TargetTypeFilterSelected = true;
}
}
}
diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs
index 0bc14462de196..308c4b21fa1f8 100644
--- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs
+++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs
@@ -7,12 +7,9 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
-using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using Roslyn.Utilities;
-using RoslynCompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem;
-using RoslynCompletionList = Microsoft.CodeAnalysis.Completion.CompletionList;
using VSCompletionItem = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionItem;
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion
@@ -20,7 +17,6 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncComplet
internal sealed partial class ItemManager : IAsyncCompletionItemManager
{
public const string AggressiveDefaultsMatchingOptionName = "AggressiveDefaultsMatchingOption";
- private const string CombinedSortedList = nameof(CombinedSortedList);
private readonly RecentItemsManager _recentItemsManager;
private readonly IGlobalOptionService _globalOptions;
@@ -36,8 +32,9 @@ public Task> SortCompletionListAsync(
AsyncCompletionSessionInitialDataSnapshot data,
CancellationToken cancellationToken)
{
+ var sessionData = CompletionSessionData.GetOrCreateSessionData(session);
// This method is called exactly once, so use the opportunity to set a baseline for telemetry.
- if (session.TextView.Properties.TryGetProperty(CompletionSource.TargetTypeFilterExperimentEnabled, out bool isTargetTypeFilterEnabled) && isTargetTypeFilterEnabled)
+ if (sessionData.TargetTypeFilterExperimentEnabled)
{
AsyncCompletionLogger.LogSessionHasTargetTypeFilterEnabled();
if (data.InitialList.Any(i => i.Filters.Any(f => f.DisplayText == FeaturesResources.Target_type_matches)))
@@ -45,7 +42,7 @@ public Task> SortCompletionListAsync(
}
// Sort by default comparer of Roslyn CompletionItem
- var sortedItems = data.InitialList.OrderBy(GetOrAddRoslynCompletionItem).ToImmutableArray();
+ var sortedItems = data.InitialList.OrderBy(CompletionItemData.GetOrAddDummyRoslynItem).ToImmutableArray();
return Task.FromResult(sortedItems);
}
@@ -54,6 +51,8 @@ public Task> SortCompletionListAsync(
AsyncCompletionSessionDataSnapshot data,
CancellationToken cancellationToken)
{
+ var sessionData = CompletionSessionData.GetOrCreateSessionData(session);
+
// As explained in more details in the comments for `CompletionSource.GetCompletionContextAsync`, expanded items might
// not be provided upon initial trigger of completion to reduce typing delays, even if they are supposed to be included by default.
// While we do not expect to run in to this scenario very often, we'd still want to minimize the impact on user experience of this feature
@@ -64,57 +63,44 @@ public Task> SortCompletionListAsync(
// There is a `CompletionContext.IsIncomplete` flag, which is only supported in LSP mode at the moment. Therefore we opt to handle the checking
// and combining the items in Roslyn until the `IsIncomplete` flag is fully supported in classic mode.
- if (session.Properties.TryGetProperty(CombinedSortedList, out ImmutableArray combinedSortedList))
+ if (sessionData.CombinedSortedList.HasValue)
{
// Always use the previously saved combined list if available.
- data = new AsyncCompletionSessionDataSnapshot(combinedSortedList, data.Snapshot, data.Trigger, data.InitialTrigger, data.SelectedFilters,
+ data = new AsyncCompletionSessionDataSnapshot(sessionData.CombinedSortedList.Value, data.Snapshot, data.Trigger, data.InitialTrigger, data.SelectedFilters,
data.IsSoftSelected, data.DisplaySuggestionItem, data.Defaults);
}
- else if (session.Properties.TryGetProperty(CompletionSource.ExpandedItemsTask, out Task<(CompletionContext, RoslynCompletionList)> task)
- && task.Status == TaskStatus.RanToCompletion)
+ else if (sessionData.ExpandedItemsTask != null)
{
- // Make sure the task is removed when Adding expanded items,
- // so duplicated items won't be added in subsequent list updates.
- session.Properties.RemoveProperty(CompletionSource.ExpandedItemsTask);
-
- var (expandedContext, _) = await task.ConfigureAwait(false);
- if (expandedContext.Items.Length > 0)
+ var task = sessionData.ExpandedItemsTask;
+ if (task.Status == TaskStatus.RanToCompletion)
{
- // Here we rely on the implementation detail of `CompletionItem.CompareTo`, which always put expand items after regular ones.
- var itemsBuilder = ImmutableArray.CreateBuilder(expandedContext.Items.Length + data.InitialSortedList.Length);
- itemsBuilder.AddRange(data.InitialSortedList);
- itemsBuilder.AddRange(expandedContext.Items);
- var combinedList = itemsBuilder.MoveToImmutable();
-
- // Add expanded items into a combined list, and save it to be used for future updates during the same session.
- session.Properties[CombinedSortedList] = combinedList;
- var combinedFilterStates = FilterSet.CombineFilterStates(expandedContext.Filters, data.SelectedFilters);
-
- data = new AsyncCompletionSessionDataSnapshot(combinedList, data.Snapshot, data.Trigger, data.InitialTrigger, combinedFilterStates,
- data.IsSoftSelected, data.DisplaySuggestionItem, data.Defaults);
+ // Make sure the task is removed when Adding expanded items,
+ // so duplicated items won't be added in subsequent list updates.
+ sessionData.ExpandedItemsTask = null;
+
+ var (expandedContext, _) = await task.ConfigureAwait(false);
+ if (expandedContext.Items.Length > 0)
+ {
+ // Here we rely on the implementation detail of `CompletionItem.CompareTo`, which always put expand items after regular ones.
+ var itemsBuilder = ImmutableArray.CreateBuilder(expandedContext.Items.Length + data.InitialSortedList.Length);
+ itemsBuilder.AddRange(data.InitialSortedList);
+ itemsBuilder.AddRange(expandedContext.Items);
+ var combinedList = itemsBuilder.MoveToImmutable();
+
+ // Add expanded items into a combined list, and save it to be used for future updates during the same session.
+ sessionData.CombinedSortedList = combinedList;
+ var combinedFilterStates = FilterSet.CombineFilterStates(expandedContext.Filters, data.SelectedFilters);
+
+ data = new AsyncCompletionSessionDataSnapshot(combinedList, data.Snapshot, data.Trigger, data.InitialTrigger, combinedFilterStates,
+ data.IsSoftSelected, data.DisplaySuggestionItem, data.Defaults);
+ }
+
+ AsyncCompletionLogger.LogSessionWithDelayedImportCompletionIncludedInUpdate();
}
-
- AsyncCompletionLogger.LogSessionWithDelayedImportCompletionIncludedInUpdate();
}
- var updater = new CompletionListUpdater(session, data, _recentItemsManager, _globalOptions);
+ var updater = new CompletionListUpdater(session.ApplicableToSpan, sessionData, data, _recentItemsManager, _globalOptions);
return updater.UpdateCompletionList(cancellationToken);
}
-
- private static RoslynCompletionItem GetOrAddRoslynCompletionItem(VSCompletionItem vsItem)
- {
- if (!vsItem.Properties.TryGetProperty(CompletionSource.RoslynItem, out RoslynCompletionItem roslynItem))
- {
- roslynItem = RoslynCompletionItem.Create(
- displayText: vsItem.DisplayText,
- filterText: vsItem.FilterText,
- sortText: vsItem.SortText,
- displayTextSuffix: vsItem.Suffix);
-
- vsItem.Properties.AddProperty(CompletionSource.RoslynItem, roslynItem);
- }
-
- return roslynItem;
- }
}
}
diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs
index 1555f67c15d53..c4f674845709c 100644
--- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs
+++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs
@@ -261,7 +261,9 @@ static async Task CreateCompletionItemAsync(
// If the feature flag is off, return an InsertText.
else
{
- completionItem.InsertText = item.Properties.ContainsKey("InsertionText") ? item.Properties["InsertionText"] : completeDisplayText;
+ completionItem.InsertText = item.Properties.ContainsKey(CommonCompletionItem.InsertionTextProperty)
+ ? item.Properties[CommonCompletionItem.InsertionTextProperty]
+ : completeDisplayText;
}
var commitCharacters = GetCommitCharacters(item, commitCharacterRulesCache, supportsVSExtensions);
diff --git a/src/EditorFeatures/Test/Completion/FileSystemCompletionHelperTests.cs b/src/EditorFeatures/Test/Completion/FileSystemCompletionHelperTests.cs
index 977908cdef448..dd635658652c4 100644
--- a/src/EditorFeatures/Test/Completion/FileSystemCompletionHelperTests.cs
+++ b/src/EditorFeatures/Test/Completion/FileSystemCompletionHelperTests.cs
@@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-#nullable disable
-
using System;
using System.Collections.Immutable;
using System.Linq;
@@ -20,7 +18,7 @@ private static void AssertItemsEqual(ImmutableArray actual, para
{
AssertEx.Equal(
expected,
- actual.Select(c => $"'{c.DisplayText}', {string.Join(", ", c.Tags)}, '{c.Properties["Description"]}'"),
+ actual.Select(c => $"'{c.DisplayText}', {string.Join(", ", c.Tags)}, '{c.Properties[CommonCompletionItem.DescriptionProperty]}'"),
itemInspector: c => $"@\"{c}\"");
Assert.True(actual.All(i => i.Rules == TestFileSystemCompletionHelper.CompletionRules));
diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb
index d591d1bb73273..022364fd20aae 100644
--- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb
+++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb
@@ -24,6 +24,7 @@ Imports Microsoft.VisualStudio.Text
Imports Microsoft.VisualStudio.Text.Editor
Imports Microsoft.VisualStudio.Text.Operations
Imports Microsoft.VisualStudio.Text.Projection
+Imports Roslyn.Utilities
Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
<[UseExportProvider]>
@@ -9700,8 +9701,10 @@ class C
Await state.AssertCompletionItemsDoNotContainAny("TestUnimportedItem")
Dim session = Await state.GetCompletionSession()
- Dim expandTask As Task = Nothing
- Assert.True(session.Properties.TryGetProperty(Of Task)(CompletionSource.ExpandedItemsTask, expandTask))
+ Dim sessionData = CompletionSessionData.GetOrCreateSessionData(session)
+ Dim expandTask = sessionData.ExpandedItemsTask
+
+ Assert.NotNull(expandTask)
Assert.False(expandTask.IsCompleted)
' following up by typing a few more characters each triggers an list update
@@ -9757,8 +9760,10 @@ class C
Await state.AssertCompletionItemsDoNotContainAny("TestUnimportedItem")
Dim session = Await state.GetCompletionSession()
- Dim expandTask As Task = Nothing
- Assert.True(session.Properties.TryGetProperty(Of Task)(CompletionSource.ExpandedItemsTask, expandTask))
+ Dim sessionData = CompletionSessionData.GetOrCreateSessionData(session)
+ Dim expandTask = sessionData.ExpandedItemsTask
+
+ Assert.NotNull(expandTask)
Assert.False(expandTask.IsCompleted)
' following up by typing more characters each triggers an list update
@@ -9815,8 +9820,10 @@ class C
Await state.AssertCompletionItemsDoNotContainAny("TestUnimportedItem")
Dim session = Await state.GetCompletionSession()
- Dim expandTask As Task = Nothing
- Assert.True(session.Properties.TryGetProperty(Of Task)(CompletionSource.ExpandedItemsTask, expandTask))
+ Dim sessionData = CompletionSessionData.GetOrCreateSessionData(session)
+ Dim expandTask = sessionData.ExpandedItemsTask
+
+ Assert.NotNull(expandTask)
Assert.False(expandTask.IsCompleted)
provider.Checkpoint.Release()
diff --git a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb
index f7e9b9e8dde1d..81bf48edb6700 100644
--- a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb
+++ b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb
@@ -8,6 +8,7 @@ Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Editor.CommandHandlers
Imports Microsoft.CodeAnalysis.Editor.CSharp.CompleteStatement
Imports Microsoft.CodeAnalysis.Editor.Implementation.Formatting
+Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities
Imports Microsoft.CodeAnalysis.LanguageServices
@@ -26,7 +27,6 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Inherits AbstractCommandHandlerTestState
Private Const timeoutMs = 60000
- Friend Const RoslynItem = "RoslynItem"
Friend ReadOnly EditorCompletionCommandHandler As ICommandHandler
Friend ReadOnly CompletionPresenterProvider As ICompletionPresenterProvider
@@ -502,7 +502,15 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
End Function
Private Shared Function GetRoslynCompletionItem(item As Data.CompletionItem) As CompletionItem
- Return If(item IsNot Nothing, DirectCast(item.Properties(RoslynItem), CompletionItem), Nothing)
+ If (item Is Nothing) Then
+ Return Nothing
+ End If
+
+ Dim roslynItemData As CompletionItemData = Nothing
+ If (CompletionItemData.TryGetData(item, roslynItemData) = False) Then
+ Return Nothing
+ End If
+ Return roslynItemData.RoslynItem
End Function
Public Sub RaiseFiltersChanged(args As ImmutableArray(Of Data.CompletionFilterWithState))
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs
index b8e03bf4936ee..7d3964708e97e 100644
--- a/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/CrefCompletionProvider.cs
@@ -417,11 +417,9 @@ private static CompletionItemRules GetRules(string displayText)
}
}
- private const string InsertionTextProperty = "insertionText";
-
protected override Task GetTextChangeAsync(CompletionItem selectedItem, char? ch, CancellationToken cancellationToken)
{
- if (!selectedItem.Properties.TryGetValue(InsertionTextProperty, out var insertionText))
+ if (!selectedItem.Properties.TryGetValue(CommonCompletionItem.InsertionTextProperty, out var insertionText))
{
insertionText = selectedItem.DisplayText;
}
diff --git a/src/Features/Core/Portable/Completion/CommonCompletionItem.cs b/src/Features/Core/Portable/Completion/CommonCompletionItem.cs
index 0b1442552014b..4a5a720b8ccb0 100644
--- a/src/Features/Core/Portable/Completion/CommonCompletionItem.cs
+++ b/src/Features/Core/Portable/Completion/CommonCompletionItem.cs
@@ -2,7 +2,6 @@
// 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;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Tags;
@@ -12,6 +11,9 @@ namespace Microsoft.CodeAnalysis.Completion
{
internal static class CommonCompletionItem
{
+ public const string DescriptionProperty = nameof(DescriptionProperty);
+ public const string InsertionTextProperty = nameof(InsertionTextProperty);
+
public static CompletionItem Create(
string displayText,
string? displayTextSuffix,
@@ -43,7 +45,7 @@ public static CompletionItem Create(
properties ??= ImmutableDictionary.Empty;
if (!description.IsDefault && description.Length > 0)
{
- properties = properties.Add("Description", EncodeDescription(description.ToTaggedText()));
+ properties = properties.Add(DescriptionProperty, EncodeDescription(description.ToTaggedText()));
}
return CompletionItem.Create(
@@ -60,11 +62,11 @@ public static CompletionItem Create(
}
public static bool HasDescription(CompletionItem item)
- => item.Properties.ContainsKey("Description");
+ => item.Properties.ContainsKey(DescriptionProperty);
public static CompletionDescription GetDescription(CompletionItem item)
{
- if (item.Properties.TryGetValue("Description", out var encodedDescription))
+ if (item.Properties.TryGetValue(DescriptionProperty, out var encodedDescription))
{
return DecodeDescription(encodedDescription);
}
diff --git a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs
index 3ec0e32e4fcea..c8f90f98d8176 100644
--- a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs
+++ b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs
@@ -43,7 +43,7 @@ private static CompletionItem CreateWorker(
if (insertionText != null)
{
- props = props.Add("InsertionText", insertionText);
+ props = props.Add(CommonCompletionItem.InsertionTextProperty, insertionText);
}
props = props.Add("ContextPosition", contextPosition.ToString());
@@ -254,7 +254,7 @@ public static int GetDescriptionPosition(CompletionItem item)
=> GetContextPosition(item);
public static string GetInsertionText(CompletionItem item)
- => item.Properties["InsertionText"];
+ => item.Properties[CommonCompletionItem.InsertionTextProperty];
// COMPAT OVERLOAD: This is used by IntelliCode.
public static CompletionItem CreateWithSymbolId(