PageRenderTime 102ms CodeModel.GetById 55ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 1ms

/ILSpy/TextView/DecompilerTextView.cs

http://github.com/icsharpcode/ILSpy
C# | 1113 lines | 904 code | 103 blank | 106 comment | 191 complexity | 3067f89207382256ff6fc8de332f575c MD5 | raw file
   1// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
   2// 
   3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
   4// software and associated documentation files (the "Software"), to deal in the Software
   5// without restriction, including without limitation the rights to use, copy, modify, merge,
   6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
   7// to whom the Software is furnished to do so, subject to the following conditions:
   8// 
   9// The above copyright notice and this permission notice shall be included in all copies or
  10// substantial portions of the Software.
  11// 
  12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17// DEALINGS IN THE SOFTWARE.
  18
  19using System;
  20using System.Collections.Generic;
  21using System.ComponentModel;
  22using System.ComponentModel.Composition;
  23using System.Diagnostics;
  24using System.IO;
  25using System.Linq;
  26using System.Reflection.Metadata;
  27using System.Threading;
  28using System.Threading.Tasks;
  29using System.Windows;
  30using System.Windows.Controls;
  31using System.Windows.Controls.Primitives;
  32using System.Windows.Data;
  33using System.Windows.Documents;
  34using System.Windows.Input;
  35using System.Windows.Media;
  36using System.Windows.Media.Animation;
  37using System.Windows.Threading;
  38using System.Xml;
  39using ICSharpCode.AvalonEdit;
  40using ICSharpCode.AvalonEdit.Document;
  41using ICSharpCode.AvalonEdit.Editing;
  42using ICSharpCode.AvalonEdit.Folding;
  43using ICSharpCode.AvalonEdit.Highlighting;
  44using ICSharpCode.AvalonEdit.Highlighting.Xshd;
  45using ICSharpCode.AvalonEdit.Rendering;
  46using ICSharpCode.AvalonEdit.Search;
  47using ICSharpCode.Decompiler;
  48using ICSharpCode.Decompiler.CSharp;
  49using ICSharpCode.Decompiler.CSharp.OutputVisitor;
  50using ICSharpCode.Decompiler.Documentation;
  51using ICSharpCode.Decompiler.Metadata;
  52using ICSharpCode.Decompiler.Output;
  53using ICSharpCode.Decompiler.TypeSystem;
  54using ICSharpCode.ILSpy.AvalonEdit;
  55using ICSharpCode.ILSpy.Options;
  56using ICSharpCode.ILSpy.TreeNodes;
  57using ICSharpCode.ILSpy.ViewModels;
  58using Microsoft.Win32;
  59
  60namespace ICSharpCode.ILSpy.TextView
  61{
  62	/// <summary>
  63	/// Manages the TextEditor showing the decompiled code.
  64	/// Contains all the threading logic that makes the decompiler work in the background.
  65	/// </summary>
  66	public sealed partial class DecompilerTextView : UserControl, IDisposable, IHaveState
  67	{
  68		readonly ReferenceElementGenerator referenceElementGenerator;
  69		readonly UIElementGenerator uiElementGenerator;
  70		readonly List<VisualLineElementGenerator> activeCustomElementGenerators = new List<VisualLineElementGenerator>();
  71		RichTextColorizer activeRichTextColorizer;
  72		BracketHighlightRenderer bracketHighlightRenderer;
  73		FoldingManager foldingManager;
  74		ILSpyTreeNode[] decompiledNodes;
  75		Uri currentAddress;
  76		
  77		DefinitionLookup definitionLookup;
  78		TextSegmentCollection<ReferenceSegment> references;
  79		CancellationTokenSource currentCancellationTokenSource;
  80		
  81		readonly TextMarkerService textMarkerService;
  82		readonly List<ITextMarker> localReferenceMarks = new List<ITextMarker>();
  83		
  84		#region Constructor
  85		public DecompilerTextView()
  86		{
  87			HighlightingManager.Instance.RegisterHighlighting(
  88				"ILAsm", new string[] { ".il" },
  89				delegate {
  90					using (Stream s = typeof(DecompilerTextView).Assembly.GetManifestResourceStream(typeof(DecompilerTextView), "ILAsm-Mode.xshd")) {
  91						using (XmlTextReader reader = new XmlTextReader(s)) {
  92							return HighlightingLoader.Load(reader, HighlightingManager.Instance);
  93						}
  94					}
  95				});
  96
  97			HighlightingManager.Instance.RegisterHighlighting(
  98				"C#", new string[] { ".cs" },
  99				delegate {
 100					using (Stream s = typeof(DecompilerTextView).Assembly.GetManifestResourceStream(typeof(DecompilerTextView), "CSharp-Mode.xshd")) {
 101						using (XmlTextReader reader = new XmlTextReader(s)) {
 102							return HighlightingLoader.Load(reader, HighlightingManager.Instance);
 103						}
 104					}
 105				});
 106
 107			HighlightingManager.Instance.RegisterHighlighting(
 108				"Asm", new string[] { ".s", ".asm" },
 109				delegate {
 110					using (Stream s = typeof(DecompilerTextView).Assembly.GetManifestResourceStream(typeof(DecompilerTextView), "Asm-Mode.xshd")) {
 111						using (XmlTextReader reader = new XmlTextReader(s)) {
 112							return HighlightingLoader.Load(reader, HighlightingManager.Instance);
 113						}
 114					}
 115				});
 116
 117			InitializeComponent();
 118
 119			this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference, this.IsLink);
 120			textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator);
 121			this.uiElementGenerator = new UIElementGenerator();
 122			this.bracketHighlightRenderer = new BracketHighlightRenderer(textEditor.TextArea.TextView);
 123			textEditor.TextArea.TextView.ElementGenerators.Add(uiElementGenerator);
 124			textEditor.Options.RequireControlModifierForHyperlinkClick = false;
 125			textEditor.TextArea.TextView.MouseHover += TextViewMouseHover;
 126			textEditor.TextArea.TextView.MouseHoverStopped += TextViewMouseHoverStopped;
 127			textEditor.TextArea.PreviewMouseDown += TextAreaMouseDown;
 128			textEditor.TextArea.PreviewMouseUp += TextAreaMouseUp;
 129			textEditor.TextArea.Caret.PositionChanged += HighlightBrackets;
 130			textEditor.MouseMove += TextEditorMouseMove;
 131			textEditor.MouseLeave += TextEditorMouseLeave;
 132			textEditor.SetBinding(Control.FontFamilyProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFont") });
 133			textEditor.SetBinding(Control.FontSizeProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFontSize") });
 134			textEditor.SetBinding(TextEditor.WordWrapProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("EnableWordWrap") });
 135
 136			// disable Tab editing command (useless for read-only editor); allow using tab for focus navigation instead
 137			RemoveEditCommand(EditingCommands.TabForward);
 138			RemoveEditCommand(EditingCommands.TabBackward);
 139			
 140			textMarkerService = new TextMarkerService(textEditor.TextArea.TextView);
 141			textEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService);
 142			textEditor.TextArea.TextView.LineTransformers.Add(textMarkerService);
 143			textEditor.ShowLineNumbers = true;
 144			DisplaySettingsPanel.CurrentDisplaySettings.PropertyChanged += CurrentDisplaySettings_PropertyChanged;
 145
 146			// SearchPanel
 147			SearchPanel.Install(textEditor.TextArea)
 148				.RegisterCommands(Application.Current.MainWindow.CommandBindings);
 149			
 150			ShowLineMargin();
 151			
 152			// add marker service & margin
 153			textEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService);
 154			textEditor.TextArea.TextView.LineTransformers.Add(textMarkerService);
 155
 156			ContextMenuProvider.Add(this);
 157		}
 158
 159		void RemoveEditCommand(RoutedUICommand command)
 160		{
 161			var handler = textEditor.TextArea.DefaultInputHandler.Editing;
 162			var inputBinding = handler.InputBindings.FirstOrDefault(b => b.Command == command);
 163			if (inputBinding != null)
 164				handler.InputBindings.Remove(inputBinding);
 165			var commandBinding = handler.CommandBindings.FirstOrDefault(b => b.Command == command);
 166			if (commandBinding != null)
 167				handler.CommandBindings.Remove(commandBinding);
 168		}
 169		#endregion
 170		
 171		#region Line margin
 172
 173		void CurrentDisplaySettings_PropertyChanged(object sender, PropertyChangedEventArgs e)
 174		{
 175			if (e.PropertyName == "ShowLineNumbers") {
 176				ShowLineMargin();
 177			}
 178		}
 179		
 180		void ShowLineMargin()
 181		{
 182			foreach (var margin in this.textEditor.TextArea.LeftMargins) {
 183				if (margin is LineNumberMargin || margin is System.Windows.Shapes.Line) {
 184					margin.Visibility = DisplaySettingsPanel.CurrentDisplaySettings.ShowLineNumbers ? Visibility.Visible : Visibility.Collapsed;
 185				}
 186			}
 187		}
 188
 189		#endregion
 190
 191		#region Tooltip support
 192		ToolTip toolTip;
 193		Popup popupToolTip;
 194
 195		void TextViewMouseHover(object sender, MouseEventArgs e)
 196		{
 197			if (!TryCloseExistingPopup(false)) {
 198				return;
 199			}
 200			TextViewPosition? position = GetPositionFromMousePosition();
 201			if (position == null)
 202				return;
 203			int offset = textEditor.Document.GetOffset(position.Value.Location);
 204			if (referenceElementGenerator.References == null)
 205				return;
 206			ReferenceSegment seg = referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault();
 207			if (seg == null)
 208				return;
 209			object content = GenerateTooltip(seg);
 210	
 211			if (content != null) {
 212				popupToolTip = content as Popup;
 213
 214				if (popupToolTip != null) {
 215					var popupPosition = GetPopupPosition(e);
 216					popupToolTip.Closed += ToolTipClosed;
 217					popupToolTip.HorizontalOffset = popupPosition.X;
 218					popupToolTip.VerticalOffset = popupPosition.Y;
 219					popupToolTip.StaysOpen = true;  // We will close it ourselves
 220
 221					e.Handled = true;
 222					popupToolTip.IsOpen = true;
 223					distanceToPopupLimit = double.PositiveInfinity; // reset limit; we'll re-calculate it on the next mouse movement
 224				} else {
 225					if (toolTip == null) {
 226						toolTip = new ToolTip();
 227						toolTip.Closed += ToolTipClosed;
 228					}
 229					toolTip.PlacementTarget = this; // required for property inheritance
 230
 231					if (content is string s) {
 232						toolTip.Content = new TextBlock {
 233							Text = s,
 234							TextWrapping = TextWrapping.Wrap
 235						};
 236					} else
 237						toolTip.Content = content;
 238
 239					e.Handled = true;
 240					toolTip.IsOpen = true;
 241				}
 242			}
 243		}
 244
 245		bool TryCloseExistingPopup(bool mouseClick)
 246		{
 247			if (popupToolTip != null) {
 248				if (popupToolTip.IsOpen && !mouseClick && popupToolTip is FlowDocumentTooltip t && !t.CloseWhenMouseMovesAway) {
 249					return false; // Popup does not want to be closed yet
 250				}
 251				popupToolTip.IsOpen = false;
 252				popupToolTip = null;
 253			}
 254			return true;
 255		}
 256
 257		/// <summary> Returns Popup position based on mouse position, in device independent units </summary>
 258		Point GetPopupPosition(MouseEventArgs mouseArgs)
 259		{
 260			Point mousePos = mouseArgs.GetPosition(this);
 261			Point positionInPixels;
 262			// align Popup with line bottom
 263			TextViewPosition? logicalPos = textEditor.GetPositionFromPoint(mousePos);
 264			if (logicalPos.HasValue) {
 265				var textView = textEditor.TextArea.TextView;
 266				positionInPixels =
 267					textView.PointToScreen(
 268						textView.GetVisualPosition(logicalPos.Value, VisualYPosition.LineBottom) - textView.ScrollOffset);
 269				positionInPixels.X -= 4;
 270			} else {
 271				positionInPixels = PointToScreen(mousePos + new Vector(-4, 6));
 272			}
 273			// use device independent units, because Popup Left/Top are in independent units
 274			return positionInPixels.TransformFromDevice(this);
 275		}
 276
 277		void TextViewMouseHoverStopped(object sender, MouseEventArgs e)
 278		{
 279			// Non-popup tooltips get closed as soon as the mouse starts moving again
 280			if (toolTip != null) {
 281				toolTip.IsOpen = false;
 282				e.Handled = true;
 283			}
 284		}
 285
 286		double distanceToPopupLimit;
 287		const double MaxMovementAwayFromPopup = 5;
 288
 289		void TextEditorMouseMove(object sender, MouseEventArgs e)
 290		{
 291			if (popupToolTip != null) {
 292				double distanceToPopup = GetDistanceToPopup(e);
 293				if (distanceToPopup > distanceToPopupLimit) {
 294					// Close popup if mouse moved away, exceeding the limit
 295					TryCloseExistingPopup(false);
 296				} else {
 297					// reduce distanceToPopupLimit
 298					distanceToPopupLimit = Math.Min(distanceToPopupLimit, distanceToPopup + MaxMovementAwayFromPopup);
 299				}
 300			}
 301		}
 302
 303		double GetDistanceToPopup(MouseEventArgs e)
 304		{
 305			Point p = popupToolTip.Child.PointFromScreen(PointToScreen(e.GetPosition(this)));
 306			Size size = popupToolTip.Child.RenderSize;
 307			double x = 0;
 308			if (p.X < 0)
 309				x = -p.X;
 310			else if (p.X > size.Width)
 311				x = p.X - size.Width;
 312			double y = 0;
 313			if (p.Y < 0)
 314				y = -p.Y;
 315			else if (p.Y > size.Height)
 316				y = p.Y - size.Height;
 317			return Math.Sqrt(x * x + y * y);
 318		}
 319
 320		void TextEditorMouseLeave(object sender, MouseEventArgs e)
 321		{
 322			if (popupToolTip != null && !popupToolTip.IsMouseOver) {
 323				// do not close popup if mouse moved from editor to popup
 324				TryCloseExistingPopup(false);
 325			}
 326		}
 327
 328		void OnUnloaded(object sender, EventArgs e)
 329		{
 330			// Close popup when another document gets selected
 331			// TextEditorMouseLeave is not sufficient for this because the mouse might be over the popup when the document switch happens (e.g. Ctrl+Tab)
 332			TryCloseExistingPopup(true);
 333		}
 334
 335		void ToolTipClosed(object sender, EventArgs e)
 336		{
 337			if (toolTip == sender) {
 338				toolTip = null;
 339			}
 340			if (popupToolTip == sender) {
 341				// Because popupToolTip instances are created by the tooltip provider,
 342				// they might be reused; so we should detach the event handler
 343				popupToolTip.Closed -= ToolTipClosed;
 344				popupToolTip = null;
 345			}
 346		}
 347
 348		object GenerateTooltip(ReferenceSegment segment)
 349		{
 350			if (segment.Reference is ICSharpCode.Decompiler.Disassembler.OpCodeInfo code) {
 351				XmlDocumentationProvider docProvider = XmlDocLoader.MscorlibDocumentation;
 352				DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), MainWindow.Instance.CurrentLanguage.SyntaxHighlighting);
 353				renderer.AddSignatureBlock($"{code.Name} (0x{code.Code:x})");
 354				if (docProvider != null) {
 355					string documentation = docProvider.GetDocumentation("F:System.Reflection.Emit.OpCodes." + code.EncodedName);
 356					if (documentation != null) {
 357						renderer.AddXmlDocumentation(documentation, null, null);
 358					}
 359				}
 360				return new FlowDocumentTooltip(renderer.CreateDocument());
 361			} else if (segment.Reference is IEntity entity) {
 362				var document = CreateTooltipForEntity(entity);
 363				if (document == null)
 364					return null;
 365				return new FlowDocumentTooltip(document);
 366			} else if (segment.Reference is EntityReference unresolvedEntity) {
 367				var typeSystem = new DecompilerTypeSystem(unresolvedEntity.Module, unresolvedEntity.Module.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached);
 368				try {
 369					IEntity resolved = typeSystem.MainModule.ResolveEntity((EntityHandle)unresolvedEntity.Handle);
 370					if (resolved == null)
 371						return null;
 372					var document = CreateTooltipForEntity(resolved);
 373					if (document == null)
 374						return null;
 375					return new FlowDocumentTooltip(document);
 376				} catch (BadImageFormatException) {
 377					return null;
 378				}
 379			}
 380			return null;
 381		}
 382
 383		static FlowDocument CreateTooltipForEntity(IEntity resolved)
 384		{
 385			Language currentLanguage = MainWindow.Instance.CurrentLanguage;
 386			DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), currentLanguage.SyntaxHighlighting);
 387			RichText richText = currentLanguage.GetRichTextTooltip(resolved);
 388			renderer.AddSignatureBlock(richText.Text, richText.ToRichTextModel());
 389			try {
 390				if (resolved.ParentModule == null || resolved.ParentModule.PEFile == null)
 391					return null;
 392				var docProvider = XmlDocLoader.LoadDocumentation(resolved.ParentModule.PEFile);
 393				if (docProvider != null) {
 394					string documentation = docProvider.GetDocumentation(resolved.GetIdString());
 395					if (documentation != null) {
 396						renderer.AddXmlDocumentation(documentation, resolved, ResolveReference);
 397					}
 398				}
 399			} catch (XmlException) {
 400				// ignore
 401			}
 402			return renderer.CreateDocument();
 403
 404			IEntity ResolveReference(string idString)
 405			{
 406				return MainWindow.FindEntityInRelevantAssemblies(idString, MainWindow.Instance.CurrentAssemblyList.GetAssemblies());
 407			}
 408		}
 409
 410		sealed class FlowDocumentTooltip : Popup
 411		{
 412			readonly FlowDocumentScrollViewer viewer;
 413
 414			public FlowDocumentTooltip(FlowDocument document)
 415			{
 416				TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
 417				double fontSize = DisplaySettingsPanel.CurrentDisplaySettings.SelectedFontSize;
 418				viewer = new FlowDocumentScrollViewer() {
 419					Width = document.MinPageWidth + fontSize * 5,
 420					MaxWidth = MainWindow.Instance.ActualWidth
 421				};
 422				viewer.Document = document;
 423				Border border = new Border {
 424					Background = SystemColors.ControlBrush,
 425					BorderBrush = SystemColors.ControlDarkBrush,
 426					BorderThickness = new Thickness(1),
 427					MaxHeight = 400,
 428					Child = viewer
 429				};
 430				this.Child = border;
 431				viewer.Foreground = SystemColors.InfoTextBrush;
 432				document.TextAlignment = TextAlignment.Left;
 433				document.FontSize = fontSize;
 434				document.FontFamily = SystemFonts.SmallCaptionFontFamily;
 435			}
 436
 437			public bool CloseWhenMouseMovesAway {
 438				get { return !this.IsKeyboardFocusWithin; }
 439			}
 440
 441			protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
 442			{
 443				base.OnLostKeyboardFocus(e);
 444				this.IsOpen = false;
 445			}
 446
 447			protected override void OnMouseLeave(MouseEventArgs e)
 448			{
 449				base.OnMouseLeave(e);
 450				// When the mouse is over the popup, it is possible for ILSpy to be minimized,
 451				// or moved into the background, and yet the popup stays open.
 452				// We don't have a good method here to check whether the mouse moved back into the text area
 453				// or somewhere else, so we'll just close the popup.
 454				if (CloseWhenMouseMovesAway)
 455					this.IsOpen = false;
 456			}
 457		}
 458		#endregion
 459
 460		#region Highlight brackets
 461		void HighlightBrackets(object sender, EventArgs e)
 462		{
 463			if (DisplaySettingsPanel.CurrentDisplaySettings.HighlightMatchingBraces) {
 464				var result = MainWindow.Instance.CurrentLanguage.BracketSearcher.SearchBracket(textEditor.Document, textEditor.CaretOffset);
 465				bracketHighlightRenderer.SetHighlight(result);
 466			} else {
 467				bracketHighlightRenderer.SetHighlight(null);
 468			}
 469		}
 470		#endregion
 471
 472		#region RunWithCancellation
 473		/// <summary>
 474		/// Switches the GUI into "waiting" mode, then calls <paramref name="taskCreation"/> to create
 475		/// the task.
 476		/// When the task completes without being cancelled, the <paramref name="taskCompleted"/>
 477		/// callback is called on the GUI thread.
 478		/// When the task is cancelled before completing, the callback is not called; and any result
 479		/// of the task (including exceptions) are ignored.
 480		/// </summary>
 481		[Obsolete("RunWithCancellation(taskCreation).ContinueWith(taskCompleted) instead")]
 482		public void RunWithCancellation<T>(Func<CancellationToken, Task<T>> taskCreation, Action<Task<T>> taskCompleted)
 483		{
 484			RunWithCancellation(taskCreation).ContinueWith(taskCompleted, CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());
 485		}
 486		
 487		/// <summary>
 488		/// Switches the GUI into "waiting" mode, then calls <paramref name="taskCreation"/> to create
 489		/// the task.
 490		/// If another task is started before the previous task finishes running, the previous task is cancelled.
 491		/// </summary>
 492		public Task<T> RunWithCancellation<T>(Func<CancellationToken, Task<T>> taskCreation)
 493		{
 494			if (waitAdorner.Visibility != Visibility.Visible) {
 495				waitAdorner.Visibility = Visibility.Visible;
 496				// Work around a WPF bug by setting IsIndeterminate only while the progress bar is visible.
 497				// https://github.com/icsharpcode/ILSpy/issues/593
 498				progressBar.IsIndeterminate = true;
 499				waitAdorner.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(0.5)), FillBehavior.Stop));
 500				var taskBar = MainWindow.Instance.TaskbarItemInfo;
 501				if (taskBar != null) {
 502					taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Indeterminate;
 503				}
 504			}
 505			CancellationTokenSource previousCancellationTokenSource = currentCancellationTokenSource;
 506			var myCancellationTokenSource = new CancellationTokenSource();
 507			currentCancellationTokenSource = myCancellationTokenSource;
 508			// cancel the previous only after current was set to the new one (avoid that the old one still finishes successfully)
 509			if (previousCancellationTokenSource != null)
 510				previousCancellationTokenSource.Cancel();
 511			
 512			var tcs = new TaskCompletionSource<T>();
 513			Task<T> task;
 514			try {
 515				task = taskCreation(myCancellationTokenSource.Token);
 516			} catch (OperationCanceledException) {
 517				task = TaskHelper.FromCancellation<T>();
 518			} catch (Exception ex) {
 519				task = TaskHelper.FromException<T>(ex);
 520			}
 521			Action continuation = delegate {
 522				try {
 523					if (currentCancellationTokenSource == myCancellationTokenSource) {
 524						currentCancellationTokenSource = null;
 525						waitAdorner.Visibility = Visibility.Collapsed;
 526						progressBar.IsIndeterminate = false;
 527						var taskBar = MainWindow.Instance.TaskbarItemInfo;
 528						if (taskBar != null) {
 529							taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
 530						}
 531						if (task.IsCanceled) {
 532							AvalonEditTextOutput output = new AvalonEditTextOutput();
 533							output.WriteLine("The operation was canceled.");
 534							ShowOutput(output);
 535						}
 536						tcs.SetFromTask(task);
 537					} else {
 538						tcs.SetCanceled();
 539					}
 540				} finally {
 541					myCancellationTokenSource.Dispose();
 542				}
 543			};
 544			task.ContinueWith(delegate { Dispatcher.BeginInvoke(DispatcherPriority.Normal, continuation); });
 545			return tcs.Task;
 546		}
 547		
 548		void CancelButton_Click(object sender, RoutedEventArgs e)
 549		{
 550			if (currentCancellationTokenSource != null) {
 551				currentCancellationTokenSource.Cancel();
 552				// Don't set to null: the task still needs to produce output and hide the wait adorner
 553			}
 554		}
 555		#endregion
 556		
 557		#region ShowOutput
 558		public void ShowText(AvalonEditTextOutput textOutput)
 559		{
 560			ShowNodes(textOutput, null);
 561		}
 562
 563		public void ShowNode(AvalonEditTextOutput textOutput, ILSpyTreeNode node, IHighlightingDefinition highlighting = null)
 564		{
 565			ShowNodes(textOutput, new[] { node }, highlighting);
 566		}
 567
 568		/// <summary>
 569		/// Shows the given output in the text view.
 570		/// Cancels any currently running decompilation tasks.
 571		/// </summary>
 572		public void ShowNodes(AvalonEditTextOutput textOutput, ILSpyTreeNode[] nodes, IHighlightingDefinition highlighting = null)
 573		{
 574			// Cancel the decompilation task:
 575			if (currentCancellationTokenSource != null) {
 576				currentCancellationTokenSource.Cancel();
 577				currentCancellationTokenSource = null; // prevent canceled task from producing output
 578			}
 579			if (this.nextDecompilationRun != null) {
 580				// remove scheduled decompilation run
 581				this.nextDecompilationRun.TaskCompletionSource.TrySetCanceled();
 582				this.nextDecompilationRun = null;
 583			}
 584			if (nodes != null && string.IsNullOrEmpty(textOutput.Title))
 585				textOutput.Title = string.Join(", ", nodes.Select(n => n.Text));
 586			ShowOutput(textOutput, highlighting);
 587			decompiledNodes = nodes;
 588		}
 589		
 590		/// <summary>
 591		/// Shows the given output in the text view.
 592		/// </summary>
 593		void ShowOutput(AvalonEditTextOutput textOutput, IHighlightingDefinition highlighting = null, DecompilerTextViewState state = null)
 594		{
 595			Debug.WriteLine("Showing {0} characters of output", textOutput.TextLength);
 596			Stopwatch w = Stopwatch.StartNew();
 597
 598			ClearLocalReferenceMarks();
 599			textEditor.ScrollToHome();
 600			if (foldingManager != null) {
 601				FoldingManager.Uninstall(foldingManager);
 602				foldingManager = null;
 603			}
 604			textEditor.Document = null; // clear old document while we're changing the highlighting
 605			uiElementGenerator.UIElements = textOutput.UIElements;
 606			referenceElementGenerator.References = textOutput.References;
 607			references = textOutput.References;
 608			definitionLookup = textOutput.DefinitionLookup;
 609			textEditor.SyntaxHighlighting = highlighting;
 610			textEditor.Options.EnableEmailHyperlinks = textOutput.EnableHyperlinks;
 611			textEditor.Options.EnableHyperlinks = textOutput.EnableHyperlinks;
 612			if (activeRichTextColorizer != null)
 613				textEditor.TextArea.TextView.LineTransformers.Remove(activeRichTextColorizer);
 614			if (textOutput.HighlightingModel != null) {
 615				activeRichTextColorizer = new RichTextColorizer(textOutput.HighlightingModel);
 616				textEditor.TextArea.TextView.LineTransformers.Insert(highlighting == null ? 0 : 1, activeRichTextColorizer);
 617			}
 618			
 619			// Change the set of active element generators:
 620			foreach (var elementGenerator in activeCustomElementGenerators) {
 621				textEditor.TextArea.TextView.ElementGenerators.Remove(elementGenerator);
 622			}
 623			activeCustomElementGenerators.Clear();
 624			
 625			foreach (var elementGenerator in textOutput.elementGenerators) {
 626				textEditor.TextArea.TextView.ElementGenerators.Add(elementGenerator);
 627				activeCustomElementGenerators.Add(elementGenerator);
 628			}
 629			
 630			Debug.WriteLine("  Set-up: {0}", w.Elapsed); w.Restart();
 631			textEditor.Document = textOutput.GetDocument();
 632			Debug.WriteLine("  Assigning document: {0}", w.Elapsed); w.Restart();
 633			if (textOutput.Foldings.Count > 0) {
 634				if (state != null) {
 635					state.RestoreFoldings(textOutput.Foldings);
 636					textEditor.ScrollToVerticalOffset(state.VerticalOffset);
 637					textEditor.ScrollToHorizontalOffset(state.HorizontalOffset);
 638				}
 639				foldingManager = FoldingManager.Install(textEditor.TextArea);
 640				foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1);
 641				Debug.WriteLine("  Updating folding: {0}", w.Elapsed); w.Restart();
 642			} else if (highlighting?.Name == "XML") {
 643				foldingManager = FoldingManager.Install(textEditor.TextArea);
 644				var foldingStrategy = new XmlFoldingStrategy();
 645				foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document);
 646				Debug.WriteLine("  Updating folding: {0}", w.Elapsed); w.Restart();
 647			}
 648
 649			if (this.DataContext is PaneModel model) {
 650				model.Title = textOutput.Title;
 651			}
 652			currentAddress = textOutput.Address;
 653		}
 654		#endregion
 655		
 656		#region Decompile (for display)
 657		// more than 5M characters is too slow to output (when user browses treeview)
 658		public const int DefaultOutputLengthLimit  =  5000000;
 659		
 660		// more than 75M characters can get us into trouble with memory usage
 661		public const int ExtendedOutputLengthLimit = 75000000;
 662		
 663		DecompilationContext nextDecompilationRun;
 664		
 665		[Obsolete("Use DecompileAsync() instead")]
 666		public void Decompile(ILSpy.Language language, IEnumerable<ILSpyTreeNode> treeNodes, DecompilationOptions options)
 667		{
 668			DecompileAsync(language, treeNodes, options).HandleExceptions();
 669		}
 670		
 671		/// <summary>
 672		/// Starts the decompilation of the given nodes.
 673		/// The result is displayed in the text view.
 674		/// If any errors occur, the error message is displayed in the text view, and the task returned by this method completes successfully.
 675		/// If the operation is cancelled (by starting another decompilation action); the returned task is marked as cancelled.
 676		/// </summary>
 677		public Task DecompileAsync(ILSpy.Language language, IEnumerable<ILSpyTreeNode> treeNodes, DecompilationOptions options)
 678		{
 679			// Some actions like loading an assembly list cause several selection changes in the tree view,
 680			// and each of those will start a decompilation action.
 681			
 682			bool isDecompilationScheduled = this.nextDecompilationRun != null;
 683			if (this.nextDecompilationRun != null)
 684				this.nextDecompilationRun.TaskCompletionSource.TrySetCanceled();
 685			this.nextDecompilationRun = new DecompilationContext(language, treeNodes.ToArray(), options);
 686			var task = this.nextDecompilationRun.TaskCompletionSource.Task;
 687			if (!isDecompilationScheduled) {
 688				Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(
 689					delegate {
 690						var context = this.nextDecompilationRun;
 691						this.nextDecompilationRun = null;
 692						if (context != null)
 693							DoDecompile(context, DefaultOutputLengthLimit)
 694								.ContinueWith(t => context.TaskCompletionSource.SetFromTask(t)).HandleExceptions();
 695					}
 696				));
 697			}
 698			return task;
 699		}
 700		
 701		sealed class DecompilationContext
 702		{
 703			public readonly ILSpy.Language Language;
 704			public readonly ILSpyTreeNode[] TreeNodes;
 705			public readonly DecompilationOptions Options;
 706			public readonly TaskCompletionSource<object> TaskCompletionSource = new TaskCompletionSource<object>();
 707			
 708			public DecompilationContext(ILSpy.Language language, ILSpyTreeNode[] treeNodes, DecompilationOptions options)
 709			{
 710				this.Language = language;
 711				this.TreeNodes = treeNodes;
 712				this.Options = options;
 713			}
 714		}
 715		
 716		Task DoDecompile(DecompilationContext context, int outputLengthLimit)
 717		{
 718			return RunWithCancellation(
 719				delegate (CancellationToken ct) { // creation of the background task
 720					context.Options.CancellationToken = ct;
 721					return DecompileAsync(context, outputLengthLimit);
 722				})
 723			.Then(
 724				delegate (AvalonEditTextOutput textOutput) { // handling the result
 725					ShowOutput(textOutput, context.Language.SyntaxHighlighting, context.Options.TextViewState);
 726					decompiledNodes = context.TreeNodes;
 727				})
 728			.Catch<Exception>(exception => {
 729					textEditor.SyntaxHighlighting = null;
 730					Debug.WriteLine("Decompiler crashed: " + exception.ToString());
 731					AvalonEditTextOutput output = new AvalonEditTextOutput();
 732					if (exception is OutputLengthExceededException) {
 733						WriteOutputLengthExceededMessage(output, context, outputLengthLimit == DefaultOutputLengthLimit);
 734					} else {
 735						output.WriteLine(exception.ToString());
 736					}
 737					ShowOutput(output);
 738					decompiledNodes = context.TreeNodes;
 739				});
 740		}
 741		
 742		Task<AvalonEditTextOutput> DecompileAsync(DecompilationContext context, int outputLengthLimit)
 743		{
 744			Debug.WriteLine("Start decompilation of {0} tree nodes", context.TreeNodes.Length);
 745			
 746			TaskCompletionSource<AvalonEditTextOutput> tcs = new TaskCompletionSource<AvalonEditTextOutput>();
 747			if (context.TreeNodes.Length == 0) {
 748				// If there's nothing to be decompiled, don't bother starting up a thread.
 749				// (Improves perf in some cases since we don't have to wait for the thread-pool to accept our task)
 750				tcs.SetResult(new AvalonEditTextOutput());
 751				return tcs.Task;
 752			}
 753			
 754			Thread thread = new Thread(new ThreadStart(
 755				delegate {
 756					try {
 757						AvalonEditTextOutput textOutput = new AvalonEditTextOutput();
 758						textOutput.LengthLimit = outputLengthLimit;
 759						DecompileNodes(context, textOutput);
 760						textOutput.PrepareDocument();
 761						tcs.SetResult(textOutput);
 762					} catch (OperationCanceledException) {
 763						tcs.SetCanceled();
 764					} catch (Exception ex) {
 765						tcs.SetException(ex);
 766					}
 767				}));
 768			thread.Start();
 769			return tcs.Task;
 770		}
 771		
 772		void DecompileNodes(DecompilationContext context, ITextOutput textOutput)
 773		{
 774			var nodes = context.TreeNodes;
 775			if (textOutput is ISmartTextOutput smartTextOutput) {
 776				smartTextOutput.Title = string.Join(", ", nodes.Select(n => n.Text));
 777			}
 778			for (int i = 0; i < nodes.Length; i++) {
 779				if (i > 0)
 780					textOutput.WriteLine();
 781				
 782				context.Options.CancellationToken.ThrowIfCancellationRequested();
 783				nodes[i].Decompile(context.Language, textOutput, context.Options);
 784			}
 785		}
 786		#endregion
 787		
 788		#region WriteOutputLengthExceededMessage
 789		/// <summary>
 790		/// Creates a message that the decompiler output was too long.
 791		/// The message contains buttons that allow re-trying (with larger limit) or saving to a file.
 792		/// </summary>
 793		void WriteOutputLengthExceededMessage(ISmartTextOutput output, DecompilationContext context, bool wasNormalLimit)
 794		{
 795			if (wasNormalLimit) {
 796				output.WriteLine("You have selected too much code for it to be displayed automatically.");
 797			} else {
 798				output.WriteLine("You have selected too much code; it cannot be displayed here.");
 799			}
 800			output.WriteLine();
 801			if (wasNormalLimit) {
 802				output.AddButton(
 803					Images.ViewCode, Properties.Resources.DisplayCode,
 804					delegate {
 805						DoDecompile(context, ExtendedOutputLengthLimit).HandleExceptions();
 806					});
 807				output.WriteLine();
 808			}
 809			
 810			output.AddButton(
 811				Images.Save, Properties.Resources.SaveCode,
 812				delegate {
 813					SaveToDisk(context.Language, context.TreeNodes, context.Options);
 814				});
 815			output.WriteLine();
 816		}
 817		#endregion
 818
 819		#region JumpToReference
 820		/// <summary>
 821		/// Jumps to the definition referred to by the <see cref="ReferenceSegment"/>.
 822		/// </summary>
 823		internal void JumpToReference(ReferenceSegment referenceSegment)
 824		{
 825			object reference = referenceSegment.Reference;
 826			if (referenceSegment.IsLocal) {
 827				ClearLocalReferenceMarks();
 828				if (references != null) {
 829					foreach (var r in references) {
 830						if (reference.Equals(r.Reference)) {
 831							var mark = textMarkerService.Create(r.StartOffset, r.Length);
 832							mark.BackgroundColor = r.IsDefinition ? Colors.LightSeaGreen : Colors.GreenYellow;
 833							localReferenceMarks.Add(mark);
 834						}
 835					}
 836				}
 837				return;
 838			}
 839			if (definitionLookup != null) {
 840				int pos = definitionLookup.GetDefinitionPosition(reference);
 841				if (pos >= 0) {
 842					textEditor.TextArea.Focus();
 843					textEditor.Select(pos, 0);
 844					textEditor.ScrollTo(textEditor.TextArea.Caret.Line, textEditor.TextArea.Caret.Column);
 845					Dispatcher.Invoke(DispatcherPriority.Background, new Action(
 846						delegate {
 847							CaretHighlightAdorner.DisplayCaretHighlightAnimation(textEditor.TextArea);
 848						}));
 849					return;
 850				}
 851			}
 852			MainWindow.Instance.JumpToReference(reference);
 853		}
 854
 855		Point? mouseDownPos;
 856
 857		void TextAreaMouseDown(object sender, MouseButtonEventArgs e)
 858		{
 859			mouseDownPos = e.GetPosition(this);
 860		}
 861		
 862		void TextAreaMouseUp(object sender, MouseButtonEventArgs e)
 863		{
 864			if (mouseDownPos == null)
 865				return;
 866			Vector dragDistance = e.GetPosition(this) - mouseDownPos.Value;
 867			if (Math.Abs(dragDistance.X) < SystemParameters.MinimumHorizontalDragDistance
 868				&& Math.Abs(dragDistance.Y) < SystemParameters.MinimumVerticalDragDistance
 869				&& e.ChangedButton == MouseButton.Left)
 870			{
 871				// click without moving mouse
 872				var referenceSegment = GetReferenceSegmentAtMousePosition();
 873				if (referenceSegment == null) {
 874					ClearLocalReferenceMarks();
 875				} else if (referenceSegment.IsLocal || !referenceSegment.IsDefinition) {
 876					textEditor.TextArea.ClearSelection();
 877					// cancel mouse selection to avoid AvalonEdit selecting between the new
 878					// cursor position and the mouse position.
 879					textEditor.TextArea.MouseSelectionMode = MouseSelectionMode.None;
 880
 881					JumpToReference(referenceSegment);
 882				}
 883			}
 884		}
 885		
 886		void ClearLocalReferenceMarks()
 887		{
 888			foreach (var mark in localReferenceMarks) {
 889				textMarkerService.Remove(mark);
 890			}
 891			localReferenceMarks.Clear();
 892		}
 893		
 894		/// <summary>
 895		/// Filters all ReferenceSegments that are no real links.
 896		/// </summary>
 897		bool IsLink(ReferenceSegment referenceSegment)
 898		{
 899			return referenceSegment.IsLocal || !referenceSegment.IsDefinition;
 900		}
 901		#endregion
 902		
 903		#region SaveToDisk
 904		/// <summary>
 905		/// Shows the 'save file dialog', prompting the user to save the decompiled nodes to disk.
 906		/// </summary>
 907		public void SaveToDisk(ILSpy.Language language, IEnumerable<ILSpyTreeNode> treeNodes, DecompilationOptions options)
 908		{
 909			if (!treeNodes.Any())
 910				return;
 911			
 912			SaveFileDialog dlg = new SaveFileDialog();
 913			dlg.DefaultExt = language.FileExtension;
 914			dlg.Filter = language.Name + "|*" + language.FileExtension + Properties.Resources.AllFiles;
 915			dlg.FileName = CleanUpName(treeNodes.First().ToString()) + language.FileExtension;
 916			if (dlg.ShowDialog() == true) {
 917				SaveToDisk(new DecompilationContext(language, treeNodes.ToArray(), options), dlg.FileName);
 918			}
 919		}
 920		
 921		public void SaveToDisk(ILSpy.Language language, IEnumerable<ILSpyTreeNode> treeNodes, DecompilationOptions options, string fileName)
 922		{
 923			SaveToDisk(new DecompilationContext(language, treeNodes.ToArray(), options), fileName);
 924		}
 925		
 926		/// <summary>
 927		/// Starts the decompilation of the given nodes.
 928		/// The result will be saved to the given file name.
 929		/// </summary>
 930		void SaveToDisk(DecompilationContext context, string fileName)
 931		{
 932			RunWithCancellation(
 933				delegate (CancellationToken ct) {
 934					context.Options.CancellationToken = ct;
 935					return SaveToDiskAsync(context, fileName);
 936				})
 937				.Then(output => ShowOutput(output))
 938				.Catch((Exception ex) => {
 939					textEditor.SyntaxHighlighting = null;
 940					Debug.WriteLine("Decompiler crashed: " + ex.ToString());
 941					// Unpack aggregate exceptions as long as there's only a single exception:
 942					// (assembly load errors might produce nested aggregate exceptions)
 943					AvalonEditTextOutput output = new AvalonEditTextOutput();
 944					output.WriteLine(ex.ToString());
 945					ShowOutput(output);
 946				}).HandleExceptions();
 947		}
 948
 949		Task<AvalonEditTextOutput> SaveToDiskAsync(DecompilationContext context, string fileName)
 950		{
 951			TaskCompletionSource<AvalonEditTextOutput> tcs = new TaskCompletionSource<AvalonEditTextOutput>();
 952			Thread thread = new Thread(new ThreadStart(
 953				delegate {
 954					try {
 955						context.Options.EscapeInvalidIdentifiers = true;
 956						Stopwatch stopwatch = new Stopwatch();
 957						stopwatch.Start();
 958						using (StreamWriter w = new StreamWriter(fileName)) {
 959							try {
 960								DecompileNodes(context, new PlainTextOutput(w));
 961							} catch (OperationCanceledException) {
 962								w.WriteLine();
 963								w.WriteLine("Decompiled was cancelled.");
 964								throw;
 965							}
 966						}
 967						stopwatch.Stop();
 968						AvalonEditTextOutput output = new AvalonEditTextOutput();
 969						output.WriteLine("Decompilation complete in " + stopwatch.Elapsed.TotalSeconds.ToString("F1") + " seconds.");
 970						output.WriteLine();
 971						output.AddButton(null, Properties.Resources.OpenExplorer, delegate { Process.Start("explorer", "/select,\"" + fileName + "\""); });
 972						output.WriteLine();
 973						tcs.SetResult(output);
 974					} catch (OperationCanceledException) {
 975						tcs.SetCanceled();
 976					} catch (Exception ex) {
 977						tcs.SetException(ex);
 978					}
 979				}));
 980			thread.Start();
 981			return tcs.Task;
 982		}
 983		
 984		/// <summary>
 985		/// Cleans up a node name for use as a file name.
 986		/// </summary>
 987		internal static string CleanUpName(string text)
 988		{
 989			return WholeProjectDecompiler.CleanUpFileName(text);
 990		}
 991		#endregion
 992
 993		internal ReferenceSegment GetReferenceSegmentAtMousePosition()
 994		{
 995			if (referenceElementGenerator.References == null)
 996				return null;
 997			TextViewPosition? position = GetPositionFromMousePosition();
 998			if (position == null)
 999				return null;
1000			int offset = textEditor.Document.GetOffset(position.Value.Location);
1001			return referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault();
1002		}
1003		
1004		internal TextViewPosition? GetPositionFromMousePosition()
1005		{
1006			var position = textEditor.TextArea.TextView.GetPosition(Mouse.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset);
1007			if (position == null)
1008				return null;
1009			var lineLength = textEditor.Document.GetLineByNumber(position.Value.Line).Length + 1;
1010			if (position.Value.Column == lineLength)
1011				return null;
1012			return position;
1013		}
1014		
1015		public DecompilerTextViewState GetState()
1016		{
1017			if (decompiledNodes == null && currentAddress == null)
1018				return null;
1019
1020			var state = new DecompilerTextViewState();
1021			if (foldingManager != null)
1022				state.SaveFoldingsState(foldingManager.AllFoldings);
1023			state.VerticalOffset = textEditor.VerticalOffset;
1024			state.HorizontalOffset = textEditor.HorizontalOffset;
1025			state.DecompiledNodes = decompiledNodes == null ? null : new HashSet<ILSpyTreeNode>(decompiledNodes);
1026			state.ViewedUri = currentAddress;
1027			return state;
1028		}
1029
1030		ViewState IHaveState.GetState() => GetState();
1031
1032		public void Dispose()
1033		{
1034			DisplaySettingsPanel.CurrentDisplaySettings.PropertyChanged -= CurrentDisplaySettings_PropertyChanged;
1035		}
1036		
1037		#region Unfold
1038		public void UnfoldAndScroll(int lineNumber)
1039		{
1040			if (lineNumber <= 0 || lineNumber > textEditor.Document.LineCount)
1041				return;
1042			
1043			var line = textEditor.Document.GetLineByNumber(lineNumber);
1044			
1045			// unfold
1046			var foldings = foldingManager.GetFoldingsContaining(line.Offset);
1047			if (foldings != null) {
1048				foreach (var folding in foldings) {
1049					if (folding.IsFolded) {
1050						folding.IsFolded = false;
1051					}
1052				}
1053			}
1054			// scroll to
1055			textEditor.ScrollTo(lineNumber, 0);
1056		}
1057		
1058		public FoldingManager FoldingManager
1059		{
1060			get {
1061				return foldingManager;
1062			}
1063		}
1064		#endregion
1065	}
1066
1067	[DebuggerDisplay("Nodes = {DecompiledNodes}, ViewedUri = {ViewedUri}")]
1068	public class ViewState : IEquatable<ViewState>
1069	{
1070		public HashSet<ILSpyTreeNode> DecompiledNodes;
1071		public Uri ViewedUri;
1072
1073		public virtual bool Equals(ViewState other)
1074		{
1075			return other != null
1076				&& ViewedUri == other.ViewedUri
1077				&& (DecompiledNodes == other.DecompiledNodes || DecompiledNodes?.SetEquals(other.DecompiledNodes) == true);
1078		}
1079	}
1080	
1081	public class DecompilerTextViewState : ViewState
1082	{
1083		private List<Tuple<int, int>> ExpandedFoldings;
1084		private int FoldingsChecksum;
1085		public double VerticalOffset;
1086		public double HorizontalOffset;
1087
1088		public void SaveFoldingsState(IEnumerable<FoldingSection> foldings)
1089		{
1090			ExpandedFoldings = foldings.Where(f => !f.IsFolded).Select(f => Tuple.Create(f.StartOffset, f.EndOffset)).ToList();
1091			FoldingsChecksum = unchecked(foldings.Select(f => f.StartOffset * 3 - f.EndOffset).DefaultIfEmpty().Aggregate((a, b) => a + b));
1092		}
1093
1094		internal void RestoreFoldings(List<NewFolding> list)
1095		{
1096			var checksum = unchecked(list.Select(f => f.StartOffset * 3 - f.EndOffset).DefaultIfEmpty().Aggregate((a, b) => a + b));
1097			if (FoldingsChecksum == checksum)
1098				foreach (var folding in list)
1099					folding.DefaultClosed = !ExpandedFoldings.Any(f => f.Item1 == folding.StartOffset && f.Item2 == folding.EndOffset);
1100		}
1101
1102		public override bool Equals(ViewState other)
1103		{
1104			if (other is DecompilerTextViewState vs) {
1105				return base.Equals(vs)
1106					&& FoldingsChecksum == vs.FoldingsChecksum
1107					&& VerticalOffset == vs.VerticalOffset
1108					&& HorizontalOffset == vs.HorizontalOffset;
1109			}
1110			return false;
1111		}
1112	}
1113}