Update, add tooltips for more Symbol types (#49)

Co-authored-by: Matt Parker <61717342+MattParkerDev@users.noreply.github.com>
This commit is contained in:
Manuel Gasser
2025-12-22 04:10:18 +01:00
committed by GitHub
parent 9867a5d7c9
commit 2228785a3d
15 changed files with 438 additions and 31 deletions

View File

@@ -260,6 +260,7 @@ public partial class CustomHighlighter : SyntaxHighlighter
"type parameter name" => CachedColors.ClassGreen,
"delegate name" => CachedColors.ClassGreen,
"event name" => CachedColors.White,
"label name" => CachedColors.White,
// Punctuation & operators
"operator" => CachedColors.White,

View File

@@ -146,15 +146,21 @@ public partial class SharpIdeCodeEdit
diagnosticPanel?.AddThemeStyleboxOverride(ThemeStringNames.Panel, styleBox);
diagnosticPanel?.AddChild(diagnosticNode);
var symbolInfoNode = roslynSymbol switch
{
IMethodSymbol methodSymbol => SymbolInfoComponents.GetMethodSymbolInfo(methodSymbol),
INamedTypeSymbol namedTypeSymbol => SymbolInfoComponents.GetNamedTypeSymbolInfo(namedTypeSymbol),
IPropertySymbol propertySymbol => SymbolInfoComponents.GetPropertySymbolInfo(propertySymbol),
IFieldSymbol { ContainingType.TypeKind: TypeKind.Enum } fieldSymbol => SymbolInfoComponents.GetEnumValueSymbolInfo(fieldSymbol),
IFieldSymbol fieldSymbol => SymbolInfoComponents.GetFieldSymbolInfo(fieldSymbol),
IParameterSymbol parameterSymbol => SymbolInfoComponents.GetParameterSymbolInfo(parameterSymbol),
ILocalSymbol localSymbol => SymbolInfoComponents.GetLocalVariableSymbolInfo(localSymbol),
INamespaceSymbol namespaceSymbol => SymbolInfoComponents.GetNamespaceSymbolInfo(namespaceSymbol),
ITypeParameterSymbol typeParameterSymbol => SymbolInfoComponents.GetTypeParameterSymbolInfo(typeParameterSymbol),
IDynamicTypeSymbol dynamicTypeSymbol => SymbolInfoComponents.GetDynamicTypeSymbolInfo(dynamicTypeSymbol),
IEventSymbol eventSymbol => SymbolInfoComponents.GetEventSymbolInfo(eventSymbol),
IDiscardSymbol discardSymbol => SymbolInfoComponents.GetDiscardSymbolInfo(discardSymbol),
ILabelSymbol labelSymbol => SymbolInfoComponents.GetLabelSymbolInfo(labelSymbol),
_ => SymbolInfoComponents.GetUnknownTooltip(roslynSymbol)
};
symbolInfoNode.FitContent = true;

View File

@@ -49,6 +49,17 @@ public static partial class SymbolInfoComponents
label.Pop();
}
private static void AddSealedModifier(this RichTextLabel label, ISymbol symbol)
{
if (symbol.IsSealed)
{
label.PushColor(CachedColors.KeywordBlue);
label.AddText("sealed");
label.Pop();
label.AddText(" ");
}
}
private static void AddOverrideModifier(this RichTextLabel label, ISymbol methodSymbol)
{
if (methodSymbol.IsOverride)
@@ -97,16 +108,37 @@ public static partial class SymbolInfoComponents
{
if (symbol.ContainingNamespace is null || symbol.ContainingNamespace.IsGlobalNamespace) return; // might be wrong
label.Newline();
if (symbol.ContainingType is null)
{
label.AddText("in namespace ");
}
else
{
label.AddText("in class ");
}
var namespaces = symbol.ContainingNamespace.ToDisplayString().Split('.');
label.AddText("in ");
label.AddText(GetNamedTypeSymbolTypeName(symbol.ContainingType)); // e.g. 'class' or 'namespace'
label.AddText(" ");
label.PushMeta("TODO", RichTextLabel.MetaUnderline.OnHover);
label.AddNamespace(symbol.ContainingNamespace);
if (symbol.ContainingType is not null)
{
label.AddText(".");
label.AddType(symbol.ContainingType);
}
label.Pop(); // meta
}
private static string GetNamedTypeSymbolTypeName(INamedTypeSymbol? symbol) => symbol?.TypeKind switch
{
TypeKind.Class when symbol.IsRecord => "record",
TypeKind.Class => "class",
TypeKind.Delegate => "delegate",
TypeKind.Enum => "enum",
TypeKind.Interface => "interface",
TypeKind.Struct when symbol.IsRecord => "record struct",
TypeKind.Struct => "struct",
null => "namespace",
_ => symbol.TypeKind.ToString().ToLowerInvariant()
};
private static void AddNamespace(this RichTextLabel label, INamespaceSymbol symbol)
{
var namespaces = symbol.ToDisplayString().Split('.');
foreach (var (index, ns) in namespaces.Index())
{
label.PushColor(CachedColors.KeywordBlue);
@@ -114,14 +146,6 @@ public static partial class SymbolInfoComponents
label.Pop();
if (index < namespaces.Length - 1) label.AddText(".");
}
if (symbol.ContainingType is not null)
{
label.AddText(".");
label.PushColor(CachedColors.ClassGreen);
label.AddText(symbol.ContainingType.Name);
label.Pop();
}
label.Pop(); // meta
}
private static void AddAttribute(this RichTextLabel label, AttributeData attribute, bool newLines)
@@ -358,6 +382,7 @@ public static partial class SymbolInfoComponents
INamedTypeSymbol namedTypeSymbol => label.AddNamedType(namedTypeSymbol),
ITypeParameterSymbol typeParameterSymbol => label.AddTypeParameter(typeParameterSymbol),
IArrayTypeSymbol arrayTypeSymbol => label.AddArrayType(arrayTypeSymbol),
IDynamicTypeSymbol dynamicTypeSymbol => label.AddDynamicType(dynamicTypeSymbol),
_ => label.AddUnknownType(symbol)
};
}
@@ -380,7 +405,7 @@ public static partial class SymbolInfoComponents
private static RichTextLabel AddSpecialType(this RichTextLabel label, ITypeSymbol symbol)
{
label.PushColor(CachedColors.KeywordBlue);
label.PushColor(symbol.GetSymbolColourByType());
label.AddText(symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
label.Pop();
return label;
@@ -423,13 +448,34 @@ public static partial class SymbolInfoComponents
label.Pop();
return label;
}
private static RichTextLabel AddDynamicType(this RichTextLabel label, IDynamicTypeSymbol symbol)
{
label.PushColor(CachedColors.KeywordBlue);
label.AddText(symbol.Name);
label.Pop();
return label;
}
// TODO: handle arrays etc, where there are multiple colours in one type
private static Color GetSymbolColourByType(this ITypeSymbol symbol)
{
Color colour = symbol switch
{
{SpecialType: not SpecialType.None} => CachedColors.KeywordBlue,
{SpecialType: not SpecialType.None} => symbol.SpecialType switch
{
SpecialType.System_Collections_IEnumerable => CachedColors.InterfaceGreen,
SpecialType.System_Collections_Generic_IEnumerable_T => CachedColors.InterfaceGreen,
SpecialType.System_Collections_Generic_IList_T => CachedColors.InterfaceGreen,
SpecialType.System_Collections_Generic_ICollection_T => CachedColors.InterfaceGreen,
SpecialType.System_Collections_IEnumerator => CachedColors.InterfaceGreen,
SpecialType.System_Collections_Generic_IEnumerator_T => CachedColors.InterfaceGreen,
SpecialType.System_Collections_Generic_IReadOnlyList_T => CachedColors.InterfaceGreen,
SpecialType.System_Collections_Generic_IReadOnlyCollection_T => CachedColors.InterfaceGreen,
SpecialType.System_IDisposable => CachedColors.InterfaceGreen,
SpecialType.System_IAsyncResult => CachedColors.InterfaceGreen,
_ => CachedColors.KeywordBlue
},
INamedTypeSymbol namedTypeSymbol => namedTypeSymbol.TypeKind switch
{
TypeKind.Class => CachedColors.ClassGreen,
@@ -437,6 +483,7 @@ public static partial class SymbolInfoComponents
TypeKind.Struct => CachedColors.ClassGreen,
TypeKind.Enum => CachedColors.InterfaceGreen,
TypeKind.Delegate => CachedColors.ClassGreen,
TypeKind.Dynamic => CachedColors.KeywordBlue,
_ => CachedColors.Orange
},
_ => CachedColors.Orange

View File

@@ -0,0 +1,29 @@
using Godot;
using Microsoft.CodeAnalysis;
namespace SharpIDE.Godot.Features.CodeEditor;
public static partial class SymbolInfoComponents
{
public static RichTextLabel GetDiscardSymbolInfo(IDiscardSymbol symbol)
{
var label = new RichTextLabel();
label.PushColor(CachedColors.White);
label.PushFont(MonospaceFont);
label.AddText("discard ");
label.AddType(symbol.Type);
label.AddText(" ");
label.AddDiscard(symbol);
label.Pop();
label.Pop();
return label;
}
private static void AddDiscard(this RichTextLabel label, IDiscardSymbol _)
{
label.PushColor(CachedColors.VariableBlue);
label.AddText("_");
label.Pop();
}
}

View File

@@ -0,0 +1,21 @@
using Godot;
using Microsoft.CodeAnalysis;
namespace SharpIDE.Godot.Features.CodeEditor;
public static partial class SymbolInfoComponents
{
public static RichTextLabel GetDynamicTypeSymbolInfo(IDynamicTypeSymbol symbol)
{
var label = new RichTextLabel();
label.PushColor(CachedColors.White);
label.PushFont(MonospaceFont);
label.PushColor(CachedColors.KeywordBlue);
label.AddText(symbol.ToDisplayString());
label.Pop();
label.Pop();
label.Pop();
return label;
}
}

View File

@@ -0,0 +1,35 @@
using Godot;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace SharpIDE.Godot.Features.CodeEditor;
public static partial class SymbolInfoComponents
{
public static RichTextLabel GetEnumValueSymbolInfo(IFieldSymbol symbol)
{
if (symbol is { ContainingType.TypeKind: not TypeKind.Enum })
{
throw new ArgumentException("The containing type of the symbol must be an enum type.", nameof(symbol));
}
var label = new RichTextLabel();
label.PushColor(CachedColors.White);
label.PushFont(MonospaceFont);
label.AddAttributes(symbol);
label.AddFieldName(symbol);
label.AddText(" = ");
label.PushColor(CachedColors.NumberGreen);
label.AddText($"{symbol.ConstantValue}");
label.Pop();
label.AddText(";");
label.AddContainingNamespaceAndClass(symbol);
label.Newline();
label.Pop();
label.AddDocs(symbol);
label.Pop();
return label;
}
}

View File

@@ -0,0 +1,59 @@
using Godot;
using Microsoft.CodeAnalysis;
namespace SharpIDE.Godot.Features.CodeEditor;
public static partial class SymbolInfoComponents
{
public static RichTextLabel GetEventSymbolInfo(IEventSymbol symbol)
{
var label = new RichTextLabel();
label.PushColor(CachedColors.White);
label.PushFont(MonospaceFont);
label.AddAccessibilityModifier(symbol);
label.AddEventKeyword(symbol);
label.AddEventTypeName(symbol);
label.AddEventName(symbol);
label.AddEventMethods(symbol);
label.AddContainingNamespaceAndClass(symbol);
label.Newline();
label.Pop();
label.AddDocs(symbol);
label.Pop();
return label;
}
private static void AddEventKeyword(this RichTextLabel label, IEventSymbol symbol)
{
label.PushColor(CachedColors.KeywordBlue);
label.AddText("event ");
label.Pop();
}
private static void AddEventTypeName(this RichTextLabel label, IEventSymbol symbol)
{
label.AddType(symbol.Type);
label.AddText(" ");
}
private static void AddEventName(this RichTextLabel label, IEventSymbol symbol)
{
label.PushColor(CachedColors.White);
label.AddText(symbol.Name);
label.Pop();
}
private static void AddEventMethods(this RichTextLabel label, IEventSymbol symbol)
{
label.AddText(" { ");
label.PushColor(CachedColors.KeywordBlue);
label.AddText("add");
label.Pop();
label.AddText("; ");
label.PushColor(CachedColors.KeywordBlue);
label.AddText("remove");
label.Pop();
label.AddText("; }");
}
}

View File

@@ -17,6 +17,7 @@ public static partial class SymbolInfoComponents
label.AddVirtualModifier(symbol);
label.AddAbstractModifier(symbol);
label.AddOverrideModifier(symbol);
label.AddRequiredModifier(symbol);
label.AddFieldTypeName(symbol);
label.AddFieldName(symbol);
label.AddText(";");
@@ -51,6 +52,17 @@ public static partial class SymbolInfoComponents
label.AddText(" ");
}
}
private static void AddRequiredModifier(this RichTextLabel label, IFieldSymbol symbol)
{
if (symbol.IsRequired)
{
label.PushColor(CachedColors.KeywordBlue);
label.AddText("required");
label.Pop();
label.AddText(" ");
}
}
private static void AddFieldTypeName(this RichTextLabel label, IFieldSymbol fieldSymbol)
{

View File

@@ -0,0 +1,27 @@
using Godot;
using Microsoft.CodeAnalysis;
namespace SharpIDE.Godot.Features.CodeEditor;
public static partial class SymbolInfoComponents
{
public static RichTextLabel GetLabelSymbolInfo(ILabelSymbol symbol)
{
var label = new RichTextLabel();
label.PushColor(CachedColors.White);
label.PushFont(MonospaceFont);
label.AddText("label ");
label.AddLabelName(symbol);
label.Pop();
label.Pop();
return label;
}
private static void AddLabelName(this RichTextLabel label, ILabelSymbol symbol)
{
label.PushColor(CachedColors.White);
label.AddText(symbol.Name);
label.Pop();
}
}

View File

@@ -35,7 +35,7 @@ public static partial class SymbolInfoComponents
private static void AddLocalVariableName(this RichTextLabel label, ILocalSymbol symbol)
{
label.PushColor(CachedColors.White);
label.PushColor(CachedColors.VariableBlue);
label.AddText(symbol.Name);
label.Pop();
}

View File

@@ -1,5 +1,7 @@
using Godot;
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
namespace SharpIDE.Godot.Features.CodeEditor;
@@ -15,6 +17,7 @@ public static partial class SymbolInfoComponents
label.AddMethodStaticModifier(methodSymbol);
label.AddVirtualModifier(methodSymbol);
label.AddAbstractModifier(methodSymbol);
label.AddSealedModifier(methodSymbol);
label.AddOverrideModifier(methodSymbol);
label.AddMethodAsyncModifier(methodSymbol);
label.AddMethodReturnType(methodSymbol);
@@ -24,6 +27,7 @@ public static partial class SymbolInfoComponents
label.AddText("(");
label.AddParameters(methodSymbol);
label.AddText(")");
label.AddTypeParameterConstraints(methodSymbol.TypeParameters);
label.AddContainingNamespaceAndClass(methodSymbol);
label.Newline();
label.AddTypeParameterArguments(methodSymbol);
@@ -221,4 +225,89 @@ public static partial class SymbolInfoComponents
}
}
}
}
private static void AddTypeParameterConstraints(this RichTextLabel label, ImmutableArray<ITypeParameterSymbol> typeParameters)
{
foreach (var typeParameter in typeParameters)
{
var hasConstraints = typeParameter.HasReferenceTypeConstraint || typeParameter.HasValueTypeConstraint || typeParameter.HasUnmanagedTypeConstraint || typeParameter.HasNotNullConstraint || typeParameter.HasConstructorConstraint || typeParameter.AllowsRefLikeType || typeParameter.ConstraintTypes.Length > 0;
if (hasConstraints is false) continue;
label.AddText(" ");
label.PushColor(CachedColors.KeywordBlue);
label.AddText("where");
label.Pop();
label.AddText(" ");
label.AddTypeParameter(typeParameter);
label.AddText(" : ");
var firstConstraintAdded = false;
if (typeParameter.HasReferenceTypeConstraint)
{
label.PushColor(CachedColors.KeywordBlue);
label.AddText("class");
label.Pop();
}
if (typeParameter.HasValueTypeConstraint)
{
MaybeAddComma();
label.PushColor(CachedColors.KeywordBlue);
label.AddText("struct");
label.Pop();
}
if (typeParameter.HasUnmanagedTypeConstraint)
{
MaybeAddComma();
label.PushColor(CachedColors.KeywordBlue);
label.AddText("unmanaged");
label.Pop();
}
if (typeParameter.HasNotNullConstraint)
{
MaybeAddComma();
label.PushColor(CachedColors.KeywordBlue);
label.AddText("notnull");
label.Pop();
}
foreach (var typeParameterConstraintType in typeParameter.ConstraintTypes)
{
MaybeAddComma();
label.AddType(typeParameterConstraintType);
}
if (typeParameter.HasConstructorConstraint)
{
MaybeAddComma();
label.PushColor(CachedColors.KeywordBlue);
label.AddText("new");
label.Pop();
label.AddText("()");
}
if (typeParameter.AllowsRefLikeType)
{
MaybeAddComma();
label.PushColor(CachedColors.KeywordBlue);
label.AddText("allows ref struct");
label.Pop();
}
continue;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void MaybeAddComma()
{
if (firstConstraintAdded is false)
{
firstConstraintAdded = true;
}
else
{
label.AddText(", ");
}
}
}
}
}

View File

@@ -12,6 +12,8 @@ public static partial class SymbolInfoComponents
label.PushFont(MonospaceFont);
label.AddAttributes(symbol);
label.AddAccessibilityModifier(symbol);
label.AddSealedModifier(symbol);
label.AddReadonlyModifier(symbol);
label.AddStaticModifier(symbol);
label.AddVirtualModifier(symbol);
label.AddAbstractModifier(symbol);
@@ -19,6 +21,7 @@ public static partial class SymbolInfoComponents
label.AddNamedTypeSymbolType(symbol);
label.AddNamedTypeSymbolName(symbol);
label.AddInheritedTypes(symbol);
label.AddTypeParameterConstraints(symbol.TypeParameters);
label.AddContainingNamespaceAndClass(symbol);
label.AddContainingPackage(symbol);
label.Newline();
@@ -37,15 +40,7 @@ public static partial class SymbolInfoComponents
private static void AddNamedTypeSymbolType(this RichTextLabel label, INamedTypeSymbol symbol)
{
label.PushColor(CachedColors.KeywordBlue);
switch (symbol.TypeKind)
{
case TypeKind.Class: label.AddText("class"); break;
case TypeKind.Struct: label.AddText("struct"); break;
case TypeKind.Interface: label.AddText("interface"); break;
case TypeKind.Enum: label.AddText("enum"); break;
case TypeKind.Delegate: label.AddText("delegate"); break;
default: label.AddText(symbol.TypeKind.ToString().ToLowerInvariant()); break;
}
label.AddText(GetNamedTypeSymbolTypeName(symbol));
label.Pop();
label.AddText(" ");
}
@@ -90,4 +85,15 @@ public static partial class SymbolInfoComponents
label.Pop();
}
}
private static void AddReadonlyModifier(this RichTextLabel label, INamedTypeSymbol symbol)
{
if (symbol.IsReadOnly)
{
label.PushColor(CachedColors.KeywordBlue);
label.AddText("readonly");
label.Pop();
label.AddText(" ");
}
}
}

View File

@@ -0,0 +1,23 @@
using Godot;
using Microsoft.CodeAnalysis;
namespace SharpIDE.Godot.Features.CodeEditor;
public static partial class SymbolInfoComponents
{
public static RichTextLabel GetNamespaceSymbolInfo(INamespaceSymbol symbol)
{
var label = new RichTextLabel();
label.PushColor(CachedColors.White);
label.PushFont(MonospaceFont);
label.PushColor(CachedColors.KeywordBlue);
label.AddText("namespace");
label.Pop(); // color
label.AddText(" ");
label.AddNamespace(symbol);
label.Pop(); // font
label.Pop(); // color
return label;
}
}

View File

@@ -17,6 +17,7 @@ public partial class SymbolInfoComponents
label.AddVirtualModifier(symbol);
label.AddAbstractModifier(symbol);
label.AddOverrideModifier(symbol);
label.AddRequiredModifier(symbol);
label.AddPropertyTypeName(symbol);
label.AddPropertyName(symbol);
label.AddGetSetAccessors(symbol);
@@ -40,6 +41,17 @@ public partial class SymbolInfoComponents
label.AddText(" ");
}
}
private static void AddRequiredModifier(this RichTextLabel label, IPropertySymbol symbol)
{
if (symbol.IsRequired)
{
label.PushColor(CachedColors.KeywordBlue);
label.AddText("required");
label.Pop();
label.AddText(" ");
}
}
private static void AddPropertyTypeName(this RichTextLabel label, IPropertySymbol symbol)
{

View File

@@ -0,0 +1,40 @@
using Godot;
using Microsoft.CodeAnalysis;
namespace SharpIDE.Godot.Features.CodeEditor;
public static partial class SymbolInfoComponents
{
public static RichTextLabel GetTypeParameterSymbolInfo(ITypeParameterSymbol symbol)
{
var label = new RichTextLabel();
label.PushColor(CachedColors.White);
label.PushFont(MonospaceFont);
label.AddTypeParameter(symbol);
label.AddText(" in ");
if (symbol.DeclaringMethod is { } declaringMethod)
{
label.AddDeclaringMethod(declaringMethod);
}
else
{
label.AddType(symbol.ContainingType);
label.AddTypeParameterConstraints(symbol.ContainingType.TypeParameters);
}
label.Pop();
label.Pop();
return label;
}
private static void AddDeclaringMethod(this RichTextLabel label, IMethodSymbol symbol)
{
label.AddType(symbol.ContainingType);
label.AddText(".");
label.AddMethodName(symbol);
label.AddTypeParameters(symbol);
label.AddTypeParameterConstraints(symbol.TypeParameters);
}
}