From 4f1c13c86ebc779328678be09729001d50cbe596 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 27 Apr 2026 07:52:02 +0200 Subject: [PATCH] Semantic syntax highlighting via the WPF host's token writer Wires up CSharpHighlightingTokenWriter so the decompiler view paints keywords / types / members / parameters in distinct colours, identical to the WPF host. Supporting changes: Assisted-by: Claude:claude-opus-4-7:Claude Code --- .../CSharpHighlightingTokenWriter.cs | 577 ++++++++++++++++++ ILSpy/Languages/CSharpLanguage.cs | 5 + ILSpy/TextView/AvaloniaEditTextOutput.cs | 34 +- ILSpy/TextView/DecompilerTabPageModel.cs | 11 + ILSpy/TextView/DecompilerTextView.axaml.cs | 22 +- ILSpy/TextView/ISmartTextOutput.cs | 39 ++ 6 files changed, 682 insertions(+), 6 deletions(-) create mode 100644 ILSpy/Languages/CSharpHighlightingTokenWriter.cs create mode 100644 ILSpy/TextView/ISmartTextOutput.cs diff --git a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs new file mode 100644 index 000000000..fddecb452 --- /dev/null +++ b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs @@ -0,0 +1,577 @@ +// Copyright (c) 2018 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +using AvaloniaEdit.Highlighting; + +using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.CSharp.OutputVisitor; +using ICSharpCode.Decompiler.CSharp.Syntax; +using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpyX.Extensions; + +using ILSpy.TextView; + +namespace ILSpy.Languages +{ + class CSharpHighlightingTokenWriter : DecoratingTokenWriter + { + 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 fieldDeclarationColor; + HighlightingColor fieldAccessColor; + HighlightingColor propertyDeclarationColor; + HighlightingColor propertyAccessColor; + HighlightingColor eventDeclarationColor; + HighlightingColor eventAccessColor; + + HighlightingColor variableColor; + HighlightingColor parameterColor; + + HighlightingColor valueKeywordColor; + HighlightingColor thisKeywordColor; + HighlightingColor trueKeywordColor; + HighlightingColor typeKeywordsColor; + + public RichTextModel HighlightingModel { get; } = new RichTextModel(); + + public CSharpHighlightingTokenWriter(TokenWriter decoratedWriter, ISmartTextOutput? textOutput = null, ILocatable? locatable = null) + : base(decoratedWriter) + { + var highlighting = HighlightingManager.Instance.GetDefinition("C#"); + + this.locatable = locatable; + this.textOutput = textOutput; + + 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 = highlighting.GetNamedColor("MethodDeclaration"); + this.methodCallColor = highlighting.GetNamedColor("MethodCall"); + this.fieldDeclarationColor = highlighting.GetNamedColor("FieldDeclaration"); + this.fieldAccessColor = highlighting.GetNamedColor("FieldAccess"); + this.propertyDeclarationColor = highlighting.GetNamedColor("PropertyDeclaration"); + this.propertyAccessColor = highlighting.GetNamedColor("PropertyAccess"); + this.eventDeclarationColor = highlighting.GetNamedColor("EventDeclaration"); + this.eventAccessColor = highlighting.GetNamedColor("EventAccess"); + this.variableColor = highlighting.GetNamedColor("Variable"); + this.parameterColor = highlighting.GetNamedColor("Parameter"); + 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 "await": + color = structureKeywordsColor; + break; + case "where": + if (nodeStack.PeekOrDefault() is QueryClause) + color = queryKeywordsColor; + else + color = structureKeywordsColor; + break; + case "in": + if (nodeStack.PeekOrDefault() is ForeachStatement) + color = structureKeywordsColor; + else if (nodeStack.PeekOrDefault() is QueryClause) + color = queryKeywordsColor; + else + color = parameterModifierColor; + break; + case "as": + case "is": + case "new": + case "sizeof": + case "typeof": + case "nameof": + case "stackalloc": + color = typeKeywordsColor; + break; + case "with": + if (role == WithInitializerExpression.WithKeywordRole) + 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": + case "init": + if (role == PropertyDeclaration.GetKeywordRole || + role == PropertyDeclaration.SetKeywordRole || + role == PropertyDeclaration.InitKeywordRole || + role == CustomEventDeclaration.AddKeywordRole || + role == CustomEventDeclaration.RemoveKeywordRole) + color = accessorKeywordsColor; + break; + case "abstract": + case "const": + case "event": + case "extern": + case "override": + case "sealed": + case "static": + case "virtual": + case "volatile": + case "async": + case "partial": + color = modifiersColor; + break; + case "readonly": + if (role == ComposedType.ReadonlyRole) + color = parameterModifierColor; + else + 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": + case "extension": + color = referenceTypeKeywordsColor; + break; + case "record": + color = role == Roles.RecordKeyword ? referenceTypeKeywordsColor : valueTypeKeywordsColor; + break; + case "select": + case "group": + case "by": + case "into": + case "from": + case "orderby": + case "let": + case "join": + case "on": + case "equals": + if (nodeStack.PeekOrDefault() is QueryClause) + color = queryKeywordsColor; + break; + case "ascending": + case "descending": + if (nodeStack.PeekOrDefault() is QueryOrdering) + color = queryKeywordsColor; + break; + case "explicit": + case "implicit": + case "operator": + color = operatorKeywordsColor; + break; + case "params": + case "ref": + case "out": + case "scoped": + color = parameterModifierColor; + break; + case "break": + case "continue": + case "goto": + case "yield": + case "return": + color = gotoKeywordsColor; + break; + } + if (nodeStack.PeekOrDefault() is AttributeSection) + color = attributeKeywordsColor; + if (color != null) + { + BeginSpan(color); + } + base.WriteKeyword(role, keyword); + if (color != null) + { + EndSpan(); + } + } + + public override void WritePrimitiveType(string type) + { + HighlightingColor? color = null; + switch (type) + { + case "new": + case "notnull": + // Not sure if reference type or value type + color = referenceTypeKeywordsColor; + 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": + case "unmanaged": + case "nint": + case "nuint": + color = valueTypeKeywordsColor; + break; + case "class": + case "object": + case "string": + case "void": + case "dynamic": + color = referenceTypeKeywordsColor; + break; + } + if (color != null) + { + BeginSpan(color); + } + base.WritePrimitiveType(type); + if (color != null) + { + EndSpan(); + } + } + + public override void WriteIdentifier(Identifier identifier) + { + HighlightingColor? color = null; + if (identifier.Parent?.GetResolveResult() is ILVariableResolveResult rr) + { + if (rr.Variable.Kind == VariableKind.Parameter) + { + if (identifier.Name == "value" + && identifier.Ancestors.OfType().FirstOrDefault() is { } accessor + && accessor.Role != PropertyDeclaration.GetterRole) + { + color = valueKeywordColor; + } + else + { + color = parameterColor; + } + } + else + { + color = variableColor; + } + } + if (identifier.Parent is AstType) + { + switch (identifier.Name) + { + case "var": + color = queryKeywordsColor; + break; + case "global": + color = structureKeywordsColor; + break; + } + } + switch (GetCurrentDefinition()) + { + case ITypeDefinition t: + ApplyTypeColor(t, ref color); + break; + case IMethod: + color = methodDeclarationColor; + break; + case IField: + color = fieldDeclarationColor; + break; + case IProperty: + color = propertyDeclarationColor; + break; + case IEvent: + color = eventDeclarationColor; + break; + } + switch (GetCurrentMemberReference()) + { + case IType t: + ApplyTypeColor(t, ref color); + break; + case IMethod m: + color = methodCallColor; + if (m.IsConstructor) + ApplyTypeColor(m.DeclaringType, ref color); + break; + case IField: + color = fieldAccessColor; + break; + case IProperty: + color = propertyAccessColor; + break; + case IEvent: + color = eventAccessColor; + break; + } + if (color != null) + { + BeginSpan(color); + } + base.WriteIdentifier(identifier); + if (color != null) + { + EndSpan(); + } + } + + void ApplyTypeColor(IType type, ref HighlightingColor? color) + { + switch (type?.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; + } + } + + public override void WritePrimitiveValue(object value, ICSharpCode.Decompiler.CSharp.Syntax.LiteralFormat format) + { + HighlightingColor? color = null; + if (value is null) + { + color = valueKeywordColor; + } + if (value is true || value is false) + { + color = trueKeywordColor; + } + if (color != null) + { + BeginSpan(color); + } + base.WritePrimitiveValue(value, format); + if (color != null) + { + EndSpan(); + } + } + + ISymbol? GetCurrentDefinition() + { + if (nodeStack == null || nodeStack.Count == 0) + return null; + + var node = nodeStack.Peek(); + if (node is Identifier) + node = node.Parent; + if (ICSharpCode.Decompiler.TextTokenWriter.IsDefinition(ref node)) + return node.GetSymbol(); + + return null; + } + + ISymbol? GetCurrentMemberReference() + { + if (nodeStack == null || nodeStack.Count == 0) + return null; + + 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.Role == Roles.Type && node.Parent is ObjectCreateExpression) + { + var ctorSymbol = node.Parent.GetSymbol(); + if (ctorSymbol != null) + symbol = ctorSymbol; + } + 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; + } + + readonly 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(); + } + + readonly Stack colorStack = new Stack(); + HighlightingColor currentColor = new HighlightingColor(); + int currentColorBegin = -1; + readonly ILocatable? locatable; + readonly ISmartTextOutput? textOutput; + + private void BeginSpan(HighlightingColor highlightingColor) + { + if (textOutput != null) + { + textOutput.BeginSpan(highlightingColor); + return; + } + + // At least one of textOutput / locatable is supplied to the constructor; + // the early-return above handles the textOutput path, so locatable is non-null here. + Debug.Assert(locatable != null); + if (currentColorBegin > -1) + HighlightingModel.SetHighlighting(currentColorBegin, locatable.Length - currentColorBegin, currentColor); + colorStack.Push(currentColor); + currentColor = currentColor.Clone(); + currentColorBegin = locatable.Length; + currentColor.MergeWith(highlightingColor); + currentColor.Freeze(); + } + + private void EndSpan() + { + if (textOutput != null) + { + textOutput.EndSpan(); + return; + } + + // See BeginSpan: locatable is the non-null branch when textOutput is unset. + Debug.Assert(locatable != null); + HighlightingModel.SetHighlighting(currentColorBegin, locatable.Length - currentColorBegin, currentColor); + currentColor = colorStack.Pop(); + currentColorBegin = locatable.Length; + } + } +} diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index b555590ec..1bc749171 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -139,6 +139,11 @@ namespace ILSpy.Languages syntaxTree.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }); output.IndentationString = settings.CSharpFormattingOptions.IndentationString; TokenWriter tokenWriter = new TextTokenWriter(output, settings, typeSystem); + // Wrap with semantic highlighting when the output supports it. The writer emits + // BeginSpan/EndSpan around tokens, which AvaloniaEditTextOutput records into a + // RichTextModel that the view's RichTextColorizer paints. + if (output is TextView.ISmartTextOutput smartOutput) + tokenWriter = new CSharpHighlightingTokenWriter(tokenWriter, smartOutput); syntaxTree.AcceptVisitor(new CSharpOutputVisitor(tokenWriter, settings.CSharpFormattingOptions)); } } diff --git a/ILSpy/TextView/AvaloniaEditTextOutput.cs b/ILSpy/TextView/AvaloniaEditTextOutput.cs index 8b0f34881..4dae1edc3 100644 --- a/ILSpy/TextView/AvaloniaEditTextOutput.cs +++ b/ILSpy/TextView/AvaloniaEditTextOutput.cs @@ -16,9 +16,12 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Collections.Generic; using System.Reflection.Metadata; using System.Text; +using AvaloniaEdit.Highlighting; + using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Disassembler; using ICSharpCode.Decompiler.Metadata; @@ -27,17 +30,23 @@ using ICSharpCode.Decompiler.TypeSystem; namespace ILSpy.TextView { /// - /// Phase-1 for the Avalonia text view: just accumulates text into a - /// with the indentation contract the decompiler expects. Reference - /// markers, fold ranges, and inline UI elements are intentionally dropped — those will land - /// when we add hyperlinks and folding support. + /// Avalonia-side + : accumulates text + /// into a and tracks BeginSpan/EndSpan pairs into a + /// that the text view renders via RichTextColorizer. + /// Reference markers, fold ranges, and inline UI elements are intentionally dropped here — + /// those land when we add hyperlinks and folding support. /// - sealed class AvaloniaEditTextOutput : ITextOutput + sealed class AvaloniaEditTextOutput : ISmartTextOutput { readonly StringBuilder builder = new(); + readonly Stack<(int Offset, HighlightingColor Color)> openSpans = new(); int indent; bool needsIndent; + public RichTextModel HighlightingModel { get; } = new RichTextModel(); + + public string Title { get; set; } = string.Empty; + public string IndentationString { get; set; } = "\t"; public int TextLength => builder.Length; @@ -102,5 +111,20 @@ namespace ILSpy.TextView { // Folding support arrives in a later phase. } + + public void BeginSpan(HighlightingColor highlightingColor) + { + openSpans.Push((builder.Length, highlightingColor)); + } + + public void EndSpan() + { + if (openSpans.Count == 0) + return; + var (start, color) = openSpans.Pop(); + var length = builder.Length - start; + if (length > 0) + HighlightingModel.SetHighlighting(start, length, color); + } } } diff --git a/ILSpy/TextView/DecompilerTabPageModel.cs b/ILSpy/TextView/DecompilerTabPageModel.cs index 60037a059..585ecc077 100644 --- a/ILSpy/TextView/DecompilerTabPageModel.cs +++ b/ILSpy/TextView/DecompilerTabPageModel.cs @@ -22,6 +22,8 @@ using System.Threading.Tasks; using Avalonia.Threading; +using AvaloniaEdit.Highlighting; + using CommunityToolkit.Mvvm.ComponentModel; using ICSharpCode.Decompiler; @@ -50,6 +52,13 @@ namespace ILSpy.TextView [ObservableProperty] private string syntaxExtension = ".cs"; + /// + /// Semantic-highlighting spans collected during decompilation; the view feeds this into + /// AvaloniaEdit's RichTextColorizer. + /// + [ObservableProperty] + private RichTextModel? highlightingModel; + ILSpyTreeNode? currentNode; public ILSpyTreeNode? CurrentNode { @@ -112,7 +121,9 @@ namespace ILSpy.TextView return; var rendered = output.GetText(); + var model = output.HighlightingModel; await Dispatcher.UIThread.InvokeAsync(() => { + HighlightingModel = model; Text = rendered; }); } diff --git a/ILSpy/TextView/DecompilerTextView.axaml.cs b/ILSpy/TextView/DecompilerTextView.axaml.cs index 369d8c68a..c4d496634 100644 --- a/ILSpy/TextView/DecompilerTextView.axaml.cs +++ b/ILSpy/TextView/DecompilerTextView.axaml.cs @@ -20,10 +20,14 @@ using System.ComponentModel; using Avalonia.Controls; +using AvaloniaEdit.Highlighting; + namespace ILSpy.TextView { public partial class DecompilerTextView : UserControl { + RichTextColorizer? activeColorizer; + public DecompilerTextView() { InitializeComponent(); @@ -43,7 +47,8 @@ namespace ILSpy.TextView { if (sender is DecompilerTabPageModel model && (e.PropertyName == nameof(DecompilerTabPageModel.Text) - || e.PropertyName == nameof(DecompilerTabPageModel.SyntaxExtension))) + || e.PropertyName == nameof(DecompilerTabPageModel.SyntaxExtension) + || e.PropertyName == nameof(DecompilerTabPageModel.HighlightingModel))) { ApplyDocument(model); } @@ -53,6 +58,21 @@ namespace ILSpy.TextView { Editor.SyntaxHighlighting = HighlightingService.GetByExtension(model.SyntaxExtension); Editor.Document.Text = model.Text; + + // Swap the semantic-highlighting colorizer. AvaloniaEdit only exposes Add/Remove on + // LineTransformers, so we keep a reference to the previous one. + var transformers = Editor.TextArea.TextView.LineTransformers; + if (activeColorizer != null) + { + transformers.Remove(activeColorizer); + activeColorizer = null; + } + if (model.HighlightingModel is { } richModel) + { + activeColorizer = new RichTextColorizer(richModel); + transformers.Add(activeColorizer); + } + Editor.ScrollToHome(); } } diff --git a/ILSpy/TextView/ISmartTextOutput.cs b/ILSpy/TextView/ISmartTextOutput.cs new file mode 100644 index 000000000..23e6bab0e --- /dev/null +++ b/ILSpy/TextView/ISmartTextOutput.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using AvaloniaEdit.Highlighting; + +using ICSharpCode.Decompiler; + +namespace ILSpy.TextView +{ + /// + /// Adds Avalonia-specific output features to — currently just + /// span-based highlighting. Inline UI elements are deferred to a later phase. + /// + public interface ISmartTextOutput : ITextOutput + { + void BeginSpan(HighlightingColor highlightingColor); + void EndSpan(); + + /// + /// Title displayed in the document tab's header. + /// + string Title { get; set; } + } +}