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