hover method symbol v1
This commit is contained in:
@@ -14,8 +14,8 @@ using Microsoft.CodeAnalysis.MSBuild;
|
||||
using Microsoft.CodeAnalysis.Razor.SemanticTokens;
|
||||
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Remote.Razor.SemanticTokens;
|
||||
using Microsoft.CodeAnalysis.Shared.Utilities;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using NuGet.Packaging;
|
||||
using SharpIDE.Application.Features.Analysis.FixLoaders;
|
||||
using SharpIDE.Application.Features.Analysis.Razor;
|
||||
using SharpIDE.Application.Features.SolutionDiscovery;
|
||||
@@ -476,6 +476,37 @@ public static class RoslynAnalysis
|
||||
return changedFilesWithText;
|
||||
}
|
||||
|
||||
public static async Task<ISymbol?> LookupSymbol(SharpIdeFile fileModel, LinePosition linePosition)
|
||||
{
|
||||
await _solutionLoadedTcs.Task;
|
||||
var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == ((IChildSharpIdeNode)fileModel).GetNearestProjectNode()!.FilePath);
|
||||
var document = project.Documents.Single(s => s.FilePath == fileModel.Path);
|
||||
Guard.Against.Null(document, nameof(document));
|
||||
var sourceText = await document.GetTextAsync();
|
||||
var position = sourceText.GetPosition(linePosition);
|
||||
var semanticModel = await document.GetSemanticModelAsync();
|
||||
Guard.Against.Null(semanticModel, nameof(semanticModel));
|
||||
var syntaxRoot = await document.GetSyntaxRootAsync();
|
||||
var node = syntaxRoot!.FindToken(position).Parent!;
|
||||
var symbol = semanticModel.GetSymbolInfo(node).Symbol ?? semanticModel.GetDeclaredSymbol(node);
|
||||
|
||||
if (symbol is null)
|
||||
{
|
||||
Console.WriteLine("No symbol found at position");
|
||||
return null;
|
||||
}
|
||||
|
||||
var documentationCommentXml = symbol.GetDocumentationCommentXml();
|
||||
if (documentationCommentXml is not null)
|
||||
{
|
||||
var comment = DocumentationComment.FromXmlFragment(documentationCommentXml);
|
||||
;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Symbol found: {symbol.Name} ({symbol.Kind}) - {symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}");
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public static void UpdateDocument(SharpIdeFile fileModel, string newContent)
|
||||
{
|
||||
Guard.Against.Null(fileModel, nameof(fileModel));
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.LanguageServer.Protocol" />
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.ExternalAccess.Razor.Features" />
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.Razor.SemanticTokens" />
|
||||
<IgnoresAccessChecksTo Include="Microsoft.CodeAnalysis.Workspaces" />
|
||||
<IgnoresAccessChecksToExcludeTypeName Include="System.Linq.RoslynEnumerableExtensions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -146,9 +146,9 @@ public partial class CustomHighlighter : SyntaxHighlighter
|
||||
"number" => CachedColors.NumberGreen,
|
||||
|
||||
// Types (User Types)
|
||||
"class name" => CachedColors.ClassBlue,
|
||||
"record class name" => CachedColors.ClassBlue,
|
||||
"struct name" => CachedColors.ClassBlue,
|
||||
"class name" => CachedColors.ClassGreen,
|
||||
"record class name" => CachedColors.ClassGreen,
|
||||
"struct name" => CachedColors.ClassGreen,
|
||||
"interface name" => CachedColors.InterfaceGreen,
|
||||
"enum name" => CachedColors.InterfaceGreen,
|
||||
"namespace name" => CachedColors.White,
|
||||
@@ -188,7 +188,7 @@ public partial class CustomHighlighter : SyntaxHighlighter
|
||||
}
|
||||
}
|
||||
|
||||
file static class CachedColors
|
||||
public static class CachedColors
|
||||
{
|
||||
public static readonly Color Orange = new("f27718");
|
||||
public static readonly Color White = new("dcdcdc");
|
||||
@@ -198,7 +198,7 @@ file static class CachedColors
|
||||
public static readonly Color LightOrangeBrown = new("d69d85");
|
||||
public static readonly Color NumberGreen = new("b5cea8");
|
||||
public static readonly Color InterfaceGreen = new("b8d7a3");
|
||||
public static readonly Color ClassBlue = new("4ec9b0");
|
||||
public static readonly Color ClassGreen = new("4ec9b0");
|
||||
public static readonly Color VariableBlue = new("9cdcfe");
|
||||
public static readonly Color Gray = new("a9a9a9");
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[gd_resource type="FontVariation" load_steps=2 format=3 uid="uid://cctwlwcoycek7"]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://7jc0nj310cu6" path="res://Features/CodeEditor/Resources/CascadiaCode.ttf" id="1_ba3y1"]
|
||||
|
||||
[resource]
|
||||
base_font = ExtResource("1_ba3y1")
|
||||
spacing_top = 3
|
||||
spacing_bottom = 2
|
||||
baseline_offset = 0.05
|
||||
@@ -82,12 +82,62 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
||||
private void OnSymbolValidate(string symbol)
|
||||
{
|
||||
GD.Print($"Symbol validating: {symbol}");
|
||||
//var valid = symbol.Contains(' ') is false;
|
||||
//SetSymbolLookupWordAsValid(valid);
|
||||
SetSymbolLookupWordAsValid(true);
|
||||
}
|
||||
|
||||
private void OnSymbolHovered(string symbol, long line, long column)
|
||||
private async void OnSymbolHovered(string symbol, long line, long column)
|
||||
{
|
||||
GD.Print($"Symbol hovered: {symbol}");
|
||||
if (HasFocus() is false) return; // only show if we have focus, every tab is currently listening for this event, maybe find a better way
|
||||
GD.Print($"Symbol hovered: {symbol} at line {line}, column {column}");
|
||||
|
||||
var roslynSymbol = await RoslynAnalysis.LookupSymbol(_currentFile, new LinePosition((int)line, (int)column));
|
||||
if (roslynSymbol is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var popupPanel = new PopupPanel();
|
||||
popupPanel.Unfocusable = true; // may need to change eventually for navigating to other symbols
|
||||
popupPanel.MouseExited += () => popupPanel.QueueFree();
|
||||
// set background color
|
||||
|
||||
var styleBox = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("2b2d30"),
|
||||
BorderColor = new Color("3e4045"),
|
||||
BorderWidthTop = 1,
|
||||
BorderWidthBottom = 1,
|
||||
BorderWidthLeft = 1,
|
||||
BorderWidthRight = 1,
|
||||
CornerRadiusBottomLeft = 4,
|
||||
CornerRadiusBottomRight = 4,
|
||||
CornerRadiusTopLeft = 4,
|
||||
CornerRadiusTopRight = 4,
|
||||
ShadowSize = 2,
|
||||
ShadowColor = new Color(0, 0, 0, 0.5f),
|
||||
ContentMarginTop = 10,
|
||||
ContentMarginBottom = 10,
|
||||
ContentMarginLeft = 10,
|
||||
ContentMarginRight = 10
|
||||
};
|
||||
popupPanel.AddThemeStyleboxOverride("panel", styleBox);
|
||||
|
||||
var symbolInfoNode = roslynSymbol switch
|
||||
{
|
||||
IMethodSymbol methodSymbol => SymbolInfoComponents.GetMethodSymbolInfo(methodSymbol),
|
||||
_ => new Control()
|
||||
};
|
||||
popupPanel.AddChild(symbolInfoNode);
|
||||
AddChild(popupPanel);
|
||||
|
||||
var globalSymbolPosition = GetRectAtLineColumn((int)line, (int)column).Position + GetGlobalPosition();
|
||||
globalSymbolPosition.Y += GetLineHeight();
|
||||
|
||||
var globalMousePosition = GetGlobalMousePosition();
|
||||
popupPanel.Position = new Vector2I((int)globalMousePosition.X, (int)globalSymbolPosition.Y);
|
||||
popupPanel.Popup();
|
||||
}
|
||||
|
||||
private void OnCaretChanged()
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://cinaqbdghcvoi"]
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cinaqbdghcvoi"]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://7jc0nj310cu6" path="res://Features/CodeEditor/Resources/CascadiaCode.ttf" id="1_s7ira"]
|
||||
[ext_resource type="FontVariation" uid="uid://cctwlwcoycek7" path="res://Features/CodeEditor/Resources/CascadiaFontVariation.tres" id="1_s7ira"]
|
||||
[ext_resource type="Script" uid="uid://du2lt7r1p1qfy" path="res://Features/CodeEditor/SharpIdeCodeEdit.cs" id="2_kp2fd"]
|
||||
|
||||
[sub_resource type="FontVariation" id="FontVariation_y3aoi"]
|
||||
base_font = ExtResource("1_s7ira")
|
||||
spacing_top = 3
|
||||
spacing_bottom = 2
|
||||
baseline_offset = 0.05
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v06ln"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
@@ -26,7 +20,7 @@ corner_detail = 5
|
||||
|
||||
[node name="SharpIdeCodeEdit" type="CodeEdit"]
|
||||
theme_override_colors/current_line_color = Color(0.0588235, 0.0588235, 0.0588235, 1)
|
||||
theme_override_fonts/font = SubResource("FontVariation_y3aoi")
|
||||
theme_override_fonts/font = ExtResource("1_s7ira")
|
||||
theme_override_font_sizes/font_size = 18
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_v06ln")
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_7ptyn")
|
||||
|
||||
276
src/SharpIDE.Godot/Features/CodeEditor/SymbolInfoComponents.cs
Normal file
276
src/SharpIDE.Godot/Features/CodeEditor/SymbolInfoComponents.cs
Normal file
@@ -0,0 +1,276 @@
|
||||
using Godot;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace SharpIDE.Godot.Features.CodeEditor;
|
||||
|
||||
public static class SymbolInfoComponents
|
||||
{
|
||||
private static readonly FontVariation MonospaceFont = ResourceLoader.Load<FontVariation>("uid://cctwlwcoycek7");
|
||||
public static RichTextLabel GetMethodSymbolInfo(IMethodSymbol methodSymbol)
|
||||
{
|
||||
var label = new RichTextLabel();
|
||||
label.FitContent = true;
|
||||
label.AutowrapMode = TextServer.AutowrapMode.Off;
|
||||
label.SetAnchorsPreset(Control.LayoutPreset.FullRect);
|
||||
label.PushFont(MonospaceFont);
|
||||
label.PushColor(CachedColors.KeywordBlue);
|
||||
label.AddText(methodSymbol.DeclaredAccessibility.GetAccessibilityString());
|
||||
label.Pop();
|
||||
label.AddText(" ");
|
||||
label.AddStaticModifier(methodSymbol);
|
||||
label.AddText(" ");
|
||||
label.AddMethodReturnType(methodSymbol);
|
||||
label.AddText(" ");
|
||||
label.AddMethodName(methodSymbol);
|
||||
label.AddTypeParameters(methodSymbol);
|
||||
label.AddText("(");
|
||||
label.AddParameters(methodSymbol);
|
||||
label.AddText(")");
|
||||
label.Newline();
|
||||
label.AddText("in class ");
|
||||
label.AddContainingNamespaceAndClass(methodSymbol);
|
||||
label.Newline(); // TODO: Make this only 1.5 lines high
|
||||
label.Newline(); //
|
||||
label.AddTypeParameterArguments(methodSymbol);
|
||||
label.AddHr(100, 1, CachedColors.Gray);
|
||||
label.Newline();
|
||||
label.Pop(); // font
|
||||
label.AddText("docs");
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
private static string GetAccessibilityString(this Accessibility accessibility) => accessibility switch
|
||||
{
|
||||
Accessibility.Public => "public",
|
||||
Accessibility.Private => "private",
|
||||
Accessibility.Protected => "protected",
|
||||
Accessibility.Internal => "internal",
|
||||
Accessibility.ProtectedOrInternal => "protected internal",
|
||||
Accessibility.ProtectedAndInternal => "private protected",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
private static void AddStaticModifier(this RichTextLabel label, IMethodSymbol methodSymbol)
|
||||
{
|
||||
if (methodSymbol.IsStatic || methodSymbol.ReducedFrom?.IsStatic is true)
|
||||
{
|
||||
label.PushColor(CachedColors.KeywordBlue);
|
||||
label.AddText("static");
|
||||
label.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddMethodReturnType(this RichTextLabel label, IMethodSymbol methodSymbol)
|
||||
{
|
||||
if (methodSymbol.ReturnsVoid)
|
||||
{
|
||||
label.PushColor(CachedColors.KeywordBlue);
|
||||
label.AddText("void");
|
||||
label.Pop();
|
||||
return;
|
||||
}
|
||||
|
||||
label.PushColor(CachedColors.ClassGreen);
|
||||
label.AddText(methodSymbol.ReturnType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
|
||||
label.Pop();
|
||||
}
|
||||
|
||||
private static void AddMethodName(this RichTextLabel label, IMethodSymbol methodSymbol)
|
||||
{
|
||||
label.PushColor(CachedColors.Yellow);
|
||||
label.AddText(methodSymbol.Name);
|
||||
label.Pop();
|
||||
}
|
||||
|
||||
private static void AddTypeParameters(this RichTextLabel label, IMethodSymbol methodSymbol)
|
||||
{
|
||||
if (methodSymbol.TypeParameters.Length == 0) return;
|
||||
label.PushColor(CachedColors.White);
|
||||
label.AddText("<");
|
||||
label.Pop();
|
||||
foreach (var (index, typeParameter) in methodSymbol.TypeParameters.Index())
|
||||
{
|
||||
label.PushColor(CachedColors.ClassGreen);
|
||||
label.AddText(typeParameter.Name);
|
||||
label.Pop();
|
||||
if (index < methodSymbol.TypeParameters.Length - 1)
|
||||
{
|
||||
label.AddText(", ");
|
||||
}
|
||||
}
|
||||
label.PushColor(CachedColors.White);
|
||||
label.AddText(">");
|
||||
label.Pop();
|
||||
}
|
||||
|
||||
private static void AddParameters(this RichTextLabel label, IMethodSymbol methodSymbol)
|
||||
{
|
||||
if (methodSymbol.IsExtensionMethod)
|
||||
{
|
||||
label.PushColor(CachedColors.KeywordBlue);
|
||||
label.AddText("this");
|
||||
label.Pop();
|
||||
label.AddText(" ");
|
||||
}
|
||||
foreach (var (index, parameterSymbol) in methodSymbol.Parameters.Index())
|
||||
{
|
||||
var attributes = parameterSymbol.GetAttributes();
|
||||
if (attributes.Length is not 0)
|
||||
{
|
||||
foreach (var (attrIndex, attribute) in attributes.Index())
|
||||
{
|
||||
label.AddText("[");
|
||||
label.PushColor(CachedColors.ClassGreen);
|
||||
var displayString = attribute.AttributeClass?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
|
||||
if (displayString?.EndsWith("Attribute") is true) displayString = displayString[..^9]; // remove last 9 chars
|
||||
label.AddText(displayString ?? "unknown");
|
||||
label.Pop();
|
||||
label.AddText("]");
|
||||
label.AddText(" ");
|
||||
}
|
||||
}
|
||||
if (parameterSymbol.RefKind != RefKind.None) // ref, in, out
|
||||
{
|
||||
label.PushColor(CachedColors.KeywordBlue);
|
||||
label.AddText(parameterSymbol.RefKind.ToString().ToLower());
|
||||
label.Pop();
|
||||
label.AddText(" ");
|
||||
}
|
||||
else if (parameterSymbol.IsParams)
|
||||
{
|
||||
label.PushColor(CachedColors.KeywordBlue);
|
||||
label.AddText("params");
|
||||
label.Pop();
|
||||
label.AddText(" ");
|
||||
}
|
||||
label.PushColor(parameterSymbol.Type.GetSymbolColourByType());
|
||||
label.AddText(parameterSymbol.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
|
||||
label.Pop();
|
||||
label.AddText(" ");
|
||||
label.PushColor(CachedColors.VariableBlue);
|
||||
label.AddText(parameterSymbol.Name);
|
||||
label.Pop();
|
||||
// default value
|
||||
if (parameterSymbol.HasExplicitDefaultValue)
|
||||
{
|
||||
label.AddText(" = ");
|
||||
if (parameterSymbol.ExplicitDefaultValue is null)
|
||||
{
|
||||
label.PushColor(CachedColors.KeywordBlue);
|
||||
label.AddText("null");
|
||||
label.Pop();
|
||||
}
|
||||
else if (parameterSymbol.Type.TypeKind == TypeKind.Enum)
|
||||
{
|
||||
var explicitDefaultValue = parameterSymbol.ExplicitDefaultValue;
|
||||
// Find the enum field with the same constant value
|
||||
var enumMember = parameterSymbol.Type.GetMembers()
|
||||
.OfType<IFieldSymbol>()
|
||||
.FirstOrDefault(f => f.HasConstantValue && Equals(f.ConstantValue, explicitDefaultValue));
|
||||
|
||||
if (enumMember != null)
|
||||
{
|
||||
label.PushColor(CachedColors.InterfaceGreen);
|
||||
label.AddText(parameterSymbol.Type.Name);
|
||||
label.Pop();
|
||||
label.PushColor(CachedColors.White);
|
||||
label.AddText(".");
|
||||
label.Pop();
|
||||
label.PushColor(CachedColors.White);
|
||||
label.AddText(enumMember.Name);
|
||||
label.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
label.PushColor(CachedColors.InterfaceGreen);
|
||||
label.AddText(parameterSymbol.Type.Name);
|
||||
label.Pop();
|
||||
label.AddText($"({explicitDefaultValue})");
|
||||
}
|
||||
}
|
||||
else if (parameterSymbol.ExplicitDefaultValue is string str)
|
||||
{
|
||||
label.PushColor(CachedColors.LightOrangeBrown);
|
||||
label.AddText($"""
|
||||
"{str}"
|
||||
""");
|
||||
label.Pop();
|
||||
}
|
||||
else if (parameterSymbol.ExplicitDefaultValue is bool b)
|
||||
{
|
||||
label.PushColor(CachedColors.KeywordBlue);
|
||||
label.AddText(b ? "true" : "false");
|
||||
label.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
label.AddText(parameterSymbol.ExplicitDefaultValue.ToString() ?? "unknown");
|
||||
}
|
||||
}
|
||||
|
||||
if (index < methodSymbol.Parameters.Length - 1)
|
||||
{
|
||||
label.AddText(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddContainingNamespaceAndClass(this RichTextLabel label, IMethodSymbol methodSymbol)
|
||||
{
|
||||
if (methodSymbol.ContainingNamespace is null || methodSymbol.ContainingNamespace.IsGlobalNamespace) return;
|
||||
var namespaces = methodSymbol.ContainingNamespace.ToDisplayString().Split('.');
|
||||
foreach (var ns in namespaces)
|
||||
{
|
||||
label.PushColor(CachedColors.KeywordBlue);
|
||||
label.AddText(ns);
|
||||
label.Pop();
|
||||
label.AddText(".");
|
||||
}
|
||||
label.PushColor(CachedColors.ClassGreen);
|
||||
label.AddText(methodSymbol.ContainingType.Name);
|
||||
label.Pop();
|
||||
}
|
||||
|
||||
private static void AddTypeParameterArguments(this RichTextLabel label, IMethodSymbol methodSymbol)
|
||||
{
|
||||
if (methodSymbol.TypeArguments.Length == 0) return;
|
||||
var typeParameters = methodSymbol.TypeParameters;
|
||||
var typeArguments = methodSymbol.TypeArguments;
|
||||
if (typeParameters.Length != typeArguments.Length) throw new Exception("Type parameters and type arguments length mismatch.");
|
||||
foreach (var (index, (typeArgument, typeParameter)) in methodSymbol.TypeArguments.Zip(typeParameters).Index())
|
||||
{
|
||||
label.PushColor(CachedColors.ClassGreen);
|
||||
label.AddText(typeParameter.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
|
||||
label.Pop();
|
||||
label.AddText(" is ");
|
||||
label.PushColor(typeArgument.GetSymbolColourByType());
|
||||
label.AddText(typeArgument.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
|
||||
label.Pop();
|
||||
if (index < methodSymbol.TypeArguments.Length - 1)
|
||||
{
|
||||
label.Newline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
INamedTypeSymbol namedTypeSymbol => namedTypeSymbol.TypeKind switch
|
||||
{
|
||||
TypeKind.Class => CachedColors.ClassGreen,
|
||||
TypeKind.Interface => CachedColors.InterfaceGreen,
|
||||
TypeKind.Struct => CachedColors.ClassGreen,
|
||||
TypeKind.Enum => CachedColors.InterfaceGreen,
|
||||
TypeKind.Delegate => CachedColors.ClassGreen,
|
||||
_ => CachedColors.Orange
|
||||
},
|
||||
_ => CachedColors.Orange
|
||||
};
|
||||
return colour;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://c48r2nff1ckv
|
||||
Reference in New Issue
Block a user