diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj index d572f6c8b0..ac84b63f8b 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj @@ -342,6 +342,7 @@ + diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/ITextRunConstructionContext.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/ITextRunConstructionContext.cs index 83a2184540..f0fd8d1be0 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/ITextRunConstructionContext.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/ITextRunConstructionContext.cs @@ -4,6 +4,7 @@ using System; using System.Windows.Media.TextFormatting; using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit.Utils; namespace ICSharpCode.AvalonEdit.Rendering { @@ -31,5 +32,16 @@ namespace ICSharpCode.AvalonEdit.Rendering /// Gets the global text run properties. /// TextRunProperties GlobalTextRunProperties { get; } + + /// + /// Gets a piece of text from the document. + /// + /// + /// This method is allowed to return a larger string than requested. + /// It does this by returning a that describes the requested segment within the returned string. + /// This method should be the preferred text access method in the text transformation pipeline, as it can avoid repeatedly allocating string instances + /// for text within the same line. + /// + StringSegment GetText(int offset, int length); } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/LinkElementGenerator.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/LinkElementGenerator.cs index 0040decd12..70d59358ae 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/LinkElementGenerator.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/LinkElementGenerator.cs @@ -2,16 +2,8 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; -using System.Diagnostics; using System.Text.RegularExpressions; -using System.Windows; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.TextFormatting; - -using ICSharpCode.AvalonEdit.Document; -using System.Windows.Navigation; +using ICSharpCode.AvalonEdit.Utils; namespace ICSharpCode.AvalonEdit.Rendering { @@ -68,8 +60,8 @@ namespace ICSharpCode.AvalonEdit.Rendering Match GetMatch(int startOffset) { int endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; - string relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset); - return linkRegex.Match(relevantText); + StringSegment relevantText = CurrentContext.GetText(startOffset, endOffset - startOffset); + return linkRegex.Match(relevantText.Text, relevantText.Offset, relevantText.Count); } /// diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs index 3d26e7c9b9..4ff5def317 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs @@ -60,10 +60,11 @@ namespace ICSharpCode.AvalonEdit.Rendering public override int GetFirstInterestedOffset(int startOffset) { DocumentLine endLine = CurrentContext.VisualLine.LastDocumentLine; - string relevantText = CurrentContext.Document.GetText(startOffset, endLine.EndOffset - startOffset); + StringSegment relevantText = CurrentContext.GetText(startOffset, endLine.EndOffset - startOffset); - for (int i = 0; i < relevantText.Length; i++) { - char c = relevantText[i]; + int endPos = relevantText.Offset + relevantText.Count; + for (int i = relevantText.Offset; i < endPos; i++) { + char c = relevantText.Text[i]; switch (c) { case ' ': if (ShowSpaces) diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineText.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineText.cs index 3cefe1e264..f5d7ae19ed 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineText.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineText.cs @@ -53,8 +53,8 @@ namespace ICSharpCode.AvalonEdit.Rendering throw new ArgumentNullException("context"); int relativeOffset = startVisualColumn - VisualColumn; - string text = context.Document.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset + relativeOffset, DocumentLength - relativeOffset); - return new TextCharacters(text, 0, text.Length, this.TextRunProperties); + StringSegment text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset + relativeOffset, DocumentLength - relativeOffset); + return new TextCharacters(text.Text, text.Offset, text.Count, this.TextRunProperties); } /// @@ -71,8 +71,8 @@ namespace ICSharpCode.AvalonEdit.Rendering throw new ArgumentNullException("context"); int relativeOffset = visualColumnLimit - VisualColumn; - string text = context.Document.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset, relativeOffset); - CharacterBufferRange range = new CharacterBufferRange(text, 0, text.Length); + StringSegment text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset, relativeOffset); + CharacterBufferRange range = new CharacterBufferRange(text.Text, text.Offset, text.Count); return new TextSpan(range.Length, new CultureSpecificCharacterBufferRange(this.TextRunProperties.CultureInfo, range)); } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs index eea016446f..375a09bf9e 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Windows.Media.TextFormatting; using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit.Utils; namespace ICSharpCode.AvalonEdit.Rendering { @@ -83,5 +84,19 @@ namespace ICSharpCode.AvalonEdit.Rendering { throw new NotSupportedException(); } + + string cachedString; + int cachedStringOffset; + + public StringSegment GetText(int offset, int length) + { + if (cachedString != null) { + if (offset >= cachedStringOffset && offset + length <= cachedStringOffset + cachedString.Length) { + return new StringSegment(cachedString, offset - cachedStringOffset, length); + } + } + cachedStringOffset = offset; + return new StringSegment(cachedString = this.Document.GetText(offset, length)); + } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/StringSegment.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/StringSegment.cs new file mode 100644 index 0000000000..8a39ea874f --- /dev/null +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/StringSegment.cs @@ -0,0 +1,107 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; + +namespace ICSharpCode.AvalonEdit.Utils +{ + /// + /// Represents a string with a segment. + /// Similar to System.ArraySegment<T>, but for strings instead of arrays. + /// + public struct StringSegment : IEquatable + { + readonly string text; + readonly int offset; + readonly int count; + + /// + /// Creates a new StringSegment. + /// + public StringSegment(string text, int offset, int count) + { + if (text == null) + throw new ArgumentNullException("text"); + if (offset < 0 || offset > text.Length) + throw new ArgumentOutOfRangeException("offset"); + if (offset + count > text.Length) + throw new ArgumentOutOfRangeException("count"); + this.text = text; + this.offset = offset; + this.count = count; + } + + /// + /// Creates a new StringSegment. + /// + public StringSegment(string text) + { + if (text == null) + throw new ArgumentNullException("text"); + this.text = text; + this.offset = 0; + this.count = text.Length; + } + + /// + /// Gets the string used for this segment. + /// + public string Text { + get { return text; } + } + + /// + /// Gets the start offset of the segment with the text. + /// + public int Offset { + get { return offset; } + } + + /// + /// Gets the length of the segment. + /// + public int Count { + get { return count; } + } + + #region Equals and GetHashCode implementation + /// + public override bool Equals(object obj) + { + if (obj is StringSegment) + return Equals((StringSegment)obj); // use Equals method below + else + return false; + } + + /// + public bool Equals(StringSegment other) + { + // add comparisions for all members here + return object.ReferenceEquals(this.text, other.text) && offset == other.offset && count == other.count; + } + + /// + public override int GetHashCode() + { + return text.GetHashCode() ^ offset ^ count; + } + + /// + /// Equality operator. + /// + public static bool operator ==(StringSegment left, StringSegment right) + { + return left.Equals(right); + } + + /// + /// Inequality operator. + /// + public static bool operator !=(StringSegment left, StringSegment right) + { + return !left.Equals(right); + } + #endregion + } +}