// Copyright (c) 2011 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.
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using System.Xml;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Search;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.AvalonEdit;
using ICSharpCode.ILSpy.Options;
using ICSharpCode.ILSpy.Themes;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpyX;
using Microsoft.Win32;
using TomsToolbox.Composition;
using TomsToolbox.Wpf;
using ResourceKeys = ICSharpCode.ILSpy.Themes.ResourceKeys;
namespace ICSharpCode.ILSpy.TextView
{
///
/// Manages the TextEditor showing the decompiled code.
/// Contains all the threading logic that makes the decompiler work in the background.
///
public sealed partial class DecompilerTextView : UserControl, IHaveState, IProgress
{
readonly IExportProvider exportProvider;
readonly SettingsService settingsService;
readonly LanguageService languageService;
readonly MainWindow mainWindow;
readonly ReferenceElementGenerator referenceElementGenerator;
readonly UIElementGenerator uiElementGenerator;
readonly List activeCustomElementGenerators = new List();
readonly BracketHighlightRenderer bracketHighlightRenderer;
RichTextColorizer? activeRichTextColorizer;
RichTextModel? activeRichTextModel;
FoldingManager? foldingManager;
ILSpyTreeNode[]? decompiledNodes;
Uri? currentAddress;
string? currentTitle;
bool expandMemberDefinitions;
DefinitionLookup? definitionLookup;
TextSegmentCollection? references;
CancellationTokenSource? currentCancellationTokenSource;
readonly TextMarkerService textMarkerService;
readonly List localReferenceMarks = new List();
#region Constructor
public DecompilerTextView(IExportProvider exportProvider)
{
this.exportProvider = exportProvider;
settingsService = exportProvider.GetExportedValue();
languageService = exportProvider.GetExportedValue();
mainWindow = exportProvider.GetExportedValue();
RegisterHighlighting();
InitializeComponent();
this.referenceElementGenerator = new ReferenceElementGenerator(this.IsLink);
textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator);
this.uiElementGenerator = new UIElementGenerator();
this.bracketHighlightRenderer = new BracketHighlightRenderer(textEditor.TextArea.TextView);
textEditor.TextArea.TextView.ElementGenerators.Add(uiElementGenerator);
textEditor.Options.RequireControlModifierForHyperlinkClick = false;
textEditor.TextArea.TextView.MouseHover += TextViewMouseHover;
textEditor.TextArea.TextView.MouseHoverStopped += TextViewMouseHoverStopped;
textEditor.TextArea.PreviewMouseDown += TextAreaMouseDown;
textEditor.TextArea.PreviewMouseUp += TextAreaMouseUp;
textEditor.TextArea.Caret.PositionChanged += HighlightBrackets;
textEditor.MouseMove += TextEditorMouseMove;
textEditor.MouseLeave += TextEditorMouseLeave;
textEditor.SetBinding(Control.FontFamilyProperty, new Binding { Source = settingsService.DisplaySettings, Path = new PropertyPath("SelectedFont") });
textEditor.SetBinding(Control.FontSizeProperty, new Binding { Source = settingsService.DisplaySettings, Path = new PropertyPath("SelectedFontSize") });
textEditor.SetBinding(TextEditor.WordWrapProperty, new Binding { Source = settingsService.DisplaySettings, Path = new PropertyPath("EnableWordWrap") });
// disable Tab editing command (useless for read-only editor); allow using tab for focus navigation instead
RemoveEditCommand(EditingCommands.TabForward);
RemoveEditCommand(EditingCommands.TabBackward);
textMarkerService = new TextMarkerService(textEditor.TextArea.TextView);
textEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService);
textEditor.TextArea.TextView.LineTransformers.Add(textMarkerService);
textEditor.ShowLineNumbers = true;
MessageBus.Subscribers += Settings_Changed;
// SearchPanel
SearchPanel searchPanel = SearchPanel.Install(textEditor.TextArea);
searchPanel.RegisterCommands(mainWindow.CommandBindings);
searchPanel.SetResourceReference(SearchPanel.MarkerBrushProperty, ResourceKeys.SearchResultBackgroundBrush);
searchPanel.Loaded += (_, _) => {
// HACK: fix search text box
var textBox = searchPanel.Template.FindName("PART_searchTextBox", searchPanel) as TextBox;
if (textBox != null)
{
// the hardcoded but misaligned margin
textBox.Margin = new Thickness(3);
// the hardcoded height
textBox.Height = double.NaN;
}
};
ShowLineMargin();
SetHighlightCurrentLine();
ContextMenuProvider.Add(this);
textEditor.TextArea.TextView.SetResourceReference(ICSharpCode.AvalonEdit.Rendering.TextView.LinkTextForegroundBrushProperty, ResourceKeys.LinkTextForegroundBrush);
textEditor.TextArea.TextView.SetResourceReference(ICSharpCode.AvalonEdit.Rendering.TextView.CurrentLineBackgroundProperty, ResourceKeys.CurrentLineBackgroundBrush);
textEditor.TextArea.TextView.SetResourceReference(ICSharpCode.AvalonEdit.Rendering.TextView.CurrentLineBorderProperty, ResourceKeys.CurrentLineBorderPen);
DataObject.AddSettingDataHandler(textEditor.TextArea, OnSettingData);
this.DataContextChanged += DecompilerTextView_DataContextChanged;
}
private void DecompilerTextView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (this.DataContext is PaneModel model)
{
model.Title = currentTitle ?? ILSpy.Properties.Resources.Decompiling;
}
}
void RemoveEditCommand(RoutedUICommand command)
{
var handler = textEditor.TextArea.DefaultInputHandler.Editing;
var inputBinding = handler.InputBindings.FirstOrDefault(b => b.Command == command);
if (inputBinding != null)
handler.InputBindings.Remove(inputBinding);
var commandBinding = handler.CommandBindings.FirstOrDefault(b => b.Command == command);
if (commandBinding != null)
handler.CommandBindings.Remove(commandBinding);
}
#endregion
#region Line margin
private void Settings_Changed(object? sender, SettingsChangedEventArgs e)
{
Settings_PropertyChanged(sender, e);
}
private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (sender is not DisplaySettings)
return;
switch (e.PropertyName)
{
case nameof(DisplaySettings.ShowLineNumbers):
ShowLineMargin();
break;
case nameof(DisplaySettings.HighlightCurrentLine):
SetHighlightCurrentLine();
break;
}
}
void ShowLineMargin()
{
foreach (var margin in this.textEditor.TextArea.LeftMargins)
{
if (margin is LineNumberMargin || margin is System.Windows.Shapes.Line)
{
margin.Visibility = settingsService.DisplaySettings.ShowLineNumbers ? Visibility.Visible : Visibility.Collapsed;
}
}
}
void SetHighlightCurrentLine()
{
textEditor.Options.HighlightCurrentLine = settingsService.DisplaySettings.HighlightCurrentLine;
}
#endregion
#region Tooltip support
ToolTip? toolTip;
Popup? popupToolTip;
void TextViewMouseHover(object sender, MouseEventArgs e)
{
if (!TryCloseExistingPopup(false))
{
return;
}
TextViewPosition? position = GetPositionFromMousePosition();
if (position == null)
return;
int offset = textEditor.Document.GetOffset(position.Value.Location);
if (referenceElementGenerator.References == null)
return;
ReferenceSegment? seg = referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault();
if (seg == null)
return;
object? content = GenerateTooltip(seg);
if (content != null)
{
popupToolTip = content as Popup;
if (popupToolTip != null)
{
var popupPosition = GetPopupPosition(e);
popupToolTip.Closed += ToolTipClosed;
popupToolTip.Placement = PlacementMode.Relative;
popupToolTip.PlacementTarget = this;
popupToolTip.HorizontalOffset = popupPosition.X;
popupToolTip.VerticalOffset = popupPosition.Y;
popupToolTip.StaysOpen = true; // We will close it ourselves
e.Handled = true;
popupToolTip.IsOpen = true;
distanceToPopupLimit = double.PositiveInfinity; // reset limit; we'll re-calculate it on the next mouse movement
}
else
{
if (toolTip == null)
{
toolTip = new ToolTip();
toolTip.Closed += ToolTipClosed;
}
toolTip.PlacementTarget = this; // required for property inheritance
if (content is string s)
{
toolTip.Content = new TextBlock {
Text = s,
TextWrapping = TextWrapping.Wrap
};
}
else
toolTip.Content = content;
e.Handled = true;
toolTip.IsOpen = true;
}
}
}
bool TryCloseExistingPopup(bool mouseClick)
{
if (popupToolTip != null)
{
if (popupToolTip.IsOpen && !mouseClick && popupToolTip is FlowDocumentTooltip t && !t.CloseWhenMouseMovesAway)
{
return false; // Popup does not want to be closed yet
}
popupToolTip.IsOpen = false;
popupToolTip = null;
}
return true;
}
/// Returns Popup position based on mouse position, in device independent units
Point GetPopupPosition(MouseEventArgs mouseArgs)
{
Point mousePos = mouseArgs.GetPosition(this);
// align Popup with line bottom
TextViewPosition? logicalPos = textEditor.GetPositionFromPoint(mousePos);
if (logicalPos.HasValue)
{
var textView = textEditor.TextArea.TextView;
return textView.GetVisualPosition(logicalPos.Value, VisualYPosition.LineBottom)
- textView.ScrollOffset
+ new Vector(-4, 0);
}
else
{
return mousePos + new Vector(-4, 6);
}
}
void TextViewMouseHoverStopped(object sender, MouseEventArgs e)
{
// Non-popup tooltips get closed as soon as the mouse starts moving again
if (toolTip != null)
{
toolTip.IsOpen = false;
e.Handled = true;
}
}
double distanceToPopupLimit;
const double MaxMovementAwayFromPopup = 5;
void TextEditorMouseMove(object sender, MouseEventArgs e)
{
if (popupToolTip != null && PresentationSource.FromVisual(popupToolTip.Child) != null)
{
double distanceToPopup = GetDistanceToPopup(e);
if (distanceToPopup > distanceToPopupLimit)
{
// Close popup if mouse moved away, exceeding the limit
TryCloseExistingPopup(false);
}
else
{
// reduce distanceToPopupLimit
distanceToPopupLimit = Math.Min(distanceToPopupLimit, distanceToPopup + MaxMovementAwayFromPopup);
}
}
}
double GetDistanceToPopup(MouseEventArgs e)
{
Point p = popupToolTip!.Child.PointFromScreen(PointToScreen(e.GetPosition(this)));
Size size = popupToolTip.Child.RenderSize;
double x = 0;
if (p.X < 0)
x = -p.X;
else if (p.X > size.Width)
x = p.X - size.Width;
double y = 0;
if (p.Y < 0)
y = -p.Y;
else if (p.Y > size.Height)
y = p.Y - size.Height;
return Math.Sqrt(x * x + y * y);
}
void TextEditorMouseLeave(object sender, MouseEventArgs e)
{
if (popupToolTip != null && !popupToolTip.IsMouseOver)
{
// do not close popup if mouse moved from editor to popup
TryCloseExistingPopup(false);
}
}
void OnUnloaded(object sender, EventArgs e)
{
// Close popup when another document gets selected
// TextEditorMouseLeave is not sufficient for this because the mouse might be over the popup when the document switch happens (e.g. Ctrl+Tab)
TryCloseExistingPopup(true);
}
void ToolTipClosed(object? sender, EventArgs e)
{
if (toolTip == sender)
{
toolTip = null;
}
if (popupToolTip == sender)
{
// Because popupToolTip instances are created by the tooltip provider,
// they might be reused; so we should detach the event handler
if (popupToolTip != null)
{
popupToolTip.Closed -= ToolTipClosed;
}
popupToolTip = null;
}
}
object? GenerateTooltip(ReferenceSegment segment)
{
var fontSize = settingsService.DisplaySettings.SelectedFontSize;
if (segment.Reference is ICSharpCode.Decompiler.Disassembler.OpCodeInfo code)
{
XmlDocumentationProvider docProvider = XmlDocLoader.MscorlibDocumentation;
DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), languageService.Language.SyntaxHighlighting, settingsService.DisplaySettings, mainWindow);
renderer.AddSignatureBlock($"{code.Name} (0x{code.Code:x})");
if (docProvider != null)
{
string documentation = docProvider.GetDocumentation("F:System.Reflection.Emit.OpCodes." + code.EncodedName);
if (documentation != null)
{
renderer.AddXmlDocumentation(documentation, null, null);
}
}
return new FlowDocumentTooltip(renderer.CreateDocument(), fontSize, mainWindow.ActualWidth);
}
else if (segment.Reference is IEntity entity)
{
var document = CreateTooltipForEntity(entity);
if (document == null)
return null;
return new FlowDocumentTooltip(document, fontSize, mainWindow.ActualWidth);
}
else if (segment.Reference is EntityReference unresolvedEntity)
{
var assemblyList = exportProvider.GetExportedValue();
var module = unresolvedEntity.ResolveAssembly(assemblyList);
if (module == null)
return null;
var typeSystem = new DecompilerTypeSystem(module,
module.GetAssemblyResolver(),
TypeSystemOptions.Default | TypeSystemOptions.Uncached);
try
{
Handle handle = unresolvedEntity.Handle;
if (!handle.IsEntityHandle())
return null;
IEntity resolved = typeSystem.MainModule.ResolveEntity((EntityHandle)handle);
if (resolved == null)
return null;
var document = CreateTooltipForEntity(resolved);
if (document == null)
return null;
return new FlowDocumentTooltip(document, fontSize, mainWindow.ActualWidth);
}
catch (BadImageFormatException)
{
return null;
}
}
return null;
}
FlowDocument? CreateTooltipForEntity(IEntity resolved)
{
Language currentLanguage = languageService.Language;
DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), currentLanguage.SyntaxHighlighting, settingsService.DisplaySettings, mainWindow);
RichText richText = currentLanguage.GetRichTextTooltip(resolved);
if (richText == null)
{
return null;
}
renderer.AddSignatureBlock(richText.Text, richText.ToRichTextModel());
try
{
if (resolved.ParentModule == null || resolved.ParentModule.MetadataFile == null)
return null;
var docProvider = XmlDocLoader.LoadDocumentation(resolved.ParentModule.MetadataFile);
if (docProvider != null)
{
string documentation = docProvider.GetDocumentation(resolved.GetIdString());
if (documentation != null)
{
renderer.AddXmlDocumentation(documentation, resolved, ResolveReference);
}
}
}
catch (XmlException)
{
// ignore
}
return renderer.CreateDocument();
IEntity? ResolveReference(string idString)
{
var assemblyList = exportProvider.GetExportedValue();
return AssemblyTreeModel.FindEntityInRelevantAssemblies(idString, assemblyList.GetAssemblies());
}
}
sealed class FlowDocumentTooltip : Popup
{
readonly FlowDocumentScrollViewer viewer;
public FlowDocumentTooltip(FlowDocument document, double fontSize, double maxWith)
{
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
viewer = new() {
Width = document.MinPageWidth + fontSize * 5,
MaxWidth = maxWith,
Document = document
};
Border border = new Border {
BorderThickness = new Thickness(1),
MaxHeight = 400,
Child = viewer
};
border.SetResourceReference(Border.BackgroundProperty, SystemColors.ControlBrushKey);
border.SetResourceReference(Border.BorderBrushProperty, SystemColors.ControlDarkBrushKey);
this.Child = border;
viewer.SetResourceReference(ForegroundProperty, SystemColors.InfoTextBrushKey);
document.TextAlignment = TextAlignment.Left;
document.FontSize = fontSize;
document.FontFamily = SystemFonts.SmallCaptionFontFamily;
}
public bool CloseWhenMouseMovesAway {
get { return !this.IsKeyboardFocusWithin; }
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
this.IsOpen = false;
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
// When the mouse is over the popup, it is possible for ILSpy to be minimized,
// or moved into the background, and yet the popup stays open.
// We don't have a good method here to check whether the mouse moved back into the text area
// or somewhere else, so we'll just close the popup.
if (CloseWhenMouseMovesAway)
this.IsOpen = false;
}
}
#endregion
#region Highlight brackets
void HighlightBrackets(object? sender, EventArgs e)
{
if (settingsService.DisplaySettings.HighlightMatchingBraces)
{
var result = languageService.Language.BracketSearcher.SearchBracket(textEditor.Document, textEditor.CaretOffset);
bracketHighlightRenderer.SetHighlight(result);
}
else
{
bracketHighlightRenderer.SetHighlight(null);
}
}
#endregion
#region RunWithCancellation
public void Report(DecompilationProgress value)
{
double v = (double)value.UnitsCompleted / value.TotalUnits;
Dispatcher.BeginInvoke(DispatcherPriority.Normal, delegate {
progressBar.IsIndeterminate = !double.IsFinite(v);
progressBar.Value = v * 100.0;
progressTitle.Text = !string.IsNullOrWhiteSpace(value.Title) ? value.Title : Properties.Resources.Decompiling;
progressText.Text = value.Status;
progressText.Visibility = !string.IsNullOrWhiteSpace(progressText.Text) ? Visibility.Visible : Visibility.Collapsed;
var taskBar = mainWindow.TaskbarItemInfo;
if (taskBar != null)
{
taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Normal;
taskBar.ProgressValue = v;
}
if (this.DataContext is TabPageModel model)
{
model.Title = progressTitle.Text;
}
});
}
///
/// Switches the GUI into "waiting" mode, then calls to create
/// the task.
/// If another task is started before the previous task finishes running, the previous task is cancelled.
///
public Task RunWithCancellation(Func> taskCreation, string? progressTitle = null)
{
if (waitAdorner.Visibility != Visibility.Visible)
{
waitAdorner.Visibility = Visibility.Visible;
// Work around a WPF bug by setting IsIndeterminate only while the progress bar is visible.
// https://github.com/icsharpcode/ILSpy/issues/593
this.progressTitle.Text = progressTitle == null ? Properties.Resources.Decompiling : progressTitle;
progressBar.IsIndeterminate = true;
progressText.Text = null;
progressText.Visibility = Visibility.Collapsed;
waitAdorner.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(0.5)), FillBehavior.Stop));
var taskBar = mainWindow.TaskbarItemInfo;
if (taskBar != null)
{
taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Indeterminate;
}
}
CancellationTokenSource? previousCancellationTokenSource = currentCancellationTokenSource;
var myCancellationTokenSource = new CancellationTokenSource();
currentCancellationTokenSource = myCancellationTokenSource;
// cancel the previous only after current was set to the new one (avoid that the old one still finishes successfully)
if (previousCancellationTokenSource != null)
{
previousCancellationTokenSource.Cancel();
}
var tcs = new TaskCompletionSource();
Task task;
try
{
task = taskCreation(myCancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
task = TaskHelper.FromCancellation();
}
catch (Exception ex)
{
task = TaskHelper.FromException(ex);
}
Action continuation = delegate {
try
{
if (currentCancellationTokenSource == myCancellationTokenSource)
{
currentCancellationTokenSource = null;
waitAdorner.Visibility = Visibility.Collapsed;
progressBar.IsIndeterminate = false;
progressText.Text = null;
progressText.Visibility = Visibility.Collapsed;
var taskBar = mainWindow.TaskbarItemInfo;
if (taskBar != null)
{
taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
}
if (task.IsCanceled)
{
AvalonEditTextOutput output = new AvalonEditTextOutput();
output.WriteLine("The operation was canceled.");
ShowOutput(output);
}
tcs.SetFromTask(task);
}
else
{
tcs.SetCanceled();
}
}
finally
{
myCancellationTokenSource.Dispose();
}
};
task.ContinueWith(delegate { Dispatcher.BeginInvoke(DispatcherPriority.Normal, continuation); });
return tcs.Task;
}
void CancelButton_Click(object sender, RoutedEventArgs e)
{
if (currentCancellationTokenSource != null)
{
currentCancellationTokenSource.Cancel();
// Don't set to null: the task still needs to produce output and hide the wait adorner
}
}
#endregion
#region ShowOutput
public void ShowText(AvalonEditTextOutput textOutput)
{
ShowNodes(textOutput, null);
}
public void ShowNode(AvalonEditTextOutput textOutput, ILSpyTreeNode node, IHighlightingDefinition? highlighting = null)
{
ShowNodes(textOutput, new[] { node }, highlighting);
}
///
/// Shows the given output in the text view.
/// Cancels any currently running decompilation tasks.
///
public void ShowNodes(AvalonEditTextOutput textOutput, ILSpyTreeNode[]? nodes, IHighlightingDefinition? highlighting = null)
{
// Cancel the decompilation task:
if (currentCancellationTokenSource != null)
{
currentCancellationTokenSource.Cancel();
currentCancellationTokenSource = null; // prevent canceled task from producing output
}
if (this.nextDecompilationRun != null)
{
// remove scheduled decompilation run
this.nextDecompilationRun.TaskCompletionSource.TrySetCanceled();
this.nextDecompilationRun = null;
}
if (nodes != null && (string.IsNullOrEmpty(textOutput.Title)
|| textOutput.Title == Properties.Resources.NewTab))
{
textOutput.Title = string.Join(", ", nodes.Select(n => n.Text));
}
decompiledNodes = nodes;
ShowOutput(textOutput, highlighting);
}
///
/// Shows the given output in the text view.
///
void ShowOutput(AvalonEditTextOutput textOutput, IHighlightingDefinition? highlighting = null, DecompilerTextViewState? state = null)
{
Debug.WriteLine("Showing {0} characters of output", textOutput.TextLength);
Stopwatch w = Stopwatch.StartNew();
ClearLocalReferenceMarks();
textEditor.ScrollToHome();
if (foldingManager != null)
{
FoldingManager.Uninstall(foldingManager);
foldingManager = null;
}
textEditor.Document = null; // clear old document while we're changing the highlighting
uiElementGenerator.UIElements = textOutput.UIElements;
referenceElementGenerator.References = textOutput.References;
references = textOutput.References;
definitionLookup = textOutput.DefinitionLookup;
textEditor.SyntaxHighlighting = highlighting;
textEditor.Options.EnableEmailHyperlinks = textOutput.EnableHyperlinks;
textEditor.Options.EnableHyperlinks = textOutput.EnableHyperlinks;
activeRichTextModel = null;
if (activeRichTextColorizer != null)
textEditor.TextArea.TextView.LineTransformers.Remove(activeRichTextColorizer);
if (textOutput.HighlightingModel != null)
{
activeRichTextModel = textOutput.HighlightingModel;
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)
{
textEditor.TextArea.TextView.ElementGenerators.Remove(elementGenerator);
}
activeCustomElementGenerators.Clear();
foreach (var elementGenerator in textOutput.elementGenerators)
{
textEditor.TextArea.TextView.ElementGenerators.Add(elementGenerator);
activeCustomElementGenerators.Add(elementGenerator);
}
Debug.WriteLine(" Set-up: {0}", w.Elapsed);
w.Restart();
textEditor.Document = textOutput.GetDocument();
Debug.WriteLine(" Assigning document: {0}", w.Elapsed);
w.Restart();
if (textOutput.Foldings.Count > 0)
{
if (state != null)
{
state.RestoreFoldings(textOutput.Foldings, settingsService.DisplaySettings.ExpandMemberDefinitions);
textEditor.ScrollToVerticalOffset(state.VerticalOffset);
textEditor.ScrollToHorizontalOffset(state.HorizontalOffset);
}
foldingManager = FoldingManager.Install(textEditor.TextArea);
foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1);
Debug.WriteLine(" Updating folding: {0}", w.Elapsed);
w.Restart();
}
else if (highlighting?.Name == "XML")
{
foldingManager = FoldingManager.Install(textEditor.TextArea);
var foldingStrategy = new XmlFoldingStrategy();
foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document);
Debug.WriteLine(" Updating folding: {0}", w.Elapsed);
w.Restart();
}
if (this.DataContext is PaneModel model)
{
model.Title = textOutput.Title;
}
currentAddress = textOutput.Address;
currentTitle = textOutput.Title;
expandMemberDefinitions = settingsService.DisplaySettings.ExpandMemberDefinitions;
}
#endregion
#region Decompile (for display)
// more than 5M characters is too slow to output (when user browses treeview)
public const int DefaultOutputLengthLimit = 5000000;
// more than 75M characters can get us into trouble with memory usage
public const int ExtendedOutputLengthLimit = 75000000;
DecompilationContext? nextDecompilationRun;
[Obsolete("Use DecompileAsync() instead")]
public void Decompile(ILSpy.Language language, IEnumerable treeNodes, DecompilationOptions options)
{
DecompileAsync(language, treeNodes, options).HandleExceptions();
}
///
/// Starts the decompilation of the given nodes.
/// The result is displayed in the text view.
/// If any errors occur, the error message is displayed in the text view, and the task returned by this method completes successfully.
/// If the operation is cancelled (by starting another decompilation action); the returned task is marked as cancelled.
///
public Task DecompileAsync(ILSpy.Language language, IEnumerable treeNodes, DecompilationOptions options)
{
// Some actions like loading an assembly list cause several selection changes in the tree view,
// and each of those will start a decompilation action.
bool isDecompilationScheduled = this.nextDecompilationRun != null;
if (this.nextDecompilationRun != null)
this.nextDecompilationRun.TaskCompletionSource.TrySetCanceled();
this.nextDecompilationRun = new DecompilationContext(language, treeNodes.ToArray(), options);
var task = this.nextDecompilationRun.TaskCompletionSource.Task;
if (!isDecompilationScheduled)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(
delegate {
var context = this.nextDecompilationRun;
this.nextDecompilationRun = null;
if (context != null)
DoDecompile(context, DefaultOutputLengthLimit)
.ContinueWith(t => context.TaskCompletionSource.SetFromTask(t)).HandleExceptions();
}
));
}
return task;
}
sealed class DecompilationContext
{
public readonly ILSpy.Language Language;
public readonly ILSpyTreeNode[] TreeNodes;
public readonly DecompilationOptions Options;
public readonly TaskCompletionSource