From 0b135c23e49821446a5da604e61b276c790067ae Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 17 Sep 2017 21:33:24 +0200 Subject: [PATCH] Add C# semantic highlighting --- .../Output/TextTokenWriter.cs | 17 +- ILSpy/ExtensionMethods.cs | 9 +- ILSpy/ILSpy.csproj | 1 + ILSpy/ISmartTextOutput.cs | 5 +- ILSpy/Languages/CSharpLanguage.cs | 465 +++++++++++++++++- ILSpy/TextView/AvalonEditTextOutput.cs | 26 + ILSpy/TextView/CSharp-Mode.xshd | 136 +++++ ILSpy/TextView/DecompilerTextView.cs | 19 +- 8 files changed, 647 insertions(+), 31 deletions(-) create mode 100644 ILSpy/TextView/CSharp-Mode.xshd diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs index a60099bd3..7396af144 100644 --- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs +++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.Linq; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.Syntax; @@ -189,7 +188,7 @@ namespace ICSharpCode.Decompiler var node = nodeStack.Peek(); if (node is Identifier) node = node.Parent; - if (IsDefinition(node)) + if (IsDefinition(ref node)) return node.GetSymbol(); return null; @@ -403,11 +402,17 @@ namespace ICSharpCode.Decompiler // } } - private static bool IsDefinition(AstNode node) + static bool IsDefinition(ref AstNode node) { - return node is EntityDeclaration - || (node is VariableInitializer && node.Parent is FieldDeclaration) - || node is FixedVariableInitializer; + if (node is EntityDeclaration) + return true; + if (node is VariableInitializer && node.Parent is FieldDeclaration) { + node = node.Parent; + return true; + } + if (node is FixedVariableInitializer) + return true; + return false; } } } diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs index 3d41c0031..32cd8ea30 100644 --- a/ILSpy/ExtensionMethods.cs +++ b/ILSpy/ExtensionMethods.cs @@ -34,7 +34,14 @@ namespace ICSharpCode.ILSpy if (!list.Contains(item)) list.Add(item); } - + + public static T PeekOrDefault(this Stack stack) + { + if (stack.Count == 0) + return default(T); + return stack.Peek(); + } + public static int BinarySearch(this IList list, T item, int start, int count, IComparer comparer) { if (list == null) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 2225c6e09..4cc633465 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -232,6 +232,7 @@ + diff --git a/ILSpy/ISmartTextOutput.cs b/ILSpy/ISmartTextOutput.cs index fce8ad09b..7b217f398 100644 --- a/ILSpy/ISmartTextOutput.cs +++ b/ILSpy/ISmartTextOutput.cs @@ -21,7 +21,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; - +using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.Decompiler; namespace ICSharpCode.ILSpy @@ -35,6 +35,9 @@ namespace ICSharpCode.ILSpy /// Inserts an interactive UI element at the current position in the text output. /// void AddUIElement(Func element); + + void BeginSpan(HighlightingColor highlightingColor); + void EndSpan(); } public static class SmartTextOutputExtensions diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 7ac2810c1..ae1ae033d 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -35,6 +35,8 @@ using System.Windows; using System.Windows.Controls; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.Decompiler.CSharp.Transforms; +using ICSharpCode.AvalonEdit.Highlighting; +using System.Windows.Media; namespace ICSharpCode.ILSpy { @@ -96,9 +98,11 @@ namespace ICSharpCode.ILSpy void WriteCode(ITextOutput output, DecompilerSettings settings, SyntaxTree syntaxTree, IDecompilerTypeSystem typeSystem) { syntaxTree.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }); - var outputFormatter = new TextTokenWriter(output, settings, typeSystem) { FoldBraces = settings.FoldBraces }; - var formattingPolicy = settings.CSharpFormattingOptions; - syntaxTree.AcceptVisitor(new CSharpOutputVisitor(outputFormatter, formattingPolicy)); + TokenWriter tokenWriter = new TextTokenWriter(output, settings, typeSystem) { FoldBraces = settings.FoldBraces }; + if (output is ISmartTextOutput highlightingOutput) { + tokenWriter = new HighlightingTokenWriter(tokenWriter, highlightingOutput); + } + syntaxTree.AcceptVisitor(new CSharpOutputVisitor(tokenWriter, settings.CSharpFormattingOptions)); } public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) @@ -396,25 +400,6 @@ namespace ICSharpCode.ILSpy } } - /* - AstBuilder CreateAstBuilder(DecompilationOptions options, ModuleDefinition currentModule = null, TypeDefinition currentType = null, bool isSingleMember = false) - { - if (currentModule == null) - currentModule = currentType.Module; - DecompilerSettings settings = options.DecompilerSettings; - if (isSingleMember) { - settings = settings.Clone(); - settings.UsingDeclarations = false; - } - return new AstBuilder( - new DecompilerContext(currentModule) { - CancellationToken = options.CancellationToken, - CurrentType = currentType, - Settings = settings - }); - } - */ - public override string TypeToString(TypeReference type, bool includeNamespace, ICustomAttributeProvider typeAttributes = null) { ConvertTypeOptions options = ConvertTypeOptions.IncludeTypeParameterDefinitions; @@ -533,5 +518,441 @@ namespace ICSharpCode.ILSpy var flags = ConversionFlags.All & ~ConversionFlags.ShowBody; return new CSharpAmbience() { ConversionFlags = flags }.ConvertSymbol(symbol); } + + class HighlightingTokenWriter : DecoratingTokenWriter + { + ISmartTextOutput textOutput; + + HighlightingColor defaultTextColor; + + HighlightingColor visibilityKeywordsColor; + HighlightingColor namespaceKeywordsColor; + HighlightingColor structureKeywordsColor; + HighlightingColor gotoKeywordsColor; + HighlightingColor queryKeywordsColor; + HighlightingColor exceptionKeywordsColor; + HighlightingColor checkedKeywordColor; + HighlightingColor unsafeKeywordsColor; + HighlightingColor valueTypeKeywordsColor; + HighlightingColor referenceTypeKeywordsColor; + HighlightingColor operatorKeywordsColor; + HighlightingColor parameterModifierColor; + HighlightingColor modifiersColor; + HighlightingColor accessorKeywordsColor; + HighlightingColor attributeKeywordsColor; + + HighlightingColor referenceTypeColor; + HighlightingColor valueTypeColor; + HighlightingColor interfaceTypeColor; + HighlightingColor enumerationTypeColor; + HighlightingColor typeParameterTypeColor; + HighlightingColor delegateTypeColor; + + HighlightingColor methodCallColor; + HighlightingColor methodDeclarationColor; + + HighlightingColor eventDeclarationColor; + HighlightingColor eventAccessColor; + + HighlightingColor propertyDeclarationColor; + HighlightingColor propertyAccessColor; + + HighlightingColor fieldDeclarationColor; + HighlightingColor fieldAccessColor; + + HighlightingColor variableDeclarationColor; + HighlightingColor variableAccessColor; + + HighlightingColor parameterDeclarationColor; + HighlightingColor parameterAccessColor; + + HighlightingColor valueKeywordColor; + HighlightingColor thisKeywordColor; + HighlightingColor trueKeywordColor; + HighlightingColor typeKeywordsColor; + HighlightingColor externAliasKeywordColor; + HighlightingColor varKeywordTypeColor; + + HighlightingColor stringFormatItemColor; + + public HighlightingTokenWriter(TokenWriter decoratedWriter, ISmartTextOutput textOutput) : base(decoratedWriter) + { + this.textOutput = textOutput; + var highlighting = HighlightingManager.Instance.GetDefinition("C#"); + + //this.defaultTextColor = ???; + + this.visibilityKeywordsColor = highlighting.GetNamedColor("Visibility"); + this.namespaceKeywordsColor = highlighting.GetNamedColor("NamespaceKeywords"); + this.structureKeywordsColor = highlighting.GetNamedColor("Keywords"); + this.gotoKeywordsColor = highlighting.GetNamedColor("GotoKeywords"); + this.queryKeywordsColor = highlighting.GetNamedColor("QueryKeywords"); + this.exceptionKeywordsColor = highlighting.GetNamedColor("ExceptionKeywords"); + this.checkedKeywordColor = highlighting.GetNamedColor("CheckedKeyword"); + this.unsafeKeywordsColor = highlighting.GetNamedColor("UnsafeKeywords"); + this.valueTypeKeywordsColor = highlighting.GetNamedColor("ValueTypeKeywords"); + this.referenceTypeKeywordsColor = highlighting.GetNamedColor("ReferenceTypeKeywords"); + this.operatorKeywordsColor = highlighting.GetNamedColor("OperatorKeywords"); + this.parameterModifierColor = highlighting.GetNamedColor("ParameterModifiers"); + this.modifiersColor = highlighting.GetNamedColor("Modifiers"); + this.accessorKeywordsColor = highlighting.GetNamedColor("GetSetAddRemove"); + + this.referenceTypeColor = highlighting.GetNamedColor("ReferenceTypes"); + this.valueTypeColor = highlighting.GetNamedColor("ValueTypes"); + this.interfaceTypeColor = highlighting.GetNamedColor("InterfaceTypes"); + this.enumerationTypeColor = highlighting.GetNamedColor("EnumTypes"); + this.typeParameterTypeColor = highlighting.GetNamedColor("TypeParameters"); + this.delegateTypeColor = highlighting.GetNamedColor("DelegateTypes"); + this.methodDeclarationColor = this.methodCallColor = highlighting.GetNamedColor("MethodCall"); + //this.eventDeclarationColor = this.eventAccessColor = defaultTextColor; + //this.propertyDeclarationColor = this.propertyAccessColor = defaultTextColor; + this.fieldDeclarationColor = this.fieldAccessColor = highlighting.GetNamedColor("FieldAccess"); + //this.variableDeclarationColor = this.variableAccessColor = defaultTextColor; + //this.parameterDeclarationColor = this.parameterAccessColor = defaultTextColor; + this.valueKeywordColor = highlighting.GetNamedColor("NullOrValueKeywords"); + this.thisKeywordColor = highlighting.GetNamedColor("ThisOrBaseReference"); + this.trueKeywordColor = highlighting.GetNamedColor("TrueFalse"); + this.typeKeywordsColor = highlighting.GetNamedColor("TypeKeywords"); + this.attributeKeywordsColor = highlighting.GetNamedColor("AttributeKeywords"); + //this.externAliasKeywordColor = ...; + } + + public override void WriteKeyword(Role role, string keyword) + { + HighlightingColor color = null; + switch (keyword) { + case "namespace": + case "using": + if (role == UsingStatement.UsingKeywordRole) + color = structureKeywordsColor; + else + color = namespaceKeywordsColor; + break; + case "this": + case "base": + color = thisKeywordColor; + break; + case "true": + case "false": + color = trueKeywordColor; + break; + case "public": + case "internal": + case "protected": + case "private": + color = visibilityKeywordsColor; + break; + case "if": + case "else": + case "switch": + case "case": + case "default": + case "while": + case "do": + case "for": + case "foreach": + case "lock": + case "global": + case "dynamic": + case "await": + case "where": + color = structureKeywordsColor; + break; + case "in": + if (nodeStack.PeekOrDefault() is ForeachStatement) + color = structureKeywordsColor; + else if (nodeStack.PeekOrDefault() is QueryExpression) + color = queryKeywordsColor; + else + color = parameterModifierColor; + break; + case "as": + case "is": + case "new": + case "sizeof": + case "typeof": + case "nameof": + case "stackalloc": + color = typeKeywordsColor; + break; + case "try": + case "throw": + case "catch": + case "finally": + color = exceptionKeywordsColor; + break; + case "when": + if (role == CatchClause.WhenKeywordRole) + color = exceptionKeywordsColor; + break; + case "get": + case "set": + case "add": + case "remove": + if (role == PropertyDeclaration.GetKeywordRole || + role == PropertyDeclaration.SetKeywordRole || + role == CustomEventDeclaration.AddKeywordRole || + role == CustomEventDeclaration.RemoveKeywordRole) + color = accessorKeywordsColor; + break; + case "abstract": + case "const": + case "event": + case "extern": + case "override": + case "readonly": + case "sealed": + case "static": + case "virtual": + case "volatile": + case "async": + case "partial": + color = modifiersColor; + break; + case "checked": + case "unchecked": + color = checkedKeywordColor; + break; + case "fixed": + case "unsafe": + color = unsafeKeywordsColor; + break; + case "enum": + case "struct": + color = valueTypeKeywordsColor; + break; + case "class": + case "interface": + case "delegate": + color = referenceTypeKeywordsColor; + break; + case "select": + case "group": + case "by": + case "into": + case "from": + case "ascending": + case "descending": + case "orderby": + case "let": + case "join": + case "on": + case "equals": + if (nodeStack.PeekOrDefault() is QueryExpression) + color = queryKeywordsColor; + break; + case "explicit": + case "implicit": + case "operator": + color = operatorKeywordsColor; + break; + case "params": + case "ref": + case "out": + color = parameterModifierColor; + break; + case "break": + case "continue": + case "goto": + case "yield": + case "return": + color = gotoKeywordsColor; + break; + } + if (Roles.AttributeTargetRole == role) + color = attributeKeywordsColor; + if (color != null) { + textOutput.BeginSpan(color); + } + base.WriteKeyword(role, keyword); + if (color != null) { + textOutput.EndSpan(); + } + } + + public override void WritePrimitiveType(string type) + { + HighlightingColor color = null; + switch (type) { + case "new": + color = typeKeywordsColor; + break; + case "bool": + case "byte": + case "char": + case "decimal": + case "double": + case "enum": + case "float": + case "int": + case "long": + case "sbyte": + case "short": + case "struct": + case "uint": + case "ushort": + case "ulong": + color = valueTypeKeywordsColor; + break; + case "object": + case "string": + case "void": + color = referenceTypeKeywordsColor; + break; + } + if (color != null) { + textOutput.BeginSpan(color); + } + base.WritePrimitiveType(type); + if (color != null) { + textOutput.EndSpan(); + } + } + + public override void WriteIdentifier(Identifier identifier) + { + HighlightingColor color = null; + if (identifier.Name == "value" && nodeStack.PeekOrDefault() is Accessor accessor && accessor.Role != PropertyDeclaration.GetterRole) + color = valueKeywordColor; + switch (GetCurrentDefinition()) { + case ITypeDefinition t: + switch (t.Kind) { + case TypeKind.Delegate: + color = delegateTypeColor; + break; + case TypeKind.Class: + color = referenceTypeColor; + break; + case TypeKind.Interface: + color = interfaceTypeColor; + break; + case TypeKind.Enum: + color = enumerationTypeColor; + break; + case TypeKind.Struct: + color = valueTypeColor; + break; + } + break; + case IMethod m: + color = methodDeclarationColor; + break; + case IField f: + color = fieldDeclarationColor; + break; + } + switch (GetCurrentMemberReference()) { + case IType t: + switch (t.Kind) { + case TypeKind.Delegate: + color = delegateTypeColor; + break; + case TypeKind.Class: + color = referenceTypeColor; + break; + case TypeKind.Interface: + color = interfaceTypeColor; + break; + case TypeKind.Enum: + color = enumerationTypeColor; + break; + case TypeKind.Struct: + color = valueTypeColor; + break; + } + break; + case IMethod m: + color = methodCallColor; + break; + case IField f: + color = fieldAccessColor; + break; + } + if (color != null) { + textOutput.BeginSpan(color); + } + base.WriteIdentifier(identifier); + if (color != null) { + textOutput.EndSpan(); + } + } + + public override void WritePrimitiveValue(object value, string literalValue = null) + { + HighlightingColor color = null; + if (value is null) { + color = valueKeywordColor; + } + if (value is true || value is false) { + color = trueKeywordColor; + } + if (color != null) { + textOutput.BeginSpan(color); + } + base.WritePrimitiveValue(value, literalValue); + if (color != null) { + textOutput.EndSpan(); + } + } + + ISymbol GetCurrentDefinition() + { + if (nodeStack == null || nodeStack.Count == 0) + return null; + + var node = nodeStack.Peek(); + if (node is Identifier) + node = node.Parent; + if (IsDefinition(ref node)) + return node.GetSymbol(); + + return null; + } + + static bool IsDefinition(ref AstNode node) + { + if (node is EntityDeclaration) + return true; + if (node is VariableInitializer && node.Parent is FieldDeclaration) { + node = node.Parent; + return true; + } + if (node is FixedVariableInitializer) + return true; + return false; + } + + ISymbol GetCurrentMemberReference() + { + AstNode node = nodeStack.Peek(); + var symbol = node.GetSymbol(); + if (symbol == null && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression) { + symbol = node.Parent.GetSymbol(); + } + if (symbol != null && node.Parent is ObjectCreateExpression) { + symbol = node.Parent.GetSymbol(); + } + if (node is IdentifierExpression && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression && symbol is IMember member) { + var declaringType = member.DeclaringType; + if (declaringType != null && declaringType.Kind == TypeKind.Delegate) + return null; + } + return symbol; + } + + Stack nodeStack = new Stack(); + + public override void StartNode(AstNode node) + { + nodeStack.Push(node); + base.StartNode(node); + } + + public override void EndNode(AstNode node) + { + base.EndNode(node); + nodeStack.Pop(); + } + } } } diff --git a/ILSpy/TextView/AvalonEditTextOutput.cs b/ILSpy/TextView/AvalonEditTextOutput.cs index 74c330e27..c7f64093e 100644 --- a/ILSpy/TextView/AvalonEditTextOutput.cs +++ b/ILSpy/TextView/AvalonEditTextOutput.cs @@ -25,6 +25,7 @@ using System.Windows; using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Folding; +using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Rendering; using TextLocation = ICSharpCode.Decompiler.CSharp.Syntax.TextLocation; @@ -91,6 +92,8 @@ namespace ICSharpCode.ILSpy.TextView /// Embedded UIElements, see . internal readonly List>> UIElements = new List>>(); + + public RichTextModel HighlightingModel { get; } = new RichTextModel(); public AvalonEditTextOutput() { @@ -247,5 +250,28 @@ namespace ICSharpCode.ILSpy.TextView this.UIElements.Add(new KeyValuePair>(this.TextLength, new Lazy(element))); } } + + readonly Stack colorStack = new Stack(); + HighlightingColor currentColor = new HighlightingColor(); + int currentColorBegin = -1; + + public void BeginSpan(HighlightingColor highlightingColor) + { + WriteIndent(); + if (currentColorBegin > -1) + HighlightingModel.SetHighlighting(currentColorBegin, b.Length - currentColorBegin, currentColor); + colorStack.Push(currentColor); + currentColor = currentColor.Clone(); + currentColorBegin = b.Length; + currentColor.MergeWith(highlightingColor); + currentColor.Freeze(); + } + + public void EndSpan() + { + HighlightingModel.SetHighlighting(currentColorBegin, b.Length - currentColorBegin, currentColor); + currentColor = colorStack.Pop(); + currentColorBegin = b.Length; + } } } diff --git a/ILSpy/TextView/CSharp-Mode.xshd b/ILSpy/TextView/CSharp-Mode.xshd new file mode 100644 index 000000000..0d713cde7 --- /dev/null +++ b/ILSpy/TextView/CSharp-Mode.xshd @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TODO + FIXME + + + HACK + UNDONE + + + + + + + \# + + + + (define|undef|if|elif|else|endif|line)\b + + + + // + + + + + + (region|endregion|error|warning|pragma)\b + + + + + + + ///(?!/) + + + + + + + + // + + + + /\* + \*/ + + + + " + " + + + + + + + + ' + ' + + + + + + + + @" + " + + + + + + + + + \b0[xX][0-9a-fA-F]+ # hex number + | + ( \b\d+(\.[0-9]+)? #number with optional floating point + | \.[0-9]+ #or just starting with floating point + ) + ([eE][+-]?[0-9]+)? # optional exponent + + + diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index d719b2d92..ad52a7046 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -63,6 +63,7 @@ namespace ICSharpCode.ILSpy.TextView readonly ReferenceElementGenerator referenceElementGenerator; readonly UIElementGenerator uiElementGenerator; List activeCustomElementGenerators = new List(); + RichTextColorizer activeRichTextColorizer; FoldingManager foldingManager; ILSpyTreeNode[] decompiledNodes; @@ -85,7 +86,17 @@ namespace ICSharpCode.ILSpy.TextView } } }); - + + HighlightingManager.Instance.RegisterHighlighting( + "C#", new string[] { ".cs" }, + delegate { + using (Stream s = typeof(DecompilerTextView).Assembly.GetManifestResourceStream(typeof(DecompilerTextView), "CSharp-Mode.xshd")) { + using (XmlTextReader reader = new XmlTextReader(s)) { + return HighlightingLoader.Load(reader, HighlightingManager.Instance); + } + } + }); + InitializeComponent(); this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference, this.IsLink); @@ -361,6 +372,12 @@ namespace ICSharpCode.ILSpy.TextView references = textOutput.References; definitionLookup = textOutput.DefinitionLookup; textEditor.SyntaxHighlighting = highlighting; + if (activeRichTextColorizer != null) + textEditor.TextArea.TextView.LineTransformers.Remove(activeRichTextColorizer); + if (textOutput.HighlightingModel != null) { + activeRichTextColorizer = new RichTextColorizer(textOutput.HighlightingModel); + textEditor.TextArea.TextView.LineTransformers.Insert(highlighting == null ? 0 : 1, activeRichTextColorizer); + } // Change the set of active element generators: foreach (var elementGenerator in activeCustomElementGenerators) {