From afc5e65e58ec0fd2ef2b132213e75b768ffa0bf1 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Sun, 27 Nov 2016 17:18:12 -0800 Subject: [PATCH] Remove blocking-wait from completoin when caret is moved. --- .../Completion/Controller.Session.cs | 2 +- .../Controller.Session_FilterModel.cs | 62 +++++++++++++------ .../Controller_CaretPositionChanged.cs | 37 ++++------- ...ntroller_CommitUniqueCompletionListItem.cs | 1 - .../CSharpCompletionCommandHandlerTests.vb | 29 +++++++++ .../Completion/CompletionFilterReason.cs | 6 +- 6 files changed, 87 insertions(+), 50 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session.cs index c5b6fc25d71..cd015071241 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller.Session.cs @@ -14,7 +14,7 @@ internal partial class Session : Session b) || - filterState.Values.All(b => !b)) - { - filterState = null; - } - } - // 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 becaus the user typed more text and added more filter items. if (recheckCaretPosition && Controller.IsCaretOutsideAllItemBounds(model, caretPosition)) { return null; @@ -155,6 +144,7 @@ internal partial class Session } } + var filterItemState = ComputeFilterItemState(model); foreach (var currentItem in model.TotalItems) { // Check if something new has happened and there's a later on filter operation @@ -165,7 +155,7 @@ internal partial class Session return model; } - if (ItemIsFilteredOut(currentItem, filterState)) + if (ItemIsFilteredOut(currentItem, filterItemState)) { continue; } @@ -207,10 +197,28 @@ internal partial class Session } return HandleNormalFiltering( - model, document, filterReason, textSnapshot, + model, document, filterReason, caretPosition, helper, recentItems, filterText, filterResults); } + private static ImmutableDictionary ComputeFilterItemState(Model model) + { + var filterState = model.FilterState; + + // If all the filters are on, or all the filters are off then we don't actually + // need to filter. + if (filterState != null) + { + if (filterState.Values.All(b => b) || + filterState.Values.All(b => !b)) + { + filterState = null; + } + } + + return filterState; + } + private Boolean IsAfterDot(Model model, ITextSnapshot textSnapshot, Dictionary textSpanToText) { var span = model.OriginalList.Span; @@ -226,7 +234,7 @@ private Boolean IsAfterDot(Model model, ITextSnapshot textSnapshot, Dictionary recentItems, string filterText, @@ -255,7 +263,7 @@ private Boolean IsAfterDot(Model model, ITextSnapshot textSnapshot, Dictionary |blah -> !|blah + // We want the filter span non-empty because we still want completion in the following case: + // + // A a = new | + if (caretPosition == itemViewSpan.TextSpan.Start && itemViewSpan.TextSpan.Length > 0) + { + return false; + } + + // There was either filter text, or this was a preselect match. In either case, we // can hard select this. return true; diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs index efed6ebddf1..8b732be2a92 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs @@ -25,36 +25,23 @@ internal override void OnCaretPositionChanged(object sender, EventArgs args) // caret isn't within the bounds of the items, then we dismiss completion. var caretPoint = this.GetCaretPointInViewBuffer(); var model = sessionOpt.Computation.InitialUnfilteredModel; - if (model == null || - this.IsCaretOutsideAllItemBounds(model, caretPoint)) - { - // Completions hadn't even been computed yet or the caret is out of bounds. - // Just cancel everything we're doing. - this.StopModelComputation(); - return; - } - - // TODO(cyrusn): Find a way to allow the user to cancel out of this. - model = sessionOpt.WaitForModel(); if (model == null) { + // Completions hadn't even been computed yet. Just cancel everything we're doing + // and move to the Inactive state. + this.StopModelComputation(); return; } - if (model.SelectedItem != null && model.IsHardSelection) - { - // Switch to soft selection, if user moved caret to the start of a non-empty filter span. - // This prevents commiting if user types a commit character at this position later, but still has the list if user types filter character - // i.e. blah| -> |blah -> !|blah - // We want the filter span non-empty because we still want completion in the following case: - // A a = new | -> A a = new (| - - var currentSpan = model.GetViewBufferSpan(model.SelectedItem.Span).TextSpan; - if (caretPoint == currentSpan.Start && currentSpan.Length > 0) - { - sessionOpt.SetModelIsHardSelection(false); - } - } + // We're currently computing items. We'll need to make sure that the caret point + // 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); } 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 353d306626c..a65796fc1b0 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CommitUniqueCompletionListItem.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_CommitUniqueCompletionListItem.cs @@ -48,7 +48,6 @@ CommandState ICommandHandler.GetComma // with how that convoluted code worked. So I'm not maintaining that behavior here. If // we do want it through, it would be easy to get again simply by asking the model // computation to remove all filtering. - if (model.IsUnique) { // We had a unique item in the list. Commit it and dismiss this session. diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 05426321a1c..94ade0e8715 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -2570,5 +2570,34 @@ class C Await state.AssertSelectedCompletionItem("ConfigureAwait") End Using End Function + + + Public Async Function MovingCaretToStartSoftSelects() As Task + Using state = TestState.CreateCSharpTestState( + +using System; + +class C +{ + void M() + { + $$ + } +} + ) + + state.SendTypeChars("Conso") + Await state.WaitForAsynchronousOperationsAsync() + Await state.AssertSelectedCompletionItem(displayText:="Console", isHardSelected:=True) + For Each ch In "Conso" + state.SendLeftKey() + Next + + Await state.AssertSelectedCompletionItem(displayText:="Console", isHardSelected:=False) + + state.SendRightKey() + Await state.AssertSelectedCompletionItem(displayText:="Console", isHardSelected:=True) + End Using + End Function End Class End Namespace \ No newline at end of file diff --git a/src/Features/Core/Portable/Completion/CompletionFilterReason.cs b/src/Features/Core/Portable/Completion/CompletionFilterReason.cs index 208e51b5ce7..e16af4fa821 100644 --- a/src/Features/Core/Portable/Completion/CompletionFilterReason.cs +++ b/src/Features/Core/Portable/Completion/CompletionFilterReason.cs @@ -1,6 +1,5 @@ // 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 @@ -8,6 +7,7 @@ internal enum CompletionFilterReason Other, TypeChar, BackspaceOrDelete, - ItemFiltersChanged + ItemFiltersChanged, + CaretPositionChanged, } -} +} \ No newline at end of file -- GitLab