You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

602 lines
16 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  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.Diagnostics;
  21. using System.Linq;
  22. using System.Text;
  23. using System.Windows;
  24. using System.Windows.Controls;
  25. using System.Windows.Documents;
  26. using System.Windows.Media;
  27. using System.Xml.Linq;
  28. using ICSharpCode.AvalonEdit.Document;
  29. using ICSharpCode.AvalonEdit.Highlighting;
  30. using ICSharpCode.AvalonEdit.Utils;
  31. using ICSharpCode.Decompiler.Documentation;
  32. using ICSharpCode.Decompiler.Output;
  33. using ICSharpCode.Decompiler.TypeSystem;
  34. using ICSharpCode.ILSpy.Options;
  35. namespace ICSharpCode.ILSpy.TextView
  36. {
  37. /// <summary>
  38. /// Renders XML documentation into a WPF <see cref="FlowDocument"/>.
  39. /// </summary>
  40. public class DocumentationUIBuilder
  41. {
  42. readonly IAmbience ambience;
  43. readonly IHighlightingDefinition highlightingDefinition;
  44. readonly DisplaySettings displaySettings;
  45. readonly MainWindow mainWindow;
  46. readonly FlowDocument document;
  47. BlockCollection blockCollection;
  48. InlineCollection inlineCollection;
  49. public DocumentationUIBuilder(IAmbience ambience, IHighlightingDefinition highlightingDefinition, DisplaySettings displaySettings, MainWindow mainWindow)
  50. {
  51. this.ambience = ambience;
  52. this.highlightingDefinition = highlightingDefinition;
  53. this.displaySettings = displaySettings;
  54. this.mainWindow = mainWindow;
  55. this.document = new FlowDocument();
  56. this.blockCollection = document.Blocks;
  57. this.ShowSummary = true;
  58. this.ShowAllParameters = true;
  59. this.ShowReturns = true;
  60. this.ShowThreadSafety = true;
  61. this.ShowExceptions = true;
  62. this.ShowTypeParameters = true;
  63. this.ShowExample = true;
  64. this.ShowPreliminary = true;
  65. this.ShowSeeAlso = true;
  66. this.ShowValue = true;
  67. this.ShowPermissions = true;
  68. this.ShowRemarks = true;
  69. }
  70. public FlowDocument CreateDocument()
  71. {
  72. FlushAddedText(true);
  73. return document;
  74. }
  75. public bool ShowExceptions { get; set; }
  76. public bool ShowPermissions { get; set; }
  77. public bool ShowExample { get; set; }
  78. public bool ShowPreliminary { get; set; }
  79. public bool ShowRemarks { get; set; }
  80. public bool ShowSummary { get; set; }
  81. public bool ShowReturns { get; set; }
  82. public bool ShowSeeAlso { get; set; }
  83. public bool ShowThreadSafety { get; set; }
  84. public bool ShowTypeParameters { get; set; }
  85. public bool ShowValue { get; set; }
  86. public bool ShowAllParameters { get; set; }
  87. public void AddCodeBlock(string textContent, bool keepLargeMargin = false)
  88. {
  89. var document = new TextDocument(textContent);
  90. var highlighter = new DocumentHighlighter(document, highlightingDefinition);
  91. var richText = DocumentPrinter.ConvertTextDocumentToRichText(document, highlighter).ToRichTextModel();
  92. var block = new Paragraph();
  93. block.Inlines.AddRange(richText.CreateRuns(document));
  94. block.FontFamily = GetCodeFont();
  95. if (!keepLargeMargin)
  96. block.Margin = new Thickness(0, 6, 0, 6);
  97. AddBlock(block);
  98. }
  99. public void AddSignatureBlock(string signature, RichTextModel highlighting = null)
  100. {
  101. var document = new TextDocument(signature);
  102. var richText = highlighting ?? DocumentPrinter.ConvertTextDocumentToRichText(document, new DocumentHighlighter(document, highlightingDefinition)).ToRichTextModel();
  103. var block = new Paragraph();
  104. // HACK: measure width of signature using a TextBlock
  105. // Paragraph sadly does not support TextWrapping.NoWrap
  106. var text = new TextBlock {
  107. FontFamily = GetCodeFont(),
  108. FontSize = displaySettings.SelectedFontSize,
  109. TextAlignment = TextAlignment.Left
  110. };
  111. text.Inlines.AddRange(richText.CreateRuns(document));
  112. text.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  113. this.document.MinPageWidth = Math.Min(text.DesiredSize.Width, mainWindow.ActualWidth);
  114. block.Inlines.AddRange(richText.CreateRuns(document));
  115. block.FontFamily = GetCodeFont();
  116. block.TextAlignment = TextAlignment.Left;
  117. AddBlock(block);
  118. }
  119. public void AddXmlDocumentation(string xmlDocumentation, IEntity declaringEntity, Func<string, IEntity> resolver)
  120. {
  121. if (xmlDocumentation == null)
  122. return;
  123. Debug.WriteLine(xmlDocumentation);
  124. var xml = XElement.Parse("<doc>" + xmlDocumentation + "</doc>");
  125. AddDocumentationElement(new XmlDocumentationElement(xml, declaringEntity, resolver));
  126. }
  127. /// <summary>
  128. /// Gets/Sets the name of the parameter that should be shown.
  129. /// </summary>
  130. public string ParameterName { get; set; }
  131. public void AddDocumentationElement(XmlDocumentationElement element)
  132. {
  133. if (element == null)
  134. throw new ArgumentNullException("element");
  135. if (element.IsTextNode)
  136. {
  137. AddText(element.TextContent);
  138. return;
  139. }
  140. switch (element.Name)
  141. {
  142. case "b":
  143. AddSpan(new Bold(), element.Children);
  144. break;
  145. case "i":
  146. AddSpan(new Italic(), element.Children);
  147. break;
  148. case "c":
  149. AddSpan(new Span { FontFamily = GetCodeFont() }, element.Children);
  150. break;
  151. case "code":
  152. AddCodeBlock(element.TextContent);
  153. break;
  154. case "example":
  155. if (ShowExample)
  156. AddSection("Example: ", element.Children);
  157. break;
  158. case "exception":
  159. if (ShowExceptions)
  160. AddException(element.ReferencedEntity, element.Children);
  161. break;
  162. case "list":
  163. AddList(element.GetAttribute("type"), element.Children);
  164. break;
  165. //case "note":
  166. // throw new NotImplementedException();
  167. case "para":
  168. AddParagraph(new Paragraph { Margin = new Thickness(0, 5, 0, 5) }, element.Children);
  169. break;
  170. case "param":
  171. if (ShowAllParameters || (ParameterName != null && ParameterName == element.GetAttribute("name")))
  172. AddParam(element.GetAttribute("name"), element.Children);
  173. break;
  174. case "paramref":
  175. AddParamRef(element.GetAttribute("name"));
  176. break;
  177. case "permission":
  178. if (ShowPermissions)
  179. AddPermission(element.ReferencedEntity, element.Children);
  180. break;
  181. case "preliminary":
  182. if (ShowPreliminary)
  183. AddPreliminary(element.Children);
  184. break;
  185. case "remarks":
  186. if (ShowRemarks)
  187. AddSection("Remarks: ", element.Children);
  188. break;
  189. case "returns":
  190. if (ShowReturns)
  191. AddSection("Returns: ", element.Children);
  192. break;
  193. case "see":
  194. AddSee(element);
  195. break;
  196. case "seealso":
  197. if (inlineCollection != null)
  198. AddSee(element);
  199. else if (ShowSeeAlso)
  200. AddSection(new Run("See also: "), () => AddSee(element));
  201. break;
  202. case "summary":
  203. if (ShowSummary)
  204. AddSection("Summary: ", element.Children);
  205. break;
  206. case "threadsafety":
  207. if (ShowThreadSafety)
  208. AddThreadSafety(ParseBool(element.GetAttribute("static")), ParseBool(element.GetAttribute("instance")), element.Children);
  209. break;
  210. case "typeparam":
  211. if (ShowTypeParameters)
  212. AddSection("Type parameter " + element.GetAttribute("name") + ": ", element.Children);
  213. break;
  214. case "typeparamref":
  215. AddText(element.GetAttribute("name"));
  216. break;
  217. case "value":
  218. if (ShowValue)
  219. AddSection("Value: ", element.Children);
  220. break;
  221. case "exclude":
  222. case "filterpriority":
  223. case "overloads":
  224. // ignore children
  225. break;
  226. case "br":
  227. AddLineBreak();
  228. break;
  229. default:
  230. foreach (var child in element.Children)
  231. AddDocumentationElement(child);
  232. break;
  233. }
  234. }
  235. void AddList(string type, IEnumerable<XmlDocumentationElement> items)
  236. {
  237. List list = new List();
  238. AddBlock(list);
  239. list.Margin = new Thickness(0, 5, 0, 5);
  240. if (type == "number")
  241. list.MarkerStyle = TextMarkerStyle.Decimal;
  242. else if (type == "bullet")
  243. list.MarkerStyle = TextMarkerStyle.Disc;
  244. var oldBlockCollection = blockCollection;
  245. try
  246. {
  247. foreach (var itemElement in items)
  248. {
  249. if (itemElement.Name == "listheader" || itemElement.Name == "item")
  250. {
  251. ListItem item = new ListItem();
  252. blockCollection = item.Blocks;
  253. inlineCollection = null;
  254. foreach (var prop in itemElement.Children)
  255. {
  256. AddDocumentationElement(prop);
  257. }
  258. FlushAddedText(false);
  259. list.ListItems.Add(item);
  260. }
  261. }
  262. }
  263. finally
  264. {
  265. blockCollection = oldBlockCollection;
  266. }
  267. }
  268. bool? ParseBool(string input)
  269. {
  270. if (bool.TryParse(input, out bool result))
  271. return result;
  272. else
  273. return null;
  274. }
  275. void AddThreadSafety(bool? staticThreadSafe, bool? instanceThreadSafe, IEnumerable<XmlDocumentationElement> children)
  276. {
  277. AddSection(
  278. new Run("Thread-safety: "),
  279. delegate {
  280. if (staticThreadSafe == true)
  281. AddText("Any public static members of this type are thread safe. ");
  282. else if (staticThreadSafe == false)
  283. AddText("The static members of this type are not thread safe. ");
  284. if (instanceThreadSafe == true)
  285. AddText("Any public instance members of this type are thread safe. ");
  286. else if (instanceThreadSafe == false)
  287. AddText("Any instance members are not guaranteed to be thread safe. ");
  288. foreach (var child in children)
  289. AddDocumentationElement(child);
  290. });
  291. }
  292. void AddException(IEntity referencedEntity, IList<XmlDocumentationElement> children)
  293. {
  294. Span span = new Span();
  295. if (referencedEntity != null)
  296. span.Inlines.Add(ConvertReference(referencedEntity));
  297. else
  298. span.Inlines.Add("Exception");
  299. span.Inlines.Add(": ");
  300. AddSection(span, children);
  301. }
  302. void AddPermission(IEntity referencedEntity, IList<XmlDocumentationElement> children)
  303. {
  304. Span span = new Span();
  305. span.Inlines.Add("Permission");
  306. if (referencedEntity != null)
  307. {
  308. span.Inlines.Add(" ");
  309. span.Inlines.Add(ConvertReference(referencedEntity));
  310. }
  311. span.Inlines.Add(": ");
  312. AddSection(span, children);
  313. }
  314. Inline ConvertReference(IEntity referencedEntity)
  315. {
  316. var h = new Hyperlink(new Run(ambience.ConvertSymbol(referencedEntity)));
  317. h.Click += (sender, e) => {
  318. MessageBus.Send(this, new NavigateToReferenceEventArgs(referencedEntity));
  319. };
  320. return h;
  321. }
  322. void AddParam(string name, IEnumerable<XmlDocumentationElement> children)
  323. {
  324. Span span = new Span();
  325. span.Inlines.Add(new Run(name ?? string.Empty) { FontStyle = FontStyles.Italic });
  326. span.Inlines.Add(": ");
  327. AddSection(span, children);
  328. }
  329. void AddParamRef(string name)
  330. {
  331. if (name != null)
  332. {
  333. AddInline(new Run(name) { FontStyle = FontStyles.Italic });
  334. }
  335. }
  336. void AddPreliminary(IEnumerable<XmlDocumentationElement> children)
  337. {
  338. if (children.Any())
  339. {
  340. foreach (var child in children)
  341. AddDocumentationElement(child);
  342. }
  343. else
  344. {
  345. AddText("[This is preliminary documentation and subject to change.]");
  346. }
  347. }
  348. void AddSee(XmlDocumentationElement element)
  349. {
  350. IEntity referencedEntity = element.ReferencedEntity;
  351. if (referencedEntity != null)
  352. {
  353. if (element.Children.Any())
  354. {
  355. Hyperlink link = new Hyperlink();
  356. link.Click += (sender, e) => {
  357. MessageBus.Send(this, new NavigateToReferenceEventArgs(referencedEntity));
  358. };
  359. AddSpan(link, element.Children);
  360. }
  361. else
  362. {
  363. AddInline(ConvertReference(referencedEntity));
  364. }
  365. }
  366. else if (element.GetAttribute("langword") != null)
  367. {
  368. AddInline(new Run(element.GetAttribute("langword")) { FontFamily = GetCodeFont() });
  369. }
  370. else if (element.GetAttribute("href") != null)
  371. {
  372. Uri uri;
  373. if (Uri.TryCreate(element.GetAttribute("href"), UriKind.Absolute, out uri))
  374. {
  375. if (element.Children.Any())
  376. {
  377. AddSpan(new Hyperlink { NavigateUri = uri }, element.Children);
  378. }
  379. else
  380. {
  381. AddInline(new Hyperlink(new Run(element.GetAttribute("href"))) { NavigateUri = uri });
  382. }
  383. }
  384. }
  385. else
  386. {
  387. // Invalid reference: print the cref value
  388. AddText(element.GetAttribute("cref"));
  389. }
  390. }
  391. static string GetCref(string cref)
  392. {
  393. if (cref == null || cref.Trim().Length == 0)
  394. {
  395. return "";
  396. }
  397. if (cref.Length < 2)
  398. {
  399. return cref;
  400. }
  401. if (cref.Substring(1, 1) == ":")
  402. {
  403. return cref.Substring(2, cref.Length - 2);
  404. }
  405. return cref;
  406. }
  407. FontFamily GetCodeFont()
  408. {
  409. return displaySettings.SelectedFont;
  410. }
  411. public void AddInline(Inline inline)
  412. {
  413. FlushAddedText(false);
  414. if (inlineCollection == null)
  415. {
  416. var para = new Paragraph();
  417. para.Margin = new Thickness(0, 0, 0, 5);
  418. inlineCollection = para.Inlines;
  419. AddBlock(para);
  420. }
  421. inlineCollection.Add(inline);
  422. ignoreWhitespace = false;
  423. }
  424. void AddSection(string title, IEnumerable<XmlDocumentationElement> children)
  425. {
  426. AddSection(new Run(title), children);
  427. }
  428. void AddSection(Inline title, IEnumerable<XmlDocumentationElement> children)
  429. {
  430. AddSection(
  431. title, delegate {
  432. foreach (var child in children)
  433. AddDocumentationElement(child);
  434. });
  435. }
  436. void AddSection(Inline title, Action addChildren)
  437. {
  438. var section = new Section();
  439. AddBlock(section);
  440. var oldBlockCollection = blockCollection;
  441. try
  442. {
  443. blockCollection = section.Blocks;
  444. inlineCollection = null;
  445. if (title != null)
  446. AddInline(new Bold(title));
  447. addChildren();
  448. FlushAddedText(false);
  449. }
  450. finally
  451. {
  452. blockCollection = oldBlockCollection;
  453. inlineCollection = null;
  454. }
  455. }
  456. void AddParagraph(Paragraph para, IEnumerable<XmlDocumentationElement> children)
  457. {
  458. AddBlock(para);
  459. try
  460. {
  461. inlineCollection = para.Inlines;
  462. foreach (var child in children)
  463. AddDocumentationElement(child);
  464. FlushAddedText(false);
  465. }
  466. finally
  467. {
  468. inlineCollection = null;
  469. }
  470. }
  471. void AddSpan(Span span, IEnumerable<XmlDocumentationElement> children)
  472. {
  473. AddInline(span);
  474. var oldInlineCollection = inlineCollection;
  475. try
  476. {
  477. inlineCollection = span.Inlines;
  478. foreach (var child in children)
  479. AddDocumentationElement(child);
  480. FlushAddedText(false);
  481. }
  482. finally
  483. {
  484. inlineCollection = oldInlineCollection;
  485. }
  486. }
  487. public void AddBlock(Block block)
  488. {
  489. FlushAddedText(true);
  490. blockCollection.Add(block);
  491. }
  492. StringBuilder addedText = new StringBuilder();
  493. bool ignoreWhitespace;
  494. public void AddLineBreak()
  495. {
  496. TrimEndOfAddedText();
  497. addedText.AppendLine();
  498. ignoreWhitespace = true;
  499. }
  500. public void AddText(string textContent)
  501. {
  502. if (string.IsNullOrEmpty(textContent))
  503. return;
  504. for (int i = 0; i < textContent.Length; i++)
  505. {
  506. char c = textContent[i];
  507. if (c == '\n' && IsEmptyLineBefore(textContent, i))
  508. {
  509. AddLineBreak(); // empty line -> line break
  510. }
  511. else if (char.IsWhiteSpace(c))
  512. {
  513. // any whitespace sequence gets converted to a single space (like HTML)
  514. if (!ignoreWhitespace)
  515. {
  516. addedText.Append(' ');
  517. ignoreWhitespace = true;
  518. }
  519. }
  520. else
  521. {
  522. addedText.Append(c);
  523. ignoreWhitespace = false;
  524. }
  525. }
  526. }
  527. bool IsEmptyLineBefore(string text, int i)
  528. {
  529. // Skip previous whitespace
  530. do
  531. {
  532. i--;
  533. } while (i >= 0 && (text[i] == ' ' || text[i] == '\r'));
  534. // Check if previous non-whitespace char is \n
  535. return i >= 0 && text[i] == '\n';
  536. }
  537. void TrimEndOfAddedText()
  538. {
  539. while (addedText.Length > 0 && addedText[addedText.Length - 1] == ' ')
  540. {
  541. addedText.Length--;
  542. }
  543. }
  544. void FlushAddedText(bool trim)
  545. {
  546. if (trim) // trim end of current text element
  547. TrimEndOfAddedText();
  548. if (addedText.Length == 0)
  549. return;
  550. string text = addedText.ToString();
  551. addedText.Length = 0;
  552. AddInline(new Run(text));
  553. ignoreWhitespace = trim; // trim start of next text element
  554. }
  555. }
  556. }