提交 afc5e65e 编写于 作者: C CyrusNajmabadi

Remove blocking-wait from completoin when caret is moved.

上级 8f062669
......@@ -14,7 +14,7 @@ internal partial class Session : Session<Controller, Model, ICompletionPresenter
// When we issue filter tasks, provide them with a (monotonically increasing) id. That
// way, when they run we can bail on computation if they've been superseded by another
// filter task.
// filter task.
private int _filterId;
#endregion
......
......@@ -98,20 +98,9 @@ internal partial class Session
return null;
}
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;
}
}
// 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<CompletionItemFilter, bool> 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<TextSpan, string> textSpanToText)
{
var span = model.OriginalList.Span;
......@@ -226,7 +234,7 @@ private Boolean IsAfterDot(Model model, ITextSnapshot textSnapshot, Dictionary<T
Model model,
Document document,
CompletionFilterReason filterReason,
ITextSnapshot textSnapshot,
SnapshotPoint caretPosition,
CompletionHelper helper,
ImmutableArray<string> recentItems,
string filterText,
......@@ -255,7 +263,7 @@ private Boolean IsAfterDot(Model model, ITextSnapshot textSnapshot, Dictionary<T
var bestOrFirstCompletionItem = bestCompletionItem ?? filterResults.First().CompletionItem;
var hardSelection = IsHardSelection(
model, bestOrFirstCompletionItem, textSnapshot, helper, filterReason);
model, bestOrFirstCompletionItem, caretPosition, helper, filterReason);
// Determine if we should consider this item 'unique' or not. A unique item
// will be automatically committed if the user hits the 'invoke completion'
......@@ -528,7 +536,7 @@ private static bool IsAllDigits(string filterText)
private bool IsHardSelection(
Model model,
CompletionItem bestFilterMatch,
ITextSnapshot textSnapshot,
SnapshotPoint caretPosition,
CompletionHelper completionHelper,
CompletionFilterReason reason)
{
......@@ -537,6 +545,8 @@ private static bool IsAllDigits(string filterText)
return false;
}
var textSnapshot = caretPosition.Snapshot;
// We don't have a builder and we have a best match. Normally this will be hard
// selected, except for a few cases. Specifically, if no filter text has been
// provided, and this is not a preselect match then we will soft select it. This
......@@ -548,8 +558,8 @@ private static bool IsAllDigits(string filterText)
//
// Completion will comes up after = with 'integer' selected (Because of MRU). We do
// not want 'space' to commit this.
var viewSpan = model.GetViewBufferSpan(bestFilterMatch.Span);
var fullFilterText = model.GetCurrentTextInSnapshot(viewSpan, textSnapshot, endPoint: null);
var itemViewSpan = model.GetViewBufferSpan(bestFilterMatch.Span);
var fullFilterText = model.GetCurrentTextInSnapshot(itemViewSpan, textSnapshot, endPoint: null);
var trigger = model.Trigger;
var shouldSoftSelect = ShouldSoftSelectItem(bestFilterMatch, fullFilterText, trigger);
......@@ -565,6 +575,18 @@ private static bool IsAllDigits(string filterText)
return false;
}
// 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 |
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;
......
......@@ -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)
......
......@@ -48,7 +48,6 @@ CommandState ICommandHandler<CommitUniqueCompletionListItemCommandArgs>.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.
......
......@@ -2570,5 +2570,34 @@ class C
Await state.AssertSelectedCompletionItem("ConfigureAwait")
End Using
End Function
<WpfFact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function MovingCaretToStartSoftSelects() As Task
Using state = TestState.CreateCSharpTestState(
<Document>
using System;
class C
{
void M()
{
$$
}
}
</Document>)
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
// 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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册