Browse Source

Prepare DocumentLineTree for optimization.

Added (unfinished) CP article about AvalonEdit.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4973 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 16 years ago
parent
commit
4feb97519c
  1. BIN
      samples/AvalonEdit.Sample/RenderingPipeline.pptx
  2. 2
      samples/AvalonEdit.Sample/Window1.xaml
  3. 8
      samples/AvalonEdit.Sample/Window1.xaml.cs
  4. 290
      samples/AvalonEdit.Sample/article.html
  5. BIN
      samples/AvalonEdit.Sample/renderingPipeline.png
  6. 2
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs
  7. 20
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/LineManagerTests.cs
  8. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ChangeTrackingCheckpoint.cs
  9. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLine.cs
  10. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLineTree.cs
  11. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ISegment.cs
  12. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs
  13. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs
  14. 26
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextUtilities.cs
  15. 12
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs
  16. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextArea.cs
  17. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs
  18. 18
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs
  19. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HtmlClipboard.cs
  20. 7
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Indentation/DefaultIndentationStrategy.cs
  21. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Indentation/IIndentationStrategy.cs
  22. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
  23. 8
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
  24. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineText.cs
  25. 11
      src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditDocumentAdapter.cs
  26. 4
      src/Main/Base/Project/Src/Editor/AvalonEdit/IndentationStrategyAdapter.cs

BIN
samples/AvalonEdit.Sample/RenderingPipeline.pptx

Binary file not shown.

2
samples/AvalonEdit.Sample/Window1.xaml

@ -57,8 +57,8 @@
<ComboBox Name="propertyGridComboBox" DockPanel.Dock="Top" <ComboBox Name="propertyGridComboBox" DockPanel.Dock="Top"
SelectedIndex="0" SelectionChanged="propertyGridComboBoxSelectionChanged"> SelectedIndex="0" SelectionChanged="propertyGridComboBoxSelectionChanged">
<ComboBoxItem>TextEditor</ComboBoxItem> <ComboBoxItem>TextEditor</ComboBoxItem>
<ComboBoxItem>Options</ComboBoxItem>
<ComboBoxItem>TextArea</ComboBoxItem> <ComboBoxItem>TextArea</ComboBoxItem>
<ComboBoxItem>Options</ComboBoxItem>
</ComboBox> </ComboBox>
<WindowsFormsHost DockPanel.Dock="Right" Name="propertyGridHost"> <WindowsFormsHost DockPanel.Dock="Right" Name="propertyGridHost">
<forms:PropertyGrid x:Name="propertyGrid"/> <forms:PropertyGrid x:Name="propertyGrid"/>

8
samples/AvalonEdit.Sample/Window1.xaml.cs

@ -29,6 +29,10 @@ namespace AvalonEdit.Sample
public Window1() public Window1()
{ {
InitializeComponent(); InitializeComponent();
propertyGridComboBox.SelectedIndex = 2;
textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".xml");
//textEditor.TextArea.TextView.ElementGenerators.Add(new WordFilterElementGenerator());
} }
string currentFileName; string currentFileName;
@ -67,10 +71,10 @@ namespace AvalonEdit.Sample
propertyGrid.SelectedObject = textEditor; propertyGrid.SelectedObject = textEditor;
break; break;
case 1: case 1:
propertyGrid.SelectedObject = textEditor.Options; propertyGrid.SelectedObject = textEditor.TextArea;
break; break;
case 2: case 2:
propertyGrid.SelectedObject = textEditor.TextArea; propertyGrid.SelectedObject = textEditor.Options;
break; break;
} }
} }

290
samples/AvalonEdit.Sample/article.html

@ -0,0 +1,290 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!--------------------------------------------------------------------------->
<!-- INTRODUCTION
The Code Project article submission template (HTML version)
Using this template will help us post your article sooner. To use, just
follow the 3 easy steps below:
1. Fill in the article description details
2. Add links to your images and downloads
3. Include the main article text
That's all there is to it! All formatting will be done by our submission
scripts and style sheets.
-->
<!--------------------------------------------------------------------------->
<!-- IGNORE THIS SECTION -->
<html>
<head>
<title>The Code Project</title>
<Style>
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt }
H2,H3,H4,H5 { color: #ff9900; font-weight: bold; }
H2 { font-size: 13pt; }
H3 { font-size: 12pt; }
H4 { font-size: 10pt; color: black; }
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; }
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; }
</style>
<link rel="stylesheet" type="text/css" href="http://www.codeproject.com/App_Themes/NetCommunity/CodeProject.css">
</head>
<body bgcolor="#FFFFFF" color=#000000>
<!--------------------------------------------------------------------------->
<!------------------------------- STEP 1 --------------------------->
<!-- Fill in the details (CodeProject will reformat this section for you) -->
<!------------------------------- STEP 2 --------------------------->
<!-- Include download and sample image information. -->
<ul class=download>
<li><a href="Article_demo.zip">Download demo project - XXX Kb </a></li>
<li><a href="Article_src.zip">Download source - XXX Kb</a></li>
</ul>
<p><img src="Article.gif" alt="Sample Image - maximum width is 600 pixels" width=400 height=200></p>
<!------------------------------- STEP 3 --------------------------->
<!-- Add the article text. Please use simple formatting (<h2>, <p> etc) -->
<h2>Introduction</h2>
<p>ICSharpCode.AvalonEdit is the WPF-based text editor that I've written for SharpDevelop 4.0. It is meant as a replacement
for <a href="http://www.codeproject.com/KB/edit/TextEditorControl.aspx">ICSharpCode.TextEditor</a>, but should be:
<ul>
<li>Extensible</li>
<li>Easy to use</li>
<li>Better at handling large files</li>
</ul>
<p>
<b>Extensible</b> means that I wanted SharpDevelop AddIns to be able to add features to the text editor.
For example, an AddIn should be able to allow inserting images into comments - this way you could put
stuff like class diagrams right into the source code!
<p>
With, <b>Easy to use</b>, I'm referring to the programming API. It should just work&trade;.
For example, this means if you change the document text,
the editor should automatically redraw without having to call Invalidate().
And if you do something wrong, you should get a meaningful exception, not corrupted state and crash later at an unrelated location.
<p>
<b>Better at handling large files</b> means that the editor should be able to handle large files (e.g.
the mscorlib XML documentation file, 7 MB, 74100 LOC), even when features like folding (code collapsing) are enabled.
<h2>Using the Code</h2>
<p>The main class of the editor is <code>ICSharpCode.AvalonEdit.TextEditor</code>.
You can use it just similar to a normal WPF TextBox:
<pre lang="cs">&lt;avalonEdit:TextEditor
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Name="textEditor"
FontFamily="Consolas"
FontSize="10pt"/&gt;</pre>
<p>To enable syntax highlighting, use:
<pre lang="cs">textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".cs");</pre>
AvalonEdit has syntax highlighting definitions built in for:
ASP.NET, Batch files, Boo, Coco/R grammars, C++, C#, HTML, Java, JavaScript, Patch files, PHP, TeX, VB.NET, XML
<p>If you need more of AvalonEdit than a simple text box with syntax highlighting, you will first have to learn more about the architecture of AvalonEdit.
<h2>Architecture</h2>
TODO: overview of the namespaces, insert graph from NDepend
<h2>Document (The Model)</h2>
<p>So, what is the model of a text editor that has support for complex features like syntax highlighting and folding?<br>
Would you expect to be able to access collapsed text using the document model, given that the text is folded away?<br>
Is the syntax highlighting part of the model?
<p>In my quest for a good representation of the model, I decided on a radical strategy:
<b>if it's not a <code>char</code>, it's not in the model</b>!
<p>The main class of the model is <code>ICSharpCode.AvalonEdit.Document.TextDocument</code>,
and it's sort of a <code>StringBuilder</code> with events.
<p><i>Simplified</i> definition of TextDocument:
<pre lang="cs">public sealed class TextDocument : ITextSource
{
public event EventHandler UpdateStarted;
public event EventHandler&lt;DocumentChangeEventArgs&gt; Changing;
public event EventHandler&lt;DocumentChangeEventArgs&gt; Changed;
public event EventHandler TextChanged;
public event EventHandler UpdateFinished;
public TextAnchor CreateAnchor(int offset);
public ITextSource CreateSnapshot();
public IList&lt;DocumentLine&gt; Lines { get; }
public DocumentLine GetLineByNumber(int number);
public DocumentLine GetLineByOffset(int offset);
public TextLocation GetLocation(int offset);
public int GetOffset(int line, int column);
public char GetCharAt(int offset);
public string GetText(int offset, int length);
public void BeginUpdate();
public bool IsInUpdate { get; }
public void EndUpdate();
public void Insert(int offset, string text);
public void Remove(int offset, int length);
public void Replace(int offset, int length, string text);
public string Text { get; set; }
public int LineCount { get; }
public int TextLength { get; }
public UndoStack UndoStack { get; }
}</pre>
<h3>Offsets</h3>
In AvalonEdit, an index into the document is called an <b>offset</b>.
<p>Offsets usually represent the position between two characters.
The first offset at the start of the document is 0, the offset after the first <code>char</code> in the document is 1.
The last valid offset is <code>document.TextLength</code>, representing the end of the document.
<p>This is exactly the same as the 'index' parameter used by methods in the .NET <code>String</code> or <code>StringBuilder</code> classes.
Offsets are used because they are dead simple. Want to get all text between offset 10 and offset 30?
Simply call <code>document.GetText(10, 20)</code> - just like <code>String.Substring</code>, AvalonEdit usually uses <code>Offset / Length</code> pairs to refer to text segments.
<p>To easily pass such segments around, AvalonEdit defines the <code>ISegment</code> interface:
<pre lang="cs">public interface ISegment
{
int Offset { get; }
int Length { get; } // must be non-negative
int EndOffset { get; } // must return Offset+Length
}</pre>
All TextDocument methods taking Offset/Length parameters also have an overload taking an <code>ISegment</code> instance - I have just removed those from the code listing above to make it easier to read.
<h3>Lines</h3>
Offsets are easy to use, but sometimes you need Line / Column pairs instead.
AvalonEdit defines a struct called <code>TextLocation</code> for those.
<p>The TextDocument provides the methods <code>GetLocation</code> and <code>GetOffset</code> to convert between offsets and <code>TextLocation</code>s.
Those are convenience methods built on top of the <code>DocumentLine</code>.
<p>The TextDocument.Lines collection contains one DocumentLine instance for every line in the document.
This collection is read-only to user code and is automatically updated to always<sup><small>*</small></sup> reflect the current document content.
<p>Internally, the DocumentLine instances build a binary tree that allows for both efficient updates and lookup.
Looking up the start offset from a line number is possible in O(lg N) time, and the data structure also updates all offsets in O(lg N) time whenever text is inserted/removed.
<p><small>* tiny exception: it is possible to see the line collection in an inconsistent state inside <code>ILineTracker</code> callbacks. Don't use <code>ILineTracker</code>
unless you know what you are doing!</small>
<h3>Change Events</h3>
Here is the order in which events are raised during a document update:
<p><b>BeginUpdate()</b>
<ul><li><code>UpdateStarted</code> event is raised</li></ul>
<p><b>Insert() / Remove() / Replace()</b>
<ul>
<li><code>Changing</code> event is raised</li>
<li>The document is changed</li>
<li><code>TextAnchor.Deleted</code> events are raised if anchors were in the deleted text portion</li>
<li><code>Changed</code> event is raised</li>
</ul>
<p><b>EndUpdate()</b>
<ul><li><code>TextChanged</code> event is raised</li>
<li><code>TextLengthChanged</code> event is raised</li>
<li><code>LineCountChanged</code> event is raised</li>
<li><code>UpdateFinished</code> event is raised</li></ul>
<p>If the insert/remove/replace methods are called without a call to <code>BeginUpdate()</code>, they will call
<code>BeginUpdate()</code> and <code>EndUpdate()</code> to ensure no change happens outside of UpdateStarted/UpdateFinished.
<p>There can be multiple document changes between the <code>BeginUpdate()</code> and <code>EndUpdate()</code> calls.
In this case, the events associated with EndUpdate will be raised only once after the whole document update is done.
<p>The <code>UndoStack</code> listens to the <code>UpdateStarted</code> and <code>UpdateFinished</code> events to group
all changes into a single undo step.
<h3>TextAnchor</h3>
If you are working with the text editor, you will likely run into the problem that you need to store an offset, but want it to adjust
automatically whenever text is inserted prior to that offset.
<p>Sure, you could listen to the <code>TextDocument.Changed</code> event and call <code>GetNewOffset</code> on the <code>DocumentChangeEventArgs</code> to translate
the offset, but that gets tedious; especially when your object is short-lived and you have to deal with deregistering the event handler at the correct point of time.<br>
<p>A much simpler solution is to use the <code>TextAnchor</code> class. Usage:
<pre lang="cs">TextAnchor anchor = document.CreateAnchor(offset);
ChangeMyDocument();
int newOffset = anchor.Offset;</pre>
<p>The document will automatically update all text anchors; and because it uses weak references to do so, the GC can simply collect the anchor object when you don't need it anymore.
<p>Moreover, the document is able to efficiently update a large number of anchors without having to look at each anchor object individually. Updating the offsets of all anchors
usually only takes time logarithmic to the number of anchors. Retrieving the <code>TextAnchor.Offset</code> property also runs in O(lg N).
<p>When a piece of text containing an anchor is removed; that anchor will be deleted. First, the <code>TextAnchor.IsDeleted</code> property is set to true on all deleted anchors, then the
<code>TextAnchor.Deleted</code> events are raised. You cannot retrieve the offset from an anchor that has been deleted.
<p>This deletion behavior might be useful when using anchors for building a bookmark feature, but in other cases you want to still be able to use the anchor. For those cases, set <code>TextAnchor.SurviveDeletion = true</code>.
<p>Note that anchor movement is ambiguous if text is inserted exactly at the anchor's location. Does the anchor stay before the inserted text, or does it move after it?
The property <code>TextAnchor.MovementType</code> will be used to determine which of these two options the anchor will choose. The default value is <code>AnchorMovementType.BeforeInsertion</code>.
<p>If you want to track a segment, you can use the <code>AnchorSegment</code> class which implements <code>ISegment</code> using two text anchors.
<h3>TextSegmentCollection</h3>
<p>Sometimes it is useful to store a list of segments and be able to efficiently find all segments overlapping with some other segment.<br>
Example: you might want to store a large number compiler warnings and render squiggly underlines only those that are in the visible region of the document.
<p>The <code>TextSegmentCollection</code> serves this purpose. Connected to a document, it will automatically update the offsets of all TextSegment instances inside the collection;
but it also has the useful methods <code>FindOverlappingSegments</code> and <code>FindFirstSegmentWithStartAfter</code>.
The underlying data structure is a hybrid between the one used for text anchors and an <a href="http://en.wikipedia.org/wiki/Interval_tree#Augmented_tree">interval tree</a>, so it is able to do both jobs quite fast.
<h3>Thread Safety</h3>
<p>The <code>TextDocument</code> class is not thread-safe. It expects to have a single owner thread and will throw an <code>InvalidOperationException</code> when accessed from another thread.
<p>However, there is a single method that is thread-safe: <code>CreateSnapshot()</code><br>
It returns an immutable snapshot of the document. The snapshot is thread-safe, so it is very useful for features like a background parser that is running on its own thread.
The overload <code>CreateSnapshot(out ChangeTrackingCheckpoint)</code> also returns a <code>ChangeTrackingCheckpoint</code> for the document snapshot.
Once you have two checkpoints, you can call <code>GetChangesTo</code> to retrieve the complete list of document changes that happened between those versions of the document.
<h2>Rendering</h2>
Noticed how through the whole Document section, there was no mention of extensibility?
The text rendering infrastructure now has to compensate for that by being completely extensible.
<p>The <code>ICSharpCode.AvalonEdit.Rendering.TextView</code> class is the heart of AvalonEdit.
It takes care of getting the document onto the screen.
<p>To do this in an extensible way, the TextView uses its own kind of model: the <code>VisualLine</code>.
VisualLines are created only for the visible part of the document.
<p>The creation process looks like this:<br>
<img src="renderingPipeline.png" alt="rendering pipeline">
<h2>Editing</h2>
TODO: write this section
<h2>Points of Interest</h2>
<p>Did you learn anything interesting/fun/annoying while writing the code? Did you
do anything particularly clever or wild or zany?
<h2>History</h2>
<p>Keep a running update of any changes or improvements you've made here.
<p><b>Note: although my sample code is provided under the BSD license, ICSharpCode.AvalonEdit itself is provided under the terms of the GNU LGPL.</b>
<!------------------------------- That's it! --------------------------->
</body>
</html>

BIN
samples/AvalonEdit.Sample/renderingPipeline.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

2
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs

@ -103,7 +103,7 @@ namespace ICSharpCode.AvalonEdit.AddIn
public override INavigationPoint BuildNavPoint() public override INavigationPoint BuildNavPoint()
{ {
int lineNumber = this.Line; int lineNumber = this.Line;
string txt = codeEditor.Document.GetLineByNumber(lineNumber).Text; string txt = codeEditor.Document.GetText(codeEditor.Document.GetLineByNumber(lineNumber));
return new TextNavigationPoint(this.PrimaryFileName, lineNumber, this.Column, txt); return new TextNavigationPoint(this.PrimaryFileName, lineNumber, this.Column, txt);
} }

20
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/LineManagerTests.cs

@ -63,7 +63,6 @@ namespace ICSharpCode.AvalonEdit.Document
Assert.AreEqual(0, line.Length); Assert.AreEqual(0, line.Length);
Assert.AreEqual(0, line.TotalLength); Assert.AreEqual(0, line.TotalLength);
Assert.AreEqual(0, line.DelimiterLength); Assert.AreEqual(0, line.DelimiterLength);
Assert.AreEqual("", line.Text);
} }
[Test] [Test]
@ -87,7 +86,7 @@ namespace ICSharpCode.AvalonEdit.Document
document.Insert(0, "a"); document.Insert(0, "a");
Assert.AreEqual(document.LineCount, 1); Assert.AreEqual(document.LineCount, 1);
DocumentLine line = document.GetLineByNumber(1); DocumentLine line = document.GetLineByNumber(1);
Assert.AreEqual("a", line.Text); Assert.AreEqual("a", document.GetText(line));
} }
[Test] [Test]
@ -96,7 +95,7 @@ namespace ICSharpCode.AvalonEdit.Document
document.Text = "a"; document.Text = "a";
Assert.AreEqual(document.LineCount, 1); Assert.AreEqual(document.LineCount, 1);
DocumentLine line = document.GetLineByNumber(1); DocumentLine line = document.GetLineByNumber(1);
Assert.AreEqual("a", line.Text); Assert.AreEqual("a", document.GetText(line));
} }
[Test] [Test]
@ -235,7 +234,7 @@ namespace ICSharpCode.AvalonEdit.Document
for (int i = 1; i < 4; i++) { for (int i = 1; i < 4; i++) {
DocumentLine line = document.GetLineByNumber(i); DocumentLine line = document.GetLineByNumber(i);
Assert.AreEqual(i, line.LineNumber); Assert.AreEqual(i, line.LineNumber);
Assert.AreEqual("line " + i, line.Text); Assert.AreEqual("line " + i, document.GetText(line));
} }
Assert.AreEqual(1, document.GetLineByNumber(1).DelimiterLength); Assert.AreEqual(1, document.GetLineByNumber(1).DelimiterLength);
Assert.AreEqual(2, document.GetLineByNumber(2).DelimiterLength); Assert.AreEqual(2, document.GetLineByNumber(2).DelimiterLength);
@ -304,6 +303,17 @@ namespace ICSharpCode.AvalonEdit.Document
"c"); "c");
} }
[Test]
public void RemoveLineContentAndJoinNonMatchingDelimiters2()
{
document.Text = "a\nb\rc";
document.Remove(2, 1);
Assert.AreEqual("a\n\rc", document.Text);
CheckDocumentLines("a",
"",
"c");
}
[Test] [Test]
public void RemoveMultilineUpToFirstPartOfDelimiter() public void RemoveMultilineUpToFirstPartOfDelimiter()
{ {
@ -397,7 +407,7 @@ namespace ICSharpCode.AvalonEdit.Document
{ {
Assert.AreEqual(lines.Length, document.LineCount, "LineCount"); Assert.AreEqual(lines.Length, document.LineCount, "LineCount");
for (int i = 0; i < lines.Length; i++) { for (int i = 0; i < lines.Length; i++) {
Assert.AreEqual(lines[i], document.Lines[i].Text, "Text of line " + (i + 1)); Assert.AreEqual(lines[i], document.GetText(document.Lines[i]), "Text of line " + (i + 1));
} }
} }

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ChangeTrackingCheckpoint.cs

@ -27,6 +27,8 @@ namespace ICSharpCode.AvalonEdit.Document
readonly object documentIdentifier; readonly object documentIdentifier;
// 'value' is the change from the previous checkpoint to this checkpoint // 'value' is the change from the previous checkpoint to this checkpoint
// TODO: store the change in the older checkpoint instead - if only a reference to the
// newest document version exists, the GC should be able to collect all DocumentChangeEventArgs.
readonly DocumentChangeEventArgs value; readonly DocumentChangeEventArgs value;
readonly int id; readonly int id;
ChangeTrackingCheckpoint next; ChangeTrackingCheckpoint next;

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLine.cs

@ -17,7 +17,7 @@ namespace ICSharpCode.AvalonEdit.Document
public sealed partial class DocumentLine : ISegment public sealed partial class DocumentLine : ISegment
{ {
#region Constructor #region Constructor
readonly TextDocument document; internal readonly TextDocument document;
internal bool isDeleted; internal bool isDeleted;
internal DocumentLine(TextDocument document) internal DocumentLine(TextDocument document)
@ -32,6 +32,7 @@ namespace ICSharpCode.AvalonEdit.Document
/// Gets the text document that owns this DocumentLine. O(1). /// Gets the text document that owns this DocumentLine. O(1).
/// </summary> /// </summary>
/// <remarks>This property is still available even if the line was deleted.</remarks> /// <remarks>This property is still available even if the line was deleted.</remarks>
[ObsoleteAttribute("Supporting this property causes DocumentLine to use more memory than otherwise necessary. It will be removed in a future AvalonEdit version.")]
public TextDocument Document { public TextDocument Document {
get { get {
document.DebugVerifyAccess(); document.DebugVerifyAccess();
@ -44,6 +45,7 @@ namespace ICSharpCode.AvalonEdit.Document
/// Gets the text on this line. /// Gets the text on this line.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">The line was deleted.</exception> /// <exception cref="InvalidOperationException">The line was deleted.</exception>
[ObsoleteAttribute("Supporting this property causes DocumentLine to use more memory than otherwise necessary. It will be removed in a future AvalonEdit version.")]
public string Text { public string Text {
get { get {
return document.GetText(this.Offset, this.Length); return document.GetText(this.Offset, this.Length);

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLineTree.cs

@ -639,7 +639,7 @@ namespace ICSharpCode.AvalonEdit.Document
int IList<DocumentLine>.IndexOf(DocumentLine item) int IList<DocumentLine>.IndexOf(DocumentLine item)
{ {
document.VerifyAccess(); document.VerifyAccess();
if (item == null || item.Document != document || item.IsDeleted) if (item == null || item.document != document || item.IsDeleted)
return -1; return -1;
else else
return item.LineNumber - 1; return item.LineNumber - 1;
@ -668,7 +668,7 @@ namespace ICSharpCode.AvalonEdit.Document
bool ICollection<DocumentLine>.Contains(DocumentLine item) bool ICollection<DocumentLine>.Contains(DocumentLine item)
{ {
document.VerifyAccess(); document.VerifyAccess();
return item != null && item.Document == document && !item.IsDeleted; return item != null && item.document == document && !item.IsDeleted;
} }
void ICollection<DocumentLine>.CopyTo(DocumentLine[] array, int arrayIndex) void ICollection<DocumentLine>.CopyTo(DocumentLine[] array, int arrayIndex)

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ISegment.cs

@ -25,6 +25,7 @@ namespace ICSharpCode.AvalonEdit.Document
/// <summary> /// <summary>
/// Gets the length of the segment. /// Gets the length of the segment.
/// </summary> /// </summary>
/// <remarks>Must not be negative.</remarks>
int Length { get; } int Length { get; }
/// <summary> /// <summary>

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs

@ -135,12 +135,12 @@ namespace ICSharpCode.AvalonEdit.Document
public enum AnchorMovementType public enum AnchorMovementType
{ {
/// <summary> /// <summary>
/// Behaves like a start marker - when text is inserted at the anchor position, the anchor will stay /// When text is inserted at the anchor position, the anchor will stay
/// before the inserted text. /// before the inserted text.
/// </summary> /// </summary>
BeforeInsertion, BeforeInsertion,
/// <summary> /// <summary>
/// Behave like an end marker - when text is insered at the anchor position, the anchor will move /// When text is insered at the anchor position, the anchor will move
/// after the inserted text. /// after the inserted text.
/// </summary> /// </summary>
AfterInsertion AfterInsertion

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs

@ -142,8 +142,6 @@ namespace ICSharpCode.AvalonEdit.Document
/// <summary> /// <summary>
/// Gets/Sets the text of the whole document. /// Gets/Sets the text of the whole document.
/// Get: O(n)
/// Set: O(n * log n)
/// </summary> /// </summary>
public string Text { public string Text {
get { get {

26
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextUtilities.cs

@ -122,16 +122,25 @@ namespace ICSharpCode.AvalonEdit.Document
return new SimpleSegment(pos, offset - pos); return new SimpleSegment(pos, offset - pos);
} }
/// <summary>
/// Gets the leading whitespace segment on the document line.
/// </summary>
[ObsoleteAttribute("Supporting this method causes DocumentLine to use more memory than otherwise necessary. It will be removed in a future AvalonEdit version.")]
public static ISegment GetLeadingWhitespace(DocumentLine documentLine)
{
return GetLeadingWhitespace(documentLine.Document, documentLine);
}
/// <summary> /// <summary>
/// Gets the leading whitespace segment on the document line. /// Gets the leading whitespace segment on the document line.
/// </summary> /// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")] Justification = "WPF uses 'Whitespace'")]
public static ISegment GetLeadingWhitespace(DocumentLine documentLine) public static ISegment GetLeadingWhitespace(TextDocument document, DocumentLine documentLine)
{ {
if (documentLine == null) if (documentLine == null)
throw new ArgumentNullException("documentLine"); throw new ArgumentNullException("documentLine");
return GetWhitespaceAfter(documentLine.Document, documentLine.Offset); return GetWhitespaceAfter(document, documentLine.Offset);
} }
/// <summary> /// <summary>
@ -139,11 +148,22 @@ namespace ICSharpCode.AvalonEdit.Document
/// </summary> /// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")] Justification = "WPF uses 'Whitespace'")]
[ObsoleteAttribute("Supporting this method causes DocumentLine to use more memory than otherwise necessary. It will be removed in a future AvalonEdit version.")]
public static ISegment GetTrailingWhitespace(DocumentLine documentLine) public static ISegment GetTrailingWhitespace(DocumentLine documentLine)
{
return GetTrailingWhitespace(documentLine.Document, documentLine);
}
/// <summary>
/// Gets the trailing whitespace segment on the document line.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static ISegment GetTrailingWhitespace(TextDocument document, DocumentLine documentLine)
{ {
if (documentLine == null) if (documentLine == null)
throw new ArgumentNullException("documentLine"); throw new ArgumentNullException("documentLine");
ISegment segment = GetWhitespaceBefore(documentLine.Document, documentLine.EndOffset); ISegment segment = GetWhitespaceBefore(document, documentLine.EndOffset);
// If the whole line consists of whitespace, we consider all of it as leading whitespace, // If the whole line consists of whitespace, we consider all of it as leading whitespace,
// so return an empty segment as trailing whitespace. // so return an empty segment as trailing whitespace.
if (segment.Offset == documentLine.Offset) if (segment.Offset == documentLine.Offset)

12
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs

@ -206,7 +206,7 @@ namespace ICSharpCode.AvalonEdit.Editing
TransformSelectedLines( TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) { delegate (TextArea textArea, DocumentLine line) {
int offset = line.Offset; int offset = line.Offset;
ISegment s = TextUtilities.GetSingleIndentationSegment(line.Document, offset, textArea.Options.IndentationSize); ISegment s = TextUtilities.GetSingleIndentationSegment(textArea.Document, offset, textArea.Options.IndentationSize);
if (s.Length > 0) { if (s.Length > 0) {
s = textArea.GetDeletableSegments(s).FirstOrDefault(); s = textArea.GetDeletableSegments(s).FirstOrDefault();
if (s != null && s.Length > 0) { if (s != null && s.Length > 0) {
@ -396,7 +396,7 @@ namespace ICSharpCode.AvalonEdit.Editing
{ {
TransformSelectedLines( TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) { delegate (TextArea textArea, DocumentLine line) {
line.Document.Remove(TextUtilities.GetLeadingWhitespace(line)); textArea.Document.Remove(TextUtilities.GetLeadingWhitespace(textArea.Document, line));
}, target, args, DefaultSegmentType.WholeDocument); }, target, args, DefaultSegmentType.WholeDocument);
} }
@ -404,7 +404,7 @@ namespace ICSharpCode.AvalonEdit.Editing
{ {
TransformSelectedLines( TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) { delegate (TextArea textArea, DocumentLine line) {
line.Document.Remove(TextUtilities.GetTrailingWhitespace(line)); textArea.Document.Remove(TextUtilities.GetTrailingWhitespace(textArea.Document, line));
}, target, args, DefaultSegmentType.WholeDocument); }, target, args, DefaultSegmentType.WholeDocument);
} }
@ -417,7 +417,7 @@ namespace ICSharpCode.AvalonEdit.Editing
{ {
TransformSelectedLines( TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) { delegate (TextArea textArea, DocumentLine line) {
ConvertTabsToSpaces(textArea, TextUtilities.GetLeadingWhitespace(line)); ConvertTabsToSpaces(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line));
}, target, args, DefaultSegmentType.WholeDocument); }, target, args, DefaultSegmentType.WholeDocument);
} }
@ -443,7 +443,7 @@ namespace ICSharpCode.AvalonEdit.Editing
{ {
TransformSelectedLines( TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) { delegate (TextArea textArea, DocumentLine line) {
ConvertSpacesToTabs(textArea, TextUtilities.GetLeadingWhitespace(line)); ConvertSpacesToTabs(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line));
}, target, args, DefaultSegmentType.WholeDocument); }, target, args, DefaultSegmentType.WholeDocument);
} }
@ -525,7 +525,7 @@ namespace ICSharpCode.AvalonEdit.Editing
start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset).LineNumber; start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset).LineNumber;
end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.EndOffset).LineNumber; end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.EndOffset).LineNumber;
} }
textArea.IndentationStrategy.IndentLines(start, end); textArea.IndentationStrategy.IndentLines(textArea.Document, start, end);
} }
textArea.Caret.BringCaretToView(); textArea.Caret.BringCaretToView();
args.Handled = true; args.Handled = true;

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextArea.cs

@ -750,7 +750,7 @@ namespace ICSharpCode.AvalonEdit.Editing
ReplaceSelectionWithText(newLine); ReplaceSelectionWithText(newLine);
if (this.IndentationStrategy != null) { if (this.IndentationStrategy != null) {
DocumentLine line = this.Document.GetLineByNumber(this.Caret.Line); DocumentLine line = this.Document.GetLineByNumber(this.Caret.Line);
this.IndentationStrategy.IndentLine(line); this.IndentationStrategy.IndentLine(this.Document, line);
} }
} }
} }

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs

@ -128,7 +128,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
try { try {
int targetLineNumber = line.LineNumber; int targetLineNumber = line.LineNumber;
HighlightUpTo(targetLineNumber); HighlightUpTo(targetLineNumber);
highlightedLine = new HighlightedLine(line); highlightedLine = new HighlightedLine(document, line);
HighlightLineAndUpdateTreeList(line, targetLineNumber); HighlightLineAndUpdateTreeList(line, targetLineNumber);
return highlightedLine; return highlightedLine;
} finally { } finally {
@ -223,7 +223,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
void HighlightLineInternal(DocumentLine line) void HighlightLineInternal(DocumentLine line)
{ {
lineStartOffset = line.Offset; lineStartOffset = line.Offset;
lineText = line.Text; lineText = document.GetText(line.Offset, line.Length);
position = 0; position = 0;
ResetColorStack(); ResetColorStack();
HighlightingRuleSet currentRuleSet = this.CurrentRuleSet; HighlightingRuleSet currentRuleSet = this.CurrentRuleSet;

18
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs

@ -22,16 +22,24 @@ namespace ICSharpCode.AvalonEdit.Highlighting
/// <summary> /// <summary>
/// Creates a new HighlightedLine instance. /// Creates a new HighlightedLine instance.
/// </summary> /// </summary>
public HighlightedLine(DocumentLine documentLine) public HighlightedLine(TextDocument document, DocumentLine documentLine)
{ {
if (documentLine == null) if (document == null)
throw new ArgumentNullException("documentLine"); throw new ArgumentNullException("document");
if (!document.Lines.Contains(documentLine))
throw new ArgumentException("Line is null or not part of document");
this.Document = document;
this.DocumentLine = documentLine; this.DocumentLine = documentLine;
this.Sections = new NullSafeCollection<HighlightedSection>(); this.Sections = new NullSafeCollection<HighlightedSection>();
} }
/// <summary> /// <summary>
/// Gets/Sets the document line associated with this HighlightedLine. /// Gets the document associated with this HighlightedLine.
/// </summary>
public TextDocument Document { get; private set; }
/// <summary>
/// Gets the document line associated with this HighlightedLine.
/// </summary> /// </summary>
public DocumentLine DocumentLine { get; private set; } public DocumentLine DocumentLine { get; private set; }
@ -112,7 +120,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
} }
elements.Sort(); elements.Sort();
TextDocument document = DocumentLine.Document; TextDocument document = this.Document;
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
int textOffset = startOffset; int textOffset = startOffset;
foreach (HtmlElement e in elements) { foreach (HtmlElement e in elements) {

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HtmlClipboard.cs

@ -86,7 +86,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
if (highlighter != null) if (highlighter != null)
highlightedLine = highlighter.HighlightLine(line); highlightedLine = highlighter.HighlightLine(line);
else else
highlightedLine = new HighlightedLine(line); highlightedLine = new HighlightedLine(document, line);
SimpleSegment s = segment.GetOverlap(line); SimpleSegment s = segment.GetOverlap(line);
if (html.Length > 0) if (html.Length > 0)
html.AppendLine("<br>"); html.AppendLine("<br>");

7
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Indentation/DefaultIndentationStrategy.cs

@ -18,11 +18,12 @@ namespace ICSharpCode.AvalonEdit.Indentation
public class DefaultIndentationStrategy : IIndentationStrategy public class DefaultIndentationStrategy : IIndentationStrategy
{ {
/// <inheritdoc/> /// <inheritdoc/>
public virtual void IndentLine(DocumentLine line) public virtual void IndentLine(TextDocument document, DocumentLine line)
{ {
if (document == null)
throw new ArgumentNullException("document");
if (line == null) if (line == null)
throw new ArgumentNullException("line"); throw new ArgumentNullException("line");
TextDocument document = line.Document;
DocumentLine previousLine = line.PreviousLine; DocumentLine previousLine = line.PreviousLine;
if (previousLine != null) { if (previousLine != null) {
ISegment indentationSegment = TextUtilities.GetWhitespaceAfter(document, previousLine.Offset); ISegment indentationSegment = TextUtilities.GetWhitespaceAfter(document, previousLine.Offset);
@ -36,7 +37,7 @@ namespace ICSharpCode.AvalonEdit.Indentation
/// <summary> /// <summary>
/// Does nothing: indenting multiple lines is useless without a smart indentation strategy. /// Does nothing: indenting multiple lines is useless without a smart indentation strategy.
/// </summary> /// </summary>
public virtual void IndentLines(int beginLine, int endLine) public virtual void IndentLines(TextDocument document, int beginLine, int endLine)
{ {
} }
} }

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Indentation/IIndentationStrategy.cs

@ -19,11 +19,11 @@ namespace ICSharpCode.AvalonEdit.Indentation
/// Sets the indentation for the specified line. /// Sets the indentation for the specified line.
/// Usually this is constructed from the indentation of the previous line. /// Usually this is constructed from the indentation of the previous line.
/// </summary> /// </summary>
void IndentLine(DocumentLine line); void IndentLine(TextDocument document, DocumentLine line);
/// <summary> /// <summary>
/// Reindents a set of lines. /// Reindents a set of lines.
/// </summary> /// </summary>
void IndentLines(int beginLine, int endLine); void IndentLines(TextDocument document, int beginLine, int endLine);
} }
} }

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs

@ -505,7 +505,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
{ {
if (documentLine == null) if (documentLine == null)
throw new ArgumentNullException("documentLine"); throw new ArgumentNullException("documentLine");
if (documentLine.Document != this.Document) if (!this.Document.Lines.Contains(documentLine))
throw new InvalidOperationException("Line belongs to wrong document"); throw new InvalidOperationException("Line belongs to wrong document");
VerifyAccess(); VerifyAccess();

8
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs

@ -27,6 +27,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
TextView textView; TextView textView;
List<VisualLineElement> elements; List<VisualLineElement> elements;
/// <summary>
/// Gets the document to which this VisualLine belongs.
/// </summary>
public TextDocument Document { get; private set; }
/// <summary> /// <summary>
/// Gets the first document line displayed by this visual line. /// Gets the first document line displayed by this visual line.
/// </summary> /// </summary>
@ -67,6 +72,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
Debug.Assert(textView != null); Debug.Assert(textView != null);
Debug.Assert(firstDocumentLine != null); Debug.Assert(firstDocumentLine != null);
this.textView = textView; this.textView = textView;
this.Document = textView.Document;
this.FirstDocumentLine = firstDocumentLine; this.FirstDocumentLine = firstDocumentLine;
} }
@ -87,7 +93,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
void PerformVisualElementConstruction(VisualLineElementGenerator[] generators) void PerformVisualElementConstruction(VisualLineElementGenerator[] generators)
{ {
TextDocument document = FirstDocumentLine.Document; TextDocument document = this.Document;
int offset = FirstDocumentLine.Offset; int offset = FirstDocumentLine.Offset;
int currentLineEnd = offset + FirstDocumentLine.Length; int currentLineEnd = offset + FirstDocumentLine.Length;
LastDocumentLine = FirstDocumentLine; LastDocumentLine = FirstDocumentLine;

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineText.cs

@ -98,7 +98,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
{ {
int textOffset = parentVisualLine.FirstDocumentLine.Offset + this.RelativeTextOffset; int textOffset = parentVisualLine.FirstDocumentLine.Offset + this.RelativeTextOffset;
TextSourceView view = new TextSourceView( TextSourceView view = new TextSourceView(
parentVisualLine.FirstDocumentLine.Document, parentVisualLine.Document,
new SimpleSegment(textOffset, this.DocumentLength)); new SimpleSegment(textOffset, this.DocumentLength));
int pos = TextUtilities.GetNextCaretPosition(view, visualColumn - this.VisualColumn, direction, mode); int pos = TextUtilities.GetNextCaretPosition(view, visualColumn - this.VisualColumn, direction, mode);
if (pos < 0) if (pos < 0)

11
src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditDocumentAdapter.cs

@ -55,11 +55,14 @@ namespace ICSharpCode.SharpDevelop.Editor.AvalonEdit
sealed class LineAdapter : IDocumentLine sealed class LineAdapter : IDocumentLine
{ {
readonly TextDocument document;
readonly DocumentLine line; readonly DocumentLine line;
public LineAdapter(DocumentLine line) public LineAdapter(TextDocument document, DocumentLine line)
{ {
Debug.Assert(document != null);
Debug.Assert(line != null); Debug.Assert(line != null);
this.document = document;
this.line = line; this.line = line;
} }
@ -84,7 +87,7 @@ namespace ICSharpCode.SharpDevelop.Editor.AvalonEdit
} }
public string Text { public string Text {
get { return line.Text; } get { return document.GetText(line); }
} }
} }
@ -108,12 +111,12 @@ namespace ICSharpCode.SharpDevelop.Editor.AvalonEdit
public IDocumentLine GetLine(int lineNumber) public IDocumentLine GetLine(int lineNumber)
{ {
return new LineAdapter(document.GetLineByNumber(lineNumber)); return new LineAdapter(document, document.GetLineByNumber(lineNumber));
} }
public IDocumentLine GetLineForOffset(int offset) public IDocumentLine GetLineForOffset(int offset)
{ {
return new LineAdapter(document.GetLineByOffset(offset)); return new LineAdapter(document, document.GetLineByOffset(offset));
} }
public int PositionToOffset(int line, int column) public int PositionToOffset(int line, int column)

4
src/Main/Base/Project/Src/Editor/AvalonEdit/IndentationStrategyAdapter.cs

@ -30,14 +30,14 @@ namespace ICSharpCode.SharpDevelop.Editor.AvalonEdit
this.formattingStrategy = formattingStrategy; this.formattingStrategy = formattingStrategy;
} }
public void IndentLine(DocumentLine line) public void IndentLine(TextDocument document, DocumentLine line)
{ {
if (line == null) if (line == null)
throw new ArgumentNullException("line"); throw new ArgumentNullException("line");
formattingStrategy.IndentLine(editor, editor.Document.GetLine(line.LineNumber)); formattingStrategy.IndentLine(editor, editor.Document.GetLine(line.LineNumber));
} }
public void IndentLines(int beginLine, int endLine) public void IndentLines(TextDocument document, int beginLine, int endLine)
{ {
formattingStrategy.IndentLines(editor, beginLine, endLine); formattingStrategy.IndentLines(editor, beginLine, endLine);
} }

Loading…
Cancel
Save