Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vs-editor-api.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSandy Armstrong <sandy@xamarin.com>2019-08-28 23:09:45 +0300
committerSandy Armstrong <sandy@xamarin.com>2019-08-28 23:09:45 +0300
commitcc54ccf435221210660b51f0f9ca49c0c99407fa (patch)
tree86a4b1cf2f5450f3964dc47362f4064e46bb1059 /src
parenta566dffd4c3899a71da7c72071c448500ba8cb9b (diff)
Sync with vs-editor-core@21efac1c7
Diffstat (limited to 'src')
-rw-r--r--src/Editor/Core/Def/BaseUtility/IGuardedOperations.cs4
-rw-r--r--src/Editor/Core/Def/BaseUtility/IGuardedOperations2.cs25
-rw-r--r--src/Editor/Core/Def/BaseUtility/IGuardedOperationsInternal.cs49
-rw-r--r--src/Editor/Core/Def/ContentType/StandardContentTypeNames.cs36
-rw-r--r--src/Editor/Core/Def/CoreUtility.csproj1
-rw-r--r--src/Editor/Core/Def/CoreUtilityAssemblyInfo.cs5
-rw-r--r--src/Editor/Core/Def/PooledObjects/ArrayBuilder.Enumerator.cs (renamed from src/Editor/Text/Util/TextDataUtil/PooledObjects/ArrayBuilder.Enumerator.cs)6
-rw-r--r--src/Editor/Core/Def/PooledObjects/ArrayBuilder.cs (renamed from src/Editor/Text/Util/TextDataUtil/PooledObjects/ArrayBuilder.cs)78
-rw-r--r--src/Editor/Core/Def/PooledObjects/ObjectPool`1.cs (renamed from src/Editor/Text/Util/TextDataUtil/PooledObjects/ObjectPool`1.cs)17
-rw-r--r--src/Editor/Core/Def/PooledObjects/PooledDictionary.cs (renamed from src/Editor/Text/Util/TextDataUtil/PooledObjects/PooledDictionary.cs)13
-rw-r--r--src/Editor/Core/Def/PooledObjects/PooledHashSet.cs (renamed from src/Editor/Text/Util/TextDataUtil/PooledObjects/PooledHashSet.cs)13
-rw-r--r--src/Editor/Core/Def/PooledObjects/PooledStopwatch.cs38
-rw-r--r--src/Editor/Core/Def/PooledObjects/PooledStringBuilder.cs (renamed from src/Editor/Text/Util/TextDataUtil/PooledObjects/PooledStringBuilder.cs)18
-rw-r--r--src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionContext.cs70
-rw-r--r--src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionExpander.cs40
-rw-r--r--src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionFilter.cs6
-rw-r--r--src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionItemWithHighlight.cs7
-rw-r--r--src/Editor/Language/Def/Language/AsyncCompletion/Data/ComputedCompletionItems.cs42
-rw-r--r--src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSession.cs4
-rw-r--r--src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSessionOperations.cs20
-rw-r--r--src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSource.cs9
-rw-r--r--src/Editor/Language/Def/Language/AsyncCompletion/IAsyncExpandingCompletionSource.cs40
-rw-r--r--src/Editor/Language/Def/Language/AsyncCompletion/PredefinedCompletionNames.cs10
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionBroker.cs19
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionSession.cs519
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/CompletionAvailabilityUtility.cs76
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/CompletionCommandHandlers.cs124
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/CompletionModel.cs47
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/CompletionSourceConnectionResult.cs11
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/CompletionTelemetry.cs153
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/CompletionUtilities.cs45
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/DefaultCompletionItemManager.cs50
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/DeferredBlockingOperation.cs44
-rw-r--r--src/Editor/Language/Impl/Language/AsyncCompletion/UseAsyncCompletionOptionDefinition.cs30
-rw-r--r--src/Editor/Text/Def/Internal/TextUI/IBraceCompletionManager.cs5
-rw-r--r--src/Editor/Text/Def/TextData/Model/IExtensionErrorHandler.cs2
-rw-r--r--src/Editor/Text/Def/TextData/Model/IExtensionErrorHandler2.cs25
-rw-r--r--src/Editor/Text/Def/TextLogic/AssemblyInfo.cs3
-rw-r--r--src/Editor/Text/Def/TextLogic/EditorOptions/DefaultOptions.cs116
-rw-r--r--src/Editor/Text/Def/TextLogic/PatternMatching/IPatternMatcher.cs6
-rw-r--r--src/Editor/Text/Def/TextLogic/PatternMatching/IPatternMatcherFactory2.cs56
-rw-r--r--src/Editor/Text/Impl/BraceCompletion/BraceCompletionManager.cs40
-rw-r--r--src/Editor/Text/Impl/PatternMatching/AllLowerCamelCaseMatcher.cs2
-rw-r--r--src/Editor/Text/Impl/PatternMatching/CamelCaseResult.cs2
-rw-r--r--src/Editor/Text/Impl/PatternMatching/ContainerPatternMatcher.cs9
-rw-r--r--src/Editor/Text/Impl/PatternMatching/PatternMatchExtensions.cs7
-rw-r--r--src/Editor/Text/Impl/PatternMatching/PatternMatcher.cs51
-rw-r--r--src/Editor/Text/Impl/PatternMatching/PatternMatcherFactory.cs17
-rw-r--r--src/Editor/Text/Impl/PatternMatching/SimplePatternMatcher.cs9
-rw-r--r--src/Editor/Text/Impl/PatternMatching/StringBreaker.cs2
-rw-r--r--src/Editor/Text/Util/TextDataUtil/GuardedOperations.cs124
-rw-r--r--src/Editor/Text/Util/TextUIUtil/DiagnosticLogger.cs36
52 files changed, 1801 insertions, 380 deletions
diff --git a/src/Editor/Core/Def/BaseUtility/IGuardedOperations.cs b/src/Editor/Core/Def/BaseUtility/IGuardedOperations.cs
index 6180a0e..b1c7ef4 100644
--- a/src/Editor/Core/Def/BaseUtility/IGuardedOperations.cs
+++ b/src/Editor/Core/Def/BaseUtility/IGuardedOperations.cs
@@ -132,7 +132,9 @@ namespace Microsoft.VisualStudio.Utilities
/// <param name="errorSource">Reference to the extension object or event handler that threw the exception</param>
/// <param name="e">Exception to handle</param>
/// <remarks>This class supports the Visual Studio
- /// infrastructure and in general is not intended to be used directly from your code.</remarks>
+ /// infrastructure and in general is not intended to be used directly from your code.
+ /// In Visual Studio, this method logs the exception to ActivityLogs and the telemetry, and displays an error message to the user if possible.
+ /// This method can be invoked from any thread.</remarks>
void HandleException(object errorSource, Exception e);
/// <summary>
diff --git a/src/Editor/Core/Def/BaseUtility/IGuardedOperations2.cs b/src/Editor/Core/Def/BaseUtility/IGuardedOperations2.cs
new file mode 100644
index 0000000..50ddf5d
--- /dev/null
+++ b/src/Editor/Core/Def/BaseUtility/IGuardedOperations2.cs
@@ -0,0 +1,25 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+
+using System;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Operations that guard calls to extensions code, track performance and log errors.
+ /// </summary>
+ /// <remarks>This class supports the Visual Studio
+ /// infrastructure and in general is not intended to be used directly from your code.</remarks>
+ public interface IGuardedOperations2 : IGuardedOperations
+ {
+ /// <summary>
+ /// Logs an exception silently, without notifying the user.
+ /// </summary>
+ /// <param name="errorSource">Reference to the extension object or event handler that threw the exception</param>
+ /// <param name="e">Exception to log</param>
+ /// <remarks>This method can be invoked from any thread.</remarks>
+ void LogException(object errorSource, Exception e);
+ }
+}
diff --git a/src/Editor/Core/Def/BaseUtility/IGuardedOperationsInternal.cs b/src/Editor/Core/Def/BaseUtility/IGuardedOperationsInternal.cs
new file mode 100644
index 0000000..578b292
--- /dev/null
+++ b/src/Editor/Core/Def/BaseUtility/IGuardedOperationsInternal.cs
@@ -0,0 +1,49 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+
+using System;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Operations that guard calls to extensions code, track performance and log errors.
+ /// This interface contains method signatures which will be moved to <see cref="IGuardedOperations" />
+ /// in future releases of Visual Studio. Microsoft reserves the right to modify this interface.
+ /// </summary>
+ /// <remarks>This class supports the Visual Studio
+ /// infrastructure and in general is not intended to be used directly from your code.</remarks>
+ internal interface IGuardedOperationsInternal : IGuardedOperations
+ {
+ /// <summary>
+ /// Makes a guarded call to an extension point.
+ /// </summary>
+ /// <param name="errorSource">Reference to the extension object or event handler that may throw an exception.
+ /// Used for tracking performance and errors.</param>
+ /// <param name="call">Delegate that calls the extension point.</param>
+ /// <param name="valueOnThrow">The value returned if the delegate call failed.</param>
+ /// <param name="exceptionToIgnore">Determines which exceptions should be ignored. This predicate is evaluated first</param>
+ /// <param name="exceptionToHandle">Determines which exceptions should be logged. This predicate is evaluated second.
+ /// If both predicates return <c>false</c>, then the exceptions remains unhandled and may be caught in the calling code.</param>
+ /// <returns>The result of the <paramref name="call"/> or <paramref name="valueOnThrow"/>.</returns>
+ /// <remarks>This class supports the Visual Studio
+ /// infrastructure and in general is not intended to be used directly from your code.</remarks>
+ /// <example>
+ /// The following code will synchronously call <c>extension.GetData()</c>.
+ /// exceptionToIgnore predicate prevents logging the <c>OperationCanceledException</c> when relevant cancellation token is canceled.
+ /// exceptionToHandle predicate causes all other exceptions to be logged.
+ /// If both predicates returned false, the exception would remain unhandled and may be caught in the calling code.
+ /// <code>
+ /// var result = GuardedOperations.CallExtensionPoint(
+ /// errorSource: extension,
+ /// call: () => extension.GetData(token),
+ /// valueOnThrow: string.Empty,
+ /// exceptionToIgnore: (e) => e is OperationCanceledException && token.IsCancellationRequested,
+ /// exceptionToHandle: (e) => true);
+ /// </code>
+ /// </example>
+ T CallExtensionPoint<T>(object errorSource, Func<T> call, T valueOnThrow, Predicate<Exception> exceptionToIgnore, Predicate<Exception> exceptionToHandle);
+
+ }
+}
diff --git a/src/Editor/Core/Def/ContentType/StandardContentTypeNames.cs b/src/Editor/Core/Def/ContentType/StandardContentTypeNames.cs
new file mode 100644
index 0000000..df2fbba
--- /dev/null
+++ b/src/Editor/Core/Def/ContentType/StandardContentTypeNames.cs
@@ -0,0 +1,36 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+namespace Microsoft.VisualStudio.Utilities
+{
+ public static class StandardContentTypeNames
+ {
+ /// <summary>
+ /// Base content type of all contents types except for <see cref="Inert"/>.
+ /// </summary>
+ public const string Any = "any";
+
+ /// <summary>
+ /// Base content type of any content type use for a document. Note that <see cref="Projection"/> does not derive from <see cref="Text"/>.
+ /// </summary>
+ public const string Text = "text";
+
+ /// <summary>
+ /// Base content type of any document containing code. Derives from <see cref="Text"/>.
+ /// </summary>
+ public const string Code = "code";
+
+ /// <summary>
+ /// Base content type for a projection of a document that contains a mix of distinct content types (e.g. a .aspx file containing
+ /// html and embedded c#).
+ /// </summary>
+ public const string Projection = "projection";
+
+ /// <summary>
+ /// A content type for which no associated artifacts are automatically created.
+ /// </summary>
+ public const string Inert = "inert";
+ }
+}
+
diff --git a/src/Editor/Core/Def/CoreUtility.csproj b/src/Editor/Core/Def/CoreUtility.csproj
index e89b22f..9920ca4 100644
--- a/src/Editor/Core/Def/CoreUtility.csproj
+++ b/src/Editor/Core/Def/CoreUtility.csproj
@@ -18,5 +18,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Threading" />
+ <PackageReference Include="System.Collections.Immutable" />
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/src/Editor/Core/Def/CoreUtilityAssemblyInfo.cs b/src/Editor/Core/Def/CoreUtilityAssemblyInfo.cs
index a47d690..446fe0d 100644
--- a/src/Editor/Core/Def/CoreUtilityAssemblyInfo.cs
+++ b/src/Editor/Core/Def/CoreUtilityAssemblyInfo.cs
@@ -4,9 +4,14 @@
//
using System.Reflection;
using System.Runtime.ConstrainedExecution;
+using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
+[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Language.Implementation, PublicKey=" + ThisAssembly.PublicKey)]
+[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Platform.VSEditor, PublicKey=" + ThisAssembly.PublicKey)]
+[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Text.Data.Utilities, PublicKey=" + ThisAssembly.PublicKey)]
+
//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
diff --git a/src/Editor/Text/Util/TextDataUtil/PooledObjects/ArrayBuilder.Enumerator.cs b/src/Editor/Core/Def/PooledObjects/ArrayBuilder.Enumerator.cs
index dc61741..cd70903 100644
--- a/src/Editor/Text/Util/TextDataUtil/PooledObjects/ArrayBuilder.Enumerator.cs
+++ b/src/Editor/Core/Def/PooledObjects/ArrayBuilder.Enumerator.cs
@@ -2,14 +2,14 @@
using System.Collections.Generic;
-namespace Microsoft.VisualStudio.Text.Utilities
+namespace Microsoft.VisualStudio.Utilities
{
- internal partial class ArrayBuilder<T>
+ public partial class ArrayBuilder<T>
{
/// <summary>
/// struct enumerator used in foreach.
/// </summary>
- internal struct Enumerator : IEnumerator<T>
+ public struct Enumerator : IEnumerator<T>
{
private readonly ArrayBuilder<T> _builder;
private int _index;
diff --git a/src/Editor/Text/Util/TextDataUtil/PooledObjects/ArrayBuilder.cs b/src/Editor/Core/Def/PooledObjects/ArrayBuilder.cs
index 7e205b3..02ce7d4 100644
--- a/src/Editor/Text/Util/TextDataUtil/PooledObjects/ArrayBuilder.cs
+++ b/src/Editor/Core/Def/PooledObjects/ArrayBuilder.cs
@@ -5,11 +5,11 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
-namespace Microsoft.VisualStudio.Text.Utilities
+namespace Microsoft.VisualStudio.Utilities
{
[DebuggerDisplay("Count = {Count,nq}")]
[DebuggerTypeProxy(typeof(ArrayBuilder<>.DebuggerProxy))]
- internal sealed partial class ArrayBuilder<T> : IReadOnlyCollection<T>, IReadOnlyList<T>
+ public sealed partial class ArrayBuilder<T> : IReadOnlyCollection<T>, IReadOnlyList<T>
{
#region DebuggerProxy
@@ -28,7 +28,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
get
{
var result = new T[_builder.Count];
- for (int i = 0; i < result.Length; i++)
+ for (var i = 0; i < result.Length; i++)
{
result[i] = _builder[i];
}
@@ -49,12 +49,12 @@ namespace Microsoft.VisualStudio.Text.Utilities
_builder = ImmutableArray.CreateBuilder<T>(size);
}
- public ArrayBuilder() :
- this(8)
+ public ArrayBuilder()
+ : this(8)
{ }
- private ArrayBuilder(ObjectPool<ArrayBuilder<T>> pool) :
- this()
+ private ArrayBuilder(ObjectPool<ArrayBuilder<T>> pool)
+ : this()
{
_pool = pool;
}
@@ -100,7 +100,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
while (index > _builder.Count)
{
- _builder.Add(default(T));
+ _builder.Add(default);
}
if (index == _builder.Count)
@@ -164,8 +164,8 @@ namespace Microsoft.VisualStudio.Text.Utilities
public int FindIndex(int startIndex, int count, Predicate<T> match)
{
- int endIndex = startIndex + count;
- for (int i = startIndex; i < endIndex; i++)
+ var endIndex = startIndex + count;
+ for (var i = startIndex; i < endIndex; i++)
{
if (match(_builder[i]))
{
@@ -176,6 +176,11 @@ namespace Microsoft.VisualStudio.Text.Utilities
return -1;
}
+ public bool Remove(T element)
+ {
+ return _builder.Remove(element);
+ }
+
public void RemoveAt(int index)
{
_builder.RemoveAt(index);
@@ -241,7 +246,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (Count == 0)
{
- return default(ImmutableArray<T>);
+ return default;
}
return this.ToImmutable();
@@ -272,7 +277,20 @@ namespace Microsoft.VisualStudio.Text.Utilities
/// </summary>
public ImmutableArray<T> ToImmutableAndFree()
{
- var result = this.ToImmutable();
+ ImmutableArray<T> result;
+ if (Count == 0)
+ {
+ result = ImmutableArray<T>.Empty;
+ }
+ else if (_builder.Capacity == Count)
+ {
+ result = _builder.MoveToImmutable();
+ }
+ else
+ {
+ result = ToImmutable();
+ }
+
this.Free();
return result;
}
@@ -302,7 +320,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
// while the chance that we will need their size is diminishingly small.
// It makes sense to constrain the size to some "not too small" number.
// Overall perf does not seem to be very sensitive to this number, so I picked 128 as a limit.
- if (this.Count < 128)
+ if (_builder.Capacity < 128)
{
if (this.Count != 0)
{
@@ -341,7 +359,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
var builder = GetInstance();
builder.EnsureCapacity(capacity);
- for (int i = 0; i < capacity; i++)
+ for (var i = 0; i < capacity; i++)
{
builder.Add(fillWithValue);
}
@@ -383,7 +401,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
if (this.Count == 1)
{
var dictionary1 = new Dictionary<K, ImmutableArray<T>>(1, comparer);
- T value = this[0];
+ var value = this[0];
dictionary1.Add(keySelector(value), ImmutableArray.Create(value));
return dictionary1;
}
@@ -396,7 +414,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
// bucketize
// prevent reallocation. it may not have 'count' entries, but it won't have more.
var accumulator = new Dictionary<K, ArrayBuilder<T>>(Count, comparer);
- for (int i = 0; i < Count; i++)
+ for (var i = 0; i < Count; i++)
{
var item = this[i];
var key = keySelector(item);
@@ -482,7 +500,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public void AddMany(T item, int count)
{
- for (int i = 0; i < count; i++)
+ for (var i = 0; i < count; i++)
{
Add(item);
}
@@ -492,8 +510,8 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
var set = PooledHashSet<T>.GetInstance();
- int j = 0;
- for (int i = 0; i < Count; i++)
+ var j = 0;
+ for (var i = 0; i < Count; i++)
{
if (set.Add(this[i]))
{
@@ -506,6 +524,28 @@ namespace Microsoft.VisualStudio.Text.Utilities
set.Free();
}
+ public void SortAndRemoveDuplicates(IComparer<T> comparer)
+ {
+ if (Count <= 1)
+ {
+ return;
+ }
+
+ Sort(comparer);
+
+ int j = 0;
+ for (int i = 1; i < Count; i++)
+ {
+ if (comparer.Compare(this[j], this[i]) < 0)
+ {
+ j++;
+ this[j] = this[i];
+ }
+ }
+
+ Clip(j + 1);
+ }
+
public ImmutableArray<S> SelectDistinct<S>(Func<T, S> selector)
{
var result = ArrayBuilder<S>.GetInstance(Count);
diff --git a/src/Editor/Text/Util/TextDataUtil/PooledObjects/ObjectPool`1.cs b/src/Editor/Core/Def/PooledObjects/ObjectPool`1.cs
index 8af4993..43cbb17 100644
--- a/src/Editor/Text/Util/TextDataUtil/PooledObjects/ObjectPool`1.cs
+++ b/src/Editor/Core/Def/PooledObjects/ObjectPool`1.cs
@@ -18,7 +18,7 @@ using System.Threading;
using System.Runtime.CompilerServices;
#endif
-namespace Microsoft.VisualStudio.Text.Utilities
+namespace Microsoft.VisualStudio.Utilities
{
/// <summary>
/// Generic implementation of object pooling pattern with predefined pool size limit. The main
@@ -37,7 +37,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
/// Rationale:
/// If there is no intent for reusing the object, do not use pool - just use "new".
/// </summary>
- internal class ObjectPool<T> where T : class
+ public class ObjectPool<T> where T : class
{
[DebuggerDisplay("{Value,nq}")]
private struct Element
@@ -133,7 +133,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
// Note that the initial read is optimistically not synchronized. That is intentional.
// We will interlock only when we have a candidate. in a worst case we may miss some
// recently returned objects. Not a big deal.
- T inst = _firstItem;
+ var inst = _firstItem;
if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst))
{
inst = AllocateSlow();
@@ -155,12 +155,12 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
var items = _items;
- for (int i = 0; i < items.Length; i++)
+ for (var i = 0; i < items.Length; i++)
{
// Note that the initial read is optimistically not synchronized. That is intentional.
// We will interlock only when we have a candidate. in a worst case we may miss some
// recently returned objects. Not a big deal.
- T inst = items[i].Value;
+ var inst = items[i].Value;
if (inst != null)
{
if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst))
@@ -202,7 +202,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
private void FreeSlow(T obj)
{
var items = _items;
- for (int i = 0; i < items.Length; i++)
+ for (var i = 0; i < items.Length; i++)
{
if (items[i].Value == null)
{
@@ -224,9 +224,8 @@ namespace Microsoft.VisualStudio.Text.Utilities
/// return a larger array to the pool than was originally allocated.
/// </summary>
[Conditional("DEBUG")]
-#pragma warning disable CA1822 // Mark members as static
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822")]
internal void ForgetTrackedObject(T old, T replacement = null)
-#pragma warning restore CA1822 // Mark members as static
{
#if DETECT_LEAKS
LeakTracker tracker;
@@ -266,7 +265,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
Debug.Assert(_firstItem != obj, "freeing twice?");
var items = _items;
- for (int i = 0; i < items.Length; i++)
+ for (var i = 0; i < items.Length; i++)
{
var value = items[i].Value;
if (value == null)
diff --git a/src/Editor/Text/Util/TextDataUtil/PooledObjects/PooledDictionary.cs b/src/Editor/Core/Def/PooledObjects/PooledDictionary.cs
index a98addd..c630e0a 100644
--- a/src/Editor/Text/Util/TextDataUtil/PooledObjects/PooledDictionary.cs
+++ b/src/Editor/Core/Def/PooledObjects/PooledDictionary.cs
@@ -4,15 +4,16 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
-namespace Microsoft.VisualStudio.Text.Utilities
+namespace Microsoft.VisualStudio.Utilities
{
// Dictionary that can be recycled via an object pool
// NOTE: these dictionaries always have the default comparer.
- internal class PooledDictionary<K, V> : Dictionary<K, V>
+ public sealed class PooledDictionary<K, V> : Dictionary<K, V>
{
private readonly ObjectPool<PooledDictionary<K, V>> _pool;
- private PooledDictionary(ObjectPool<PooledDictionary<K, V>> pool)
+ private PooledDictionary(ObjectPool<PooledDictionary<K, V>> pool, IEqualityComparer<K> keyComparer)
+ : base(keyComparer)
{
_pool = pool;
}
@@ -31,13 +32,13 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
// global pool
- private static readonly ObjectPool<PooledDictionary<K, V>> s_poolInstance = CreatePool();
+ private static readonly ObjectPool<PooledDictionary<K, V>> s_poolInstance = CreatePool(EqualityComparer<K>.Default);
// if someone needs to create a pool;
- public static ObjectPool<PooledDictionary<K, V>> CreatePool()
+ public static ObjectPool<PooledDictionary<K, V>> CreatePool(IEqualityComparer<K> keyComparer)
{
ObjectPool<PooledDictionary<K, V>> pool = null;
- pool = new ObjectPool<PooledDictionary<K, V>>(() => new PooledDictionary<K, V>(pool), 128);
+ pool = new ObjectPool<PooledDictionary<K, V>>(() => new PooledDictionary<K, V>(pool, keyComparer), 128);
return pool;
}
diff --git a/src/Editor/Text/Util/TextDataUtil/PooledObjects/PooledHashSet.cs b/src/Editor/Core/Def/PooledObjects/PooledHashSet.cs
index 2969170..4e09136 100644
--- a/src/Editor/Text/Util/TextDataUtil/PooledObjects/PooledHashSet.cs
+++ b/src/Editor/Core/Def/PooledObjects/PooledHashSet.cs
@@ -3,15 +3,16 @@
using System.Collections.Generic;
using System.Diagnostics;
-namespace Microsoft.VisualStudio.Text.Utilities
+namespace Microsoft.VisualStudio.Utilities
{
// HashSet that can be recycled via an object pool
// NOTE: these HashSets always have the default comparer.
- internal class PooledHashSet<T> : HashSet<T>
+ public sealed class PooledHashSet<T> : HashSet<T>
{
private readonly ObjectPool<PooledHashSet<T>> _pool;
- private PooledHashSet(ObjectPool<PooledHashSet<T>> pool)
+ private PooledHashSet(ObjectPool<PooledHashSet<T>> pool, IEqualityComparer<T> equalityComparer) :
+ base(equalityComparer)
{
_pool = pool;
}
@@ -23,13 +24,13 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
// global pool
- private static readonly ObjectPool<PooledHashSet<T>> s_poolInstance = CreatePool();
+ private static readonly ObjectPool<PooledHashSet<T>> s_poolInstance = CreatePool(EqualityComparer<T>.Default);
// if someone needs to create a pool;
- public static ObjectPool<PooledHashSet<T>> CreatePool()
+ public static ObjectPool<PooledHashSet<T>> CreatePool(IEqualityComparer<T> equalityComparer)
{
ObjectPool<PooledHashSet<T>> pool = null;
- pool = new ObjectPool<PooledHashSet<T>>(() => new PooledHashSet<T>(pool), 128);
+ pool = new ObjectPool<PooledHashSet<T>>(() => new PooledHashSet<T>(pool, equalityComparer), 128);
return pool;
}
diff --git a/src/Editor/Core/Def/PooledObjects/PooledStopwatch.cs b/src/Editor/Core/Def/PooledObjects/PooledStopwatch.cs
new file mode 100644
index 0000000..d81ca9b
--- /dev/null
+++ b/src/Editor/Core/Def/PooledObjects/PooledStopwatch.cs
@@ -0,0 +1,38 @@
+// 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.Diagnostics;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ public class PooledStopwatch : Stopwatch
+ {
+ private static readonly ObjectPool<PooledStopwatch> s_poolInstance = CreatePool();
+
+ private readonly ObjectPool<PooledStopwatch> _pool;
+
+ private PooledStopwatch(ObjectPool<PooledStopwatch> pool)
+ {
+ _pool = pool;
+ }
+
+ public void Free()
+ {
+ Reset();
+ _pool?.Free(this);
+ }
+
+ public static ObjectPool<PooledStopwatch> CreatePool()
+ {
+ ObjectPool<PooledStopwatch> pool = null;
+ pool = new ObjectPool<PooledStopwatch>(() => new PooledStopwatch(pool), 128);
+ return pool;
+ }
+
+ public static PooledStopwatch StartInstance()
+ {
+ var instance = s_poolInstance.Allocate();
+ instance.Restart();
+ return instance;
+ }
+ }
+}
diff --git a/src/Editor/Text/Util/TextDataUtil/PooledObjects/PooledStringBuilder.cs b/src/Editor/Core/Def/PooledObjects/PooledStringBuilder.cs
index b618095..abd3867 100644
--- a/src/Editor/Text/Util/TextDataUtil/PooledObjects/PooledStringBuilder.cs
+++ b/src/Editor/Core/Def/PooledObjects/PooledStringBuilder.cs
@@ -3,7 +3,7 @@
using System.Diagnostics;
using System.Text;
-namespace Microsoft.VisualStudio.Text.Utilities
+namespace Microsoft.VisualStudio.Utilities
{
/// <summary>
/// The usage is:
@@ -13,8 +13,9 @@ namespace Microsoft.VisualStudio.Text.Utilities
/// ... sb.ToString() ...
/// inst.Free();
/// </summary>
- internal class PooledStringBuilder
+ public class PooledStringBuilder
{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104")]
public readonly StringBuilder Builder = new StringBuilder();
private readonly ObjectPool<PooledStringBuilder> _pool;
@@ -53,7 +54,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public string ToStringAndFree()
{
- string result = this.Builder.ToString();
+ var result = this.Builder.ToString();
this.Free();
return result;
@@ -61,7 +62,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public string ToStringAndFree(int startIndex, int length)
{
- string result = this.Builder.ToString(startIndex, length);
+ var result = this.Builder.ToString(startIndex, length);
this.Free();
return result;
@@ -71,10 +72,15 @@ namespace Microsoft.VisualStudio.Text.Utilities
private static readonly ObjectPool<PooledStringBuilder> s_poolInstance = CreatePool();
// if someone needs to create a private pool;
- public static ObjectPool<PooledStringBuilder> CreatePool()
+ /// <summary>
+ /// If someone need to create a private pool
+ /// </summary>
+ /// <param name="size">The size of the pool.</param>
+ /// <returns></returns>
+ public static ObjectPool<PooledStringBuilder> CreatePool(int size = 32)
{
ObjectPool<PooledStringBuilder> pool = null;
- pool = new ObjectPool<PooledStringBuilder>(() => new PooledStringBuilder(pool), 32);
+ pool = new ObjectPool<PooledStringBuilder>(() => new PooledStringBuilder(pool), size);
return pool;
}
diff --git a/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionContext.cs b/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionContext.cs
index aa5d181..7e75d4e 100644
--- a/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionContext.cs
+++ b/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionContext.cs
@@ -1,8 +1,6 @@
using System;
-using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
-using Microsoft.VisualStudio.Text;
namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
{
@@ -16,7 +14,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
/// <summary>
/// Empty completion context, when <see cref="IAsyncCompletionSource"/> offers no items pertinent to given location.
/// </summary>
- public static CompletionContext Empty { get; } = new CompletionContext(ImmutableArray<CompletionItem>.Empty);
+ public static CompletionContext Empty { get; } = new CompletionContext(ImmutableArray<CompletionItem>.Empty, ImmutableArray<CompletionFilterWithState>.Empty);
/// <summary>
/// Set of completion items available at a location
@@ -24,6 +22,22 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
public ImmutableArray<CompletionItem> Items { get; }
/// <summary>
+ /// <para>
+ /// Set of completion filters available for this session.
+ /// Each filter's <see cref="CompletionFilterWithState.IsSelected"/> property is used to determine initial selection.
+ /// The <see cref="CompletionFilterWithState.IsAvailable"/> property is ignored.
+ /// </para>
+ /// <para>
+ /// Typically, this is used to select <see cref="CompletionExpander"/>s that correspond to provided <see cref="CompletionItem"/>s,
+ /// in scenarios when the completion source provides expanded items by default.
+ /// </para>
+ /// </summary>
+ /// <remarks>
+ /// When the value is uninitialized, then <see cref="Items"/> need to be enumerated to find the filters.
+ /// </remarks>
+ public ImmutableArray<CompletionFilterWithState> Filters { get; }
+
+ /// <summary>
/// Recommends the initial selection method for the completion list.
/// When <see cref="SuggestionItemOptions"/> is defined, "soft selection" will be used without a need to set this property.
/// </summary>
@@ -37,12 +51,32 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
public SuggestionItemOptions SuggestionItemOptions { get; }
/// <summary>
- /// Constructs <see cref="CompletionContext"/> with specified <see cref="CompletionItem"/>s,
+ /// [Deprecated] Constructs <see cref="CompletionContext"/> with specified <see cref="CompletionItem"/>s,
/// with recommendation to not use suggestion mode and to use use regular selection.
+ /// Note: completion will iterate through all items to determine filters.
+ /// For better performance, use the overload which accepts <see cref="ImmutableArray{CompletionFilterWithState}"/>
/// </summary>
/// <param name="items">Available completion items. If none are available, use <c>CompletionContext.Default</c></param>
public CompletionContext(ImmutableArray<CompletionItem> items)
- : this(items, suggestionItemOptions: null, selectionHint: InitialSelectionHint.RegularSelection)
+ : this(items,
+ suggestionItemOptions: null,
+ selectionHint: InitialSelectionHint.RegularSelection,
+ filters: default)
+ {
+ }
+
+ /// <summary>
+ /// Constructs <see cref="CompletionContext"/> with specified <see cref="CompletionItem"/>s and <see cref="CompletionFilterWithState"/>s
+ /// with recommendation to not use suggestion mode and to use use regular selection.
+ /// </summary>
+ /// <param name="items">Available completion items. If none are available, use <c>CompletionContext.Default</c></param>
+ /// <param name="filters">Available completion filters. Each filter's <see cref="CompletionFilterWithState.IsSelected"/> property is used to determine initial selection.
+ /// The <see cref="CompletionFilterWithState.IsAvailable"/> property is ignored.</param>
+ public CompletionContext(ImmutableArray<CompletionItem> items, ImmutableArray<CompletionFilterWithState> filters)
+ : this(items,
+ suggestionItemOptions: null,
+ selectionHint: InitialSelectionHint.RegularSelection,
+ filters: filters)
{
}
@@ -55,7 +89,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
public CompletionContext(
ImmutableArray<CompletionItem> items,
SuggestionItemOptions suggestionItemOptions)
- : this(items, suggestionItemOptions, InitialSelectionHint.RegularSelection)
+ : this(items,
+ suggestionItemOptions,
+ selectionHint: InitialSelectionHint.RegularSelection,
+ filters: default)
{
}
@@ -70,12 +107,33 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
ImmutableArray<CompletionItem> items,
SuggestionItemOptions suggestionItemOptions,
InitialSelectionHint selectionHint)
+ : this (items,
+ suggestionItemOptions,
+ selectionHint,
+ filters: default)
+ { }
+
+ /// <summary>
+ /// Constructs <see cref="CompletionContext"/> with specified <see cref="CompletionItem"/>s,
+ /// with recommendation to use suggestion mode item and to use a specific selection mode.
+ /// </summary>
+ /// <param name="items">Available completion items</param>
+ /// <param name="suggestionItemOptions">Suggestion mode options, or null to not use suggestion mode. Default is <c>null</c></param>
+ /// <param name="selectionHint">Recommended selection mode. Suggestion mode automatically sets soft selection Default is <c>InitialSelectionHint.RegularSelection</c></param>
+ /// <param name="filters">Available completion filters. Each filter's <see cref="CompletionFilterWithState.IsSelected"/> property is used to determine initial selection.
+ /// The <see cref="CompletionFilterWithState.IsAvailable"/> property is ignored.</param>
+ public CompletionContext(
+ ImmutableArray<CompletionItem> items,
+ SuggestionItemOptions suggestionItemOptions,
+ InitialSelectionHint selectionHint,
+ ImmutableArray<CompletionFilterWithState> filters)
{
if (items.IsDefault)
throw new ArgumentException("Array must be initialized", nameof(items));
Items = items;
SelectionHint = selectionHint;
SuggestionItemOptions = suggestionItemOptions;
+ Filters = filters;
}
}
}
diff --git a/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionExpander.cs b/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionExpander.cs
new file mode 100644
index 0000000..cbb9533
--- /dev/null
+++ b/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionExpander.cs
@@ -0,0 +1,40 @@
+using System.Diagnostics;
+using Microsoft.VisualStudio.Text.Adornments;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Identifies an expander that adds <see cref="CompletionItem"/>s that reference it to the list of completions.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Expander apperars in the UI alongside <see cref="CompletionFilter"/>s, but behaves differently:
+ /// When <see cref="CompletionFilter"/> is selected, then <see cref="CompletionItem"/>s that don't reference it are hidden
+ /// When <see cref="CompletionExpander"/> is selected, then <see cref="CompletionItem"/>s that reference it are visible
+ /// When no <see cref="CompletionFilter"/>s are selected, then all <see cref="CompletionItem"/>s that don't reference an expander are visible
+ /// When no <see cref="CompletionExpander"/>s are selected, then all <see cref="CompletionItem"/>s reference an expander are hidden
+ /// </para>
+ /// <para>
+ /// These instances should be singletons. All <see cref="CompletionItem"/>s that should be filtered
+ /// using the same expander button must use the same reference to the instance of <see cref="CompletionExpander"/>.
+ /// </para>
+ /// </remarks>
+ /// <example>
+ /// <code>
+ /// static CompletionExpander MyExpander = new CompletionFilter("Additional items", "a", MyAdditionalItemsImageElement);
+ /// </code>
+ /// </example>
+ [DebuggerDisplay("+ {DisplayText}")]
+ public class CompletionExpander : CompletionFilter
+ {
+ /// <summary>
+ /// Constructs an instance of <see cref="CompletionExpander"/>.
+ /// </summary>
+ /// <param name="displayText">Name of this expander</param>
+ /// <param name="accessKey">Key used in a keyboard shortcut that toggles this expander</param>
+ /// <param name="image">Image which represents this expander</param>
+ public CompletionExpander(string displayText, string accessKey, ImageElement image)
+ : base(displayText, accessKey, image)
+ { }
+ }
+}
diff --git a/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionFilter.cs b/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionFilter.cs
index 9486259..a4c848b 100644
--- a/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionFilter.cs
+++ b/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionFilter.cs
@@ -18,7 +18,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
/// </code>
/// </example>
[DebuggerDisplay("{DisplayText}")]
- public sealed class CompletionFilter : INotifyPropertyChanged
+ public class CompletionFilter : INotifyPropertyChanged
{
/// <summary>
/// Localized name of this filter.
@@ -36,11 +36,11 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
public ImageElement Image { get; }
/// <summary>
- /// Constructs an instance of CompletionFilter.
+ /// Constructs an instance of <see cref="CompletionFilter"/>.
/// </summary>
/// <param name="displayText">Name of this filter</param>
/// <param name="accessKey">Key used in a keyboard shortcut that toggles this filter.</param>
- /// <param name="image">Image that represents this filter</param>
+ /// <param name="image">Image which represents this filter</param>
public CompletionFilter(string displayText, string accessKey, ImageElement image)
{
if (string.IsNullOrWhiteSpace(displayText))
diff --git a/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionItemWithHighlight.cs b/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionItemWithHighlight.cs
index 2539c82..95709c6 100644
--- a/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionItemWithHighlight.cs
+++ b/src/Editor/Language/Def/Language/AsyncCompletion/Data/CompletionItemWithHighlight.cs
@@ -55,6 +55,11 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
public static bool operator !=(CompletionItemWithHighlight left, CompletionItemWithHighlight right) => !(left == right);
- public override int GetHashCode() => CompletionItem.GetHashCode() ^ HighlightedSpans.GetHashCode();
+ /// <summary>
+ /// Assumption: We won't see two instances of <see cref="CompletionItemWithHighlight"/> with same <see cref="CompletionItem"/> but different highlighting.
+ /// Therefore, we don't calculate hash code for the highlights.
+ /// </summary>
+ /// <returns><see cref="CompletionItem.GetHashCode"/></returns>
+ public override int GetHashCode() => CompletionItem.GetHashCode();
}
}
diff --git a/src/Editor/Language/Def/Language/AsyncCompletion/Data/ComputedCompletionItems.cs b/src/Editor/Language/Def/Language/AsyncCompletion/Data/ComputedCompletionItems.cs
index 463b5f7..df2866b 100644
--- a/src/Editor/Language/Def/Language/AsyncCompletion/Data/ComputedCompletionItems.cs
+++ b/src/Editor/Language/Def/Language/AsyncCompletion/Data/ComputedCompletionItems.cs
@@ -26,7 +26,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
bool suggestionItemSelected,
bool usesSoftSelection)
{
- _items = items;
+ _itemsWithoutHighlight = items;
SuggestionItem = suggestionItem;
SelectedItem = selectedItem;
SuggestionItemSelected = suggestionItemSelected;
@@ -61,13 +61,47 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
/// </summary>
public static ComputedCompletionItems Empty { get; } = new ComputedCompletionItems(ImmutableArray<CompletionItem>.Empty, null, null, false, false);
- private IEnumerable<CompletionItem> _items = null;
- private IEnumerable<CompletionItemWithHighlight> _itemsWithHighlight = null;
+ private ImmutableArray<CompletionItem> _itemsWithoutHighlight;
+ private ImmutableArray<CompletionItemWithHighlight> _itemsWithHighlight;
+
+ private IEnumerable<CompletionItem> _computedItems = null;
/// <summary>
/// <see cref="CompletionItem"/>s displayed in the completion UI
/// </summary>
- public IEnumerable<CompletionItem> Items => _items ?? _itemsWithHighlight.Select(n => n.CompletionItem);
+ public IEnumerable<CompletionItem> Items
+ {
+ get
+ {
+ if (_computedItems == null)
+ {
+ // We were constructed with either items or itemsWithHighlight
+ if (_itemsWithoutHighlight != null)
+ {
+ _computedItems = _itemsWithoutHighlight.IsDefault
+ ? Enumerable.Empty<CompletionItem>()
+ : _itemsWithoutHighlight;
+ }
+ else
+ {
+ if (_itemsWithHighlight.IsDefault)
+ {
+ _computedItems = Enumerable.Empty<CompletionItem>();
+ }
+ else
+ {
+ var items = new List<CompletionItem>(_itemsWithHighlight.Length);
+ for (int i = 0; i < _itemsWithHighlight.Length; i++)
+ {
+ items.Add(_itemsWithHighlight[i].CompletionItem);
+ }
+ _computedItems = items;
+ }
+ }
+ }
+ return _computedItems;
+ }
+ }
/// <summary>
/// Suggestion <see cref="CompletionItem"/> displayed in the UI, or null if no suggestion is displayed
diff --git a/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSession.cs b/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSession.cs
index e425879..b011ea1 100644
--- a/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSession.cs
+++ b/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSession.cs
@@ -60,9 +60,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
/// Must be called on UI thread.
/// </summary>
/// <param name="token">Token used to cancel this operation</param>
- /// <returns>Whether the unique item was committed.</returns>
+ /// <returns><c>true</c> if the unique item was committed</returns>
bool CommitIfUnique(CancellationToken token);
-
+
/// <summary>
/// Returns the <see cref="ITextView"/> this session is active on.
/// </summary>
diff --git a/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSessionOperations.cs b/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSessionOperations.cs
index 6a307eb..88e22bf 100644
--- a/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSessionOperations.cs
+++ b/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSessionOperations.cs
@@ -1,4 +1,5 @@
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
@@ -23,6 +24,16 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
bool IsStarted { get; }
/// <summary>
+ /// Returns the intial trigger
+ /// </summary>
+ CompletionTrigger InitialTrigger { get; }
+
+ /// <summary>
+ /// Returns the location of the initial trigger
+ /// </summary>
+ SnapshotPoint InitialTriggerLocation { get; }
+
+ /// <summary>
/// Enqueues selection a specified item. When all queued tasks are completed, the UI updates.
/// </summary>
void SelectCompletionItem(CompletionItem item);
@@ -38,6 +49,15 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
void InvokeAndCommitIfUnique(CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token);
/// <summary>
+ /// Starts asynchronous computation, which results in either committing of the single <see cref="CompletionItem"/> or opening the completion UI.
+ /// Calling <see cref="OpenOrUpdate(CompletionTrigger, SnapshotPoint, CancellationToken)"/> cancels the operation and dismisses the session.
+ /// Must be called on the UI thread to correctly set state of the session.
+ /// </summary>
+ /// <param name="token">Token used to cancel this operation</param>
+ /// <returns><c>true</c> if the unique item was committed</returns>
+ Task<bool> CommitIfUniqueAsync(CancellationToken token);
+
+ /// <summary>
/// Enqueues selecting the next item. When all queued tasks are completed, the UI updates.
/// </summary>
void SelectDown();
diff --git a/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSource.cs b/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSource.cs
index 8db4447..080c327 100644
--- a/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSource.cs
+++ b/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncCompletionSource.cs
@@ -8,7 +8,7 @@ using Microsoft.VisualStudio.Text.Editor;
namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
{
/// <summary>
- /// Represents a class that provides <see cref="CompletionItem"/>s and other information
+ /// Represents an object that provides <see cref="CompletionItem"/>s and other information
/// relevant to the completion feature at a specific <see cref="SnapshotPoint"/>.
/// </summary>
/// <remarks>
@@ -26,7 +26,12 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
/// <param name="applicableToSpan">Location where completion will take place, on the view's data buffer: <see cref="ITextView.TextBuffer"/></param>
/// <param name="token">Cancellation token that may interrupt this operation</param>
/// <returns>A struct that holds completion items and applicable span</returns>
- Task<CompletionContext> GetCompletionContextAsync(IAsyncCompletionSession session, CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableToSpan, CancellationToken token);
+ Task<CompletionContext> GetCompletionContextAsync(
+ IAsyncCompletionSession session,
+ CompletionTrigger trigger,
+ SnapshotPoint triggerLocation,
+ SnapshotSpan applicableToSpan,
+ CancellationToken token);
/// <summary>
/// Returns tooltip associated with provided <see cref="CompletionItem"/>.
diff --git a/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncExpandingCompletionSource.cs b/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncExpandingCompletionSource.cs
new file mode 100644
index 0000000..51ac57a
--- /dev/null
+++ b/src/Editor/Language/Def/Language/AsyncCompletion/IAsyncExpandingCompletionSource.cs
@@ -0,0 +1,40 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Represents an object that provides <see cref="CompletionItem"/>s and other information
+ /// relevant to the completion feature at a specific <see cref="SnapshotPoint"/>.
+ /// Additionally, this object has capability to provide additional <see cref="CompletionItem"/>s
+ /// in a reaction to user interacting with <see cref="CompletionExpander"/>. If this capability
+ /// is not necessary, then it is sufficient to implement just <see cref="IAsyncCompletionSource"/>.
+ /// </summary>
+ /// <remarks>
+ /// Instances of this class should be created by <see cref="IAsyncCompletionSourceProvider"/>, which is a MEF part.
+ /// </remarks>
+ public interface IAsyncExpandingCompletionSource : IAsyncCompletionSource
+ {
+ /// <summary>
+ /// Called when user interacts with expander buttons,
+ /// requesting the completion source to provide additional completion items pertinent to the expander button.
+ /// For best performance, do not provide <see cref="CompletionContext.Filters"/> unless expansion should add new filters.
+ /// Called on a background thread.
+ /// </summary>
+ /// <param name="session">Reference to the active <see cref="IAsyncCompletionSession"/></param>
+ /// <param name="expander">Expander which caused this call</param>
+ /// <param name="initialTrigger">What initially caused the completion</param>
+ /// <param name="applicableToSpan">Location where completion will take place, on the view's data buffer: <see cref="ITextView.TextBuffer"/></param>
+ /// <param name="token">Cancellation token that may interrupt this operation</param>
+ /// <returns>A struct that holds completion items and applicable span</returns>
+ Task<CompletionContext> GetExpandedCompletionContextAsync(
+ IAsyncCompletionSession session,
+ CompletionExpander expander,
+ CompletionTrigger initialTrigger,
+ SnapshotSpan applicableToSpan,
+ CancellationToken token);
+ }
+}
diff --git a/src/Editor/Language/Def/Language/AsyncCompletion/PredefinedCompletionNames.cs b/src/Editor/Language/Def/Language/AsyncCompletion/PredefinedCompletionNames.cs
index 17becc9..5195588 100644
--- a/src/Editor/Language/Def/Language/AsyncCompletion/PredefinedCompletionNames.cs
+++ b/src/Editor/Language/Def/Language/AsyncCompletion/PredefinedCompletionNames.cs
@@ -1,4 +1,5 @@
-using Microsoft.VisualStudio.Commanding;
+using System;
+using Microsoft.VisualStudio.Commanding;
namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
{
@@ -22,6 +23,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
/// </summary>
public const string CompletionCommandHandler = "CompletionCommandHandler";
+ [Obsolete("Use Microsoft.VisualStudio.Text.Editor.DefaultOptions.NonBlockingCompletionOptionName instead")]
/// <summary>
/// Name of the editor option that stores user's preference for dismissing completion rather than blocking for potentially long running tasks.
/// </summary>
@@ -36,5 +38,11 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
/// Name of the editor option that stores user's preference for the completion mode during debugging.
/// </summary>
public const string SuggestionModeInDebuggerCompletionOptionName = "SuggestionModeInDebuggerViewCompletion";
+
+ /// <summary>
+ /// Order your MEF part of type <see cref="Data.CompletionFilter"/> relatively to this name,
+ /// so that it tends to be the default expander (order before this name) or not be the default expander (order after this name).
+ /// </summary>
+ public const string DefaultCompletionExpander = "DefaultCompletionExpander";
}
}
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionBroker.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionBroker.cs
index 798f68a..b873c88 100644
--- a/src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionBroker.cs
+++ b/src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionBroker.cs
@@ -20,7 +20,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
internal sealed class AsyncCompletionBroker : IAsyncCompletionBroker
{
[Import]
- private IGuardedOperations GuardedOperations;
+ private IGuardedOperationsInternal GuardedOperations;
[Import]
private JoinableTaskContext JoinableTaskContext;
@@ -84,7 +84,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
public bool IsCompletionActive(ITextView textView)
{
- return textView.Properties.ContainsProperty(typeof(IAsyncCompletionSession));
+ return textView?.Properties?.ContainsProperty(typeof(IAsyncCompletionSession)) == true;
}
public bool IsCompletionSupported(IContentType contentType) => CompletionAvailability.IsAvailable(contentType, roles: null); // This will call HasCompletionProviders among doing other checks
@@ -133,7 +133,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
// If it succeeds, we will map triggerLocation to available buffers to discover MEF parts.
// This is expensive but projected languages require it to discover parts in all available buffers.
// To avoid doing this work, call IsCompletionSupported with appropriate IContentType prior to calling TriggerCompletion
- if (!CompletionAvailability.IsCurrentlyAvailable(textView, contentTypeToCheckBlacklist: triggerLocation.Snapshot.ContentType))
+ if (!CompletionAvailability.IsCurrentlyAvailable(textView))
return null;
if (textView.IsClosed)
@@ -150,6 +150,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
if (token.IsCancellationRequested || textView.IsClosed)
return null;
+ // See if we can use more aggressive cancellation token for typing scenarios
+ if (trigger.Reason == CompletionTriggerReason.Insertion)
+ token = CompletionUtilities.GetResponsiveToken(textView, token);
+
GetCompletionSources(triggerLocation, GetItemSourceProviders, rootSnapshot, textView, textView.BufferGraph, trigger, telemetry, token,
out var sourcesWithLocations, out var applicableToSpan);
@@ -223,13 +227,16 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
var aggregatingSession = AsyncCompletionSession.CreateAggregatingSession(applicableToSpan, JoinableTaskContext, sourcesWithLocations, this, textView, telemetry, GuardedOperations);
- var completionData = await aggregatingSession.ConnectToCompletionSources(trigger, triggerLocation, rootSnapshot, token).ConfigureAwait(true);
+ var completionData = await aggregatingSession.ConnectToCompletionSources(
+ trigger, triggerLocation, rootSnapshot,
+ getExpandedContext: false, initialItems: default, expander: default,
+ token: token).ConfigureAwait(true);
if (completionData.IsCanceled)
return AggregatedCompletionContext.Empty;
var aggregateCompletionContext = new CompletionContext(
- completionData.InitialCompletionItems,
+ completionData.Items,
completionData.RequestedSuggestionItemOptions,
completionData.InitialSelectionHint);
return new AggregatedCompletionContext(aggregateCompletionContext, aggregatingSession);
@@ -249,7 +256,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
/// is inappropriate, because it might be an elision buffer. If we map down from the elision buffer,
/// we may locate incorrect points around elided text.
///
- /// /// Note that the root snapshot cannot be use to realize the <see cref="IAsyncCompletionSession.ApplicableToSpan"/>,
+ /// Note that the root snapshot cannot be use to realize the <see cref="IAsyncCompletionSession.ApplicableToSpan"/>,
/// which is always defined on the <see cref="ITextView.TextSnapshot"/>
/// </summary>
/// <param name="textView">TextView which will host completion</param>
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionSession.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionSession.cs
index 0a13199..ca0f01c 100644
--- a/src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionSession.cs
+++ b/src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionSession.cs
@@ -5,9 +5,9 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Language.Intellisense.Implementation.AsyncCompletion;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
-using Microsoft.VisualStudio.Text.Projection;
using Microsoft.VisualStudio.Text.Utilities;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Utilities;
@@ -25,11 +25,11 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
private readonly IList<(IAsyncCompletionSource Source, SnapshotPoint Point)> _completionSources;
private readonly IList<(IAsyncCompletionCommitManager, ITextBuffer)> _commitManagers;
private readonly IAsyncCompletionItemManager _completionItemManager;
- private readonly JoinableTaskContext JoinableTaskContext;
+ private readonly JoinableTaskContext _jtc;
private readonly ICompletionPresenterProvider _presenterProvider;
private readonly AsyncCompletionBroker _broker;
private readonly ITextView _textView;
- private readonly IGuardedOperations _guardedOperations;
+ private readonly IGuardedOperationsInternal _guardedOperations;
private readonly ImmutableArray<char> _potentialCommitChars;
// Presentation:
@@ -69,9 +69,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
}
/// <summary>
- /// Stores the initial reason this session was triggererd.
+ /// Stores the reason this session was initially triggererd.
/// </summary>
- private CompletionTrigger InitialTrigger { get; set; }
+ public CompletionTrigger InitialTrigger { get; private set; }
+
+ /// <summary>
+ /// Stores the location this session was initially triggered.
+ /// </summary>
+ public SnapshotPoint InitialTriggerLocation { get; private set; }
/// <summary>
/// Text to display in place of suggestion mode when filtered text is empty.
@@ -91,7 +96,8 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
private readonly CompletionSessionTelemetry _telemetry;
/// <summary>
- /// Records noteworthy event which led to committing or dismissing
+ /// Records noteworthy event which led to committing or dismissing. Used in End To End telemetry.
+ /// If left unset, it means that the scenario is unremarkable.
/// </summary>
CompletionSessionState _finalSessionState;
@@ -121,6 +127,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
public PropertyCollection Properties { get; }
+ // Allow a blocking operation to run on the background thread until canceled by user's action
+ private DeferredBlockingOperation<bool> DeferredOperation { get; set; }
+
public AsyncCompletionSession(
SnapshotSpan initialApplicableToSpan,
ImmutableArray<char> potentialCommitChars,
@@ -132,10 +141,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
AsyncCompletionBroker broker,
ITextView textView,
CompletionSessionTelemetry telemetry,
- IGuardedOperations guardedOperations)
+ IGuardedOperationsInternal guardedOperations)
{
_potentialCommitChars = potentialCommitChars;
- JoinableTaskContext = joinableTaskContext;
+ _jtc = joinableTaskContext;
_presenterProvider = presenterProvider;
_broker = broker;
_completionSources = completionSources; // still prorotype at the momemnt.
@@ -163,7 +172,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
AsyncCompletionBroker broker,
ITextView textView,
CompletionSessionTelemetry telemetry,
- IGuardedOperations guardedOperations)
+ IGuardedOperationsInternal guardedOperations)
{
return new AsyncCompletionSession(
applicableToSpan,
@@ -183,10 +192,25 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
{
if (IsDismissed)
return false;
- if (!JoinableTaskContext.IsOnMainThread)
+ if (!_jtc.IsOnMainThread)
throw new InvalidOperationException($"This method must be callled on the UI thread.");
if (!_potentialCommitChars.Contains(typedChar))
return false;
+ if (DeferredOperation != null)
+ DeferredOperation.Cancel();
+
+ // Before we further block UI thread, let's see if we can dismiss in suggestion or non blocking mode
+ var inNonBlockingMode = CompletionUtilities.GetNonBlockingCompletionOption(_textView);
+ var inSuggestionMode = CompletionUtilities.GetSuggestionModeOption(_textView);
+ if (EligibleToQuicklyDismiss(_computation, typedChar, inSuggestionMode || inNonBlockingMode))
+ {
+ // For simplicity of implementation, let's pretend that we want to commit,
+ // so that the commit code appropriately dismisses the session.
+ return true;
+ }
+
+ // See if we can use more aggressive cancellation token
+ token = CompletionUtilities.GetResponsiveToken(_textView, token);
var rootSnapshot = AsyncCompletionBroker.GetRootSnapshot(TextView);
var points = MappingHelper.GetPointsAtLocation(triggerLocation, rootSnapshot);
@@ -211,7 +235,16 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
var shouldCommit = _guardedOperations.CallExtensionPoint(
errorSource: commitManager,
call: () => commitManager.ShouldCommitCompletion(this, relevantPoint, typedChar, token),
- valueOnThrow: false);
+ valueOnThrow: false,
+ exceptionToIgnore: (e) => e is OperationCanceledException && token.IsCancellationRequested,
+ exceptionToHandle: (e) => true);
+
+ if (token.IsCancellationRequested)
+ {
+ _telemetry.RecordBlockingExtension(commitManager);
+ _finalSessionState = CompletionSessionState.DismissedDueToCancellation;
+ return false;
+ }
if (shouldCommit)
return true;
@@ -224,7 +257,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
if (IsDismissed)
return false;
- if (!JoinableTaskContext.IsOnMainThread)
+ if (!_jtc.IsOnMainThread)
throw new InvalidOperationException($"This method must be callled on the UI thread.");
_telemetry.UiStopwatch.Restart();
@@ -240,7 +273,64 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
{
return false;
}
- else if (lastModel.UniqueItem != null)
+
+ return CommitIfUniqueCore(lastModel, token);
+ }
+
+ async Task<bool> IAsyncCompletionSessionOperations.CommitIfUniqueAsync(CancellationToken token)
+ {
+ if (IsDismissed)
+ return await Task.FromResult(false).ConfigureAwait(false);
+
+ if (!_jtc.IsOnMainThread)
+ throw new InvalidOperationException($"This method must be callled on the UI thread.");
+
+ if (DeferredOperation == null)
+ {
+ var deferredOperation = new DeferredBlockingOperation<bool>(_jtc, CommitIfUniqueAsyncOperation, token);
+
+ // Assign the property to allow others to cancel this operation.
+ DeferredOperation = deferredOperation;
+ }
+ else
+ {
+ // DeferredOperation is already set (for example, user repeatedly pressed Ctrl+Space)
+ // Instead of resetting it, await completion of the existing DeferredOperation.
+ }
+
+ var result = await DeferredOperation.Operation.Task.ConfigureAwait(false);
+ if (result)
+ Dismiss();
+
+ return result;
+ }
+
+ private async Task<bool> CommitIfUniqueAsyncOperation(CancellationToken token)
+ {
+ _telemetry.UiStopwatch.Restart();
+ var lastModel = _computation.WaitAndGetResult(cancelUi: true, token);
+ _telemetry.UiStopwatch.Stop();
+ _telemetry.RecordBlockingWaitForComputation(_telemetry.UiStopwatch.ElapsedMilliseconds);
+
+ if (lastModel == null)
+ {
+ return false;
+ }
+ else if (lastModel.Uninitialized)
+ {
+ return false;
+ }
+
+ var responsiveCommitToken = CompletionUtilities.GetResponsiveToken(_textView, CancellationToken.None);
+ await _jtc.Factory.SwitchToMainThreadAsync();
+ DeferredOperation = null; // we no longer need this
+
+ return CommitIfUniqueCore(lastModel, responsiveCommitToken);
+ }
+
+ private bool CommitIfUniqueCore(CompletionModel lastModel, CancellationToken token)
+ {
+ if (lastModel.UniqueItem != null)
{
_finalSessionState = CompletionSessionState.CommittedThroughCompleteWord;
var behavior = CommitItem(default, lastModel.UniqueItem, ApplicableToSpan, token);
@@ -283,31 +373,64 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
if (IsDismissed)
return CommitBehavior.None;
- if (!JoinableTaskContext.IsOnMainThread)
+ if (!_jtc.IsOnMainThread)
throw new InvalidOperationException($"This method must be callled on the UI thread.");
- // We are in either low latency mode or suggestion mode
- // user did not press tab, and we don't have results yet
- // => dismiss
- if ((_computation.RecentModel == default || _computation.RecentModel.Uninitialized)
- && (CompletionUtilities.GetSuggestionModeOption(_textView) || CompletionUtilities.GetNonBlockingCompletionOption(_textView))
- && !(typedChar.Equals(default) || typedChar.Equals('\t')))
- {
- _finalSessionState = CompletionSessionState.DismissedDueToNonBlockingMode;
- ((IAsyncCompletionSession)this).Dismiss();
- return CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers;
- }
+ if (DeferredOperation != null)
+ DeferredOperation.Cancel();
- CompletionModel lastModel;
+ var inSuggestionMode = CompletionUtilities.GetSuggestionModeOption(_textView);
+ var inNonBlockingMode = CompletionUtilities.GetNonBlockingCompletionOption(_textView);
+ var inResponisveMode = CompletionUtilities.GetResponsiveCompletionOption(_textView);
- // We are in low latency mode
- // => use recently computed model, but don't block waiting for one
- if (CompletionUtilities.GetNonBlockingCompletionOption(_textView))
+ CompletionModel lastModel;
+ if (EligibleToQuicklyDismiss(_computation, typedChar, inSuggestionMode || inNonBlockingMode || inResponisveMode))
{
- lastModel = _computation.RecentModel;
+ // We haven't received the completion items yet. See if we are in any eligible modes.
+ if (inNonBlockingMode || inSuggestionMode)
+ {
+ // We are fully non blocking. Dismiss immediately
+ _finalSessionState = inNonBlockingMode ? CompletionSessionState.DismissedDueToNonBlockingMode : CompletionSessionState.DismissedDueToSuggestionMode;
+ ((IAsyncCompletionSession)this).Dismiss();
+ return CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers;
+ }
+ else
+ {
+ // Await for completion of tasks, but no longer than either commanding or responsive token
+ var computationWaitToken = CompletionUtilities.GetResponsiveToken(_textView, token);
+
+ _telemetry.UiStopwatch.Restart();
+ lastModel = _computation.WaitAndGetResult(cancelUi: true, computationWaitToken);
+ _telemetry.UiStopwatch.Stop();
+
+ // We already waited a little bit.
+ // If lastModel is not null, we have finished all computation
+ // If lastModel is null, check if we at least received completion items. If that's the case, continue waiting for filtering
+
+ if (lastModel == null && (_computation == default || _computation.RecentModel == default || _computation.RecentModel.Uninitialized))
+ {
+ _telemetry.RecordBlockingWaitForComputation(_telemetry.UiStopwatch.ElapsedMilliseconds);
+
+ // We still haven't received completion items. Dismiss.
+ _finalSessionState = CompletionSessionState.DismissedDueToResponsiveMode;
+ ((IAsyncCompletionSession)this).Dismiss();
+ return CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers;
+ }
+ else
+ {
+ // Continue waiting for rest of computation (e.g. filtering)
+ _telemetry.UiStopwatch.Start();
+ lastModel = _computation.WaitAndGetResult(cancelUi: true, token);
+ _telemetry.UiStopwatch.Stop();
+ _telemetry.RecordBlockingWaitForComputation(_telemetry.UiStopwatch.ElapsedMilliseconds);
+ }
+ }
}
else
{
+ // User explicitly wanted to commit, or we already had results.
+ // Wrap up remaining filtering tasks and continue with commit
+
_telemetry.UiStopwatch.Restart();
lastModel = _computation.WaitAndGetResult(cancelUi: true, token);
_telemetry.UiStopwatch.Stop();
@@ -327,18 +450,18 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
}
else if (lastModel.Uninitialized)
{
+ _finalSessionState = CompletionSessionState.DismissedUninitialized;
((IAsyncCompletionSession)this).Dismiss();
return CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers;
}
else if (lastModel.UseSoftSelection
- && !(typedChar.Equals(default)
- || typedChar.Equals('\t')
- || TypedCharShouldNotDismissInSoftSelection(typedChar)))
+ && !(IsTabOrEmpty(typedChar) || TypedCharShouldNotDismissInSoftSelection(typedChar)))
{
// In soft selection mode, allow commit under the following circumstances:
// 1. User commits explicitly (click, tab)
// 2. User typed a character which is excluded from list of potential commit characters in the given session
// Otherwise, dismiss the session
+ _finalSessionState = CompletionSessionState.DismissedDueToSuggestionMode;
((IAsyncCompletionSession)this).Dismiss();
return CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers;
}
@@ -356,13 +479,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
else if (lastModel.PresentedItems.IsDefaultOrEmpty)
{
// There is nothing to commit
+ _finalSessionState = CompletionSessionState.DismissedDueToNoItems;
Dismiss();
return CommitBehavior.None;
}
else
{
// Regular commit
- _finalSessionState = CompletionSessionState.Committed;
+ _finalSessionState = IsTabOrEmpty(typedChar) ? CompletionSessionState.Committed : CompletionSessionState.CommittedThroughTypedChar;
return CommitItem(typedChar, lastModel.PresentedItems[lastModel.SelectedIndex].CompletionItem, ApplicableToSpan, token);
}
}
@@ -376,13 +500,20 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
_telemetry.UiStopwatch.Restart();
IAsyncCompletionCommitManager managerWhoCommitted = null;
+ var versionBeforeChange = applicableToSpan.TextBuffer.CurrentSnapshot.Version;
+
bool commitHandled = false;
- foreach (var commitManager in _commitManagers)
+ foreach (var commitManagerWithBuffer in _commitManagers)
{
+ var commitManager = commitManagerWithBuffer.Item1;
+ var textBuffer = commitManagerWithBuffer.Item2;
+
var commitResult = _guardedOperations.CallExtensionPoint(
errorSource: commitManager,
- call: () => commitManager.Item1.TryCommit(this, commitManager.Item2 /* buffer */, itemToCommit, typedChar, token),
- valueOnThrow: CommitResult.Unhandled);
+ call: () => commitManager.TryCommit(this, textBuffer, itemToCommit, typedChar, token),
+ valueOnThrow: CommitResult.Unhandled,
+ exceptionToIgnore: (e) => e is OperationCanceledException && token.IsCancellationRequested,
+ exceptionToHandle: (e) => true);
if (commitResult.Behavior == CommitBehavior.CancelCommit)
{
@@ -398,11 +529,12 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
commitHandled |= commitResult.IsHandled;
if (commitResult.IsHandled)
{
- managerWhoCommitted = commitManager.Item1;
+ managerWhoCommitted = commitManager;
break;
}
if (token.IsCancellationRequested)
{
+ _telemetry.RecordBlockingExtension(commitManager);
break;
}
}
@@ -412,11 +544,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
InsertIntoBuffer(_textView, applicableToSpan, itemToCommit.InsertText);
}
+ var versionAfterChange = applicableToSpan.TextBuffer.CurrentSnapshot.Version;
+ bool editsAreNoops = AreEditsNoops(versionBeforeChange, versionAfterChange);
+
_telemetry.UiStopwatch.Stop();
_telemetry.E2EStopwatch.Stop();
_guardedOperations.RaiseEvent(this, ItemCommitted, new CompletionItemEventArgs(itemToCommit));
- _telemetry.RecordCommitted(_telemetry.UiStopwatch.ElapsedMilliseconds, managerWhoCommitted);
+ _telemetry.RecordCommitted(_telemetry.UiStopwatch.ElapsedMilliseconds, editsAreNoops, managerWhoCommitted);
Dismiss();
@@ -461,13 +596,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
_broker.ForgetSession(this);
_textView.Caret.PositionChanged -= OnCaretPositionChanged;
_computationCancellation.Cancel();
+ DeferredOperation?.Cancel();
// This method may be invoked on any thread. We promised extenders we will raise Dismissed event on UI thread.
if (Dismissed != null)
{
- JoinableTaskContext.Factory.Run(async () =>
+ _jtc.Factory.Run(async () =>
{
- await JoinableTaskContext.Factory.SwitchToMainThreadAsync();
+ await _jtc.Factory.SwitchToMainThreadAsync();
_guardedOperations.RaiseEvent(this, Dismissed);
});
}
@@ -479,7 +615,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
errorSource: _gui,
asyncAction: async () =>
{
- await JoinableTaskContext.Factory.SwitchToMainThreadAsync();
+ await _jtc.Factory.SwitchToMainThreadAsync();
_telemetry.UiStopwatch.Restart();
copyOfGui.FiltersChanged -= OnFiltersChanged;
copyOfGui.CommitRequested -= OnCommitRequested;
@@ -505,9 +641,12 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
if (IsDismissed)
return;
- if (!JoinableTaskContext.IsOnMainThread)
+ if (!_jtc.IsOnMainThread)
throw new InvalidOperationException($"This method must be callled on the UI thread.");
+ if (DeferredOperation != null)
+ DeferredOperation.Cancel();
+
var rootSnapshot = AsyncCompletionBroker.GetRootSnapshot(TextView);
commandToken.Register(_computationCancellation.Cancel);
@@ -517,13 +656,22 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
{
_computation = new ModelComputation<CompletionModel>(
PrioritizedTaskScheduler.AboveNormalInstance,
- JoinableTaskContext,
+ _jtc,
(model, token) => GetInitialModel(trigger, triggerLocation, rootSnapshot, token),
_computationCancellation.Token,
_guardedOperations,
this
);
}
+ else if (trigger.Reason == CompletionTriggerReason.Invoke && _computation.RecentModel != default && !_computation.RecentModel.Uninitialized)
+ {
+ // Completion session already exists and it is in a well defined state.
+ // User invoked completion again - we will treat this as shortcut to toggle the first expander
+ // If no expander is available, UpdateCompletionByTogglingDefaultExpander will call UpdateSnapshot to preserve behavior
+ var expandTaskId = Interlocked.Increment(ref _lastFilteringTaskId);
+ _computation.Enqueue((model, token) => UpdateCompletionByTogglingDefaultExpander(model, trigger, triggerLocation, rootSnapshot, expandTaskId, token), updateUi: true);
+ return;
+ }
var taskId = Interlocked.Increment(ref _lastFilteringTaskId);
_computation.Enqueue((model, token) => UpdateSnapshot(model, trigger, triggerLocation, rootSnapshot, taskId, token), updateUi: true);
@@ -532,9 +680,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
ComputedCompletionItems IAsyncCompletionSession.GetComputedItems(CancellationToken token)
{
if (_computation == null)
- return ComputedCompletionItems.Empty; // Call OpenOrUpdate first to kick off computation
+ {
+ // Computation hasn't started yet. Call OpenOrUpdate first.
+ return ComputedCompletionItems.Empty;
+ }
- var model = _computation.WaitAndGetResult(cancelUi: true, token); // We don't want user initiated action to hide UI
+ var model = _computation.WaitAndGetResult(
+ cancelUi: false, // Don't hide the UI on user or extension initiated action. As a tradeoff, we will wait for UI to render.
+ token: token);
return ComputeCompletionItems(model);
}
@@ -552,10 +705,15 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
((IAsyncCompletionSession)this).OpenOrUpdate(trigger, triggerLocation, token);
}
- if (((IAsyncCompletionSession)this).CommitIfUnique(token))
+ // CancellationToken.None allows unlimited time to process this request.
+ // The CommitIfUnique can be anyways canceled by any user action.
+ _jtc.Factory.RunAsync(async () =>
{
- ((IAsyncCompletionSession)this).Dismiss();
- }
+ // RunAsync allows us to remain on UI thread, so that so that DeferredOperation is set
+ var committed = await ((IAsyncCompletionSessionOperations)this).CommitIfUniqueAsync(CancellationToken.None).ConfigureAwait(false);
+ if (committed)
+ Dismiss();
+ });
}
public void SetSuggestionMode(bool useSuggestionMode)
@@ -565,7 +723,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
public void SelectDown()
{
- if (_computation.RecentModel == default || _computation.RecentModel.Uninitialized)
+ if (DeferredOperation != null || _computation.RecentModel == default || _computation.RecentModel.Uninitialized)
{
// https://github.com/dotnet/roslyn/issues/31131 Dismiss completion so that up and down gestures are not blocked
Dismiss();
@@ -575,7 +733,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
public void SelectPageDown()
{
- if (_computation.RecentModel == default || _computation.RecentModel.Uninitialized)
+ if (DeferredOperation != null || _computation.RecentModel == default || _computation.RecentModel.Uninitialized)
{
// https://github.com/dotnet/roslyn/issues/31131 Dismiss completion so that up and down gestures are not blocked
Dismiss();
@@ -585,7 +743,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
public void SelectUp()
{
- if (_computation.RecentModel == default || _computation.RecentModel.Uninitialized)
+ if (DeferredOperation != null || _computation.RecentModel == default || _computation.RecentModel.Uninitialized)
{
// https://github.com/dotnet/roslyn/issues/31131 Dismiss completion so that up and down gestures are not blocked
Dismiss();
@@ -595,7 +753,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
public void SelectPageUp()
{
- if (_computation.RecentModel == default || _computation.RecentModel.Uninitialized)
+ if (DeferredOperation != null || _computation.RecentModel == default || _computation.RecentModel.Uninitialized)
{
// https://github.com/dotnet/roslyn/issues/31131 Dismiss completion so that up and down gestures are not blocked
Dismiss();
@@ -605,6 +763,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
public void SelectCompletionItem(CompletionItem item)
{
+ if (DeferredOperation != null)
+ DeferredOperation.Cancel();
+
// To prevent inifinite loops, UI interacts with computation using the OnItemSelected event handler
_computation.Enqueue((model, token) => UpdateSelectedItem(model, item, false, token), updateUi: true);
}
@@ -630,6 +791,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
private void OnFiltersChanged(object sender, CompletionFilterChangedEventArgs args)
{
+ if (DeferredOperation != null)
+ DeferredOperation.Cancel();
+
var taskId = Interlocked.Increment(ref _lastFilteringTaskId);
_computation.Enqueue((model, token) => UpdateFilters(model, args.Filters, taskId, token), updateUi: true);
}
@@ -724,7 +888,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
async Task IModelComputationCallbackHandler<CompletionModel>.UpdateUI(CompletionModel model, CancellationToken token)
{
if (_presenterProvider == null) return;
- await JoinableTaskContext.Factory.SwitchToMainThreadAsync(token);
+ await _jtc.Factory.SwitchToMainThreadAsync(token);
if (token.IsCancellationRequested) return;
UpdateUiInner(model);
}
@@ -741,7 +905,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
throw new ArgumentNullException(nameof(model));
if (model.Uninitialized)
return; // Language service wishes to not show completion yet.
- if (!JoinableTaskContext.IsOnMainThread)
+ if (!_jtc.IsOnMainThread)
throw new InvalidOperationException($"This method must be callled on the UI thread.");
// TODO: Consider building CompletionPresentationViewModel in BG and passing it here
@@ -780,23 +944,30 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
}
/// <summary>
- /// Gets <see cref="CompletionContext"/> from available <see cref="IAsyncCompletionSource"/>s
+ /// Gets <see cref="CompletionContext"/> from available <see cref="IAsyncCompletionSource"/>s,
+ /// or <see cref="IAsyncExpandingCompletionSource"/>s if <paramref name="getExpandedContext"/> is set.
/// </summary>
/// <returns>Aggregate data built from all received <see cref="CompletionContext"/>s</returns>
- internal async Task<CompletionSourceConnectionResult>ConnectToCompletionSources(CompletionTrigger trigger, SnapshotPoint triggerLocation, ITextSnapshot rootSnapshot, CancellationToken token)
+ internal async Task<CompletionSourceConnectionResult>ConnectToCompletionSources(CompletionTrigger trigger, SnapshotPoint triggerLocation, ITextSnapshot rootSnapshot, bool getExpandedContext, ImmutableArray<CompletionItem> initialItems, CompletionExpander expander, CancellationToken token)
{
bool sourceUsesSuggestionMode = false;
SuggestionItemOptions requestedSuggestionItemOptions = null;
InitialSelectionHint initialSelectionHint = InitialSelectionHint.RegularSelection;
var initialItemsBuilder = ImmutableArray.CreateBuilder<CompletionItem>();
+ if (!initialItems.IsDefaultOrEmpty)
+ initialItemsBuilder.AddRange(initialItems);
+ var filterBuilder = ImmutableArray.CreateBuilder<CompletionFilterWithState>();
- // We use rootSnapshot to obtain buffers that participate in comple
+ // We use rootSnapshot to obtain buffers which participate in completion
var points = MappingHelper.GetPointsAtLocation(triggerLocation, rootSnapshot);
for (int i = 0; i < _completionSources.Count; i++)
{
var sourceAndLocation = _completionSources[i]; // Capture the source, since `i` will change during the async call
+ if (getExpandedContext && !(sourceAndLocation.Source is IAsyncExpandingCompletionSource))
+ continue;
+
_telemetry.ComputationStopwatch.Restart();
var context = await _guardedOperations.CallExtensionPointAsync(
errorSource: sourceAndLocation.Source,
@@ -808,7 +979,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
return Task.FromResult(CompletionContext.Empty);
// Don't use rootSnapshot: ApplicableToSpan is defined on the triggerLocation's snapshot
- return sourceAndLocation.Source.GetCompletionContextAsync(this, trigger, mappedPoint, ApplicableToSpan.GetSpan(triggerLocation.Snapshot), token);
+ if (getExpandedContext)
+ return ((IAsyncExpandingCompletionSource)sourceAndLocation.Source).GetExpandedCompletionContextAsync(this, expander, InitialTrigger, ApplicableToSpan.GetSpan(triggerLocation.Snapshot), token);
+ else
+ return sourceAndLocation.Source.GetCompletionContextAsync(this, trigger, mappedPoint, ApplicableToSpan.GetSpan(triggerLocation.Snapshot), token);
},
valueOnThrow: null
).ConfigureAwait(true);
@@ -816,7 +990,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
_telemetry.RecordObtainingSourceContext(sourceAndLocation.Source, _telemetry.ComputationStopwatch.ElapsedMilliseconds);
if (token.IsCancellationRequested)
+ {
+ _telemetry.RecordBlockingExtension(sourceAndLocation.Source);
return CompletionSourceConnectionResult.Canceled;
+ }
if (context == null)
continue;
@@ -828,11 +1005,22 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
if (!context.Items.IsDefaultOrEmpty)
initialItemsBuilder.AddRange(context.Items);
+ if (context.Filters.IsDefault)
+ {
+ // Iterate through items to get filters
+ filterBuilder.AddRange(context.Items.SelectMany(n => n.Filters).Distinct().Select(n => new CompletionFilterWithState(n, isAvailable: false, isSelected: false)));
+ }
+ else
+ {
+ filterBuilder.AddRange(context.Filters);
+ }
+
// We use SuggestionModeOptions of the first source that provides it
if (requestedSuggestionItemOptions == null && context.SuggestionItemOptions != null)
requestedSuggestionItemOptions = context.SuggestionItemOptions;
}
- return new CompletionSourceConnectionResult(sourceUsesSuggestionMode, requestedSuggestionItemOptions, initialSelectionHint, initialItemsBuilder.ToImmutable());
+
+ return new CompletionSourceConnectionResult(sourceUsesSuggestionMode, requestedSuggestionItemOptions, initialSelectionHint, initialItemsBuilder.ToImmutable(), filterBuilder.ToImmutable());
}
/// <summary>
@@ -840,14 +1028,16 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
/// </summary>
private async Task<CompletionModel> GetInitialModel(CompletionTrigger trigger, SnapshotPoint triggerLocation, ITextSnapshot rootSnapshot, CancellationToken token)
{
- var completionData = await ConnectToCompletionSources(trigger, triggerLocation, rootSnapshot, token).ConfigureAwait(true);
+ var completionData = await ConnectToCompletionSources(trigger, triggerLocation, rootSnapshot,
+ getExpandedContext: false, initialItems: default, expander: default,
+ token: token).ConfigureAwait(true);
// Do not continue without items
if (completionData.IsCanceled)
{
return default;
}
- else if (completionData.InitialCompletionItems.IsDefaultOrEmpty)
+ else if (completionData.Items.IsDefaultOrEmpty)
{
return CompletionModel.GetUninitializedModel(triggerLocation.Snapshot);
}
@@ -857,13 +1047,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
// Store the data that won't change throughout the session
InitialTrigger = trigger;
+ InitialTriggerLocation = triggerLocation;
SuggestionModeCompletionItemSource = new SuggestionModeCompletionItemSource(SuggestionItemOptions);
- var availableFilters = completionData.InitialCompletionItems
- .SelectMany(n => n.Filters)
- .Distinct()
- .Select(n => new CompletionFilterWithState(n, true))
- .ToImmutableArray();
+ var primedExpanders = GetPrimedExpanders(completionData.Filters);
var viewUsesSuggestionMode = CompletionUtilities.GetSuggestionModeOption(_textView);
var useSuggestionMode = completionData.SourceUsesSuggestionMode || viewUsesSuggestionMode;
@@ -877,15 +1064,85 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
errorSource: _completionItemManager,
asyncCall: () => _completionItemManager.SortCompletionListAsync(
session: this,
- data: new AsyncCompletionSessionInitialDataSnapshot(completionData.InitialCompletionItems, triggerLocation.Snapshot, InitialTrigger),
+ data: new AsyncCompletionSessionInitialDataSnapshot(completionData.Items, triggerLocation.Snapshot, InitialTrigger),
token: token),
- valueOnThrow: completionData.InitialCompletionItems).ConfigureAwait(true);
+ valueOnThrow: completionData.Items).ConfigureAwait(true);
_telemetry.ComputationStopwatch.Stop();
- _telemetry.RecordProcessing(_telemetry.ComputationStopwatch.ElapsedMilliseconds, completionData.InitialCompletionItems.Length);
+ _telemetry.RecordProcessing(_telemetry.ComputationStopwatch.ElapsedMilliseconds, completionData.Items.Length);
_telemetry.RecordKeystroke();
+ if (token.IsCancellationRequested)
+ {
+ _telemetry.RecordBlockingExtension(_completionItemManager);
+ }
- return new CompletionModel(completionData.InitialCompletionItems, sortedList, triggerLocation.Snapshot,
- availableFilters, useSoftSelection, useSuggestionMode, selectSuggestionItem, suggestionItem: null);
+ return new CompletionModel(completionData.Items, sortedList, triggerLocation.Snapshot,
+ completionData.Filters, primedExpanders, useSoftSelection, useSuggestionMode, selectSuggestionItem, suggestionItem: null);
+ }
+
+ private async Task<CompletionModel> ExpandModel(CompletionModel model, CompletionExpander expander, ITextSnapshot rootSnapshot, CancellationToken token)
+ {
+ var triggerLocation = ApplicableToSpan.GetStartPoint(model.Snapshot); // it's made up
+ var completionData = await ConnectToCompletionSources(InitialTrigger, triggerLocation, rootSnapshot,
+ getExpandedContext: true, initialItems: model.InitialItems, expander: expander,
+ token: token).ConfigureAwait(true);
+
+ // Do not continue without items
+ if (completionData.IsCanceled || completionData.Items.IsDefaultOrEmpty)
+ return model;
+ // Ignore the part of CompletionData which pertains soft selection and suggestion mode
+ // So far, nobody made a request that we make adjust suggestion mode or selection during expansion.
+
+ // Mark currently used expander as primed, together with any other expanders provided by the language service
+ var primedExpanders = model.PrimedExpanders.AddRange(GetPrimedExpanders(completionData.Filters)).Add(expander);
+ var deduplicatedItems = completionData.Items.Distinct().ToImmutableArray();
+
+ // Sort items
+ _telemetry.ComputationStopwatch.Restart();
+ var sortedList = await _guardedOperations.CallExtensionPointAsync(
+ errorSource: _completionItemManager,
+ asyncCall: () => _completionItemManager.SortCompletionListAsync(
+ session: this,
+ data: new AsyncCompletionSessionInitialDataSnapshot(deduplicatedItems, triggerLocation.Snapshot, InitialTrigger),
+ token: token),
+ valueOnThrow: deduplicatedItems).ConfigureAwait(true);
+ _telemetry.ComputationStopwatch.Stop();
+ _telemetry.RecordProcessing(_telemetry.ComputationStopwatch.ElapsedMilliseconds, deduplicatedItems.Length);
+ _telemetry.RecordKeystroke();
+ if (token.IsCancellationRequested)
+ {
+ _telemetry.RecordBlockingExtension(_completionItemManager);
+ }
+
+ // Combine existing filters with potential new items
+ // Ensure that previously selected filters remain selected, and that newly selected filters are selected
+ var filters = model.Filters;
+ if (completionData.Filters.Any())
+ {
+ var filterBuilder = ImmutableArray.CreateBuilder<CompletionFilterWithState>(model.Filters.Length);
+ for (int i = 0; i < completionData.Filters.Length; i++)
+ {
+ var newFilter = completionData.Filters[i];
+ var existingFilter = model.Filters.FirstOrDefault(n => n.Filter == newFilter.Filter);
+ if (existingFilter != null && existingFilter.IsSelected)
+ {
+ filterBuilder.Add(existingFilter);
+ }
+ else
+ {
+ filterBuilder.Add(newFilter);
+ }
+ }
+ filters = filterBuilder.ToImmutableArray();
+ }
+ return model.WithExpansion(deduplicatedItems, sortedList, filters, primedExpanders);
+ }
+
+ private static ImmutableArray<CompletionExpander> GetPrimedExpanders(ImmutableArray<CompletionFilterWithState> filters)
+ {
+ return filters
+ .Where(n => n.IsSelected && n.Filter is CompletionExpander)
+ .Select(n => (CompletionExpander)n.Filter)
+ .ToImmutableArray();
}
/// <summary>
@@ -1040,6 +1297,13 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
valueOnThrow: null).ConfigureAwait(true);
// Error cases are handled by logging them above and dismissing the session.
+ if (token.IsCancellationRequested)
+ {
+ _telemetry.RecordBlockingExtension(_completionItemManager);
+ _finalSessionState = CompletionSessionState.DismissedDueToCancellation;
+ ((IAsyncCompletionSession)this).Dismiss();
+ return model;
+ }
if (filteredCompletion == null)
{
_finalSessionState = CompletionSessionState.DismissedDuringFiltering;
@@ -1172,7 +1436,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
/// <returns></returns>
private async Task<bool> TryDismissSafely(int currentTaskId)
{
- await JoinableTaskContext.Factory.SwitchToMainThreadAsync();
+ await _jtc.Factory.SwitchToMainThreadAsync();
// Tasks are enqueued on the UI thread, so we know that _lastFilteringTaskId won't change
if (currentTaskId < _lastFilteringTaskId)
@@ -1196,7 +1460,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
_telemetry.RecordChangingFilters();
_telemetry.RecordKeystroke();
- // This operation just got preempted, preserve new filters until next time we have a chance to update the completion list.
+ // See if any of the selected filters are expanders used for the first time
+ model = await ExpandCompletionWithSpecificFilter(model, newFilters, token).ConfigureAwait(false);
+
+ // Filtering just got preempted, preserve new filters until next time we have a chance to update the completion list.
if (token.IsCancellationRequested || thisId != _lastFilteringTaskId)
return model.WithFilters(newFilters);
@@ -1216,9 +1483,18 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
valueOnThrow: null).ConfigureAwait(true);
// Handle error cases by logging the issue and discarding the request to filter
- if (filteredCompletion == null)
+ if (token.IsCancellationRequested)
+ {
+ _telemetry.RecordBlockingExtension(_completionItemManager);
+ _finalSessionState = CompletionSessionState.DismissedDueToCancellation;
+ ((IAsyncCompletionSession)this).Dismiss();
return model;
- if (filteredCompletion.Filters.Length != newFilters.Length)
+ }
+ else if (filteredCompletion == null)
+ {
+ return model;
+ }
+ else if (filteredCompletion.Filters.Length != newFilters.Length)
{
_guardedOperations.HandleException(
errorSource: _completionItemManager,
@@ -1229,6 +1505,37 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
return model.WithFilters(filteredCompletion.Filters).WithPresentedItems(filteredCompletion.Items, filteredCompletion.SelectedItemIndex);
}
+ private async Task<CompletionModel> UpdateCompletionByTogglingDefaultExpander(CompletionModel model, CompletionTrigger trigger, SnapshotPoint triggerLocation, ITextSnapshot rootSnapshot, int thisId, CancellationToken token)
+ {
+ if (token.IsCancellationRequested)
+ return model;
+
+ if (!(model.Filters.FirstOrDefault()?.Filter is CompletionExpander))
+ {
+ // Preserve the default behavior
+ return await UpdateSnapshot(model, trigger, triggerLocation, rootSnapshot, thisId, token).ConfigureAwait(false);
+ }
+
+ var filtersWithToggledExpander = ImmutableArray.CreateRange(model.Filters.Select((n, i) => i == 0 ? n.WithSelected(!n.IsSelected) : n));
+ return await UpdateFilters(model, filtersWithToggledExpander, thisId, token).ConfigureAwait(false);
+ }
+
+ private async Task<CompletionModel> ExpandCompletionWithSpecificFilter(CompletionModel model, ImmutableArray<CompletionFilterWithState> newFilters, CancellationToken token)
+ {
+ for (int i = 0; i < newFilters.Length; i++)
+ {
+ if (newFilters[i].IsSelected && newFilters[i].Filter is CompletionExpander expander)
+ {
+ if (!model.PrimedExpanders.Contains(expander))
+ {
+ var rootSnapshot = AsyncCompletionBroker.GetRootSnapshot(TextView);
+ model = await ExpandModel(model, expander, rootSnapshot, token).ConfigureAwait(true);
+ }
+ }
+ }
+ return model;
+ }
+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
#pragma warning disable CA1801 // Parameter token is never used
/// <summary>
@@ -1371,5 +1678,65 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
suggestionItemSelected: model.SelectSuggestionItem,
usesSoftSelection: model.UseSoftSelection);
}
+
+ /// <summary>
+ /// Checks whether all versions between <paramref name="startVersion"/> and <paramref name="endVersion"/>
+ /// have no associated changes.
+ /// </summary>
+ /// <param name="startVersion"></param>
+ /// <param name="endVersion"></param>
+ /// <returns></returns>
+ private static bool AreEditsNoops(ITextVersion startVersion, ITextVersion endVersion)
+ {
+ if (startVersion.TextBuffer != endVersion.TextBuffer)
+ throw new ArgumentException("Versions must apply to the same buffer");
+
+ if (startVersion.VersionNumber > endVersion.VersionNumber)
+ throw new ArgumentException($"{nameof(startVersion)} must be before {nameof(endVersion)}.");
+
+ if (startVersion == endVersion)
+ {
+ return false;
+ }
+ else
+ {
+ var inspectedVersion = startVersion;
+ while (inspectedVersion.VersionNumber < endVersion.VersionNumber || inspectedVersion == null)
+ {
+ if (inspectedVersion.Changes.Count > 0)
+ {
+ return true;
+ }
+ inspectedVersion = inspectedVersion.Next;
+ }
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Returns whether there are no available items, user did not perform a blocking operation,
+ /// and completion session is in one of the eligible modes. Specifically,
+ /// 1. In suggestion mode, we don't wait on computation. Instead, we just hide the UI (dismiss) on commit
+ /// 2. Non blocking mode is set by langauge services to immediately stop computation when user presses a commit character
+ /// 3. Responsive mode is a moderate version of non blocking mode, where language services get grace period to finish computation.
+ /// we introduced Responsive mode because most delays, if any, are less than 20ms.
+ /// </summary>
+ private static bool EligibleToQuicklyDismiss(ModelComputation<CompletionModel> computation, char typedChar, bool inEligibleMode)
+ {
+ return (computation == default || computation.RecentModel == default || computation.RecentModel.Uninitialized)
+ && inEligibleMode
+ && !IsTabOrEmpty(typedChar);
+ }
+
+ /// <summary>
+ /// Returns whether <paramref name="typedChar"/> represents Tab or empty character,
+ /// which commit completion differently than any other character.
+ /// </summary>
+ /// <param name="typedChar">Character to examine</param>
+ /// <returns><c>true</c> if <paramref name="typedChar"/> is <c>'\0'</c> or <c>'\t'</c> </returns>
+ private static bool IsTabOrEmpty(char typedChar)
+ {
+ return typedChar.Equals(default) || typedChar.Equals('\t');
+ }
}
}
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionAvailabilityUtility.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionAvailabilityUtility.cs
index 6d1cc36..14fe115 100644
--- a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionAvailabilityUtility.cs
+++ b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionAvailabilityUtility.cs
@@ -12,33 +12,19 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
{
/// <summary>
/// Provides information whether modern completion should be enabled,
- /// based on the state of <see cref="IExperimentationServiceInternal"/> and <see cref="IFeatureServiceFactory" />
+ /// based on the state of <see cref="PredefinedEditorFeatureNames.AsyncCompletion"/> in <see cref="IFeatureServiceFactory" />
/// for the given <see cref="IContentType"/> and <see cref="ITextView"/>.
/// </summary>
[Export]
internal class CompletionAvailabilityUtility
{
[Import]
- private IExperimentationServiceInternal ExperimentationService;
-
- [Import]
private IFeatureServiceFactory FeatureServiceFactory;
[Import]
private AsyncCompletionBroker Broker; // We're using internal method to check if relevant MEF parts exist.
- [Import]
- private IEditorOptionsFactoryService EditorOptionsFactory;
-
- // Black list by content type
- private const string CompletionFlightName = "CompletionAPI";
- private const string RoslynLanguagesContentType = "Roslyn Languages";
- private const string RazorContentType = "Razor";
- private const string LanguageServerContentType = "code-languageserver-preview";
- private bool _treatmentFlightDataInitialized;
-
// Quick access data:
- private bool _treatmentFlightEnabled;
private IFeatureCookie _globalCompletionCookie;
private IFeatureCookie GlobalCompletionCookie =>
_globalCompletionCookie
@@ -47,69 +33,21 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
/// <summary>
/// Returns whether completion is available for the given <see cref="IContentType"/> and <see cref="ITextViewRoleSet" />.
/// </summary>
- /// <returns>true if experiment is enabled, feature is enabled in the <see cref="ITextView" />'s scope, and broker has providers that match the supplied <see cref="IContentType" /></returns>
+ /// <returns>true if feature is enabled in the <see cref="ITextView" />'s scope, and broker has providers that match the supplied <see cref="IContentType" /></returns>
internal bool IsAvailable(IContentType contentType, ITextViewRoleSet roles)
{
- if (!GlobalCompletionCookie.IsEnabled)
- return false;
-
- if (!Broker.HasCompletionProviders(contentType, roles))
- return false;
-
- // Roslyn and Razor providers exist in the MEF cache, but Roslyn is not ready for public rollout yet.
- // However, We do want other languages (e.g. AXML, EditorConfig) to work with Async Completion API
- // We will remove this check once Roslyn fully embraces Async Completion API.
- if (!IsExperimentEnabled() && !contentType.IsOfType(LanguageServerContentType) && (contentType.IsOfType(RoslynLanguagesContentType) || contentType.IsOfType(RazorContentType)))
- return false;
-
- return true;
+ return GlobalCompletionCookie.IsEnabled
+ && Broker.HasCompletionProviders(contentType, roles);
}
/// <summary>
/// Returns whether completion feature is available in the given <see cref="ITextView" />.
- /// Note: the second parameter <see cref="IContentType"/> is to be removed in dev16 when the experiment ends.
/// </summary>
- /// <returns>true if experiment is enabled and feature is enabled in <see cref="ITextView"/>'s scope</returns>
- internal bool IsCurrentlyAvailable(ITextView textView, IContentType contentTypeToCheckBlacklist)
+ /// <returns>true if feature is enabled in <see cref="ITextView"/>'s scope</returns>
+ internal bool IsCurrentlyAvailable(ITextView textView)
{
var featureService = FeatureServiceFactory.GetOrCreate(textView);
- if (!featureService.IsEnabled(PredefinedEditorFeatureNames.AsyncCompletion))
- return false;
-
- // Roslyn and Razor providers exist in the MEF cache, but Roslyn is not ready for public rollout yet.
- // However, We do want other languages (e.g. AXML, EditorConfig) to work with Async Completion API
- // We will remove this check once Roslyn fully embraces Async Completion API.
- if (!IsExperimentEnabled() && !contentTypeToCheckBlacklist.IsOfType(LanguageServerContentType) && (contentTypeToCheckBlacklist.IsOfType(RoslynLanguagesContentType) || contentTypeToCheckBlacklist.IsOfType(RazorContentType)))
- return false;
-
- return true;
- }
-
- private bool IsExperimentEnabled()
- {
- int userSetting = 0;
- if (EditorOptionsFactory.GlobalOptions.IsOptionDefined(UseAsyncCompletionOptionDefinition.OptionName, localScopeOnly: false))
- {
- userSetting = EditorOptionsFactory.GlobalOptions.GetOptionValue<int>(UseAsyncCompletionOptionDefinition.OptionName);
- }
-
- if (userSetting == 1)
- {
- return true;
- }
- else if (userSetting == -1)
- {
- return false;
- }
- else
- {
- if (_treatmentFlightDataInitialized)
- return _treatmentFlightEnabled;
-
- _treatmentFlightEnabled = ExperimentationService.IsCachedFlightEnabled(CompletionFlightName);
- _treatmentFlightDataInitialized = true;
- return _treatmentFlightEnabled;
- }
+ return featureService.IsEnabled(PredefinedEditorFeatureNames.AsyncCompletion);
}
}
}
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionCommandHandlers.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionCommandHandlers.cs
index c3201c8..c4a5621 100644
--- a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionCommandHandlers.cs
+++ b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionCommandHandlers.cs
@@ -1,13 +1,15 @@
using System;
using System.ComponentModel.Composition;
+using System.Globalization;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.BraceCompletion;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.Commanding;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Text.Operations;
-using Microsoft.VisualStudio.Text.Utilities;
+using Microsoft.VisualStudio.Text.UI.Utilities;
using Microsoft.VisualStudio.Utilities;
using CommonImplementation = Microsoft.VisualStudio.Language.Intellisense.Implementation;
@@ -33,6 +35,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
IDynamicCommandHandler<EscapeKeyCommandArgs>,
ICommandHandler<InsertSnippetCommandArgs>,
ICommandHandler<InvokeCompletionListCommandArgs>,
+ IDynamicCommandHandler<InvokeCompletionListCommandArgs>,
ICommandHandler<PageDownKeyCommandArgs>,
ICommandHandler<PageUpKeyCommandArgs>,
ICommandHandler<PasteCommandArgs>,
@@ -205,6 +208,12 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
// Execute other commands in the chain to see the change in the buffer.
nextCommandHandler();
+ if (args.TextView.TextSnapshot == snapshotBeforeEdit)
+ {
+ // Buffer has not changed. Don't invoke completion.
+ return;
+ }
+
var session = Broker.GetSession(args.TextView);
var location = args.TextView.Caret.Position.BufferPosition;
var trigger = new CompletionTrigger(CompletionTriggerReason.Backspace, snapshotBeforeEdit);
@@ -241,6 +250,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
CommandState ICommandHandler<InvokeCompletionListCommandArgs>.GetCommandState(InvokeCompletionListCommandArgs args)
=> GetCommandStateIfCompletionIsAvailable(args.SubjectBuffer.ContentType, args.TextView);
+ bool IDynamicCommandHandler<InvokeCompletionListCommandArgs>.CanExecuteCommand(InvokeCompletionListCommandArgs args)
+ => CompletionAvailability.IsAvailable(args.SubjectBuffer.ContentType, args.TextView.Roles);
+
bool ICommandHandler<InvokeCompletionListCommandArgs>.ExecuteCommand(InvokeCompletionListCommandArgs args, CommandExecutionContext executionContext)
{
if (!GetCommandStateIfCompletionIsAvailable(args.SubjectBuffer.ContentType, args.TextView).IsAvailable)
@@ -357,6 +369,12 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
// Execute other commands in the chain to see the change in the buffer.
nextCommandHandler();
+ if (args.TextView.TextSnapshot == snapshotBeforeEdit)
+ {
+ // Buffer has not changed. Don't invoke completion.
+ return;
+ }
+
var session = Broker.GetSession(args.TextView);
var location = args.TextView.Caret.Position.BufferPosition;
var trigger = new CompletionTrigger(CompletionTriggerReason.Deletion, snapshotBeforeEdit);
@@ -470,6 +488,8 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
var session = Broker.GetSession(args.TextView);
if (session != null)
{
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Return: begin commit");
var commitBehavior = session.Commit(typedChar, executionContext.OperationContext.UserCancellationToken);
session.Dismiss();
@@ -479,23 +499,42 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
if ((commitBehavior & CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers) == 0
|| CompletionUtilities.IsDebuggerTextView(args.TextView)
|| CompletionUtilities.IsImmediateTextView(args.TextView))
+ {
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Return: do nothing after commit", commitBehavior);
return;
+ }
}
var snapshotBeforeEdit = args.TextView.TextSnapshot;
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Return: next handler");
nextCommandHandler();
+ if (args.TextView.TextSnapshot == snapshotBeforeEdit)
+ {
+ // Buffer has not changed. Don't invoke completion.
+ return;
+ }
+
// Buffer has changed. Update it for when we try to trigger new session.
var location = args.TextView.Caret.Position.BufferPosition;
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Return: try make new session");
var trigger = new CompletionTrigger(CompletionTriggerReason.Insertion, snapshotBeforeEdit, typedChar);
var newSession = Broker.TriggerCompletion(args.TextView, trigger, location, executionContext.OperationContext.UserCancellationToken);
if (newSession is IAsyncCompletionSessionOperations sessionInternal)
{
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Return: created new session");
RealizeVirtualSpaceUpdateApplicableToSpan(sessionInternal, args.TextView);
}
location = args.TextView.Caret.Position.BufferPosition; // Buffer may have changed. Update the location.
newSession?.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
+
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Return: finish");
});
}
@@ -514,6 +553,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
var session = Broker.GetSession(args.TextView);
if (session != null)
{
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Tab: begin commit");
+
var commitBehavior = session.Commit(typedChar, executionContext.OperationContext.UserCancellationToken);
session.Dismiss();
@@ -523,17 +565,39 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
if ((commitBehavior & CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers) == 0
|| CompletionUtilities.IsDebuggerTextView(args.TextView)
|| CompletionUtilities.IsImmediateTextView(args.TextView))
+ {
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Tab: do nothing after commit", commitBehavior);
return;
+ }
}
var snapshotBeforeEdit = args.TextView.TextSnapshot;
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Tab: next handler");
nextCommandHandler();
+ if (args.TextView.TextSnapshot == snapshotBeforeEdit)
+ {
+ // Buffer has not changed. Don't invoke completion.
+ return;
+ }
+
// Buffer has changed. Update it for when we try to trigger new session.
var location = args.TextView.Caret.Position.BufferPosition;
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Tab: try make new session");
var trigger = new CompletionTrigger(CompletionTriggerReason.Insertion, snapshotBeforeEdit, typedChar);
var newSession = Broker.TriggerCompletion(args.TextView, trigger, location, executionContext.OperationContext.UserCancellationToken);
+ if (newSession is IAsyncCompletionSessionOperations sessionInternal)
+ {
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Tab: created new session");
+ }
newSession?.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
+
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("Commit with tab: finish");
});
}
@@ -547,6 +611,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
{
RunOnceIfAvailable(args, nextCommandHandler, () =>
{
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("TypeChar: ", args.TypedChar);
+
var view = args.TextView;
var location = view.Caret.Position.BufferPosition;
var initialTextSnapshot = args.SubjectBuffer.CurrentSnapshot;
@@ -566,33 +633,68 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
((AsyncCompletionSession)sessionToCommit).IgnoreCaretMovement(ignore: true);
}
+ // BraceCompletionManager is accessible through well known property name.
+ IBraceCompletionManager braceCompletionManager;
+ args.TextView.Properties.TryGetProperty("BraceCompletionManager", out braceCompletionManager);
+ var braceCompletionSessionsBeforeEdit = braceCompletionManager?.ActiveSessionCount;
var snapshotBeforeEdit = args.TextView.TextSnapshot;
+
// Execute other commands in the chain to see the change in the buffer. This includes brace completion.
- // Note regarding undo: This will be 2nd in the undo stack
+
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("TypeChar invokes nextCommandHandler...");
+
nextCommandHandler();
- // if on different version than initialTextSnapshot, we will NOT rollback and we will NOT replay the nextCommandHandler
- // DP to figure out why ShouldCommit returns false or Commit doesn't do anything
- var braceCompletionSpecialHandling = args.SubjectBuffer.CurrentSnapshot.Version == initialTextSnapshot.Version;
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("...TypeChar invoked nextCommandHandler");
+
+ var braceCompletionSessionAfterEdit = braceCompletionManager?.ActiveSessionCount;
+
+ if (args.TextView.TextSnapshot == snapshotBeforeEdit
+ && braceCompletionSessionAfterEdit == braceCompletionSessionsBeforeEdit)
+ {
+ // Buffer has not changed, and neither did state of brace completion.
+ // Don't invoke completion.
+ return;
+ }
+
+ // If brace completion just closed, we will not undo the last type char
+ var dontUndoBraceCompletion = braceCompletionSessionAfterEdit < braceCompletionSessionsBeforeEdit;
// Pass location from before calling nextCommandHandler
// so that extenders get the same view of the buffer in both ShouldCommit and Commit
if (sessionToCommit?.ShouldCommit(args.TypedChar, location, executionContext.OperationContext.UserCancellationToken) == true)
{
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("TypeChar: ShouldCommit");
+
// Buffer has changed, update the snapshot
location = view.Caret.Position.BufferPosition;
// Note regarding undo: this transaction will be 1st in the undo stack
using (var undoTransaction = new CaretPreservingEditTransaction("Completion", view, UndoHistoryRegistry, EditorOperationsFactoryService))
{
- if (!braceCompletionSpecialHandling)
+ // Undo the typechar, because that's what language service expects
+ // Note that Roslyn expects brace to be there, because it can't handle undoing brace completion
+ if (!dontUndoBraceCompletion)
+ {
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("TypeChar: commit. roll back");
UndoUtilities.RollbackToBeforeTypeChar(initialTextSnapshot, args.SubjectBuffer);
- // Now the buffer doesn't have the commit character nor the matching brace, if any
+ }
+ // Now the buffer doesn't have the commit character, but may have a matching brace
var commitBehavior = sessionToCommit.Commit(args.TypedChar, executionContext.OperationContext.UserCancellationToken);
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("TypeChar: commit. behavior: ", commitBehavior);
- if (!braceCompletionSpecialHandling && (commitBehavior & CommitBehavior.SuppressFurtherTypeCharCommandHandlers) == 0)
+ if (!dontUndoBraceCompletion && (commitBehavior & CommitBehavior.SuppressFurtherTypeCharCommandHandlers) == 0)
+ {
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("TypeChar: commit. nextCommandHandler");
nextCommandHandler(); // Replay the key, so that we get brace completion.
+ }
// Complete the transaction before stopping it.
undoTransaction.Complete();
@@ -612,10 +714,16 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
var session = Broker.GetSession(args.TextView);
if (session != null)
{
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("TypeChar: Update session");
+
session.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
}
else
{
+ if (DiagnosticLogger.IsLoggingEnabled(args.TextView))
+ DiagnosticLogger.Add("TypeChar: Create new session");
+
var newSession = Broker.TriggerCompletion(args.TextView, trigger, location, executionContext.OperationContext.UserCancellationToken);
newSession?.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
}
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionModel.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionModel.cs
index 2d75b0e..bc1fef4 100644
--- a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionModel.cs
+++ b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionModel.cs
@@ -31,6 +31,11 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
public readonly ImmutableArray<CompletionFilterWithState> Filters;
/// <summary>
+ /// Invoked expansions involved in this completion model.
+ /// </summary>
+ public readonly ImmutableArray<CompletionExpander> PrimedExpanders;
+
+ /// <summary>
/// Items to be displayed in the UI.
/// </summary>
public readonly ImmutableArray<CompletionItemWithHighlight> PresentedItems;
@@ -85,20 +90,21 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
/// </summary>
public static CompletionModel GetUninitializedModel(ITextSnapshot snapshot)
{
- return new CompletionModel(default, default, snapshot, default, default, default, default, default, default, default, default, default, uninitialized: true);
+ return new CompletionModel(default, default, snapshot, default, default, default, default, default, default, default, default, default, default, uninitialized: true);
}
/// <summary>
/// Constructor for the initial model
/// </summary>
public CompletionModel(ImmutableArray<CompletionItem> initialItems, ImmutableArray<CompletionItem> sortedItems,
- ITextSnapshot snapshot, ImmutableArray<CompletionFilterWithState> filters, bool useSoftSelection,
+ ITextSnapshot snapshot, ImmutableArray<CompletionFilterWithState> filters, ImmutableArray<CompletionExpander> primedExpanders, bool useSoftSelection,
bool displaySuggestionItem, bool selectSuggestionItem, CompletionItem suggestionItem)
{
InitialItems = initialItems;
SortedItems = sortedItems;
Snapshot = snapshot;
Filters = filters;
+ PrimedExpanders = primedExpanders;
SelectedIndex = 0;
UseSoftSelection = useSoftSelection;
DisplaySuggestionItem = displaySuggestionItem;
@@ -113,7 +119,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
/// Private constructor for the With* methods
/// </summary>
private CompletionModel(ImmutableArray<CompletionItem> initialItems, ImmutableArray<CompletionItem> sortedItems,
- ITextSnapshot snapshot, ImmutableArray<CompletionFilterWithState> filters, ImmutableArray<CompletionItemWithHighlight> presentedItems,
+ ITextSnapshot snapshot, ImmutableArray<CompletionFilterWithState> filters, ImmutableArray<CompletionExpander> primedExpanders, ImmutableArray<CompletionItemWithHighlight> presentedItems,
bool useSoftSelection, bool displaySuggestionItem, int selectedIndex, bool selectSuggestionItem, CompletionItem suggestionItem,
CompletionItem uniqueItem, bool applicableToSpanWasEmpty, bool uninitialized)
{
@@ -121,6 +127,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
SortedItems = sortedItems;
Snapshot = snapshot;
Filters = filters;
+ PrimedExpanders = primedExpanders;
PresentedItems = presentedItems;
SelectedIndex = selectedIndex;
UseSoftSelection = useSoftSelection;
@@ -139,6 +146,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
sortedItems: SortedItems,
snapshot: Snapshot,
filters: Filters,
+ primedExpanders: PrimedExpanders,
presentedItems: newPresentedItems, // Updated
useSoftSelection: UseSoftSelection,
displaySuggestionItem: DisplaySuggestionItem,
@@ -158,6 +166,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
sortedItems: SortedItems,
snapshot: newSnapshot, // Updated
filters: Filters,
+ primedExpanders: PrimedExpanders,
presentedItems: PresentedItems,
useSoftSelection: UseSoftSelection,
displaySuggestionItem: DisplaySuggestionItem,
@@ -177,6 +186,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
sortedItems: SortedItems,
snapshot: Snapshot,
filters: newFilters, // Updated
+ primedExpanders: PrimedExpanders,
presentedItems: PresentedItems,
useSoftSelection: UseSoftSelection,
displaySuggestionItem: DisplaySuggestionItem,
@@ -196,6 +206,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
sortedItems: SortedItems,
snapshot: Snapshot,
filters: Filters,
+ primedExpanders: PrimedExpanders,
presentedItems: PresentedItems,
useSoftSelection: preserveSoftSelection ? UseSoftSelection : false, // Updated conditionally
displaySuggestionItem: DisplaySuggestionItem,
@@ -215,6 +226,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
sortedItems: SortedItems,
snapshot: Snapshot,
filters: Filters,
+ primedExpanders: PrimedExpanders,
presentedItems: PresentedItems,
useSoftSelection: false, // Explicit selection and soft selection are mutually exclusive
displaySuggestionItem: DisplaySuggestionItem,
@@ -234,6 +246,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
sortedItems: SortedItems,
snapshot: Snapshot,
filters: Filters,
+ primedExpanders: PrimedExpanders,
presentedItems: PresentedItems,
useSoftSelection: UseSoftSelection | newDisplaySuggestionItem, // Enabling suggestion mode also enables soft selection
displaySuggestionItem: newDisplaySuggestionItem, // Updated
@@ -257,6 +270,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
sortedItems: SortedItems,
snapshot: Snapshot,
filters: Filters,
+ primedExpanders: PrimedExpanders,
presentedItems: PresentedItems,
useSoftSelection: UseSoftSelection,
displaySuggestionItem: DisplaySuggestionItem,
@@ -276,6 +290,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
sortedItems: SortedItems,
snapshot: Snapshot,
filters: Filters,
+ primedExpanders: PrimedExpanders,
presentedItems: PresentedItems,
useSoftSelection: newSoftSelection, // Updated
displaySuggestionItem: DisplaySuggestionItem,
@@ -296,6 +311,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
sortedItems: SortedItems,
snapshot: snapshot, // Updated
filters: filters, // Updated
+ primedExpanders: PrimedExpanders,
presentedItems: presentedItems, // Updated
useSoftSelection: UseSoftSelection,
displaySuggestionItem: DisplaySuggestionItem,
@@ -315,6 +331,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
sortedItems: SortedItems,
snapshot: Snapshot,
filters: Filters,
+ primedExpanders: PrimedExpanders,
presentedItems: PresentedItems,
useSoftSelection: UseSoftSelection,
displaySuggestionItem: DisplaySuggestionItem,
@@ -326,5 +343,29 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
uninitialized: Uninitialized
);
}
+
+ internal CompletionModel WithExpansion(
+ ImmutableArray<CompletionItem> expandedItems,
+ ImmutableArray<CompletionItem> sortedItems,
+ ImmutableArray<CompletionFilterWithState> filters,
+ ImmutableArray<CompletionExpander> primedExpanders)
+ {
+ return new CompletionModel(
+ initialItems: expandedItems, // updated
+ sortedItems: sortedItems, // updated
+ snapshot: Snapshot,
+ filters: filters, // updated
+ primedExpanders: primedExpanders, // Updated
+ presentedItems: PresentedItems,
+ useSoftSelection: UseSoftSelection,
+ displaySuggestionItem: DisplaySuggestionItem,
+ selectedIndex: SelectedIndex,
+ selectSuggestionItem: SelectSuggestionItem,
+ suggestionItem: SuggestionItem,
+ uniqueItem: UniqueItem,
+ applicableToSpanWasEmpty: ApplicableToSpanWasEmpty,
+ uninitialized: Uninitialized
+ );
+ }
}
}
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionSourceConnectionResult.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionSourceConnectionResult.cs
index 2869bea..aecfefe 100644
--- a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionSourceConnectionResult.cs
+++ b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionSourceConnectionResult.cs
@@ -3,28 +3,31 @@ using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
{
- internal struct CompletionSourceConnectionResult
+ internal sealed class CompletionSourceConnectionResult
{
internal bool SourceUsesSuggestionMode { get; set; }
internal SuggestionItemOptions RequestedSuggestionItemOptions { get; set; }
internal InitialSelectionHint InitialSelectionHint { get; set; }
- internal ImmutableArray<CompletionItem> InitialCompletionItems { get; set; }
+ internal ImmutableArray<CompletionItem> Items { get; set; }
+ internal ImmutableArray<CompletionFilterWithState> Filters { get; set; }
internal bool IsCanceled { get; set; }
internal CompletionSourceConnectionResult(bool sourceUsesSuggestionMode,
SuggestionItemOptions requestedSuggestionItemOptions,
InitialSelectionHint initialSelectionHint,
ImmutableArray<CompletionItem> initialCompletionItems,
+ ImmutableArray<CompletionFilterWithState> initialCompletionFilters,
bool isCanceled = false)
{
SourceUsesSuggestionMode = sourceUsesSuggestionMode;
RequestedSuggestionItemOptions = requestedSuggestionItemOptions;
InitialSelectionHint = initialSelectionHint;
- InitialCompletionItems = initialCompletionItems;
+ Items = initialCompletionItems;
+ Filters = initialCompletionFilters;
IsCanceled = isCanceled;
}
internal static CompletionSourceConnectionResult Canceled
- => new CompletionSourceConnectionResult(default, default, default, default, isCanceled: true);
+ => new CompletionSourceConnectionResult(default, default, default, default, default, isCanceled: true);
}
}
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionTelemetry.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionTelemetry.cs
index 6818dd6..01a8c66 100644
--- a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionTelemetry.cs
+++ b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionTelemetry.cs
@@ -64,7 +64,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
internal long BlockingComputationDuration { get; private set; }
// Additional data for the E2E telemetry
- public CompletionSessionState CompletionState { get; private set; }
+ internal CompletionSessionState CompletionState { get; private set; }
+ internal bool NoChanges { get; private set; }
+ internal bool UserWaitedForNoChanges { get; private set; }
+ internal Dictionary<string, int> BlockingExtensionCounter { get; } = new Dictionary<string, int>();
// Additional parameters related to work done by IAsyncCompletionItemManager
internal bool UserEverScrolled { get; private set; }
@@ -119,11 +122,12 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
NumberOfKeystrokes++;
}
- internal void RecordCommitted(long duration,
+ internal void RecordCommitted(long duration, bool noChanges,
IAsyncCompletionCommitManager manager)
{
CommitManagerName = CompletionTelemetryHost.GetCommitManagerName(manager);
CommitDuration = duration;
+ NoChanges = noChanges;
}
internal void RecordClosing(long duration)
@@ -139,6 +143,8 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
ItemManagerName = CompletionTelemetryHost.GetItemManagerName(itemManager);
PresenterProviderName = CompletionTelemetryHost.GetPresenterProviderName(presenterProvider);
CompletionState = state;
+ if (NoChanges && BlockingComputationDuration > 0)
+ UserWaitedForNoChanges = true;
_telemetryHost.Add(this);
}
@@ -168,6 +174,33 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
{
BlockingComputationDuration = elapsedMilliseconds;
}
+
+ internal void RecordBlockingExtension(object extension)
+ {
+ if (extension == null)
+ return;
+
+ string extensionName;
+ switch (extension)
+ {
+ case IAsyncCompletionSource source:
+ extensionName = CompletionTelemetryHost.GetSourceName(source);
+ break;
+ case IAsyncCompletionItemManager itemManager:
+ extensionName = CompletionTelemetryHost.GetItemManagerName(itemManager);
+ break;
+ case IAsyncCompletionCommitManager commitManager:
+ extensionName = CompletionTelemetryHost.GetCommitManagerName(commitManager);
+ break;
+ default:
+ extensionName = extension.GetType().ToString();
+ break;
+ }
+
+ if (!BlockingExtensionCounter.ContainsKey(extensionName))
+ BlockingExtensionCounter[extensionName] = 0;
+ BlockingExtensionCounter[extensionName]++;
+ }
}
/// <summary>
@@ -241,16 +274,21 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
internal int CommittedThroughClick;
internal int CommittedThroughCompleteWord;
internal int CommittedSuggestionItem;
+ internal int CommittedThroughTypedChar;
internal int Dismissed;
internal int DismissedDueToBackspace;
internal int DismissedDueToCancellation;
internal int DismissedDueToCaretLeaving;
internal int DismissedDuringFiltering;
+ internal int DismissedDueToNoItems;
internal int DismissedDueToNonBlockingMode;
+ internal int DismissedDueToResponsiveMode;
+ internal int DismissedDueToSuggestionMode;
internal int DismissedDueToUnhandledError;
internal int DismissedThroughUI;
internal int DismissedUninitialized;
+ // Measuring distribution of time spent between triggering session and displaying UI or committing the item, whichever is sooner
internal int HistogramBucket25;
internal int HistogramBucket50;
internal int HistogramBucket100;
@@ -261,12 +299,23 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
internal int HistogramBucketLast;
internal int HistogramBucketCanceled;
internal int HistogramBucketInvalid;
+
+ // Measuring distribution of user action and reaction of completion session
+ internal int HistogramNoChanges;
+ internal int HistogramNoChangesAndUserWaited;
+ internal int HistogramNoChangesThroughTypedChar;
+ internal int HistogramNoChangesAndUserWaitedThroughTypedChar;
+ internal int HistogramChanges;
+ internal int HistogramChangesAndUserWaited;
+ internal int HistogramChangesThroughTypedChar;
+ internal int HistogramChangesAndUserWaitedThroughTypedChar;
}
Dictionary<string, AggregateCommitManagerData> CommitManagerData = new Dictionary<string, AggregateCommitManagerData>();
Dictionary<string, AggregateItemManagerData> ItemManagerData = new Dictionary<string, AggregateItemManagerData>();
Dictionary<string, AggregatePresenterData> PresenterData = new Dictionary<string, AggregatePresenterData>();
Dictionary<string, AggregateSourceData> SourceData = new Dictionary<string, AggregateSourceData>();
+ Dictionary<string, int> BlockingExtensionData = new Dictionary<string, int>();
AggregateE2EData E2EData = new AggregateE2EData();
private readonly ILoggingServiceInternal _logger;
@@ -299,6 +348,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
AddCommitManagerData(telemetry, CommitManagerData);
AddPresenterData(telemetry, PresenterData);
AddE2EData(telemetry, E2EData);
+ AddBlockingExtensionData(telemetry, BlockingExtensionData);
}
/// <summary>
@@ -399,16 +449,35 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
(E2ECommittedClick, E2EData.CommittedThroughClick),
(E2ECommittedCompleteWord, E2EData.CommittedThroughCompleteWord),
(E2ECommittedSuggestionItem, E2EData.CommittedSuggestionItem),
+ (E2ECommittedThroughTypedChar, E2EData.CommittedThroughTypedChar),
(E2EDismissedStandard, E2EData.Dismissed),
(E2EDismissedBackspace, E2EData.DismissedDueToBackspace),
(E2EDismissedCancellation, E2EData.DismissedDueToCancellation),
(E2EDismissedCaretLeaving, E2EData.DismissedDueToCaretLeaving),
(E2EDismissedFiltering, E2EData.DismissedDuringFiltering),
+ (E2EDismissedNoItems, E2EData.DismissedDueToNoItems),
(E2EDismissedNonBlocking, E2EData.DismissedDueToNonBlockingMode),
+ (E2EDismissedResponsive, E2EData.DismissedDueToResponsiveMode),
+ (E2EDismissedSuggestion, E2EData.DismissedDueToSuggestionMode),
(E2EDismissedUnhandledError, E2EData.DismissedDueToUnhandledError),
(E2EDismissedUI, E2EData.DismissedThroughUI),
- (E2EDismissedUninitialized, E2EData.DismissedUninitialized)
+ (E2EDismissedUninitialized, E2EData.DismissedUninitialized),
+ (E2EScenarioNoChanges, E2EData.HistogramNoChanges),
+ (E2EScenarioUserWaitedForNoChanges, E2EData.HistogramNoChangesAndUserWaited)
);
+
+ foreach (var data in BlockingExtensionData)
+ {
+ if (data.Value == 0)
+ continue;
+
+ _logger.PostEvent(TelemetryEventType.Operation,
+ BlockingExtensionEventName,
+ TelemetryResult.Success,
+ (BlockingExtensionName, data.Key),
+ (BlockingCount, data.Value)
+ );
+ }
}
/// <summary>
@@ -538,6 +607,9 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
case CompletionSessionState.CommittedThroughCompleteWord:
e2eData.CommittedThroughCompleteWord++;
break;
+ case CompletionSessionState.CommittedThroughTypedChar:
+ e2eData.CommittedThroughTypedChar++;
+ break;
case CompletionSessionState.DismissedDueToBackspace:
e2eData.DismissedDueToBackspace++;
break;
@@ -550,9 +622,18 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
case CompletionSessionState.DismissedDuringFiltering:
e2eData.DismissedDuringFiltering++;
break;
+ case CompletionSessionState.DismissedDueToNoItems:
+ e2eData.DismissedDueToNoItems++;
+ break;
case CompletionSessionState.DismissedDueToNonBlockingMode:
e2eData.DismissedDueToNonBlockingMode++;
break;
+ case CompletionSessionState.DismissedDueToResponsiveMode:
+ e2eData.DismissedDueToResponsiveMode++;
+ break;
+ case CompletionSessionState.DismissedDueToSuggestionMode:
+ e2eData.DismissedDueToSuggestionMode++;
+ break;
case CompletionSessionState.DismissedDueToUnhandledError:
e2eData.DismissedDueToUnhandledError++;
break;
@@ -593,6 +674,42 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
else
e2eData.HistogramBucketLast++;
}
+
+ if (telemetry.CompletionState == CompletionSessionState.Committed || telemetry.CompletionState == CompletionSessionState.CommittedThroughTypedChar || telemetry.CompletionState == CompletionSessionState.CommittedThroughCompleteWord)
+ {
+ if (telemetry.NoChanges)
+ {
+ if (telemetry.CompletionState == CompletionSessionState.CommittedThroughTypedChar && telemetry.UserWaitedForNoChanges)
+ e2eData.HistogramNoChangesAndUserWaitedThroughTypedChar++;
+ else if (telemetry.CompletionState == CompletionSessionState.CommittedThroughTypedChar && !telemetry.UserWaitedForNoChanges)
+ e2eData.HistogramNoChangesThroughTypedChar++;
+ else if (telemetry.CompletionState == CompletionSessionState.CommittedThroughTypedChar && telemetry.UserWaitedForNoChanges)
+ e2eData.HistogramNoChangesAndUserWaited++;
+ else if (telemetry.CompletionState == CompletionSessionState.CommittedThroughTypedChar && !telemetry.UserWaitedForNoChanges)
+ e2eData.HistogramNoChanges++;
+ }
+ else
+ {
+ if (telemetry.CompletionState == CompletionSessionState.CommittedThroughTypedChar && telemetry.UserWaitedForNoChanges)
+ e2eData.HistogramChangesAndUserWaitedThroughTypedChar++;
+ else if (telemetry.CompletionState == CompletionSessionState.CommittedThroughTypedChar && !telemetry.UserWaitedForNoChanges)
+ e2eData.HistogramChangesThroughTypedChar++;
+ else if (telemetry.CompletionState != CompletionSessionState.CommittedThroughTypedChar && telemetry.UserWaitedForNoChanges)
+ e2eData.HistogramChangesAndUserWaited++;
+ else if (telemetry.CompletionState != CompletionSessionState.CommittedThroughTypedChar && !telemetry.UserWaitedForNoChanges)
+ e2eData.HistogramChanges++;
+ }
+ }
+ }
+
+ private static void AddBlockingExtensionData(CompletionSessionTelemetry telemetry, Dictionary<string, int> blockingExtensionData)
+ {
+ foreach (var blockingExtension in telemetry.BlockingExtensionCounter)
+ {
+ if (!blockingExtensionData.ContainsKey(blockingExtension.Key))
+ blockingExtensionData[blockingExtension.Key] = 0;
+ blockingExtensionData[blockingExtension.Key] += blockingExtension.Value;
+ }
}
// Property and event names
@@ -628,6 +745,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
internal const string SourceAverageSetupDuration = "Property.Source.SetupDuration";
internal const string SourceMaxSetupDuration = "Property.Source.MaxSetupDuration";
+ internal const string BlockingExtensionEventName = "VS/Editor/Completion/BlockingExtensionData";
+ internal const string BlockingExtensionName = "Property.Extension.Name";
+ internal const string BlockingCount = "Property.Extension.GetBlockingCount";
+
internal const string E2EEventName = "VS/Editor/Completion/E2EData";
internal const string E2EContentType = "Property.E2E.ContentType";
internal const string E2EBucket25 = "Property.E2E.Bucket.25";
@@ -644,15 +765,21 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
internal const string E2ECommittedClick = "Property.E2E.Committed.ThroughClick";
internal const string E2ECommittedCompleteWord = "Property.E2E.Committed.CompleteWord";
internal const string E2ECommittedSuggestionItem = "Property.E2E.Committed.SuggestionItem";
+ internal const string E2ECommittedThroughTypedChar = "Property.E2E.Committed.TypedChar";
internal const string E2EDismissedStandard = "Property.E2E.Dismissed.Standard";
internal const string E2EDismissedBackspace = "Property.E2E.Dismissed.Backspace";
internal const string E2EDismissedCancellation = "Property.E2E.Dismissed.Cancellation";
internal const string E2EDismissedCaretLeaving = "Property.E2E.Dismissed.CaretLeaving";
internal const string E2EDismissedFiltering = "Property.E2E.Dismissed.Filtering";
+ internal const string E2EDismissedNoItems = "Property.E2E.Dismissed.NoItems";
internal const string E2EDismissedNonBlocking = "Property.E2E.Dismissed.NonBlocking";
+ internal const string E2EDismissedResponsive = "Property.E2E.Dismissed.Responsive";
+ internal const string E2EDismissedSuggestion = "Property.E2E.Dismissed.Suggestion";
internal const string E2EDismissedUnhandledError = "Property.E2E.Dismissed.UnhandledError";
internal const string E2EDismissedUI = "Property.E2E.Dismissed.UI";
internal const string E2EDismissedUninitialized = "Property.E2E.Dismissed.Uninitialized";
+ internal const string E2EScenarioNoChanges = "Property.E2E.Scenario.NoChanges";
+ internal const string E2EScenarioUserWaitedForNoChanges = "Property.E2E.Scenario.UserWaitedForNoChanges";
}
/// <summary>
@@ -666,7 +793,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
/// </summary>
Default,
/// <summary>
- /// Session committed through typing, enter, tab or programmatically
+ /// Session committed through enter, tab or programmatically. Excludes committing through typed char.
/// </summary>
Committed,
/// <summary>
@@ -682,6 +809,10 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
/// </summary>
CommittedSuggestionItem,
/// <summary>
+ /// Session committed through typing
+ /// </summary>
+ CommittedThroughTypedChar,
+ /// <summary>
/// Session dismissed because user erased its contents
/// </summary>
DismissedDueToBackspace,
@@ -698,10 +829,22 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
/// </summary>
DismissedDuringFiltering,
/// <summary>
- /// Session dismissed because computation has not finished before attempt to commit.
+ /// Session dismissed because there was no item to commit
+ /// </summary>
+ DismissedDueToNoItems,
+ /// <summary>
+ /// Session dismissed because computation has not finished before attempt to commit
/// </summary>
DismissedDueToNonBlockingMode,
/// <summary>
+ /// Session dismissed because computation has not finished within grace period before attempt to commit
+ /// </summary>
+ DismissedDueToResponsiveMode,
+ /// <summary>
+ /// Session dismissed because it was in suggestion mode and user did not use tab to commit it
+ /// </summary>
+ DismissedDueToSuggestionMode,
+ /// <summary>
/// Session dismissed because an error brought down the computation
/// </summary>
DismissedDueToUnhandledError,
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionUtilities.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionUtilities.cs
index ceea5ec..8281c6f 100644
--- a/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionUtilities.cs
+++ b/src/Editor/Language/Impl/Language/AsyncCompletion/CompletionUtilities.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
+using System.Threading;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
@@ -43,10 +44,8 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
/// <returns>True if the view has "COMMANDVIEW" text view role.</returns>
internal static bool IsImmediateTextView(ITextView textView) => textView.Roles.Contains("COMMANDVIEW");
- static readonly EditorOptionKey<bool> NonBlockingCompletionOptionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.NonBlockingCompletionOptionName);
static readonly EditorOptionKey<bool> SuggestionModeOptionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.SuggestionModeInCompletionOptionName);
static readonly EditorOptionKey<bool> SuggestionModeInDebuggerCompletionOptionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName);
- private const bool NonBlockingCompletionDefaultValue = false;
private const bool UseSuggestionModeDefaultValue = false;
private const bool UseSuggestionModeInDebuggerCompletionDefaultValue = true;
@@ -72,17 +71,6 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
public override string Name => PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName;
}
- [Export(typeof(EditorOptionDefinition))]
- [Name(PredefinedCompletionNames.NonBlockingCompletionOptionName)]
- class NonBlockingCompletionOptionDefinition : EditorOptionDefinition
- {
- public override object DefaultValue => NonBlockingCompletionDefaultValue;
-
- public override Type ValueType => typeof(bool);
-
- public override string Name => PredefinedCompletionNames.NonBlockingCompletionOptionName;
- }
-
internal static bool GetSuggestionModeOption(ITextView textView)
{
var options = textView.Options.GlobalOptions;
@@ -107,18 +95,31 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
internal static bool GetNonBlockingCompletionOption(ITextView textView)
{
- var options = textView.Options.GlobalOptions;
- if (!(options.IsOptionDefined(NonBlockingCompletionOptionKey, localScopeOnly: false)))
- {
- options.SetOptionValue(NonBlockingCompletionOptionKey, NonBlockingCompletionDefaultValue);
- }
- return options.GetOptionValue(NonBlockingCompletionOptionKey);
+ return textView.Options.GetOptionValue(DefaultOptions.NonBlockingCompletionOptionId);
}
- internal static void SetNonBlockingModeOption(ITextView textView, bool value)
+ internal static bool GetResponsiveCompletionOption(ITextView textView)
{
- var options = textView.Options.GlobalOptions;
- options.SetOptionValue(NonBlockingCompletionOptionKey, value);
+ return textView.Options.GetOptionValue(DefaultOptions.ResponsiveCompletionOptionId)
+ && textView.Options.GetOptionValue(DefaultOptions.RemoteControlledResponsiveCompletionOptionId);
+ }
+
+ internal static int GetResponsiveCompletionThresholdOption(ITextView textView)
+ {
+ return textView.Options.GetOptionValue(DefaultOptions.ResponsiveCompletionThresholdOptionId);
+ }
+
+ internal static CancellationToken GetResponsiveToken(ITextView textView, CancellationToken commandingToken)
+ {
+ var inResponisveMode = CompletionUtilities.GetResponsiveCompletionOption(textView);
+ if (!inResponisveMode)
+ return commandingToken;
+
+ var responsiveCompletionThreshold = CompletionUtilities.GetResponsiveCompletionThresholdOption(textView);
+ var responsiveCancellationSource = new CancellationTokenSource(responsiveCompletionThreshold);
+ var responsiveToken = responsiveCancellationSource.Token;
+ var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(commandingToken, responsiveToken);
+ return combinedCancellationSource.Token;
}
}
}
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/DefaultCompletionItemManager.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/DefaultCompletionItemManager.cs
index 8a4ab8b..125f1b0 100644
--- a/src/Editor/Language/Impl/Language/AsyncCompletion/DefaultCompletionItemManager.cs
+++ b/src/Editor/Language/Impl/Language/AsyncCompletion/DefaultCompletionItemManager.cs
@@ -69,22 +69,38 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
// Perform pattern matching
.Select(completionItem => (completionItem, patternMatcher.TryMatch(completionItem.FilterText)))
// Pick only items that were matched, unless length of filter text is 1
- .Where(n => (filterText.Length == 1 || n.Item2.HasValue));
+ .Where(n => (filterText.Length == 1 || patternMatcher.HasInvalidPattern || n.Item2.HasValue));
// See which filters might be enabled based on the typed code
var textFilteredFilters = matches.SelectMany(n => n.completionItem.Filters).Distinct();
- // When no items are available for a given filter, it becomes unavailable
- var updatedFilters = ImmutableArray.CreateRange(data.SelectedFilters.Select(n => n.WithAvailability(textFilteredFilters.Contains(n.Filter))));
+ // When no items are available for a given filter, it becomes unavailable. Expanders always appear available.
+ var updatedFilters = ImmutableArray.CreateRange(data.SelectedFilters.Select(n => n.WithAvailability(
+ n.Filter is CompletionExpander ? true : textFilteredFilters.Contains(n.Filter))));
// Filter by user-selected filters. The value on availableFiltersWithSelectionState conveys whether the filter is selected.
var filterFilteredList = matches;
- if (data.SelectedFilters.Any(n => n.IsSelected))
+ if (data.SelectedFilters.Any(n => (n.Filter is CompletionExpander)))
{
- filterFilteredList = matches.Where(n => ShouldBeInCompletionList(n.completionItem, data.SelectedFilters));
+ filterFilteredList = matches.Where(n => ShouldBeInExpandedCompletionList(n.completionItem, data.SelectedFilters));
+ }
+ if (data.SelectedFilters.Any(n => !(n.Filter is CompletionExpander) && n.IsSelected))
+ {
+ filterFilteredList = filterFilteredList.Where(n => ShouldBeInCompletionList(n.completionItem, data.SelectedFilters));
+ }
+
+ (CompletionItem completionItem, PatternMatch? patternMatch) bestMatch;
+ if (patternMatcher.HasInvalidPattern)
+ {
+ // In a rare edge case where the pattern is invalid (e.g. it is just punctuation), see if any items match directly what user typed.
+ bestMatch = filterFilteredList.FirstOrDefault(n => string.Equals(n.completionItem.FilterText, filterText, StringComparison.OrdinalIgnoreCase));
+ }
+ else
+ {
+ // 99.% cases fall here
+ bestMatch = filterFilteredList.OrderByDescending(n => n.Item2.HasValue).ThenBy(n => n.Item2).FirstOrDefault();
}
- var bestMatch = filterFilteredList.OrderByDescending(n => n.Item2.HasValue).ThenBy(n => n.Item2).FirstOrDefault();
var listWithHighlights = filterFilteredList.Select(n =>
{
ImmutableArray<Span> safeMatchedSpans = ImmutableArray<Span>.Empty;
@@ -116,6 +132,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
}).ToImmutableArray();
int selectedItemIndex = 0;
+ var selectionHint = UpdateSelectionHint.NoChange;
if (data.DisplaySuggestionItem)
{
selectedItemIndex = -1;
@@ -127,12 +144,13 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
if (listWithHighlights[i].CompletionItem == bestMatch.completionItem)
{
selectedItemIndex = i;
+ selectionHint = UpdateSelectionHint.Selected;
break;
}
}
}
- return Task.FromResult(new FilteredCompletionModel(listWithHighlights, selectedItemIndex, updatedFilters));
+ return Task.FromResult(new FilteredCompletionModel(listWithHighlights, selectedItemIndex, updatedFilters, selectionHint, centerSelection: true, uniqueItem: null));
}
Task<ImmutableArray<CompletionItem>> IAsyncCompletionItemManager.SortCompletionListAsync
@@ -147,7 +165,8 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
CompletionItem item,
ImmutableArray<CompletionFilterWithState> filtersWithState)
{
- foreach (var filterWithState in filtersWithState.Where(n => n.IsSelected))
+ // Filter out items which don't have a filter which matches selected Filter Button
+ foreach (var filterWithState in filtersWithState.Where(n => !(n.Filter is CompletionExpander) && n.IsSelected))
{
if (item.Filters.Any(n => n == filterWithState.Filter))
{
@@ -157,6 +176,21 @@ namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implement
return false;
}
+ private static bool ShouldBeInExpandedCompletionList(
+ CompletionItem item,
+ ImmutableArray<CompletionFilterWithState> filtersWithState)
+ {
+ // Remove items which have a filter which matches deselected Expander Button
+ foreach (var filterWithState in filtersWithState.Where(n => n.Filter is CompletionExpander && !(n.IsSelected)))
+ {
+ if (item.Filters.Any(n => n is CompletionExpander && n == filterWithState.Filter))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
#endregion
}
}
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/DeferredBlockingOperation.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/DeferredBlockingOperation.cs
new file mode 100644
index 0000000..8d8384a
--- /dev/null
+++ b/src/Editor/Language/Impl/Language/AsyncCompletion/DeferredBlockingOperation.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Threading;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.Implementation.AsyncCompletion
+{
+ internal class DeferredBlockingOperation<T>
+ {
+ public JoinableTask<T> Operation { get; }
+
+ private CancellationTokenSource CancellationSource { get; }
+ private bool _canceled = false;
+
+ /// <summary>
+ /// Create instance of <see cref="DeferredBlockingOperation"/>, which wraps a blocking <paramref name="operation"/>
+ /// that is immediately run on the background thread, can be canceled via <paramref name="token"/>
+ /// and accessed via <see cref="Operation"/>
+ /// </summary>
+ /// <param name="jtc">Reference to <see cref="JoinableTaskContext"/></param>
+ /// <param name="operation">Blocking operation</param>
+ /// <param name="token">Token used to cancel the blocking operation</param>
+ public DeferredBlockingOperation(JoinableTaskContext jtc, Func<CancellationToken, Task<T>> operation, CancellationToken token)
+ {
+ CancellationSource = new CancellationTokenSource();
+ var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationSource.Token, token);
+ Operation = jtc.Factory.RunAsync<T>(async () =>
+ {
+ await TaskScheduler.Default; // switch to background thread
+ return await operation(linkedSource.Token).ConfigureAwait(false); // run the blocking operation
+ });
+ }
+
+ internal void Cancel()
+ {
+ if (_canceled)
+ return;
+
+ CancellationSource.Cancel();
+ CancellationSource.Dispose();
+ _canceled = true;
+ }
+ }
+}
diff --git a/src/Editor/Language/Impl/Language/AsyncCompletion/UseAsyncCompletionOptionDefinition.cs b/src/Editor/Language/Impl/Language/AsyncCompletion/UseAsyncCompletionOptionDefinition.cs
deleted file mode 100644
index 5f648c7..0000000
--- a/src/Editor/Language/Impl/Language/AsyncCompletion/UseAsyncCompletionOptionDefinition.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.Composition;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.VisualStudio.Text.Editor;
-using Microsoft.VisualStudio.Utilities;
-
-namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
-{
- [Export(typeof(EditorOptionDefinition))]
- [Name(OptionName)]
- internal class UseAsyncCompletionOptionDefinition : EditorOptionDefinition
- {
- public const string OptionName = "UseAsyncCompletion";
-
- /// <summary>
- /// The meaning of this option definition's values:
- /// -1 - user disabled async completion
- /// 0 - no changes from the user; check the experimentation service for whether to use async completion
- /// 1 - user enabled async completion
- /// </summary>
- public override object DefaultValue => 0;
-
- public override Type ValueType => typeof(int);
-
- public override string Name => OptionName;
- }
-}
diff --git a/src/Editor/Text/Def/Internal/TextUI/IBraceCompletionManager.cs b/src/Editor/Text/Def/Internal/TextUI/IBraceCompletionManager.cs
index cb633af..ebc02b6 100644
--- a/src/Editor/Text/Def/Internal/TextUI/IBraceCompletionManager.cs
+++ b/src/Editor/Text/Def/Internal/TextUI/IBraceCompletionManager.cs
@@ -23,6 +23,11 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion
bool HasActiveSessions { get; }
/// <summary>
+ /// Returns number of currently active sessions.
+ /// </summary>
+ int ActiveSessionCount { get; }
+
+ /// <summary>
/// Opening brace characters the brace completion manager is currently registered to handle.
/// </summary>
string OpeningBraces { get; }
diff --git a/src/Editor/Text/Def/TextData/Model/IExtensionErrorHandler.cs b/src/Editor/Text/Def/TextData/Model/IExtensionErrorHandler.cs
index 3a9a071..caf1057 100644
--- a/src/Editor/Text/Def/TextData/Model/IExtensionErrorHandler.cs
+++ b/src/Editor/Text/Def/TextData/Model/IExtensionErrorHandler.cs
@@ -16,7 +16,7 @@ namespace Microsoft.VisualStudio.Text
public interface IExtensionErrorHandler
{
/// <summary>
- /// Notifies that an exception has occured.
+ /// Logs an exception to ActivityLogs and the telemetry, and notifies that an exception has occured.
/// </summary>
/// <param name="sender">The extension object or event handler that threw the exception.</param>
/// <param name="exception">The exception that was thrown.</param>
diff --git a/src/Editor/Text/Def/TextData/Model/IExtensionErrorHandler2.cs b/src/Editor/Text/Def/TextData/Model/IExtensionErrorHandler2.cs
new file mode 100644
index 0000000..811bef7
--- /dev/null
+++ b/src/Editor/Text/Def/TextData/Model/IExtensionErrorHandler2.cs
@@ -0,0 +1,25 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+using System;
+
+namespace Microsoft.VisualStudio.Text
+{
+ /// <summary>
+ /// Allows editor hosts to detect exceptions that get captured at extension points.
+ /// </summary>
+ /// <remarks>This is a MEF component part, and should be imported as follows:
+ /// [Import]
+ /// IExtensionErrorHandler2 errorHandler = null;
+ /// </remarks>
+ public interface IExtensionErrorHandler2 : IExtensionErrorHandler
+ {
+ /// <summary>
+ /// Logs an exception to ActivityLogs and the telemetry.
+ /// </summary>
+ /// <param name="sender">The extension object or event handler that threw the exception.</param>
+ /// <param name="exception">The exception to log.</param>
+ void LogError(object sender, Exception exception);
+ }
+}
diff --git a/src/Editor/Text/Def/TextLogic/AssemblyInfo.cs b/src/Editor/Text/Def/TextLogic/AssemblyInfo.cs
index 6b99226..9f96b01 100644
--- a/src/Editor/Text/Def/TextLogic/AssemblyInfo.cs
+++ b/src/Editor/Text/Def/TextLogic/AssemblyInfo.cs
@@ -27,3 +27,6 @@ using System.Security.Permissions;
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Commanding.Implementation, PublicKey=" + ThisAssembly.PublicKey)]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Platform.VSEditor, PublicKey=" + ThisAssembly.PublicKey)]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Implementation, PublicKey=" + ThisAssembly.PublicKey)]
+[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Text.UI.Utilities, PublicKey=" + ThisAssembly.PublicKey)]
+[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Language.Implementation, PublicKey=" + ThisAssembly.PublicKey)]
+[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Text.TextViewUnitTestHelper, PublicKey=" + ThisAssembly.PublicKey)]
diff --git a/src/Editor/Text/Def/TextLogic/EditorOptions/DefaultOptions.cs b/src/Editor/Text/Def/TextLogic/EditorOptions/DefaultOptions.cs
index 996e758..5ddac79 100644
--- a/src/Editor/Text/Def/TextLogic/EditorOptions/DefaultOptions.cs
+++ b/src/Editor/Text/Def/TextLogic/EditorOptions/DefaultOptions.cs
@@ -249,6 +249,52 @@ namespace Microsoft.VisualStudio.Text.Editor
/// </summary>
internal static readonly EditorOptionKey<bool> EnableTypingLatencyGuardOptionId = new EditorOptionKey<bool>(EnableTypingLatencyGuardOptionName);
internal const string EnableTypingLatencyGuardOptionName = "EnableTypingLatencyGuard";
+
+ /// <summary>
+ /// Option that defines the fallback font for the editor.
+ /// </summary>
+ /// <remarks>
+ /// Note that, unlike most other options, this value is only checked once at startup on <see cref="IEditorOptionsFactoryService.GlobalOptions"/>
+ /// and we do not react to changes.
+ /// </remarks>
+ public static readonly EditorOptionKey<string> FallbackFontId = new EditorOptionKey<string>(FallbackFontName);
+ public const string FallbackFontName = "FallbackFont";
+
+ /// <summary>
+ /// Option that defines when Editor should not block waiting for computation of completion items,
+ /// and either use the last good computed set of completion items, or dismiss completion if no completion items were computed so far.
+ /// </summary>
+ public static readonly EditorOptionKey<bool> NonBlockingCompletionOptionId = new EditorOptionKey<bool>(NonBlockingCompletionOptionName);
+ public const string NonBlockingCompletionOptionName = "NonBlockingCompletion";
+
+ /// <summary>
+ /// Option that defines how long Editor should block waiting for computation of completion items, in miliseconds,
+ /// and either use the last good computed set of completion items, or dismiss completion if no completion items were computed so far.
+ /// </summary>
+ public static readonly EditorOptionKey<bool> ResponsiveCompletionOptionId = new EditorOptionKey<bool>(ResponsiveCompletionOptionName);
+ public const string ResponsiveCompletionOptionName = "ResponsiveCompletion";
+
+ /// <summary>
+ /// Option that defines how long Editor should block waiting for computation of completion items, in miliseconds,
+ /// and either use the last good computed set of completion items, or dismiss completion if no completion items were computed so far.
+ /// </summary>
+ public static readonly EditorOptionKey<int> ResponsiveCompletionThresholdOptionId = new EditorOptionKey<int>(ResponsiveCompletionThresholdOptionName);
+ public const string ResponsiveCompletionThresholdOptionName = "ResponsiveCompletionThreshold";
+
+ /// <summary>
+ /// Option that keeps track of whether the responsive mode is enabled using remotely controlled feature flags.
+ /// If set to false, the feature is off, despite user's choice stored in <see cref="ResponsiveCompletionOptionId"/>.
+ /// </summary>
+ internal static readonly EditorOptionKey<bool> RemoteControlledResponsiveCompletionOptionId = new EditorOptionKey<bool>(RemoteControlledResponsiveCompletionOptionName);
+ internal const string RemoteControlledResponsiveCompletionOptionName = "RemoteControlledResponsiveCompletion";
+
+ /// <summary>
+ /// Option that keeps track of whether user toggled the <see cref="DiagnosticModeOptionId"/>.
+ /// If set to true, Editor will produce a detailed log for a particular scenario of interest.
+ /// </summary>
+ internal static readonly EditorOptionKey<bool> DiagnosticModeOptionId = new EditorOptionKey<bool>(DiagnosticModeOptionName);
+ internal const string DiagnosticModeOptionName = "DiagnosticMode";
+
#endregion
}
@@ -519,6 +565,9 @@ namespace Microsoft.VisualStudio.Text.Editor
public override EditorOptionKey<bool> Key { get { return DefaultOptions.EnableTypingLatencyGuardOptionId; } }
}
+ // The option definition for DefaultOptions.FallbackFontId is in the implementation DLLs (since the name of the default fallback will depend
+ // on the rendering system).
+
/// <summary>
/// The option definition that determines maximum allowed typing latency value in milliseconds. Its value comes either
/// from remote settings or from <see cref="UserCustomMaximumTypingLatencyOption"/> if user specifies it in
@@ -550,5 +599,72 @@ namespace Microsoft.VisualStudio.Text.Editor
public override int Default { get { return Timeout.Infinite; } }
public override EditorOptionKey<int> Key { get { return DefaultOptions.UserCustomMaximumTypingLatencyOptionId; } }
}
+
+ /// <summary>
+ /// The option definition that determines whether editor uses non blocking completion mode,
+ /// where editor does not wait for completion items to arrive when user presses a commit character.
+ /// This option is not exposed to the users. It is controllable by laguage services.
+ /// </summary>
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(DefaultOptions.NonBlockingCompletionOptionName)]
+ public sealed class NonBlockingCompletionOption : EditorOptionDefinition<bool>
+ {
+ public override bool Default => false;
+ public override EditorOptionKey<bool> Key => DefaultOptions.NonBlockingCompletionOptionId;
+ }
+
+ /// <summary>
+ /// The option definition that determines whether editor uses responsive completion mode,
+ /// where editor waits short amount of time for completion items when user presses a commit character.
+ /// If completion items still don't exist after the delay, completion is dismissed.
+ /// This option is exposed to the users at Tools/Options/Text Editor/Advanced page.
+ /// </summary>
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(DefaultOptions.ResponsiveCompletionOptionName)]
+ public sealed class ResponsiveCompletionOption : EditorOptionDefinition<bool>
+ {
+ public override bool Default => true;
+ public override EditorOptionKey<bool> Key => DefaultOptions.ResponsiveCompletionOptionId;
+ }
+
+ /// <summary>
+ /// The option definition that determines the maximum allowed delay in responsive completion mode,
+ /// where editor waits specified amount of time for completion items when user presses a commit character.
+ /// If completion items still don't exist after the delay, completion is dismissed.
+ /// This option is not exposed to the users. It is controllable by remote setting.
+ /// </summary>
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(DefaultOptions.ResponsiveCompletionThresholdOptionName)]
+ public sealed class ResponsiveCompletionThresholdOption : EditorOptionDefinition<int>
+ {
+ public override int Default => 250;
+ public override EditorOptionKey<int> Key => DefaultOptions.ResponsiveCompletionThresholdOptionId;
+ }
+
+ /// <summary>
+ /// The option definition that determines whether responsive mode should be disabled.
+ /// This option is set using remotely controllable feature flag, and is set to <c>true</c>
+ /// so that responsive mode remains enabled when feature flag service may not be reached.
+ /// </summary>
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(DefaultOptions.RemoteControlledResponsiveCompletionOptionName)]
+ internal sealed class RemoteControlledResponsiveCompletionOption : EditorOptionDefinition<bool>
+ {
+ public override bool Default => true;
+ public override EditorOptionKey<bool> Key => DefaultOptions.RemoteControlledResponsiveCompletionOptionId;
+ }
+
+ /// <summary>
+ /// The option definition that puts Editor in a special diagnostic mode
+ /// where <c>DiagnosticLogger</c> class stores logs that can be later retrieved from a crash dump.
+ /// </summary>
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(DefaultOptions.DiagnosticModeOptionName)]
+ internal sealed class DiagnosticModeOption : EditorOptionDefinition<bool>
+ {
+ public override bool Default => false;
+ public override EditorOptionKey<bool> Key => DefaultOptions.DiagnosticModeOptionId;
+ }
+
#endregion
}
diff --git a/src/Editor/Text/Def/TextLogic/PatternMatching/IPatternMatcher.cs b/src/Editor/Text/Def/TextLogic/PatternMatching/IPatternMatcher.cs
index 27e6e2a..7851134 100644
--- a/src/Editor/Text/Def/TextLogic/PatternMatching/IPatternMatcher.cs
+++ b/src/Editor/Text/Def/TextLogic/PatternMatching/IPatternMatcher.cs
@@ -39,5 +39,11 @@ namespace Microsoft.VisualStudio.Text.PatternMatching
/// To determine sort order, call <see cref="PatternMatch.CompareTo(PatternMatch)"/>.
/// </remarks>
PatternMatch? TryMatch(string candidate);
+
+ /// <summary>
+ /// Determines whether given pattern is invalid,
+ /// in which case <see cref="TryMatch(string)"/> would return no <see cref="PatternMatch"/>es.
+ /// </summary>
+ bool HasInvalidPattern { get; }
}
}
diff --git a/src/Editor/Text/Def/TextLogic/PatternMatching/IPatternMatcherFactory2.cs b/src/Editor/Text/Def/TextLogic/PatternMatching/IPatternMatcherFactory2.cs
new file mode 100644
index 0000000..1b58499
--- /dev/null
+++ b/src/Editor/Text/Def/TextLogic/PatternMatching/IPatternMatcherFactory2.cs
@@ -0,0 +1,56 @@
+namespace Microsoft.VisualStudio.Text.PatternMatching
+{
+ using Microsoft.VisualStudio.Text.PatternMatching;
+
+ /// <summary>
+ /// Provides instances of a <see cref="IPatternMatcher"/> for a given
+ /// search string and creation options.
+ /// </summary>
+ /// <remarks>This is a MEF component part, and should be imported as follows:
+ /// [Import]
+ /// IPatternMatcherFactory2 factory = null;
+ /// </remarks>
+ public interface IPatternMatcherFactory2 : IPatternMatcherFactory
+ {
+ /// <summary>
+ /// Gets an <see cref="IPatternMatcher"/> given a search pattern and search options.
+ /// </summary>
+ /// <param name="pattern">Describes the search pattern that candidate strings will be compared against for relevancy.</param>
+ /// <param name="creationOptions">Defines parameters for what should be considered relevant in a match.</param>
+ /// <param name="linkedMatcher">A matcher whose cache should be shared with the created matcher.</param>
+ /// <remarks>
+ /// <para>
+ /// As opposed to <see cref="IPatternMatcherFactory.CreatePatternMatcher(string, PatternMatcherCreationOptions)"/>, this overload
+ /// creates a <see cref="IPatternMatcher"/> with a shared cache. Use this overload in contexts with frequently changing <paramref name="pattern"/>s
+ /// to reduce allocations and throw-away work. Note that sharing the cache between <see cref="IPatternMatcher"/>s used from multiple
+ /// threads may lead to lock contention. It's recommended to profile prior to opting in.
+ /// </para>
+ /// <para>
+ /// This pattern matcher uses the concepts of a 'Pattern' and a 'Candidate' to to differentiate between what the user types to search
+ /// and what the system compares against. The pattern and some <see cref="PatternMatcherCreationOptions"/> are specified in here in order to obtain an <see cref="IPatternMatcher"/>.
+ ///
+ /// The user can then call <see cref="IPatternMatcher.TryMatch(string)"/> repeatedly with multiple candidates to filter out non-matches, and obtain sortable <see cref="PatternMatch"/> objects to help decide
+ /// what the user actually wanted.
+ ///
+ /// A few examples are useful here. Suppose the user obtains an IPatternMatcher using the following:
+ /// Pattern = "PatMat"
+ ///
+ /// The following calls to TryMatch could expect these results:
+ /// Candidate = "PatternMatcher"
+ /// Returns a match containing <see cref="PatternMatchKind.CamelCaseExact"/>.
+ ///
+ /// Candidate = "IPatternMatcher"
+ /// Returns a match containing <see cref="PatternMatchKind.CamelCaseSubstring"/>
+ ///
+ /// Candidate = "patmat"
+ /// Returns a match containing <see cref="PatternMatchKind.Exact"/>, but <see cref="PatternMatch.IsCaseSensitive"/> will be false.
+ ///
+ /// Candidate = "Not A Match"
+ /// Returns <see langword="null"/>.
+ ///
+ /// To determine sort order, call <see cref="PatternMatch.CompareTo(PatternMatch)"/>.
+ /// </para>
+ /// </remarks>
+ IPatternMatcher CreatePatternMatcher(string pattern, PatternMatcherCreationOptions creationOptions, IPatternMatcher linkedMatcher);
+ }
+}
diff --git a/src/Editor/Text/Impl/BraceCompletion/BraceCompletionManager.cs b/src/Editor/Text/Impl/BraceCompletion/BraceCompletionManager.cs
index b5f9689..8346c6c 100644
--- a/src/Editor/Text/Impl/BraceCompletion/BraceCompletionManager.cs
+++ b/src/Editor/Text/Impl/BraceCompletion/BraceCompletionManager.cs
@@ -8,7 +8,6 @@
namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
{
using Microsoft.VisualStudio.Text.Editor;
- using Microsoft.VisualStudio.Text.Utilities;
using Microsoft.VisualStudio.Utilities;
using System;
using System.Diagnostics;
@@ -74,6 +73,11 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
get { return _stack.TopSession != null; }
}
+ public int ActiveSessionCount
+ {
+ get { return _stack.Sessions.Count; }
+ }
+
public string OpeningBraces
{
get
@@ -97,7 +101,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
IBraceCompletionSession session = _stack.TopSession;
// check for an existing session first
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: session, () =>
{
if (session.ClosingBrace.Equals(character) && IsCaretOnBuffer(session.SubjectBuffer))
{
@@ -149,7 +153,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
}
else if (_postSession != null)
{
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: _postSession, () =>
{
if (_postSession.ClosingBrace.Equals(character))
{
@@ -172,7 +176,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
{
IBraceCompletionSession session = _stack.TopSession;
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: session, () =>
{
if (IsSingleLine(session.OpeningPoint, session.ClosingPoint))
{
@@ -193,7 +197,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
{
if (_postSession != null)
{
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: _postSession, () =>
{
_postSession.PostTab();
});
@@ -212,7 +216,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
{
IBraceCompletionSession session = _stack.TopSession;
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: session, () =>
{
if (session.OpeningPoint != null && session.ClosingPoint != null)
{
@@ -233,7 +237,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
{
if (_postSession != null)
{
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: _postSession, () =>
{
_postSession.PostBackspace();
});
@@ -252,7 +256,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
{
IBraceCompletionSession session = _stack.TopSession;
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: session, () =>
{
if (session.OpeningPoint != null && session.ClosingPoint != null)
{
@@ -273,7 +277,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
{
if (_postSession != null)
{
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: _postSession, () =>
{
_postSession.PostDelete();
});
@@ -292,7 +296,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
{
IBraceCompletionSession session = _stack.TopSession;
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: session, () =>
{
if (IsSingleLine(session.OpeningPoint, session.ClosingPoint))
{
@@ -313,7 +317,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
{
if (_postSession != null)
{
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: _postSession, () =>
{
_postSession.PostReturn();
});
@@ -330,17 +334,29 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
{
_textView.Closed += textView_Closed;
_textView.Options.OptionChanged += Options_OptionChanged;
+ _textView.TextDataModel.ContentTypeChanged += OnContentTypeChanged;
+ }
+
+ private void OnContentTypeChanged(object sender, TextDataModelContentTypeChangedEventArgs e)
+ {
+ // Unhook everything if the view's content type goes inert.
+ if (!e.AfterContentType.IsOfType(StandardContentTypeNames.Any))
+ {
+ this.textView_Closed(null, null);
+ }
}
private void textView_Closed(object sender, EventArgs e)
{
UnregisterEvents();
+ _textView.Properties.RemoveProperty("BraceCompletionManager");
}
private void UnregisterEvents()
{
_textView.Closed -= textView_Closed;
_textView.Options.OptionChanged -= Options_OptionChanged;
+ _textView.TextDataModel.ContentTypeChanged -= OnContentTypeChanged;
}
private void Options_OptionChanged(object sender, EditorOptionChangedEventArgs e)
@@ -396,7 +412,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation
ITrackingPoint closingPoint = null;
IBraceCompletionSession session = _stack.TopSession;
- _guardedOperations.CallExtensionPoint(() =>
+ _guardedOperations.CallExtensionPoint(errorSource: session, () =>
{
// only set these if they are on the same buffer
if (session.OpeningPoint != null && session.ClosingPoint != null
diff --git a/src/Editor/Text/Impl/PatternMatching/AllLowerCamelCaseMatcher.cs b/src/Editor/Text/Impl/PatternMatching/AllLowerCamelCaseMatcher.cs
index 6106223..dbb45b2 100644
--- a/src/Editor/Text/Impl/PatternMatching/AllLowerCamelCaseMatcher.cs
+++ b/src/Editor/Text/Impl/PatternMatching/AllLowerCamelCaseMatcher.cs
@@ -3,7 +3,7 @@
using System;
using System.Collections.Immutable;
using System.Globalization;
-using Microsoft.VisualStudio.Text.Utilities;
+using Microsoft.VisualStudio.Utilities;
using TextSpan = Microsoft.VisualStudio.Text.Span;
namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
diff --git a/src/Editor/Text/Impl/PatternMatching/CamelCaseResult.cs b/src/Editor/Text/Impl/PatternMatching/CamelCaseResult.cs
index f11c61b..24dffc3 100644
--- a/src/Editor/Text/Impl/PatternMatching/CamelCaseResult.cs
+++ b/src/Editor/Text/Impl/PatternMatching/CamelCaseResult.cs
@@ -1,7 +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.Diagnostics;
-using Microsoft.VisualStudio.Text.Utilities;
+using Microsoft.VisualStudio.Utilities;
using TextSpan = Microsoft.VisualStudio.Text.Span;
namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
diff --git a/src/Editor/Text/Impl/PatternMatching/ContainerPatternMatcher.cs b/src/Editor/Text/Impl/PatternMatching/ContainerPatternMatcher.cs
index 55aa996..671d1c4 100644
--- a/src/Editor/Text/Impl/PatternMatching/ContainerPatternMatcher.cs
+++ b/src/Editor/Text/Impl/PatternMatching/ContainerPatternMatcher.cs
@@ -4,8 +4,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using Microsoft.VisualStudio.Text.Utilities;
-using Microsoft.VisualStudio.Utilities;
namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
{
@@ -31,8 +29,9 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
CultureInfo culture,
bool allowFuzzyMatching = false,
bool allowSimpleSubstringMatching = false,
- bool includeMatchedSpans = false)
- : base(includeMatchedSpans, culture, allowFuzzyMatching, allowSimpleSubstringMatching)
+ bool includeMatchedSpans = false,
+ PatternMatcher linkedMatcher = null)
+ : base(includeMatchedSpans, culture, allowFuzzyMatching, allowSimpleSubstringMatching, linkedMatcher)
{
_containerSplitCharacters = containerSplitCharacters.ToArray();
@@ -40,7 +39,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
.Select(text => new PatternSegment(text.Trim(), allowFuzzyMatching: allowFuzzyMatching))
.ToArray();
- _invalidPattern = _patternSegments.Length == 0 || _patternSegments.Any(s => s.IsInvalid);
+ HasInvalidPattern = _patternSegments.Length == 0 || _patternSegments.Any(s => s.IsInvalid);
}
#pragma warning disable CA1063
diff --git a/src/Editor/Text/Impl/PatternMatching/PatternMatchExtensions.cs b/src/Editor/Text/Impl/PatternMatching/PatternMatchExtensions.cs
index 55ca719..791a840 100644
--- a/src/Editor/Text/Impl/PatternMatching/PatternMatchExtensions.cs
+++ b/src/Editor/Text/Impl/PatternMatching/PatternMatchExtensions.cs
@@ -1,10 +1,5 @@
-using System;
-using System.Collections.Generic;
+using Microsoft.VisualStudio.Utilities;
using System.Collections.Immutable;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.VisualStudio.Text.Utilities;
namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
{
diff --git a/src/Editor/Text/Impl/PatternMatching/PatternMatcher.cs b/src/Editor/Text/Impl/PatternMatching/PatternMatcher.cs
index 793a632..2ab675c 100644
--- a/src/Editor/Text/Impl/PatternMatching/PatternMatcher.cs
+++ b/src/Editor/Text/Impl/PatternMatching/PatternMatcher.cs
@@ -6,7 +6,6 @@ using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Globalization;
using Microsoft.VisualStudio.Text;
-using Microsoft.VisualStudio.Text.Utilities;
using Microsoft.VisualStudio.Utilities;
using TextSpan = Microsoft.VisualStudio.Text.Span;
@@ -26,19 +25,20 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
public const int CamelCaseMatchesFromStartBonus = 2;
public const int CamelCaseMaxWeight = CamelCaseContiguousBonus + CamelCaseMatchesFromStartBonus;
- private readonly object _gate = new object();
+ private readonly object _gate;
private readonly bool _includeMatchedSpans;
private readonly bool _allowFuzzyMatching;
private readonly bool _allowSimpleSubstringMatching;
- private readonly Dictionary<string, StringBreaks> _stringToWordSpans = new Dictionary<string, StringBreaks>();
+ private readonly Dictionary<string, StringBreaks> _stringToWordSpans;
private static readonly Func<string, StringBreaks> _breakIntoWordSpans = StringBreaker.BreakIntoWordParts;
// PERF: Cache the culture's compareInfo to avoid the overhead of asking for them repeatedly in inner loops
private readonly CompareInfo _compareInfo;
- private bool _invalidPattern;
+ public bool HasInvalidPattern { get; private set; }
+
/// <summary>
/// Construct a new PatternMatcher using the specified culture.
/// </summary>
@@ -49,25 +49,32 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
bool includeMatchedSpans,
CultureInfo culture,
bool allowFuzzyMatching = false,
- bool allowSimpleSubstringMatching = false)
+ bool allowSimpleSubstringMatching = false,
+ PatternMatcher linkedMatcher = null)
{
culture = culture ?? CultureInfo.CurrentCulture;
_compareInfo = culture.CompareInfo;
_includeMatchedSpans = includeMatchedSpans;
_allowFuzzyMatching = allowFuzzyMatching;
_allowSimpleSubstringMatching = allowSimpleSubstringMatching;
+ _stringToWordSpans = linkedMatcher?._stringToWordSpans ?? new Dictionary<string, StringBreaks>();
+ _gate = linkedMatcher?._gate ?? new object();
}
#pragma warning disable CA1063
public virtual void Dispose()
#pragma warning restore CA1063
{
- foreach (var kvp in _stringToWordSpans)
+ // Disposing this pattern matcher will dispose any linked matchers as well.
+ lock (_gate)
{
- kvp.Value.Dispose();
- }
+ foreach (var kvp in _stringToWordSpans)
+ {
+ kvp.Value.Dispose();
+ }
- _stringToWordSpans.Clear();
+ _stringToWordSpans.Clear();
+ }
}
public static PatternMatcher CreateSimplePatternMatcher(
@@ -75,21 +82,35 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
CultureInfo culture = null,
bool includeMatchedSpans = false,
bool allowFuzzyMatching = false,
- bool allowSimpleSubstringMatching = false)
+ bool allowSimpleSubstringMatching = false,
+ PatternMatcher linkedMatcher = null)
{
- return new SimplePatternMatcher(pattern, culture, includeMatchedSpans, allowFuzzyMatching, allowSimpleSubstringMatching);
+ return new SimplePatternMatcher(
+ pattern,
+ culture,
+ includeMatchedSpans,
+ allowFuzzyMatching,
+ allowSimpleSubstringMatching,
+ linkedMatcher);
}
- public static PatternMatcher CreateContainerPatternMatcher(
+ internal static PatternMatcher CreateContainerPatternMatcher(
string[] patternParts,
IReadOnlyCollection<char> containerSplitCharacters,
CultureInfo culture = null,
bool allowFuzzyMatching = false,
bool allowSimpleSubstringMatching = false,
- bool includeMatchedSpans = false)
+ bool includeMatchedSpans = false,
+ PatternMatcher linkedMatcher = null)
{
return new ContainerPatternMatcher(
- patternParts, containerSplitCharacters, culture, allowFuzzyMatching, allowSimpleSubstringMatching, includeMatchedSpans);
+ patternParts,
+ containerSplitCharacters,
+ culture,
+ allowFuzzyMatching,
+ allowSimpleSubstringMatching,
+ includeMatchedSpans,
+ linkedMatcher);
}
internal static (string name, string containerOpt) GetNameAndContainer(string pattern)
@@ -104,7 +125,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
public abstract PatternMatch? TryMatch(string candidate);
private bool SkipMatch(string candidate)
- => _invalidPattern || string.IsNullOrWhiteSpace(candidate);
+ => HasInvalidPattern || string.IsNullOrWhiteSpace(candidate);
private StringBreaks GetWordSpans(string word)
{
diff --git a/src/Editor/Text/Impl/PatternMatching/PatternMatcherFactory.cs b/src/Editor/Text/Impl/PatternMatching/PatternMatcherFactory.cs
index ccf1625..fc6c4a5 100644
--- a/src/Editor/Text/Impl/PatternMatching/PatternMatcherFactory.cs
+++ b/src/Editor/Text/Impl/PatternMatching/PatternMatcherFactory.cs
@@ -6,10 +6,16 @@ using static Microsoft.VisualStudio.Text.PatternMatching.PatternMatcherCreationF
namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
{
[Export(typeof(IPatternMatcherFactory))]
- internal class PatternMatcherFactory : IPatternMatcherFactory
+ public class PatternMatcherFactory : IPatternMatcherFactory2
{
public IPatternMatcher CreatePatternMatcher(string pattern, PatternMatcherCreationOptions creationOptions)
{
+ return this.CreatePatternMatcher(pattern, creationOptions, linkedMatcher: null);
+ }
+
+#pragma warning disable CA1822
+ public IPatternMatcher CreatePatternMatcher(string pattern, PatternMatcherCreationOptions creationOptions, IPatternMatcher linkedMatcher)
+ {
if (string.IsNullOrWhiteSpace(pattern))
{
throw new ArgumentException("A non-empty pattern is required to create a pattern matcher", nameof(pattern));
@@ -20,6 +26,8 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
throw new ArgumentNullException(nameof(creationOptions));
}
+ var matcher = linkedMatcher as PatternMatcher;
+
if (creationOptions.ContainerSplitCharacters == null)
{
return PatternMatcher.CreateSimplePatternMatcher(
@@ -27,7 +35,8 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
creationOptions.CultureInfo,
creationOptions.Flags.HasFlag(IncludeMatchedSpans),
creationOptions.Flags.HasFlag(AllowFuzzyMatching),
- creationOptions.Flags.HasFlag(AllowSimpleSubstringMatching));
+ creationOptions.Flags.HasFlag(AllowSimpleSubstringMatching),
+ matcher);
}
else
{
@@ -37,8 +46,10 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
creationOptions.CultureInfo,
creationOptions.Flags.HasFlag(AllowFuzzyMatching),
creationOptions.Flags.HasFlag(AllowSimpleSubstringMatching),
- creationOptions.Flags.HasFlag(IncludeMatchedSpans));
+ creationOptions.Flags.HasFlag(IncludeMatchedSpans),
+ matcher);
}
}
+#pragma warning restore CA1822
}
}
diff --git a/src/Editor/Text/Impl/PatternMatching/SimplePatternMatcher.cs b/src/Editor/Text/Impl/PatternMatching/SimplePatternMatcher.cs
index db0e83d..c23ad30 100644
--- a/src/Editor/Text/Impl/PatternMatching/SimplePatternMatcher.cs
+++ b/src/Editor/Text/Impl/PatternMatching/SimplePatternMatcher.cs
@@ -1,7 +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.Collections.Generic;
using System.Globalization;
-using Microsoft.VisualStudio.Text.Utilities;
using Microsoft.VisualStudio.Utilities;
using TextSpan = Microsoft.VisualStudio.Text.Span;
@@ -18,13 +18,14 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
CultureInfo culture,
bool includeMatchedSpans,
bool allowFuzzyMatching,
- bool allowSimpleSubstringMatching = false)
- : base(includeMatchedSpans, culture, allowFuzzyMatching, allowSimpleSubstringMatching)
+ bool allowSimpleSubstringMatching = false,
+ PatternMatcher linkedMatcher = null)
+ : base(includeMatchedSpans, culture, allowFuzzyMatching, allowSimpleSubstringMatching, linkedMatcher)
{
pattern = pattern.Trim();
_fullPatternSegment = new PatternSegment(pattern, allowFuzzyMatching);
- _invalidPattern = _fullPatternSegment.IsInvalid;
+ HasInvalidPattern = _fullPatternSegment.IsInvalid;
}
public override void Dispose()
diff --git a/src/Editor/Text/Impl/PatternMatching/StringBreaker.cs b/src/Editor/Text/Impl/PatternMatching/StringBreaker.cs
index 343953c..044fed2 100644
--- a/src/Editor/Text/Impl/PatternMatching/StringBreaker.cs
+++ b/src/Editor/Text/Impl/PatternMatching/StringBreaker.cs
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using Microsoft.VisualStudio.Text.Utilities;
+using Microsoft.VisualStudio.Utilities;
using TextSpan = Microsoft.VisualStudio.Text.Span;
namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation
diff --git a/src/Editor/Text/Util/TextDataUtil/GuardedOperations.cs b/src/Editor/Text/Util/TextDataUtil/GuardedOperations.cs
index 92a8ad8..3b0d866 100644
--- a/src/Editor/Text/Util/TextDataUtil/GuardedOperations.cs
+++ b/src/Editor/Text/Util/TextDataUtil/GuardedOperations.cs
@@ -18,8 +18,10 @@ namespace Microsoft.VisualStudio.Text.Utilities
/// </summary>
[Export]
[Export(typeof(IGuardedOperations))]
+ [Export(typeof(IGuardedOperations2))]
+ [Export(typeof(IGuardedOperationsInternal))]
[PartCreationPolicy(CreationPolicy.Shared)]
- internal sealed class GuardedOperations : IGuardedOperations
+ internal sealed class GuardedOperations : IGuardedOperations, IGuardedOperations2, IGuardedOperationsInternal
{
[ImportMany]
private List<Lazy<IExtensionErrorHandler>> _errorHandlerExports = null;
@@ -66,7 +68,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
get
{
- if (_errorHandlers == null)
+ if (_errorHandlers == null)
{
_errorHandlers = new FrugalList<IExtensionErrorHandler>();
if (_errorHandlerExports != null) // can be null during unit testing
@@ -131,16 +133,20 @@ namespace Microsoft.VisualStudio.Text.Utilities
where TMetadataView : IContentTypeMetadata
where TExtensionFactory : class
{
- var factory = InvokeBestMatchingFactory(providerHandles, dataContentType, contentTypeRegistryService, errorSource);
-
- if (factory == null)
+ var factories = GetOrderedMatchingExtensions(providerHandles, dataContentType, contentTypeRegistryService);
+ foreach (var factoryExport in factories)
{
- return default(TExtensionInstance);
+ TExtensionFactory factory = InstantiateExtension(errorSource, factoryExport);
+ if (factory != null)
+ {
+ TExtensionInstance extensionInstance = default(TExtensionInstance);
+ this.CallExtensionPoint(errorSource, () => extensionInstance = getter(factory));
+ if (extensionInstance != null)
+ return extensionInstance;
+ }
}
- TExtensionInstance extensionInstance = default(TExtensionInstance);
- this.CallExtensionPoint(errorSource, () => extensionInstance = getter(factory));
- return extensionInstance;
+ return default(TExtensionInstance);
}
public TExtension InvokeBestMatchingFactory<TExtension, TMetadataView>
@@ -150,22 +156,36 @@ namespace Microsoft.VisualStudio.Text.Utilities
object errorSource)
where TMetadataView : IContentTypeMetadata
{
+ var extensions = GetOrderedMatchingExtensions(providerHandles, dataContentType, contentTypeRegistryService);
+ foreach (var extension in extensions)
+ {
+ TExtension factory = InstantiateExtension(errorSource, extension);
+ if (factory != null)
+ {
+ return factory;
+ }
+ }
+
+ // no suitable provider found
+ return default(TExtension);
+ }
+
+ /// <summary>
+ /// Return a list of uninstantiated extensions sorted by the specificity of the content type (assets with more specific content types come first).
+ /// </summary>
+ private static IEnumerable<Lazy<TExtension, TMetadataView>> GetOrderedMatchingExtensions<TExtension, TMetadataView>
+ (IList<Lazy<TExtension, TMetadataView>> providerHandles,
+ IContentType dataContentType,
+ IContentTypeRegistryService contentTypeRegistryService)
+ where TMetadataView : IContentTypeMetadata
+ {
var candidates = new List<Lazy<TExtension, TMetadataView>>();
for (int i = 0; (i < providerHandles.Count); ++i)
{
var providerHandle = providerHandles[i];
foreach (string contentTypeName in providerHandle.Metadata.ContentTypes)
{
- if (string.Compare(dataContentType.TypeName, contentTypeName, StringComparison.OrdinalIgnoreCase) == 0)
- {
- // we have an exact match--no need to look further if this one is happy
- TExtension factory = InstantiateExtension(errorSource, providerHandle);
- if (factory != null)
- {
- return factory;
- }
- }
- else if (dataContentType.IsOfType(contentTypeName))
+ if (dataContentType.IsOfType(contentTypeName))
{
candidates.Add(providerHandle);
break;
@@ -175,17 +195,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
SortCandidates(candidates, dataContentType, contentTypeRegistryService);
- for (int c = 0; c < candidates.Count; ++c)
- {
- TExtension factory = InstantiateExtension(errorSource, candidates[c]);
- if (factory != null)
- {
- return factory;
- }
- }
-
- // no suitable provider found
- return default(TExtension);
+ return candidates;
}
public List<TExtensionInstance> InvokeMatchingFactories<TExtensionInstance, TExtensionFactory, TMetadataView>
@@ -423,6 +433,28 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
}
+ public T CallExtensionPoint<T>(object errorSource, Func<T> call, T valueOnThrow, Predicate<Exception> exceptionToIgnore, Predicate<Exception> exceptionToHandle)
+ {
+ try
+ {
+ BeforeCallingExtensionPoint(errorSource ?? call);
+ return call();
+ }
+ catch (Exception e) when (exceptionToIgnore(e))
+ {
+ return valueOnThrow;
+ }
+ catch (Exception e) when (exceptionToHandle(e))
+ {
+ HandleException(errorSource, e);
+ return valueOnThrow;
+ }
+ finally
+ {
+ AfterCallingExtensionPoint(errorSource ?? call);
+ }
+ }
+
public void CallExtensionPoint(Action call)
{
this.CallExtensionPoint(errorSource: null, call: call);
@@ -608,6 +640,36 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
}
+ public void LogException(object errorSource, Exception e)
+ {
+ var logged = false;
+ for (int i = 0; (i < ErrorHandlers.Count); ++i)
+ {
+ var errorHandler = ErrorHandlers[i] as IExtensionErrorHandler2;
+ if (errorHandler != null)
+ {
+ try
+ {
+ GuardedOperations.LastHandledException = e;
+ GuardedOperations.LastHandleExceptionStackTrace = e.StackTrace;
+
+ errorHandler.LogError(errorSource, e);
+ logged = true;
+ }
+ catch (Exception doubleFaultException)
+ {
+ // TODO: What is the right behavior here?
+ GuardedOperations.Fail(doubleFaultException.ToString());
+ }
+ }
+ }
+
+ if (!logged)
+ {
+ Debug.WriteLine(e.Message);
+ }
+ }
+
public void HandleException(object errorSource, Exception e)
{
bool handled = false;
@@ -662,7 +724,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
contentTypes.Sort(CompareContentTypes);
candidates.Sort((left, right) =>
{
- int leftIndex = BestContentTypeScore(left.Metadata.ContentTypes, contentTypes);
+ int leftIndex = BestContentTypeScore(left.Metadata.ContentTypes, contentTypes);
int rightIndex = BestContentTypeScore(right.Metadata.ContentTypes, contentTypes);
return leftIndex - rightIndex; // Sort these in ascending order.
diff --git a/src/Editor/Text/Util/TextUIUtil/DiagnosticLogger.cs b/src/Editor/Text/Util/TextUIUtil/DiagnosticLogger.cs
new file mode 100644
index 0000000..21f2b07
--- /dev/null
+++ b/src/Editor/Text/Util/TextUIUtil/DiagnosticLogger.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Text.UI.Utilities
+{
+ public static class DiagnosticLogger
+ {
+ private static bool WasLoggingEnabled;
+ private static List<(long, string)> Log = new List<(long, string)>();
+
+ public static bool IsLoggingEnabled(ITextView textView)
+ {
+ var currentValue = textView.Options.GetOptionValue(DefaultOptions.DiagnosticModeOptionId);
+ if (!WasLoggingEnabled && currentValue)
+ {
+ Add("--- Begin new log");
+ }
+ if (WasLoggingEnabled != currentValue)
+ {
+ WasLoggingEnabled = currentValue;
+ }
+ return currentValue;
+ }
+
+ public static void Add(string message)
+ {
+ Log.Add((DateTime.Now.Ticks, message));
+ }
+
+ public static void Add(string message, object param)
+ {
+ Log.Add((DateTime.Now.Ticks, message + param.ToString()));
+ }
+ }
+}