|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.Serialization;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// A piece of text with a descriptive tag.
/// </summary>
[DataContract]
public readonly record struct TaggedText
{
/// <summary>
/// A descriptive tag from <see cref="TextTags"/>.
/// </summary>
[DataMember(Order = 0)]
public string Tag { get; }
/// <summary>
/// The actual text to be displayed.
/// </summary>
[DataMember(Order = 1)]
public string Text { get; }
/// <summary>
/// Gets the style(s) to apply to the text.
/// </summary>
[DataMember(Order = 2)]
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>
[DataMember(Order = 3)]
internal string NavigationTarget { get; }
/// <summary>
/// Gets the navigation hint for the text, or <see langword="null"/> if the text does not have a navigation
/// hint.
/// </summary>
[DataMember(Order = 4)]
internal string NavigationHint { get; }
/// <summary>
/// Creates a new instance of <see cref="TaggedText"/>
/// </summary>
/// <param name="tag">A descriptive tag from <see cref="TextTags"/>.</param>
/// <param name="text">The actual text to be displayed.</param>
public TaggedText(string tag, string text)
: this(tag, text, TaggedTextStyle.None, navigationTarget: null, navigationHint: null)
{
}
/// <summary>
/// Creates a new instance of <see cref="TaggedText"/>
/// </summary>
/// <param name="tag">A descriptive tag from <see cref="TextTags"/>.</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="navigationTarget">The navigation target for the text, or <see langword="null"/> if the text does not have a navigation target.</param>
/// <param name="navigationHint">The navigation hint for the text, or <see langword="null"/> if the text does not have a navigation hint.</param>
internal TaggedText(string tag, string text, TaggedTextStyle style, string navigationTarget, string navigationHint)
{
Tag = tag ?? throw new ArgumentNullException(nameof(tag));
Text = text ?? throw new ArgumentNullException(nameof(text));
Style = style;
NavigationTarget = navigationTarget;
NavigationHint = navigationHint;
}
public override string ToString()
=> Text;
}
internal static class TaggedTextExtensions
{
public static ImmutableArray<TaggedText> ToTaggedText(this IEnumerable<SymbolDisplayPart> displayParts, Func<ISymbol, string> getNavigationHint = null, bool includeNavigationHints = true)
=> displayParts.ToTaggedText(TaggedTextStyle.None, getNavigationHint, includeNavigationHints);
public static ImmutableArray<TaggedText> ToTaggedText(
this IEnumerable<SymbolDisplayPart> displayParts, TaggedTextStyle style, Func<ISymbol, string> getNavigationHint = null, bool includeNavigationHints = true)
{
if (displayParts == null)
return ImmutableArray<TaggedText>.Empty;
getNavigationHint ??= static symbol => symbol?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
return displayParts.SelectAsArray(d =>
new TaggedText(
GetTag(d),
d.ToString(),
style,
includeNavigationHints && d.Kind != SymbolDisplayPartKind.NamespaceName ? GetNavigationTarget(d.Symbol) : null,
includeNavigationHints && d.Kind != SymbolDisplayPartKind.NamespaceName ? getNavigationHint(d.Symbol) : null));
}
private static string GetTag(SymbolDisplayPart part)
{
// We don't actually have any specific classifications for aliases. So if the compiler passed us that kind,
// attempt to map to the corresponding namespace/named-type kind that matches the underlying alias target.
if (part is { Symbol: IAliasSymbol alias, Kind: SymbolDisplayPartKind.AliasName })
{
if (alias.Target is INamespaceSymbol)
return SymbolDisplayPartKindTags.GetTag(SymbolDisplayPartKind.NamespaceName);
else if (alias.Target is INamedTypeSymbol namedType)
return SymbolDisplayPartKindTags.GetTag(namedType.GetSymbolDisplayPartKind());
}
return SymbolDisplayPartKindTags.GetTag(part.Kind);
}
private static string GetNavigationTarget(ISymbol symbol)
=> symbol is null ? null : SymbolKey.CreateString(symbol);
public static string JoinText(this ImmutableArray<TaggedText> values)
{
return values.IsDefault
? null
: Join(values);
}
private static string Join(ImmutableArray<TaggedText> values)
{
var pooled = PooledStringBuilder.GetInstance();
var builder = pooled.Builder;
foreach (var val in values)
{
builder.Append(val.Text);
}
return pooled.ToStringAndFree();
}
public static string ToClassificationTypeName(this string taggedTextTag)
{
switch (taggedTextTag)
{
case TextTags.Keyword:
return ClassificationTypeNames.Keyword;
case TextTags.Class:
return ClassificationTypeNames.ClassName;
case TextTags.Delegate:
return ClassificationTypeNames.DelegateName;
case TextTags.Enum:
return ClassificationTypeNames.EnumName;
case TextTags.Interface:
return ClassificationTypeNames.InterfaceName;
case TextTags.Module:
return ClassificationTypeNames.ModuleName;
case TextTags.Struct:
return ClassificationTypeNames.StructName;
case TextTags.TypeParameter:
return ClassificationTypeNames.TypeParameterName;
case TextTags.Field:
return ClassificationTypeNames.FieldName;
case TextTags.Event:
return ClassificationTypeNames.EventName;
case TextTags.Label:
return ClassificationTypeNames.LabelName;
case TextTags.Local:
return ClassificationTypeNames.LocalName;
case TextTags.Method:
return ClassificationTypeNames.MethodName;
case TextTags.Namespace:
return ClassificationTypeNames.NamespaceName;
case TextTags.Parameter:
return ClassificationTypeNames.ParameterName;
case TextTags.Property:
return ClassificationTypeNames.PropertyName;
case TextTags.ExtensionMethod:
return ClassificationTypeNames.ExtensionMethodName;
case TextTags.EnumMember:
return ClassificationTypeNames.EnumMemberName;
case TextTags.Constant:
return ClassificationTypeNames.ConstantName;
case TextTags.Alias:
case TextTags.Assembly:
case TextTags.ErrorType:
case TextTags.RangeVariable:
return ClassificationTypeNames.Identifier;
case TextTags.NumericLiteral:
return ClassificationTypeNames.NumericLiteral;
case TextTags.StringLiteral:
return ClassificationTypeNames.StringLiteral;
case TextTags.Space:
case TextTags.LineBreak:
return ClassificationTypeNames.WhiteSpace;
case TextTags.Operator:
return ClassificationTypeNames.Operator;
case TextTags.Punctuation:
return ClassificationTypeNames.Punctuation;
case TextTags.AnonymousTypeIndicator:
case TextTags.Text:
return ClassificationTypeNames.Text;
case TextTags.Record:
return ClassificationTypeNames.RecordClassName;
case TextTags.RecordStruct:
return ClassificationTypeNames.RecordStructName;
case TextTags.ContainerStart:
case TextTags.ContainerEnd:
case TextTags.CodeBlockStart:
case TextTags.CodeBlockEnd:
// These tags are not visible so classify them as whitespace
return ClassificationTypeNames.WhiteSpace;
default:
throw ExceptionUtilities.UnexpectedValue(taggedTextTag);
}
}
public static IEnumerable<ClassifiedSpan> ToClassifiedSpans(
this IEnumerable<TaggedText> parts)
{
var index = 0;
foreach (var part in parts)
{
var text = part.ToString();
var classificationTypeName = part.Tag.ToClassificationTypeName();
yield return new ClassifiedSpan(new TextSpan(index, text.Length), classificationTypeName);
index += text.Length;
}
}
private const string LeftToRightMarkerPrefix = "\u200e";
public static string ToVisibleDisplayString(this TaggedText part, bool includeLeftToRightMarker)
{
var text = part.ToString();
if (includeLeftToRightMarker)
{
var classificationTypeName = part.Tag.ToClassificationTypeName();
if (classificationTypeName is ClassificationTypeNames.Punctuation or
ClassificationTypeNames.WhiteSpace)
{
text = LeftToRightMarkerPrefix + text;
}
}
return text;
}
public static string ToVisibleDisplayString(this IEnumerable<TaggedText> parts, bool includeLeftToRightMarker)
{
return string.Join(string.Empty, parts.Select(
p => p.ToVisibleDisplayString(includeLeftToRightMarker)));
}
public static string GetFullText(this IEnumerable<TaggedText> parts)
=> string.Join(string.Empty, parts.Select(p => p.ToString()));
public static void AddAliasName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Alias, text));
public static void AddAssemblyName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Assembly, text));
public static void AddClassName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Class, text));
public static void AddDelegateName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Delegate, text));
public static void AddEnumName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Enum, text));
public static void AddErrorTypeName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.ErrorType, text));
public static void AddEventName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Event, text));
public static void AddFieldName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Field, text));
public static void AddInterfaceName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Interface, text));
public static void AddKeyword(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Keyword, text));
public static void AddLabelName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Label, text));
public static void AddLineBreak(this IList<TaggedText> parts, string text = "\r\n")
=> parts.Add(new TaggedText(TextTags.LineBreak, text));
public static void AddNumericLiteral(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.NumericLiteral, text));
public static void AddStringLiteral(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.StringLiteral, text));
public static void AddLocalName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Local, text));
public static void AddMethodName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Method, text));
public static void AddModuleName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Module, text));
public static void AddNamespaceName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Namespace, text));
public static void AddOperator(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Operator, text));
public static void AddParameterName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Parameter, text));
public static void AddPropertyName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Property, text));
public static void AddPunctuation(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Punctuation, text));
public static void AddRangeVariableName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.RangeVariable, text));
public static void AddStructName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Struct, text));
public static void AddSpace(this IList<TaggedText> parts, string text = " ")
=> parts.Add(new TaggedText(TextTags.Space, text));
public static void AddText(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.Text, text));
public static void AddTypeParameterName(this IList<TaggedText> parts, string text)
=> parts.Add(new TaggedText(TextTags.TypeParameter, text));
}
}
|