From 0a0ca73d79d175ca902fd46ce470b933e52f5614 Mon Sep 17 00:00:00 2001
From: wuyangfan <1102042793@qq.com>
Date: Sun, 17 May 2026 22:29:07 +0800
Subject: [PATCH 1/2] feat(analyzer): syntax-highlight entity signatures in
Analyze panel
Render analyzer result entries with C# syntax coloring so type and member
names are easier to scan in long signature lists.
Also fix "assmbly" -> "assembly" in ilspycmd help text.
Fixes #2164
---
ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs | 4 ++--
ICSharpCode.ILSpyCmd/README.md | 4 ++--
ILSpy/Analyzers/AnalyzerEntityTreeNode.cs | 20 +++++++++++++++++++
.../TreeNodes/AnalyzedEventTreeNode.cs | 2 +-
.../TreeNodes/AnalyzedFieldTreeNode.cs | 2 +-
.../TreeNodes/AnalyzedMethodTreeNode.cs | 2 +-
.../TreeNodes/AnalyzedPropertyTreeNode.cs | 2 +-
.../TreeNodes/AnalyzedTypeTreeNode.cs | 2 +-
8 files changed, 29 insertions(+), 9 deletions(-)
diff --git a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs
index 36587d730d..4dcc350b3e 100644
--- a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs
+++ b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs
@@ -171,8 +171,8 @@ class ILSpyCmdProgram
public bool ReportExcludedTypes { get; set; }
[Option(generateDiagrammerCmd + "-docs", "The path or file:// URI of the XML file containing the target assembly's documentation comments." +
- " You only need to set this if a) you want your diagrams annotated with them and b) the file name differs from that of the assmbly." +
- " To enable XML documentation output for your assmbly, see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/#create-xml-documentation-output",
+ " You only need to set this if a) you want your diagrams annotated with them and b) the file name differs from that of the assembly." +
+ " To enable XML documentation output for your assembly, see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/#create-xml-documentation-output",
CommandOptionType.SingleValue)]
public string XmlDocs { get; set; }
diff --git a/ICSharpCode.ILSpyCmd/README.md b/ICSharpCode.ILSpyCmd/README.md
index e523fa1220..f4924eb3f3 100644
--- a/ICSharpCode.ILSpyCmd/README.md
+++ b/ICSharpCode.ILSpyCmd/README.md
@@ -70,8 +70,8 @@ Options:
your regular expressions.
--generate-diagrammer-docs The path or file:// URI of the XML file containing the target assembly's
documentation comments. You only need to set this if a) you want your diagrams
- annotated with them and b) the file name differs from that of the assmbly. To
- enable XML documentation output for your assmbly, see
+ annotated with them and b) the file name differs from that of the assembly. To
+ enable XML documentation output for your assembly, see
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/#create-xml-documentation-output
--generate-diagrammer-strip-namespaces Optional space-separated namespace names that are removed for brevity from XML
documentation comments. Note that the order matters: e.g. replace
diff --git a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
index c86dc1e808..25b0493746 100644
--- a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
+++ b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
@@ -19,7 +19,11 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
+using System.Windows.Controls;
+using ICSharpCode.AvalonEdit.Document;
+using ICSharpCode.AvalonEdit.Highlighting;
+using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpyX;
@@ -57,6 +61,22 @@ public override void ActivateItem(IPlatformRoutedEventArgs e)
public override object? ToolTip => Member?.ParentModule?.MetadataFile?.FileName;
+ ///
+ /// Renders a member signature with C# syntax highlighting for the Analyze tree view.
+ ///
+ protected object CreateHighlightedSignatureText(string signature)
+ {
+ IHighlightingDefinition? highlighting = Language.SyntaxHighlighting;
+ if (highlighting == null)
+ return signature;
+
+ var document = new TextDocument(signature);
+ var richText = DocumentPrinter.ConvertTextDocumentToRichText(document, new DocumentHighlighter(document, highlighting)).ToRichTextModel();
+ var textBlock = new TextBlock();
+ textBlock.Inlines.AddRange(richText.CreateRuns(document));
+ return textBlock;
+ }
+
public override bool HandleAssemblyListChanged(ICollection removedAssemblies, ICollection addedAssemblies)
{
if (Member == null)
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedEventTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedEventTreeNode.cs
index 9df55a0614..372cfbdd66 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedEventTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedEventTreeNode.cs
@@ -46,7 +46,7 @@ public AnalyzedEventTreeNode(IEvent analyzedEvent, IEntity? source, string prefi
public override object Icon => EventTreeNode.GetIcon(analyzedEvent);
// TODO: This way of formatting is not suitable for events which explicitly implement interfaces.
- public override object Text => prefix + Language.EntityToString(analyzedEvent, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames);
+ public override object Text => CreateHighlightedSignatureText(prefix + Language.EntityToString(analyzedEvent, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames));
protected override void LoadChildren()
{
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedFieldTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedFieldTreeNode.cs
index 64a508c57e..ef48f4cd76 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedFieldTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedFieldTreeNode.cs
@@ -40,7 +40,7 @@ public AnalyzedFieldTreeNode(IField analyzedField, IEntity? source)
public override object Icon => FieldTreeNode.GetIcon(analyzedField);
- public override object Text => Language.EntityToString(analyzedField, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames);
+ public override object Text => CreateHighlightedSignatureText(Language.EntityToString(analyzedField, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames));
protected override void LoadChildren()
{
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedMethodTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedMethodTreeNode.cs
index 2ce76fe51b..3f320f84fa 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedMethodTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedMethodTreeNode.cs
@@ -42,7 +42,7 @@ public AnalyzedMethodTreeNode(IMethod analyzedMethod, IEntity? source, string pr
public override object Icon => MethodTreeNode.GetIcon(analyzedMethod);
- public override object Text => prefix + Language.EntityToString(analyzedMethod, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames);
+ public override object Text => CreateHighlightedSignatureText(prefix + Language.EntityToString(analyzedMethod, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames));
protected override void LoadChildren()
{
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedPropertyTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedPropertyTreeNode.cs
index d6cb1df5e6..767e3ecf18 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedPropertyTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedPropertyTreeNode.cs
@@ -43,7 +43,7 @@ public AnalyzedPropertyTreeNode(IProperty analyzedProperty, IEntity? source, str
public override object Icon => PropertyTreeNode.GetIcon(analyzedProperty);
// TODO: This way of formatting is not suitable for properties which explicitly implement interfaces.
- public override object Text => prefix + Language.EntityToString(analyzedProperty, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames);
+ public override object Text => CreateHighlightedSignatureText(prefix + Language.EntityToString(analyzedProperty, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames));
protected override void LoadChildren()
{
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedTypeTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedTypeTreeNode.cs
index 67f37d6f98..3079172c6d 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedTypeTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedTypeTreeNode.cs
@@ -39,7 +39,7 @@ public AnalyzedTypeTreeNode(ITypeDefinition analyzedType, IEntity? source)
public override object Icon => TypeTreeNode.GetIcon(analyzedType);
- public override object Text => Language.TypeToString(analyzedType);
+ public override object Text => CreateHighlightedSignatureText(Language.TypeToString(analyzedType));
protected override void LoadChildren()
{
From 6f4c6c3bf8aaf7fef59df68e4d8f9ee1066862e1 Mon Sep 17 00:00:00 2001
From: wuyangfan <1102042793@qq.com>
Date: Sun, 17 May 2026 23:13:46 +0800
Subject: [PATCH 2/2] feat(analyzer): bold type names in Analyze panel
signatures
Use semantic C# highlighting for analyzer entity nodes and apply bold
font weight to type name spans so signatures are easier to scan.
Complements syntax highlighting for #2164.
---
ILSpy/Analyzers/AnalyzerEntityTreeNode.cs | 107 +++++++++++++++++-
.../TreeNodes/AnalyzedEventTreeNode.cs | 2 +-
.../TreeNodes/AnalyzedFieldTreeNode.cs | 2 +-
.../TreeNodes/AnalyzedMethodTreeNode.cs | 2 +-
.../TreeNodes/AnalyzedPropertyTreeNode.cs | 2 +-
.../TreeNodes/AnalyzedTypeTreeNode.cs | 2 +-
6 files changed, 110 insertions(+), 7 deletions(-)
diff --git a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
index 25b0493746..78fdfd93f8 100644
--- a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
+++ b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs
@@ -16,16 +16,26 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
+using System.Linq;
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Documents;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils;
+using ICSharpCode.Decompiler.CSharp;
+using ICSharpCode.Decompiler.CSharp.OutputVisitor;
+using ICSharpCode.Decompiler.CSharp.Syntax;
+using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.TreeNodes;
+using ICSharpCode.ILSpy.Util;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.TreeView;
using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
@@ -39,6 +49,14 @@ namespace ICSharpCode.ILSpy.Analyzers
///
public abstract class AnalyzerEntityTreeNode : AnalyzerTreeNode, IMemberTreeNode
{
+ static readonly string[] TypeHighlightingColorNames = {
+ "ReferenceTypes",
+ "ValueTypes",
+ "InterfaceTypes",
+ "EnumTypes",
+ "DelegateTypes",
+ };
+
public abstract IEntity? Member { get; }
public IEntity? SourceMember { get; protected set; }
@@ -62,9 +80,56 @@ public override void ActivateItem(IPlatformRoutedEventArgs e)
public override object? ToolTip => Member?.ParentModule?.MetadataFile?.FileName;
///
- /// Renders a member signature with C# syntax highlighting for the Analyze tree view.
+ /// Renders a member signature with semantic C# highlighting and bold type names for the Analyze tree view.
///
- protected object CreateHighlightedSignatureText(string signature)
+ protected object CreateHighlightedMemberText(string prefix, ConversionFlags conversionFlags)
+ {
+ var member = Member;
+ if (member == null)
+ return prefix;
+
+ var fallbackText = prefix + Language.EntityToString(member, conversionFlags);
+ IHighlightingDefinition? highlighting = Language.SyntaxHighlighting;
+ if (highlighting == null)
+ return fallbackText;
+
+ var richText = TryCreateSemanticRichText(member, conversionFlags);
+ if (richText == null)
+ return CreateLexicalHighlightedText(fallbackText);
+
+ var signature = richText.Text;
+ var document = new TextDocument(signature);
+ var textBlock = new TextBlock();
+ if (prefix.Length > 0)
+ textBlock.Inlines.Add(new Run(prefix));
+ textBlock.Inlines.AddRange(richText.CreateRuns(document));
+ ApplyBoldToTypeNames(textBlock, richText, document, highlighting, prefix.Length);
+ return textBlock;
+ }
+
+ static RichText? TryCreateSemanticRichText(IEntity member, ConversionFlags conversionFlags)
+ {
+ if (Language is not CSharpLanguage)
+ return null;
+
+ var output = new StringWriter();
+ var decoratedWriter = new TextWriterTokenWriter(output);
+ var writer = new CSharpHighlightingTokenWriter(TokenWriter.InsertRequiredSpaces(decoratedWriter), locatable: decoratedWriter);
+ var settings = SettingsService.DecompilerSettings.Clone();
+ if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
+ languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
+ settings.SetLanguageVersion(languageVersion);
+ if (member is IMethod { IsLocalFunction: true })
+ {
+ writer.WriteIdentifier(Identifier.Create("(local)"));
+ }
+ new CSharpAmbience() {
+ ConversionFlags = conversionFlags,
+ }.ConvertSymbol(member, writer, settings.CSharpFormattingOptions);
+ return new RichText(output.ToString(), writer.HighlightingModel);
+ }
+
+ static object CreateLexicalHighlightedText(string signature)
{
IHighlightingDefinition? highlighting = Language.SyntaxHighlighting;
if (highlighting == null)
@@ -77,6 +142,44 @@ protected object CreateHighlightedSignatureText(string signature)
return textBlock;
}
+ static void ApplyBoldToTypeNames(TextBlock textBlock, RichText richText, TextDocument document, IHighlightingDefinition highlighting, int textOffset)
+ {
+ var typeColors = GetTypeHighlightingColors(highlighting);
+ var model = richText.ToRichTextModel();
+ if (model == null || typeColors.Count == 0)
+ return;
+
+ var boldRanges = model.GetHighlightedSections(0, document.TextLength)
+ .Where(section => section.Color != null && typeColors.Contains(section.Color))
+ .Select(section => (Start: textOffset + section.Offset, End: textOffset + section.Offset + section.Length))
+ .ToList();
+ if (boldRanges.Count == 0)
+ return;
+
+ int charOffset = 0;
+ foreach (var run in textBlock.Inlines.OfType())
+ {
+ int runLength = run.Text?.Length ?? 0;
+ int runStart = charOffset;
+ int runEnd = charOffset + runLength;
+ if (boldRanges.Any(range => runEnd > range.Start && runStart < range.End))
+ run.FontWeight = FontWeights.Bold;
+ charOffset += runLength;
+ }
+ }
+
+ static HashSet GetTypeHighlightingColors(IHighlightingDefinition highlighting)
+ {
+ var typeColors = new HashSet();
+ foreach (string colorName in TypeHighlightingColorNames)
+ {
+ var color = highlighting.GetNamedColor(colorName);
+ if (color != null)
+ typeColors.Add(color);
+ }
+ return typeColors;
+ }
+
public override bool HandleAssemblyListChanged(ICollection removedAssemblies, ICollection addedAssemblies)
{
if (Member == null)
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedEventTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedEventTreeNode.cs
index 372cfbdd66..9c74765649 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedEventTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedEventTreeNode.cs
@@ -46,7 +46,7 @@ public AnalyzedEventTreeNode(IEvent analyzedEvent, IEntity? source, string prefi
public override object Icon => EventTreeNode.GetIcon(analyzedEvent);
// TODO: This way of formatting is not suitable for events which explicitly implement interfaces.
- public override object Text => CreateHighlightedSignatureText(prefix + Language.EntityToString(analyzedEvent, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames));
+ public override object Text => CreateHighlightedMemberText(prefix, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames);
protected override void LoadChildren()
{
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedFieldTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedFieldTreeNode.cs
index ef48f4cd76..b6e17a5768 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedFieldTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedFieldTreeNode.cs
@@ -40,7 +40,7 @@ public AnalyzedFieldTreeNode(IField analyzedField, IEntity? source)
public override object Icon => FieldTreeNode.GetIcon(analyzedField);
- public override object Text => CreateHighlightedSignatureText(Language.EntityToString(analyzedField, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames));
+ public override object Text => CreateHighlightedMemberText("", ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames);
protected override void LoadChildren()
{
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedMethodTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedMethodTreeNode.cs
index 3f320f84fa..ba3ac424ee 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedMethodTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedMethodTreeNode.cs
@@ -42,7 +42,7 @@ public AnalyzedMethodTreeNode(IMethod analyzedMethod, IEntity? source, string pr
public override object Icon => MethodTreeNode.GetIcon(analyzedMethod);
- public override object Text => CreateHighlightedSignatureText(prefix + Language.EntityToString(analyzedMethod, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames));
+ public override object Text => CreateHighlightedMemberText(prefix, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames);
protected override void LoadChildren()
{
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedPropertyTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedPropertyTreeNode.cs
index 767e3ecf18..ab3fa30436 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedPropertyTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedPropertyTreeNode.cs
@@ -43,7 +43,7 @@ public AnalyzedPropertyTreeNode(IProperty analyzedProperty, IEntity? source, str
public override object Icon => PropertyTreeNode.GetIcon(analyzedProperty);
// TODO: This way of formatting is not suitable for properties which explicitly implement interfaces.
- public override object Text => CreateHighlightedSignatureText(prefix + Language.EntityToString(analyzedProperty, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames));
+ public override object Text => CreateHighlightedMemberText(prefix, ConversionFlags.ShowDeclaringType | ConversionFlags.UseFullyQualifiedEntityNames);
protected override void LoadChildren()
{
diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedTypeTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedTypeTreeNode.cs
index 3079172c6d..c77ba31dc4 100644
--- a/ILSpy/Analyzers/TreeNodes/AnalyzedTypeTreeNode.cs
+++ b/ILSpy/Analyzers/TreeNodes/AnalyzedTypeTreeNode.cs
@@ -39,7 +39,7 @@ public AnalyzedTypeTreeNode(ITypeDefinition analyzedType, IEntity? source)
public override object Icon => TypeTreeNode.GetIcon(analyzedType);
- public override object Text => CreateHighlightedSignatureText(Language.TypeToString(analyzedType));
+ public override object Text => CreateHighlightedMemberText("", ConversionFlags.UseFullyQualifiedTypeNames | ConversionFlags.UseFullyQualifiedEntityNames);
protected override void LoadChildren()
{