diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs index be1669836da00..f7dde3e36408e 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs @@ -139,7 +139,7 @@ protected async Task VerifySendEnterThroughToEnterAsync(string initialMarkup, st sendThroughEnterOption); var service = GetCompletionService(workspace); - var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Default); + var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke); var item = completionList.Items.First(i => i.DisplayText.StartsWith(textTypedSoFar)); Assert.Equal(expected, Controller.SendEnterThroughToEditor(service.GetRules(), item, textTypedSoFar)); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs index 3c4c59b3c0668..5275619931bd7 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs @@ -438,7 +438,7 @@ class C var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); var service = CreateCompletionService(workspace, ImmutableArray.Create(provider)); - var completionList = await GetCompletionListAsync(service, document, hostDocument.CursorPosition.Value, CompletionTrigger.Default); + var completionList = await GetCompletionListAsync(service, document, hostDocument.CursorPosition.Value, CompletionTrigger.Invoke); Assert.True(called); } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs index 34323f4f9693b..dbf637fef3673 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs @@ -2125,7 +2125,7 @@ public override void set_Bar(int bay, int value) var solution = testWorkspace.CurrentSolution; var documentId = testWorkspace.Documents.Single(d => d.Name == "CSharpDocument").Id; var document = solution.GetDocument(documentId); - var triggerInfo = CompletionTrigger.Default; + var triggerInfo = CompletionTrigger.Invoke; var service = GetCompletionService(testWorkspace); var completionList = await GetCompletionListAsync(service, document, position, triggerInfo); @@ -2382,7 +2382,7 @@ public override bool Equals(object obj) var solution = testWorkspace.CurrentSolution; var documentId = testWorkspace.Documents.Single(d => d.Name == "CSharpDocument2").Id; var document = solution.GetDocument(documentId); - var triggerInfo = CompletionTrigger.Default; + var triggerInfo = CompletionTrigger.Invoke; var service = GetCompletionService(testWorkspace); var completionList = await GetCompletionListAsync(service, document, position, triggerInfo); @@ -2438,7 +2438,7 @@ public override bool Equals(object obj) var solution = testWorkspace.CurrentSolution; var documentId = testWorkspace.Documents.Single(d => d.Name == "CSharpDocument").Id; var document = solution.GetDocument(documentId); - var triggerInfo = CompletionTrigger.Default; + var triggerInfo = CompletionTrigger.Invoke; var service = GetCompletionService(testWorkspace); var completionList = await GetCompletionListAsync(service, document, cursorPosition, triggerInfo); @@ -2543,7 +2543,7 @@ static void Main(string[] args) var document = workspace.CurrentSolution.GetDocument(testDocument.Id); var service = GetCompletionService(workspace); - var completionList = await GetCompletionListAsync(service, document, testDocument.CursorPosition.Value, CompletionTrigger.Default); + var completionList = await GetCompletionListAsync(service, document, testDocument.CursorPosition.Value, CompletionTrigger.Invoke); var oldTree = await document.GetSyntaxTreeAsync(); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs index 60688a2b864d2..7815499b56050 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.cs @@ -793,7 +793,7 @@ private async Task CheckResultsAsync(Document document, int position, bool isBui { var triggerInfos = new List(); triggerInfos.Add(CompletionTrigger.CreateInsertionTrigger('a')); - triggerInfos.Add(CompletionTrigger.Default); + triggerInfos.Add(CompletionTrigger.Invoke); triggerInfos.Add(CompletionTrigger.CreateDeletionTrigger('z')); var service = GetCompletionService(document.Project.Solution.Workspace); diff --git a/src/EditorFeatures/Core/EditorFeatures.csproj b/src/EditorFeatures/Core/EditorFeatures.csproj index 70df6d05fe564..b04763caf1100 100644 --- a/src/EditorFeatures/Core/EditorFeatures.csproj +++ b/src/EditorFeatures/Core/EditorFeatures.csproj @@ -109,6 +109,8 @@ + + diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/CompletionFilterReason.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/CompletionFilterReason.cs new file mode 100644 index 0000000000000..40c14044c05d4 --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/CompletionFilterReason.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Completion +{ + internal enum CompletionFilterReason + { + Insertion, + Deletion, + CaretPositionChanged, + Other, + +#if false + // If necessary, we could add additional filter reasons. For example, for the below items. + // However, we have no need for them currently. That somewhat makes sense. We only want + // to really customize our filtering behavior depending on if a user was typing/deleting + // in the buffer. + Snippets, + ItemFiltersChanged, + Invoke, + InvokeAndCommitIfUnique +#endif + } + + internal static class CompletionTriggerExtensions + { + public static CompletionFilterReason GetFilterReason(this CompletionTrigger trigger) + => trigger.Kind.GetFilterReason(); + } + + internal static class CompletionTriggerKindExtensions + { + public static CompletionFilterReason GetFilterReason(this CompletionTriggerKind kind) + { + switch (kind) + { + case CompletionTriggerKind.Insertion: + return CompletionFilterReason.Insertion; + case CompletionTriggerKind.Deletion: + return CompletionFilterReason.Deletion; + case CompletionTriggerKind.Snippets: + case CompletionTriggerKind.Invoke: + case CompletionTriggerKind.InvokeAndCommitIfUnique: + return CompletionFilterReason.Other; + default: + throw ExceptionUtilities.UnexpectedValue(kind); + } + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session.cs index cd01507124134..56a2adb131c8f 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session.cs @@ -100,10 +100,7 @@ private void OnPresenterSessionCompletionItemFilterStateChanged( // Update the filter state for the model. Note: if we end up filtering everything // out we do *not* want to dismiss the completion list. - this.FilterModel(CompletionFilterReason.ItemFiltersChanged, - dismissIfEmptyAllowed: false, - recheckCaretPosition: false, - filterState: e.FilterState); + this.FilterModel(CompletionFilterReason.Other, filterState: e.FilterState); } } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs index d63e22a2f7a32..8f175b441e5e4 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session_FilterModel.cs @@ -21,8 +21,6 @@ internal partial class Session { public void FilterModel( CompletionFilterReason filterReason, - bool dismissIfEmptyAllowed, - bool recheckCaretPosition, ImmutableDictionary filterState) { AssertIsForeground(); @@ -34,54 +32,21 @@ public void FilterModel( Interlocked.Increment(ref _filterId); var localId = _filterId; Computation.ChainTaskAndNotifyControllerWhenFinished( - model => - { - if (model != null && filterState != null) - { - // If the UI specified an updated filter state, then incorporate that - // into our model. - model = model.WithFilterState(filterState); - } - - return FilterModelInBackground( - model, localId, caretPosition, recheckCaretPosition, dismissIfEmptyAllowed, filterReason); - }); - } - - public void IdentifyBestMatchAndFilterToAllItems( - CompletionFilterReason filterReason, bool recheckCaretPosition, bool dismissIfEmptyAllowed) - { - AssertIsForeground(); - - var caretPosition = GetCaretPointInViewBuffer(); - - // Use an interlocked increment so that reads by existing filter tasks will see the - // change. - Interlocked.Increment(ref _filterId); - var localId = _filterId; - Computation.ChainTaskAndNotifyControllerWhenFinished(model => - { - var filteredModel = FilterModelInBackground( - model, localId, caretPosition, recheckCaretPosition, dismissIfEmptyAllowed, filterReason); - - return filteredModel != null - ? filteredModel.WithFilteredItems(filteredModel.TotalItems).WithSelectedItem(filteredModel.SelectedItemOpt) - : null; - }); + model => FilterModelInBackground( + model, localId, caretPosition, filterReason, filterState)); } private Model FilterModelInBackground( Model model, int id, SnapshotPoint caretPosition, - bool recheckCaretPosition, - bool dismissIfEmptyAllowed, - CompletionFilterReason filterReason) + CompletionFilterReason filterReason, + ImmutableDictionary filterState) { using (Logger.LogBlock(FunctionId.Completion_ModelComputation_FilterModelInBackground, CancellationToken.None)) { return FilterModelInBackgroundWorker( - model, id, caretPosition, recheckCaretPosition, dismissIfEmptyAllowed, filterReason); + model, id, caretPosition, filterReason, filterState); } } @@ -89,9 +54,8 @@ private Model FilterModelInBackgroundWorker( Model model, int id, SnapshotPoint caretPosition, - bool recheckCaretPosition, - bool dismissIfEmptyAllowed, - CompletionFilterReason filterReason) + CompletionFilterReason filterReason, + ImmutableDictionary filterState) { if (model == null) { @@ -101,11 +65,24 @@ private Model FilterModelInBackgroundWorker( // We want to dismiss the session if the caret ever moved outside our bounds. // Do this before we check the _filterId. We don't want this work to not happen // just because the user typed more text and added more filter items. - if (recheckCaretPosition && Controller.IsCaretOutsideAllItemBounds(model, caretPosition)) + if (filterReason == CompletionFilterReason.CaretPositionChanged && + Controller.IsCaretOutsideAllItemBounds(model, caretPosition)) { return null; } + // If the UI specified an updated filter state, then incorporate that + // into our model. Do this before we check the _filterId. We don't + // want this work to not happen just because the user typed more text + // and added more filter items. + if (filterState != null) + { + model = model.WithFilterState(filterState); + } + + // If there's another request in the queue to filter items, then just + // bail out immediately. No point in doing extra work that's just + // going to be overridden by the next filter task. if (id != _filterId) { return model; @@ -155,7 +132,8 @@ private Model FilterModelInBackgroundWorker( return model; } - if (ItemIsFilteredOut(currentItem, effectiveFilterItemState)) + if (CompletionItemFilter.ShouldBeFilteredOutOfCompletionList( + currentItem, effectiveFilterItemState)) { continue; } @@ -170,11 +148,23 @@ private Model FilterModelInBackgroundWorker( } else { - if (filterText.Length <= 1) + // The item didn't match the filter text. We'll still keep it in the list + // if one of two things is true: + // + // 1. The user has only typed a single character. In this case they might + // have just typed the character to get completion. Filtering out items + // here is not desirable. + // + // 2. They brough up completion with ctrl-j or through deletion. In these + // cases we just always keep all the items in the list. + + var wasTriggeredByDeleteOrSimpleInvoke = + model.Trigger.Kind == CompletionTriggerKind.Deletion || + model.Trigger.Kind == CompletionTriggerKind.Invoke; + var shouldKeepItem = filterText.Length <= 1 || wasTriggeredByDeleteOrSimpleInvoke; + + if (shouldKeepItem) { - // Even though the rule provider didn't match this, we'll still include it - // since we want to allow a user typing a single character and seeing all - // possibly completions. filterResults.Add(new FilterResult( currentItem, filterText, matchedFilterText: false)); } @@ -186,14 +176,14 @@ private Model FilterModelInBackgroundWorker( // If no items matched the filter text then determine what we should do. if (filterResults.Count == 0) { - return HandleAllItemsFilteredOut(model, filterReason, dismissIfEmptyAllowed); + return HandleAllItemsFilteredOut(model, filterReason); } // If this was deletion, then we control the entire behavior of deletion // ourselves. if (model.Trigger.Kind == CompletionTriggerKind.Deletion) { - return HandleDeletionTrigger(model, filterResults); + return HandleDeletionTrigger(model, filterReason, filterResults); } return HandleNormalFiltering( @@ -219,7 +209,7 @@ private static ImmutableDictionary ComputeEffectiveF return filterState; } - private Boolean IsAfterDot(Model model, ITextSnapshot textSnapshot, Dictionary textSpanToText) + private bool IsAfterDot(Model model, ITextSnapshot textSnapshot, Dictionary textSpanToText) { var span = model.OriginalList.Span; @@ -335,8 +325,21 @@ private CompletionItem GetBestCompletionItemBasedOnMRU( return bestItem; } - private Model HandleDeletionTrigger(Model model, List filterResults) + private Model HandleDeletionTrigger( + Model model, CompletionFilterReason filterReason, List filterResults) { + if (filterReason == CompletionFilterReason.Insertion && + !filterResults.Any(r => r.MatchedFilterText)) + { + // The user has typed something, but nothing in the actual list matched what + // they were typing. In this case, we want to dismiss completion entirely. + // The thought process is as follows: we aggressively brough up completion + // to help them when they typed delete (in case they wanted to pick another + // item). However, they're typing something that doesn't seem to match at all + // The completion list is just distracting at this point. + return null; + } + FilterResult? bestFilterResult = null; int matchCount = 0; foreach (var currentFilterResult in filterResults.Where(r => r.MatchedFilterText)) @@ -403,28 +406,12 @@ private bool IsBetterDeletionMatch(FilterResult result1, FilterResult result2) return false; } - private struct FilterResult - { - public readonly CompletionItem CompletionItem; - public readonly bool MatchedFilterText; - public readonly string FilterText; - - public FilterResult(CompletionItem completionItem, string filterText, bool matchedFilterText) - { - CompletionItem = completionItem; - MatchedFilterText = matchedFilterText; - FilterText = filterText; - } - } - private static Model HandleAllItemsFilteredOut( Model model, - CompletionFilterReason filterReason, - bool dismissIfEmptyAllowed) + CompletionFilterReason filterReason) { - if (dismissIfEmptyAllowed && - model.DismissIfEmpty && - filterReason == CompletionFilterReason.TypeChar) + if (model.DismissIfEmpty && + filterReason == CompletionFilterReason.Insertion) { // If the user was just typing, and the list went to empty *and* this is a // language that wants to dismiss on empty, then just return a null model @@ -464,7 +451,7 @@ private static bool MatchesFilterText( // Specifically, to avoid being too aggressive when matching an item during // completion, we require that the current filter text be a prefix of the // item in the list. - if (filterReason == CompletionFilterReason.BackspaceOrDelete && + if (filterReason == CompletionFilterReason.Deletion && trigger.Kind == CompletionTriggerKind.Deletion) { return item.FilterText.GetCaseInsensitivePrefixLength(filterText) > 0; @@ -507,34 +494,6 @@ private static bool IsAllDigits(string filterText) return true; } - private bool ItemIsFilteredOut( - CompletionItem item, - ImmutableDictionary filterState) - { - if (filterState == null) - { - // No filtering. The item is not filtered out. - return false; - } - - foreach (var filter in CompletionItemFilter.AllFilters) - { - // only consider filters that match the item - var matches = filter.Matches(item); - if (matches) - { - // if the specific filter is enabled then it is not filtered out - if (filterState.TryGetValue(filter, out var enabled) && enabled) - { - return false; - } - } - } - - // The item was filtered out. - return true; - } - private bool IsHardSelection( Model model, CompletionItem bestFilterMatch, @@ -648,4 +607,4 @@ private static bool IsAllPunctuation(string filterText) } } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.cs index 234f0c0aa6513..309e8e275078f 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.cs @@ -149,18 +149,9 @@ internal override void OnModelUpdated(Model modelOpt) } } - private bool StartNewModelComputation( - CompletionService completionService, bool filterItems, bool dismissIfEmptyAllowed) - { - return StartNewModelComputation( - completionService, CompletionTrigger.Default, filterItems, dismissIfEmptyAllowed); - } - private bool StartNewModelComputation( CompletionService completionService, - CompletionTrigger trigger, - bool filterItems, - bool dismissIfEmptyAllowed) + CompletionTrigger trigger) { AssertIsForeground(); Contract.ThrowIfTrue(sessionOpt != null); @@ -194,37 +185,11 @@ private bool StartNewModelComputation( this.sessionOpt = new Session(this, computation, Presenter.CreateSession(TextView, SubjectBuffer, null)); sessionOpt.ComputeModel(completionService, trigger, _roles, GetOptions()); - - var filterReason = trigger.Kind == CompletionTriggerKind.Deletion - ? CompletionFilterReason.BackspaceOrDelete - : trigger.Kind == CompletionTriggerKind.Other - ? CompletionFilterReason.Other - : CompletionFilterReason.TypeChar; - - FilterToSomeOrAllItems(filterItems, dismissIfEmptyAllowed, filterReason); + sessionOpt.FilterModel(trigger.GetFilterReason(), filterState: null); return true; } - private void FilterToSomeOrAllItems(bool filterItems, bool dismissIfEmptyAllowed, CompletionFilterReason filterReason) - { - if (filterItems) - { - sessionOpt.FilterModel( - filterReason, - recheckCaretPosition: false, - dismissIfEmptyAllowed: dismissIfEmptyAllowed, - filterState: null); - } - else - { - sessionOpt.IdentifyBestMatchAndFilterToAllItems( - filterReason, - recheckCaretPosition: false, - dismissIfEmptyAllowed: dismissIfEmptyAllowed); - } - } - private CompletionService GetCompletionService() { if (!Workspace.TryGetWorkspace(this.SubjectBuffer.AsTextContainer(), out var workspace)) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_Backspace.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_Backspace.cs index 119fd01f9d3ea..3d2149f8ab48f 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_Backspace.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_Backspace.cs @@ -67,8 +67,7 @@ private void ExecuteBackspaceOrDelete(ITextView textView, Action nextHandler, bo if (completionService != null) { - this.StartNewModelComputation( - completionService, trigger, filterItems: false, dismissIfEmptyAllowed: true); + this.StartNewModelComputation(completionService, trigger); } return; @@ -103,14 +102,7 @@ private void ExecuteBackspaceOrDelete(ITextView textView, Action nextHandler, bo } else if (model != null) { - // If we were triggered on backspace/delete, and we're still deleting, - // then we don't want to filter out items (i.e. we still want all items). - // However, we do still want to run the code to figure out what the best - // item is to select from all those items. - FilterToSomeOrAllItems( - filterItems: model.Trigger.Kind != CompletionTriggerKind.Deletion, - dismissIfEmptyAllowed: true, - filterReason: CompletionFilterReason.BackspaceOrDelete); + sessionOpt.FilterModel(CompletionFilterReason.Deletion, filterState: null); } } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs index b7dcda6b08aaa..b483d73dab490 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs @@ -37,11 +37,7 @@ internal override void OnCaretPositionChanged(object sender, EventArgs args) // hasn't moved outside all of the items. If so, we'd want to dismiss completions. // Just refilter the list, asking it to make sure that the caret is still within // bounds. - sessionOpt.FilterModel( - CompletionFilterReason.CaretPositionChanged, - dismissIfEmptyAllowed: false, - recheckCaretPosition: true, - filterState: null); + sessionOpt.FilterModel(CompletionFilterReason.CaretPositionChanged, filterState: null); } internal bool IsCaretOutsideAllItemBounds(Model model, SnapshotPoint caretPoint) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CommitUniqueCompletionListItem.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CommitUniqueCompletionListItem.cs index 023e0d6d042a2..856be0aa34bf2 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CommitUniqueCompletionListItem.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CommitUniqueCompletionListItem.cs @@ -3,6 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Editor.Commands; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -31,7 +32,8 @@ void ICommandHandler.ExecuteCommand( return; } - if (!StartNewModelComputation(completionService, filterItems: true, dismissIfEmptyAllowed: true)) + var trigger = new CompletionTrigger(CompletionTriggerKind.InvokeAndCommitIfUnique); + if (!StartNewModelComputation(completionService, trigger)) { return; } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_InvokeCompletionList.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_InvokeCompletionList.cs index b249545c53358..6e8fa48c8045b 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_InvokeCompletionList.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_InvokeCompletionList.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Editor.Commands; namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion @@ -31,8 +32,8 @@ void ICommandHandler.ExecuteCommand(InvokeCompl return; } - StartNewModelComputation( - completionService, filterItems: false, dismissIfEmptyAllowed: false); + var trigger = CompletionTrigger.Invoke; + StartNewModelComputation(completionService, trigger); } } } \ No newline at end of file diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_OnTextViewBufferPostChanged.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_OnTextViewBufferPostChanged.cs index e095a8b3ff052..86cfbfbb25190 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_OnTextViewBufferPostChanged.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_OnTextViewBufferPostChanged.cs @@ -29,12 +29,14 @@ internal override void OnTextViewBufferPostChanged(object sender, EventArgs e) } else { - // Filter the model, recheck the caret position if we haven't computed the initial model yet - sessionOpt.FilterModel( - CompletionFilterReason.TypeChar, - recheckCaretPosition: model == null, - dismissIfEmptyAllowed: true, - filterState: null); + // Something changed in the buffer without us hearing about the change first. + // This can happen in complex projection scenarios (with buffers being mapped/unmapped). + // In this case, queue up a CaretPositionChanged filter first. That way if the caret + // moved out of bounds of the items, then we'll dismiss. Also queue up an insertion. + // that way we go and filter things properly to ensure that the list contains the + // appropriate items. + sessionOpt.FilterModel(CompletionFilterReason.CaretPositionChanged, filterState: null); + sessionOpt.FilterModel(CompletionFilterReason.Insertion, filterState: null); } } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs index 502677421015e..2fc34fe40a2d4 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs @@ -113,8 +113,7 @@ private bool TryInvokeSnippetCompletion(TabKeyCommandArgs args) var textChange = new TextChange(TextSpan.FromBounds(caretPoint - 1, caretPoint), string.Empty); workspace.ApplyTextChanges(documentId, textChange, CancellationToken.None); this.StartNewModelComputation( - completionService, new CompletionTrigger(CompletionTriggerKind.Snippets), - filterItems: false, dismissIfEmptyAllowed: true); + completionService, new CompletionTrigger(CompletionTriggerKind.Snippets)); return true; } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs index 9cd2b768c33b4..0a53b0a3809d5 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TypeChar.cs @@ -148,7 +148,7 @@ void ICommandHandler.ExecuteCommand(TypeCharCommandArgs arg { // First create the session that represents that we now have a potential // completion list. Then tell it to start computing. - StartNewModelComputation(completionService, trigger, filterItems: true, dismissIfEmptyAllowed: true); + StartNewModelComputation(completionService, trigger); return; } else @@ -179,11 +179,7 @@ void ICommandHandler.ExecuteCommand(TypeCharCommandArgs arg } // Now filter whatever result we have. - sessionOpt.FilterModel( - CompletionFilterReason.TypeChar, - recheckCaretPosition: false, - dismissIfEmptyAllowed: true, - filterState: null); + sessionOpt.FilterModel(CompletionFilterReason.Insertion, filterState: null); } else { @@ -206,10 +202,8 @@ void ICommandHandler.ExecuteCommand(TypeCharCommandArgs arg { // Known to be a filter character for the currently selected item. So just // filter the session. - sessionOpt.FilterModel(CompletionFilterReason.TypeChar, - recheckCaretPosition: false, - dismissIfEmptyAllowed: true, - filterState: null); + + sessionOpt.FilterModel(CompletionFilterReason.Insertion, filterState: null); return; } @@ -230,7 +224,7 @@ void ICommandHandler.ExecuteCommand(TypeCharCommandArgs arg if (isTextuallyTriggered) { StartNewModelComputation( - completionService, trigger, filterItems: true, dismissIfEmptyAllowed: true); + completionService, trigger); return; } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/FilterResult.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/FilterResult.cs new file mode 100644 index 0000000000000..79784dffde401 --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/FilterResult.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Completion; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion +{ + internal partial class Controller + { + internal partial class Session + { + private struct FilterResult + { + public readonly CompletionItem CompletionItem; + public readonly bool MatchedFilterText; + public readonly string FilterText; + + public FilterResult(CompletionItem completionItem, string filterText, bool matchedFilterText) + { + CompletionItem = completionItem; + MatchedFilterText = matchedFilterText; + FilterText = filterText; + } + } + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index dd992562d0255..296c6c09292e9 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -2929,5 +2929,57 @@ class AAttribute: Attribute Assert.Equal("[A(Skip )]", state.GetLineTextFromCaretPosition()) End Using End Function + + + + Public Async Function TestFilteringAfterSimpleInvokeShowsAllItemsMatchingFilter() As Task + Using state = TestState.CreateCSharpTestState( + ) + + state.SendInvokeCompletionList() + Await state.AssertSelectedCompletionItem("Red") + state.CompletionItemsContainsAll(displayText:={"Red", "Green", "Blue", "Equals"}) + + Dim filters = state.CurrentCompletionPresenterSession.CompletionItemFilters + Dim dict = New Dictionary(Of CompletionItemFilter, Boolean) + For Each f In filters + dict(f) = False + Next + + dict(CompletionItemFilter.EnumFilter) = True + + Dim args = New CompletionItemFilterStateChangedEventArgs(dict.ToImmutableDictionary()) + state.CurrentCompletionPresenterSession.RaiseFiltersChanged(args) + Await state.AssertSelectedCompletionItem("Red") + state.CompletionItemsContainsAll(displayText:={"Red", "Green", "Blue"}) + Assert.False(state.CurrentCompletionPresenterSession.CompletionItems.Any(Function(i) i.DisplayText = "Equals")) + + For Each f In filters + dict(f) = False + Next + + args = New CompletionItemFilterStateChangedEventArgs(dict.ToImmutableDictionary()) + state.CurrentCompletionPresenterSession.RaiseFiltersChanged(args) + Await state.AssertSelectedCompletionItem("Red") + state.CompletionItemsContainsAll(displayText:={"Red", "Green", "Blue", "Equals"}) + + End Using + End Function End Class End Namespace \ No newline at end of file diff --git a/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb b/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb index ea3a8feb377e9..41278c89b4606 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb @@ -26,7 +26,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Dim list = Await completionService.GetCompletionsAsync( document:=document, caretPosition:=0, - trigger:=CompletionTrigger.Default, + trigger:=CompletionTrigger.Invoke, options:=Nothing, cancellationToken:=Nothing) diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs index 2a6cf2d8963f7..db588c57e1093 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs @@ -84,7 +84,7 @@ protected async Task CheckResultsAsync( { var code = (await document.GetTextAsync()).ToString(); - CompletionTrigger trigger = CompletionTrigger.Default; + var trigger = CompletionTrigger.Invoke; if (usePreviousCharAsTrigger) { @@ -298,7 +298,7 @@ private async Task VerifyCustomCommitProviderCheckResultsAsync(Document document var textBuffer = workspace.Documents.Single().TextBuffer; var service = GetCompletionService(workspace); - var items = (await GetCompletionListAsync(service, document, position, CompletionTrigger.Default)).Items; + var items = (await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke)).Items; var firstItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); var customCommitCompletionProvider = service.ExclusiveProviders?[0] as ICustomCommitCompletionProvider; @@ -409,7 +409,7 @@ private async Task VerifyProviderCommitCheckResultsAsync( var textSnapshot = textBuffer.CurrentSnapshot.AsText(); var service = GetCompletionService(workspace); - var items = (await GetCompletionListAsync(service, document, position, CompletionTrigger.Default)).Items; + var items = (await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke)).Items; var firstItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); var completionRules = GetCompletionHelper(document); @@ -544,7 +544,7 @@ private async Task VerifyItemWithReferenceWorkerAsync( testWorkspace.Options = testWorkspace.Options.WithChangedOption(CompletionOptions.HideAdvancedMembers, document.Project.Language, hideAdvancedMembers); - var triggerInfo = CompletionTrigger.Default; + var triggerInfo = CompletionTrigger.Invoke; var completionService = GetCompletionService(testWorkspace); var completionList = await GetCompletionListAsync(completionService, document, position, triggerInfo); @@ -599,7 +599,7 @@ private async Task VerifyItemWithMscorlib45WorkerAsync( var documentId = testWorkspace.Documents.Single(d => d.Name == "SourceDocument").Id; var document = solution.GetDocument(documentId); - var triggerInfo = CompletionTrigger.Default; + var triggerInfo = CompletionTrigger.Invoke; var completionService = GetCompletionService(testWorkspace); var completionList = await GetCompletionListAsync(completionService, document, position, triggerInfo); @@ -630,7 +630,7 @@ protected async Task VerifyItemInLinkedFilesAsync(string xmlString, string expec var currentContextDocumentId = testWorkspace.GetDocumentIdInCurrentContext(textContainer); var document = solution.GetDocument(currentContextDocumentId); - var triggerInfo = CompletionTrigger.Default; + var triggerInfo = CompletionTrigger.Invoke; var completionService = GetCompletionService(testWorkspace); var completionList = await GetCompletionListAsync(completionService, document, position, triggerInfo); @@ -781,7 +781,7 @@ protected async Task VerifyCommitCharactersAsync(string initialMarkup, string te var position = hostDocument.CursorPosition.Value; var service = GetCompletionService(workspace); - var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Default); + var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke); var item = completionList.Items.First(i => i.DisplayText.StartsWith(textTypedSoFar)); foreach (var ch in validChars) diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb index 4294d7f8bc208..a2099c17196a9 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb @@ -99,7 +99,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet Dim position = hostDocument.CursorPosition.Value Dim service = GetCompletionService(workspace) - Dim completionList = Await GetCompletionListAsync(service, document, position, CompletionTrigger.Default) + Dim completionList = Await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke) Dim item = completionList.Items.First(Function(i) i.DisplayText.StartsWith(textTypedSoFar)) Assert.Equal(expected, Controller.SendEnterThroughToEditor(service.GetRules(), item, textTypedSoFar)) diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb index d82c8ecee3d2d..64f8d865013d2 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb @@ -435,7 +435,7 @@ End Class]]>.Value.NormalizeLineEndings() Dim service = CreateCompletionService( workspace, ImmutableArray.Create(Of CompletionProvider)(completionProvider)) - Dim completionList = Await GetCompletionListAsync(service, document, hostDocument.CursorPosition.Value, CompletionTrigger.Default) + Dim completionList = Await GetCompletionListAsync(service, document, hostDocument.CursorPosition.Value, CompletionTrigger.Invoke) Assert.True(called) End Using diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ImplementsClauseCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ImplementsClauseCompletionProviderTests.vb index 78cd7bd046ed8..7f995ae79b120 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ImplementsClauseCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ImplementsClauseCompletionProviderTests.vb @@ -643,7 +643,7 @@ End Interface Dim position = workspace.Documents.Single().CursorPosition.Value Dim document = workspace.CurrentSolution.GetDocument(workspace.Documents.Single().Id) Dim service = GetCompletionService(workspace) - Dim completionList = Await GetCompletionListAsync(service, document, position, CompletionTrigger.Default) + Dim completionList = Await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke) AssertEx.Any(completionList.Items, Function(c) c.DisplayText = "Workcover") End Using End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb index f32500e9d4a92..d319f9fff53c1 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb @@ -412,7 +412,7 @@ End Program Dim caretPosition = hostDocument.CursorPosition.Value Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id) Dim service = GetCompletionService(workspace) - Dim completionList = Await GetCompletionListAsync(service, document, caretPosition, CompletionTrigger.Default) + Dim completionList = Await GetCompletionListAsync(service, document, caretPosition, CompletionTrigger.Invoke) Assert.True(completionList Is Nothing OrElse completionList.IsExclusive, "Expected always exclusive") End Using End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb index a18ea891ec297..3836ca84d4014 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb @@ -1746,7 +1746,7 @@ public class C Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id) Dim service = GetCompletionService(workspace) - Dim completionList = Await GetCompletionListAsync(service, document, caretPosition, CompletionTrigger.Default) + Dim completionList = Await GetCompletionListAsync(service, document, caretPosition, CompletionTrigger.Invoke) Assert.False(completionList.Items.Any(Function(c) c.DisplayText = "e")) End Using End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb index f8803fb9f4d39..eae88aff0c7b3 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SuggestionModeCompletionProviderTests.vb @@ -296,7 +296,7 @@ End Class Namespace $$ End Namespace - Await VerifyBuilderAsync(markup, CompletionTrigger.Default) + Await VerifyBuilderAsync(markup, CompletionTrigger.Invoke) End Function @@ -306,35 +306,35 @@ End Namespace Namespace A.$$ End Namespace - Await VerifyBuilderAsync(markup, CompletionTrigger.Default) + Await VerifyBuilderAsync(markup, CompletionTrigger.Invoke) End Function Public Async Function PartialClassName() As Task Dim markup = Partial Class $$ - Await VerifyBuilderAsync(markup, CompletionTrigger.Default) + Await VerifyBuilderAsync(markup, CompletionTrigger.Invoke) End Function Public Async Function PartialStructureName() As Task Dim markup = Partial Structure $$ - Await VerifyBuilderAsync(markup, CompletionTrigger.Default) + Await VerifyBuilderAsync(markup, CompletionTrigger.Invoke) End Function Public Async Function PartialInterfaceName() As Task Dim markup = Partial Interface $$ - Await VerifyBuilderAsync(markup, CompletionTrigger.Default) + Await VerifyBuilderAsync(markup, CompletionTrigger.Invoke) End Function Public Async Function PartialModuleName() As Task Dim markup = Partial Module $$ - Await VerifyBuilderAsync(markup, CompletionTrigger.Default) + Await VerifyBuilderAsync(markup, CompletionTrigger.Invoke) End Function diff --git a/src/Features/Core/Portable/Completion/CompletionFilterReason.cs b/src/Features/Core/Portable/Completion/CompletionFilterReason.cs deleted file mode 100644 index e16af4fa82127..0000000000000 --- a/src/Features/Core/Portable/Completion/CompletionFilterReason.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.CodeAnalysis.Completion -{ - internal enum CompletionFilterReason - { - Other, - TypeChar, - BackspaceOrDelete, - ItemFiltersChanged, - CaretPositionChanged, - } -} \ No newline at end of file diff --git a/src/Features/Core/Portable/Completion/CompletionItem.cs b/src/Features/Core/Portable/Completion/CompletionItem.cs index a187f6f24ad9c..b2ce5d5c4286a 100644 --- a/src/Features/Core/Portable/Completion/CompletionItem.cs +++ b/src/Features/Core/Portable/Completion/CompletionItem.cs @@ -257,9 +257,6 @@ int IComparable.CompareTo(CompletionItem other) return result; } - public override string ToString() - { - return DisplayText; - } + public override string ToString() => DisplayText; } } \ No newline at end of file diff --git a/src/Features/Core/Portable/Completion/CompletionItemFilter.cs b/src/Features/Core/Portable/Completion/CompletionItemFilter.cs index a419fb3d4e1d7..32ed698d07075 100644 --- a/src/Features/Core/Portable/Completion/CompletionItemFilter.cs +++ b/src/Features/Core/Portable/Completion/CompletionItemFilter.cs @@ -33,6 +33,34 @@ public bool Matches(CompletionItem item) return false; } + public static bool ShouldBeFilteredOutOfCompletionList( + CompletionItem item, + ImmutableDictionary filterState) + { + if (filterState == null) + { + // No filtering. The item is not filtered out. + return false; + } + + foreach (var filter in AllFilters) + { + // only consider filters that match the item + var matches = filter.Matches(item); + if (matches) + { + // if the specific filter is enabled then it is not filtered out + if (filterState.TryGetValue(filter, out var enabled) && enabled) + { + return false; + } + } + } + + // The item was filtered out. + return true; + } + public static readonly CompletionItemFilter NamespaceFilter = new CompletionItemFilter(FeaturesResources.Namespaces, CompletionTags.Namespace, 'n'); public static readonly CompletionItemFilter ClassFilter = new CompletionItemFilter(FeaturesResources.Classes, CompletionTags.Class, 'c'); public static readonly CompletionItemFilter ModuleFilter = new CompletionItemFilter(FeaturesResources.Modules, CompletionTags.Module, 'u'); diff --git a/src/Features/Core/Portable/Completion/CompletionTrigger.cs b/src/Features/Core/Portable/Completion/CompletionTrigger.cs index aa2facd13c71e..a143d647e2002 100644 --- a/src/Features/Core/Portable/Completion/CompletionTrigger.cs +++ b/src/Features/Core/Portable/Completion/CompletionTrigger.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; namespace Microsoft.CodeAnalysis.Completion { @@ -25,25 +26,29 @@ internal CompletionTrigger(CompletionTriggerKind kind, char character = (char)0) this.Character = character; } + /// + /// Do not use. Use instead. + /// + [Obsolete("Use 'Invoke' instead.")] + public static readonly CompletionTrigger Default = + new CompletionTrigger(CompletionTriggerKind.Other); + /// /// The default when none is specified. /// - public static readonly CompletionTrigger Default = new CompletionTrigger(CompletionTriggerKind.Other); + public static readonly CompletionTrigger Invoke = + new CompletionTrigger(CompletionTriggerKind.Invoke); /// /// Creates a new instance of a association with the insertion of a typed character into the document. /// public static CompletionTrigger CreateInsertionTrigger(char insertedCharacter) - { - return new CompletionTrigger(CompletionTriggerKind.Insertion, insertedCharacter); - } + => new CompletionTrigger(CompletionTriggerKind.Insertion, insertedCharacter); /// /// Creates a new instance of a association with the deletion of a character from the document. /// public static CompletionTrigger CreateDeletionTrigger(char deletedCharacter) - { - return new CompletionTrigger(CompletionTriggerKind.Deletion, deletedCharacter); - } + => new CompletionTrigger(CompletionTriggerKind.Deletion, deletedCharacter); } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Completion/CompletionTriggerKind.cs b/src/Features/Core/Portable/Completion/CompletionTriggerKind.cs index a689447d41826..4072fc07b4dc7 100644 --- a/src/Features/Core/Portable/Completion/CompletionTriggerKind.cs +++ b/src/Features/Core/Portable/Completion/CompletionTriggerKind.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; + namespace Microsoft.CodeAnalysis.Completion { /// @@ -10,21 +12,34 @@ public enum CompletionTriggerKind /// /// Completion was triggered via some other mechanism. /// + [Obsolete("Use 'Invoke' instead.")] Other = 0, + /// + /// Completion was trigger by a direct invocation of the completion feature + /// (ctrl-j in Visual Studio). + /// + Invoke = 0, + /// /// Completion was triggered via an action inserting a character into the document. /// - Insertion, + Insertion = 1, /// /// Completion was triggered via an action deleting a character from the document. /// - Deletion, + Deletion = 2, /// /// Completion was triggered for snippets only. /// - Snippets + Snippets = 3, + + /// + /// Completion was triggered with a request to commit if a unique item would be selected + /// (ctrl-space in Visual Studio). + /// + InvokeAndCommitIfUnique = 4 } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 1486995f9c833..d3abb24a97d92 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -273,7 +273,6 @@ - diff --git a/src/Features/Core/Portable/PublicAPI.Unshipped.txt b/src/Features/Core/Portable/PublicAPI.Unshipped.txt index b0b07953f53a7..2fc7264590681 100644 --- a/src/Features/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Features/Core/Portable/PublicAPI.Unshipped.txt @@ -15,6 +15,8 @@ Microsoft.CodeAnalysis.Completion.CompletionList.Span.get -> Microsoft.CodeAnaly Microsoft.CodeAnalysis.Completion.CompletionList.WithSpan(Microsoft.CodeAnalysis.Text.TextSpan span) -> Microsoft.CodeAnalysis.Completion.CompletionList Microsoft.CodeAnalysis.Completion.CompletionRules.SnippetsRule.get -> Microsoft.CodeAnalysis.Completion.SnippetsRule Microsoft.CodeAnalysis.Completion.CompletionRules.WithSnippetsRule(Microsoft.CodeAnalysis.Completion.SnippetsRule snippetsRule) -> Microsoft.CodeAnalysis.Completion.CompletionRules +Microsoft.CodeAnalysis.Completion.CompletionTriggerKind.Invoke = 0 -> Microsoft.CodeAnalysis.Completion.CompletionTriggerKind +Microsoft.CodeAnalysis.Completion.CompletionTriggerKind.InvokeAndCommitIfUnique = 4 -> Microsoft.CodeAnalysis.Completion.CompletionTriggerKind Microsoft.CodeAnalysis.Completion.SnippetsRule Microsoft.CodeAnalysis.Completion.SnippetsRule.AlwaysInclude = 2 -> Microsoft.CodeAnalysis.Completion.SnippetsRule Microsoft.CodeAnalysis.Completion.SnippetsRule.Default = 0 -> Microsoft.CodeAnalysis.Completion.SnippetsRule @@ -26,5 +28,6 @@ static Microsoft.CodeAnalysis.Completion.CompletionItem.Create(string displayTex static Microsoft.CodeAnalysis.Completion.CompletionItem.Create(string displayText, string filterText, string sortText, Microsoft.CodeAnalysis.Text.TextSpan span, System.Collections.Immutable.ImmutableDictionary properties, System.Collections.Immutable.ImmutableArray tags, Microsoft.CodeAnalysis.Completion.CompletionItemRules rules) -> Microsoft.CodeAnalysis.Completion.CompletionItem static Microsoft.CodeAnalysis.Completion.CompletionItemRules.Create(System.Collections.Immutable.ImmutableArray filterCharacterRules = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray commitCharacterRules = default(System.Collections.Immutable.ImmutableArray), Microsoft.CodeAnalysis.Completion.EnterKeyRule enterKeyRule = Microsoft.CodeAnalysis.Completion.EnterKeyRule.Default, bool formatOnCommit = false, int? matchPriority = null, Microsoft.CodeAnalysis.Completion.CompletionItemSelectionBehavior selectionBehavior = Microsoft.CodeAnalysis.Completion.CompletionItemSelectionBehavior.Default) -> Microsoft.CodeAnalysis.Completion.CompletionItemRules static Microsoft.CodeAnalysis.Completion.CompletionRules.Create(bool dismissIfEmpty = false, bool dismissIfLastCharacterDeleted = false, System.Collections.Immutable.ImmutableArray defaultCommitCharacters = default(System.Collections.Immutable.ImmutableArray), Microsoft.CodeAnalysis.Completion.EnterKeyRule defaultEnterKeyRule = Microsoft.CodeAnalysis.Completion.EnterKeyRule.Default, Microsoft.CodeAnalysis.Completion.SnippetsRule snippetsRule = Microsoft.CodeAnalysis.Completion.SnippetsRule.Default) -> Microsoft.CodeAnalysis.Completion.CompletionRules +static readonly Microsoft.CodeAnalysis.Completion.CompletionTrigger.Invoke -> Microsoft.CodeAnalysis.Completion.CompletionTrigger virtual Microsoft.CodeAnalysis.Completion.CompletionService.FilterItems(Microsoft.CodeAnalysis.Document document, System.Collections.Immutable.ImmutableArray items, string filterText) -> System.Collections.Immutable.ImmutableArray virtual Microsoft.CodeAnalysis.Completion.CompletionService.GetDefaultCompletionListSpan(Microsoft.CodeAnalysis.Text.SourceText text, int caretPosition) -> Microsoft.CodeAnalysis.Text.TextSpan \ No newline at end of file