/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/LinkElementGenerator.cs

http://github.com/icsharpcode/ILSpy · C# · 159 lines · 82 code · 18 blank · 59 comment · 10 complexity · 32109ef2bbb09f1061f24456333ad371 MD5 · raw file

  1. // Copyright (c) 2014 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.Text.RegularExpressions;
  20. using ICSharpCode.AvalonEdit.Utils;
  21. namespace ICSharpCode.AvalonEdit.Rendering
  22. {
  23. // This class is public because it can be used as a base class for custom links.
  24. /// <summary>
  25. /// Detects hyperlinks and makes them clickable.
  26. /// </summary>
  27. /// <remarks>
  28. /// This element generator can be easily enabled and configured using the
  29. /// <see cref="TextEditorOptions"/>.
  30. /// </remarks>
  31. public class LinkElementGenerator : VisualLineElementGenerator, IBuiltinElementGenerator
  32. {
  33. // a link starts with a protocol (or just with www), followed by 0 or more 'link characters', followed by a link end character
  34. // (this allows accepting punctuation inside links but not at the end)
  35. internal readonly static Regex defaultLinkRegex = new Regex(@"\b(https?://|ftp://|www\.)[\w\d\._/\-~%@()+:?&=#!]*[\w\d/]");
  36. // try to detect email addresses
  37. internal readonly static Regex defaultMailRegex = new Regex(@"\b[\w\d\.\-]+\@[\w\d\.\-]+\.[a-z]{2,6}\b");
  38. readonly Regex linkRegex;
  39. /// <summary>
  40. /// Gets/Sets whether the user needs to press Control to click the link.
  41. /// The default value is true.
  42. /// </summary>
  43. public bool RequireControlModifierForClick { get; set; }
  44. /// <summary>
  45. /// Creates a new LinkElementGenerator.
  46. /// </summary>
  47. public LinkElementGenerator()
  48. {
  49. this.linkRegex = defaultLinkRegex;
  50. this.RequireControlModifierForClick = true;
  51. }
  52. /// <summary>
  53. /// Creates a new LinkElementGenerator using the specified regex.
  54. /// </summary>
  55. protected LinkElementGenerator(Regex regex) : this()
  56. {
  57. if (regex == null)
  58. throw new ArgumentNullException("regex");
  59. this.linkRegex = regex;
  60. }
  61. void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options)
  62. {
  63. this.RequireControlModifierForClick = options.RequireControlModifierForHyperlinkClick;
  64. }
  65. Match GetMatch(int startOffset, out int matchOffset)
  66. {
  67. int endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
  68. StringSegment relevantText = CurrentContext.GetText(startOffset, endOffset - startOffset);
  69. Match m = linkRegex.Match(relevantText.Text, relevantText.Offset, relevantText.Count);
  70. matchOffset = m.Success ? m.Index - relevantText.Offset + startOffset : -1;
  71. return m;
  72. }
  73. /// <inheritdoc/>
  74. public override int GetFirstInterestedOffset(int startOffset)
  75. {
  76. int matchOffset;
  77. GetMatch(startOffset, out matchOffset);
  78. return matchOffset;
  79. }
  80. /// <inheritdoc/>
  81. public override VisualLineElement ConstructElement(int offset)
  82. {
  83. int matchOffset;
  84. Match m = GetMatch(offset, out matchOffset);
  85. if (m.Success && matchOffset == offset) {
  86. return ConstructElementFromMatch(m);
  87. } else {
  88. return null;
  89. }
  90. }
  91. /// <summary>
  92. /// Constructs a VisualLineElement that replaces the matched text.
  93. /// The default implementation will create a <see cref="VisualLineLinkText"/>
  94. /// based on the URI provided by <see cref="GetUriFromMatch"/>.
  95. /// </summary>
  96. protected virtual VisualLineElement ConstructElementFromMatch(Match m)
  97. {
  98. Uri uri = GetUriFromMatch(m);
  99. if (uri == null)
  100. return null;
  101. VisualLineLinkText linkText = new VisualLineLinkText(CurrentContext.VisualLine, m.Length);
  102. linkText.NavigateUri = uri;
  103. linkText.RequireControlModifierForClick = this.RequireControlModifierForClick;
  104. return linkText;
  105. }
  106. /// <summary>
  107. /// Fetches the URI from the regex match. Returns null if the URI format is invalid.
  108. /// </summary>
  109. protected virtual Uri GetUriFromMatch(Match match)
  110. {
  111. string targetUrl = match.Value;
  112. if (targetUrl.StartsWith("www.", StringComparison.Ordinal))
  113. targetUrl = "http://" + targetUrl;
  114. if (Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute))
  115. return new Uri(targetUrl);
  116. return null;
  117. }
  118. }
  119. // This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions.
  120. /// <summary>
  121. /// Detects e-mail addresses and makes them clickable.
  122. /// </summary>
  123. /// <remarks>
  124. /// This element generator can be easily enabled and configured using the
  125. /// <see cref="TextEditorOptions"/>.
  126. /// </remarks>
  127. sealed class MailLinkElementGenerator : LinkElementGenerator
  128. {
  129. /// <summary>
  130. /// Creates a new MailLinkElementGenerator.
  131. /// </summary>
  132. public MailLinkElementGenerator()
  133. : base(defaultMailRegex)
  134. {
  135. }
  136. protected override Uri GetUriFromMatch(Match match)
  137. {
  138. return new Uri("mailto:" + match.Value);
  139. }
  140. }
  141. }