提交 70ac333a 编写于 作者: S Sam Harwell

Support URL navigation for href in Quick Info

上级 217f1b38
...@@ -37,6 +37,7 @@ internal static class DocumentationCommentXmlNames ...@@ -37,6 +37,7 @@ internal static class DocumentationCommentXmlNames
public const string ValueElementName = "value"; public const string ValueElementName = "value";
public const string CrefAttributeName = "cref"; public const string CrefAttributeName = "cref";
public const string HrefAttributeName = "href";
public const string FileAttributeName = "file"; public const string FileAttributeName = "file";
public const string InstanceAttributeName = "instance"; public const string InstanceAttributeName = "instance";
public const string LangwordAttributeName = "langword"; public const string LangwordAttributeName = "langword";
......
...@@ -294,15 +294,16 @@ public async Task<object> GetDescriptionAsync(IAsyncCompletionSession session, V ...@@ -294,15 +294,16 @@ public async Task<object> GetDescriptionAsync(IAsyncCompletionSession session, V
} }
var service = document.GetLanguageService<CompletionService>(); var service = document.GetLanguageService<CompletionService>();
if (service == null) if (service == null)
{ {
return null; return null;
} }
var navigateToLinkService = document.Project.Solution.Workspace.Services.GetRequiredService<INavigateToLinkService>();
var description = await service.GetDescriptionAsync(document, roslynItem, cancellationToken).ConfigureAwait(false); var description = await service.GetDescriptionAsync(document, roslynItem, cancellationToken).ConfigureAwait(false);
var elements = IntelliSense.Helpers.BuildClassifiedTextElements(description.TaggedParts).ToArray(); var elements = IntelliSense.Helpers.BuildClassifiedTextElements(description.TaggedParts, navigateToLinkService).ToArray();
if (elements.Length == 0) if (elements.Length == 0)
{ {
return new ClassifiedTextElement(); return new ClassifiedTextElement();
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // 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.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Classification;
using Microsoft.VisualStudio.Text.Adornments; using Microsoft.VisualStudio.Text.Adornments;
using Roslyn.Utilities; using Roslyn.Utilities;
...@@ -11,13 +13,13 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense ...@@ -11,13 +13,13 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense
{ {
internal static class Helpers internal static class Helpers
{ {
internal static IEnumerable<object> BuildClassifiedTextElements(ImmutableArray<TaggedText> taggedTexts) internal static IEnumerable<object> BuildClassifiedTextElements(ImmutableArray<TaggedText> taggedTexts, INavigateToLinkService navigateToLinkService)
{ {
var index = 0; var index = 0;
return BuildClassifiedTextElements(taggedTexts, ref index); return BuildClassifiedTextElements(taggedTexts, ref index, navigateToLinkService);
} }
private static IReadOnlyCollection<object> BuildClassifiedTextElements(ImmutableArray<TaggedText> taggedTexts, ref int index) private static IReadOnlyCollection<object> BuildClassifiedTextElements(ImmutableArray<TaggedText> taggedTexts, ref int index, INavigateToLinkService navigateToLinkService)
{ {
// This method produces a sequence of zero or more paragraphs // This method produces a sequence of zero or more paragraphs
var paragraphs = new List<object>(); var paragraphs = new List<object>();
...@@ -41,7 +43,7 @@ private static IReadOnlyCollection<object> BuildClassifiedTextElements(Immutable ...@@ -41,7 +43,7 @@ private static IReadOnlyCollection<object> BuildClassifiedTextElements(Immutable
} }
index++; index++;
var nestedElements = BuildClassifiedTextElements(taggedTexts, ref index); var nestedElements = BuildClassifiedTextElements(taggedTexts, ref index, navigateToLinkService);
if (nestedElements.Count <= 1) if (nestedElements.Count <= 1)
{ {
currentParagraph.Add(new ContainerElement( currentParagraph.Add(new ContainerElement(
...@@ -108,7 +110,15 @@ private static IReadOnlyCollection<object> BuildClassifiedTextElements(Immutable ...@@ -108,7 +110,15 @@ private static IReadOnlyCollection<object> BuildClassifiedTextElements(Immutable
{ {
// This is tagged text getting added to the current line we are building. // This is tagged text getting added to the current line we are building.
var style = GetClassifiedTextRunStyle(part.Style); var style = GetClassifiedTextRunStyle(part.Style);
currentRuns.Add(new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text, style)); if (part.NavigationTarget is object)
{
var tooltip = part.NavigationTarget;
currentRuns.Add(new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text, () => NavigateToQuickInfoTarget(tooltip, navigateToLinkService), tooltip, style));
}
else
{
currentRuns.Add(new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text, style));
}
} }
} }
...@@ -127,6 +137,15 @@ private static IReadOnlyCollection<object> BuildClassifiedTextElements(Immutable ...@@ -127,6 +137,15 @@ private static IReadOnlyCollection<object> BuildClassifiedTextElements(Immutable
return paragraphs; return paragraphs;
} }
private static void NavigateToQuickInfoTarget(string navigationTarget, INavigateToLinkService navigateToLinkService)
{
if (Uri.TryCreate(navigationTarget, UriKind.Absolute, out var absoluteUri))
{
navigateToLinkService.TryNavigateToLinkAsync(absoluteUri, CancellationToken.None);
return;
}
}
private static ClassifiedTextRunStyle GetClassifiedTextRunStyle(TaggedTextStyle style) private static ClassifiedTextRunStyle GetClassifiedTextRunStyle(TaggedTextStyle style)
{ {
var result = ClassifiedTextRunStyle.Plain; var result = ClassifiedTextRunStyle.Plain;
......
...@@ -43,10 +43,11 @@ internal static class IntellisenseQuickInfoBuilder ...@@ -43,10 +43,11 @@ internal static class IntellisenseQuickInfoBuilder
var elements = new List<object>(); var elements = new List<object>();
var descSection = quickInfoItem.Sections.FirstOrDefault(s => s.Kind == QuickInfoSectionKinds.Description); var descSection = quickInfoItem.Sections.FirstOrDefault(s => s.Kind == QuickInfoSectionKinds.Description);
var navigateToLinkService = document.Project.Solution.Workspace.Services.GetRequiredService<INavigateToLinkService>();
if (descSection != null) if (descSection != null)
{ {
var isFirstElement = true; var isFirstElement = true;
foreach (var element in Helpers.BuildClassifiedTextElements(descSection.TaggedParts)) foreach (var element in Helpers.BuildClassifiedTextElements(descSection.TaggedParts, navigateToLinkService))
{ {
if (isFirstElement) if (isFirstElement)
{ {
...@@ -68,7 +69,7 @@ internal static class IntellisenseQuickInfoBuilder ...@@ -68,7 +69,7 @@ internal static class IntellisenseQuickInfoBuilder
if (documentationCommentSection != null) if (documentationCommentSection != null)
{ {
var isFirstElement = true; var isFirstElement = true;
foreach (var element in Helpers.BuildClassifiedTextElements(documentationCommentSection.TaggedParts)) foreach (var element in Helpers.BuildClassifiedTextElements(documentationCommentSection.TaggedParts, navigateToLinkService))
{ {
if (isFirstElement) if (isFirstElement)
{ {
...@@ -92,7 +93,7 @@ internal static class IntellisenseQuickInfoBuilder ...@@ -92,7 +93,7 @@ internal static class IntellisenseQuickInfoBuilder
// Add the remaining sections as Stacked style // Add the remaining sections as Stacked style
elements.AddRange( elements.AddRange(
quickInfoItem.Sections.Where(s => s.Kind != QuickInfoSectionKinds.Description && s.Kind != QuickInfoSectionKinds.DocumentationComments) quickInfoItem.Sections.Where(s => s.Kind != QuickInfoSectionKinds.Description && s.Kind != QuickInfoSectionKinds.DocumentationComments)
.SelectMany(s => Helpers.BuildClassifiedTextElements(s.TaggedParts))); .SelectMany(s => Helpers.BuildClassifiedTextElements(s.TaggedParts, navigateToLinkService)));
// build text for RelatedSpan // build text for RelatedSpan
if (quickInfoItem.RelatedSpans.Any()) if (quickInfoItem.RelatedSpans.Any())
......
...@@ -189,6 +189,11 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense ...@@ -189,6 +189,11 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
If classifiedTextRun IsNot Nothing Then If classifiedTextRun IsNot Nothing Then
Dim classification = GetKnownClassification(classifiedTextRun.ClassificationTypeName) Dim classification = GetKnownClassification(classifiedTextRun.ClassificationTypeName)
result.Append($"{classification}, ""{classifiedTextRun.Text.Replace("""", """""")}""") result.Append($"{classification}, ""{classifiedTextRun.Text.Replace("""", """""")}""")
If classifiedTextRun.NavigationAction IsNot Nothing OrElse Not String.IsNullOrEmpty(classifiedTextRun.Tooltip) Then
Dim tooltip = If(classifiedTextRun.Tooltip IsNot Nothing, $"""{classifiedTextRun.Tooltip.Replace("""", """""")}""", "Nothing")
result.Append($", navigationAction:=Sub() Return, {tooltip}")
End If
If classifiedTextRun.Style <> ClassifiedTextRunStyle.Plain Then If classifiedTextRun.Style <> ClassifiedTextRunStyle.Plain Then
result.Append($", {TextRunStyleToString(classifiedTextRun.Style)}") result.Append($", {TextRunStyleToString(classifiedTextRun.Style)}")
End If End If
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.Classification
Imports Microsoft.VisualStudio.Core.Imaging
Imports Microsoft.VisualStudio.Imaging
Imports Microsoft.VisualStudio.Text.Adornments
Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Public Class IntellisenseQuickInfoBuilderTests_Links
Inherits AbstractIntellisenseQuickInfoBuilderTests
<WpfTheory, Trait(Traits.Feature, Traits.Features.QuickInfo)>
<InlineData("see")>
<InlineData("a")>
Public Async Sub QuickInfoForPlainHyperlink(tag As String)
Dim workspace =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
using System.Threading;
class MyClass {
/// &lt;summary&gt;
/// This contains a link to &lt;<%= tag %> href="https://github.com/dotnet/roslyn"/&gt;.
/// &lt;/summary&gt;
void MyMethod() {
MyM$$ethod();
}
}
</Document>
</Project>
</Workspace>
Dim intellisenseQuickInfo = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp)
Dim expected = New ContainerElement(
ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding,
New ContainerElement(
ContainerElementStyle.Stacked,
New ContainerElement(
ContainerElementStyle.Wrapped,
New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.MethodPrivate)),
New ClassifiedTextElement(
New ClassifiedTextRun(ClassificationTypeNames.Keyword, "void"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.ClassName, "MyClass"),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."),
New ClassifiedTextRun(ClassificationTypeNames.MethodName, "MyMethod"),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")"))),
New ClassifiedTextElement(
New ClassifiedTextRun(ClassificationTypeNames.Text, "This contains a link to"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.Text, "https://github.com/dotnet/roslyn", navigationAction:=Sub() Return, "https://github.com/dotnet/roslyn"),
New ClassifiedTextRun(ClassificationTypeNames.Text, "."))))
AssertEqualAdornments(expected, intellisenseQuickInfo.Item)
End Sub
<WpfTheory, Trait(Traits.Feature, Traits.Features.QuickInfo)>
<InlineData("see")>
<InlineData("a")>
Public Async Sub QuickInfoForHyperlinkWithText(tag As String)
Dim workspace =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
using System.Threading;
class MyClass {
/// &lt;summary&gt;
/// This contains a link to &lt;<%= tag %> href="https://github.com/dotnet/roslyn"&gt;dotnet/roslyn&lt;/<%= tag %>&gt;.
/// &lt;/summary&gt;
void MyMethod() {
MyM$$ethod();
}
}
</Document>
</Project>
</Workspace>
Dim intellisenseQuickInfo = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp)
Dim expected = New ContainerElement(
ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding,
New ContainerElement(
ContainerElementStyle.Stacked,
New ContainerElement(
ContainerElementStyle.Wrapped,
New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.MethodPrivate)),
New ClassifiedTextElement(
New ClassifiedTextRun(ClassificationTypeNames.Keyword, "void"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.ClassName, "MyClass"),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."),
New ClassifiedTextRun(ClassificationTypeNames.MethodName, "MyMethod"),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")"))),
New ClassifiedTextElement(
New ClassifiedTextRun(ClassificationTypeNames.Text, "This contains a link to"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.Text, "dotnet/roslyn", navigationAction:=Sub() Return, "https://github.com/dotnet/roslyn"),
New ClassifiedTextRun(ClassificationTypeNames.Text, "."))))
AssertEqualAdornments(expected, intellisenseQuickInfo.Item)
End Sub
End Class
End Namespace
...@@ -31,13 +31,19 @@ namespace Microsoft.CodeAnalysis ...@@ -31,13 +31,19 @@ namespace Microsoft.CodeAnalysis
/// </summary> /// </summary>
internal TaggedTextStyle Style { get; } internal TaggedTextStyle Style { get; }
/// <summary>
/// Gets the navigation target for the text, or <see langword="null"/> if the text does not have a navigation
/// target.
/// </summary>
internal string NavigationTarget { get; }
/// <summary> /// <summary>
/// Creates a new instance of <see cref="TaggedText"/> /// Creates a new instance of <see cref="TaggedText"/>
/// </summary> /// </summary>
/// <param name="tag">A descriptive tag from <see cref="TextTags"/>.</param> /// <param name="tag">A descriptive tag from <see cref="TextTags"/>.</param>
/// <param name="text">The actual text to be displayed.</param> /// <param name="text">The actual text to be displayed.</param>
public TaggedText(string tag, string text) public TaggedText(string tag, string text)
: this(tag, text, TaggedTextStyle.None) : this(tag, text, TaggedTextStyle.None, navigationTarget: null)
{ {
} }
...@@ -47,11 +53,13 @@ public TaggedText(string tag, string text) ...@@ -47,11 +53,13 @@ public TaggedText(string tag, string text)
/// <param name="tag">A descriptive tag from <see cref="TextTags"/>.</param> /// <param name="tag">A descriptive tag from <see cref="TextTags"/>.</param>
/// <param name="text">The actual text to be displayed.</param> /// <param name="text">The actual text to be displayed.</param>
/// <param name="style">The style(s) to apply to the text.</param> /// <param name="style">The style(s) to apply to the text.</param>
internal TaggedText(string tag, string text, TaggedTextStyle style) /// <param name="navigationTarget">The navigation target for the text, or <see langword="null"/> if the text does not have a navigation target.</param>
internal TaggedText(string tag, string text, TaggedTextStyle style, string navigationTarget)
{ {
Tag = tag ?? throw new ArgumentNullException(nameof(tag)); Tag = tag ?? throw new ArgumentNullException(nameof(tag));
Text = text ?? throw new ArgumentNullException(nameof(text)); Text = text ?? throw new ArgumentNullException(nameof(text));
Style = style; Style = style;
NavigationTarget = navigationTarget;
} }
public override string ToString() public override string ToString()
...@@ -73,7 +81,7 @@ public static ImmutableArray<TaggedText> ToTaggedText(this IEnumerable<SymbolDis ...@@ -73,7 +81,7 @@ public static ImmutableArray<TaggedText> ToTaggedText(this IEnumerable<SymbolDis
} }
return displayParts.SelectAsArray(d => return displayParts.SelectAsArray(d =>
new TaggedText(SymbolDisplayPartKindTags.GetTag(d.Kind), d.ToString(), style)); new TaggedText(SymbolDisplayPartKindTags.GetTag(d.Kind), d.ToString(), style, navigationTarget: null));
} }
public static string JoinText(this ImmutableArray<TaggedText> values) public static string JoinText(this ImmutableArray<TaggedText> values)
......
...@@ -31,10 +31,12 @@ private class FormatterState ...@@ -31,10 +31,12 @@ private class FormatterState
internal readonly List<TaggedText> Builder = new List<TaggedText>(); internal readonly List<TaggedText> Builder = new List<TaggedText>();
private readonly List<(DocumentationCommentListType type, int index, bool renderedItem)> _listStack = new List<(DocumentationCommentListType type, int index, bool renderedItem)>(); private readonly List<(DocumentationCommentListType type, int index, bool renderedItem)> _listStack = new List<(DocumentationCommentListType type, int index, bool renderedItem)>();
private readonly Stack<string> _navigationTargetStack = new Stack<string>();
private readonly Stack<TaggedTextStyle> _styleStack = new Stack<TaggedTextStyle>(); private readonly Stack<TaggedTextStyle> _styleStack = new Stack<TaggedTextStyle>();
public FormatterState() public FormatterState()
{ {
_navigationTargetStack.Push(null);
_styleStack.Push(TaggedTextStyle.None); _styleStack.Push(TaggedTextStyle.None);
} }
...@@ -51,6 +53,7 @@ public bool AtBeginning ...@@ -51,6 +53,7 @@ public bool AtBeginning
public SymbolDisplayFormat Format { get; internal set; } public SymbolDisplayFormat Format { get; internal set; }
internal string NavigationTarget => _navigationTargetStack.Peek();
internal TaggedTextStyle Style => _styleStack.Peek(); internal TaggedTextStyle Style => _styleStack.Peek();
public void AppendSingleSpace() public void AppendSingleSpace()
...@@ -62,7 +65,7 @@ public void AppendString(string s) ...@@ -62,7 +65,7 @@ public void AppendString(string s)
{ {
EmitPendingChars(); EmitPendingChars();
Builder.Add(new TaggedText(TextTags.Text, s, Style)); Builder.Add(new TaggedText(TextTags.Text, s, Style, NavigationTarget));
_anyNonWhitespaceSinceLastPara = true; _anyNonWhitespaceSinceLastPara = true;
} }
...@@ -115,6 +118,16 @@ public void PopList() ...@@ -115,6 +118,16 @@ public void PopList()
MarkBeginOrEndPara(); MarkBeginOrEndPara();
} }
public void PushNavigationTarget(string navigationTarget)
{
_navigationTargetStack.Push(navigationTarget);
}
public void PopNavigationTarget()
{
_navigationTargetStack.Pop();
}
public void PushStyle(TaggedTextStyle style) public void PushStyle(TaggedTextStyle style)
{ {
_styleStack.Push(_styleStack.Peek() | style); _styleStack.Push(_styleStack.Peek() | style);
...@@ -272,9 +285,11 @@ private static void AppendTextFromNode(FormatterState state, XNode node, Compila ...@@ -272,9 +285,11 @@ private static void AppendTextFromNode(FormatterState state, XNode node, Compila
var name = element.Name.LocalName; var name = element.Name.LocalName;
var needPopStyle = false; var needPopStyle = false;
string navigationTarget = null;
if (name == DocumentationCommentXmlNames.SeeElementName || if (name == DocumentationCommentXmlNames.SeeElementName ||
name == DocumentationCommentXmlNames.SeeAlsoElementName) name == DocumentationCommentXmlNames.SeeAlsoElementName ||
name == "a")
{ {
if (element.IsEmpty || element.FirstNode == null) if (element.IsEmpty || element.FirstNode == null)
{ {
...@@ -285,6 +300,14 @@ private static void AppendTextFromNode(FormatterState state, XNode node, Compila ...@@ -285,6 +300,14 @@ private static void AppendTextFromNode(FormatterState state, XNode node, Compila
return; return;
} }
else
{
navigationTarget = GetNavigationTarget(element);
if (navigationTarget is object)
{
state.PushNavigationTarget(navigationTarget);
}
}
} }
else if (name == DocumentationCommentXmlNames.ParameterReferenceElementName || else if (name == DocumentationCommentXmlNames.ParameterReferenceElementName ||
name == DocumentationCommentXmlNames.TypeParameterReferenceElementName) name == DocumentationCommentXmlNames.TypeParameterReferenceElementName)
...@@ -381,6 +404,11 @@ private static void AppendTextFromNode(FormatterState state, XNode node, Compila ...@@ -381,6 +404,11 @@ private static void AppendTextFromNode(FormatterState state, XNode node, Compila
state.PopStyle(); state.PopStyle();
} }
if (navigationTarget is object)
{
state.PopNavigationTarget();
}
if (name == DocumentationCommentXmlNames.TermElementName) if (name == DocumentationCommentXmlNames.TermElementName)
{ {
state.AppendSingleSpace(); state.AppendSingleSpace();
...@@ -388,6 +416,17 @@ private static void AppendTextFromNode(FormatterState state, XNode node, Compila ...@@ -388,6 +416,17 @@ private static void AppendTextFromNode(FormatterState state, XNode node, Compila
} }
} }
private static string GetNavigationTarget(XElement element)
{
var hrefAttribute = element.Attribute(DocumentationCommentXmlNames.HrefAttributeName);
if (hrefAttribute is object)
{
return hrefAttribute.Value;
}
return null;
}
private static void AppendTextFromAttribute(FormatterState state, XElement element, XAttribute attribute, string attributeNameToParse, SymbolDisplayPartKind kind) private static void AppendTextFromAttribute(FormatterState state, XElement element, XAttribute attribute, string attributeNameToParse, SymbolDisplayPartKind kind)
{ {
var attributeName = attribute.Name.LocalName; var attributeName = attribute.Name.LocalName;
...@@ -401,7 +440,12 @@ private static void AppendTextFromAttribute(FormatterState state, XElement eleme ...@@ -401,7 +440,12 @@ private static void AppendTextFromAttribute(FormatterState state, XElement eleme
var displayKind = attributeName == DocumentationCommentXmlNames.LangwordAttributeName var displayKind = attributeName == DocumentationCommentXmlNames.LangwordAttributeName
? TextTags.Keyword ? TextTags.Keyword
: TextTags.Text; : TextTags.Text;
state.AppendParts(SpecializedCollections.SingletonEnumerable(new TaggedText(displayKind, attribute.Value, state.Style))); var text = attribute.Value;
var style = state.Style;
var navigationTarget = attributeName == DocumentationCommentXmlNames.HrefAttributeName
? attribute.Value
: null;
state.AppendParts(SpecializedCollections.SingletonEnumerable(new TaggedText(displayKind, text, style, navigationTarget)));
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册