提交 bd849bdb 编写于 作者: C CyrusNajmabadi

Merge pull request #3431 from CyrusNajmabadi/asyncToolTips

Make computation and display of completion item descriptions actually asynchronous.
......@@ -61,10 +61,26 @@ public override string Description
{
get
{
// If the completion item has an async description, then we don't want to force it
// to be computed here. That will cause blocking on the UI thread. Note: the only
// caller of this is the VS tooltip code which uses the presence of the Description
// to then decide to show the tooltip. But once they decide to show the tooltip,
// they defer to us to get the contents for it asynchronously. As such, we just want
// to give them something non-empty so they know to go get the async description.
if (this.CompletionItem.HasAsyncDescription)
{
return "...";
}
return this.CompletionItem.GetDescriptionAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None).GetFullText();
}
}
public string GetDescription_TestingOnly()
{
return this.CompletionItem.GetDescriptionAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None).GetFullText();
}
public override ImageMoniker IconMoniker
{
get
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.VisualStudio.Language.Intellisense;
......@@ -19,22 +25,71 @@ internal class ToolTipProvider : IUIElementProvider<VSCompletion, ICompletionSes
{
private readonly ClassificationTypeMap _typeMap;
// The textblock containing "..." that will be displayed until the actual completion
// description has been computed.
private readonly TextBlock _defaultTextBlock;
[ImportingConstructor]
public ToolTipProvider(ClassificationTypeMap typeMap)
{
_typeMap = typeMap;
_defaultTextBlock = SymbolDisplayPartExtensions.ToTextBlock(
new[] { new SymbolDisplayPart(SymbolDisplayPartKind.Text, symbol: null, text: "...") },
typeMap);
}
public UIElement GetUIElement(VSCompletion itemToRender, ICompletionSession context, UIElementType elementType)
{
if (!(itemToRender is CustomCommitCompletion))
var item = itemToRender as CustomCommitCompletion;
if (item == null)
{
return null;
}
var item = (CustomCommitCompletion)itemToRender;
var descriptionParts = item.CompletionItem.GetDescriptionAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None);
return descriptionParts.ToTextBlock(_typeMap);
return new CancellableContentControl(this, item.CompletionItem);
}
private class CancellableContentControl : ContentControl
{
private readonly ForegroundThreadAffinitizedObject foregroundObject = new ForegroundThreadAffinitizedObject();
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly ToolTipProvider toolTipProvider;
public CancellableContentControl(ToolTipProvider toolTipProvider, CompletionItem completionItem)
{
Debug.Assert(foregroundObject.IsForeground());
this.toolTipProvider = toolTipProvider;
// Set our content to be "..." initially.
this.Content = toolTipProvider._defaultTextBlock;
// Kick off the task to produce the new content. When it completes, call back on
// the UI thread to update the display.
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
completionItem.GetDescriptionAsync(cancellationTokenSource.Token)
.ContinueWith(ProcessDescription, cancellationTokenSource.Token,
TaskContinuationOptions.OnlyOnRanToCompletion, scheduler);
// If we get unloaded (i.e. the user scrolls down in the completion list and VS
// dismisses the existing tooltip), then cancel the work we're doing
this.Unloaded += (s, e) => this.cancellationTokenSource.Cancel();
}
private void ProcessDescription(Task<ImmutableArray<SymbolDisplayPart>> obj)
{
Debug.Assert(foregroundObject.IsForeground());
// If we were canceled, or didn't run all the way to completion, then don't bother
// updating the UI.
if (cancellationTokenSource.IsCancellationRequested ||
obj.Status != TaskStatus.RanToCompletion)
{
return;
}
var descriptionParts = obj.Result;
this.Content = descriptionParts.ToTextBlock(toolTipProvider._typeMap);
}
}
}
}
}
\ No newline at end of file
......@@ -35,7 +35,7 @@ public static TextBlock ToTextBlock(this ImmutableArray<SymbolDisplayPart> parts
public static TextBlock ToTextBlock(this IEnumerable<SymbolDisplayPart> parts, ClassificationTypeMap typeMap)
{
var result = new TextBlock() { TextWrapping = TextWrapping.Wrap};
var result = new TextBlock() { TextWrapping = TextWrapping.Wrap };
var formatMap = typeMap.ClassificationFormatMapService.GetClassificationFormatMap("tooltip");
result.SetDefaultTextProperties(formatMap);
......
......@@ -16,6 +16,8 @@ internal class CompletionItem : IComparable<CompletionItem>
{
internal AsyncLazy<ImmutableArray<SymbolDisplayPart>> LazyDescription;
internal bool HasAsyncDescription { get; }
/// <summary>
/// An appropriate icon to present to the user for this completion item.
/// </summary>
......@@ -102,7 +104,7 @@ internal class CompletionItem : IComparable<CompletionItem>
bool shouldFormatOnCommit = false)
: this(completionProvider, displayText, filterSpan,
description.IsDefault ? (Func<CancellationToken, Task<ImmutableArray<SymbolDisplayPart>>>)null : c => Task.FromResult(description),
glyph, sortText, filterText, preselect, isBuilder, showsWarningIcon, shouldFormatOnCommit)
glyph, /*hasAsyncDescription*/ false, sortText, filterText, preselect, isBuilder, showsWarningIcon, shouldFormatOnCommit)
{
}
......@@ -117,6 +119,23 @@ internal class CompletionItem : IComparable<CompletionItem>
bool preselect = false,
bool isBuilder = false,
bool showsWarningIcon = false,
bool shouldFormatOnCommit = false) :
this(completionProvider, displayText, filterSpan, descriptionFactory, glyph, /*hasAsyncDescription*/ true, sortText, filterText, preselect, isBuilder, showsWarningIcon, shouldFormatOnCommit)
{
}
private CompletionItem(
ICompletionProvider completionProvider,
string displayText,
TextSpan filterSpan,
Func<CancellationToken, Task<ImmutableArray<SymbolDisplayPart>>> descriptionFactory,
Glyph? glyph,
bool hasAsyncDescription,
string sortText = null,
string filterText = null,
bool preselect = false,
bool isBuilder = false,
bool showsWarningIcon = false,
bool shouldFormatOnCommit = false)
{
this.CompletionProvider = completionProvider;
......@@ -129,6 +148,7 @@ internal class CompletionItem : IComparable<CompletionItem>
this.IsBuilder = isBuilder;
this.ShowsWarningIcon = showsWarningIcon;
this.ShouldFormatOnCommit = shouldFormatOnCommit;
this.HasAsyncDescription = hasAsyncDescription;
if (descriptionFactory != null)
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册