// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Text.RegularExpressions; using ICSharpCode.AvalonEdit.Utils; namespace ICSharpCode.AvalonEdit.Rendering { // This class is public because it can be used as a base class for custom links. /// /// Detects hyperlinks and makes them clickable. /// /// /// This element generator can be easily enabled and configured using the /// . /// public class LinkElementGenerator : VisualLineElementGenerator, IBuiltinElementGenerator { // a link starts with a protocol (or just with www), followed by 0 or more 'link characters', followed by a link end character // (this allows accepting punctuation inside links but not at the end) internal readonly static Regex defaultLinkRegex = new Regex(@"\b(https?://|ftp://|www\.)[\w\d\._/\-~%@()+:?&=#!]*[\w\d/]"); // try to detect email addresses internal readonly static Regex defaultMailRegex = new Regex(@"\b[\w\d\.\-]+\@[\w\d\.\-]+\.[a-z]{2,6}\b"); readonly Regex linkRegex; /// /// Gets/Sets whether the user needs to press Control to click the link. /// The default value is true. /// public bool RequireControlModifierForClick { get; set; } /// /// Creates a new LinkElementGenerator. /// public LinkElementGenerator() { this.linkRegex = defaultLinkRegex; this.RequireControlModifierForClick = true; } /// /// Creates a new LinkElementGenerator using the specified regex. /// protected LinkElementGenerator(Regex regex) : this() { if (regex == null) throw new ArgumentNullException("regex"); this.linkRegex = regex; } void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options) { this.RequireControlModifierForClick = options.RequireControlModifierForHyperlinkClick; } Match GetMatch(int startOffset, out int matchOffset) { int endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; StringSegment relevantText = CurrentContext.GetText(startOffset, endOffset - startOffset); Match m = linkRegex.Match(relevantText.Text, relevantText.Offset, relevantText.Count); matchOffset = m.Success ? m.Index - relevantText.Offset + startOffset : -1; return m; } /// public override int GetFirstInterestedOffset(int startOffset) { int matchOffset; GetMatch(startOffset, out matchOffset); return matchOffset; } /// public override VisualLineElement ConstructElement(int offset) { int matchOffset; Match m = GetMatch(offset, out matchOffset); if (m.Success && matchOffset == offset) { Uri uri = GetUriFromMatch(m); if (uri == null) return null; VisualLineLinkText linkText = new VisualLineLinkText(CurrentContext.VisualLine, m.Length); linkText.NavigateUri = uri; linkText.RequireControlModifierForClick = this.RequireControlModifierForClick; return linkText; } else { return null; } } /// /// Fetches the URI from the regex match. Returns null if the URI format is invalid. /// protected virtual Uri GetUriFromMatch(Match match) { string targetUrl = match.Value; if (targetUrl.StartsWith("www.", StringComparison.Ordinal)) targetUrl = "http://" + targetUrl; if (Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute)) return new Uri(targetUrl); return null; } } // This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions. /// /// Detects e-mail addresses and makes them clickable. /// /// /// This element generator can be easily enabled and configured using the /// . /// sealed class MailLinkElementGenerator : LinkElementGenerator { /// /// Creates a new MailLinkElementGenerator. /// public MailLinkElementGenerator() : base(defaultMailRegex) { } /// protected override Uri GetUriFromMatch(Match match) { return new Uri("mailto:" + match.Value); } } }