diff --git a/ICSharpCode.AvalonEdit.Tests/Rendering/LinkElementGeneratorTests.cs b/ICSharpCode.AvalonEdit.Tests/Rendering/LinkElementGeneratorTests.cs new file mode 100644 index 00000000..a1951735 --- /dev/null +++ b/ICSharpCode.AvalonEdit.Tests/Rendering/LinkElementGeneratorTests.cs @@ -0,0 +1,77 @@ +using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit.Rendering; + +using NUnit.Framework; + +namespace ICSharpCode.AvalonEdit.Tests.Rendering +{ + [TestFixture] + [Apartment(System.Threading.ApartmentState.STA)] + public class LinkElementGeneratorTests + { + [Test] + public void ParsingOfCustomLinkRegexes() + { + var linkElementGenerator = new LinkElementGenerator(); + const string link = "example://mylink"; + const string input = "Visit " + link + " please"; + var textSource = GetTextSource(input); + linkElementGenerator.StartGeneration(textSource); + + var interestedOffsetBeforeCustomRegex = linkElementGenerator.GetFirstInterestedOffset(0); + Assert.That(interestedOffsetBeforeCustomRegex, Is.EqualTo(-1)); + + var optionsWithCustomLink = new TextEditorOptions { + // Check for example:// protocol instead of http:// protocol. + LinkRegex = @"\bexample://\w+\b", + }; + ((IBuiltinElementGenerator)linkElementGenerator).FetchOptions(optionsWithCustomLink); + + var interestedOffsetAfterCustomRegex = linkElementGenerator.GetFirstInterestedOffset(0); + var expectedOffset = input.IndexOf(link); + Assert.That(interestedOffsetAfterCustomRegex, Is.EqualTo(expectedOffset)); + + linkElementGenerator.FinishGeneration(); + } + + [Test] + public void ParsingOfCustomMailRegexes() + { + var linkElementGenerator = new MailLinkElementGenerator(); + const string link = "example@example.verylongtld"; + const string input = "Email " + link + " please"; + var textSource = GetTextSource(input); + linkElementGenerator.StartGeneration(textSource); + + var interestedOffsetBeforeCustomRegex = linkElementGenerator.GetFirstInterestedOffset(0); + Assert.That(interestedOffsetBeforeCustomRegex, Is.EqualTo(-1)); + + var optionsWithCustomLink = new TextEditorOptions { + // The same as the default except that TLDs can be up to 12 chars now. + MailRegex = @"\b[\w\d\.\-]+\@[\w\d\.\-]+\.[a-z]{2,12}\b", + }; + ((IBuiltinElementGenerator)linkElementGenerator).FetchOptions(optionsWithCustomLink); + + var interestedOffsetAfterCustomRegex = linkElementGenerator.GetFirstInterestedOffset(0); + var expectedOffset = input.IndexOf(link); + Assert.That(interestedOffsetAfterCustomRegex, Is.EqualTo(expectedOffset)); + + linkElementGenerator.FinishGeneration(); + } + + private static VisualLineTextSource GetTextSource(string input) + { + var textDocument = new TextDocument(input); + var textView = new TextView { + Document = textDocument, + }; + var documentLine = textDocument.Lines[0]; + var visualLine = textView.GetOrConstructVisualLine(documentLine); + var textSource = new VisualLineTextSource(visualLine) { + Document = textDocument, + TextView = textView, + }; + return textSource; + } + } +} diff --git a/ICSharpCode.AvalonEdit/Rendering/LinkElementGenerator.cs b/ICSharpCode.AvalonEdit/Rendering/LinkElementGenerator.cs index eb838fc2..1d66c528 100644 --- a/ICSharpCode.AvalonEdit/Rendering/LinkElementGenerator.cs +++ b/ICSharpCode.AvalonEdit/Rendering/LinkElementGenerator.cs @@ -41,7 +41,10 @@ public class LinkElementGenerator : VisualLineElementGenerator, IBuiltinElementG // 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; + /// + /// The regex used to parse the type of link this class represents. + /// + protected Regex linkRegex; /// /// Gets/Sets whether the user needs to press Control to click the link. @@ -71,6 +74,7 @@ protected LinkElementGenerator(Regex regex) : this() void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options) { this.RequireControlModifierForClick = options.RequireControlModifierForHyperlinkClick; + this.linkRegex = new Regex(options.LinkRegex); } Match GetMatch(int startOffset, out int matchOffset) @@ -142,7 +146,7 @@ protected virtual Uri GetUriFromMatch(Match match) /// This element generator can be easily enabled and configured using the /// . /// - sealed class MailLinkElementGenerator : LinkElementGenerator + sealed class MailLinkElementGenerator : LinkElementGenerator, IBuiltinElementGenerator { /// /// Creates a new MailLinkElementGenerator. @@ -152,6 +156,12 @@ public MailLinkElementGenerator() { } + void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options) + { + this.RequireControlModifierForClick = options.RequireControlModifierForHyperlinkClick; + this.linkRegex = new Regex(options.MailRegex); + } + protected override Uri GetUriFromMatch(Match match) { var targetUrl = "mailto:" + match.Value; diff --git a/ICSharpCode.AvalonEdit/TextEditorOptions.cs b/ICSharpCode.AvalonEdit/TextEditorOptions.cs index 66ee37f9..ed9cbdad 100644 --- a/ICSharpCode.AvalonEdit/TextEditorOptions.cs +++ b/ICSharpCode.AvalonEdit/TextEditorOptions.cs @@ -19,6 +19,9 @@ using System; using System.ComponentModel; using System.Reflection; +using System.Text.RegularExpressions; + +using ICSharpCode.AvalonEdit.Rendering; namespace ICSharpCode.AvalonEdit { @@ -166,6 +169,20 @@ public virtual bool EnableHyperlinks { } } + string linkRegex = LinkElementGenerator.defaultLinkRegex.ToString(); + /// + /// Gets/Sets the regex used to parse hyperlinks. + /// + public virtual string LinkRegex { + get { return linkRegex; } + set { + if (!Equals(linkRegex, value)) { + linkRegex = value; + OnPropertyChanged(nameof(LinkRegex)); + } + } + } + bool enableEmailHyperlinks = true; /// @@ -183,6 +200,20 @@ public virtual bool EnableEmailHyperlinks { } } + string mailRegex = LinkElementGenerator.defaultMailRegex.ToString(); + /// + /// Gets/Sets the regex used to parse mail hyperlinks. + /// + public virtual string MailRegex { + get { return mailRegex; } + set { + if (!Equals(mailRegex, value)) { + mailRegex = value; + OnPropertyChanged(nameof(MailRegex)); + } + } + } + bool requireControlModifierForHyperlinkClick = true; ///