未验证 提交 a8ee63e8 编写于 作者: D David 提交者: GitHub

Merge pull request #46576 from dibarbet/colorize_hover

Add temporary support for colorization in LSP hover
......@@ -276,7 +276,6 @@ private static QuickInfoProvider CreateProvider()
protected override async Task AssertContentIsAsync(
TestWorkspace workspace,
Document document,
ITextSnapshot snapshot,
int position,
string expectedContent,
string expectedDocumentationComment = null)
......@@ -289,7 +288,7 @@ private static QuickInfoProvider CreateProvider()
var trackingSpan = new Mock<ITrackingSpan>(MockBehavior.Strict);
var threadingContext = workspace.ExportProvider.GetExportedValue<IThreadingContext>();
var streamingPresenter = workspace.ExportProvider.GetExport<IStreamingFindUsagesPresenter>();
var quickInfoItem = await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan.Object, info, snapshot, document, threadingContext, streamingPresenter, CancellationToken.None);
var quickInfoItem = await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan.Object, info, document, threadingContext, streamingPresenter, CancellationToken.None);
var containerElement = quickInfoItem.Item as ContainerElement;
var textElements = containerElement.Elements.OfType<ClassifiedTextElement>();
......@@ -327,7 +326,6 @@ protected override Task TestInScriptAsync(string code, string expectedContent, s
var testDocument = workspace.Documents.Single();
var position = testDocument.CursorPosition.Value;
var document = workspace.CurrentSolution.Projects.First().Documents.First();
var snapshot = testDocument.GetTextBuffer().CurrentSnapshot;
if (string.IsNullOrEmpty(expectedContent))
{
......@@ -335,7 +333,7 @@ protected override Task TestInScriptAsync(string code, string expectedContent, s
}
else
{
await AssertContentIsAsync(workspace, document, snapshot, position, expectedContent, expectedDocumentationComment);
await AssertContentIsAsync(workspace, document, position, expectedContent, expectedDocumentationComment);
}
}
}
......
......@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
......@@ -33,8 +35,8 @@ internal static class Helpers
ImmutableArray<TaggedText> taggedTexts,
ref int index,
Document document,
IThreadingContext threadingContext,
Lazy<IStreamingFindUsagesPresenter> streamingPresenter)
IThreadingContext? threadingContext,
Lazy<IStreamingFindUsagesPresenter>? streamingPresenter)
{
// This method produces a sequence of zero or more paragraphs
var paragraphs = new List<object>();
......@@ -127,7 +129,7 @@ internal static class Helpers
{
// This is tagged text getting added to the current line we are building.
var style = GetClassifiedTextRunStyle(part.Style);
if (part.NavigationTarget is object)
if (part.NavigationTarget is object && streamingPresenter != null && threadingContext != null)
{
if (Uri.TryCreate(part.NavigationTarget, UriKind.Absolute, out var absoluteUri))
{
......
......@@ -14,7 +14,6 @@
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Adornments;
......@@ -25,9 +24,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo
{
internal static class IntellisenseQuickInfoBuilder
{
internal static async Task<IntellisenseQuickInfoItem> BuildItemAsync(ITrackingSpan trackingSpan,
CodeAnalysisQuickInfoItem quickInfoItem,
ITextSnapshot snapshot,
private static async Task<ContainerElement> BuildInteractiveContentAsync(CodeAnalysisQuickInfoItem quickInfoItem,
Document document,
IThreadingContext threadingContext,
Lazy<IStreamingFindUsagesPresenter> streamingPresenter,
......@@ -114,7 +111,7 @@ internal static class IntellisenseQuickInfoBuilder
var tabSize = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.TabSize, document.Project.Language);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var spans = IndentationHelper.GetSpansWithAlignedIndentation(text, classifiedSpanList.ToImmutableArray(), tabSize);
var textRuns = spans.Select(s => new ClassifiedTextRun(s.ClassificationType, snapshot.GetText(s.TextSpan.ToSpan()), ClassifiedTextRunStyle.UseClassificationFont));
var textRuns = spans.Select(s => new ClassifiedTextRun(s.ClassificationType, text.GetSubText(s.TextSpan).ToString(), ClassifiedTextRunStyle.UseClassificationFont));
if (textRuns.Any())
{
......@@ -122,11 +119,36 @@ internal static class IntellisenseQuickInfoBuilder
}
}
var content = new ContainerElement(
return new ContainerElement(
ContainerElementStyle.Stacked | ContainerElementStyle.VerticalPadding,
elements);
}
internal static async Task<IntellisenseQuickInfoItem> BuildItemAsync(
ITrackingSpan trackingSpan,
CodeAnalysisQuickInfoItem quickInfoItem,
Document document,
IThreadingContext threadingContext,
Lazy<IStreamingFindUsagesPresenter> streamingPresenter,
CancellationToken cancellationToken)
{
var content = await BuildInteractiveContentAsync(quickInfoItem, document, threadingContext, streamingPresenter, cancellationToken).ConfigureAwait(false);
return new IntellisenseQuickInfoItem(trackingSpan, content);
}
/// <summary>
/// Builds the classified hover content without navigation actions and requiring
/// an instance of <see cref="IStreamingFindUsagesPresenter"/>
/// TODO - This can be removed once LSP supports colorization in markupcontent
/// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/918138
/// </summary>
internal static Task<ContainerElement> BuildContentWithoutNavigationActionsAsync(
CodeAnalysisQuickInfoItem quickInfoItem,
Document document,
CancellationToken cancellationToken)
{
return BuildInteractiveContentAsync(quickInfoItem, document, threadingContext: null, streamingPresenter: null, cancellationToken);
}
}
}
......@@ -70,7 +70,7 @@ public async Task<IntellisenseQuickInfoItem> GetQuickInfoItemAsync(IAsyncQuickIn
{
var textVersion = snapshot.Version;
var trackingSpan = textVersion.CreateTrackingSpan(item.Span.ToSpan(), SpanTrackingMode.EdgeInclusive);
return await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan, item, snapshot, document, _threadingContext, _streamingPresenter, cancellationToken).ConfigureAwait(false);
return await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan, item, document, _threadingContext, _streamingPresenter, cancellationToken).ConfigureAwait(false);
}
return null;
......
......@@ -44,7 +44,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext)()
Dim streamingPresenter = workspace.ExportProvider.GetExport(Of IStreamingFindUsagesPresenter)()
Return Await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan.Object, quickInfoItem, cursorBuffer.CurrentSnapshot, document, threadingContext, streamingPresenter, CancellationToken.None)
Return Await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan.Object, quickInfoItem, document, threadingContext, streamingPresenter, CancellationToken.None)
End Using
End Function
......@@ -68,7 +68,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext)()
Dim streamingPresenter = workspace.ExportProvider.GetExport(Of IStreamingFindUsagesPresenter)()
Return Await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan.Object, codeAnalysisQuickInfoItem, cursorBuffer.CurrentSnapshot, document, threadingContext, streamingPresenter, CancellationToken.None)
Return Await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan.Object, codeAnalysisQuickInfoItem, document, threadingContext, streamingPresenter, CancellationToken.None)
End Using
End Function
End Class
......
......@@ -51,7 +51,6 @@ protected async Task TestInMethodAndScriptAsync(string code, string expectedCont
protected abstract Task AssertContentIsAsync(
TestWorkspace workspace,
Document document,
ITextSnapshot snapshot,
int position,
string expectedContent,
string expectedDocumentationComment = null);
......
......@@ -9,6 +9,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.VisualStudio.LanguageServer.Protocol;
......@@ -43,21 +44,16 @@ public HoverHandler(ILspSolutionProvider solutionProvider) : base(solutionProvid
}
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
return new Hover
// TODO - Switch to markup content once it supports classifications.
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/918138
return new VSHover
{
Range = ProtocolConversions.TextSpanToRange(info.Span, text),
Contents = new MarkupContent
{
Kind = MarkupKind.Markdown,
Value = GetMarkdownString(info)
}
Contents = new SumType<SumType<string, MarkedString>, SumType<string, MarkedString>[], MarkupContent>(string.Empty),
// Build the classified text without navigation actions - they are not serializable.
RawContent = await IntellisenseQuickInfoBuilder.BuildContentWithoutNavigationActionsAsync(info, document, cancellationToken).ConfigureAwait(false)
};
// local functions
// TODO - This should return correctly formatted markdown from quick info.
// https://github.com/dotnet/roslyn/issues/43387
static string GetMarkdownString(QuickInfoItem info)
=> string.Join("\r\n", info.Sections.Select(section => section.Text).Where(text => !string.IsNullOrEmpty(text)));
}
}
}
......@@ -5,6 +5,8 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.VisualStudio.Text.Adornments;
using Roslyn.Test.Utilities;
using Xunit;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
......@@ -31,10 +33,9 @@ public async Task TestGetHoverAsync()
using var workspace = CreateTestWorkspace(markup, out var locations);
var expectedLocation = locations["caret"].Single();
var expected = CreateHover(expectedLocation, $"string A.Method(int i)\r\n A great method\r\n\r\n{FeaturesResources.Returns_colon}\r\n a string");
var results = await RunGetHoverAsync(workspace.CurrentSolution, expectedLocation).ConfigureAwait(false);
AssertJsonEquals(expected, results);
VerifyContent(results, $"string A.Method(int i)|A great method|{FeaturesResources.Returns_colon}| |a string");
}
[Fact]
......@@ -55,10 +56,9 @@ public async Task TestGetHoverAsync_WithExceptions()
}";
using var workspace = CreateTestWorkspace(markup, out var locations);
var expectedLocation = locations["caret"].Single();
var expected = CreateHover(expectedLocation, $"string A.Method(int i)\r\n A great method\r\n\r\n{FeaturesResources.Exceptions_colon}\r\n System.NullReferenceException");
var results = await RunGetHoverAsync(workspace.CurrentSolution, expectedLocation).ConfigureAwait(false);
AssertJsonEquals(expected, results);
VerifyContent(results, $"string A.Method(int i)|A great method|{FeaturesResources.Exceptions_colon}| System.NullReferenceException");
}
[Fact]
......@@ -79,10 +79,9 @@ public async Task TestGetHoverAsync_WithRemarks()
}";
using var workspace = CreateTestWorkspace(markup, out var locations);
var expectedLocation = locations["caret"].Single();
var expected = CreateHover(expectedLocation, "string A.Method(int i)\r\n A great method\r\n\r\nRemarks are cool too.");
var results = await RunGetHoverAsync(workspace.CurrentSolution, expectedLocation).ConfigureAwait(false);
AssertJsonEquals(expected, results);
VerifyContent(results, "string A.Method(int i)|A great method|Remarks are cool too.");
}
[Fact]
......@@ -108,10 +107,9 @@ public async Task TestGetHoverAsync_WithList()
}";
using var workspace = CreateTestWorkspace(markup, out var locations);
var expectedLocation = locations["caret"].Single();
var expected = CreateHover(expectedLocation, "string A.Method(int i)\r\n A great method\r\n\r\n• Item 1.\r\n• Item 2.");
var results = await RunGetHoverAsync(workspace.CurrentSolution, expectedLocation).ConfigureAwait(false);
AssertJsonEquals(expected, results);
VerifyContent(results, "string A.Method(int i)|A great method|• |Item 1.|• |Item 2.");
}
[Fact]
......@@ -187,24 +185,37 @@ static void Main(string[] args)
var result = await RunGetHoverAsync(workspace.CurrentSolution, location, project.Id);
var expectedConstant = project.Name == "Net472" ? "Target in net472" : "Target in netcoreapp3.1";
var expectedHover = CreateHover(location, $"({FeaturesResources.constant}) string WithConstant.Target = \"{expectedConstant}\"");
AssertJsonEquals(expectedHover, result);
VerifyContent(result, $"({FeaturesResources.constant}) string WithConstant.Target = \"{expectedConstant}\"");
}
}
private static async Task<LSP.Hover> RunGetHoverAsync(Solution solution, LSP.Location caret, ProjectId projectContext = null)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.Hover>(LSP.Methods.TextDocumentHoverName,
private static async Task<LSP.VSHover> RunGetHoverAsync(Solution solution, LSP.Location caret, ProjectId projectContext = null)
=> (LSP.VSHover)await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.Hover>(LSP.Methods.TextDocumentHoverName,
CreateTextDocumentPositionParams(caret, projectContext), new LSP.ClientCapabilities(), null, CancellationToken.None);
private static LSP.Hover CreateHover(LSP.Location location, string text)
=> new LSP.Hover()
private void VerifyContent(LSP.VSHover result, string expectedContent)
{
var containerElement = (ContainerElement)result.RawContent;
using var _ = ArrayBuilder<ClassifiedTextElement>.GetInstance(out var classifiedTextElements);
GetClassifiedTextElements(containerElement, classifiedTextElements);
Assert.False(classifiedTextElements.SelectMany(classifiedTextElements => classifiedTextElements.Runs).Any(run => run.NavigationAction != null));
var content = string.Join("|", classifiedTextElements.Select(cte => string.Join(string.Empty, cte.Runs.Select(ctr => ctr.Text))));
Assert.Equal(expectedContent, content);
}
private void GetClassifiedTextElements(ContainerElement container, ArrayBuilder<ClassifiedTextElement> classifiedTextElements)
{
foreach (var element in container.Elements)
{
Contents = new LSP.MarkupContent()
if (element is ClassifiedTextElement classifiedTextElement)
{
classifiedTextElements.Add(classifiedTextElement);
}
else if (element is ContainerElement containerElement)
{
Kind = LSP.MarkupKind.Markdown,
Value = text
},
Range = location.Range
};
GetClassifiedTextElements(containerElement, classifiedTextElements);
}
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册