diff --git a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs index 7e1bf5fdf..49786563a 100644 --- a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs @@ -29,6 +29,7 @@ using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Controls.TreeView; +using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.Util; namespace ICSharpCode.ILSpy.Analyzers @@ -60,18 +61,21 @@ namespace ICSharpCode.ILSpy.Analyzers | ConversionFlags.ShowTypeParameterList; RichText? richText; - bool richTextComputed; + // The build key: the signature's colours depend on the active theme and its formatting on the + // active language, so the cache is rebuilt when either changes (not just once). + (string? theme, object language)? richTextKey; /// - /// The highlighted, type-name-emboldened signature for this row, computed once and cached. - /// Falls back to (plain Text) for non-entity rows. + /// The highlighted, type-name-emboldened signature for this row, cached until the theme or + /// language changes. Falls back to (plain Text) for non-entity rows. /// public RichText? CreateRichText() { - if (!richTextComputed) + var key = (ThemeManager.Current.Theme, (object)Language); + if (richTextKey != key) { richText = BuildRichText(); - richTextComputed = true; + richTextKey = key; } return richText; } diff --git a/ILSpy/Controls/TreeView/RichNodeText.cs b/ILSpy/Controls/TreeView/RichNodeText.cs index 3cb2d3bcc..83bfd75d7 100644 --- a/ILSpy/Controls/TreeView/RichNodeText.cs +++ b/ILSpy/Controls/TreeView/RichNodeText.cs @@ -16,13 +16,16 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.ComponentModel; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Documents; +using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpyX.TreeView; namespace ICSharpCode.ILSpy.Controls.TreeView @@ -39,9 +42,25 @@ namespace ICSharpCode.ILSpy.Controls.TreeView public static readonly AttachedProperty NodeProperty = AvaloniaProperty.RegisterAttached("Node", typeof(RichNodeText)); - // Holds the live PropertyChanged subscription so a recycled TextBlock detaches cleanly. - static readonly AttachedProperty HandlerProperty = - AvaloniaProperty.RegisterAttached("Handler", typeof(RichNodeText)); + // Live PropertyChanged subscription on the bound node, so a recycled TextBlock detaches cleanly. + static readonly AttachedProperty TextHandlerProperty = + AvaloniaProperty.RegisterAttached("TextHandler", typeof(RichNodeText)); + + // Live ThemeChanged / language-settings subscriptions (rich nodes only): the signature colours + // and per-language formatting are baked into the node's cached RichText, so the inlines must be + // rebuilt when the theme or the active language changes. + static readonly AttachedProperty ThemeHandlerProperty = + AvaloniaProperty.RegisterAttached("ThemeHandler", typeof(RichNodeText)); + + static readonly AttachedProperty LanguageHandlerProperty = + AvaloniaProperty.RegisterAttached("LanguageHandler", typeof(RichNodeText)); + + static readonly AttachedProperty CleanupHookedProperty = + AvaloniaProperty.RegisterAttached("CleanupHooked", typeof(RichNodeText)); + + static LanguageSettings? languageSettings; + static LanguageSettings? GetLanguageSettings() + => languageSettings ??= AppComposition.TryGetExport()?.SessionSettings.LanguageSettings; static RichNodeText() { @@ -53,31 +72,65 @@ namespace ICSharpCode.ILSpy.Controls.TreeView static void OnNodeChanged(TextBlock textBlock, AvaloniaPropertyChangedEventArgs e) { - if (e.OldValue is INotifyPropertyChanged oldNode - && textBlock.GetValue(HandlerProperty) is { } oldHandler) - { - oldNode.PropertyChanged -= oldHandler; - textBlock.SetValue(HandlerProperty, null); - } + Detach(textBlock, e.OldValue as SharpTreeNode); var node = e.NewValue as SharpTreeNode; - if (node is INotifyPropertyChanged newNode) + if (node is INotifyPropertyChanged notify) { // Mirror the {Binding Text} the cell used to have: refresh when Text changes // (e.g. a lazily-loaded node swapping its placeholder for the real label). - void Handler(object? sender, PropertyChangedEventArgs args) + void TextHandler(object? sender, PropertyChangedEventArgs args) { if (args.PropertyName is nameof(SharpTreeNode.Text) or null or "") Render(textBlock, node); } - newNode.PropertyChanged += Handler; - textBlock.SetValue(HandlerProperty, Handler); + notify.PropertyChanged += TextHandler; + textBlock.SetValue(TextHandlerProperty, (PropertyChangedEventHandler)TextHandler); + } + + if (node is IRichTextNode) + { + // Rebuild on theme change (palette) and on language change (signature syntax/colours). + void ThemeHandler(object? sender, EventArgs args) => Render(textBlock, GetNode(textBlock)); + + ThemeManager.Current.ThemeChanged += ThemeHandler; + textBlock.SetValue(ThemeHandlerProperty, (EventHandler)ThemeHandler); + + if (GetLanguageSettings() is { } settings) + { + void LanguageHandler(object? sender, PropertyChangedEventArgs args) => Render(textBlock, GetNode(textBlock)); + + settings.PropertyChanged += LanguageHandler; + textBlock.SetValue(LanguageHandlerProperty, (PropertyChangedEventHandler)LanguageHandler); + } + } + + if (!textBlock.GetValue(CleanupHookedProperty)) + { + textBlock.SetValue(CleanupHookedProperty, true); + textBlock.DetachedFromVisualTree += (sender, args) => Detach(textBlock, GetNode(textBlock)); } Render(textBlock, node); } + // Drops the live subscriptions for ; safe to call repeatedly. + static void Detach(TextBlock textBlock, SharpTreeNode? node) + { + if (node is INotifyPropertyChanged notify && textBlock.GetValue(TextHandlerProperty) is { } textHandler) + notify.PropertyChanged -= textHandler; + textBlock.SetValue(TextHandlerProperty, null); + + if (textBlock.GetValue(ThemeHandlerProperty) is { } themeHandler) + ThemeManager.Current.ThemeChanged -= themeHandler; + textBlock.SetValue(ThemeHandlerProperty, null); + + if (textBlock.GetValue(LanguageHandlerProperty) is { } languageHandler && GetLanguageSettings() is { } settings) + settings.PropertyChanged -= languageHandler; + textBlock.SetValue(LanguageHandlerProperty, null); + } + static void Render(TextBlock textBlock, SharpTreeNode? node) { if (node is IRichTextNode richNode && richNode.CreateRichText() is { } rich)