source: branches/CodeEditor/HeuristicLab.CodeEditor/3.4/LanguageFeatures/CodeCompletion/CSharp/CSharpDocumentationBuilder.cs @ 11804

Last change on this file since 11804 was 11804, checked in by jkarder, 5 years ago

#2077:

  • added fancy xml documentation
  • fixed configurations and plattforms
File size: 17.7 KB
Line 
1#region License Information
2// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5// software and associated documentation files (the "Software"), to deal in the Software
6// without restriction, including without limitation the rights to use, copy, modify, merge,
7// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8// to whom the Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all copies or
11// substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18// DEALINGS IN THE SOFTWARE.
19
20/* HeuristicLab
21 * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
22 *
23 * This file is part of HeuristicLab.
24 *
25 * HeuristicLab is free software: you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation, either version 3 of the License, or
28 * (at your option) any later version.
29 *
30 * HeuristicLab is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33 * GNU General Public License for more details.
34 *
35 * You should have received a copy of the GNU General Public License
36 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
37 */
38#endregion
39
40using System;
41using System.Collections.Generic;
42using System.Diagnostics;
43using System.Linq;
44using System.Text;
45using System.Windows;
46using System.Windows.Documents;
47using System.Windows.Media;
48using ICSharpCode.AvalonEdit.Highlighting;
49using ICSharpCode.AvalonEdit.Utils;
50using ICSharpCode.NRefactory.Editor;
51using ICSharpCode.NRefactory.TypeSystem;
52using ICSharpCode.NRefactory.Xml;
53
54namespace HeuristicLab.CodeEditor {
55  /// <summary>
56  /// Builds a FlowDocument for XML documentation.
57  /// </summary>
58  public class CSharpDocumentationBuilder {
59    const string SyntaxHighlighting = "C#";
60    const string FontFamily = "Consolas";
61    const double FontSize = 13.0;
62    const string MsdnLibraryUrl = "http://msdn.microsoft.com/library/";
63    readonly FlowDocument flowDocument;
64    readonly IAmbience ambience;
65    BlockCollection blockCollection;
66    InlineCollection inlineCollection;
67
68    public CSharpDocumentationBuilder(IAmbience ambience) {
69      this.ambience = ambience;
70      this.flowDocument = new FlowDocument();
71      this.blockCollection = flowDocument.Blocks;
72
73      this.ShowSummary = true;
74      this.ShowAllParameters = true;
75      this.ShowReturns = true;
76      this.ShowThreadSafety = true;
77      this.ShowExceptions = true;
78      this.ShowTypeParameters = true;
79
80      this.ShowExample = true;
81      this.ShowPreliminary = true;
82      this.ShowSeeAlso = true;
83      this.ShowValue = true;
84      this.ShowPermissions = true;
85      this.ShowRemarks = true;
86    }
87
88    public FlowDocument CreateFlowDocument() {
89      FlushAddedText(true);
90      flowDocument.FontSize = FontSize;
91      return flowDocument;
92    }
93
94    public bool ShowExceptions { get; set; }
95    public bool ShowPermissions { get; set; }
96    public bool ShowExample { get; set; }
97    public bool ShowPreliminary { get; set; }
98    public bool ShowRemarks { get; set; }
99    public bool ShowSummary { get; set; }
100    public bool ShowReturns { get; set; }
101    public bool ShowSeeAlso { get; set; }
102    public bool ShowThreadSafety { get; set; }
103    public bool ShowTypeParameters { get; set; }
104    public bool ShowValue { get; set; }
105    public bool ShowAllParameters { get; set; }
106
107    /// <summary>
108    /// Gets/Sets the name of the parameter that should be shown.
109    /// </summary>
110    public string ParameterName { get; set; }
111
112    public void AddDocumentationElement(XmlDocumentationElement element) {
113      if (element == null)
114        throw new ArgumentNullException("element");
115      if (element.IsTextNode) {
116        AddText(element.TextContent);
117        return;
118      }
119      switch (element.Name) {
120        case "b":
121          AddSpan(new Bold(), element.Children);
122          break;
123        case "i":
124          AddSpan(new Italic(), element.Children);
125          break;
126        case "c":
127          AddSpan(new Span { FontFamily = GetCodeFont() }, element.Children);
128          break;
129        case "code":
130          AddCodeBlock(element.TextContent);
131          break;
132        case "example":
133          if (ShowExample)
134            AddSection("Example: ", element.Children);
135          break;
136        case "exception":
137          if (ShowExceptions)
138            AddException(element.ReferencedEntity, element.Children);
139          break;
140        case "list":
141          AddList(element.GetAttribute("type"), element.Children);
142          break;
143        //case "note":
144        //  throw new NotImplementedException();
145        case "para":
146          AddParagraph(new Paragraph { Margin = new Thickness(0, 5, 0, 5) }, element.Children);
147          break;
148        case "param":
149          if (ShowAllParameters || (ParameterName != null && ParameterName == element.GetAttribute("name")))
150            AddParam(element.GetAttribute("name"), element.Children);
151          break;
152        case "paramref":
153          AddParamRef(element.GetAttribute("name"));
154          break;
155        case "permission":
156          if (ShowPermissions)
157            AddPermission(element.ReferencedEntity, element.Children);
158          break;
159        case "preliminary":
160          if (ShowPreliminary)
161            AddPreliminary(element.Children);
162          break;
163        case "remarks":
164          if (ShowRemarks)
165            AddSection("Remarks: ", element.Children);
166          break;
167        case "returns":
168          if (ShowReturns)
169            AddSection("Returns: ", element.Children);
170          break;
171        case "see":
172          AddSee(element);
173          break;
174        case "seealso":
175          if (inlineCollection != null)
176            AddSee(element);
177          else if (ShowSeeAlso)
178            AddSection(new Run("See also: "), () => AddSee(element));
179          break;
180        case "summary":
181          if (ShowSummary)
182            AddSection("Summary: ", element.Children);
183          break;
184        case "threadsafety":
185          if (ShowThreadSafety)
186            AddThreadSafety(ParseBool(element.GetAttribute("static")), ParseBool(element.GetAttribute("instance")), element.Children);
187          break;
188        case "typeparam":
189          if (ShowTypeParameters)
190            AddSection("Type parameter " + element.GetAttribute("name") + ": ", element.Children);
191          break;
192        case "typeparamref":
193          AddText(element.GetAttribute("name"));
194          break;
195        case "value":
196          if (ShowValue)
197            AddSection("Value: ", element.Children);
198          break;
199        case "exclude":
200        case "filterpriority":
201        case "overloads":
202          // ignore children
203          break;
204        case "br":
205          AddLineBreak();
206          break;
207        default:
208          foreach (var child in element.Children)
209            AddDocumentationElement(child);
210          break;
211      }
212    }
213
214    void AddList(string type, IEnumerable<XmlDocumentationElement> items) {
215      List list = new List();
216      AddBlock(list);
217      list.Margin = new Thickness(0, 5, 0, 5);
218      if (type == "number")
219        list.MarkerStyle = TextMarkerStyle.Decimal;
220      else if (type == "bullet")
221        list.MarkerStyle = TextMarkerStyle.Disc;
222      var oldBlockCollection = blockCollection;
223      try {
224        foreach (var itemElement in items) {
225          if (itemElement.Name == "listheader" || itemElement.Name == "item") {
226            ListItem item = new ListItem();
227            blockCollection = item.Blocks;
228            inlineCollection = null;
229            foreach (var prop in itemElement.Children) {
230              AddDocumentationElement(prop);
231            }
232            FlushAddedText(false);
233            list.ListItems.Add(item);
234          }
235        }
236      } finally {
237        blockCollection = oldBlockCollection;
238      }
239    }
240
241    public void AddCodeBlock(string textContent, bool keepLargeMargin = false) {
242      var document = new ReadOnlyDocument(textContent);
243      var highlightingDefinition = HighlightingManager.Instance.GetDefinition(SyntaxHighlighting);
244
245      var block = DocumentPrinter.ConvertTextDocumentToBlock(document, highlightingDefinition);
246      block.FontFamily = GetCodeFont();
247      if (!keepLargeMargin)
248        block.Margin = new Thickness(0, 6, 0, 6);
249      AddBlock(block);
250    }
251
252    public void AddSignatureBlock(string signature, int currentParameterOffset, int currentParameterLength, string currentParameterName) {
253      ParameterName = currentParameterName;
254      var document = new ReadOnlyDocument(signature);
255      var highlightingDefinition = HighlightingManager.Instance.GetDefinition(SyntaxHighlighting);
256
257      var richText = DocumentPrinter.ConvertTextDocumentToRichText(document, highlightingDefinition).ToRichTextModel();
258      richText.SetFontWeight(currentParameterOffset, currentParameterLength, FontWeights.Bold);
259      var block = new Paragraph();
260      block.Inlines.AddRange(richText.CreateRuns(document));
261      block.FontFamily = GetCodeFont();
262      block.TextAlignment = TextAlignment.Left;
263      AddBlock(block);
264    }
265
266    bool? ParseBool(string input) {
267      bool result;
268      if (bool.TryParse(input, out result))
269        return result;
270      else
271        return null;
272    }
273
274    void AddThreadSafety(bool? staticThreadSafe, bool? instanceThreadSafe, IEnumerable<XmlDocumentationElement> children) {
275      AddSection(
276        new Run("Thread-safety: "),
277        delegate {
278          if (staticThreadSafe == true)
279            AddText("Any public static members of this type are thread safe. ");
280          else if (staticThreadSafe == false)
281            AddText("The static members of this type are not thread safe. ");
282
283          if (instanceThreadSafe == true)
284            AddText("Any public instance members of this type are thread safe. ");
285          else if (instanceThreadSafe == false)
286            AddText("Any instance members are not guaranteed to be thread safe. ");
287
288          foreach (var child in children)
289            AddDocumentationElement(child);
290        });
291    }
292
293    FontFamily GetCodeFont() {
294      return new FontFamily(FontFamily);
295    }
296
297    void AddException(IEntity referencedEntity, IList<XmlDocumentationElement> children) {
298      Span span = new Span();
299      if (referencedEntity != null)
300        span.Inlines.Add(ConvertReference(referencedEntity));
301      else
302        span.Inlines.Add("Exception");
303      span.Inlines.Add(": ");
304      AddSection(span, children);
305    }
306
307
308    void AddPermission(IEntity referencedEntity, IList<XmlDocumentationElement> children) {
309      Span span = new Span();
310      span.Inlines.Add("Permission");
311      if (referencedEntity != null) {
312        span.Inlines.Add(" ");
313        span.Inlines.Add(ConvertReference(referencedEntity));
314      }
315      span.Inlines.Add(": ");
316      AddSection(span, children);
317    }
318
319    Inline ConvertReference(IEntity referencedEntity) {
320      var h = new Hyperlink(new Run(ambience.ConvertSymbol(referencedEntity)));
321      h.Click += CreateNavigateOnClickHandler(referencedEntity);
322      return h;
323    }
324
325    void AddParam(string name, IEnumerable<XmlDocumentationElement> children) {
326      Span span = new Span();
327      span.Inlines.Add(new Run(name ?? string.Empty) { FontStyle = FontStyles.Italic });
328      span.Inlines.Add(": ");
329      AddSection(span, children);
330    }
331
332    void AddParamRef(string name) {
333      if (name != null) {
334        AddInline(new Run(name) { FontStyle = FontStyles.Italic });
335      }
336    }
337
338    void AddPreliminary(IEnumerable<XmlDocumentationElement> children) {
339      if (children.Any()) {
340        foreach (var child in children)
341          AddDocumentationElement(child);
342      } else {
343        AddText("[This is preliminary documentation and subject to change.]");
344      }
345    }
346
347    void AddSee(XmlDocumentationElement element) {
348      IEntity referencedEntity = element.ReferencedEntity;
349      if (referencedEntity != null) {
350        if (element.Children.Any()) {
351          Hyperlink link = new Hyperlink();
352          link.Click += CreateNavigateOnClickHandler(referencedEntity);
353          AddSpan(link, element.Children);
354        } else {
355          AddInline(ConvertReference(referencedEntity));
356        }
357      } else if (element.GetAttribute("langword") != null) {
358        AddInline(new Run(element.GetAttribute("langword")) { FontFamily = GetCodeFont() });
359      } else if (element.GetAttribute("href") != null) {
360        Uri uri;
361        if (Uri.TryCreate(element.GetAttribute("href"), UriKind.Absolute, out uri)) {
362          if (element.Children.Any()) {
363            AddSpan(new Hyperlink { NavigateUri = uri }, element.Children);
364          } else {
365            AddInline(new Hyperlink(new Run(element.GetAttribute("href"))) { NavigateUri = uri });
366          }
367        }
368      } else {
369        // Invalid reference: print the cref value
370        AddText(element.GetAttribute("cref"));
371      }
372    }
373
374    RoutedEventHandler CreateNavigateOnClickHandler(IEntity referencedEntity) {
375      return delegate(object sender, RoutedEventArgs e) {
376        Process.Start(MsdnLibraryUrl + referencedEntity.FullName);
377        e.Handled = true;
378      };
379    }
380
381    void AddSection(string title, IEnumerable<XmlDocumentationElement> children) {
382      AddSection(new Run(title), children);
383    }
384
385    void AddSection(Inline title, IEnumerable<XmlDocumentationElement> children) {
386      AddSection(
387        title, delegate {
388        foreach (var child in children)
389          AddDocumentationElement(child);
390      });
391    }
392
393    void AddSection(Inline title, Action addChildren) {
394      var section = new Section();
395      AddBlock(section);
396      var oldBlockCollection = blockCollection;
397      try {
398        blockCollection = section.Blocks;
399        inlineCollection = null;
400
401        if (title != null)
402          AddInline(new Bold(title));
403
404        addChildren();
405        FlushAddedText(false);
406      } finally {
407        blockCollection = oldBlockCollection;
408        inlineCollection = null;
409      }
410    }
411
412    void AddParagraph(Paragraph para, IEnumerable<XmlDocumentationElement> children) {
413      AddBlock(para);
414      try {
415        inlineCollection = para.Inlines;
416
417        foreach (var child in children)
418          AddDocumentationElement(child);
419        FlushAddedText(false);
420      } finally {
421        inlineCollection = null;
422      }
423    }
424
425    void AddSpan(Span span, IEnumerable<XmlDocumentationElement> children) {
426      AddInline(span);
427      var oldInlineCollection = inlineCollection;
428      try {
429        inlineCollection = span.Inlines;
430        foreach (var child in children)
431          AddDocumentationElement(child);
432        FlushAddedText(false);
433      } finally {
434        inlineCollection = oldInlineCollection;
435      }
436    }
437
438    public void AddInline(Inline inline) {
439      FlushAddedText(false);
440      if (inlineCollection == null) {
441        var para = new Paragraph();
442        para.Margin = new Thickness(0, 0, 0, 5);
443        inlineCollection = para.Inlines;
444        AddBlock(para);
445      }
446      inlineCollection.Add(inline);
447      ignoreWhitespace = false;
448    }
449
450    public void AddBlock(Block block) {
451      FlushAddedText(true);
452      blockCollection.Add(block);
453    }
454
455    StringBuilder addedText = new StringBuilder();
456    bool ignoreWhitespace;
457
458    public void AddLineBreak() {
459      TrimEndOfAddedText();
460      addedText.AppendLine();
461      ignoreWhitespace = true;
462    }
463
464    public void AddText(string textContent) {
465      if (string.IsNullOrEmpty(textContent))
466        return;
467      for (int i = 0; i < textContent.Length; i++) {
468        char c = textContent[i];
469        if (c == '\n' && IsEmptyLineBefore(textContent, i)) {
470          AddLineBreak(); // empty line -> line break
471        } else if (char.IsWhiteSpace(c)) {
472          // any whitespace sequence gets converted to a single space (like HTML)
473          if (!ignoreWhitespace) {
474            addedText.Append(' ');
475            ignoreWhitespace = true;
476          }
477        } else {
478          addedText.Append(c);
479          ignoreWhitespace = false;
480        }
481      }
482    }
483
484    bool IsEmptyLineBefore(string text, int i) {
485      // Skip previous whitespace
486      do {
487        i--;
488      } while (i >= 0 && (text[i] == ' ' || text[i] == '\r'));
489      // Check if previous non-whitespace char is \n
490      return i >= 0 && text[i] == '\n';
491    }
492
493    void TrimEndOfAddedText() {
494      while (addedText.Length > 0 && addedText[addedText.Length - 1] == ' ') {
495        addedText.Length--;
496      }
497    }
498
499    void FlushAddedText(bool trim) {
500      if (trim) // trim end of current text element
501        TrimEndOfAddedText();
502      if (addedText.Length == 0)
503        return;
504      string text = addedText.ToString();
505      addedText.Length = 0;
506      AddInline(new Run(text));
507      ignoreWhitespace = trim; // trim start of next text element
508    }
509  }
510}
Note: See TracBrowser for help on using the repository browser.