Browse Source

Remove AvalonEdit, NRefactory and cecil; in preparation for using git submodules.

pull/730/merge
Daniel Grunwald 10 years ago
parent
commit
a5776d437b
  1. 7
      src/Libraries/AvalonEdit/.gitignore
  2. 33
      src/Libraries/AvalonEdit/Documentation/Architecture.aml
  3. 133
      src/Libraries/AvalonEdit/Documentation/Code Completion.aml
  4. 175
      src/Libraries/AvalonEdit/Documentation/Coordinate Systems.aml
  5. 58
      src/Libraries/AvalonEdit/Documentation/Folding.aml
  6. 11
      src/Libraries/AvalonEdit/Documentation/ICSharpCode.AvalonEdit.content
  7. 209
      src/Libraries/AvalonEdit/Documentation/ICSharpCode.AvalonEdit.shfbproj
  8. 35
      src/Libraries/AvalonEdit/Documentation/License.html
  9. BIN
      src/Libraries/AvalonEdit/Documentation/Media/NamespaceDependencies.png
  10. BIN
      src/Libraries/AvalonEdit/Documentation/Media/RenderingPipeline.png
  11. BIN
      src/Libraries/AvalonEdit/Documentation/Media/VisualTree.png
  12. BIN
      src/Libraries/AvalonEdit/Documentation/Media/WelcomeScreenshot.png
  13. 78
      src/Libraries/AvalonEdit/Documentation/Sample Application.aml
  14. 234
      src/Libraries/AvalonEdit/Documentation/Syntax Highlighting.aml
  15. 178
      src/Libraries/AvalonEdit/Documentation/Text Rendering.aml
  16. 70
      src/Libraries/AvalonEdit/Documentation/Welcome.aml
  17. 79
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/ChangeTrackingTest.cs
  18. 170
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/CollapsingTests.cs
  19. 91
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/HeightTests.cs
  20. 553
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/LineManagerTests.cs
  21. 183
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/RandomizedLineManagerTest.cs
  22. 331
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/TextAnchorTest.cs
  23. 368
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/TextSegmentTreeTest.cs
  24. 94
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/TextUtilitiesTests.cs
  25. 92
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/UndoStackTests.cs
  26. 78
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Editing/ChangeDocumentTests.cs
  27. 183
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Editing/TextSegmentReadOnlySectionTests.cs
  28. 182
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Highlighting/HighlightedLineMergeTests.cs
  29. 56
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Highlighting/HtmlClipboardTests.cs
  30. 3
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.PartCover.Settings
  31. 126
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj
  32. 44
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/MultipleUIThreads.cs
  33. 55
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Properties/AssemblyInfo.cs
  34. 132
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Search/FindTests.cs
  35. 158
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/CaretNavigationTests.cs
  36. 146
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/CompressingTreeListTests.cs
  37. 51
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/ExtensionMethodsTests.cs
  38. 51
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/IndentationStringTests.cs
  39. 195
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/RopeTests.cs
  40. 127
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/WeakReferenceTests.cs
  41. 102
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/AvalonEditCommands.cs
  42. 416
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs
  43. 56
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.xaml
  44. 111
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionListBox.cs
  45. 210
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs
  46. 397
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindowBase.cs
  47. 73
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/ICompletionData.cs
  48. 57
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/IOverloadProvider.cs
  49. 109
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/InsightWindow.cs
  50. 118
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/InsightWindow.xaml
  51. 73
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/OverloadInsightWindow.cs
  52. 116
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/OverloadViewer.cs
  53. 123
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeEventArgs.cs
  54. 67
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeOperation.cs
  55. 268
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLine.cs
  56. 727
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLineTree.cs
  57. 86
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentTextWriter.cs
  58. 207
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/GapTextBuffer.cs
  59. 343
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/IDocument.cs
  60. 77
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ILineTracker.cs
  61. 142
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ITextAnchor.cs
  62. 357
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ITextSource.cs
  63. 45
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/IUndoableOperation.cs
  64. 318
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineManager.cs
  65. 98
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineNode.cs
  66. 150
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/NewLineFinder.cs
  67. 364
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/OffsetChangeMap.cs
  68. 169
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeTextSource.cs
  69. 197
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/SimpleSegment.cs
  70. 158
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs
  71. 102
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchorNode.cs
  72. 770
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchorTree.cs
  73. 1151
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs
  74. 167
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocumentWeakEventManager.cs
  75. 271
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextLocation.cs
  76. 266
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegment.cs
  77. 989
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs
  78. 136
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSourceVersionProvider.cs
  79. 422
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextUtilities.cs
  80. 76
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoOperationGroup.cs
  81. 458
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs
  82. 109
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/WeakLineTracker.cs
  83. 117
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/AbstractMargin.cs
  84. 538
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
  85. 115
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretLayer.cs
  86. 392
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs
  87. 48
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretWeakEventHandler.cs
  88. 78
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/DottedLineMargin.cs
  89. 61
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/DragDropException.cs
  90. 643
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs
  91. 105
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EmptySelection.cs
  92. 51
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/IReadOnlySectionProvider.cs
  93. 221
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/ImeNativeWrapper.cs
  94. 165
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/ImeSupport.cs
  95. 260
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/LineNumberMargin.cs
  96. 66
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/NoReadOnlySections.cs
  97. 417
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs
  98. 302
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Selection.cs
  99. 74
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionColorizer.cs
  100. 68
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionLayer.cs
  101. Some files were not shown because too many files have changed in this diff Show More

7
src/Libraries/AvalonEdit/.gitignore vendored

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
# ignore all obj and bin folders (even in subdirectories)
obj/
bin/
/Documentation/Help
/packages/AvalonEdit
/packages/AvalonEdit.Sample
/packages/NUnit.2.6.3

33
src/Libraries/AvalonEdit/Documentation/Architecture.aml

@ -1,33 +0,0 @@ @@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="5d1af8a2-fc1b-4a1b-b6c1-f33fb14bec1f" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<!--
<summary>
<para>Optional summary abstract</para>
</summary>
-->
<introduction>
<!-- Uncomment this to generate an outline of the section and sub-section
titles. Specify a numeric value as the inner text to limit it to
a specific number of sub-topics when creating the outline. Specify
zero (0) to limit it to top-level sections only. -->
<!-- <autoOutline /> -->
<mediaLink><image xlink:href="NamespaceDependencies" placement="center"/></mediaLink>
<para>As you can see in this dependency graph, AvalonEdit consists of a few
sub-namespaces that have cleanly separated jobs.
Most of the namespaces have a kind of 'main' class.</para>
<para>Here is the visual tree of the TextEditor control:</para>
<mediaLink><image xlink:href="VisualTree" placement="center"/></mediaLink>
<para>It's important to understand that AvalonEdit is a composite control
with the three layers:
<codeEntityReference>T:ICSharpCode.AvalonEdit.TextEditor</codeEntityReference> (main control),
<codeEntityReference>T:ICSharpCode.AvalonEdit.Editing.TextArea</codeEntityReference> (editing),
<codeEntityReference>T:ICSharpCode.AvalonEdit.Rendering.TextView</codeEntityReference> (rendering).
</para><para>
While the main control provides some convenience methods for common tasks,
for most advanced features you have to work directly with the inner controls.
You can access them using <codeInline>textEditor.TextArea</codeInline> or
<codeInline>textEditor.TextArea.TextView</codeInline>.</para>
</introduction>
</developerConceptualDocument>
</topic>

133
src/Libraries/AvalonEdit/Documentation/Code Completion.aml

@ -1,133 +0,0 @@ @@ -1,133 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="47c58b63-f30c-4290-a2f2-881d21227446" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<introduction>
<para>
AvalonEdit comes with a code completion drop down window.
You only have to handle the text entering events to determine
when you want to show the window; all the UI is already done for you.
</para>
</introduction>
<section>
<title>Usage of the Code Completion Window</title>
<content>
<code language="cs">
// in the constructor:
textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering;
textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered;
}
CompletionWindow completionWindow;
void textEditor_TextArea_TextEntered(object sender, TextCompositionEventArgs e)
{
if (e.Text == ".") {
// Open code completion after the user has pressed dot:
completionWindow = new CompletionWindow(textEditor.TextArea);
IList&lt;ICompletionData&gt; data = completionWindow.CompletionList.CompletionData;
data.Add(new MyCompletionData("Item1"));
data.Add(new MyCompletionData("Item2"));
data.Add(new MyCompletionData("Item3"));
completionWindow.Show();
completionWindow.Closed += delegate {
completionWindow = null;
};
}
}
void textEditor_TextArea_TextEntering(object sender, TextCompositionEventArgs e)
{
if (e.Text.Length > 0 &amp;&amp; completionWindow != null) {
if (!char.IsLetterOrDigit(e.Text[0])) {
// Whenever a non-letter is typed while the completion window is open,
// insert the currently selected element.
completionWindow.CompletionList.RequestInsertion(e);
}
}
// Do not set e.Handled=true.
// We still want to insert the character that was typed.
}
</code>
<para>
This code will open the code completion window whenever '.' is pressed.
By default, the
<codeEntityReference>T:ICSharpCode.AvalonEdit.CodeCompletion.CompletionWindow</codeEntityReference>
only handles key presses like Tab and Enter to insert the currently
selected item. To also make it complete when keys like '.' or ';' are pressed,
we attach another handler to the <codeInline>TextEntering</codeInline> event
and tell the completion window to insert the selected item.
</para>
<para>
The <codeInline>CompletionWindow</codeInline> will actually never have
focus - instead, it hijacks
the WPF keyboard input events on the text area and passes them through its
<codeInline>ListBox</codeInline>.
This allows selecting entries in the completion list using the
keyboard and normal typing in the editor at the same time.
</para>
<para>
Here is the implementation of the MyCompletionData class used in the code above:
<code language="cs">
/// Implements AvalonEdit ICompletionData interface to provide the entries in the
/// completion drop down.
public class MyCompletionData : ICompletionData
{
public MyCompletionData(string text)
{
this.Text = text;
}
public System.Windows.Media.ImageSource Image {
get { return null; }
}
public string Text { get; private set; }
// Use this property if you want to show a fancy UIElement in the list.
public object Content {
get { return this.Text; }
}
public object Description {
get { return "Description for " + this.Text; }
}
public void Complete(TextArea textArea, ISegment completionSegment,
EventArgs insertionRequestEventArgs)
{
textArea.Document.Replace(completionSegment, this.Text);
}
}
</code>
Both the content and the description shown may be any content acceptable in WPF,
including custom UIElements.
You may also implement custom logic in the <codeInline>Complete</codeInline>
method if you want to do more than simply inserting the text.
The <codeInline>insertionRequestEventArgs</codeInline> can help decide which
kind of insertion the user wants - depending on how the insertion was triggered,
it is an instance of <codeInline>TextCompositionEventArgs</codeInline>,
<codeInline>KeyEventArgs</codeInline> or <codeInline>MouseEventArgs</codeInline>.
</para>
</content>
</section>
<section>
<title>Code Completion for C#</title>
<content>
<para>
Full C# code completion is not in the scope of AvalonEdit.
You will need a C# parser, a C# type system, and the ability
to resolve C# expressions in your type system.
</para>
<para>
If you want to learn how this is handled in SharpDevelop, please
see:
<externalLink>
<linkText>Code Completion in SharpDevelop</linkText>
<linkUri>https://github.com/icsharpcode/SharpDevelop/wiki/Code-Completion</linkUri>
<linkTarget>_blank</linkTarget>
</externalLink>
</para>
</content>
</section>
</developerConceptualDocument>
</topic>

175
src/Libraries/AvalonEdit/Documentation/Coordinate Systems.aml

@ -1,175 +0,0 @@ @@ -1,175 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="5b1854b4-884c-4713-b921-b28e96a1b43e" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<!--
<summary>
<para>Optional summary abstract</para>
</summary>
-->
<introduction>
<!-- Uncomment this to generate an outline of the section and sub-section
titles. Specify a numeric value as the inner text to limit it to
a specific number of sub-topics when creating the outline. Specify
zero (0) to limit it to top-level sections only. -->
<!-- <autoOutline /> -->
<para>The text editor makes use of several different coordinate systems.
Here's an explanation of them.</para>
</introduction>
<!-- Add one or more top-level section elements. These are collapsible.
If using <autoOutline /> tag, add an address attribute to identify
it so that it can be jumped to with a hyperlink. -->
<section>
<title>Offset</title>
<content>
<para>In AvalonEdit, an index into the document is called an <newTerm>offset</newTerm>.</para>
<para>
Offsets usually represent the position between two characters.
The first offset at the start of the document is 0;
the offset after the first char in the document is 1.
The last valid offset is <codeInline>document.TextLength</codeInline>,
representing the end of the document.
This is exactly the same as the <codeInline>index</codeInline> parameter
used by methods in the .NET String or StringBuilder classes.
</para>
</content>
</section>
<section>
<title>TextLocation</title>
<content>
<para>The
<codeEntityReference qualifyHint="true">T:ICSharpCode.AvalonEdit.Document.TextLocation</codeEntityReference>
struct represents a Line/Column pair. Line and column are counted from 1.</para>
<para>The document provides the methods
<codeEntityReference qualifyHint="true">M:ICSharpCode.AvalonEdit.Document.TextDocument.GetLocation(System.Int32)</codeEntityReference>
and
<codeEntityReference qualifyHint="true">M:ICSharpCode.AvalonEdit.Document.TextDocument.GetOffset(ICSharpCode.AvalonEdit.Document.TextLocation)</codeEntityReference>
to convert between offsets and <codeInline>TextLocation</codeInline>s.</para>
</content>
</section>
<section>
<title>TextAnchor</title>
<content>
<para>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. </para>
<para>Sure, you could listen to the TextDocument.Changed event and call
GetNewOffset on the DocumentChangeEventArgs 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.</para>
<para>A text anchor object stores an Offset, but automatically
updates the offset when text is inserted/removed before the offset.
</para>
<para>
A much simpler solution is to use the
<codeEntityReference qualifyHint="true">T:ICSharpCode.AvalonEdit.Document.TextAnchor</codeEntityReference>
class.
Please take a look at the documentation for that class for more details.
</para>
</content>
</section>
<section>
<title>RelativeTextOffset</title>
<content>
<para>An offset in the document, but relative to the start offset of a <codeEntityReference>T:ICSharpCode.AvalonEdit.Rendering.VisualLine</codeEntityReference>.</para>
<para>Relative text offsets are used to store document offsets in visual lines.</para>
<para>You can convert between relative text offsets and document offsets
by adding/subtracting
<codeEntityReference qualifyHint="true">P:ICSharpCode.AvalonEdit.Rendering.VisualLine.FirstDocumentLine</codeEntityReference>.<codeEntityReference>P:ICSharpCode.AvalonEdit.Document.DocumentLine.Offset</codeEntityReference>.
</para>
</content>
</section>
<section>
<title>VisualColumn</title>
<content>
<para>An integer value that specifies a position inside a VisualLine.</para>
<para>
Not only text has a length in the visual line, but also other VisualLineElements.
VisualColumn is counting from 0 for each visual line.
</para>
<para>For example, tab markers take 2 visual columns (the marker and the tab space),
newline markers take 1 visual column; folding markers take just 1 visual column
even though they are longer in the document text.</para>
<para>Use the
<codeEntityReference qualifyHint="true">M:ICSharpCode.AvalonEdit.Rendering.VisualLine.GetVisualColumn(System.Int32)</codeEntityReference>
and
<codeEntityReference qualifyHint="true">M:ICSharpCode.AvalonEdit.Rendering.VisualLine.GetRelativeOffset(System.Int32)</codeEntityReference>
methods to convert between
visual columns and relative text offsets.</para>
<alert class="note">
<para>Do not confuse VisualColumn with text columns.
VisualColumn starts at 0, text column at 1. Text may have different length
in the two coordinate systems (e.g. tab markers, foldings).</para>
</alert>
</content>
</section>
<section>
<title>TextViewPosition</title>
<content>
<para>A Line,Column,VisualColumn triple.</para>
<para>The <codeEntityReference qualifyHint="true">T:ICSharpCode.AvalonEdit.TextViewPosition</codeEntityReference>
struct can be implicitly converted
to <codeEntityReference qualifyHint="false">T:ICSharpCode.AvalonEdit.Document.TextLocation</codeEntityReference>,
but has the additional VisualColumn information
that is necessary to accurately hold the caret position when
VisualLineElements with DocumentLength 0 are in use.</para>
</content>
</section>
<section>
<title>VisualTop</title>
<content>
<para>A double value that specifies the distance from the top of
the document to the top of a line measured in device-independent pixels.</para>
<para>VisualTop is equivalent to the Y component of a VisualPosition.</para>
</content>
</section>
<section>
<title>VisualPosition</title>
<content>
<para>A Point value (double X,Y) that specifies the position of an
element from the top left document corner measured in device-independent pixels.</para>
<para>To convert a VisualPosition to or from a (mouse) position inside
the TextView, simply subtract or add
<codeEntityReference qualifyHint="true">P:ICSharpCode.AvalonEdit.Rendering.TextView.ScrollOffset</codeEntityReference>
to it.
</para>
</content>
</section>
<relatedTopics>
<!-- One or more of the following:
- A local link
- An external link
- A code entity reference
<link xlink:href="Other Topic's ID"/>
<link xlink:href="Other Topic's ID">Link inner text</link>
<externalLink>
<linkText>Link text</linkText>
<linkAlternateText>Optional alternate link text</linkAlternateText>
<linkUri>URI</linkUri>
</externalLink>
<codeEntityReference>API member ID</codeEntityReference>
Examples:
<link xlink:href="00e97994-e9e6-46e0-b420-5be86b2f8270" />
<link xlink:href="00e97994-e9e6-46e0-b420-5be86b2f8278">Some other topic</link>
<externalLink>
<linkText>SHFB on CodePlex</linkText>
<linkAlternateText>Go to CodePlex</linkAlternateText>
<linkUri>http://www.codeplex.com/SHFB</linkUri>
</externalLink>
<codeEntityReference>T:TestDoc.TestClass</codeEntityReference>
<codeEntityReference>P:TestDoc.TestClass.SomeProperty</codeEntityReference>
<codeEntityReference>M:TestDoc.TestClass.#ctor</codeEntityReference>
<codeEntityReference>M:TestDoc.TestClass.#ctor(System.String,System.Int32)</codeEntityReference>
<codeEntityReference>M:TestDoc.TestClass.ToString</codeEntityReference>
<codeEntityReference>M:TestDoc.TestClass.FirstMethod</codeEntityReference>
<codeEntityReference>M:TestDoc.TestClass.SecondMethod(System.Int32,System.String)</codeEntityReference>
-->
</relatedTopics>
</developerConceptualDocument>
</topic>

58
src/Libraries/AvalonEdit/Documentation/Folding.aml

@ -1,58 +0,0 @@ @@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="440df648-413e-4f42-a28b-6b2b0e9b1084" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<!--<introduction>
<para>
Introduction for 'Folding'.
</para>
</introduction>-->
<section>
<title>How to use Folding in your application</title>
<content>
<para>
Folding (code collapsing) is implemented as an extension to the editor.
It could have been implemented in a separate assembly without having to
modify the AvalonEdit code.
A <codeEntityReference>T:ICSharpCode.AvalonEdit.Rendering.VisualLineElementGenerator</codeEntityReference>
takes care of the collapsed sections in the text document; and a custom
margin draws the plus and minus buttons.
</para>
<para>You could use the relevant classes separately;
but, to make it a bit easier to use, the static
<codeEntityReference qualifyHint="true">M:ICSharpCode.AvalonEdit.Folding.FoldingManager.Install(ICSharpCode.AvalonEdit.Editing.TextArea)</codeEntityReference>
method will create and register the necessary parts automatically.</para>
<para>All that's left for you is to regularly call
<codeEntityReference qualifyHint="true">M:ICSharpCode.AvalonEdit.Folding.FoldingManager.UpdateFoldings(System.Collections.Generic.IEnumerable{ICSharpCode.AvalonEdit.Folding.NewFolding},System.Int32)</codeEntityReference>
with the list of foldings you want to provide.
You could calculate that list yourself, or you could use a built-in
folding strategy to do it for you.</para>
<para>Here is the full code required to enable folding:
<code language="cs">foldingManager = FoldingManager.Install(textEditor.TextArea);
foldingStrategy = new XmlFoldingStrategy();
foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document);</code>
If you want the folding markers to update when the text is changed,
you have to repeat the <codeInline>foldingStrategy.UpdateFoldings</codeInline> call regularly.
</para>
</content>
</section>
<section>
<title>How the FoldingManager works</title>
<content>
<para>
The FoldingManager maintains a list of foldings. The FoldMargin displays those foldings and provides
the UI for collapsing/expanding.</para><para>
Folded foldings cause the FoldingElementGenerator to produce a line element that spans the whole folded
text section, causing the text generation for the visual line that contains the folding start to
continue after the folding end in another line.</para><para>
To ensure scrolling works correctly in the presence of foldings, lines inside folded regions must not
be used as start lines for the visual line generation. This is done by setting the line height of all
such lines to 0. To efficiently set the height on a large number of lines and support reverting to the
old height when the folding is uncollapsed, a CollapsedLineSection is used.
</para>
</content>
</section>
<relatedTopics>
<codeEntityReference>T:ICSharpCode.AvalonEdit.Folding.FoldingManager</codeEntityReference>
</relatedTopics>
</developerConceptualDocument>
</topic>

11
src/Libraries/AvalonEdit/Documentation/ICSharpCode.AvalonEdit.content

@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Topics defaultTopic="c52241ea-3eba-4ddf-b463-6349cbff38fd">
<Topic id="c52241ea-3eba-4ddf-b463-6349cbff38fd" visible="True" />
<Topic id="70c4df51-5ecb-4e24-a574-8c5a84306bd1" visible="True" />
<Topic id="5d1af8a2-fc1b-4a1b-b6c1-f33fb14bec1f" visible="True" />
<Topic id="5b1854b4-884c-4713-b921-b28e96a1b43e" visible="True" />
<Topic id="c06e9832-9ef0-4d65-ac2e-11f7ce9c7774" visible="True" />
<Topic id="4d4ceb51-154d-43f0-b876-ad9640c5d2d8" visible="True" />
<Topic id="440df648-413e-4f42-a28b-6b2b0e9b1084" visible="True" />
<Topic id="47c58b63-f30c-4290-a2f2-881d21227446" visible="True" />
</Topics>

209
src/Libraries/AvalonEdit/Documentation/ICSharpCode.AvalonEdit.shfbproj

@ -1,209 +0,0 @@ @@ -1,209 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<!-- The configuration and platform will be used to determine which
assemblies to include from solution and project documentation
sources -->
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{850b6602-0a7f-413a-864a-e816b98d7407}</ProjectGuid>
<SHFBSchemaVersion>1.9.5.0</SHFBSchemaVersion>
<!-- AssemblyName, Name, and RootNamespace are not used by SHFB but Visual
Studio adds them anyway -->
<AssemblyName>Documentation</AssemblyName>
<RootNamespace>Documentation</RootNamespace>
<Name>Documentation</Name>
<!-- SHFB properties -->
<OutputPath>.\Help\</OutputPath>
<HtmlHelpName>AvalonEdit Documentation</HtmlHelpName>
<ProjectSummary>
</ProjectSummary>
<MissingTags>Summary, AutoDocumentCtors, Namespace</MissingTags>
<VisibleItems>InheritedMembers, InheritedFrameworkMembers, Protected, ProtectedInternalAsProtected</VisibleItems>
<HtmlHelp1xCompilerPath>
</HtmlHelp1xCompilerPath>
<HtmlHelp2xCompilerPath>
</HtmlHelp2xCompilerPath>
<SandcastlePath>
</SandcastlePath>
<WorkingPath>
</WorkingPath>
<BuildLogFile>
</BuildLogFile>
<FrameworkVersion>.NET Framework 4.0</FrameworkVersion>
<HelpTitle>AvalonEdit</HelpTitle>
<CopyrightText>Copyright 2008-2014, Daniel Grunwald</CopyrightText>
<PresentationStyle>Prototype</PresentationStyle>
<HelpFileVersion>4.4.0.0</HelpFileVersion>
<ComponentConfigurations>
<ComponentConfig id="Code Block Component" enabled="True">
<component id="Code Block Component" type="SandcastleBuilder.Components.CodeBlockComponent" assembly="{@SHFBFolder}SandcastleBuilder.Components.dll">
<!-- Base path for relative filenames in source attributes (optional) -->
<basePath value="{@HtmlEncProjectFolder}" />
<!-- Base output paths for the files (required). These should match the parent folder of the output path
of the HTML files (see each of the SaveComponent instances in the configuration files). -->
<outputPaths>
{@HelpFormatOutputPaths}
</outputPaths>
<!-- Allow missing source files (Optional). If omitted, it will generate errors if referenced source files
are missing. -->
<allowMissingSource value="false" />
<!-- Remove region markers from imported code blocks. If omitted, region markers in imported code blocks
are left alone. -->
<removeRegionMarkers value="false" />
<!-- Code colorizer options (required).
Attributes:
Language syntax configuration file (required)
XSLT stylesheet file (required)
CSS stylesheet file (required)
Script file (required)
Disabled (optional, leading whitespace normalization only)
Default language (optional)
Enable line numbering (optional)
Enable outlining (optional)
Keep XML comment "see" tags within the code (optional)
Tab size override (optional, 0 = Use syntax file setting)
Use language name as default title (optional) -->
<colorizer syntaxFile="{@SHFBFolder}Colorizer\highlight.xml" styleFile="{@SHFBFolder}Colorizer\highlight.xsl" stylesheet="{@SHFBFolder}Colorizer\highlight.css" scriptFile="{@SHFBFolder}Colorizer\highlight.js" disabled="{@DisableCodeBlockComponent}" language="cs" numberLines="false" outlining="false" keepSeeTags="false" tabSize="0" defaultTitle="true" />
</component>
</ComponentConfig>
<ComponentConfig id="IntelliSense Component" enabled="True">
<component id="IntelliSense Component" type="SandcastleBuilder.Components.IntelliSenseComponent" assembly="{@SHFBFolder}SandcastleBuilder.Components.dll">
<!-- Output options (optional)
Attributes:
Include Namespaces (false by default)
Namespaces filename ("Namespaces" if not specified or empty)
Directory (current folder if not specified or empty) -->
<output includeNamespaces="false" namespacesFile="Namespaces" folder="{@OutputFolder}" />
</component>
</ComponentConfig>
<ComponentConfig id="Cached Framework Comments Index Data" enabled="True">
<component id="Cached Framework Comments Index Data" type="SandcastleBuilder.Components.CachedCopyFromIndexComponent" assembly="{@SHFBFolder}SandcastleBuilder.Components.dll">
<index name="comments" value="/doc/members/member" key="@name" cache="100">
{@CachedFrameworkCommentList}
{@CommentFileList}
</index>
<copy name="comments" source="*" target="/document/comments" />
</component>
</ComponentConfig>
<ComponentConfig id="Cached Reflection Index Data" enabled="True">
<component id="Cached Reflection Index Data" type="SandcastleBuilder.Components.CachedCopyFromIndexComponent" assembly="{@SHFBFolder}SandcastleBuilder.Components.dll">
<index name="reflection" value="/reflection/apis/api" key="@id" cache="10">
<cache base="{@SandcastlePath}Data\Reflection" recurse="true" files="*.xml" cacheFile="{@LocalDataFolder}Cache\Reflection.cache" />
<data files="reflection.xml" />
</index>
<copy name="reflection" source="*" target="/document/reference" />
</component>
</ComponentConfig>
<ComponentConfig id="API Token Resolution" enabled="True">
<component id="API Token Resolution" type="Microsoft.Ddue.Tools.SharedContentComponent" assembly="{@SandcastlePath}ProductionTools\BuildComponents.dll">
{@TokenFiles}
<replace elements="/*//token" item="string(.)" /></component>
</ComponentConfig>
<ComponentConfig id="Cached MSDN URL References" enabled="True">
<component id="Cached MSDN URL References" type="SandcastleBuilder.Components.CachedResolveReferenceLinksComponent" assembly="{@SHFBFolder}SandcastleBuilder.Components.dll" locale="{@Locale}" linkTarget="{@SdkLinkTarget}">
<helpOutput format="HtmlHelp1">
<cache filename="{@LocalDataFolder}Cache\MsdnUrl.cache" />
<targets base="{@SandcastlePath}Data\Reflection" recurse="true" files="*.xml" type="{@HtmlSdkLinkType}" />
<targets files="reflection.xml" type="Local" />
</helpOutput>
<helpOutput format="MSHelp2">
<cache filename="{@LocalDataFolder}Cache\MsdnUrl.cache" />
<targets base="{@SandcastlePath}Data\Reflection" recurse="true" files="*.xml" type="{@MSHelp2SdkLinkType}" />
<targets files="reflection.xml" type="Index" />
</helpOutput>
<helpOutput format="MSHelpViewer">
<cache filename="{@LocalDataFolder}Cache\MsdnUrl.cache" />
<targets base="{@SandcastlePath}Data\Reflection" recurse="true" files="*.xml" type="{@MSHelpViewerSdkLinkType}" />
<targets files="reflection.xml" type="Id" />
</helpOutput>
<helpOutput format="Website">
<cache filename="{@LocalDataFolder}Cache\MsdnUrl.cache" />
<targets base="{@SandcastlePath}Data\Reflection" recurse="true" files="*.xml" type="{@WebsiteSdkLinkType}" />
<targets files="reflection.xml" type="Local" />
</helpOutput>
</component>
</ComponentConfig>
</ComponentConfigurations>
<DocumentationSources>
<DocumentationSource sourceFile="..\ICSharpCode.AvalonEdit\ICSharpCode.AvalonEdit.csproj" />
</DocumentationSources>
<NamespaceSummaries>
<NamespaceSummaryItem name="(global)" isDocumented="False" />
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit" isDocumented="True">This is the main AvalonEdit namespace.</NamespaceSummaryItem>
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.CodeCompletion" isDocumented="True">This namespace contains classes to show the code completion window.</NamespaceSummaryItem>
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.Document" isDocumented="True">This namespace contains the document model.
The most important class here is TextDocument, which represents document that can be displayed and edited in the text editor.</NamespaceSummaryItem>
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.Editing" isDocumented="True">This namespace is the home of the TextArea class. It manages user input and handles the caret and the selection.</NamespaceSummaryItem>
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.Folding" isDocumented="True">This namespace contains the folding (code collapsing) implementation.</NamespaceSummaryItem>
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.Highlighting" isDocumented="True">This namespace contains the engine for highlighting text documents (DocumentHighlighter).
Additionally, the class HighlightingColorizer provides integration of the highlighting engine into the text editor GUI.</NamespaceSummaryItem>
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.Highlighting.Xshd" isDocumented="True">This namespace contains a document model for syntax highlighting definitions (.xshd files).</NamespaceSummaryItem>
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.Indentation" isDocumented="True">This namespace contains the logic for automatic indentation.</NamespaceSummaryItem>
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.Rendering" isDocumented="True">This namespace contains the text rendering infrastructure.</NamespaceSummaryItem>
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.Utils" isDocumented="True">This namespace contains various utility classes.</NamespaceSummaryItem>
<NamespaceSummaryItem name="XamlGeneratedNamespace" isDocumented="False" />
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.Xml" isDocumented="True">This namespace contains an error-tolerant XML parser with support for incremental parsing, only reparsing the changed regions of a TextDocument.</NamespaceSummaryItem>
<NamespaceSummaryItem name="ICSharpCode.AvalonEdit.Snippets" isDocumented="True">Snippets perform automatic text insertion. Snippets can be interactive after they were expanded, for example to allow the user to easily replace fields in the expanded snippet.</NamespaceSummaryItem>
</NamespaceSummaries>
<CleanIntermediates>True</CleanIntermediates>
<SyntaxFilters>Standard</SyntaxFilters>
<SdkLinkTarget>Blank</SdkLinkTarget>
<RootNamespaceContainer>False</RootNamespaceContainer>
<Preliminary>False</Preliminary>
<NamingMethod>Guid</NamingMethod>
<Language>en-US</Language>
<ContentPlacement>AboveNamespaces</ContentPlacement>
<BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity>
<HelpFileFormat>HtmlHelp1, Website</HelpFileFormat>
<IndentHtml>False</IndentHtml>
<KeepLogFile>True</KeepLogFile>
<DisableCodeBlockComponent>False</DisableCodeBlockComponent>
<CppCommentsFixup>False</CppCommentsFixup>
</PropertyGroup>
<!-- There are no properties for these two groups but they need to appear in
order for Visual Studio to perform the build. -->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup>
<ItemGroup>
<None Include="Coordinate Systems.aml" />
<None Include="Architecture.aml" />
<None Include="Code Completion.aml" />
<None Include="Sample Application.aml" />
<None Include="Folding.aml" />
<None Include="Syntax Highlighting.aml" />
<None Include="Text Rendering.aml" />
<None Include="Welcome.aml" />
</ItemGroup>
<ItemGroup>
<ContentLayout Include="ICSharpCode.AvalonEdit.content" />
</ItemGroup>
<ItemGroup>
<Image Include="Media\WelcomeScreenshot.png">
<ImageId>WelcomeScreenshot</ImageId>
</Image>
<Image Include="Media\VisualTree.png">
<ImageId>VisualTree</ImageId>
<AlternateText>Visual Tree</AlternateText>
</Image>
<Image Include="Media\RenderingPipeline.png">
<ImageId>RenderingPipeline</ImageId>
</Image>
<Image Include="Media\NamespaceDependencies.png">
<ImageId>NamespaceDependencies</ImageId>
<AlternateText>Namespace Dependency Graph</AlternateText>
</Image>
<Content Include="License.html">
<ExcludeFromToc>True</ExcludeFromToc>
</Content>
</ItemGroup>
<ItemGroup>
<Folder Include="Media\" />
</ItemGroup>
<!-- Import the SHFB build targets -->
<Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
</Project>

35
src/Libraries/AvalonEdit/Documentation/License.html

@ -1,35 +0,0 @@ @@ -1,35 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>License</title>
<link rel="stylesheet" type="text/css" href="../styles/presentation.css" />
</head>
<body>
<h3>The MIT License (MIT)</h3>
<p>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
</p>
<p>
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
</p>
<p>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</p>
</body>
</html>

BIN
src/Libraries/AvalonEdit/Documentation/Media/NamespaceDependencies.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

BIN
src/Libraries/AvalonEdit/Documentation/Media/RenderingPipeline.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

BIN
src/Libraries/AvalonEdit/Documentation/Media/VisualTree.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

BIN
src/Libraries/AvalonEdit/Documentation/Media/WelcomeScreenshot.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

78
src/Libraries/AvalonEdit/Documentation/Sample Application.aml

@ -1,78 +0,0 @@ @@ -1,78 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="70c4df51-5ecb-4e24-a574-8c5a84306bd1" revisionNumber="1">
<developerSampleDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<!--
<summary>
<para>Optional summary abstract</para>
</summary>
-->
<introduction>
<!-- Uncomment this to generate an outline of the section and sub-section
titles. Specify a numeric value as the inner text to limit it to
a specific number of sub-topics when creating the outline. Specify
zero (0) to limit it to top-level sections only. -->
<!-- <autoOutline /> -->
<para>In the SharpDevelop source code download, you will find a small sample
application in SharpDevelop\samples\AvalonEdit.Sample.</para>
</introduction>
<mediaLink><image xlink:href="WelcomeScreenshot" placement="center"/></mediaLink>
<!-- <procedure>Optional procedures. See How To document for procedure layout example.</procedure> -->
<!-- <requirements>Optional requirements section</requirements> -->
<!-- <demonstrates>Optional info about what is demonstrated</demonstrates> -->
<!-- <codeExample>Optional code example</codeExample> -->
<!-- Add one or more top-level section elements. These are collapsible.
If using <autoOutline />, add an address attribute to identify it
and specify a title so that it can be jumped to with a hyperlink. -->
<section>
<title>The Code Project article</title>
<content>
<para>
There is a Code Project article based on the sample application:
<externalLink>
<linkText>http://www.codeproject.com/KB/edit/AvalonEdit.aspx</linkText>
<linkUri>http://www.codeproject.com/KB/edit/AvalonEdit.aspx</linkUri>
<linkTarget>_blank</linkTarget>
</externalLink>
</para>
<para>
However, most of the material from that article has been included in this help file.
</para>
</content>
</section>
<relatedTopics>
<!-- One or more of the following:
- A local link
- An external link
- A code entity reference
<link xlink:href="Other Topic's ID">Link text</link>
<externalLink>
<linkText>Link text</linkText>
<linkAlternateText>Optional alternate link text</linkAlternateText>
<linkUri>URI</linkUri>
</externalLink>
<codeEntityReference>API member ID</codeEntityReference>
Examples:
<link xlink:href="00e97994-e9e6-46e0-b420-5be86b2f8278">Some other topic</link>
<externalLink>
<linkText>SHFB on CodePlex</linkText>
<linkAlternateText>Go to CodePlex</linkAlternateText>
<linkUri>http://shfb.codeplex.com</linkUri>
</externalLink>
<codeEntityReference>T:TestDoc.TestClass</codeEntityReference>
<codeEntityReference>P:TestDoc.TestClass.SomeProperty</codeEntityReference>
<codeEntityReference>M:TestDoc.TestClass.#ctor</codeEntityReference>
<codeEntityReference>M:TestDoc.TestClass.#ctor(System.String,System.Int32)</codeEntityReference>
<codeEntityReference>M:TestDoc.TestClass.ToString</codeEntityReference>
<codeEntityReference>M:TestDoc.TestClass.FirstMethod</codeEntityReference>
<codeEntityReference>M:TestDoc.TestClass.SecondMethod(System.Int32,System.String)</codeEntityReference>
-->
</relatedTopics>
</developerSampleDocument>
</topic>

234
src/Libraries/AvalonEdit/Documentation/Syntax Highlighting.aml

@ -1,234 +0,0 @@ @@ -1,234 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="4d4ceb51-154d-43f0-b876-ad9640c5d2d8" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<introduction>
<para>Probably the most important feature for any text editor is syntax highlighting.</para>
<para>AvalonEdit has a flexible text rendering model, see
<link xlink:href="c06e9832-9ef0-4d65-ac2e-11f7ce9c7774" />. Among the
text rendering extension points is the support for "visual line transformers" that
can change the display of a visual line after it has been constructed by the "visual element generators".
A useful base class implementing IVisualLineTransformer for the purpose of syntax highlighting
is <codeEntityReference>T:ICSharpCode.AvalonEdit.Rendering.DocumentColorizingTransformer</codeEntityReference>.
Take a look at that class' documentation to see
how to write fully custom syntax highlighters. This article only discusses the XML-driven built-in
highlighting engine.
</para>
</introduction>
<section>
<title>The highlighting engine</title>
<content>
<para>
The highlighting engine in AvalonEdit is implemented in the class
<codeEntityReference>T:ICSharpCode.AvalonEdit.Highlighting.DocumentHighlighter</codeEntityReference>.
Highlighting is the process of taking a DocumentLine and constructing
a <codeEntityReference>T:ICSharpCode.AvalonEdit.Highlighting.HighlightedLine</codeEntityReference>
instance for it by assigning colors to different sections of the line.
A <codeInline>HighlightedLine</codeInline> is simply a list of
(possibly nested) highlighted text sections.
</para><para>
The <codeInline>HighlightingColorizer</codeInline> class is the only
link between highlighting and rendering.
It uses a <codeInline>DocumentHighlighter</codeInline> to implement
a line transformer that applies the
highlighting to the visual lines in the rendering process.
</para><para>
Except for this single call, syntax highlighting is independent from the
rendering namespace. To help with other potential uses of the highlighting
engine, the <codeInline>HighlightedLine</codeInline> class has the
method <codeInline>ToHtml()</codeInline>
to produce syntax highlighted HTML source code.
</para>
<para>The highlighting rules used by the highlighting engine to highlight
the document are described by the following classes:
</para>
<definitionTable>
<definedTerm>HighlightingRuleSet</definedTerm>
<definition>Describes a set of highlighting spans and rules.</definition>
<definedTerm>HighlightingSpan</definedTerm>
<definition>A span consists of two regular expressions (Start and End), a color,
and a child ruleset.
The region between Start and End expressions will be assigned the
given color, and inside that span, the rules of the child
ruleset apply.
If the child ruleset also has <codeInline>HighlightingSpan</codeInline>s,
they can be nested, allowing highlighting constructs like nested comments or one language
embedded in another.</definition>
<definedTerm>HighlightingRule</definedTerm>
<definition>A highlighting rule is a regular expression with a color.
It will highlight matches of the regular expression using that color.</definition>
<definedTerm>HighlightingColor</definedTerm>
<definition>A highlighting color isn't just a color: it consists of a foreground
color, font weight and font style.</definition>
</definitionTable>
<para>
The highlighting engine works by first analyzing the spans: whenever a
begin RegEx matches some text, that span is pushed onto a stack.
Whenever the end RegEx of the current span matches some text,
the span is popped from the stack.
</para><para>
Each span has a nested rule set associated with it, which is empty
by default. This is why keywords won't be highlighted inside comments:
the span's empty ruleset is active there, so the keyword rule is not applied.
</para><para>
This feature is also used in the string span: the nested span will match
when a backslash is encountered, and the character following the backslash
will be consumed by the end RegEx of the nested span
(<codeInline>.</codeInline> matches any character).
This ensures that <codeInline>\"</codeInline> does not denote the end of the string span;
but <codeInline>\\"</codeInline> still does.
</para><para>
What's great about the highlighting engine is that it highlights only
on-demand, works incrementally, and yet usually requires only a
few KB of memory even for large code files.
</para><para>
On-demand means that when a document is opened, only the lines initially
visible will be highlighted. When the user scrolls down, highlighting will continue
from the point where it stopped the last time. If the user scrolls quickly,
so that the first visible line is far below the last highlighted line,
then the highlighting engine still has to process all the lines in between
– there might be comment starts in them. However, it will only scan that region
for changes in the span stack; highlighting rules will not be tested.
</para><para>
The stack of active spans is stored at the beginning of every line.
If the user scrolls back up, the lines getting into view can be highlighted
immediately because the necessary context (the span stack) is still available.
</para><para>
Incrementally means that even if the document is changed, the stored span stacks
will be reused as far as possible. If the user types <codeInline>/*</codeInline>,
that would theoretically cause the whole remainder of the file to become
highlighted in the comment color.
However, because the engine works on-demand, it will only update the span
stacks within the currently visible region and keep a notice
'the highlighting state is not consistent between line X and line X+1',
where X is the last line in the visible region.
Now, if the user would scroll down,
the highlighting state would be updated and the 'not consistent' notice
would be moved down. But usually, the user will continue typing
and type <codeInline>*/</codeInline> only a few lines later.
Now the highlighting state in the visible region will revert to the normal
'only the main ruleset is on the stack of active spans'.
When the user now scrolls down below the line with the 'not consistent' marker;
the engine will notice that the old stack and the new stack are identical;
and will remove the 'not consistent' marker.
This allows reusing the stored span stacks cached from before the user typed
<codeInline>/*</codeInline>.
</para><para>
While the stack of active spans might change frequently inside the lines,
it rarely changes from the beginning of one line to the beginning of the next line.
With most languages, such changes happen only at the start and end of multiline comments.
The highlighting engine exploits this property by storing the list of
span stacks in a special data structure
(<codeEntityReference>T:ICSharpCode.AvalonEdit.Utils.CompressingTreeList`1</codeEntityReference>).
The memory usage of the highlighting engine is linear to the number of span stack changes;
not to the total number of lines.
This allows the highlighting engine to store the span stacks for big code
files using only a tiny amount of memory, especially in languages like
C# where sequences of <codeInline>//</codeInline> or <codeInline>///</codeInline>
are more popular than <codeInline>/* */</codeInline> comments.
</para>
</content>
</section>
<section>
<title>XML highlighting definitions</title>
<content>
<para>AvalonEdit supports XML syntax highlighting definitions (.xshd files).</para>
<para>In the AvalonEdit source code, you can find the file
<codeInline>ICSharpCode.AvalonEdit\Highlighting\Resources\ModeV2.xsd</codeInline>.
This is an XML schema for the .xshd file format; you can use it to
code completion for .xshd files in XML editors.
</para>
<para>Here is an example highlighting definition for a sub-set of C#:
<code language="xml"><![CDATA[
<SyntaxDefinition name="C#"
xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Comment" foreground="Green" />
<Color name="String" foreground="Blue" />
<!-- This is the main ruleset. -->
<RuleSet>
<Span color="Comment" begin="//" />
<Span color="Comment" multiline="true" begin="/\*" end="\*/" />
<Span color="String">
<Begin>"</Begin>
<End>"</End>
<RuleSet>
<!-- nested span for escape sequences -->
<Span begin="\\" end="." />
</RuleSet>
</Span>
<Keywords fontWeight="bold" foreground="Blue">
<Word>if</Word>
<Word>else</Word>
<!-- ... -->
</Keywords>
<!-- Digits -->
<Rule foreground="DarkBlue">
\b0[xX][0-9a-fA-F]+ # hex number
| \b
( \d+(\.[0-9]+)? #number with optional floating point
| \.[0-9]+ #or just starting with floating point
)
([eE][+-]?[0-9]+)? # optional exponent
</Rule>
</RuleSet>
</SyntaxDefinition>
]]></code>
</para>
</content>
</section>
<section>
<title>ICSharpCode.TextEditor XML highlighting definitions</title>
<content>
<para>ICSharpCode.TextEditor (the predecessor of AvalonEdit) used
a different version of the XSHD file format.
AvalonEdit detects the difference between the formats using the XML namespace:
The new format uses <codeInline>xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"</codeInline>,
the old format does not use any XML namespace.
</para><para>
AvalonEdit can load .xshd files written in that old format, and even
automatically convert them to the new format. However, not all
constructs of the old file format are supported by AvalonEdit.
</para>
<code language="cs"><![CDATA[// convert from old .xshd format to new format
XshdSyntaxDefinition xshd;
using (XmlTextReader reader = new XmlTextReader("input.xshd")) {
xshd = HighlightingLoader.LoadXshd(reader);
}
using (XmlTextWriter writer = new XmlTextWriter("output.xshd", System.Text.Encoding.UTF8)) {
writer.Formatting = Formatting.Indented;
new SaveXshdVisitor(writer).WriteDefinition(xshd);
}
]]></code>
</content>
</section>
<section>
<title>Programmatically accessing highlighting information</title>
<content>
<para>As described above, the highlighting engine only stores the "span stack"
at the start of each line. This information can be retrieved using the
<codeEntityReference>M:ICSharpCode.AvalonEdit.Highlighting.DocumentHighlighter.GetSpanStack(System.Int32)</codeEntityReference>
method:
<code language="cs"><![CDATA[bool isInComment = documentHighlighter.GetSpanStack(1).Any(
s => s.SpanColor != null && s.SpanColor.Name == "Comment");
// returns true if the end of line 1 (=start of line 2) is inside a multiline comment]]></code>
Spans can be identified using their color. For this purpose, named colors should be used in the syntax definition.
</para>
<para>For more detailed results inside lines, the highlighting algorithm
must be executed for that line:
<code language="cs"><![CDATA[int off = document.GetOffset(7, 22);
HighlightedLine result = documentHighlighter.HighlightLine(document.GetLineByNumber(7));
bool isInComment = result.Sections.Any(
s => s.Offset <= off && s.Offset+s.Length >= off
&& s.Color.Name == "Comment");]]></code>
</para>
</content>
</section>
<relatedTopics>
<codeEntityReference>N:ICSharpCode.AvalonEdit.Highlighting</codeEntityReference>
</relatedTopics>
</developerConceptualDocument>
</topic>

178
src/Libraries/AvalonEdit/Documentation/Text Rendering.aml

@ -1,178 +0,0 @@ @@ -1,178 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="c06e9832-9ef0-4d65-ac2e-11f7ce9c7774" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<summary>
<para>This document describes how the TextView class renders the text, and
how you can extend the text rendering process to add new features to the text editor.
</para>
</summary>
<introduction>
<para>The <codeEntityReference qualifyHint="true">T:ICSharpCode.AvalonEdit.Rendering.TextView</codeEntityReference>
class is the heart of AvalonEdit.
It takes care of getting the document onto the screen.</para>
<para>To do this in an extensible way, the TextView uses its own kind of model:
the <codeEntityReference>T:ICSharpCode.AvalonEdit.Rendering.VisualLine</codeEntityReference>.
Visual lines are created only for the visible part of the document.</para>
<para>
The rendering process looks like this:
<mediaLink><image xlink:href="RenderingPipeline" placement="center"/></mediaLink>
The "element generators", "line transformers" and "background renderers" are
the extension points; it is possible to add custom implementations of them to
the TextView to implement additional features in the editor.
</para>
</introduction>
<!-- Add one or more top-level section elements. These are collapsible.
If using <autoOutline /> tag, add an address attribute to identify
it so that it can be jumped to with a hyperlink. -->
<section>
<title>Lifetime of VisualLines</title>
<content>
<para>
VisualLines are only created for the visible part of the document.
Lots of actions can trigger their creation, but most commonly the creation will be
caused by the MeasureOverride method of TextView.
When the TextView is measured, it uses the height tree to determine the first
document line in the visible region. Then, it constructs and measures a VisualLine
for that first line, and repeats that with the following lines
until the visible region is filled.
</para>
<para>
The TextView caches VisualLines - when the user scrolls down, only the VisualLines
coming into view are created, the rest is reused.
The VisualLine cache can be manually invalidated using the Redraw method family;
moreover, lots of actions cause automatic invalidation:
<list class="bullet"><listItem>any change in the document will invalidate the affected VisualLines</listItem><listItem>changing the width of the TextView invalidates all VisualLines if word-wrap is enabled</listItem><listItem>changing any text editor settings (word-wrap, font size, etc.) will invalidate all VisualLines</listItem><listItem>VisualLines leaving the visible area after scrolling will be disposed</listItem></list>
In general, manual invalidation is required only if you have written a text editor extension
(BackgroundRenderer, VisualLineElementGenerator or VisualLineTransformer) that also consumes
external data - in that case, you'll have to notify the text editor that VisualLines need
to be recreated when your external data changes.
</para>
<alert class="note">
<para>If external data used by your text editor extension changes, call
<codeEntityReference qualifyHint="true">M:ICSharpCode.AvalonEdit.Rendering.TextView.Redraw</codeEntityReference>
to invalidate the VisualLine.
</para>
</alert>
<para>
Invalidating VisualLines does not cause immediate recreation of the lines!
Rather, the VisualLines are recreated when the text view is next re-measured.
While measurement in WPF normally happens with DispatcherPriority.Render,
the TextView also supports redrawing with a lower priority than that.
For example, normal text insertion causes a redraw at background priority, so that
in case the user types faster than the text view can redraw, there will be only
one redraw for multiple input actions.
</para>
<alert class="note">
<para>
The TextView will never return invalid lines to you, but you
may run into the case that the valid visual lines are not available.
</para>
<para>
What happens in this case depends on the method you are calling -
the new visual line might get created automatically,
null could be returned, or you may get a
<codeEntityReference>T:ICSharpCode.AvalonEdit.Rendering.VisualLinesInvalidException</codeEntityReference>.
</para>
<para>
You can call
<codeEntityReference qualifyHint="true">M:ICSharpCode.AvalonEdit.Rendering.TextView.EnsureVisualLines</codeEntityReference>
to make the text view create all VisualLines in the visible region.
</para>
</alert>
</content>
</section>
<section>
<title>Building visual line elements</title>
<content>
<para>
As a first step, the VisualLineElementGenerators are used to produce elements. The
room in between the elements returned from the generators is filled with text elements.
Then, the VisualLine assigns the VisualColumn and RelativeTextOffset properties of the line elements.
</para>
<para>
For example, a line contains the text "Hello, World".
The user has enabled "ShowSpaces", so the text editor should show a little dot instead of the space.
In this case, the SingleCharacterElementGenerator, which is responsible for ShowSpaces, will produce
a "SpaceTextElement" for the space character. Because no other generators are interested in the line,
the remaining strings "Hello," and "World" will be represented by VisualLineText elements.
</para>
</content>
</section>
<section>
<title>Transforming visual line elements</title>
<content>
<para>
After that, the IVisualLineTransformers are used to modify the produced line elements. Transformers
must not add elements, but they may split existing elements, e.g. to colorize only parts of an
element. When splitting elements (or somehow modifying the elements collection), care must be taken
that the VisualColumn,VisualLine,RelativeTextOffset and DocumentLength properties stay correct.
</para>
<para>
The ColorizingTransformer base class provides helper methods for splitting, so the derived class
can simply say "color this section in that color".
</para>
<para>
The DocumentColorizingTransformer extends the ColorizingTransformer and additionally
allows highlighting on per DocumentLine, coloring text segments (instead of directly
working with visual line elements).
</para>
</content>
</section>
<section>
<title>Constructing TextLines</title>
<content>
<para>
After building the visual line elements, the TextLines for the visual line are constructed.
A visual line may result in multiple text lines when word wrapping is active or when special visual
line elements force a line break.
Building text lines:
The text line construction is done by a WPF TextFormatter.
The VisualLineTextSource will take the visual line elements and build WPF TextRuns from it,
while the WPF TextFormatter takes care of word wrapping etc.
VisualLineElements are requested to produce TextRuns for their full or a partial length.
The TextView will take care to measure any inline UI elements in the visual lines.
</para>
</content>
</section>
<section>
<title>Rest of the Rendering</title>
<content>
<para>
After the visible region is filled, the TextView updates the heights stored in the document lines to
the measured heights. This way, scrolling takes account for word-wrapping.
The constructed text lines are stored for the arrange and render steps.
Now, finally, the measure step is complete.
</para>
<para>
The WPF arrange step doesn't have much work to do:
It just arranges inline UI elements at their position inside the text.
</para>
<para>
The actual rendering does not happen directly in the TextView, but in its
various layers.
</para>
<para>
These are the predefined layers:
<list class="bullet">
<listItem>Background layer: renders the background colors associated with the visual elements</listItem>
<listItem>Selection layer: renders the background of the selection</listItem>
<listItem>Text layer: renders the TextLines that were constructed during the Measure step.
Starting with AvalonEdit 4.1, the TextLayer uses child elements to draw the text: one DrawingVisual for each VisualLine.
</listItem>
<listItem>
Immediately after the text layer, any inline UI elements are placed as if they were separate layers.
</listItem>
<listItem>Caret layer: renders a blinking caret</listItem></list>
It's also possible to insert new layers into the TextView using the
<codeEntityReference qualifyHint="true">M:ICSharpCode.AvalonEdit.Rendering.TextView.InsertLayer(System.Windows.UIElement,ICSharpCode.AvalonEdit.Rendering.KnownLayer,ICSharpCode.AvalonEdit.Rendering.LayerInsertionPosition)</codeEntityReference>
method.
This allows adding custom interactive components to the editor
while being in full control of the Z-Order.
</para>
</content>
</section>
<relatedTopics>
<codeEntityReference>T:ICSharpCode.AvalonEdit.Rendering.TextView</codeEntityReference>
</relatedTopics>
</developerConceptualDocument>
</topic>

70
src/Libraries/AvalonEdit/Documentation/Welcome.aml

@ -1,70 +0,0 @@ @@ -1,70 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="c52241ea-3eba-4ddf-b463-6349cbff38fd" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<summary>
<para>AvalonEdit is a WPF-based extensible text editor.</para>
</summary>
<introduction>
<para>While the WPF RichTextBox is quite powerful, you quickly run into its limits
when trying to use it as a code editor: it's hard to write efficient syntax highlighting for it,
and you cannot really implement features like code folding with the standard RichTextBox.</para>
<para>The problem is: the RichTextBox edits a rich document.
In contrast, AvalonEdit simply edits text.</para>
<para>However, AvalonEdit offers lots of possibilities on how the text document is
displayed - so it is much more suitable for a code editor where things like the text color
are not controlled by the user, but instead depend on the text (syntax highlighting).
</para>
<para>
AvalonEdit was written for the SharpDevelop IDE. It replaces our old
Windows Forms-based text editor (ICSharpCode.TextEditor).
</para>
</introduction>
<mediaLink><image xlink:href="WelcomeScreenshot" placement="center"/></mediaLink>
<section>
<title>Usage</title>
<content>
<para>The main class of the editor is <codeEntityReference qualifyHint="true">T:ICSharpCode.AvalonEdit.TextEditor</codeEntityReference>.
You can use it similar to a normal WPF TextBox:</para>
<code language="xml"><![CDATA[
<avalonEdit:TextEditor
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Name="textEditor"
SyntaxHighlighting="C#"
FontFamily="Consolas"
FontSize="10pt"/>
]]></code>
</content>
</section>
<section>
<title>System requirements</title>
<content>
<para>There are two versions of AvalonEdit - the normal one requires
<externalLink>
<linkText>.NET Framework 4.0</linkText>
<linkUri>http://msdn.microsoft.com/en-us/library/w0x726c2.aspx</linkUri>
<linkTarget>_blank</linkTarget>
</externalLink> or higher; but we also offer a modified version for .NET 3.5 SP1.
For compiling AvalonEdit, you will need a C# 4.0 compiler (SharpDevelop 4.x or Visual Studio 2010).
</para>
<para>AvalonEdit requires FullTrust and will not run as XBAP.</para>
</content>
</section>
<relatedTopics>
<codeEntityReference qualifyHint="true">T:ICSharpCode.AvalonEdit.TextEditor</codeEntityReference>
<externalLink>
<linkText>www.avalonedit.net</linkText>
<linkUri>http://www.avalonedit.net</linkUri>
<linkTarget>_blank</linkTarget>
</externalLink>
<externalLink>
<linkText>www.icsharpcode.net</linkText>
<linkUri>http://www.icsharpcode.net</linkUri>
<linkTarget>_blank</linkTarget>
</externalLink>
</relatedTopics>
</developerConceptualDocument>
</topic>

79
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/ChangeTrackingTest.cs

@ -1,79 +0,0 @@ @@ -1,79 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Document
{
[TestFixture]
public class ChangeTrackingTest
{
[Test]
public void NoChanges()
{
TextDocument document = new TextDocument("initial text");
ITextSource snapshot1 = document.CreateSnapshot();
ITextSource snapshot2 = document.CreateSnapshot();
Assert.AreEqual(0, snapshot1.Version.CompareAge(snapshot2.Version));
Assert.AreEqual(0, snapshot1.Version.GetChangesTo(snapshot2.Version).Count());
Assert.AreEqual(document.Text, snapshot1.Text);
Assert.AreEqual(document.Text, snapshot2.Text);
}
[Test]
public void ForwardChanges()
{
TextDocument document = new TextDocument("initial text");
ITextSource snapshot1 = document.CreateSnapshot();
document.Replace(0, 7, "nw");
document.Insert(1, "e");
ITextSource snapshot2 = document.CreateSnapshot();
Assert.AreEqual(-1, snapshot1.Version.CompareAge(snapshot2.Version));
TextChangeEventArgs[] arr = snapshot1.Version.GetChangesTo(snapshot2.Version).ToArray();
Assert.AreEqual(2, arr.Length);
Assert.AreEqual("nw", arr[0].InsertedText.Text);
Assert.AreEqual("e", arr[1].InsertedText.Text);
Assert.AreEqual("initial text", snapshot1.Text);
Assert.AreEqual("new text", snapshot2.Text);
}
[Test]
public void BackwardChanges()
{
TextDocument document = new TextDocument("initial text");
ITextSource snapshot1 = document.CreateSnapshot();
document.Replace(0, 7, "nw");
document.Insert(1, "e");
ITextSource snapshot2 = document.CreateSnapshot();
Assert.AreEqual(1, snapshot2.Version.CompareAge(snapshot1.Version));
TextChangeEventArgs[] arr = snapshot2.Version.GetChangesTo(snapshot1.Version).ToArray();
Assert.AreEqual(2, arr.Length);
Assert.AreEqual("", arr[0].InsertedText.Text);
Assert.AreEqual("initial", arr[1].InsertedText.Text);
Assert.AreEqual("initial text", snapshot1.Text);
Assert.AreEqual("new text", snapshot2.Text);
}
}
}

170
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/CollapsingTests.cs

@ -1,170 +0,0 @@ @@ -1,170 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using ICSharpCode.AvalonEdit.Rendering;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Document
{
[TestFixture]
public class CollapsingTests
{
TextDocument document;
HeightTree heightTree;
[SetUp]
public void Setup()
{
document = new TextDocument();
document.Text = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10";
heightTree = new HeightTree(document, 10);
foreach (DocumentLine line in document.Lines) {
heightTree.SetHeight(line, line.LineNumber);
}
}
CollapsedLineSection SimpleCheck(int from, int to)
{
CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(from), document.GetLineByNumber(to));
for (int i = 1; i < from; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
for (int i = from; i <= to; i++) {
Assert.IsTrue(heightTree.GetIsCollapsed(i));
}
for (int i = to + 1; i <= 10; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
CheckHeights();
return sec1;
}
[Test]
public void SimpleCheck()
{
SimpleCheck(4, 6);
}
[Test]
public void SimpleUncollapse()
{
CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(4), document.GetLineByNumber(6));
sec1.Uncollapse();
for (int i = 1; i <= 10; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
CheckHeights();
}
[Test]
public void FullCheck()
{
for (int from = 1; from <= 10; from++) {
for (int to = from; to <= 10; to++) {
try {
SimpleCheck(from, to).Uncollapse();
for (int i = 1; i <= 10; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
CheckHeights();
} catch {
Console.WriteLine("from = " + from + ", to = " + to);
throw;
}
}
}
}
[Test]
public void InsertInCollapsedSection()
{
CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(4), document.GetLineByNumber(6));
document.Insert(document.GetLineByNumber(5).Offset, "a\nb\nc");
for (int i = 1; i < 4; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
for (int i = 4; i <= 8; i++) {
Assert.IsTrue(heightTree.GetIsCollapsed(i));
}
for (int i = 9; i <= 12; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
CheckHeights();
}
[Test]
public void RemoveInCollapsedSection()
{
CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(3), document.GetLineByNumber(7));
int line4Offset = document.GetLineByNumber(4).Offset;
int line6Offset = document.GetLineByNumber(6).Offset;
document.Remove(line4Offset, line6Offset - line4Offset);
for (int i = 1; i < 3; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
for (int i = 3; i <= 5; i++) {
Assert.IsTrue(heightTree.GetIsCollapsed(i));
}
for (int i = 6; i <= 8; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
CheckHeights();
}
[Test]
public void RemoveEndOfCollapsedSection()
{
CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(3), document.GetLineByNumber(6));
int line5Offset = document.GetLineByNumber(5).Offset;
int line8Offset = document.GetLineByNumber(8).Offset;
document.Remove(line5Offset, line8Offset - line5Offset);
for (int i = 1; i < 3; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
for (int i = 3; i <= 5; i++) {
Assert.IsTrue(heightTree.GetIsCollapsed(i));
}
for (int i = 6; i <= 7; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
CheckHeights();
}
[Test]
public void RemoveCollapsedSection()
{
CollapsedLineSection sec1 = heightTree.CollapseText(document.GetLineByNumber(3), document.GetLineByNumber(3));
int line3Offset = document.GetLineByNumber(3).Offset;
document.Remove(line3Offset - 1, 1);
for (int i = 1; i <= 9; i++) {
Assert.IsFalse(heightTree.GetIsCollapsed(i));
}
CheckHeights();
Assert.AreSame(null, sec1.Start);
Assert.AreSame(null, sec1.End);
// section gets uncollapsed when it is removed
Assert.IsFalse(sec1.IsCollapsed);
}
void CheckHeights()
{
HeightTests.CheckHeights(document, heightTree);
}
}
}

91
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/HeightTests.cs

@ -1,91 +0,0 @@ @@ -1,91 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using ICSharpCode.AvalonEdit.Rendering;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Document
{
[TestFixture]
public class HeightTests
{
TextDocument document;
HeightTree heightTree;
[SetUp]
public void Setup()
{
document = new TextDocument();
document.Text = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10";
heightTree = new HeightTree(document, 10);
foreach (DocumentLine line in document.Lines) {
heightTree.SetHeight(line, line.LineNumber);
}
}
[Test]
public void SimpleCheck()
{
CheckHeights();
}
[Test]
public void TestLinesRemoved()
{
document.Remove(5, 4);
CheckHeights();
}
[Test]
public void TestHeightChanged()
{
heightTree.SetHeight(document.GetLineByNumber(4), 100);
CheckHeights();
}
[Test]
public void TestLinesInserted()
{
document.Insert(0, "x\ny\n");
heightTree.SetHeight(document.Lines[0], 100);
heightTree.SetHeight(document.Lines[1], 1000);
heightTree.SetHeight(document.Lines[2], 10000);
CheckHeights();
}
void CheckHeights()
{
CheckHeights(document, heightTree);
}
internal static void CheckHeights(TextDocument document, HeightTree heightTree)
{
double[] heights = document.Lines.Select(l => heightTree.GetIsCollapsed(l.LineNumber) ? 0 : heightTree.GetHeight(l)).ToArray();
double[] visualPositions = new double[document.LineCount+1];
for (int i = 0; i < heights.Length; i++) {
visualPositions[i+1]=visualPositions[i]+heights[i];
}
foreach (DocumentLine ls in document.Lines) {
Assert.AreEqual(visualPositions[ls.LineNumber-1], heightTree.GetVisualPosition(ls));
}
Assert.AreEqual(visualPositions[document.LineCount], heightTree.TotalHeight);
}
}
}

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

@ -1,553 +0,0 @@ @@ -1,553 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Document
{
[TestFixture]
public class LineManagerTests
{
TextDocument document;
[SetUp]
public void SetUp()
{
document = new TextDocument();
}
[Test]
public void CheckEmptyDocument()
{
Assert.AreEqual("", document.Text);
Assert.AreEqual(0, document.TextLength);
Assert.AreEqual(1, document.LineCount);
}
[Test]
public void CheckClearingDocument()
{
document.Text = "Hello,\nWorld!";
Assert.AreEqual(2, document.LineCount);
var oldLines = document.Lines.ToArray();
document.Text = "";
Assert.AreEqual("", document.Text);
Assert.AreEqual(0, document.TextLength);
Assert.AreEqual(1, document.LineCount);
Assert.AreSame(oldLines[0], document.Lines.Single());
Assert.IsFalse(oldLines[0].IsDeleted);
Assert.IsTrue(oldLines[1].IsDeleted);
Assert.IsNull(oldLines[0].NextLine);
Assert.IsNull(oldLines[1].PreviousLine);
}
[Test]
public void CheckGetLineInEmptyDocument()
{
Assert.AreEqual(1, document.Lines.Count);
List<DocumentLine> lines = new List<DocumentLine>(document.Lines);
Assert.AreEqual(1, lines.Count);
DocumentLine line = document.Lines[0];
Assert.AreSame(line, lines[0]);
Assert.AreSame(line, document.GetLineByNumber(1));
Assert.AreSame(line, document.GetLineByOffset(0));
}
[Test]
public void CheckLineSegmentInEmptyDocument()
{
DocumentLine line = document.GetLineByNumber(1);
Assert.AreEqual(1, line.LineNumber);
Assert.AreEqual(0, line.Offset);
Assert.IsFalse(line.IsDeleted);
Assert.AreEqual(0, line.Length);
Assert.AreEqual(0, line.TotalLength);
Assert.AreEqual(0, line.DelimiterLength);
}
[Test]
public void LineIndexOfTest()
{
DocumentLine line = document.GetLineByNumber(1);
Assert.AreEqual(0, document.Lines.IndexOf(line));
DocumentLine lineFromOtherDocument = new TextDocument().GetLineByNumber(1);
Assert.AreEqual(-1, document.Lines.IndexOf(lineFromOtherDocument));
document.Text = "a\nb\nc";
DocumentLine middleLine = document.GetLineByNumber(2);
Assert.AreEqual(1, document.Lines.IndexOf(middleLine));
document.Remove(1, 3);
Assert.IsTrue(middleLine.IsDeleted);
Assert.AreEqual(-1, document.Lines.IndexOf(middleLine));
}
[Test]
public void InsertInEmptyDocument()
{
document.Insert(0, "a");
Assert.AreEqual(document.LineCount, 1);
DocumentLine line = document.GetLineByNumber(1);
Assert.AreEqual("a", document.GetText(line));
}
[Test]
public void SetText()
{
document.Text = "a";
Assert.AreEqual(document.LineCount, 1);
DocumentLine line = document.GetLineByNumber(1);
Assert.AreEqual("a", document.GetText(line));
}
[Test]
public void InsertNothing()
{
document.Insert(0, "");
Assert.AreEqual(document.LineCount, 1);
Assert.AreEqual(document.TextLength, 0);
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void InsertNull()
{
document.Insert(0, (string)null);
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void SetTextNull()
{
document.Text = null;
}
[Test]
public void RemoveNothing()
{
document.Remove(0, 0);
Assert.AreEqual(document.LineCount, 1);
Assert.AreEqual(document.TextLength, 0);
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void GetCharAt0EmptyDocument()
{
document.GetCharAt(0);
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void GetCharAtNegativeOffset()
{
document.Text = "a\nb";
document.GetCharAt(-1);
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void GetCharAtEndOffset()
{
document.Text = "a\nb";
document.GetCharAt(document.TextLength);
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void InsertAtNegativeOffset()
{
document.Text = "a\nb";
document.Insert(-1, "text");
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void InsertAfterEndOffset()
{
document.Text = "a\nb";
document.Insert(4, "text");
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void RemoveNegativeAmount()
{
document.Text = "abcd";
document.Remove(2, -1);
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void RemoveTooMuch()
{
document.Text = "abcd";
document.Remove(2, 10);
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void GetLineByNumberNegative()
{
document.Text = "a\nb";
document.GetLineByNumber(-1);
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void GetLineByNumberTooHigh()
{
document.Text = "a\nb";
document.GetLineByNumber(3);
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void GetLineByOffsetNegative()
{
document.Text = "a\nb";
document.GetLineByOffset(-1);
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void GetLineByOffsetToHigh()
{
document.Text = "a\nb";
document.GetLineByOffset(10);
}
[Test]
public void InsertAtEndOffset()
{
document.Text = "a\nb";
CheckDocumentLines("a",
"b");
document.Insert(3, "text");
CheckDocumentLines("a",
"btext");
}
[Test]
public void GetCharAt()
{
document.Text = "a\r\nb";
Assert.AreEqual('a', document.GetCharAt(0));
Assert.AreEqual('\r', document.GetCharAt(1));
Assert.AreEqual('\n', document.GetCharAt(2));
Assert.AreEqual('b', document.GetCharAt(3));
}
[Test]
public void CheckMixedNewLineTest()
{
const string mixedNewlineText = "line 1\nline 2\r\nline 3\rline 4";
document.Text = mixedNewlineText;
Assert.AreEqual(mixedNewlineText, document.Text);
Assert.AreEqual(4, document.LineCount);
for (int i = 1; i < 4; i++) {
DocumentLine line = document.GetLineByNumber(i);
Assert.AreEqual(i, line.LineNumber);
Assert.AreEqual("line " + i, document.GetText(line));
}
Assert.AreEqual(1, document.GetLineByNumber(1).DelimiterLength);
Assert.AreEqual(2, document.GetLineByNumber(2).DelimiterLength);
Assert.AreEqual(1, document.GetLineByNumber(3).DelimiterLength);
Assert.AreEqual(0, document.GetLineByNumber(4).DelimiterLength);
}
[Test]
public void LfCrIsTwoNewLinesTest()
{
document.Text = "a\n\rb";
Assert.AreEqual("a\n\rb", document.Text);
CheckDocumentLines("a",
"",
"b");
}
[Test]
public void RemoveFirstPartOfDelimiter()
{
document.Text = "a\r\nb";
document.Remove(1, 1);
Assert.AreEqual("a\nb", document.Text);
CheckDocumentLines("a",
"b");
}
[Test]
public void RemoveLineContentAndJoinDelimiters()
{
document.Text = "a\rb\nc";
document.Remove(2, 1);
Assert.AreEqual("a\r\nc", document.Text);
CheckDocumentLines("a",
"c");
}
[Test]
public void RemoveLineContentAndJoinDelimiters2()
{
document.Text = "a\rb\nc\nd";
document.Remove(2, 3);
Assert.AreEqual("a\r\nd", document.Text);
CheckDocumentLines("a",
"d");
}
[Test]
public void RemoveLineContentAndJoinDelimiters3()
{
document.Text = "a\rb\r\nc";
document.Remove(2, 2);
Assert.AreEqual("a\r\nc", document.Text);
CheckDocumentLines("a",
"c");
}
[Test]
public void RemoveLineContentAndJoinNonMatchingDelimiters()
{
document.Text = "a\nb\nc";
document.Remove(2, 1);
Assert.AreEqual("a\n\nc", document.Text);
CheckDocumentLines("a",
"",
"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]
public void RemoveMultilineUpToFirstPartOfDelimiter()
{
document.Text = "0\n1\r\n2";
document.Remove(1, 3);
Assert.AreEqual("0\n2", document.Text);
CheckDocumentLines("0",
"2");
}
[Test]
public void RemoveSecondPartOfDelimiter()
{
document.Text = "a\r\nb";
document.Remove(2, 1);
Assert.AreEqual("a\rb", document.Text);
CheckDocumentLines("a",
"b");
}
[Test]
public void RemoveFromSecondPartOfDelimiter()
{
document.Text = "a\r\nb\nc";
document.Remove(2, 3);
Assert.AreEqual("a\rc", document.Text);
CheckDocumentLines("a",
"c");
}
[Test]
public void RemoveFromSecondPartOfDelimiterToDocumentEnd()
{
document.Text = "a\r\nb";
document.Remove(2, 2);
Assert.AreEqual("a\r", document.Text);
CheckDocumentLines("a",
"");
}
[Test]
public void RemoveUpToMatchingDelimiter1()
{
document.Text = "a\r\nb\nc";
document.Remove(2, 2);
Assert.AreEqual("a\r\nc", document.Text);
CheckDocumentLines("a",
"c");
}
[Test]
public void RemoveUpToMatchingDelimiter2()
{
document.Text = "a\r\nb\r\nc";
document.Remove(2, 3);
Assert.AreEqual("a\r\nc", document.Text);
CheckDocumentLines("a",
"c");
}
[Test]
public void RemoveUpToNonMatchingDelimiter()
{
document.Text = "a\r\nb\rc";
document.Remove(2, 2);
Assert.AreEqual("a\r\rc", document.Text);
CheckDocumentLines("a",
"",
"c");
}
[Test]
public void RemoveTwoCharDelimiter()
{
document.Text = "a\r\nb";
document.Remove(1, 2);
Assert.AreEqual("ab", document.Text);
CheckDocumentLines("ab");
}
[Test]
public void RemoveOneCharDelimiter()
{
document.Text = "a\nb";
document.Remove(1, 1);
Assert.AreEqual("ab", document.Text);
CheckDocumentLines("ab");
}
void CheckDocumentLines(params string[] lines)
{
Assert.AreEqual(lines.Length, document.LineCount, "LineCount");
for (int i = 0; i < lines.Length; i++) {
Assert.AreEqual(lines[i], document.GetText(document.Lines[i]), "Text of line " + (i + 1));
}
}
[Test]
public void FixUpFirstPartOfDelimiter()
{
document.Text = "a\n\nb";
document.Replace(1, 1, "\r");
Assert.AreEqual("a\r\nb", document.Text);
CheckDocumentLines("a",
"b");
}
[Test]
public void FixUpSecondPartOfDelimiter()
{
document.Text = "a\r\rb";
document.Replace(2, 1, "\n");
Assert.AreEqual("a\r\nb", document.Text);
CheckDocumentLines("a",
"b");
}
[Test]
public void InsertInsideDelimiter()
{
document.Text = "a\r\nc";
document.Insert(2, "b");
Assert.AreEqual("a\rb\nc", document.Text);
CheckDocumentLines("a",
"b",
"c");
}
[Test]
public void InsertInsideDelimiter2()
{
document.Text = "a\r\nd";
document.Insert(2, "b\nc");
Assert.AreEqual("a\rb\nc\nd", document.Text);
CheckDocumentLines("a",
"b",
"c",
"d");
}
[Test]
public void InsertInsideDelimiter3()
{
document.Text = "a\r\nc";
document.Insert(2, "b\r");
Assert.AreEqual("a\rb\r\nc", document.Text);
CheckDocumentLines("a",
"b",
"c");
}
[Test]
public void ExtendDelimiter1()
{
document.Text = "a\nc";
document.Insert(1, "b\r");
Assert.AreEqual("ab\r\nc", document.Text);
CheckDocumentLines("ab",
"c");
}
[Test]
public void ExtendDelimiter2()
{
document.Text = "a\rc";
document.Insert(2, "\nb");
Assert.AreEqual("a\r\nbc", document.Text);
CheckDocumentLines("a",
"bc");
}
[Test]
public void ReplaceLineContentBetweenMatchingDelimiters()
{
document.Text = "a\rb\nc";
document.Replace(2, 1, "x");
Assert.AreEqual("a\rx\nc", document.Text);
CheckDocumentLines("a",
"x",
"c");
}
[Test]
public void GetOffset()
{
document.Text = "Hello,\nWorld!";
Assert.AreEqual(0, document.GetOffset(1, 1));
Assert.AreEqual(1, document.GetOffset(1, 2));
Assert.AreEqual(5, document.GetOffset(1, 6));
Assert.AreEqual(6, document.GetOffset(1, 7));
Assert.AreEqual(7, document.GetOffset(2, 1));
Assert.AreEqual(8, document.GetOffset(2, 2));
Assert.AreEqual(12, document.GetOffset(2, 6));
Assert.AreEqual(13, document.GetOffset(2, 7));
}
[Test]
public void GetOffsetIgnoreNegativeColumns()
{
document.Text = "Hello,\nWorld!";
Assert.AreEqual(0, document.GetOffset(1, -1));
Assert.AreEqual(0, document.GetOffset(1, -100));
Assert.AreEqual(0, document.GetOffset(1, 0));
Assert.AreEqual(7, document.GetOffset(2, -1));
Assert.AreEqual(7, document.GetOffset(2, -100));
Assert.AreEqual(7, document.GetOffset(2, 0));
}
[Test]
public void GetOffsetIgnoreTooHighColumns()
{
document.Text = "Hello,\nWorld!";
Assert.AreEqual(6, document.GetOffset(1, 8));
Assert.AreEqual(6, document.GetOffset(1, 100));
Assert.AreEqual(13, document.GetOffset(2, 8));
Assert.AreEqual(13, document.GetOffset(2, 100));
}
}
}

183
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/RandomizedLineManagerTest.cs

@ -1,183 +0,0 @@ @@ -1,183 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using ICSharpCode.AvalonEdit.Rendering;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// A randomized test for the line manager.
/// </summary>
[TestFixture]
public class RandomizedLineManagerTest
{
TextDocument document;
Random rnd;
[TestFixtureSetUp]
public void FixtureSetup()
{
int seed = Environment.TickCount;
Console.WriteLine("RandomizedLineManagerTest Seed: " + seed);
rnd = new Random(seed);
}
[SetUp]
public void Setup()
{
document = new TextDocument();
}
[Test]
public void ShortReplacements()
{
char[] chars = { 'a', 'b', '\r', '\n' };
char[] buffer = new char[20];
for (int i = 0; i < 2500; i++) {
int offset = rnd.Next(0, document.TextLength);
int length = rnd.Next(0, document.TextLength - offset);
int newTextLength = rnd.Next(0, 20);
for (int j = 0; j < newTextLength; j++) {
buffer[j] = chars[rnd.Next(0, chars.Length)];
}
document.Replace(offset, length, new string(buffer, 0, newTextLength));
CheckLines();
}
}
[Test]
public void LargeReplacements()
{
char[] chars = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', '\r', '\n' };
char[] buffer = new char[1000];
for (int i = 0; i < 20; i++) {
int offset = rnd.Next(0, document.TextLength);
int length = rnd.Next(0, (document.TextLength - offset) / 4);
int newTextLength = rnd.Next(0, 1000);
for (int j = 0; j < newTextLength; j++) {
buffer[j] = chars[rnd.Next(0, chars.Length)];
}
string newText = new string(buffer, 0, newTextLength);
string expectedText = document.Text.Remove(offset, length).Insert(offset, newText);
document.Replace(offset, length, newText);
Assert.AreEqual(expectedText, document.Text);
CheckLines();
}
}
void CheckLines()
{
string text = document.Text;
int lineNumber = 1;
int lineStart = 0;
for (int i = 0; i < text.Length; i++) {
char c = text[i];
if (c == '\r' && i + 1 < text.Length && text[i + 1] == '\n') {
DocumentLine line = document.GetLineByNumber(lineNumber);
Assert.AreEqual(lineNumber, line.LineNumber);
Assert.AreEqual(2, line.DelimiterLength);
Assert.AreEqual(lineStart, line.Offset);
Assert.AreEqual(i - lineStart, line.Length);
i++; // consume \n
lineNumber++;
lineStart = i+1;
} else if (c == '\r' || c == '\n') {
DocumentLine line = document.GetLineByNumber(lineNumber);
Assert.AreEqual(lineNumber, line.LineNumber);
Assert.AreEqual(1, line.DelimiterLength);
Assert.AreEqual(lineStart, line.Offset);
Assert.AreEqual(i - lineStart, line.Length);
lineNumber++;
lineStart = i+1;
}
}
Assert.AreEqual(lineNumber, document.LineCount);
}
[Test]
public void CollapsingTest()
{
char[] chars = { 'a', 'b', '\r', '\n' };
char[] buffer = new char[20];
HeightTree heightTree = new HeightTree(document, 10);
List<CollapsedLineSection> collapsedSections = new List<CollapsedLineSection>();
for (int i = 0; i < 2500; i++) {
// Console.WriteLine("Iteration " + i);
// Console.WriteLine(heightTree.GetTreeAsString());
// foreach (CollapsedLineSection cs in collapsedSections) {
// Console.WriteLine(cs);
// }
switch (rnd.Next(0, 10)) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
int offset = rnd.Next(0, document.TextLength);
int length = rnd.Next(0, document.TextLength - offset);
int newTextLength = rnd.Next(0, 20);
for (int j = 0; j < newTextLength; j++) {
buffer[j] = chars[rnd.Next(0, chars.Length)];
}
document.Replace(offset, length, new string(buffer, 0, newTextLength));
break;
case 6:
case 7:
int startLine = rnd.Next(1, document.LineCount + 1);
int endLine = rnd.Next(startLine, document.LineCount + 1);
collapsedSections.Add(heightTree.CollapseText(document.GetLineByNumber(startLine), document.GetLineByNumber(endLine)));
break;
case 8:
if (collapsedSections.Count > 0) {
CollapsedLineSection cs = collapsedSections[rnd.Next(0, collapsedSections.Count)];
// unless the text section containing the CollapsedSection was deleted:
if (cs.Start != null) {
cs.Uncollapse();
}
collapsedSections.Remove(cs);
}
break;
case 9:
foreach (DocumentLine ls in document.Lines) {
heightTree.SetHeight(ls, ls.LineNumber);
}
break;
}
var treeSections = new HashSet<CollapsedLineSection>(heightTree.GetAllCollapsedSections());
int expectedCount = 0;
foreach (CollapsedLineSection cs in collapsedSections) {
if (cs.Start != null) {
expectedCount++;
Assert.IsTrue(treeSections.Contains(cs));
}
}
Assert.AreEqual(expectedCount, treeSections.Count);
CheckLines();
HeightTests.CheckHeights(document, heightTree);
}
}
}
}

331
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/TextAnchorTest.cs

@ -1,331 +0,0 @@ @@ -1,331 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Document
{
[TestFixture]
public class TextAnchorTest
{
TextDocument document;
[SetUp]
public void SetUp()
{
document = new TextDocument();
}
[Test]
public void AnchorInEmptyDocument()
{
TextAnchor a1 = document.CreateAnchor(0);
TextAnchor a2 = document.CreateAnchor(0);
a1.MovementType = AnchorMovementType.BeforeInsertion;
a2.MovementType = AnchorMovementType.AfterInsertion;
Assert.AreEqual(0, a1.Offset);
Assert.AreEqual(0, a2.Offset);
document.Insert(0, "x");
Assert.AreEqual(0, a1.Offset);
Assert.AreEqual(1, a2.Offset);
}
[Test]
public void AnchorsSurviveDeletion()
{
document.Text = new string(' ', 10);
TextAnchor[] a1 = new TextAnchor[11];
TextAnchor[] a2 = new TextAnchor[11];
for (int i = 0; i < 11; i++) {
//Console.WriteLine("Insert first at i = " + i);
a1[i] = document.CreateAnchor(i);
a1[i].SurviveDeletion = true;
//Console.WriteLine(document.GetTextAnchorTreeAsString());
//Console.WriteLine("Insert second at i = " + i);
a2[i] = document.CreateAnchor(i);
a2[i].SurviveDeletion = false;
//Console.WriteLine(document.GetTextAnchorTreeAsString());
}
for (int i = 0; i < 11; i++) {
Assert.AreEqual(i, a1[i].Offset);
Assert.AreEqual(i, a2[i].Offset);
}
document.Remove(1, 8);
for (int i = 0; i < 11; i++) {
if (i <= 1) {
Assert.IsFalse(a1[i].IsDeleted);
Assert.IsFalse(a2[i].IsDeleted);
Assert.AreEqual(i, a1[i].Offset);
Assert.AreEqual(i, a2[i].Offset);
} else if (i <= 8) {
Assert.IsFalse(a1[i].IsDeleted);
Assert.IsTrue(a2[i].IsDeleted);
Assert.AreEqual(1, a1[i].Offset);
} else {
Assert.IsFalse(a1[i].IsDeleted);
Assert.IsFalse(a2[i].IsDeleted);
Assert.AreEqual(i - 8, a1[i].Offset);
Assert.AreEqual(i - 8, a2[i].Offset);
}
}
}
Random rnd;
[TestFixtureSetUp]
public void FixtureSetup()
{
int seed = Environment.TickCount;
Console.WriteLine("TextAnchorTest Seed: " + seed);
rnd = new Random(seed);
}
[Test]
public void CreateAnchors()
{
List<TextAnchor> anchors = new List<TextAnchor>();
List<int> expectedOffsets = new List<int>();
document.Text = new string(' ', 1000);
for (int i = 0; i < 1000; i++) {
int offset = rnd.Next(1000);
anchors.Add(document.CreateAnchor(offset));
expectedOffsets.Add(offset);
}
for (int i = 0; i < anchors.Count; i++) {
Assert.AreEqual(expectedOffsets[i], anchors[i].Offset);
}
GC.KeepAlive(anchors);
}
[Test]
public void CreateAndGCAnchors()
{
List<TextAnchor> anchors = new List<TextAnchor>();
List<int> expectedOffsets = new List<int>();
document.Text = new string(' ', 1000);
for (int t = 0; t < 250; t++) {
int c = rnd.Next(50);
if (rnd.Next(2) == 0) {
for (int i = 0; i < c; i++) {
int offset = rnd.Next(1000);
anchors.Add(document.CreateAnchor(offset));
expectedOffsets.Add(offset);
}
} else if (c <= anchors.Count) {
anchors.RemoveRange(0, c);
expectedOffsets.RemoveRange(0, c);
GC.Collect();
}
for (int j = 0; j < anchors.Count; j++) {
Assert.AreEqual(expectedOffsets[j], anchors[j].Offset);
}
}
GC.KeepAlive(anchors);
}
[Test]
public void MoveAnchorsDuringReplace()
{
document.Text = "abcd";
TextAnchor start = document.CreateAnchor(1);
TextAnchor middleDeletable = document.CreateAnchor(2);
TextAnchor middleSurvivorLeft = document.CreateAnchor(2);
middleSurvivorLeft.SurviveDeletion = true;
middleSurvivorLeft.MovementType = AnchorMovementType.BeforeInsertion;
TextAnchor middleSurvivorRight = document.CreateAnchor(2);
middleSurvivorRight.SurviveDeletion = true;
middleSurvivorRight.MovementType = AnchorMovementType.AfterInsertion;
TextAnchor end = document.CreateAnchor(3);
document.Replace(1, 2, "BxC");
Assert.AreEqual(1, start.Offset);
Assert.IsTrue(middleDeletable.IsDeleted);
Assert.AreEqual(1, middleSurvivorLeft.Offset);
Assert.AreEqual(4, middleSurvivorRight.Offset);
Assert.AreEqual(4, end.Offset);
}
[Test]
public void CreateAndMoveAnchors()
{
List<TextAnchor> anchors = new List<TextAnchor>();
List<int> expectedOffsets = new List<int>();
document.Text = new string(' ', 1000);
for (int t = 0; t < 250; t++) {
//Console.Write("t = " + t + " ");
int c = rnd.Next(50);
switch (rnd.Next(5)) {
case 0:
//Console.WriteLine("Add c=" + c + " anchors");
for (int i = 0; i < c; i++) {
int offset = rnd.Next(document.TextLength);
TextAnchor anchor = document.CreateAnchor(offset);
if (rnd.Next(2) == 0)
anchor.MovementType = AnchorMovementType.BeforeInsertion;
else
anchor.MovementType = AnchorMovementType.AfterInsertion;
anchor.SurviveDeletion = rnd.Next(2) == 0;
anchors.Add(anchor);
expectedOffsets.Add(offset);
}
break;
case 1:
if (c <= anchors.Count) {
//Console.WriteLine("Remove c=" + c + " anchors");
anchors.RemoveRange(0, c);
expectedOffsets.RemoveRange(0, c);
GC.Collect();
}
break;
case 2:
int insertOffset = rnd.Next(document.TextLength);
int insertLength = rnd.Next(1000);
//Console.WriteLine("insertOffset=" + insertOffset + " insertLength="+insertLength);
document.Insert(insertOffset, new string(' ', insertLength));
for (int i = 0; i < anchors.Count; i++) {
if (anchors[i].MovementType == AnchorMovementType.BeforeInsertion) {
if (expectedOffsets[i] > insertOffset)
expectedOffsets[i] += insertLength;
} else {
if (expectedOffsets[i] >= insertOffset)
expectedOffsets[i] += insertLength;
}
}
break;
case 3:
int removalOffset = rnd.Next(document.TextLength);
int removalLength = rnd.Next(document.TextLength - removalOffset);
//Console.WriteLine("RemovalOffset=" + removalOffset + " RemovalLength="+removalLength);
document.Remove(removalOffset, removalLength);
for (int i = anchors.Count - 1; i >= 0; i--) {
if (expectedOffsets[i] > removalOffset && expectedOffsets[i] < removalOffset + removalLength) {
if (anchors[i].SurviveDeletion) {
expectedOffsets[i] = removalOffset;
} else {
Assert.IsTrue(anchors[i].IsDeleted);
anchors.RemoveAt(i);
expectedOffsets.RemoveAt(i);
}
} else if (expectedOffsets[i] > removalOffset) {
expectedOffsets[i] -= removalLength;
}
}
break;
case 4:
int replaceOffset = rnd.Next(document.TextLength);
int replaceRemovalLength = rnd.Next(document.TextLength - replaceOffset);
int replaceInsertLength = rnd.Next(1000);
//Console.WriteLine("ReplaceOffset=" + replaceOffset + " RemovalLength="+replaceRemovalLength + " InsertLength=" + replaceInsertLength);
document.Replace(replaceOffset, replaceRemovalLength, new string(' ', replaceInsertLength));
for (int i = anchors.Count - 1; i >= 0; i--) {
if (expectedOffsets[i] > replaceOffset && expectedOffsets[i] < replaceOffset + replaceRemovalLength) {
if (anchors[i].SurviveDeletion) {
if (anchors[i].MovementType == AnchorMovementType.AfterInsertion)
expectedOffsets[i] = replaceOffset + replaceInsertLength;
else
expectedOffsets[i] = replaceOffset;
} else {
Assert.IsTrue(anchors[i].IsDeleted);
anchors.RemoveAt(i);
expectedOffsets.RemoveAt(i);
}
} else if (expectedOffsets[i] > replaceOffset) {
expectedOffsets[i] += replaceInsertLength - replaceRemovalLength;
} else if (expectedOffsets[i] == replaceOffset && replaceRemovalLength == 0 && anchors[i].MovementType == AnchorMovementType.AfterInsertion) {
expectedOffsets[i] += replaceInsertLength - replaceRemovalLength;
}
}
break;
}
Assert.AreEqual(anchors.Count, expectedOffsets.Count);
for (int j = 0; j < anchors.Count; j++) {
Assert.AreEqual(expectedOffsets[j], anchors[j].Offset);
}
}
GC.KeepAlive(anchors);
}
[Test]
public void RepeatedTextDragDrop()
{
document.Text = new string(' ', 1000);
for (int i = 0; i < 20; i++) {
TextAnchor a = document.CreateAnchor(144);
TextAnchor b = document.CreateAnchor(157);
document.Insert(128, new string('a', 13));
document.Remove(157, 13);
a = document.CreateAnchor(128);
b = document.CreateAnchor(141);
document.Insert(157, new string('b', 13));
document.Remove(128, 13);
a = null;
b = null;
if ((i % 5) == 0)
GC.Collect();
}
}
[Test]
public void ReplaceSpacesWithTab()
{
document.Text = "a b";
TextAnchor before = document.CreateAnchor(1);
before.MovementType = AnchorMovementType.AfterInsertion;
TextAnchor after = document.CreateAnchor(5);
TextAnchor survivingMiddle = document.CreateAnchor(2);
TextAnchor deletedMiddle = document.CreateAnchor(3);
document.Replace(1, 4, "\t", OffsetChangeMappingType.CharacterReplace);
Assert.AreEqual("a\tb", document.Text);
// yes, the movement is a bit strange; but that's how CharacterReplace works when the text gets shorter
Assert.AreEqual(1, before.Offset);
Assert.AreEqual(2, after.Offset);
Assert.AreEqual(2, survivingMiddle.Offset);
Assert.AreEqual(2, deletedMiddle.Offset);
}
[Test]
public void ReplaceTwoCharactersWithThree()
{
document.Text = "a12b";
TextAnchor before = document.CreateAnchor(1);
before.MovementType = AnchorMovementType.AfterInsertion;
TextAnchor after = document.CreateAnchor(3);
before.MovementType = AnchorMovementType.BeforeInsertion;
TextAnchor middleB = document.CreateAnchor(2);
before.MovementType = AnchorMovementType.BeforeInsertion;
TextAnchor middleA = document.CreateAnchor(2);
before.MovementType = AnchorMovementType.AfterInsertion;
document.Replace(1, 2, "123", OffsetChangeMappingType.CharacterReplace);
Assert.AreEqual("a123b", document.Text);
Assert.AreEqual(1, before.Offset);
Assert.AreEqual(4, after.Offset);
Assert.AreEqual(2, middleA.Offset);
Assert.AreEqual(2, middleB.Offset);
}
}
}

368
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/TextSegmentTreeTest.cs

@ -1,368 +0,0 @@ @@ -1,368 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Document
{
[TestFixture]
public class TextSegmentTreeTest
{
Random rnd;
[TestFixtureSetUp]
public void FixtureSetup()
{
int seed = Environment.TickCount;
Console.WriteLine("TextSegmentTreeTest Seed: " + seed);
rnd = new Random(seed);
}
class TestTextSegment : TextSegment
{
internal int ExpectedOffset, ExpectedLength;
public TestTextSegment(int expectedOffset, int expectedLength)
{
this.ExpectedOffset = expectedOffset;
this.ExpectedLength = expectedLength;
this.StartOffset = expectedOffset;
this.Length = expectedLength;
}
}
TextSegmentCollection<TestTextSegment> tree;
List<TestTextSegment> expectedSegments;
[SetUp]
public void SetUp()
{
tree = new TextSegmentCollection<TestTextSegment>();
expectedSegments = new List<TestTextSegment>();
}
[Test]
public void FindInEmptyTree()
{
Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(0));
Assert.AreEqual(0, tree.FindSegmentsContaining(0).Count);
Assert.AreEqual(0, tree.FindOverlappingSegments(10, 20).Count);
}
[Test]
public void FindFirstSegmentWithStartAfter()
{
var s1 = new TestTextSegment(5, 10);
var s2 = new TestTextSegment(10, 10);
tree.Add(s1);
tree.Add(s2);
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(-100));
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(0));
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(4));
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(5));
Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(6));
Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(9));
Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(10));
Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(11));
Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(100));
}
[Test]
public void FindFirstSegmentWithStartAfterWithDuplicates()
{
var s1 = new TestTextSegment(5, 10);
var s1b = new TestTextSegment(5, 7);
var s2 = new TestTextSegment(10, 10);
var s2b = new TestTextSegment(10, 7);
tree.Add(s1);
tree.Add(s1b);
tree.Add(s2);
tree.Add(s2b);
Assert.AreSame(s1b, tree.GetNextSegment(s1));
Assert.AreSame(s2b, tree.GetNextSegment(s2));
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(-100));
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(0));
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(4));
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(5));
Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(6));
Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(9));
Assert.AreSame(s2, tree.FindFirstSegmentWithStartAfter(10));
Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(11));
Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(100));
}
[Test]
public void FindFirstSegmentWithStartAfterWithDuplicates2()
{
var s1 = new TestTextSegment(5, 1);
var s2 = new TestTextSegment(5, 2);
var s3 = new TestTextSegment(5, 3);
var s4 = new TestTextSegment(5, 4);
tree.Add(s1);
tree.Add(s2);
tree.Add(s3);
tree.Add(s4);
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(0));
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(1));
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(4));
Assert.AreSame(s1, tree.FindFirstSegmentWithStartAfter(5));
Assert.AreSame(null, tree.FindFirstSegmentWithStartAfter(6));
}
TestTextSegment AddSegment(int offset, int length)
{
// Console.WriteLine("Add " + offset + ", " + length);
TestTextSegment s = new TestTextSegment(offset, length);
tree.Add(s);
expectedSegments.Add(s);
return s;
}
void RemoveSegment(TestTextSegment s)
{
// Console.WriteLine("Remove " + s);
expectedSegments.Remove(s);
tree.Remove(s);
}
void TestRetrieval(int offset, int length)
{
HashSet<TestTextSegment> actual = new HashSet<TestTextSegment>(tree.FindOverlappingSegments(offset, length));
HashSet<TestTextSegment> expected = new HashSet<TestTextSegment>();
foreach (TestTextSegment e in expectedSegments) {
if (e.ExpectedOffset + e.ExpectedLength < offset)
continue;
if (e.ExpectedOffset > offset + length)
continue;
expected.Add(e);
}
Assert.IsTrue(actual.IsSubsetOf(expected));
Assert.IsTrue(expected.IsSubsetOf(actual));
}
void CheckSegments()
{
Assert.AreEqual(expectedSegments.Count, tree.Count);
foreach (TestTextSegment s in expectedSegments) {
Assert.AreEqual(s.ExpectedOffset, s.StartOffset /*, "startoffset for " + s*/);
Assert.AreEqual(s.ExpectedLength, s.Length /*, "length for " + s*/);
}
}
[Test]
public void AddSegments()
{
TestTextSegment s1 = AddSegment(10, 20);
TestTextSegment s2 = AddSegment(15, 10);
CheckSegments();
}
void ChangeDocument(OffsetChangeMapEntry change)
{
tree.UpdateOffsets(change);
foreach (TestTextSegment s in expectedSegments) {
int endOffset = s.ExpectedOffset + s.ExpectedLength;
s.ExpectedOffset = change.GetNewOffset(s.ExpectedOffset, AnchorMovementType.AfterInsertion);
s.ExpectedLength = Math.Max(0, change.GetNewOffset(endOffset, AnchorMovementType.BeforeInsertion) - s.ExpectedOffset);
}
}
[Test]
public void InsertionBeforeAllSegments()
{
TestTextSegment s1 = AddSegment(10, 20);
TestTextSegment s2 = AddSegment(15, 10);
ChangeDocument(new OffsetChangeMapEntry(5, 0, 2));
CheckSegments();
}
[Test]
public void ReplacementBeforeAllSegmentsTouchingFirstSegment()
{
TestTextSegment s1 = AddSegment(10, 20);
TestTextSegment s2 = AddSegment(15, 10);
ChangeDocument(new OffsetChangeMapEntry(5, 5, 2));
CheckSegments();
}
[Test]
public void InsertionAfterAllSegments()
{
TestTextSegment s1 = AddSegment(10, 20);
TestTextSegment s2 = AddSegment(15, 10);
ChangeDocument(new OffsetChangeMapEntry(45, 0, 2));
CheckSegments();
}
[Test]
public void ReplacementOverlappingWithStartOfSegment()
{
TestTextSegment s1 = AddSegment(10, 20);
TestTextSegment s2 = AddSegment(15, 10);
ChangeDocument(new OffsetChangeMapEntry(9, 7, 2));
CheckSegments();
}
[Test]
public void ReplacementOfWholeSegment()
{
TestTextSegment s1 = AddSegment(10, 20);
TestTextSegment s2 = AddSegment(15, 10);
ChangeDocument(new OffsetChangeMapEntry(10, 20, 30));
CheckSegments();
}
[Test]
public void ReplacementAtEndOfSegment()
{
TestTextSegment s1 = AddSegment(10, 20);
TestTextSegment s2 = AddSegment(15, 10);
ChangeDocument(new OffsetChangeMapEntry(24, 6, 10));
CheckSegments();
}
[Test]
public void RandomizedNoDocumentChanges()
{
for (int i = 0; i < 1000; i++) {
// Console.WriteLine(tree.GetTreeAsString());
// Console.WriteLine("Iteration " + i);
switch (rnd.Next(3)) {
case 0:
AddSegment(rnd.Next(500), rnd.Next(30));
break;
case 1:
AddSegment(rnd.Next(500), rnd.Next(300));
break;
case 2:
if (tree.Count > 0) {
RemoveSegment(expectedSegments[rnd.Next(tree.Count)]);
}
break;
}
CheckSegments();
}
}
[Test]
public void RandomizedCloseNoDocumentChanges()
{
// Lots of segments in a short document. Tests how the tree copes with multiple identical segments.
for (int i = 0; i < 1000; i++) {
switch (rnd.Next(3)) {
case 0:
AddSegment(rnd.Next(20), rnd.Next(10));
break;
case 1:
AddSegment(rnd.Next(20), rnd.Next(20));
break;
case 2:
if (tree.Count > 0) {
RemoveSegment(expectedSegments[rnd.Next(tree.Count)]);
}
break;
}
CheckSegments();
}
}
[Test]
public void RandomizedRetrievalTest()
{
for (int i = 0; i < 1000; i++) {
AddSegment(rnd.Next(500), rnd.Next(300));
}
CheckSegments();
for (int i = 0; i < 1000; i++) {
TestRetrieval(rnd.Next(1000) - 100, rnd.Next(500));
}
}
[Test]
public void RandomizedWithDocumentChanges()
{
for (int i = 0; i < 500; i++) {
// Console.WriteLine(tree.GetTreeAsString());
// Console.WriteLine("Iteration " + i);
switch (rnd.Next(6)) {
case 0:
AddSegment(rnd.Next(500), rnd.Next(30));
break;
case 1:
AddSegment(rnd.Next(500), rnd.Next(300));
break;
case 2:
if (tree.Count > 0) {
RemoveSegment(expectedSegments[rnd.Next(tree.Count)]);
}
break;
case 3:
ChangeDocument(new OffsetChangeMapEntry(rnd.Next(800), rnd.Next(50), rnd.Next(50)));
break;
case 4:
ChangeDocument(new OffsetChangeMapEntry(rnd.Next(800), 0, rnd.Next(50)));
break;
case 5:
ChangeDocument(new OffsetChangeMapEntry(rnd.Next(800), rnd.Next(50), 0));
break;
}
CheckSegments();
}
}
[Test]
public void RandomizedWithDocumentChangesClose()
{
for (int i = 0; i < 500; i++) {
// Console.WriteLine(tree.GetTreeAsString());
// Console.WriteLine("Iteration " + i);
switch (rnd.Next(6)) {
case 0:
AddSegment(rnd.Next(50), rnd.Next(30));
break;
case 1:
AddSegment(rnd.Next(50), rnd.Next(3));
break;
case 2:
if (tree.Count > 0) {
RemoveSegment(expectedSegments[rnd.Next(tree.Count)]);
}
break;
case 3:
ChangeDocument(new OffsetChangeMapEntry(rnd.Next(80), rnd.Next(10), rnd.Next(10)));
break;
case 4:
ChangeDocument(new OffsetChangeMapEntry(rnd.Next(80), 0, rnd.Next(10)));
break;
case 5:
ChangeDocument(new OffsetChangeMapEntry(rnd.Next(80), rnd.Next(10), 0));
break;
}
CheckSegments();
}
}
}
}

94
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/TextUtilitiesTests.cs

@ -1,94 +0,0 @@ @@ -1,94 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Document
{
[TestFixture]
public class TextUtilitiesTests
{
#region GetWhitespaceAfter
[Test]
public void TestGetWhitespaceAfter()
{
Assert.AreEqual(new SimpleSegment(2, 3), TextUtilities.GetWhitespaceAfter(new StringTextSource("a \t \tb"), 2));
}
[Test]
public void TestGetWhitespaceAfterDoesNotSkipNewLine()
{
Assert.AreEqual(new SimpleSegment(2, 3), TextUtilities.GetWhitespaceAfter(new StringTextSource("a \t \tb"), 2));
}
[Test]
public void TestGetWhitespaceAfterEmptyResult()
{
Assert.AreEqual(new SimpleSegment(2, 0), TextUtilities.GetWhitespaceAfter(new StringTextSource("a b"), 2));
}
[Test]
public void TestGetWhitespaceAfterEndOfString()
{
Assert.AreEqual(new SimpleSegment(2, 0), TextUtilities.GetWhitespaceAfter(new StringTextSource("a "), 2));
}
[Test]
public void TestGetWhitespaceAfterUntilEndOfString()
{
Assert.AreEqual(new SimpleSegment(2, 3), TextUtilities.GetWhitespaceAfter(new StringTextSource("a \t \t"), 2));
}
#endregion
#region GetWhitespaceBefore
[Test]
public void TestGetWhitespaceBefore()
{
Assert.AreEqual(new SimpleSegment(1, 3), TextUtilities.GetWhitespaceBefore(new StringTextSource("a\t \t b"), 4));
}
[Test]
public void TestGetWhitespaceBeforeDoesNotSkipNewLine()
{
Assert.AreEqual(new SimpleSegment(2, 1), TextUtilities.GetWhitespaceBefore(new StringTextSource("a\n b"), 3));
}
[Test]
public void TestGetWhitespaceBeforeEmptyResult()
{
Assert.AreEqual(new SimpleSegment(2, 0), TextUtilities.GetWhitespaceBefore(new StringTextSource(" a b"), 2));
}
[Test]
public void TestGetWhitespaceBeforeStartOfString()
{
Assert.AreEqual(new SimpleSegment(0, 0), TextUtilities.GetWhitespaceBefore(new StringTextSource(" a"), 0));
}
[Test]
public void TestGetWhitespaceBeforeUntilStartOfString()
{
Assert.AreEqual(new SimpleSegment(0, 2), TextUtilities.GetWhitespaceBefore(new StringTextSource(" \t a"), 2));
}
#endregion
}
}

92
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/UndoStackTests.cs

@ -1,92 +0,0 @@ @@ -1,92 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Document
{
public class UndoStackTests
{
[Test]
public void ContinueUndoGroup()
{
var doc = new TextDocument();
doc.Insert(0, "a");
doc.UndoStack.StartContinuedUndoGroup();
doc.Insert(1, "b");
doc.UndoStack.EndUndoGroup();
doc.UndoStack.Undo();
Assert.AreEqual("", doc.Text);
}
[Test]
public void ContinueEmptyUndoGroup()
{
var doc = new TextDocument();
doc.Insert(0, "a");
doc.UndoStack.StartUndoGroup();
doc.UndoStack.EndUndoGroup();
doc.UndoStack.StartContinuedUndoGroup();
doc.Insert(1, "b");
doc.UndoStack.EndUndoGroup();
doc.UndoStack.Undo();
Assert.AreEqual("a", doc.Text);
}
[Test]
public void ContinueEmptyUndoGroup_WithOptionalEntries()
{
var doc = new TextDocument();
doc.Insert(0, "a");
doc.UndoStack.StartUndoGroup();
doc.UndoStack.PushOptional(new StubUndoableAction());
doc.UndoStack.EndUndoGroup();
doc.UndoStack.StartContinuedUndoGroup();
doc.Insert(1, "b");
doc.UndoStack.EndUndoGroup();
doc.UndoStack.Undo();
Assert.AreEqual("a", doc.Text);
}
[Test]
public void EmptyContinuationGroup()
{
var doc = new TextDocument();
doc.Insert(0, "a");
doc.UndoStack.StartContinuedUndoGroup();
doc.UndoStack.EndUndoGroup();
doc.UndoStack.StartContinuedUndoGroup();
doc.Insert(1, "b");
doc.UndoStack.EndUndoGroup();
doc.UndoStack.Undo();
Assert.AreEqual("", doc.Text);
}
class StubUndoableAction : IUndoableOperation
{
public void Undo()
{
}
public void Redo()
{
}
}
}
}

78
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Editing/ChangeDocumentTests.cs

@ -1,78 +0,0 @@ @@ -1,78 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Text;
using ICSharpCode.AvalonEdit.Document;
#if NREFACTORY
using ICSharpCode.NRefactory;
#endif
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Editing
{
[TestFixture]
public class ChangeDocumentTests
{
[Test]
public void ClearCaretAndSelectionOnDocumentChange()
{
TextArea textArea = new TextArea();
textArea.Document = new TextDocument("1\n2\n3\n4th line");
textArea.Caret.Offset = 6;
textArea.Selection = Selection.Create(textArea, 3, 6);
textArea.Document = new TextDocument("1\n2nd");
Assert.AreEqual(0, textArea.Caret.Offset);
Assert.AreEqual(new TextLocation(1, 1), textArea.Caret.Location);
Assert.IsTrue(textArea.Selection.IsEmpty);
}
[Test]
public void SetDocumentToNull()
{
TextArea textArea = new TextArea();
textArea.Document = new TextDocument("1\n2\n3\n4th line");
textArea.Caret.Offset = 6;
textArea.Selection = Selection.Create(textArea, 3, 6);
textArea.Document = null;
Assert.AreEqual(0, textArea.Caret.Offset);
Assert.AreEqual(new TextLocation(1, 1), textArea.Caret.Location);
Assert.IsTrue(textArea.Selection.IsEmpty);
}
[Test]
public void CheckEventOrderOnDocumentChange()
{
TextArea textArea = new TextArea();
TextDocument newDocument = new TextDocument();
StringBuilder b = new StringBuilder();
textArea.TextView.DocumentChanged += delegate {
b.Append("TextView.DocumentChanged;");
Assert.AreSame(newDocument, textArea.TextView.Document);
Assert.AreSame(newDocument, textArea.Document);
};
textArea.DocumentChanged += delegate {
b.Append("TextArea.DocumentChanged;");
Assert.AreSame(newDocument, textArea.TextView.Document);
Assert.AreSame(newDocument, textArea.Document);
};
textArea.Document = newDocument;
Assert.AreEqual("TextView.DocumentChanged;TextArea.DocumentChanged;", b.ToString());
}
}
}

183
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Editing/TextSegmentReadOnlySectionTests.cs

@ -1,183 +0,0 @@ @@ -1,183 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using ICSharpCode.AvalonEdit.Document;
using System;
using System.Linq;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Editing
{
[TestFixture]
public class TextSegmentReadOnlySectionTests
{
TextSegmentCollection<TextSegment> segments;
TextSegmentReadOnlySectionProvider<TextSegment> provider;
[SetUp]
public void SetUp()
{
segments = new TextSegmentCollection<TextSegment>();
provider = new TextSegmentReadOnlySectionProvider<TextSegment>(segments);
}
[Test]
public void InsertionPossibleWhenNothingIsReadOnly()
{
Assert.IsTrue(provider.CanInsert(0));
Assert.IsTrue(provider.CanInsert(100));
}
[Test]
public void DeletionPossibleWhenNothingIsReadOnly()
{
var result = provider.GetDeletableSegments(new SimpleSegment(10, 20)).ToList();
Assert.AreEqual(1, result.Count);
Assert.AreEqual(10, result[0].Offset);
Assert.AreEqual(20, result[0].Length);
}
[Test]
public void EmptyDeletionPossibleWhenNothingIsReadOnly()
{
var result = provider.GetDeletableSegments(new SimpleSegment(10, 0)).ToList();
Assert.AreEqual(1, result.Count);
Assert.AreEqual(10, result[0].Offset);
Assert.AreEqual(0, result[0].Length);
}
[Test]
public void InsertionPossibleBeforeReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 10, EndOffset = 15 });
Assert.IsTrue(provider.CanInsert(5));
}
[Test]
public void InsertionPossibleAtStartOfReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 10, EndOffset = 15 });
Assert.IsTrue(provider.CanInsert(10));
}
[Test]
public void InsertionImpossibleInsideReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 10, EndOffset = 15 });
Assert.IsFalse(provider.CanInsert(11));
Assert.IsFalse(provider.CanInsert(12));
Assert.IsFalse(provider.CanInsert(13));
Assert.IsFalse(provider.CanInsert(14));
}
[Test]
public void InsertionPossibleAtEndOfReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 10, EndOffset = 15 });
Assert.IsTrue(provider.CanInsert(15));
}
[Test]
public void InsertionPossibleBetweenReadOnlySegments()
{
segments.Add(new TextSegment { StartOffset = 10, EndOffset = 15 });
segments.Add(new TextSegment { StartOffset = 15, EndOffset = 20 });
Assert.IsTrue(provider.CanInsert(15));
}
[Test]
public void DeletionImpossibleInReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 10, Length = 5 });
var result = provider.GetDeletableSegments(new SimpleSegment(11, 2)).ToList();
Assert.AreEqual(0, result.Count);
}
[Test]
public void EmptyDeletionImpossibleInReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 10, Length = 5 });
var result = provider.GetDeletableSegments(new SimpleSegment(11, 0)).ToList();
Assert.AreEqual(0, result.Count);
}
[Test]
public void EmptyDeletionPossibleAtStartOfReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 10, Length = 5 });
var result = provider.GetDeletableSegments(new SimpleSegment(10, 0)).ToList();
Assert.AreEqual(1, result.Count);
Assert.AreEqual(10, result[0].Offset);
Assert.AreEqual(0, result[0].Length);
}
[Test]
public void EmptyDeletionPossibleAtEndOfReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 10, Length = 5 });
var result = provider.GetDeletableSegments(new SimpleSegment(15, 0)).ToList();
Assert.AreEqual(1, result.Count);
Assert.AreEqual(15, result[0].Offset);
Assert.AreEqual(0, result[0].Length);
}
[Test]
public void DeletionAroundReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 20, Length = 5 });
var result = provider.GetDeletableSegments(new SimpleSegment(15, 16)).ToList();
Assert.AreEqual(2, result.Count);
Assert.AreEqual(15, result[0].Offset);
Assert.AreEqual(5, result[0].Length);
Assert.AreEqual(25, result[1].Offset);
Assert.AreEqual(6, result[1].Length);
}
[Test]
public void DeleteLastCharacterInReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 20, Length = 5 });
var result = provider.GetDeletableSegments(new SimpleSegment(24, 1)).ToList();
Assert.AreEqual(0, result.Count);
/* // we would need this result for the old Backspace code so that the last character doesn't get selected:
Assert.AreEqual(1, result.Count);
Assert.AreEqual(25, result[0].Offset);
Assert.AreEqual(0, result[0].Length);*/
}
[Test]
public void DeleteFirstCharacterInReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 20, Length = 5 });
var result = provider.GetDeletableSegments(new SimpleSegment(20, 1)).ToList();
Assert.AreEqual(0, result.Count);
/* // we would need this result for the old Delete code so that the first character doesn't get selected:
Assert.AreEqual(1, result.Count);
Assert.AreEqual(2, result[0].Offset);
Assert.AreEqual(0, result[0].Length);*/
}
[Test]
public void DeleteWholeReadOnlySegment()
{
segments.Add(new TextSegment { StartOffset = 20, Length = 5 });
var result = provider.GetDeletableSegments(new SimpleSegment(20, 5)).ToList();
Assert.AreEqual(0, result.Count);
}
}
}

182
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Highlighting/HighlightedLineMergeTests.cs

@ -1,182 +0,0 @@ @@ -1,182 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.NRefactory.Editor;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Highlighting
{
[TestFixture]
public class HighlightedLineMergeTests
{
IDocument document = new TextDocument(new string(' ', 20));
[Test]
public void SimpleMerge1()
{
HighlightedLine baseLine = new HighlightedLine(document, document.GetLineByNumber(1));
baseLine.Sections.Add(MakeSection(0, 1, "B"));
HighlightedLine additionalLine = new HighlightedLine(document, document.GetLineByNumber(1));
additionalLine.Sections.Add(MakeSection(0, 2, "A"));
baseLine.MergeWith(additionalLine);
// The additional section gets split up so that it fits into the tree structure
Assert.That(baseLine.Sections, Is.EqualTo(
new[] {
MakeSection(0, 1, "B"),
MakeSection(0, 1, "A"),
MakeSection(1, 2, "A")
}).Using(new SectionComparer()));
}
[Test]
public void SimpleMerge2()
{
HighlightedLine baseLine = new HighlightedLine(document, document.GetLineByNumber(1));
baseLine.Sections.Add(MakeSection(0, 1, "B"));
baseLine.Sections.Add(MakeSection(0, 1, "BN"));
HighlightedLine additionalLine = new HighlightedLine(document, document.GetLineByNumber(1));
additionalLine.Sections.Add(MakeSection(0, 2, "A"));
baseLine.MergeWith(additionalLine);
// The additional section gets split up so that it fits into the tree structure
Assert.That(baseLine.Sections, Is.EqualTo(
new[] {
MakeSection(0, 1, "B"),
MakeSection(0, 1, "BN"),
MakeSection(0, 1, "A"),
MakeSection(1, 2, "A")
}).Using(new SectionComparer()));
}
HighlightedSection MakeSection(int start, int end, string name)
{
return new HighlightedSection { Offset = start, Length = end - start, Color = new HighlightingColor { Name = name }};
}
class SectionComparer : IEqualityComparer<HighlightedSection>
{
public bool Equals(HighlightedSection a, HighlightedSection b)
{
return a.Offset == b.Offset && a.Length == b.Length && a.Color.Name == b.Color.Name;
}
public int GetHashCode(HighlightedSection obj)
{
return obj.Offset;
}
}
#region Automatic Test
/*
const int combinations = 6 * 3 * 4 * 3 * 3 * 4;
HighlightingColor[] baseLineColors = {
new HighlightingColor { Name = "Base-A" },
new HighlightingColor { Name = "Base-B" },
new HighlightingColor { Name = "Base-N" },
new HighlightingColor { Name = "Base-C" }
};
HighlightingColor[] additionalLineColors = {
new HighlightingColor { Name = "Add-A" },
new HighlightingColor { Name = "Add-B" },
new HighlightingColor { Name = "Add-N" },
new HighlightingColor { Name = "Add-C" }
};
HighlightedLine BuildHighlightedLine(int num, HighlightingColor[] colors)
{
// We are build a HighlightedLine with 4 segments:
// A B C (top-level) and N nested within B.
// These are the integers controlling the generating process:
int aStart = GetNum(ref num, 5); // start offset of A
int aLength = GetNum(ref num, 2); // length of A
int bDistance = GetNum(ref num, 3); // distance from start of B to end of A
int bStart = aStart + aLength + bDistance;
int nDistance = GetNum(ref num, 2); // distance from start of B to start of N, range 0-2
int nLength = GetNum(ref num, 2); // length of N
int bEndDistance = GetNum(ref num, 2); // distance from end of N to end of B
int bLength = nDistance + nLength + bEndDistance;
int cDistance = GetNum(ref num, 3); // distance from end of B to start of C
int cStart = bStart + bLength + cDistance;
int cLength = 1;
Assert.AreEqual(0, num);
var documentLine = document.GetLineByNumber(1);
HighlightedLine line = new HighlightedLine(document, documentLine);
line.Sections.Add(new HighlightedSection { Offset = aStart, Length = aLength, Color = colors[0] });
line.Sections.Add(new HighlightedSection { Offset = bStart, Length = bLength, Color = colors[1] });
line.Sections.Add(new HighlightedSection { Offset = bStart + nDistance, Length = nLength, Color = colors[2] });
line.Sections.Add(new HighlightedSection { Offset = cStart, Length = cLength, Color = colors[3] });
return line;
}
/// <summary>
/// Gets a number between 0 and max (inclusive)
/// </summary>
int GetNum(ref int num, int max)
{
int result = num % (max+1);
num = num / (max + 1);
return result;
}
[Test]
public void TestAll()
{
for (int c1 = 0; c1 < combinations; c1++) {
HighlightedLine line1 = BuildHighlightedLine(c1, additionalLineColors);
for (int c2 = 0; c2 < combinations; c2++) {
HighlightedLine line2 = BuildHighlightedLine(c2, baseLineColors);
HighlightingColor[] expectedPerCharColors = new HighlightingColor[document.TextLength];
ApplyColors(expectedPerCharColors, line2);
ApplyColors(expectedPerCharColors, line1);
try {
line2.MergeWith(line1);
} catch (InvalidOperationException ex) {
throw new InvalidOperationException(string.Format("Error for c1 = {0}, c2 = {1}", c1, c2), ex);
}
HighlightingColor[] actualPerCharColors = new HighlightingColor[document.TextLength];
ApplyColors(actualPerCharColors, line2);
Assert.AreEqual(expectedPerCharColors, actualPerCharColors, string.Format("c1 = {0}, c2 = {1}", c1, c2));
}
}
}
void ApplyColors(HighlightingColor[] perCharColors, HighlightedLine line)
{
foreach (var section in line.Sections) {
for (int i = 0; i < section.Length; i++) {
perCharColors[section.Offset + i] = section.Color;
}
}
}
*/
#endregion
}
}

56
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Highlighting/HtmlClipboardTests.cs

@ -1,56 +0,0 @@ @@ -1,56 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows;
using ICSharpCode.AvalonEdit.Document;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Highlighting
{
[TestFixture]
public class HtmlClipboardTests
{
TextDocument document;
DocumentHighlighter highlighter;
public HtmlClipboardTests()
{
document = new TextDocument("using System.Text;\n\tstring text = SomeMethod();");
highlighter = new DocumentHighlighter(document, HighlightingManager.Instance.GetDefinition("C#"));
}
[Test]
public void FullDocumentTest()
{
var segment = new TextSegment { StartOffset = 0, Length = document.TextLength };
string html = HtmlClipboard.CreateHtmlFragment(document, highlighter, segment, new HtmlOptions());
Assert.AreEqual("<span style=\"color: #008000; font-weight: bold; \">using</span> System.Text;<br>" + Environment.NewLine +
"&nbsp;&nbsp;&nbsp;&nbsp;<span style=\"color: #ff0000; \">string</span> " +
"text = <span style=\"color: #191970; font-weight: bold; \">SomeMethod</span>();", html);
}
[Test]
public void PartOfHighlightedWordTest()
{
var segment = new TextSegment { StartOffset = 1, Length = 3 };
string html = HtmlClipboard.CreateHtmlFragment(document, highlighter, segment, new HtmlOptions());
Assert.AreEqual("<span style=\"color: #008000; font-weight: bold; \">sin</span>", html);
}
}
}

3
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.PartCover.Settings

@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
<PartCoverSettings>
<Rule>+[ICSharpCode.AvalonEdit]*</Rule>
</PartCoverSettings>

126
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj

@ -1,126 +0,0 @@ @@ -1,126 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{6222A3A1-83CE-47A3-A4E4-A018F82D44D8}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">Net40</Platform>
<OutputType>Library</OutputType>
<RootNamespace>ICSharpCode.AvalonEdit</RootNamespace>
<AssemblyName>ICSharpCode.AvalonEdit.Tests</AssemblyName>
<TargetFrameworkVersion Condition=" '$(Configuration)' == '' ">v4.0</TargetFrameworkVersion>
<AppDesignerFolder>Properties</AppDesignerFolder>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\ICSharpCode.AvalonEdit\ICSharpCode.AvalonEdit.snk</AssemblyOriginatorKeyFile>
<DelaySign>False</DelaySign>
<AssemblyOriginatorKeyMode>File</AssemblyOriginatorKeyMode>
<AllowUnsafeBlocks>False</AllowUnsafeBlocks>
<NoStdLib>False</NoStdLib>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
<OutputPath>bin\$(Configuration)\</OutputPath>
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<NoWin32Manifest>False</NoWin32Manifest>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<RegisterForComInterop>False</RegisterForComInterop>
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
<BaseAddress>4194304</BaseAddress>
<PlatformTarget>AnyCPU</PlatformTarget>
<FileAlignment>4096</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'Net35' ">
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'Net40' ">
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<DefineConstants>DOTNET4</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'WithNRefactory' ">
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<DefineConstants>NREFACTORY</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>Full</DebugType>
<Optimize>False</Optimize>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<BaseIntermediateOutputPath>obj\</BaseIntermediateOutputPath>
<DefineDebug>True</DefineDebug>
<DefineTrace>True</DefineTrace>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>false</DebugSymbols>
<DebugType>None</DebugType>
<Optimize>True</Optimize>
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
<BaseIntermediateOutputPath>obj\</BaseIntermediateOutputPath>
<DefineTrace>True</DefineTrace>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
<ItemGroup>
<ProjectReference Include="..\..\NRefactory\ICSharpCode.NRefactory\ICSharpCode.NRefactory.csproj" Condition="$(DefineConstants.Contains('NREFACTORY'))">
<Project>{3B2A5653-EC97-4001-BB9B-D90F1AF2C371}</Project>
<Name>ICSharpCode.NRefactory</Name>
</ProjectReference>
<Reference Include="nunit.framework">
<HintPath>..\..\..\Tools\NUnit\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="PresentationCore">
<RequiredTargetFramework>3.0</RequiredTargetFramework>
</Reference>
<Reference Include="PresentationFramework">
<RequiredTargetFramework>3.0</RequiredTargetFramework>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase">
<RequiredTargetFramework>3.0</RequiredTargetFramework>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Document\ChangeTrackingTest.cs" />
<Compile Include="Document\TextAnchorTest.cs" />
<Compile Include="Document\TextSegmentTreeTest.cs" />
<Compile Include="Document\TextUtilitiesTests.cs" />
<Compile Include="Document\UndoStackTests.cs" />
<Compile Include="Editing\ChangeDocumentTests.cs" />
<Compile Include="Editing\TextSegmentReadOnlySectionTests.cs" />
<Compile Include="Highlighting\HighlightedLineMergeTests.cs" />
<Compile Include="Highlighting\HtmlClipboardTests.cs" />
<Compile Include="MultipleUIThreads.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Document\CollapsingTests.cs" />
<Compile Include="Document\HeightTests.cs" />
<Compile Include="Document\RandomizedLineManagerTest.cs" />
<Compile Include="Document\LineManagerTests.cs" />
<Compile Include="Search\FindTests.cs" />
<Compile Include="Utils\CaretNavigationTests.cs" />
<Compile Include="Utils\CompressingTreeListTests.cs" />
<Compile Include="Utils\ExtensionMethodsTests.cs" />
<Compile Include="Utils\IndentationStringTests.cs" />
<Compile Include="Utils\RopeTests.cs" />
<Compile Include="WeakReferenceTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ICSharpCode.AvalonEdit\ICSharpCode.AvalonEdit.csproj">
<Project>{6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}</Project>
<Name>ICSharpCode.AvalonEdit</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Search" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

44
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/MultipleUIThreads.cs

@ -1,44 +0,0 @@ @@ -1,44 +0,0 @@
// 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.Threading;
using System.Windows;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit
{
[TestFixture]
public class MultipleUIThreads
{
Exception error;
[Test]
public void CreateEditorInstancesOnMultipleUIThreads()
{
Thread t1 = new Thread(new ThreadStart(Run));
Thread t2 = new Thread(new ThreadStart(Run));
t1.SetApartmentState(ApartmentState.STA);
t2.SetApartmentState(ApartmentState.STA);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
if (error != null)
throw new InvalidOperationException(error.Message, error);
}
[STAThread]
void Run()
{
try {
var window = new Window();
window.Content = new TextEditor();
window.ShowActivated = false;
window.Show();
} catch (Exception ex) {
error = ex;
}
}
}
}

55
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Properties/AssemblyInfo.cs

@ -1,55 +0,0 @@ @@ -1,55 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#region Using directives
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using NUnit.Framework;
#endregion
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ICSharpCode.AvalonEdit.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ICSharpCode.AvalonEdit.Tests")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// This sets the default COM visibility of types in the assembly to invisible.
// If you need to expose a type to COM, use [ComVisible(true)] on that type.
[assembly: ComVisible(false)]
// The assembly version has following format :
//
// Major.Minor.Build.Revision
//
// You can specify all the values or you can use the default the Revision and
// Build Numbers by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.*")]
// Run unit tests on STA thread.
[assembly: RequiresSTA]
namespace ICSharpCode.NRefactory.Editor {}

132
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Search/FindTests.cs

@ -1,132 +0,0 @@ @@ -1,132 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using ICSharpCode.AvalonEdit.Document;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Search
{
[TestFixture]
public class FindTests
{
[Test]
public void SkipWordBorderSimple()
{
var strategy = SearchStrategyFactory.Create("All", false, true, SearchMode.Normal);
var text = new StringTextSource(" FindAllTests ");
var results = strategy.FindAll(text, 0, text.TextLength).ToArray();
Assert.IsEmpty(results, "No results should be found!");
}
[Test]
public void SkipWordBorder()
{
var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal);
var text = new StringTextSource("name=\"{FindAllTests}\"");
var results = strategy.FindAll(text, 0, text.TextLength).ToArray();
Assert.IsEmpty(results, "No results should be found!");
}
[Test]
public void SkipWordBorder2()
{
var strategy = SearchStrategyFactory.Create("AllTests", false, true, SearchMode.Normal);
var text = new StringTextSource("name=\"FindAllTests ");
var results = strategy.FindAll(text, 0, text.TextLength).ToArray();
Assert.IsEmpty(results, "No results should be found!");
}
[Test]
public void SkipWordBorder3()
{
var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal);
var text = new StringTextSource(" // findtest");
var results = strategy.FindAll(text, 0, text.TextLength).ToArray();
Assert.IsEmpty(results, "No results should be found!");
}
[Test]
public void WordBorderTest()
{
var strategy = SearchStrategyFactory.Create("// find", false, true, SearchMode.Normal);
var text = new StringTextSource(" // find me");
var results = strategy.FindAll(text, 0, text.TextLength).ToArray();
Assert.AreEqual(1, results.Length, "One result should be found!");
Assert.AreEqual(" ".Length, results[0].Offset);
Assert.AreEqual("// find".Length, results[0].Length);
}
[Test]
public void ResultAtStart()
{
var strategy = SearchStrategyFactory.Create("result", false, true, SearchMode.Normal);
var text = new StringTextSource("result // find me");
var results = strategy.FindAll(text, 0, text.TextLength).ToArray();
Assert.AreEqual(1, results.Length, "One result should be found!");
Assert.AreEqual(0, results[0].Offset);
Assert.AreEqual("result".Length, results[0].Length);
}
[Test]
public void ResultAtEnd()
{
var strategy = SearchStrategyFactory.Create("me", false, true, SearchMode.Normal);
var text = new StringTextSource("result // find me");
var results = strategy.FindAll(text, 0, text.TextLength).ToArray();
Assert.AreEqual(1, results.Length, "One result should be found!");
Assert.AreEqual("result // find ".Length, results[0].Offset);
Assert.AreEqual("me".Length, results[0].Length);
}
[Test]
public void TextWithDots()
{
var strategy = SearchStrategyFactory.Create("Text", false, true, SearchMode.Normal);
var text = new StringTextSource(".Text.");
var results = strategy.FindAll(text, 0, text.TextLength).ToArray();
Assert.AreEqual(1, results.Length, "One result should be found!");
Assert.AreEqual(".".Length, results[0].Offset);
Assert.AreEqual("Text".Length, results[0].Length);
}
[Test]
public void SimpleTest()
{
var strategy = SearchStrategyFactory.Create("AllTests", false, false, SearchMode.Normal);
var text = new StringTextSource("name=\"FindAllTests ");
var results = strategy.FindAll(text, 0, text.TextLength).ToArray();
Assert.AreEqual(1, results.Length, "One result should be found!");
Assert.AreEqual("name=\"Find".Length, results[0].Offset);
Assert.AreEqual("AllTests".Length, results[0].Length);
}
}
}

158
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/CaretNavigationTests.cs

@ -1,158 +0,0 @@ @@ -1,158 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows.Documents;
using ICSharpCode.AvalonEdit.Document;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Utils
{
[TestFixture]
public class CaretNavigationTests
{
int GetNextCaretStop(string text, int offset, CaretPositioningMode mode)
{
return TextUtilities.GetNextCaretPosition(new StringTextSource(text), offset, LogicalDirection.Forward, mode);
}
int GetPrevCaretStop(string text, int offset, CaretPositioningMode mode)
{
return TextUtilities.GetNextCaretPosition(new StringTextSource(text), offset, LogicalDirection.Backward, mode);
}
[Test]
public void CaretStopInEmptyString()
{
Assert.AreEqual(0, GetNextCaretStop("", -1, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetNextCaretStop("", 0, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetPrevCaretStop("", 0, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetPrevCaretStop("", 1, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetNextCaretStop("", -1, CaretPositioningMode.WordStart));
Assert.AreEqual(-1, GetNextCaretStop("", -1, CaretPositioningMode.WordBorder));
Assert.AreEqual(-1, GetPrevCaretStop("", 1, CaretPositioningMode.WordStart));
Assert.AreEqual(-1, GetPrevCaretStop("", 1, CaretPositioningMode.WordBorder));
}
[Test]
public void StartOfDocumentWithWordStart()
{
Assert.AreEqual(0, GetNextCaretStop("word", -1, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetNextCaretStop("word", -1, CaretPositioningMode.WordStart));
Assert.AreEqual(0, GetNextCaretStop("word", -1, CaretPositioningMode.WordBorder));
Assert.AreEqual(0, GetPrevCaretStop("word", 1, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetPrevCaretStop("word", 1, CaretPositioningMode.WordStart));
Assert.AreEqual(0, GetPrevCaretStop("word", 1, CaretPositioningMode.WordBorder));
}
[Test]
public void StartOfDocumentNoWordStart()
{
Assert.AreEqual(0, GetNextCaretStop(" word", -1, CaretPositioningMode.Normal));
Assert.AreEqual(1, GetNextCaretStop(" word", -1, CaretPositioningMode.WordStart));
Assert.AreEqual(1, GetNextCaretStop(" word", -1, CaretPositioningMode.WordBorder));
Assert.AreEqual(0, GetPrevCaretStop(" word", 1, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetPrevCaretStop(" word", 1, CaretPositioningMode.WordStart));
Assert.AreEqual(-1, GetPrevCaretStop(" word", 1, CaretPositioningMode.WordBorder));
}
[Test]
public void EndOfDocumentWordBorder()
{
Assert.AreEqual(4, GetNextCaretStop("word", 3, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetNextCaretStop("word", 3, CaretPositioningMode.WordStart));
Assert.AreEqual(4, GetNextCaretStop("word", 3, CaretPositioningMode.WordBorder));
Assert.AreEqual(4, GetPrevCaretStop("word", 5, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetPrevCaretStop("word", 5, CaretPositioningMode.WordStart));
Assert.AreEqual(4, GetPrevCaretStop("word", 5, CaretPositioningMode.WordBorder));
}
[Test]
public void EndOfDocumentNoWordBorder()
{
Assert.AreEqual(4, GetNextCaretStop("txt ", 3, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetNextCaretStop("txt ", 3, CaretPositioningMode.WordStart));
Assert.AreEqual(-1, GetNextCaretStop("txt ", 3, CaretPositioningMode.WordBorder));
Assert.AreEqual(4, GetPrevCaretStop("txt ", 5, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetPrevCaretStop("txt ", 5, CaretPositioningMode.WordStart));
Assert.AreEqual(3, GetPrevCaretStop("txt ", 5, CaretPositioningMode.WordBorder));
}
[Test]
public void SingleCharacterOutsideBMP()
{
string c = "\U0001D49E";
Assert.AreEqual(2, GetNextCaretStop(c, 0, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetPrevCaretStop(c, 2, CaretPositioningMode.Normal));
}
[Test]
public void DetectWordBordersOutsideBMP()
{
string c = " a\U0001D49Eb ";
Assert.AreEqual(1, GetNextCaretStop(c, 0, CaretPositioningMode.WordBorder));
Assert.AreEqual(5, GetNextCaretStop(c, 1, CaretPositioningMode.WordBorder));
Assert.AreEqual(5, GetPrevCaretStop(c, 6, CaretPositioningMode.WordBorder));
Assert.AreEqual(1, GetPrevCaretStop(c, 5, CaretPositioningMode.WordBorder));
}
[Test]
public void DetectWordBordersOutsideBMP2()
{
string c = " \U0001D49E\U0001D4AA ";
Assert.AreEqual(1, GetNextCaretStop(c, 0, CaretPositioningMode.WordBorder));
Assert.AreEqual(5, GetNextCaretStop(c, 1, CaretPositioningMode.WordBorder));
Assert.AreEqual(5, GetPrevCaretStop(c, 6, CaretPositioningMode.WordBorder));
Assert.AreEqual(1, GetPrevCaretStop(c, 5, CaretPositioningMode.WordBorder));
}
[Test]
public void CombiningMark()
{
string str = " x͆ ";
Assert.AreEqual(3, GetNextCaretStop(str, 1, CaretPositioningMode.Normal));
Assert.AreEqual(1, GetPrevCaretStop(str, 3, CaretPositioningMode.Normal));
}
[Test]
public void StackedCombiningMark()
{
string str = " x͆͆͆͆ ";
Assert.AreEqual(6, GetNextCaretStop(str, 1, CaretPositioningMode.Normal));
Assert.AreEqual(1, GetPrevCaretStop(str, 6, CaretPositioningMode.Normal));
}
[Test]
public void SingleClosingBraceAtLineEnd()
{
string str = "\t\t}";
Assert.AreEqual(2, GetNextCaretStop(str, 1, CaretPositioningMode.WordStart));
Assert.AreEqual(-1, GetPrevCaretStop(str, 1, CaretPositioningMode.WordStart));
}
}
}

146
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/CompressingTreeListTests.cs

@ -1,146 +0,0 @@ @@ -1,146 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Utils
{
[TestFixture]
public class CompressingTreeListTests
{
[Test]
public void EmptyTreeList()
{
CompressingTreeList<string> list = new CompressingTreeList<string>(string.Equals);
Assert.AreEqual(0, list.Count);
foreach (string v in list) {
Assert.Fail();
}
string[] arr = new string[0];
list.CopyTo(arr, 0);
}
[Test]
public void CheckAdd10BillionElements()
{
const int billion = 1000000000;
CompressingTreeList<string> list = new CompressingTreeList<string>(string.Equals);
list.InsertRange(0, billion, "A");
list.InsertRange(1, billion, "B");
Assert.AreEqual(2 * billion, list.Count);
Assert.Throws<OverflowException>(delegate { list.InsertRange(2, billion, "C"); });
}
[Test]
public void AddRepeated()
{
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
list.Add(42);
list.Add(42);
list.Add(42);
list.Insert(0, 42);
list.Insert(1, 42);
Assert.AreEqual(new[] { 42, 42, 42, 42, 42 }, list.ToArray());
}
[Test]
public void RemoveRange()
{
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
for (int i = 1; i <= 3; i++) {
list.InsertRange(list.Count, 2, i);
}
Assert.AreEqual(new[] { 1, 1, 2, 2, 3, 3 }, list.ToArray());
list.RemoveRange(1, 4);
Assert.AreEqual(new[] { 1, 3 }, list.ToArray());
list.Insert(1, 1);
list.InsertRange(2, 2, 2);
list.Insert(4, 1);
Assert.AreEqual(new[] { 1, 1, 2, 2, 1, 3 }, list.ToArray());
list.RemoveRange(2, 2);
Assert.AreEqual(new[] { 1, 1, 1, 3 }, list.ToArray());
}
[Test]
public void RemoveAtEnd()
{
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
for (int i = 1; i <= 3; i++) {
list.InsertRange(list.Count, 2, i);
}
Assert.AreEqual(new[] { 1, 1, 2, 2, 3, 3 }, list.ToArray());
list.RemoveRange(3, 3);
Assert.AreEqual(new[] { 1, 1, 2 }, list.ToArray());
}
[Test]
public void RemoveAtStart()
{
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
for (int i = 1; i <= 3; i++) {
list.InsertRange(list.Count, 2, i);
}
Assert.AreEqual(new[] { 1, 1, 2, 2, 3, 3 }, list.ToArray());
list.RemoveRange(0, 1);
Assert.AreEqual(new[] { 1, 2, 2, 3, 3 }, list.ToArray());
}
[Test]
public void RemoveAtStart2()
{
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
for (int i = 1; i <= 3; i++) {
list.InsertRange(list.Count, 2, i);
}
Assert.AreEqual(new[] { 1, 1, 2, 2, 3, 3 }, list.ToArray());
list.RemoveRange(0, 3);
Assert.AreEqual(new[] { 2, 3, 3 }, list.ToArray());
}
[Test]
public void Transform()
{
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
list.AddRange(new[] { 0, 1, 1, 0 });
int calls = 0;
list.Transform(i => { calls++; return i + 1; });
Assert.AreEqual(3, calls);
Assert.AreEqual(new[] { 1, 2, 2, 1 }, list.ToArray());
}
[Test]
public void TransformToZero()
{
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
list.AddRange(new[] { 0, 1, 1, 0 });
list.Transform(i => 0);
Assert.AreEqual(new[] { 0, 0, 0, 0 }, list.ToArray());
}
[Test]
public void TransformRange()
{
CompressingTreeList<int> list = new CompressingTreeList<int>((a, b) => a == b);
list.AddRange(new[] { 0, 1, 1, 1, 0, 0 });
list.TransformRange(2, 3, i => 0);
Assert.AreEqual(new[] { 0, 1, 0, 0, 0, 0 }, list.ToArray());
}
}
}

51
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/ExtensionMethodsTests.cs

@ -1,51 +0,0 @@ @@ -1,51 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Utils
{
[TestFixture]
public class ExtensionMethodsTests
{
[Test]
public void ZeroIsNotCloseToOne()
{
Assert.IsFalse(0.0.IsClose(1));
}
[Test]
public void ZeroIsCloseToZero()
{
Assert.IsTrue(0.0.IsClose(0));
}
[Test]
public void InfinityIsCloseToInfinity()
{
Assert.IsTrue(double.PositiveInfinity.IsClose(double.PositiveInfinity));
}
[Test]
public void NaNIsNotCloseToNaN()
{
Assert.IsFalse(double.NaN.IsClose(double.NaN));
}
}
}

51
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/IndentationStringTests.cs

@ -1,51 +0,0 @@ @@ -1,51 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Utils
{
[TestFixture]
public class IndentationStringTests
{
[Test]
public void IndentWithSingleTab()
{
var options = new TextEditorOptions { IndentationSize = 4, ConvertTabsToSpaces = false };
Assert.AreEqual("\t", options.IndentationString);
Assert.AreEqual("\t", options.GetIndentationString(2));
Assert.AreEqual("\t", options.GetIndentationString(3));
Assert.AreEqual("\t", options.GetIndentationString(4));
Assert.AreEqual("\t", options.GetIndentationString(5));
Assert.AreEqual("\t", options.GetIndentationString(6));
}
[Test]
public void IndentWith4Spaces()
{
var options = new TextEditorOptions { IndentationSize = 4, ConvertTabsToSpaces = true };
Assert.AreEqual(" ", options.IndentationString);
Assert.AreEqual(" ", options.GetIndentationString(2));
Assert.AreEqual(" ", options.GetIndentationString(3));
Assert.AreEqual(" ", options.GetIndentationString(4));
Assert.AreEqual(" ", options.GetIndentationString(5));
Assert.AreEqual(" ", options.GetIndentationString(6));
}
}
}

195
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/RopeTests.cs

@ -1,195 +0,0 @@ @@ -1,195 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.IO;
using NUnit.Framework;
using System.Text;
namespace ICSharpCode.AvalonEdit.Utils
{
[TestFixture]
public class RopeTests
{
[Test]
public void EmptyRope()
{
Rope<char> empty = new Rope<char>();
Assert.AreEqual(0, empty.Length);
Assert.AreEqual("", empty.ToString());
}
[Test]
public void EmptyRopeFromString()
{
Rope<char> empty = new Rope<char>(string.Empty);
Assert.AreEqual(0, empty.Length);
Assert.AreEqual("", empty.ToString());
}
[Test]
public void InitializeRopeFromShortString()
{
Rope<char> rope = new Rope<char>("Hello, World");
Assert.AreEqual(12, rope.Length);
Assert.AreEqual("Hello, World", rope.ToString());
}
string BuildLongString(int lines)
{
StringWriter w = new StringWriter();
w.NewLine = "\n";
for (int i = 1; i <= lines; i++) {
w.WriteLine(i.ToString());
}
return w.ToString();
}
[Test]
public void InitializeRopeFromLongString()
{
string text = BuildLongString(1000);
Rope<char> rope = new Rope<char>(text);
Assert.AreEqual(text.Length, rope.Length);
Assert.AreEqual(text, rope.ToString());
Assert.AreEqual(text.ToCharArray(), rope.ToArray());
}
[Test]
public void TestToArrayAndToStringWithParts()
{
string text = BuildLongString(1000);
Rope<char> rope = new Rope<char>(text);
string textPart = text.Substring(1200, 600);
char[] arrayPart = textPart.ToCharArray();
Assert.AreEqual(textPart, rope.ToString(1200, 600));
Assert.AreEqual(arrayPart, rope.ToArray(1200, 600));
Rope<char> partialRope = rope.GetRange(1200, 600);
Assert.AreEqual(textPart, partialRope.ToString());
Assert.AreEqual(arrayPart, partialRope.ToArray());
}
[Test]
public void ConcatenateStringToRope()
{
StringBuilder b = new StringBuilder();
Rope<char> rope = new Rope<char>();
for (int i = 1; i <= 1000; i++) {
b.Append(i.ToString());
rope.AddText(i.ToString());
b.Append(' ');
rope.Add(' ');
}
Assert.AreEqual(b.ToString(), rope.ToString());
}
[Test]
public void ConcatenateSmallRopesToRope()
{
StringBuilder b = new StringBuilder();
Rope<char> rope = new Rope<char>();
for (int i = 1; i <= 1000; i++) {
b.Append(i.ToString());
b.Append(' ');
rope.AddRange(CharRope.Create(i.ToString() + " "));
}
Assert.AreEqual(b.ToString(), rope.ToString());
}
[Test]
public void AppendLongTextToEmptyRope()
{
string text = BuildLongString(1000);
Rope<char> rope = new Rope<char>();
rope.AddText(text);
Assert.AreEqual(text, rope.ToString());
}
[Test]
public void ConcatenateStringToRopeBackwards()
{
StringBuilder b = new StringBuilder();
Rope<char> rope = new Rope<char>();
for (int i = 1; i <= 1000; i++) {
b.Append(i.ToString());
b.Append(' ');
}
for (int i = 1000; i >= 1; i--) {
rope.Insert(0, ' ');
rope.InsertText(0, i.ToString());
}
Assert.AreEqual(b.ToString(), rope.ToString());
}
[Test]
public void ConcatenateSmallRopesToRopeBackwards()
{
StringBuilder b = new StringBuilder();
Rope<char> rope = new Rope<char>();
for (int i = 1; i <= 1000; i++) {
b.Append(i.ToString());
b.Append(' ');
}
for (int i = 1000; i >= 1; i--) {
rope.InsertRange(0, CharRope.Create(i.ToString() + " "));
}
Assert.AreEqual(b.ToString(), rope.ToString());
}
[Test]
public void ConcatenateStringToRopeByInsertionInMiddle()
{
StringBuilder b = new StringBuilder();
Rope<char> rope = new Rope<char>();
for (int i = 1; i <= 998; i++) {
b.Append(i.ToString("d3"));
b.Append(' ');
}
int middle = 0;
for (int i = 1; i <= 499; i++) {
rope.InsertText(middle, i.ToString("d3"));
middle += 3;
rope.Insert(middle, ' ');
middle++;
rope.InsertText(middle, (999-i).ToString("d3"));
rope.Insert(middle + 3, ' ');
}
Assert.AreEqual(b.ToString(), rope.ToString());
}
[Test]
public void ConcatenateSmallRopesByInsertionInMiddle()
{
StringBuilder b = new StringBuilder();
Rope<char> rope = new Rope<char>();
for (int i = 1; i <= 1000; i++) {
b.Append(i.ToString("d3"));
b.Append(' ');
}
int middle = 0;
for (int i = 1; i <= 500; i++) {
rope.InsertRange(middle, CharRope.Create(i.ToString("d3") + " "));
middle += 4;
rope.InsertRange(middle, CharRope.Create((1001-i).ToString("d3") + " "));
}
Assert.AreEqual(b.ToString(), rope.ToString());
}
}
}

127
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/WeakReferenceTests.cs

@ -1,127 +0,0 @@ @@ -1,127 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Rendering;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit
{
[TestFixture]
public class WeakReferenceTests
{
[Test]
public void TextViewCanBeCollectedTest()
{
TextView textView = new TextView();
WeakReference wr = new WeakReference(textView);
textView = null;
GarbageCollect();
Assert.IsFalse(wr.IsAlive);
}
[Test]
public void DocumentDoesNotHoldReferenceToTextView()
{
TextDocument textDocument = new TextDocument();
Assert.AreEqual(0, textDocument.LineTrackers.Count);
TextView textView = new TextView();
WeakReference wr = new WeakReference(textView);
textView.Document = textDocument;
Assert.AreEqual(1, textDocument.LineTrackers.Count);
textView = null;
GarbageCollect();
Assert.IsFalse(wr.IsAlive);
// document cannot immediately clear the line tracker
Assert.AreEqual(1, textDocument.LineTrackers.Count);
// but it should clear it on the next change
textDocument.Insert(0, "a");
Assert.AreEqual(0, textDocument.LineTrackers.Count);
}
[Test]
public void DocumentDoesNotHoldReferenceToTextArea()
{
TextDocument textDocument = new TextDocument();
TextArea textArea = new TextArea();
WeakReference wr = new WeakReference(textArea);
textArea.Document = textDocument;
textArea = null;
GarbageCollect();
Assert.IsFalse(wr.IsAlive);
GC.KeepAlive(textDocument);
}
[Test]
public void DocumentDoesNotHoldReferenceToTextEditor()
{
TextDocument textDocument = new TextDocument();
TextEditor textEditor = new TextEditor();
WeakReference wr = new WeakReference(textEditor);
textEditor.Document = textDocument;
textEditor = null;
GarbageCollect();
Assert.IsFalse(wr.IsAlive);
GC.KeepAlive(textDocument);
}
[Test]
public void DocumentDoesNotHoldReferenceToLineMargin()
{
TextDocument textDocument = new TextDocument();
WeakReference wr = DocumentDoesNotHoldReferenceToLineMargin_CreateMargin(textDocument);
GarbageCollect();
Assert.IsFalse(wr.IsAlive);
GC.KeepAlive(textDocument);
}
// using a method to ensure the local variables can be garbage collected after the method returns
WeakReference DocumentDoesNotHoldReferenceToLineMargin_CreateMargin(TextDocument textDocument)
{
TextView textView = new TextView() {
Document = textDocument
};
LineNumberMargin margin = new LineNumberMargin() {
TextView = textView
};
return new WeakReference(textView);
}
static void GarbageCollect()
{
for (int i = 0; i < 3; i++) {
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
// pump WPF messages so that WeakEventManager can unregister
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new Action(delegate {}));
}
}
}
}

102
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/AvalonEditCommands.cs

@ -1,102 +0,0 @@ @@ -1,102 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows.Input;
namespace ICSharpCode.AvalonEdit
{
/// <summary>
/// Custom commands for AvalonEdit.
/// </summary>
public static class AvalonEditCommands
{
/// <summary>
/// Deletes the current line.
/// The default shortcut is Ctrl+D.
/// </summary>
public static readonly RoutedCommand DeleteLine = new RoutedCommand(
"DeleteLine", typeof(TextEditor),
new InputGestureCollection {
new KeyGesture(Key.D, ModifierKeys.Control)
});
/// <summary>
/// Removes leading whitespace from the selected lines (or the whole document if the selection is empty).
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static readonly RoutedCommand RemoveLeadingWhitespace = new RoutedCommand("RemoveLeadingWhitespace", typeof(TextEditor));
/// <summary>
/// Removes trailing whitespace from the selected lines (or the whole document if the selection is empty).
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static readonly RoutedCommand RemoveTrailingWhitespace = new RoutedCommand("RemoveTrailingWhitespace", typeof(TextEditor));
/// <summary>
/// Converts the selected text to upper case.
/// </summary>
public static readonly RoutedCommand ConvertToUppercase = new RoutedCommand("ConvertToUppercase", typeof(TextEditor));
/// <summary>
/// Converts the selected text to lower case.
/// </summary>
public static readonly RoutedCommand ConvertToLowercase = new RoutedCommand("ConvertToLowercase", typeof(TextEditor));
/// <summary>
/// Converts the selected text to title case.
/// </summary>
public static readonly RoutedCommand ConvertToTitleCase = new RoutedCommand("ConvertToTitleCase", typeof(TextEditor));
/// <summary>
/// Inverts the case of the selected text.
/// </summary>
public static readonly RoutedCommand InvertCase = new RoutedCommand("InvertCase", typeof(TextEditor));
/// <summary>
/// Converts tabs to spaces in the selected text.
/// </summary>
public static readonly RoutedCommand ConvertTabsToSpaces = new RoutedCommand("ConvertTabsToSpaces", typeof(TextEditor));
/// <summary>
/// Converts spaces to tabs in the selected text.
/// </summary>
public static readonly RoutedCommand ConvertSpacesToTabs = new RoutedCommand("ConvertSpacesToTabs", typeof(TextEditor));
/// <summary>
/// Converts leading tabs to spaces in the selected lines (or the whole document if the selection is empty).
/// </summary>
public static readonly RoutedCommand ConvertLeadingTabsToSpaces = new RoutedCommand("ConvertLeadingTabsToSpaces", typeof(TextEditor));
/// <summary>
/// Converts leading spaces to tabs in the selected lines (or the whole document if the selection is empty).
/// </summary>
public static readonly RoutedCommand ConvertLeadingSpacesToTabs = new RoutedCommand("ConvertLeadingSpacesToTabs", typeof(TextEditor));
/// <summary>
/// Runs the IIndentationStrategy on the selected lines (or the whole document if the selection is empty).
/// </summary>
public static readonly RoutedCommand IndentSelection = new RoutedCommand(
"IndentSelection", typeof(TextEditor),
new InputGestureCollection {
new KeyGesture(Key.I, ModifierKeys.Control)
});
}
}

416
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs

@ -1,416 +0,0 @@ @@ -1,416 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Linq;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
/// <summary>
/// The listbox used inside the CompletionWindow, contains CompletionListBox.
/// </summary>
public class CompletionList : Control
{
static CompletionList()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CompletionList),
new FrameworkPropertyMetadata(typeof(CompletionList)));
}
bool isFiltering = true;
/// <summary>
/// If true, the CompletionList is filtered to show only matching items. Also enables search by substring.
/// If false, enables the old behavior: no filtering, search by string.StartsWith.
/// </summary>
public bool IsFiltering {
get { return isFiltering; }
set { isFiltering = value; }
}
/// <summary>
/// Dependency property for <see cref="EmptyTemplate" />.
/// </summary>
public static readonly DependencyProperty EmptyTemplateProperty =
DependencyProperty.Register("EmptyTemplate", typeof(ControlTemplate), typeof(CompletionList),
new FrameworkPropertyMetadata());
/// <summary>
/// Content of EmptyTemplate will be shown when CompletionList contains no items.
/// If EmptyTemplate is null, nothing will be shown.
/// </summary>
public ControlTemplate EmptyTemplate {
get { return (ControlTemplate)GetValue(EmptyTemplateProperty); }
set { SetValue(EmptyTemplateProperty, value); }
}
/// <summary>
/// Is raised when the completion list indicates that the user has chosen
/// an entry to be completed.
/// </summary>
public event EventHandler InsertionRequested;
/// <summary>
/// Raises the InsertionRequested event.
/// </summary>
public void RequestInsertion(EventArgs e)
{
if (InsertionRequested != null)
InsertionRequested(this, e);
}
CompletionListBox listBox;
/// <inheritdoc/>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
listBox = GetTemplateChild("PART_ListBox") as CompletionListBox;
if (listBox != null) {
listBox.ItemsSource = completionData;
}
}
/// <summary>
/// Gets the list box.
/// </summary>
public CompletionListBox ListBox {
get {
if (listBox == null)
ApplyTemplate();
return listBox;
}
}
/// <summary>
/// Gets the scroll viewer used in this list box.
/// </summary>
public ScrollViewer ScrollViewer {
get { return listBox != null ? listBox.scrollViewer : null; }
}
ObservableCollection<ICompletionData> completionData = new ObservableCollection<ICompletionData>();
/// <summary>
/// Gets the list to which completion data can be added.
/// </summary>
public IList<ICompletionData> CompletionData {
get { return completionData; }
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled) {
HandleKey(e);
}
}
/// <summary>
/// Handles a key press. Used to let the completion list handle key presses while the
/// focus is still on the text editor.
/// </summary>
public void HandleKey(KeyEventArgs e)
{
if (listBox == null)
return;
// We have to do some key handling manually, because the default doesn't work with
// our simulated events.
// Also, the default PageUp/PageDown implementation changes the focus, so we avoid it.
switch (e.Key) {
case Key.Down:
e.Handled = true;
listBox.SelectIndex(listBox.SelectedIndex + 1);
break;
case Key.Up:
e.Handled = true;
listBox.SelectIndex(listBox.SelectedIndex - 1);
break;
case Key.PageDown:
e.Handled = true;
listBox.SelectIndex(listBox.SelectedIndex + listBox.VisibleItemCount);
break;
case Key.PageUp:
e.Handled = true;
listBox.SelectIndex(listBox.SelectedIndex - listBox.VisibleItemCount);
break;
case Key.Home:
e.Handled = true;
listBox.SelectIndex(0);
break;
case Key.End:
e.Handled = true;
listBox.SelectIndex(listBox.Items.Count - 1);
break;
case Key.Tab:
case Key.Enter:
e.Handled = true;
RequestInsertion(e);
break;
}
}
/// <inheritdoc/>
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
if (e.ChangedButton == MouseButton.Left) {
// only process double clicks on the ListBoxItems, not on the scroll bar
if (ExtensionMethods.VisualAncestorsAndSelf(e.OriginalSource as DependencyObject).TakeWhile(obj => obj != this).Any(obj => obj is ListBoxItem)) {
e.Handled = true;
RequestInsertion(e);
}
}
}
/// <summary>
/// Gets/Sets the selected item.
/// </summary>
/// <remarks>
/// The setter of this property does not scroll to the selected item.
/// You might want to also call <see cref="ScrollIntoView"/>.
/// </remarks>
public ICompletionData SelectedItem {
get {
return (listBox != null ? listBox.SelectedItem : null) as ICompletionData;
}
set {
if (listBox == null && value != null)
ApplyTemplate();
if (listBox != null) // may still be null if ApplyTemplate fails, or if listBox and value both are null
listBox.SelectedItem = value;
}
}
/// <summary>
/// Scrolls the specified item into view.
/// </summary>
public void ScrollIntoView(ICompletionData item)
{
if (listBox == null)
ApplyTemplate();
if (listBox != null)
listBox.ScrollIntoView(item);
}
/// <summary>
/// Occurs when the SelectedItem property changes.
/// </summary>
public event SelectionChangedEventHandler SelectionChanged {
add { AddHandler(Selector.SelectionChangedEvent, value); }
remove { RemoveHandler(Selector.SelectionChangedEvent, value); }
}
// SelectItem gets called twice for every typed character (once from FormatLine), this helps execute SelectItem only once
string currentText;
ObservableCollection<ICompletionData> currentList;
/// <summary>
/// Selects the best match, and filter the items if turned on using <see cref="IsFiltering" />.
/// </summary>
public void SelectItem(string text)
{
if (text == currentText)
return;
if (listBox == null)
ApplyTemplate();
if (this.IsFiltering) {
SelectItemFiltering(text);
}
else {
SelectItemWithStart(text);
}
currentText = text;
}
/// <summary>
/// Filters CompletionList items to show only those matching given query, and selects the best match.
/// </summary>
void SelectItemFiltering(string query)
{
// if the user just typed one more character, don't filter all data but just filter what we are already displaying
var listToFilter = (this.currentList != null && (!string.IsNullOrEmpty(this.currentText)) && (!string.IsNullOrEmpty(query)) &&
query.StartsWith(this.currentText, StringComparison.Ordinal)) ?
this.currentList : this.completionData;
var matchingItems =
from item in listToFilter
let quality = GetMatchQuality(item.Text, query)
where quality > 0
select new { Item = item, Quality = quality };
// e.g. "DateTimeKind k = (*cc here suggests DateTimeKind*)"
ICompletionData suggestedItem = listBox.SelectedIndex != -1 ? (ICompletionData)(listBox.Items[listBox.SelectedIndex]) : null;
var listBoxItems = new ObservableCollection<ICompletionData>();
int bestIndex = -1;
int bestQuality = -1;
double bestPriority = 0;
int i = 0;
foreach (var matchingItem in matchingItems) {
double priority = matchingItem.Item == suggestedItem ? double.PositiveInfinity : matchingItem.Item.Priority;
int quality = matchingItem.Quality;
if (quality > bestQuality || (quality == bestQuality && (priority > bestPriority))) {
bestIndex = i;
bestPriority = priority;
bestQuality = quality;
}
listBoxItems.Add(matchingItem.Item);
i++;
}
this.currentList = listBoxItems;
listBox.ItemsSource = listBoxItems;
SelectIndexCentered(bestIndex);
}
/// <summary>
/// Selects the item that starts with the specified query.
/// </summary>
void SelectItemWithStart(string query)
{
if (string.IsNullOrEmpty(query))
return;
int suggestedIndex = listBox.SelectedIndex;
int bestIndex = -1;
int bestQuality = -1;
double bestPriority = 0;
for (int i = 0; i < completionData.Count; ++i) {
int quality = GetMatchQuality(completionData[i].Text, query);
if (quality < 0)
continue;
double priority = completionData[i].Priority;
bool useThisItem;
if (bestQuality < quality) {
useThisItem = true;
} else {
if (bestIndex == suggestedIndex) {
useThisItem = false;
} else if (i == suggestedIndex) {
// prefer recommendedItem, regardless of its priority
useThisItem = bestQuality == quality;
} else {
useThisItem = bestQuality == quality && bestPriority < priority;
}
}
if (useThisItem) {
bestIndex = i;
bestPriority = priority;
bestQuality = quality;
}
}
SelectIndexCentered(bestIndex);
}
void SelectIndexCentered(int bestIndex)
{
if (bestIndex < 0) {
listBox.ClearSelection();
} else {
int firstItem = listBox.FirstVisibleItem;
if (bestIndex < firstItem || firstItem + listBox.VisibleItemCount <= bestIndex) {
// CenterViewOn does nothing as CompletionListBox.ScrollViewer is null
listBox.CenterViewOn(bestIndex);
listBox.SelectIndex(bestIndex);
} else {
listBox.SelectIndex(bestIndex);
}
}
}
int GetMatchQuality(string itemText, string query)
{
if (itemText == null)
throw new ArgumentNullException("itemText", "ICompletionData.Text returned null");
// Qualities:
// 8 = full match case sensitive
// 7 = full match
// 6 = match start case sensitive
// 5 = match start
// 4 = match CamelCase when length of query is 1 or 2 characters
// 3 = match substring case sensitive
// 2 = match substring
// 1 = match CamelCase
// -1 = no match
if (query == itemText)
return 8;
if (string.Equals(itemText, query, StringComparison.InvariantCultureIgnoreCase))
return 7;
if (itemText.StartsWith(query, StringComparison.InvariantCulture))
return 6;
if (itemText.StartsWith(query, StringComparison.InvariantCultureIgnoreCase))
return 5;
bool? camelCaseMatch = null;
if (query.Length <= 2) {
camelCaseMatch = CamelCaseMatch(itemText, query);
if (camelCaseMatch == true) return 4;
}
// search by substring, if filtering (i.e. new behavior) turned on
if (IsFiltering) {
if (itemText.IndexOf(query, StringComparison.InvariantCulture) >= 0)
return 3;
if (itemText.IndexOf(query, StringComparison.InvariantCultureIgnoreCase) >= 0)
return 2;
}
if (!camelCaseMatch.HasValue)
camelCaseMatch = CamelCaseMatch(itemText, query);
if (camelCaseMatch == true)
return 1;
return -1;
}
static bool CamelCaseMatch(string text, string query)
{
// We take the first letter of the text regardless of whether or not it's upper case so we match
// against camelCase text as well as PascalCase text ("cct" matches "camelCaseText")
var theFirstLetterOfEachWord = text.Take(1).Concat(text.Skip(1).Where(char.IsUpper));
int i = 0;
foreach (var letter in theFirstLetterOfEachWord) {
if (i > query.Length - 1)
return true; // return true here for CamelCase partial match ("CQ" matches "CodeQualityAnalysis")
if (char.ToUpperInvariant(query[i]) != char.ToUpperInvariant(letter))
return false;
i++;
}
if (i >= query.Length)
return true;
return false;
}
}
}

56
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.xaml

@ -1,56 +0,0 @@ @@ -1,56 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AvalonEdit="clr-namespace:ICSharpCode.AvalonEdit"
xmlns:cc="clr-namespace:ICSharpCode.AvalonEdit.CodeCompletion"
>
<Style TargetType="{x:Type ListBoxItem}" x:Key="CompletionListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="true">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<!-- Simplified triggers:
we don't want a gray selection background when the ListBox doesn't have focus
-->
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type cc:CompletionList}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type cc:CompletionList}">
<cc:CompletionListBox x:Name="PART_ListBox"
ItemContainerStyle="{StaticResource CompletionListBoxItem}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" Width="16" Height="16" Margin="0,0,2,0"/>
<ContentPresenter Content="{Binding Content}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</cc:CompletionListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

111
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionListBox.cs

@ -1,111 +0,0 @@ @@ -1,111 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows;
using System.Windows.Controls;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
/// <summary>
/// The list box used inside the CompletionList.
/// </summary>
public class CompletionListBox : ListBox
{
internal ScrollViewer scrollViewer;
/// <inheritdoc/>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Find the scroll viewer:
scrollViewer = null;
if (this.VisualChildrenCount > 0) {
Border border = this.GetVisualChild(0) as Border;
if (border != null)
scrollViewer = border.Child as ScrollViewer;
}
}
/// <summary>
/// Gets the number of the first visible item.
/// </summary>
public int FirstVisibleItem {
get {
if (scrollViewer == null || scrollViewer.ExtentHeight == 0) {
return 0;
} else {
return (int)(this.Items.Count * scrollViewer.VerticalOffset / scrollViewer.ExtentHeight);
}
}
set {
value = value.CoerceValue(0, this.Items.Count - this.VisibleItemCount);
if (scrollViewer != null) {
scrollViewer.ScrollToVerticalOffset((double)value / this.Items.Count * scrollViewer.ExtentHeight);
}
}
}
/// <summary>
/// Gets the number of visible items.
/// </summary>
public int VisibleItemCount {
get {
if (scrollViewer == null || scrollViewer.ExtentHeight == 0) {
return 10;
} else {
return Math.Max(
3,
(int)Math.Ceiling(this.Items.Count * scrollViewer.ViewportHeight
/ scrollViewer.ExtentHeight));
}
}
}
/// <summary>
/// Removes the selection.
/// </summary>
public void ClearSelection()
{
this.SelectedIndex = -1;
}
/// <summary>
/// Selects the item with the specified index and scrolls it into view.
/// </summary>
public void SelectIndex(int index)
{
if (index >= this.Items.Count)
index = this.Items.Count - 1;
if (index < 0)
index = 0;
this.SelectedIndex = index;
this.ScrollIntoView(this.SelectedItem);
}
/// <summary>
/// Centers the view on the item with the specified index.
/// </summary>
public void CenterViewOn(int index)
{
this.FirstVisibleItem = index - VisibleItemCount / 2;
}
}
}

210
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs

@ -1,210 +0,0 @@ @@ -1,210 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
/// <summary>
/// The code completion window.
/// </summary>
public class CompletionWindow : CompletionWindowBase
{
readonly CompletionList completionList = new CompletionList();
ToolTip toolTip = new ToolTip();
/// <summary>
/// Gets the completion list used in this completion window.
/// </summary>
public CompletionList CompletionList {
get { return completionList; }
}
/// <summary>
/// Creates a new code completion window.
/// </summary>
public CompletionWindow(TextArea textArea) : base(textArea)
{
// keep height automatic
this.CloseAutomatically = true;
this.SizeToContent = SizeToContent.Height;
this.MaxHeight = 300;
this.Width = 175;
this.Content = completionList;
// prevent user from resizing window to 0x0
this.MinHeight = 15;
this.MinWidth = 30;
toolTip.PlacementTarget = this;
toolTip.Placement = PlacementMode.Right;
toolTip.Closed += toolTip_Closed;
AttachEvents();
}
#region ToolTip handling
void toolTip_Closed(object sender, RoutedEventArgs e)
{
// Clear content after tooltip is closed.
// We cannot clear is immediately when setting IsOpen=false
// because the tooltip uses an animation for closing.
if (toolTip != null)
toolTip.Content = null;
}
void completionList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var item = completionList.SelectedItem;
if (item == null)
return;
object description = item.Description;
if (description != null) {
string descriptionText = description as string;
if (descriptionText != null) {
toolTip.Content = new TextBlock {
Text = descriptionText,
TextWrapping = TextWrapping.Wrap
};
} else {
toolTip.Content = description;
}
toolTip.IsOpen = true;
} else {
toolTip.IsOpen = false;
}
}
#endregion
void completionList_InsertionRequested(object sender, EventArgs e)
{
Close();
// The window must close before Complete() is called.
// If the Complete callback pushes stacked input handlers, we don't want to pop those when the CC window closes.
var item = completionList.SelectedItem;
if (item != null)
item.Complete(this.TextArea, new AnchorSegment(this.TextArea.Document, this.StartOffset, this.EndOffset - this.StartOffset), e);
}
void AttachEvents()
{
this.completionList.InsertionRequested += completionList_InsertionRequested;
this.completionList.SelectionChanged += completionList_SelectionChanged;
this.TextArea.Caret.PositionChanged += CaretPositionChanged;
this.TextArea.MouseWheel += textArea_MouseWheel;
this.TextArea.PreviewTextInput += textArea_PreviewTextInput;
}
/// <inheritdoc/>
protected override void DetachEvents()
{
this.completionList.InsertionRequested -= completionList_InsertionRequested;
this.completionList.SelectionChanged -= completionList_SelectionChanged;
this.TextArea.Caret.PositionChanged -= CaretPositionChanged;
this.TextArea.MouseWheel -= textArea_MouseWheel;
this.TextArea.PreviewTextInput -= textArea_PreviewTextInput;
base.DetachEvents();
}
/// <inheritdoc/>
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (toolTip != null) {
toolTip.IsOpen = false;
toolTip = null;
}
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled) {
completionList.HandleKey(e);
}
}
void textArea_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = RaiseEventPair(this, PreviewTextInputEvent, TextInputEvent,
new TextCompositionEventArgs(e.Device, e.TextComposition));
}
void textArea_MouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = RaiseEventPair(GetScrollEventTarget(),
PreviewMouseWheelEvent, MouseWheelEvent,
new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta));
}
UIElement GetScrollEventTarget()
{
if (completionList == null)
return this;
return completionList.ScrollViewer ?? completionList.ListBox ?? (UIElement)completionList;
}
/// <summary>
/// Gets/Sets whether the completion window should close automatically.
/// The default value is true.
/// </summary>
public bool CloseAutomatically { get; set; }
/// <inheritdoc/>
protected override bool CloseOnFocusLost {
get { return this.CloseAutomatically; }
}
/// <summary>
/// When this flag is set, code completion closes if the caret moves to the
/// beginning of the allowed range. This is useful in Ctrl+Space and "complete when typing",
/// but not in dot-completion.
/// Has no effect if CloseAutomatically is false.
/// </summary>
public bool CloseWhenCaretAtBeginning { get; set; }
void CaretPositionChanged(object sender, EventArgs e)
{
int offset = this.TextArea.Caret.Offset;
if (offset == this.StartOffset) {
if (CloseAutomatically && CloseWhenCaretAtBeginning) {
Close();
} else {
completionList.SelectItem(string.Empty);
}
return;
}
if (offset < this.StartOffset || offset > this.EndOffset) {
if (CloseAutomatically) {
Close();
}
} else {
TextDocument document = this.TextArea.Document;
if (document != null) {
completionList.SelectItem(document.GetText(this.StartOffset, offset - this.StartOffset));
}
}
}
}
}

397
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindowBase.cs

@ -1,397 +0,0 @@ @@ -1,397 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.NRefactory.Editor;
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
/// <summary>
/// Base class for completion windows. Handles positioning the window at the caret.
/// </summary>
public class CompletionWindowBase : Window
{
static CompletionWindowBase()
{
WindowStyleProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(WindowStyle.None));
ShowActivatedProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(Boxes.False));
ShowInTaskbarProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(Boxes.False));
}
/// <summary>
/// Gets the parent TextArea.
/// </summary>
public TextArea TextArea { get; private set; }
Window parentWindow;
TextDocument document;
/// <summary>
/// Gets/Sets the start of the text range in which the completion window stays open.
/// This text portion is used to determine the text used to select an entry in the completion list by typing.
/// </summary>
public int StartOffset { get; set; }
/// <summary>
/// Gets/Sets the end of the text range in which the completion window stays open.
/// This text portion is used to determine the text used to select an entry in the completion list by typing.
/// </summary>
public int EndOffset { get; set; }
/// <summary>
/// Gets whether the window was opened above the current line.
/// </summary>
protected bool IsUp { get; private set; }
/// <summary>
/// Creates a new CompletionWindowBase.
/// </summary>
public CompletionWindowBase(TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
this.TextArea = textArea;
parentWindow = Window.GetWindow(textArea);
this.Owner = parentWindow;
this.AddHandler(MouseUpEvent, new MouseButtonEventHandler(OnMouseUp), true);
StartOffset = EndOffset = this.TextArea.Caret.Offset;
AttachEvents();
}
#region Event Handlers
void AttachEvents()
{
document = this.TextArea.Document;
if (document != null) {
document.Changing += textArea_Document_Changing;
}
// LostKeyboardFocus seems to be more reliable than PreviewLostKeyboardFocus - see SD-1729
this.TextArea.LostKeyboardFocus += TextAreaLostFocus;
this.TextArea.TextView.ScrollOffsetChanged += TextViewScrollOffsetChanged;
this.TextArea.DocumentChanged += TextAreaDocumentChanged;
if (parentWindow != null) {
parentWindow.LocationChanged += parentWindow_LocationChanged;
}
// close previous completion windows of same type
foreach (InputHandler x in this.TextArea.StackedInputHandlers.OfType<InputHandler>()) {
if (x.window.GetType() == this.GetType())
this.TextArea.PopStackedInputHandler(x);
}
myInputHandler = new InputHandler(this);
this.TextArea.PushStackedInputHandler(myInputHandler);
}
/// <summary>
/// Detaches events from the text area.
/// </summary>
protected virtual void DetachEvents()
{
if (document != null) {
document.Changing -= textArea_Document_Changing;
}
this.TextArea.LostKeyboardFocus -= TextAreaLostFocus;
this.TextArea.TextView.ScrollOffsetChanged -= TextViewScrollOffsetChanged;
this.TextArea.DocumentChanged -= TextAreaDocumentChanged;
if (parentWindow != null) {
parentWindow.LocationChanged -= parentWindow_LocationChanged;
}
this.TextArea.PopStackedInputHandler(myInputHandler);
}
#region InputHandler
InputHandler myInputHandler;
/// <summary>
/// A dummy input handler (that justs invokes the default input handler).
/// This is used to ensure the completion window closes when any other input handler
/// becomes active.
/// </summary>
sealed class InputHandler : TextAreaStackedInputHandler
{
internal readonly CompletionWindowBase window;
public InputHandler(CompletionWindowBase window)
: base(window.TextArea)
{
Debug.Assert(window != null);
this.window = window;
}
public override void Detach()
{
base.Detach();
window.Close();
}
const Key KeyDeadCharProcessed = (Key)0xac; // Key.DeadCharProcessed; // new in .NET 4
public override void OnPreviewKeyDown(KeyEventArgs e)
{
// prevents crash when typing deadchar while CC window is open
if (e.Key == KeyDeadCharProcessed)
return;
e.Handled = RaiseEventPair(window, PreviewKeyDownEvent, KeyDownEvent,
new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key));
}
public override void OnPreviewKeyUp(KeyEventArgs e)
{
if (e.Key == KeyDeadCharProcessed)
return;
e.Handled = RaiseEventPair(window, PreviewKeyUpEvent, KeyUpEvent,
new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key));
}
}
#endregion
void TextViewScrollOffsetChanged(object sender, EventArgs e)
{
// Workaround for crash #1580 (reproduction steps unknown):
// NullReferenceException in System.Windows.Window.CreateSourceWindow()
if (!sourceIsInitialized)
return;
IScrollInfo scrollInfo = this.TextArea.TextView;
Rect visibleRect = new Rect(scrollInfo.HorizontalOffset, scrollInfo.VerticalOffset, scrollInfo.ViewportWidth, scrollInfo.ViewportHeight);
// close completion window when the user scrolls so far that the anchor position is leaving the visible area
if (visibleRect.Contains(visualLocation) || visibleRect.Contains(visualLocationTop))
UpdatePosition();
else
Close();
}
void TextAreaDocumentChanged(object sender, EventArgs e)
{
Close();
}
void TextAreaLostFocus(object sender, RoutedEventArgs e)
{
Dispatcher.BeginInvoke(new Action(CloseIfFocusLost), DispatcherPriority.Background);
}
void parentWindow_LocationChanged(object sender, EventArgs e)
{
UpdatePosition();
}
/// <inheritdoc/>
protected override void OnDeactivated(EventArgs e)
{
base.OnDeactivated(e);
Dispatcher.BeginInvoke(new Action(CloseIfFocusLost), DispatcherPriority.Background);
}
#endregion
/// <summary>
/// Raises a tunnel/bubble event pair for a WPF control.
/// </summary>
/// <param name="target">The WPF control for which the event should be raised.</param>
/// <param name="previewEvent">The tunneling event.</param>
/// <param name="event">The bubbling event.</param>
/// <param name="args">The event args to use.</param>
/// <returns>The <see cref="RoutedEventArgs.Handled"/> value of the event args.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
protected static bool RaiseEventPair(UIElement target, RoutedEvent previewEvent, RoutedEvent @event, RoutedEventArgs args)
{
if (target == null)
throw new ArgumentNullException("target");
if (previewEvent == null)
throw new ArgumentNullException("previewEvent");
if (@event == null)
throw new ArgumentNullException("event");
if (args == null)
throw new ArgumentNullException("args");
args.RoutedEvent = previewEvent;
target.RaiseEvent(args);
args.RoutedEvent = @event;
target.RaiseEvent(args);
return args.Handled;
}
// Special handler: handledEventsToo
void OnMouseUp(object sender, MouseButtonEventArgs e)
{
ActivateParentWindow();
}
/// <summary>
/// Activates the parent window.
/// </summary>
protected virtual void ActivateParentWindow()
{
if (parentWindow != null)
parentWindow.Activate();
}
void CloseIfFocusLost()
{
if (CloseOnFocusLost) {
Debug.WriteLine("CloseIfFocusLost: this.IsActive=" + this.IsActive + " IsTextAreaFocused=" + IsTextAreaFocused);
if (!this.IsActive && !IsTextAreaFocused) {
Close();
}
}
}
/// <summary>
/// Gets whether the completion window should automatically close when the text editor looses focus.
/// </summary>
protected virtual bool CloseOnFocusLost {
get { return true; }
}
bool IsTextAreaFocused {
get {
if (parentWindow != null && !parentWindow.IsActive)
return false;
return this.TextArea.IsKeyboardFocused;
}
}
bool sourceIsInitialized;
/// <inheritdoc/>
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
if (document != null && this.StartOffset != this.TextArea.Caret.Offset) {
SetPosition(new TextViewPosition(document.GetLocation(this.StartOffset)));
} else {
SetPosition(this.TextArea.Caret.Position);
}
sourceIsInitialized = true;
}
/// <inheritdoc/>
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
DetachEvents();
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled && e.Key == Key.Escape) {
e.Handled = true;
Close();
}
}
Point visualLocation, visualLocationTop;
/// <summary>
/// Positions the completion window at the specified position.
/// </summary>
protected void SetPosition(TextViewPosition position)
{
TextView textView = this.TextArea.TextView;
visualLocation = textView.GetVisualPosition(position, VisualYPosition.LineBottom);
visualLocationTop = textView.GetVisualPosition(position, VisualYPosition.LineTop);
UpdatePosition();
}
/// <summary>
/// Updates the position of the CompletionWindow based on the parent TextView position and the screen working area.
/// It ensures that the CompletionWindow is completely visible on the screen.
/// </summary>
protected void UpdatePosition()
{
TextView textView = this.TextArea.TextView;
// PointToScreen returns device dependent units (physical pixels)
Point location = textView.PointToScreen(visualLocation - textView.ScrollOffset);
Point locationTop = textView.PointToScreen(visualLocationTop - textView.ScrollOffset);
// Let's use device dependent units for everything
Size completionWindowSize = new Size(this.ActualWidth, this.ActualHeight).TransformToDevice(textView);
Rect bounds = new Rect(location, completionWindowSize);
Rect workingScreen = System.Windows.Forms.Screen.GetWorkingArea(location.ToSystemDrawing()).ToWpf();
if (!workingScreen.Contains(bounds)) {
if (bounds.Left < workingScreen.Left) {
bounds.X = workingScreen.Left;
} else if (bounds.Right > workingScreen.Right) {
bounds.X = workingScreen.Right - bounds.Width;
}
if (bounds.Bottom > workingScreen.Bottom) {
bounds.Y = locationTop.Y - bounds.Height;
IsUp = true;
} else {
IsUp = false;
}
if (bounds.Y < workingScreen.Top) {
bounds.Y = workingScreen.Top;
}
}
// Convert the window bounds to device independent units
bounds = bounds.TransformFromDevice(textView);
this.Left = bounds.X;
this.Top = bounds.Y;
}
/// <inheritdoc/>
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
if (sizeInfo.HeightChanged && IsUp) {
this.Top += sizeInfo.PreviousSize.Height - sizeInfo.NewSize.Height;
}
}
/// <summary>
/// Gets/sets whether the completion window should expect text insertion at the start offset,
/// which not go into the completion region, but before it.
/// </summary>
/// <remarks>This property allows only a single insertion, it is reset to false
/// when that insertion has occurred.</remarks>
public bool ExpectInsertionBeforeStart { get; set; }
void textArea_Document_Changing(object sender, DocumentChangeEventArgs e)
{
if (e.Offset + e.RemovalLength == this.StartOffset && e.RemovalLength > 0) {
Close(); // removal immediately in front of completion segment: close the window
// this is necessary when pressing backspace after dot-completion
}
if (e.Offset == StartOffset && e.RemovalLength == 0 && ExpectInsertionBeforeStart) {
StartOffset = e.GetNewOffset(StartOffset, AnchorMovementType.AfterInsertion);
this.ExpectInsertionBeforeStart = false;
} else {
StartOffset = e.GetNewOffset(StartOffset, AnchorMovementType.BeforeInsertion);
}
EndOffset = e.GetNewOffset(EndOffset, AnchorMovementType.AfterInsertion);
}
}
}

73
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/ICompletionData.cs

@ -1,73 +0,0 @@ @@ -1,73 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Editing;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#else
using ICSharpCode.AvalonEdit.Document;
#endif
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
/// <summary>
/// Describes an entry in the <see cref="CompletionList"/>.
/// </summary>
public interface ICompletionData
{
/// <summary>
/// Gets the image.
/// </summary>
ImageSource Image { get; }
/// <summary>
/// Gets the text. This property is used to filter the list of visible elements.
/// </summary>
string Text { get; }
/// <summary>
/// The displayed content. This can be the same as 'Text', or a WPF UIElement if
/// you want to display rich content.
/// </summary>
object Content { get; }
/// <summary>
/// Gets the description.
/// </summary>
object Description { get; }
/// <summary>
/// Gets the priority. This property is used in the selection logic. You can use it to prefer selecting those items
/// which the user is accessing most frequently.
/// </summary>
double Priority { get; }
/// <summary>
/// Perform the completion.
/// </summary>
/// <param name="textArea">The text area on which completion is performed.</param>
/// <param name="completionSegment">The text segment that was used by the completion window if
/// the user types (segment between CompletionWindow.StartOffset and CompletionWindow.EndOffset).</param>
/// <param name="insertionRequestEventArgs">The EventArgs used for the insertion request.
/// These can be TextCompositionEventArgs, KeyEventArgs, MouseEventArgs, depending on how
/// the insertion was triggered.</param>
void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs);
}
}

57
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/IOverloadProvider.cs

@ -1,57 +0,0 @@ @@ -1,57 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
/// <summary>
/// Provides the items for the OverloadViewer.
/// </summary>
public interface IOverloadProvider : INotifyPropertyChanged
{
/// <summary>
/// Gets/Sets the selected index.
/// </summary>
int SelectedIndex { get; set; }
/// <summary>
/// Gets the number of overloads.
/// </summary>
int Count { get; }
/// <summary>
/// Gets the text 'SelectedIndex of Count'.
/// </summary>
string CurrentIndexText { get; }
/// <summary>
/// Gets the current header.
/// </summary>
object CurrentHeader { get; }
/// <summary>
/// Gets the current content.
/// </summary>
object CurrentContent { get; }
}
}

109
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/InsightWindow.cs

@ -1,109 +0,0 @@ @@ -1,109 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows;
using System.Windows.Controls;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
/// <summary>
/// A popup-like window that is attached to a text segment.
/// </summary>
public class InsightWindow : CompletionWindowBase
{
static InsightWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(InsightWindow),
new FrameworkPropertyMetadata(typeof(InsightWindow)));
AllowsTransparencyProperty.OverrideMetadata(typeof(InsightWindow),
new FrameworkPropertyMetadata(Boxes.True));
}
/// <summary>
/// Creates a new InsightWindow.
/// </summary>
public InsightWindow(TextArea textArea) : base(textArea)
{
this.CloseAutomatically = true;
AttachEvents();
}
/// <inheritdoc/>
protected override void OnSourceInitialized(EventArgs e)
{
Rect caret = this.TextArea.Caret.CalculateCaretRectangle();
Point pointOnScreen = this.TextArea.TextView.PointToScreen(caret.Location - this.TextArea.TextView.ScrollOffset);
Rect workingArea = System.Windows.Forms.Screen.FromPoint(pointOnScreen.ToSystemDrawing()).WorkingArea.ToWpf().TransformFromDevice(this);
MaxHeight = workingArea.Height;
MaxWidth = Math.Min(workingArea.Width, Math.Max(1000, workingArea.Width * 0.6));
base.OnSourceInitialized(e);
}
/// <summary>
/// Gets/Sets whether the insight window should close automatically.
/// The default value is true.
/// </summary>
public bool CloseAutomatically { get; set; }
/// <inheritdoc/>
protected override bool CloseOnFocusLost {
get { return this.CloseAutomatically; }
}
void AttachEvents()
{
this.TextArea.Caret.PositionChanged += CaretPositionChanged;
}
/// <inheritdoc/>
protected override void DetachEvents()
{
this.TextArea.Caret.PositionChanged -= CaretPositionChanged;
base.DetachEvents();
}
void CaretPositionChanged(object sender, EventArgs e)
{
if (this.CloseAutomatically) {
int offset = this.TextArea.Caret.Offset;
if (offset < this.StartOffset || offset > this.EndOffset) {
Close();
}
}
}
}
/// <summary>
/// TemplateSelector for InsightWindow to replace plain string content by a TextBlock with TextWrapping.
/// </summary>
internal sealed class InsightWindowTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is string)
return (DataTemplate)((FrameworkElement)container).FindResource("TextBlockTemplate");
return null;
}
}
}

118
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/InsightWindow.xaml

@ -1,118 +0,0 @@ @@ -1,118 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:ICSharpCode.AvalonEdit.CodeCompletion"
>
<cc:InsightWindowTemplateSelector x:Key="templateSelector" />
<!-- Template for InsightWindow. Based on the template for ToolTip. -->
<Style TargetType="{x:Type cc:InsightWindow}">
<Setter Property="SizeToContent" Value="WidthAndHeight" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" />
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.InfoBrushKey}}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InfoTextBrushKey}}" />
<Setter Property="FontFamily" Value="{DynamicResource {x:Static SystemFonts.StatusFontFamilyKey}}" />
<Setter Property="FontSize" Value="{DynamicResource {x:Static SystemFonts.StatusFontSizeKey}}" />
<Setter Property="FontStyle" Value="{DynamicResource {x:Static SystemFonts.StatusFontStyleKey}}" />
<Setter Property="FontWeight" Value="{DynamicResource {x:Static SystemFonts.StatusFontWeightKey}}" />
<Setter Property="Padding" Value="1,1,3,1" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type cc:InsightWindow}">
<Border BorderThickness="{TemplateBinding Border.BorderThickness}"
Padding="{TemplateBinding Control.Padding}"
CornerRadius="2,2,2,2"
BorderBrush="{TemplateBinding Border.BorderBrush}"
Background="{TemplateBinding Panel.Background}">
<AdornerDecorator>
<ContentPresenter
Content="{TemplateBinding ContentControl.Content}"
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}"
HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</AdornerDecorator>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Template for OverloadViewer. -->
<Style TargetType="{x:Type cc:OverloadViewer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type cc:OverloadViewer}">
<Grid>
<Grid.Resources>
<cc:CollapseIfSingleOverloadConverter x:Key="collapseIfSingleOverloadConverter"/>
<!-- Style of the UpDownButton -->
<Style TargetType="{x:Type Button}" x:Key="upDownButtonStyle">
<Style.Setters>
<Setter Property="Background" Value="LightGray"/>
<Setter Property="Padding" Value="2,2,2,2"/>
<Setter Property="Width" Value="9"/>
<Setter Property="Height" Value="9"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Name="bd"
Background="{TemplateBinding Background}" CornerRadius="2">
<ContentPresenter Margin="{TemplateBinding Padding}"
Content="{TemplateBinding Content}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="bd" Property="Background" Value="LightBlue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
<DataTemplate x:Key="TextBlockTemplate">
<TextBlock TextWrapping="Wrap" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
</DataTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0"
Margin="0,0,4,0"
Orientation="Horizontal"
Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Provider.Count, Converter={StaticResource collapseIfSingleOverloadConverter}}">
<Button Name="PART_UP" Style="{StaticResource upDownButtonStyle}">
<Path Stroke="Black" Fill="Black" Data="M 0,0.866 L 1,0.866 L 0.5,0 Z" Stretch="UniformToFill" />
</Button>
<TextBlock Margin="2,0,2,0"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Provider.CurrentIndexText}"/>
<Button Name="PART_DOWN" Style="{StaticResource upDownButtonStyle}">
<Path Stroke="Black" Fill="Black" Data="M 0,0 L 1,0 L 0.5,0.866 Z" Stretch="UniformToFill" />
</Button>
</StackPanel>
<ContentPresenter Grid.Row="0" Grid.Column="1" ContentTemplateSelector="{StaticResource templateSelector}"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Provider.CurrentHeader}" />
<ContentPresenter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" ContentTemplateSelector="{StaticResource templateSelector}"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Provider.CurrentContent}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

73
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/OverloadInsightWindow.cs

@ -1,73 +0,0 @@ @@ -1,73 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Editing;
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
/// <summary>
/// Insight window that shows an OverloadViewer.
/// </summary>
public class OverloadInsightWindow : InsightWindow
{
OverloadViewer overloadViewer = new OverloadViewer();
/// <summary>
/// Creates a new OverloadInsightWindow.
/// </summary>
public OverloadInsightWindow(TextArea textArea) : base(textArea)
{
overloadViewer.Margin = new Thickness(2,0,0,0);
this.Content = overloadViewer;
}
/// <summary>
/// Gets/Sets the item provider.
/// </summary>
public IOverloadProvider Provider {
get { return overloadViewer.Provider; }
set { overloadViewer.Provider = value; }
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled && this.Provider != null && this.Provider.Count > 1) {
switch (e.Key) {
case Key.Up:
e.Handled = true;
overloadViewer.ChangeIndex(-1);
break;
case Key.Down:
e.Handled = true;
overloadViewer.ChangeIndex(+1);
break;
}
if (e.Handled) {
UpdateLayout();
UpdatePosition();
}
}
}
}
}

116
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/OverloadViewer.cs

@ -1,116 +0,0 @@ @@ -1,116 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
/// <summary>
/// Represents a text between "Up" and "Down" buttons.
/// </summary>
public class OverloadViewer : Control
{
static OverloadViewer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(OverloadViewer),
new FrameworkPropertyMetadata(typeof(OverloadViewer)));
}
/// <summary>
/// The text property.
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(OverloadViewer));
/// <summary>
/// Gets/Sets the text between the Up and Down buttons.
/// </summary>
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <inheritdoc/>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button upButton = (Button)this.Template.FindName("PART_UP", this);
upButton.Click += (sender, e) => {
e.Handled = true;
ChangeIndex(-1);
};
Button downButton = (Button)this.Template.FindName("PART_DOWN", this);
downButton.Click += (sender, e) => {
e.Handled = true;
ChangeIndex(+1);
};
}
/// <summary>
/// The ItemProvider property.
/// </summary>
public static readonly DependencyProperty ProviderProperty =
DependencyProperty.Register("Provider", typeof(IOverloadProvider), typeof(OverloadViewer));
/// <summary>
/// Gets/Sets the item provider.
/// </summary>
public IOverloadProvider Provider {
get { return (IOverloadProvider)GetValue(ProviderProperty); }
set { SetValue(ProviderProperty, value); }
}
/// <summary>
/// Changes the selected index.
/// </summary>
/// <param name="relativeIndexChange">The relative index change - usual values are +1 or -1.</param>
public void ChangeIndex(int relativeIndexChange)
{
IOverloadProvider p = this.Provider;
if (p != null) {
int newIndex = p.SelectedIndex + relativeIndexChange;
if (newIndex < 0)
newIndex = p.Count - 1;
if (newIndex >= p.Count)
newIndex = 0;
p.SelectedIndex = newIndex;
}
}
}
sealed class CollapseIfSingleOverloadConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((int)value < 2) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

123
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeEventArgs.cs

@ -1,123 +0,0 @@ @@ -1,123 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.NRefactory.Editor;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Describes a change of the document text.
/// This class is thread-safe.
/// </summary>
[Serializable]
public class DocumentChangeEventArgs : TextChangeEventArgs
{
volatile OffsetChangeMap offsetChangeMap;
/// <summary>
/// Gets the OffsetChangeMap associated with this document change.
/// </summary>
/// <remarks>The OffsetChangeMap instance is guaranteed to be frozen and thus thread-safe.</remarks>
public OffsetChangeMap OffsetChangeMap {
get {
OffsetChangeMap map = offsetChangeMap;
if (map == null) {
// create OffsetChangeMap on demand
map = OffsetChangeMap.FromSingleElement(CreateSingleChangeMapEntry());
offsetChangeMap = map;
}
return map;
}
}
internal OffsetChangeMapEntry CreateSingleChangeMapEntry()
{
return new OffsetChangeMapEntry(this.Offset, this.RemovalLength, this.InsertionLength);
}
/// <summary>
/// Gets the OffsetChangeMap, or null if the default offset map (=single replacement) is being used.
/// </summary>
internal OffsetChangeMap OffsetChangeMapOrNull {
get {
return offsetChangeMap;
}
}
/// <summary>
/// Gets the new offset where the specified offset moves after this document change.
/// </summary>
public override int GetNewOffset(int offset, AnchorMovementType movementType = AnchorMovementType.Default)
{
if (offsetChangeMap != null)
return offsetChangeMap.GetNewOffset(offset, movementType);
else
return CreateSingleChangeMapEntry().GetNewOffset(offset, movementType);
}
/// <summary>
/// Creates a new DocumentChangeEventArgs object.
/// </summary>
public DocumentChangeEventArgs(int offset, string removedText, string insertedText)
: this(offset, removedText, insertedText, null)
{
}
/// <summary>
/// Creates a new DocumentChangeEventArgs object.
/// </summary>
public DocumentChangeEventArgs(int offset, string removedText, string insertedText, OffsetChangeMap offsetChangeMap)
: base(offset, removedText, insertedText)
{
SetOffsetChangeMap(offsetChangeMap);
}
/// <summary>
/// Creates a new DocumentChangeEventArgs object.
/// </summary>
public DocumentChangeEventArgs(int offset, ITextSource removedText, ITextSource insertedText, OffsetChangeMap offsetChangeMap)
: base(offset, removedText, insertedText)
{
SetOffsetChangeMap(offsetChangeMap);
}
void SetOffsetChangeMap(OffsetChangeMap offsetChangeMap)
{
if (offsetChangeMap != null) {
if (!offsetChangeMap.IsFrozen)
throw new ArgumentException("The OffsetChangeMap must be frozen before it can be used in DocumentChangeEventArgs");
if (!offsetChangeMap.IsValidForDocumentChange(this.Offset, this.RemovalLength, this.InsertionLength))
throw new ArgumentException("OffsetChangeMap is not valid for this document change", "offsetChangeMap");
this.offsetChangeMap = offsetChangeMap;
}
}
/// <inheritdoc/>
public override TextChangeEventArgs Invert()
{
OffsetChangeMap map = this.OffsetChangeMapOrNull;
if (map != null) {
map = map.Invert();
map.Freeze();
}
return new DocumentChangeEventArgs(this.Offset, this.InsertedText, this.RemovedText, map);
}
}
}

67
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeOperation.cs

@ -1,67 +0,0 @@ @@ -1,67 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Describes a change to a TextDocument.
/// </summary>
sealed class DocumentChangeOperation : IUndoableOperationWithContext
{
TextDocument document;
DocumentChangeEventArgs change;
public DocumentChangeOperation(TextDocument document, DocumentChangeEventArgs change)
{
this.document = document;
this.change = change;
}
public void Undo(UndoStack stack)
{
Debug.Assert(stack.state == UndoStack.StatePlayback);
stack.RegisterAffectedDocument(document);
stack.state = UndoStack.StatePlaybackModifyDocument;
this.Undo();
stack.state = UndoStack.StatePlayback;
}
public void Redo(UndoStack stack)
{
Debug.Assert(stack.state == UndoStack.StatePlayback);
stack.RegisterAffectedDocument(document);
stack.state = UndoStack.StatePlaybackModifyDocument;
this.Redo();
stack.state = UndoStack.StatePlayback;
}
public void Undo()
{
OffsetChangeMap map = change.OffsetChangeMapOrNull;
document.Replace(change.Offset, change.InsertionLength, change.RemovedText, map != null ? map.Invert() : null);
}
public void Redo()
{
document.Replace(change.Offset, change.RemovalLength, change.InsertedText, change.OffsetChangeMapOrNull);
}
}
}

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

@ -1,268 +0,0 @@ @@ -1,268 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Globalization;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Represents a line inside a <see cref="TextDocument"/>.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="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 reflect the current document content.
/// </para>
/// <para>
/// Internally, the DocumentLine instances are arranged in a binary tree that allows for both efficient updates and lookup.
/// Converting between offset and line number is possible in O(lg N) time,
/// and the data structure also updates all offsets in O(lg N) whenever a line is inserted or removed.
/// </para>
/// </remarks>
public sealed partial class DocumentLine : IDocumentLine
{
#region Constructor
#if DEBUG
// Required for thread safety check which is done only in debug builds.
// To save space, we don't store the document reference in release builds as we don't need it there.
readonly TextDocument document;
#endif
internal bool isDeleted;
internal DocumentLine(TextDocument document)
{
#if DEBUG
Debug.Assert(document != null);
this.document = document;
#endif
}
[Conditional("DEBUG")]
void DebugVerifyAccess()
{
#if DEBUG
document.DebugVerifyAccess();
#endif
}
#endregion
#region Events
// /// <summary>
// /// Is raised when the line is deleted.
// /// </summary>
// public event EventHandler Deleted;
//
// /// <summary>
// /// Is raised when the line's text changes.
// /// </summary>
// public event EventHandler TextChanged;
//
// /// <summary>
// /// Raises the Deleted or TextChanged event.
// /// </summary>
// internal void RaiseChanged()
// {
// if (IsDeleted) {
// if (Deleted != null)
// Deleted(this, EventArgs.Empty);
// } else {
// if (TextChanged != null)
// TextChanged(this, EventArgs.Empty);
// }
// }
#endregion
#region Properties stored in tree
/// <summary>
/// Gets if this line was deleted from the document.
/// </summary>
public bool IsDeleted {
get {
DebugVerifyAccess();
return isDeleted;
}
}
/// <summary>
/// Gets the number of this line.
/// Runtime: O(log n)
/// </summary>
/// <exception cref="InvalidOperationException">The line was deleted.</exception>
public int LineNumber {
get {
if (IsDeleted)
throw new InvalidOperationException();
return DocumentLineTree.GetIndexFromNode(this) + 1;
}
}
/// <summary>
/// Gets the starting offset of the line in the document's text.
/// Runtime: O(log n)
/// </summary>
/// <exception cref="InvalidOperationException">The line was deleted.</exception>
public int Offset {
get {
if (IsDeleted)
throw new InvalidOperationException();
return DocumentLineTree.GetOffsetFromNode(this);
}
}
/// <summary>
/// Gets the end offset of the line in the document's text (the offset before the line delimiter).
/// Runtime: O(log n)
/// </summary>
/// <exception cref="InvalidOperationException">The line was deleted.</exception>
/// <remarks>EndOffset = <see cref="Offset"/> + <see cref="Length"/>.</remarks>
public int EndOffset {
get { return this.Offset + this.Length; }
}
#endregion
#region Length
int totalLength;
byte delimiterLength;
/// <summary>
/// Gets the length of this line. The length does not include the line delimiter. O(1)
/// </summary>
/// <remarks>This property is still available even if the line was deleted;
/// in that case, it contains the line's length before the deletion.</remarks>
public int Length {
get {
DebugVerifyAccess();
return totalLength - delimiterLength;
}
}
/// <summary>
/// Gets the length of this line, including the line delimiter. O(1)
/// </summary>
/// <remarks>This property is still available even if the line was deleted;
/// in that case, it contains the line's length before the deletion.</remarks>
public int TotalLength {
get {
DebugVerifyAccess();
return totalLength;
}
internal set {
// this is set by DocumentLineTree
totalLength = value;
}
}
/// <summary>
/// <para>Gets the length of the line delimiter.</para>
/// <para>The value is 1 for single <c>"\r"</c> or <c>"\n"</c>, 2 for the <c>"\r\n"</c> sequence;
/// and 0 for the last line in the document.</para>
/// </summary>
/// <remarks>This property is still available even if the line was deleted;
/// in that case, it contains the line delimiter's length before the deletion.</remarks>
public int DelimiterLength {
get {
DebugVerifyAccess();
return delimiterLength;
}
internal set {
Debug.Assert(value >= 0 && value <= 2);
delimiterLength = (byte)value;
}
}
#endregion
#region Previous / Next Line
/// <summary>
/// Gets the next line in the document.
/// </summary>
/// <returns>The line following this line, or null if this is the last line.</returns>
public DocumentLine NextLine {
get {
DebugVerifyAccess();
if (right != null) {
return right.LeftMost;
} else {
DocumentLine node = this;
DocumentLine oldNode;
do {
oldNode = node;
node = node.parent;
// we are on the way up from the right part, don't output node again
} while (node != null && node.right == oldNode);
return node;
}
}
}
/// <summary>
/// Gets the previous line in the document.
/// </summary>
/// <returns>The line before this line, or null if this is the first line.</returns>
public DocumentLine PreviousLine {
get {
DebugVerifyAccess();
if (left != null) {
return left.RightMost;
} else {
DocumentLine node = this;
DocumentLine oldNode;
do {
oldNode = node;
node = node.parent;
// we are on the way up from the left part, don't output node again
} while (node != null && node.left == oldNode);
return node;
}
}
}
IDocumentLine IDocumentLine.NextLine {
get { return this.NextLine; }
}
IDocumentLine IDocumentLine.PreviousLine {
get { return this.PreviousLine; }
}
#endregion
#region ToString
/// <summary>
/// Gets a string with debug output showing the line number and offset.
/// Does not include the line's text.
/// </summary>
public override string ToString()
{
if (IsDeleted)
return "[DocumentLine deleted]";
else
return string.Format(
CultureInfo.InvariantCulture,
"[DocumentLine Number={0} Offset={1} Length={2}]", LineNumber, Offset, Length);
}
#endregion
}
}

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

@ -1,727 +0,0 @@ @@ -1,727 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace ICSharpCode.AvalonEdit.Document
{
using LineNode = DocumentLine;
/// <summary>
/// Data structure for efficient management of the document lines (most operations are O(lg n)).
/// This implements an augmented red-black tree.
/// See <see cref="LineNode"/> for the augmented data.
///
/// NOTE: The tree is never empty, initially it contains an empty line.
/// </summary>
sealed class DocumentLineTree : IList<DocumentLine>
{
#region Constructor
readonly TextDocument document;
LineNode root;
public DocumentLineTree(TextDocument document)
{
this.document = document;
DocumentLine emptyLine = new DocumentLine(document);
root = emptyLine.InitLineNode();
}
#endregion
#region Rotation callbacks
internal static void UpdateAfterChildrenChange(LineNode node)
{
int totalCount = 1;
int totalLength = node.TotalLength;
if (node.left != null) {
totalCount += node.left.nodeTotalCount;
totalLength += node.left.nodeTotalLength;
}
if (node.right != null) {
totalCount += node.right.nodeTotalCount;
totalLength += node.right.nodeTotalLength;
}
if (totalCount != node.nodeTotalCount
|| totalLength != node.nodeTotalLength)
{
node.nodeTotalCount = totalCount;
node.nodeTotalLength = totalLength;
if (node.parent != null) UpdateAfterChildrenChange(node.parent);
}
}
static void UpdateAfterRotateLeft(LineNode node)
{
UpdateAfterChildrenChange(node);
// not required: rotations only happen on insertions/deletions
// -> totalCount changes -> the parent is always updated
//UpdateAfterChildrenChange(node.parent);
}
static void UpdateAfterRotateRight(LineNode node)
{
UpdateAfterChildrenChange(node);
// not required: rotations only happen on insertions/deletions
// -> totalCount changes -> the parent is always updated
//UpdateAfterChildrenChange(node.parent);
}
#endregion
#region RebuildDocument
/// <summary>
/// Rebuild the tree, in O(n).
/// </summary>
public void RebuildTree(List<DocumentLine> documentLines)
{
LineNode[] nodes = new LineNode[documentLines.Count];
for (int i = 0; i < documentLines.Count; i++) {
DocumentLine ls = documentLines[i];
LineNode node = ls.InitLineNode();
nodes[i] = node;
}
Debug.Assert(nodes.Length > 0);
// now build the corresponding balanced tree
int height = GetTreeHeight(nodes.Length);
Debug.WriteLine("DocumentLineTree will have height: " + height);
root = BuildTree(nodes, 0, nodes.Length, height);
root.color = BLACK;
#if DEBUG
CheckProperties();
#endif
}
internal static int GetTreeHeight(int size)
{
if (size == 0)
return 0;
else
return GetTreeHeight(size / 2) + 1;
}
/// <summary>
/// build a tree from a list of nodes
/// </summary>
LineNode BuildTree(LineNode[] nodes, int start, int end, int subtreeHeight)
{
Debug.Assert(start <= end);
if (start == end) {
return null;
}
int middle = (start + end) / 2;
LineNode node = nodes[middle];
node.left = BuildTree(nodes, start, middle, subtreeHeight - 1);
node.right = BuildTree(nodes, middle + 1, end, subtreeHeight - 1);
if (node.left != null) node.left.parent = node;
if (node.right != null) node.right.parent = node;
if (subtreeHeight == 1)
node.color = RED;
UpdateAfterChildrenChange(node);
return node;
}
#endregion
#region GetNodeBy... / Get...FromNode
LineNode GetNodeByIndex(int index)
{
Debug.Assert(index >= 0);
Debug.Assert(index < root.nodeTotalCount);
LineNode node = root;
while (true) {
if (node.left != null && index < node.left.nodeTotalCount) {
node = node.left;
} else {
if (node.left != null) {
index -= node.left.nodeTotalCount;
}
if (index == 0)
return node;
index--;
node = node.right;
}
}
}
internal static int GetIndexFromNode(LineNode node)
{
int index = (node.left != null) ? node.left.nodeTotalCount : 0;
while (node.parent != null) {
if (node == node.parent.right) {
if (node.parent.left != null)
index += node.parent.left.nodeTotalCount;
index++;
}
node = node.parent;
}
return index;
}
LineNode GetNodeByOffset(int offset)
{
Debug.Assert(offset >= 0);
Debug.Assert(offset <= root.nodeTotalLength);
if (offset == root.nodeTotalLength) {
return root.RightMost;
}
LineNode node = root;
while (true) {
if (node.left != null && offset < node.left.nodeTotalLength) {
node = node.left;
} else {
if (node.left != null) {
offset -= node.left.nodeTotalLength;
}
offset -= node.TotalLength;
if (offset < 0)
return node;
node = node.right;
}
}
}
internal static int GetOffsetFromNode(LineNode node)
{
int offset = (node.left != null) ? node.left.nodeTotalLength : 0;
while (node.parent != null) {
if (node == node.parent.right) {
if (node.parent.left != null)
offset += node.parent.left.nodeTotalLength;
offset += node.parent.TotalLength;
}
node = node.parent;
}
return offset;
}
#endregion
#region GetLineBy
public DocumentLine GetByNumber(int number)
{
return GetNodeByIndex(number - 1);
}
public DocumentLine GetByOffset(int offset)
{
return GetNodeByOffset(offset);
}
#endregion
#region LineCount
public int LineCount {
get {
return root.nodeTotalCount;
}
}
#endregion
#region CheckProperties
#if DEBUG
[Conditional("DATACONSISTENCYTEST")]
internal void CheckProperties()
{
Debug.Assert(root.nodeTotalLength == document.TextLength);
CheckProperties(root);
// check red-black property:
int blackCount = -1;
CheckNodeProperties(root, null, RED, 0, ref blackCount);
}
void CheckProperties(LineNode node)
{
int totalCount = 1;
int totalLength = node.TotalLength;
if (node.left != null) {
CheckProperties(node.left);
totalCount += node.left.nodeTotalCount;
totalLength += node.left.nodeTotalLength;
}
if (node.right != null) {
CheckProperties(node.right);
totalCount += node.right.nodeTotalCount;
totalLength += node.right.nodeTotalLength;
}
Debug.Assert(node.nodeTotalCount == totalCount);
Debug.Assert(node.nodeTotalLength == totalLength);
}
/*
1. A node is either red or black.
2. The root is black.
3. All leaves are black. (The leaves are the NIL children.)
4. Both children of every red node are black. (So every red node must have a black parent.)
5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.)
*/
void CheckNodeProperties(LineNode node, LineNode parentNode, bool parentColor, int blackCount, ref int expectedBlackCount)
{
if (node == null) return;
Debug.Assert(node.parent == parentNode);
if (parentColor == RED) {
Debug.Assert(node.color == BLACK);
}
if (node.color == BLACK) {
blackCount++;
}
if (node.left == null && node.right == null) {
// node is a leaf node:
if (expectedBlackCount == -1)
expectedBlackCount = blackCount;
else
Debug.Assert(expectedBlackCount == blackCount);
}
CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount);
CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public string GetTreeAsString()
{
StringBuilder b = new StringBuilder();
AppendTreeToString(root, b, 0);
return b.ToString();
}
static void AppendTreeToString(LineNode node, StringBuilder b, int indent)
{
if (node.color == RED)
b.Append("RED ");
else
b.Append("BLACK ");
b.AppendLine(node.ToString());
indent += 2;
if (node.left != null) {
b.Append(' ', indent);
b.Append("L: ");
AppendTreeToString(node.left, b, indent);
}
if (node.right != null) {
b.Append(' ', indent);
b.Append("R: ");
AppendTreeToString(node.right, b, indent);
}
}
#endif
#endregion
#region Insert/Remove lines
public void RemoveLine(DocumentLine line)
{
RemoveNode(line);
line.isDeleted = true;
}
public DocumentLine InsertLineAfter(DocumentLine line, int totalLength)
{
DocumentLine newLine = new DocumentLine(document);
newLine.TotalLength = totalLength;
InsertAfter(line, newLine);
return newLine;
}
void InsertAfter(LineNode node, DocumentLine newLine)
{
LineNode newNode = newLine.InitLineNode();
if (node.right == null) {
InsertAsRight(node, newNode);
} else {
InsertAsLeft(node.right.LeftMost, newNode);
}
}
#endregion
#region Red/Black Tree
internal const bool RED = true;
internal const bool BLACK = false;
void InsertAsLeft(LineNode parentNode, LineNode newNode)
{
Debug.Assert(parentNode.left == null);
parentNode.left = newNode;
newNode.parent = parentNode;
newNode.color = RED;
UpdateAfterChildrenChange(parentNode);
FixTreeOnInsert(newNode);
}
void InsertAsRight(LineNode parentNode, LineNode newNode)
{
Debug.Assert(parentNode.right == null);
parentNode.right = newNode;
newNode.parent = parentNode;
newNode.color = RED;
UpdateAfterChildrenChange(parentNode);
FixTreeOnInsert(newNode);
}
void FixTreeOnInsert(LineNode node)
{
Debug.Assert(node != null);
Debug.Assert(node.color == RED);
Debug.Assert(node.left == null || node.left.color == BLACK);
Debug.Assert(node.right == null || node.right.color == BLACK);
LineNode parentNode = node.parent;
if (parentNode == null) {
// we inserted in the root -> the node must be black
// since this is a root node, making the node black increments the number of black nodes
// on all paths by one, so it is still the same for all paths.
node.color = BLACK;
return;
}
if (parentNode.color == BLACK) {
// if the parent node where we inserted was black, our red node is placed correctly.
// since we inserted a red node, the number of black nodes on each path is unchanged
// -> the tree is still balanced
return;
}
// parentNode is red, so there is a conflict here!
// because the root is black, parentNode is not the root -> there is a grandparent node
LineNode grandparentNode = parentNode.parent;
LineNode uncleNode = Sibling(parentNode);
if (uncleNode != null && uncleNode.color == RED) {
parentNode.color = BLACK;
uncleNode.color = BLACK;
grandparentNode.color = RED;
FixTreeOnInsert(grandparentNode);
return;
}
// now we know: parent is red but uncle is black
// First rotation:
if (node == parentNode.right && parentNode == grandparentNode.left) {
RotateLeft(parentNode);
node = node.left;
} else if (node == parentNode.left && parentNode == grandparentNode.right) {
RotateRight(parentNode);
node = node.right;
}
// because node might have changed, reassign variables:
parentNode = node.parent;
grandparentNode = parentNode.parent;
// Now recolor a bit:
parentNode.color = BLACK;
grandparentNode.color = RED;
// Second rotation:
if (node == parentNode.left && parentNode == grandparentNode.left) {
RotateRight(grandparentNode);
} else {
// because of the first rotation, this is guaranteed:
Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right);
RotateLeft(grandparentNode);
}
}
void RemoveNode(LineNode removedNode)
{
if (removedNode.left != null && removedNode.right != null) {
// replace removedNode with it's in-order successor
LineNode leftMost = removedNode.right.LeftMost;
RemoveNode(leftMost); // remove leftMost from its current location
// and overwrite the removedNode with it
ReplaceNode(removedNode, leftMost);
leftMost.left = removedNode.left;
if (leftMost.left != null) leftMost.left.parent = leftMost;
leftMost.right = removedNode.right;
if (leftMost.right != null) leftMost.right.parent = leftMost;
leftMost.color = removedNode.color;
UpdateAfterChildrenChange(leftMost);
if (leftMost.parent != null) UpdateAfterChildrenChange(leftMost.parent);
return;
}
// now either removedNode.left or removedNode.right is null
// get the remaining child
LineNode parentNode = removedNode.parent;
LineNode childNode = removedNode.left ?? removedNode.right;
ReplaceNode(removedNode, childNode);
if (parentNode != null) UpdateAfterChildrenChange(parentNode);
if (removedNode.color == BLACK) {
if (childNode != null && childNode.color == RED) {
childNode.color = BLACK;
} else {
FixTreeOnDelete(childNode, parentNode);
}
}
}
void FixTreeOnDelete(LineNode node, LineNode parentNode)
{
Debug.Assert(node == null || node.parent == parentNode);
if (parentNode == null)
return;
// warning: node may be null
LineNode sibling = Sibling(node, parentNode);
if (sibling.color == RED) {
parentNode.color = RED;
sibling.color = BLACK;
if (node == parentNode.left) {
RotateLeft(parentNode);
} else {
RotateRight(parentNode);
}
sibling = Sibling(node, parentNode); // update value of sibling after rotation
}
if (parentNode.color == BLACK
&& sibling.color == BLACK
&& GetColor(sibling.left) == BLACK
&& GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
FixTreeOnDelete(parentNode, parentNode.parent);
return;
}
if (parentNode.color == RED
&& sibling.color == BLACK
&& GetColor(sibling.left) == BLACK
&& GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
parentNode.color = BLACK;
return;
}
if (node == parentNode.left &&
sibling.color == BLACK &&
GetColor(sibling.left) == RED &&
GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
sibling.left.color = BLACK;
RotateRight(sibling);
}
else if (node == parentNode.right &&
sibling.color == BLACK &&
GetColor(sibling.right) == RED &&
GetColor(sibling.left) == BLACK)
{
sibling.color = RED;
sibling.right.color = BLACK;
RotateLeft(sibling);
}
sibling = Sibling(node, parentNode); // update value of sibling after rotation
sibling.color = parentNode.color;
parentNode.color = BLACK;
if (node == parentNode.left) {
if (sibling.right != null) {
Debug.Assert(sibling.right.color == RED);
sibling.right.color = BLACK;
}
RotateLeft(parentNode);
} else {
if (sibling.left != null) {
Debug.Assert(sibling.left.color == RED);
sibling.left.color = BLACK;
}
RotateRight(parentNode);
}
}
void ReplaceNode(LineNode replacedNode, LineNode newNode)
{
if (replacedNode.parent == null) {
Debug.Assert(replacedNode == root);
root = newNode;
} else {
if (replacedNode.parent.left == replacedNode)
replacedNode.parent.left = newNode;
else
replacedNode.parent.right = newNode;
}
if (newNode != null) {
newNode.parent = replacedNode.parent;
}
replacedNode.parent = null;
}
void RotateLeft(LineNode p)
{
// let q be p's right child
LineNode q = p.right;
Debug.Assert(q != null);
Debug.Assert(q.parent == p);
// set q to be the new root
ReplaceNode(p, q);
// set p's right child to be q's left child
p.right = q.left;
if (p.right != null) p.right.parent = p;
// set q's left child to be p
q.left = p;
p.parent = q;
UpdateAfterRotateLeft(p);
}
void RotateRight(LineNode p)
{
// let q be p's left child
LineNode q = p.left;
Debug.Assert(q != null);
Debug.Assert(q.parent == p);
// set q to be the new root
ReplaceNode(p, q);
// set p's left child to be q's right child
p.left = q.right;
if (p.left != null) p.left.parent = p;
// set q's right child to be p
q.right = p;
p.parent = q;
UpdateAfterRotateRight(p);
}
static LineNode Sibling(LineNode node)
{
if (node == node.parent.left)
return node.parent.right;
else
return node.parent.left;
}
static LineNode Sibling(LineNode node, LineNode parentNode)
{
Debug.Assert(node == null || node.parent == parentNode);
if (node == parentNode.left)
return parentNode.right;
else
return parentNode.left;
}
static bool GetColor(LineNode node)
{
return node != null ? node.color : BLACK;
}
#endregion
#region IList implementation
DocumentLine IList<DocumentLine>.this[int index] {
get {
document.VerifyAccess();
return GetByNumber(1 + index);
}
set {
throw new NotSupportedException();
}
}
int ICollection<DocumentLine>.Count {
get {
document.VerifyAccess();
return LineCount;
}
}
bool ICollection<DocumentLine>.IsReadOnly {
get { return true; }
}
int IList<DocumentLine>.IndexOf(DocumentLine item)
{
document.VerifyAccess();
if (item == null || item.IsDeleted)
return -1;
int index = item.LineNumber - 1;
if (index < LineCount && GetNodeByIndex(index) == item)
return index;
else
return -1;
}
void IList<DocumentLine>.Insert(int index, DocumentLine item)
{
throw new NotSupportedException();
}
void IList<DocumentLine>.RemoveAt(int index)
{
throw new NotSupportedException();
}
void ICollection<DocumentLine>.Add(DocumentLine item)
{
throw new NotSupportedException();
}
void ICollection<DocumentLine>.Clear()
{
throw new NotSupportedException();
}
bool ICollection<DocumentLine>.Contains(DocumentLine item)
{
IList<DocumentLine> self = this;
return self.IndexOf(item) >= 0;
}
void ICollection<DocumentLine>.CopyTo(DocumentLine[] array, int arrayIndex)
{
if (array == null)
throw new ArgumentNullException("array");
if (array.Length < LineCount)
throw new ArgumentException("The array is too small", "array");
if (arrayIndex < 0 || arrayIndex + LineCount > array.Length)
throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "Value must be between 0 and " + (array.Length - LineCount));
foreach (DocumentLine ls in this) {
array[arrayIndex++] = ls;
}
}
bool ICollection<DocumentLine>.Remove(DocumentLine item)
{
throw new NotSupportedException();
}
public IEnumerator<DocumentLine> GetEnumerator()
{
document.VerifyAccess();
return Enumerate();
}
IEnumerator<DocumentLine> Enumerate()
{
document.VerifyAccess();
DocumentLine line = root.LeftMost;
while (line != null) {
yield return line;
line = line.NextLine;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
}
}

86
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentTextWriter.cs

@ -1,86 +0,0 @@ @@ -1,86 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.IO;
using System.Text;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// A TextWriter implementation that directly inserts into a document.
/// </summary>
public class DocumentTextWriter : TextWriter
{
readonly IDocument document;
int insertionOffset;
/// <summary>
/// Creates a new DocumentTextWriter that inserts into document, starting at insertionOffset.
/// </summary>
public DocumentTextWriter(IDocument document, int insertionOffset)
{
this.insertionOffset = insertionOffset;
if (document == null)
throw new ArgumentNullException("document");
this.document = document;
var line = document.GetLineByOffset(insertionOffset);
if (line.DelimiterLength == 0)
line = line.PreviousLine;
if (line != null)
this.NewLine = document.GetText(line.EndOffset, line.DelimiterLength);
}
/// <summary>
/// Gets/Sets the current insertion offset.
/// </summary>
public int InsertionOffset {
get { return insertionOffset; }
set { insertionOffset = value; }
}
/// <inheritdoc/>
public override void Write(char value)
{
document.Insert(insertionOffset, value.ToString());
insertionOffset++;
}
/// <inheritdoc/>
public override void Write(char[] buffer, int index, int count)
{
document.Insert(insertionOffset, new string(buffer, index, count));
insertionOffset += count;
}
/// <inheritdoc/>
public override void Write(string value)
{
document.Insert(insertionOffset, value);
insertionOffset += value.Length;
}
/// <inheritdoc/>
public override Encoding Encoding {
get { return Encoding.UTF8; }
}
}
}

207
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/GapTextBuffer.cs

@ -1,207 +0,0 @@ @@ -1,207 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Text;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Document
{
/*
/// <summary>
/// Implementation of a gap text buffer.
/// </summary>
sealed class GapTextBuffer
{
char[] buffer = Empty<char>.Array;
/// <summary>
/// The current text content.
/// Is set to null whenever the buffer changes, and gets a value only when the
/// full text content is requested.
/// </summary>
string textContent;
/// <summary>
/// last GetText result
/// </summary>
string lastGetTextResult;
int lastGetTextRequestOffset;
int gapBeginOffset;
int gapEndOffset;
int gapLength; // gapLength == gapEndOffset - gapBeginOffset
/// <summary>
/// when gap is too small for inserted text or gap is too large (exceeds maxGapLength),
/// a new buffer is reallocated with a new gap of at least this size.
/// </summary>
const int minGapLength = 128;
/// <summary>
/// when the gap exceeds this size, reallocate a smaller buffer
/// </summary>
const int maxGapLength = 4096;
public int Length {
get {
return buffer.Length - gapLength;
}
}
/// <summary>
/// Gets the buffer content.
/// </summary>
public string Text {
get {
if (textContent == null)
textContent = GetText(0, Length);
return textContent;
}
set {
Debug.Assert(value != null);
textContent = value; lastGetTextResult = null;
buffer = new char[value.Length + minGapLength];
value.CopyTo(0, buffer, 0, value.Length);
gapBeginOffset = value.Length;
gapEndOffset = buffer.Length;
gapLength = gapEndOffset - gapBeginOffset;
}
}
public char GetCharAt(int offset)
{
return offset < gapBeginOffset ? buffer[offset] : buffer[offset + gapLength];
}
public string GetText(int offset, int length)
{
if (length == 0)
return string.Empty;
if (lastGetTextRequestOffset == offset && lastGetTextResult != null && length == lastGetTextResult.Length)
return lastGetTextResult;
int end = offset + length;
string result;
if (end < gapBeginOffset) {
result = new string(buffer, offset, length);
} else if (offset > gapBeginOffset) {
result = new string(buffer, offset + gapLength, length);
} else {
int block1Size = gapBeginOffset - offset;
int block2Size = end - gapBeginOffset;
StringBuilder buf = new StringBuilder(block1Size + block2Size);
buf.Append(buffer, offset, block1Size);
buf.Append(buffer, gapEndOffset, block2Size);
result = buf.ToString();
}
lastGetTextRequestOffset = offset;
lastGetTextResult = result;
return result;
}
/// <summary>
/// Inserts text at the specified offset.
/// </summary>
public void Insert(int offset, string text)
{
Debug.Assert(offset >= 0 && offset <= Length);
if (text.Length == 0)
return;
textContent = null; lastGetTextResult = null;
PlaceGap(offset, text.Length);
text.CopyTo(0, buffer, gapBeginOffset, text.Length);
gapBeginOffset += text.Length;
gapLength = gapEndOffset - gapBeginOffset;
}
/// <summary>
/// Remove <paramref name="length"/> characters at <paramref name="offset"/>.
/// Leave a gap of at least <paramref name="reserveGapSize"/>.
/// </summary>
public void Remove(int offset, int length, int reserveGapSize)
{
Debug.Assert(offset >= 0 && offset <= Length);
Debug.Assert(length >= 0 && offset + length <= Length);
Debug.Assert(reserveGapSize >= 0);
if (length == 0)
return;
textContent = null; lastGetTextResult = null;
PlaceGap(offset, reserveGapSize - length);
gapEndOffset += length; // delete removed text
gapLength = gapEndOffset - gapBeginOffset;
if (gapLength - reserveGapSize > maxGapLength && gapLength - reserveGapSize > buffer.Length / 4) {
// shrink gap
MakeNewBuffer(gapBeginOffset, reserveGapSize + minGapLength);
}
}
void PlaceGap(int newGapOffset, int minRequiredGapLength)
{
if (gapLength < minRequiredGapLength) {
// enlarge gap
MakeNewBuffer(newGapOffset, minRequiredGapLength + Math.Max(minGapLength, buffer.Length / 8));
} else {
while (newGapOffset < gapBeginOffset) {
buffer[--gapEndOffset] = buffer[--gapBeginOffset];
}
while (newGapOffset > gapBeginOffset) {
buffer[gapBeginOffset++] = buffer[gapEndOffset++];
}
}
}
void MakeNewBuffer(int newGapOffset, int newGapLength)
{
char[] newBuffer = new char[Length + newGapLength];
Debug.WriteLine("GapTextBuffer was reallocated, new size=" + newBuffer.Length);
if (newGapOffset < gapBeginOffset) {
// gap is moving backwards
// first part:
Array.Copy(buffer, 0, newBuffer, 0, newGapOffset);
// moving middle part:
Array.Copy(buffer, newGapOffset, newBuffer, newGapOffset + newGapLength, gapBeginOffset - newGapOffset);
// last part:
Array.Copy(buffer, gapEndOffset, newBuffer, newBuffer.Length - (buffer.Length - gapEndOffset), buffer.Length - gapEndOffset);
} else {
// gap is moving forwards
// first part:
Array.Copy(buffer, 0, newBuffer, 0, gapBeginOffset);
// moving middle part:
Array.Copy(buffer, gapEndOffset, newBuffer, gapBeginOffset, newGapOffset - gapBeginOffset);
// last part:
int lastPartLength = newBuffer.Length - (newGapOffset + newGapLength);
Array.Copy(buffer, buffer.Length - lastPartLength, newBuffer, newGapOffset + newGapLength, lastPartLength);
}
gapBeginOffset = newGapOffset;
gapEndOffset = newGapOffset + newGapLength;
gapLength = newGapLength;
buffer = newBuffer;
}
}
*/
}

343
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/IDocument.cs

@ -1,343 +0,0 @@ @@ -1,343 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
namespace ICSharpCode.AvalonEdit.Document
{
#if !NREFACTORY
/// <summary>
/// A document representing a source code file for refactoring.
/// Line and column counting starts at 1.
/// Offset counting starts at 0.
/// </summary>
public interface IDocument : ITextSource, IServiceProvider
{
#if NREFACTORY
/// <summary>
/// Creates an immutable snapshot of this document.
/// </summary>
IDocument CreateDocumentSnapshot();
#endif
/// <summary>
/// Gets/Sets the text of the whole document..
/// </summary>
new string Text { get; set; } // hides ITextSource.Text to add the setter
/// <summary>
/// This event is called directly before a change is applied to the document.
/// </summary>
/// <remarks>
/// It is invalid to modify the document within this event handler.
/// Aborting the change (by throwing an exception) is likely to cause corruption of data structures
/// that listen to the Changing and Changed events.
/// </remarks>
event EventHandler<TextChangeEventArgs> TextChanging;
/// <summary>
/// This event is called directly after a change is applied to the document.
/// </summary>
/// <remarks>
/// It is invalid to modify the document within this event handler.
/// Aborting the event handler (by throwing an exception) is likely to cause corruption of data structures
/// that listen to the Changing and Changed events.
/// </remarks>
event EventHandler<TextChangeEventArgs> TextChanged;
/// <summary>
/// This event is called after a group of changes is completed.
/// </summary>
/// <seealso cref="EndUndoableAction"/>
event EventHandler ChangeCompleted;
/// <summary>
/// Gets the number of lines in the document.
/// </summary>
int LineCount { get; }
/// <summary>
/// Gets the document line with the specified number.
/// </summary>
/// <param name="lineNumber">The number of the line to retrieve. The first line has number 1.</param>
IDocumentLine GetLineByNumber(int lineNumber);
/// <summary>
/// Gets the document line that contains the specified offset.
/// </summary>
IDocumentLine GetLineByOffset(int offset);
/// <summary>
/// Gets the offset from a text location.
/// </summary>
/// <seealso cref="GetLocation"/>
int GetOffset(int line, int column);
/// <summary>
/// Gets the offset from a text location.
/// </summary>
/// <seealso cref="GetLocation"/>
int GetOffset(TextLocation location);
/// <summary>
/// Gets the location from an offset.
/// </summary>
/// <seealso cref="GetOffset(TextLocation)"/>
TextLocation GetLocation(int offset);
/// <summary>
/// Inserts text.
/// </summary>
/// <param name="offset">The offset at which the text is inserted.</param>
/// <param name="text">The new text.</param>
/// <remarks>
/// Anchors positioned exactly at the insertion offset will move according to their movement type.
/// For AnchorMovementType.Default, they will move behind the inserted text.
/// The caret will also move behind the inserted text.
/// </remarks>
void Insert(int offset, string text);
/// <summary>
/// Inserts text.
/// </summary>
/// <param name="offset">The offset at which the text is inserted.</param>
/// <param name="text">The new text.</param>
/// <remarks>
/// Anchors positioned exactly at the insertion offset will move according to their movement type.
/// For AnchorMovementType.Default, they will move behind the inserted text.
/// The caret will also move behind the inserted text.
/// </remarks>
void Insert(int offset, ITextSource text);
/// <summary>
/// Inserts text.
/// </summary>
/// <param name="offset">The offset at which the text is inserted.</param>
/// <param name="text">The new text.</param>
/// <param name="defaultAnchorMovementType">
/// Anchors positioned exactly at the insertion offset will move according to the anchor's movement type.
/// For AnchorMovementType.Default, they will move according to the movement type specified by this parameter.
/// The caret will also move according to the <paramref name="defaultAnchorMovementType"/> parameter.
/// </param>
void Insert(int offset, string text, AnchorMovementType defaultAnchorMovementType);
/// <summary>
/// Inserts text.
/// </summary>
/// <param name="offset">The offset at which the text is inserted.</param>
/// <param name="text">The new text.</param>
/// <param name="defaultAnchorMovementType">
/// Anchors positioned exactly at the insertion offset will move according to the anchor's movement type.
/// For AnchorMovementType.Default, they will move according to the movement type specified by this parameter.
/// The caret will also move according to the <paramref name="defaultAnchorMovementType"/> parameter.
/// </param>
void Insert(int offset, ITextSource text, AnchorMovementType defaultAnchorMovementType);
/// <summary>
/// Removes text.
/// </summary>
/// <param name="offset">Starting offset of the text to be removed.</param>
/// <param name="length">Length of the text to be removed.</param>
void Remove(int offset, int length);
/// <summary>
/// Replaces text.
/// </summary>
/// <param name="offset">The starting offset of the text to be replaced.</param>
/// <param name="length">The length of the text to be replaced.</param>
/// <param name="newText">The new text.</param>
void Replace(int offset, int length, string newText);
/// <summary>
/// Replaces text.
/// </summary>
/// <param name="offset">The starting offset of the text to be replaced.</param>
/// <param name="length">The length of the text to be replaced.</param>
/// <param name="newText">The new text.</param>
void Replace(int offset, int length, ITextSource newText);
/// <summary>
/// Make the document combine the following actions into a single
/// action for undo purposes.
/// </summary>
void StartUndoableAction();
/// <summary>
/// Ends the undoable action started with <see cref="StartUndoableAction"/>.
/// </summary>
void EndUndoableAction();
/// <summary>
/// Creates an undo group. Dispose the returned value to close the undo group.
/// </summary>
/// <returns>An object that closes the undo group when Dispose() is called.</returns>
IDisposable OpenUndoGroup();
/// <summary>
/// Creates a new <see cref="ITextAnchor"/> at the specified offset.
/// </summary>
/// <inheritdoc cref="ITextAnchor" select="remarks|example"/>
ITextAnchor CreateAnchor(int offset);
/// <summary>
/// Gets the name of the file the document is stored in.
/// Could also be a non-existent dummy file name or null if no name has been set.
/// </summary>
string FileName { get; }
/// <summary>
/// Fired when the file name of the document changes.
/// </summary>
event EventHandler FileNameChanged;
}
/// <summary>
/// A line inside a <see cref="IDocument"/>.
/// </summary>
public interface IDocumentLine : ISegment
{
/// <summary>
/// Gets the length of this line, including the line delimiter.
/// </summary>
int TotalLength { get; }
/// <summary>
/// Gets the length of the line terminator.
/// Returns 1 or 2; or 0 at the end of the document.
/// </summary>
int DelimiterLength { get; }
/// <summary>
/// Gets the number of this line.
/// The first line has the number 1.
/// </summary>
int LineNumber { get; }
/// <summary>
/// Gets the previous line. Returns null if this is the first line in the document.
/// </summary>
IDocumentLine PreviousLine { get; }
/// <summary>
/// Gets the next line. Returns null if this is the last line in the document.
/// </summary>
IDocumentLine NextLine { get; }
/// <summary>
/// Gets whether the line was deleted.
/// </summary>
bool IsDeleted { get; }
}
/// <summary>
/// Describes a change of the document text.
/// This class is thread-safe.
/// </summary>
[Serializable]
public class TextChangeEventArgs : EventArgs
{
readonly int offset;
readonly ITextSource removedText;
readonly ITextSource insertedText;
/// <summary>
/// The offset at which the change occurs.
/// </summary>
public int Offset {
get { return offset; }
}
/// <summary>
/// The text that was removed.
/// </summary>
public ITextSource RemovedText {
get { return removedText; }
}
/// <summary>
/// The number of characters removed.
/// </summary>
public int RemovalLength {
get { return removedText.TextLength; }
}
/// <summary>
/// The text that was inserted.
/// </summary>
public ITextSource InsertedText {
get { return insertedText; }
}
/// <summary>
/// The number of characters inserted.
/// </summary>
public int InsertionLength {
get { return insertedText.TextLength; }
}
/// <summary>
/// Creates a new TextChangeEventArgs object.
/// </summary>
public TextChangeEventArgs(int offset, string removedText, string insertedText)
{
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", offset, "offset must not be negative");
this.offset = offset;
this.removedText = removedText != null ? new StringTextSource(removedText) : StringTextSource.Empty;
this.insertedText = insertedText != null ? new StringTextSource(insertedText) : StringTextSource.Empty;
}
/// <summary>
/// Creates a new TextChangeEventArgs object.
/// </summary>
public TextChangeEventArgs(int offset, ITextSource removedText, ITextSource insertedText)
{
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", offset, "offset must not be negative");
this.offset = offset;
this.removedText = removedText ?? StringTextSource.Empty;
this.insertedText = insertedText ?? StringTextSource.Empty;
}
/// <summary>
/// Gets the new offset where the specified offset moves after this document change.
/// </summary>
public virtual int GetNewOffset(int offset, AnchorMovementType movementType = AnchorMovementType.Default)
{
if (offset >= this.Offset && offset <= this.Offset + this.RemovalLength) {
if (movementType == AnchorMovementType.BeforeInsertion)
return this.Offset;
else
return this.Offset + this.InsertionLength;
} else if (offset > this.Offset) {
return offset + this.InsertionLength - this.RemovalLength;
} else {
return offset;
}
}
/// <summary>
/// Creates TextChangeEventArgs for the reverse change.
/// </summary>
public virtual TextChangeEventArgs Invert()
{
return new TextChangeEventArgs(offset, insertedText, removedText);
}
}
#endif
}

77
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ILineTracker.cs

@ -1,77 +0,0 @@ @@ -1,77 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Allows for low-level line tracking.
/// </summary>
/// <remarks>
/// The methods on this interface are called by the TextDocument's LineManager immediately after the document
/// has changed, *while* the DocumentLineTree is updating.
/// Thus, the DocumentLineTree may be in an invalid state when these methods are called.
/// This interface should only be used to update per-line data structures like the HeightTree.
/// Line trackers must not cause any events to be raised during an update to prevent other code from seeing
/// the invalid state.
/// Line trackers may be called while the TextDocument has taken a lock.
/// You must be careful not to dead-lock inside ILineTracker callbacks.
/// </remarks>
public interface ILineTracker
{
/// <summary>
/// Is called immediately before a document line is removed.
/// </summary>
void BeforeRemoveLine(DocumentLine line);
// /// <summary>
// /// Is called immediately after a document line is removed.
// /// </summary>
// void AfterRemoveLine(DocumentLine line);
/// <summary>
/// Is called immediately before a document line changes length.
/// This method will be called whenever the line is changed, even when the length stays as it is.
/// The method might be called multiple times for a single line because
/// a replacement is internally handled as removal followed by insertion.
/// </summary>
void SetLineLength(DocumentLine line, int newTotalLength);
/// <summary>
/// Is called immediately after a line was inserted.
/// </summary>
/// <param name="newLine">The new line</param>
/// <param name="insertionPos">The existing line before the new line</param>
void LineInserted(DocumentLine insertionPos, DocumentLine newLine);
/// <summary>
/// Indicates that there were changes to the document that the line tracker was not notified of.
/// The document is in a consistent state (but the line trackers aren't), and line trackers should
/// throw away their data and rebuild the document.
/// </summary>
void RebuildDocument();
/// <summary>
/// Notifies the line tracker that a document change (a single change, not a change group) has completed.
/// This method gets called after the change has been performed, but before the <see cref="TextDocument.Changed"/> event
/// is raised.
/// </summary>
void ChangeComplete(DocumentChangeEventArgs e);
}
}

142
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ITextAnchor.cs

@ -1,142 +0,0 @@ @@ -1,142 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using ICSharpCode.NRefactory;
namespace ICSharpCode.AvalonEdit.Document
{
#if !NREFACTORY
/// <summary>
/// The TextAnchor class references an offset (a position between two characters).
/// It automatically updates the offset when text is inserted/removed in front of the anchor.
/// </summary>
/// <remarks>
/// <para>Use the <see cref="ITextAnchor.Offset"/> property to get the offset from a text anchor.
/// Use the <see cref="IDocument.CreateAnchor"/> method to create an anchor from an offset.
/// </para>
/// <para>
/// The document will automatically update all text anchors; and because it uses weak references to do so,
/// the garbage collector can simply collect the anchor object when you don't need it anymore.
/// </para>
/// <para>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 <see cref="ITextAnchor.Offset"/> property also runs in O(lg N).</para>
/// </remarks>
/// <example>
/// Usage:
/// <code>TextAnchor anchor = document.CreateAnchor(offset);
/// ChangeMyDocument();
/// int newOffset = anchor.Offset;
/// </code>
/// </example>
public interface ITextAnchor
{
/// <summary>
/// Gets the text location of this anchor.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
TextLocation Location { get; }
/// <summary>
/// Gets the offset of the text anchor.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
int Offset { get; }
/// <summary>
/// Controls how the anchor moves.
/// </summary>
/// <remarks>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 <see cref="MovementType"/> will be used to determine which of these two options the anchor will choose.
/// The default value is <see cref="AnchorMovementType.Default"/>.</remarks>
AnchorMovementType MovementType { get; set; }
/// <summary>
/// <para>
/// Specifies whether the anchor survives deletion of the text containing it.
/// </para><para>
/// <c>false</c>: The anchor is deleted when the a selection that includes the anchor is deleted.
/// <c>true</c>: The anchor is not deleted.
/// </para>
/// </summary>
/// <remarks><inheritdoc cref="IsDeleted" /></remarks>
bool SurviveDeletion { get; set; }
/// <summary>
/// Gets whether the anchor was deleted.
/// </summary>
/// <remarks>
/// <para>When a piece of text containing an anchor is removed, then that anchor will be deleted.
/// First, the <see cref="IsDeleted"/> property is set to true on all deleted anchors,
/// then the <see cref="Deleted"/> events are raised.
/// You cannot retrieve the offset from an anchor that has been deleted.</para>
/// <para>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 <c><see cref="SurviveDeletion"/> = true</c>.</para>
/// </remarks>
bool IsDeleted { get; }
/// <summary>
/// Occurs after the anchor was deleted.
/// </summary>
/// <remarks>
/// <inheritdoc cref="IsDeleted" />
/// <para>Due to the 'weak reference' nature of text anchors, you will receive
/// the Deleted event only while your code holds a reference to the TextAnchor object.
/// </para>
/// </remarks>
event EventHandler Deleted;
/// <summary>
/// Gets the line number of the anchor.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
int Line { get; }
/// <summary>
/// Gets the column number of this anchor.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
int Column { get; }
}
/// <summary>
/// Defines how a text anchor moves.
/// </summary>
public enum AnchorMovementType
{
/// <summary>
/// When text is inserted at the anchor position, the type of the insertion
/// determines where the caret moves to. For normal insertions, the anchor will move
/// after the inserted text.
/// </summary>
Default,
/// <summary>
/// Behaves like a start marker - when text is inserted at the anchor position, the anchor will stay
/// before the inserted text.
/// </summary>
BeforeInsertion,
/// <summary>
/// Behave like an end marker - when text is insered at the anchor position, the anchor will move
/// after the inserted text.
/// </summary>
AfterInsertion
}
#endif
}

357
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ITextSource.cs

@ -1,357 +0,0 @@ @@ -1,357 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Collections.Generic;
using System;
using System.IO;
namespace ICSharpCode.AvalonEdit.Document
{
#if !NREFACTORY
/// <summary>
/// A read-only view on a (potentially mutable) text source.
/// The IDocument interface derives from this interface.
/// </summary>
public interface ITextSource
{
/// <summary>
/// Gets a version identifier for this text source.
/// Returns null for unversioned text sources.
/// </summary>
ITextSourceVersion Version { get; }
/// <summary>
/// Creates an immutable snapshot of this text source.
/// Unlike all other methods in this interface, this method is thread-safe.
/// </summary>
ITextSource CreateSnapshot();
/// <summary>
/// Creates an immutable snapshot of a part of this text source.
/// Unlike all other methods in this interface, this method is thread-safe.
/// </summary>
ITextSource CreateSnapshot(int offset, int length);
/// <summary>
/// Creates a new TextReader to read from this text source.
/// </summary>
TextReader CreateReader();
/// <summary>
/// Creates a new TextReader to read from this text source.
/// </summary>
TextReader CreateReader(int offset, int length);
/// <summary>
/// Gets the total text length.
/// </summary>
/// <returns>The length of the text, in characters.</returns>
/// <remarks>This is the same as Text.Length, but is more efficient because
/// it doesn't require creating a String object.</remarks>
int TextLength { get; }
/// <summary>
/// Gets the whole text as string.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")]
string Text { get; }
/// <summary>
/// Gets a character at the specified position in the document.
/// </summary>
/// <paramref name="offset">The index of the character to get.</paramref>
/// <exception cref="ArgumentOutOfRangeException">Offset is outside the valid range (0 to TextLength-1).</exception>
/// <returns>The character at the specified position.</returns>
/// <remarks>This is the same as Text[offset], but is more efficient because
/// it doesn't require creating a String object.</remarks>
char GetCharAt(int offset);
/// <summary>
/// Retrieves the text for a portion of the document.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception>
/// <remarks>This is the same as Text.Substring, but is more efficient because
/// it doesn't require creating a String object for the whole document.</remarks>
string GetText(int offset, int length);
/// <summary>
/// Retrieves the text for a portion of the document.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception>
string GetText(ISegment segment);
/// <summary>
/// Writes the text from this document into the TextWriter.
/// </summary>
void WriteTextTo(TextWriter writer);
/// <summary>
/// Writes the text from this document into the TextWriter.
/// </summary>
void WriteTextTo(TextWriter writer, int offset, int length);
/// <summary>
/// Gets the index of the first occurrence of the character in the specified array.
/// </summary>
/// <param name="c">Character to search for</param>
/// <param name="startIndex">Start index of the area to search.</param>
/// <param name="count">Length of the area to search.</param>
/// <returns>The first index where the character was found; or -1 if no occurrence was found.</returns>
int IndexOf(char c, int startIndex, int count);
/// <summary>
/// Gets the index of the first occurrence of any character in the specified array.
/// </summary>
/// <param name="anyOf">Characters to search for</param>
/// <param name="startIndex">Start index of the area to search.</param>
/// <param name="count">Length of the area to search.</param>
/// <returns>The first index where any character was found; or -1 if no occurrence was found.</returns>
int IndexOfAny(char[] anyOf, int startIndex, int count);
/// <summary>
/// Gets the index of the first occurrence of the specified search text in this text source.
/// </summary>
/// <param name="searchText">The search text</param>
/// <param name="startIndex">Start index of the area to search.</param>
/// <param name="count">Length of the area to search.</param>
/// <param name="comparisonType">String comparison to use.</param>
/// <returns>The first index where the search term was found; or -1 if no occurrence was found.</returns>
int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType);
/// <summary>
/// Gets the index of the last occurrence of the specified character in this text source.
/// </summary>
/// <param name="c">The search character</param>
/// <param name="startIndex">Start index of the area to search.</param>
/// <param name="count">Length of the area to search.</param>
/// <returns>The last index where the search term was found; or -1 if no occurrence was found.</returns>
/// <remarks>The search proceeds backwards from (startIndex+count) to startIndex.
/// This is different than the meaning of the parameters on string.LastIndexOf!</remarks>
int LastIndexOf(char c, int startIndex, int count);
/// <summary>
/// Gets the index of the last occurrence of the specified search text in this text source.
/// </summary>
/// <param name="searchText">The search text</param>
/// <param name="startIndex">Start index of the area to search.</param>
/// <param name="count">Length of the area to search.</param>
/// <param name="comparisonType">String comparison to use.</param>
/// <returns>The last index where the search term was found; or -1 if no occurrence was found.</returns>
/// <remarks>The search proceeds backwards from (startIndex+count) to startIndex.
/// This is different than the meaning of the parameters on string.LastIndexOf!</remarks>
int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType);
/* What about:
void Insert (int offset, string value);
void Remove (int offset, int count);
void Remove (ISegment segment);
void Replace (int offset, int count, string value);
Or more search operations:
IEnumerable<int> SearchForward (string pattern, int startIndex);
IEnumerable<int> SearchForwardIgnoreCase (string pattern, int startIndex);
IEnumerable<int> SearchBackward (string pattern, int startIndex);
IEnumerable<int> SearchBackwardIgnoreCase (string pattern, int startIndex);
*/
}
/// <summary>
/// Represents a version identifier for a text source.
/// </summary>
/// <remarks>
/// Verions can be used to efficiently detect whether a document has changed and needs reparsing;
/// or even to implement incremental parsers.
/// It is a separate class from ITextSource to allow the GC to collect the text source while
/// the version checkpoint is still in use.
/// </remarks>
public interface ITextSourceVersion
{
/// <summary>
/// Gets whether this checkpoint belongs to the same document as the other checkpoint.
/// </summary>
/// <remarks>
/// Returns false when given <c>null</c>.
/// </remarks>
bool BelongsToSameDocumentAs(ITextSourceVersion other);
/// <summary>
/// Compares the age of this checkpoint to the other checkpoint.
/// </summary>
/// <remarks>This method is thread-safe.</remarks>
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this version.</exception>
/// <returns>-1 if this version is older than <paramref name="other"/>.
/// 0 if <c>this</c> version instance represents the same version as <paramref name="other"/>.
/// 1 if this version is newer than <paramref name="other"/>.</returns>
int CompareAge(ITextSourceVersion other);
/// <summary>
/// Gets the changes from this checkpoint to the other checkpoint.
/// If 'other' is older than this checkpoint, reverse changes are calculated.
/// </summary>
/// <remarks>This method is thread-safe.</remarks>
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception>
IEnumerable<TextChangeEventArgs> GetChangesTo(ITextSourceVersion other);
/// <summary>
/// Calculates where the offset has moved in the other buffer version.
/// </summary>
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception>
int MoveOffsetTo(ITextSourceVersion other, int oldOffset, AnchorMovementType movement = AnchorMovementType.Default);
}
/// <summary>
/// Implements the ITextSource interface using a string.
/// </summary>
[Serializable]
public class StringTextSource : ITextSource
{
/// <summary>
/// Gets a text source containing the empty string.
/// </summary>
public static readonly StringTextSource Empty = new StringTextSource(string.Empty);
readonly string text;
readonly ITextSourceVersion version;
/// <summary>
/// Creates a new StringTextSource with the given text.
/// </summary>
public StringTextSource(string text)
{
if (text == null)
throw new ArgumentNullException("text");
this.text = text;
}
/// <summary>
/// Creates a new StringTextSource with the given text.
/// </summary>
public StringTextSource(string text, ITextSourceVersion version)
{
if (text == null)
throw new ArgumentNullException("text");
this.text = text;
this.version = version;
}
/// <inheritdoc/>
public ITextSourceVersion Version {
get { return version; }
}
/// <inheritdoc/>
public int TextLength {
get { return text.Length; }
}
/// <inheritdoc/>
public string Text {
get { return text; }
}
/// <inheritdoc/>
public ITextSource CreateSnapshot()
{
return this; // StringTextSource is immutable
}
/// <inheritdoc/>
public ITextSource CreateSnapshot(int offset, int length)
{
return new StringTextSource(text.Substring(offset, length));
}
/// <inheritdoc/>
public TextReader CreateReader()
{
return new StringReader(text);
}
/// <inheritdoc/>
public TextReader CreateReader(int offset, int length)
{
return new StringReader(text.Substring(offset, length));
}
/// <inheritdoc/>
public void WriteTextTo(TextWriter writer)
{
writer.Write(text);
}
/// <inheritdoc/>
public void WriteTextTo(TextWriter writer, int offset, int length)
{
writer.Write(text.Substring(offset, length));
}
/// <inheritdoc/>
public char GetCharAt(int offset)
{
return text[offset];
}
/// <inheritdoc/>
public string GetText(int offset, int length)
{
return text.Substring(offset, length);
}
/// <inheritdoc/>
public string GetText(ISegment segment)
{
if (segment == null)
throw new ArgumentNullException("segment");
return text.Substring(segment.Offset, segment.Length);
}
/// <inheritdoc/>
public int IndexOf(char c, int startIndex, int count)
{
return text.IndexOf(c, startIndex, count);
}
/// <inheritdoc/>
public int IndexOfAny(char[] anyOf, int startIndex, int count)
{
return text.IndexOfAny(anyOf, startIndex, count);
}
/// <inheritdoc/>
public int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType)
{
return text.IndexOf(searchText, startIndex, count, comparisonType);
}
/// <inheritdoc/>
public int LastIndexOf(char c, int startIndex, int count)
{
return text.LastIndexOf(c, startIndex + count - 1, count);
}
/// <inheritdoc/>
public int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType)
{
return text.LastIndexOf(searchText, startIndex + count - 1, count, comparisonType);
}
}
#endif
}

45
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/IUndoableOperation.cs

@ -1,45 +0,0 @@ @@ -1,45 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// This Interface describes a the basic Undo/Redo operation
/// all Undo Operations must implement this interface.
/// </summary>
public interface IUndoableOperation
{
/// <summary>
/// Undo the last operation
/// </summary>
void Undo();
/// <summary>
/// Redo the last operation
/// </summary>
void Redo();
}
interface IUndoableOperationWithContext : IUndoableOperation
{
void Undo(UndoStack stack);
void Redo(UndoStack stack);
}
}

318
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineManager.cs

@ -1,318 +0,0 @@ @@ -1,318 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.NRefactory.Editor;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Creates/Deletes lines when text is inserted/removed.
/// </summary>
sealed class LineManager
{
#region Constructor
readonly TextDocument document;
readonly DocumentLineTree documentLineTree;
/// <summary>
/// A copy of the line trackers. We need a copy so that line trackers may remove themselves
/// while being notified (used e.g. by WeakLineTracker)
/// </summary>
ILineTracker[] lineTrackers;
internal void UpdateListOfLineTrackers()
{
this.lineTrackers = document.LineTrackers.ToArray();
}
public LineManager(DocumentLineTree documentLineTree, TextDocument document)
{
this.document = document;
this.documentLineTree = documentLineTree;
UpdateListOfLineTrackers();
Rebuild();
}
#endregion
#region Change events
/*
HashSet<DocumentLine> deletedLines = new HashSet<DocumentLine>();
readonly HashSet<DocumentLine> changedLines = new HashSet<DocumentLine>();
HashSet<DocumentLine> deletedOrChangedLines = new HashSet<DocumentLine>();
/// <summary>
/// Gets the list of lines deleted since the last RetrieveChangedLines() call.
/// The returned list is unsorted.
/// </summary>
public ICollection<DocumentLine> RetrieveDeletedLines()
{
var r = deletedLines;
deletedLines = new HashSet<DocumentLine>();
return r;
}
/// <summary>
/// Gets the list of lines changed since the last RetrieveChangedLines() call.
/// The returned list is sorted by line number and does not contain deleted lines.
/// </summary>
public List<DocumentLine> RetrieveChangedLines()
{
var list = (from line in changedLines
where !line.IsDeleted
let number = line.LineNumber
orderby number
select line).ToList();
changedLines.Clear();
return list;
}
/// <summary>
/// Gets the list of lines changed since the last RetrieveDeletedOrChangedLines() call.
/// The returned list is not sorted.
/// </summary>
public ICollection<DocumentLine> RetrieveDeletedOrChangedLines()
{
var r = deletedOrChangedLines;
deletedOrChangedLines = new HashSet<DocumentLine>();
return r;
}
*/
#endregion
#region Rebuild
public void Rebuild()
{
// keep the first document line
DocumentLine ls = documentLineTree.GetByNumber(1);
// but mark all other lines as deleted, and detach them from the other nodes
for (DocumentLine line = ls.NextLine; line != null; line = line.NextLine) {
line.isDeleted = true;
line.parent = line.left = line.right = null;
}
// Reset the first line to detach it from the deleted lines
ls.ResetLine();
SimpleSegment ds = NewLineFinder.NextNewLine(document, 0);
List<DocumentLine> lines = new List<DocumentLine>();
int lastDelimiterEnd = 0;
while (ds != SimpleSegment.Invalid) {
ls.TotalLength = ds.Offset + ds.Length - lastDelimiterEnd;
ls.DelimiterLength = ds.Length;
lastDelimiterEnd = ds.Offset + ds.Length;
lines.Add(ls);
ls = new DocumentLine(document);
ds = NewLineFinder.NextNewLine(document, lastDelimiterEnd);
}
ls.TotalLength = document.TextLength - lastDelimiterEnd;
lines.Add(ls);
documentLineTree.RebuildTree(lines);
foreach (ILineTracker lineTracker in lineTrackers)
lineTracker.RebuildDocument();
}
#endregion
#region Remove
public void Remove(int offset, int length)
{
Debug.Assert(length >= 0);
if (length == 0) return;
DocumentLine startLine = documentLineTree.GetByOffset(offset);
int startLineOffset = startLine.Offset;
Debug.Assert(offset < startLineOffset + startLine.TotalLength);
if (offset > startLineOffset + startLine.Length) {
Debug.Assert(startLine.DelimiterLength == 2);
// we are deleting starting in the middle of a delimiter
// remove last delimiter part
SetLineLength(startLine, startLine.TotalLength - 1);
// remove remaining text
Remove(offset, length - 1);
return;
}
if (offset + length < startLineOffset + startLine.TotalLength) {
// just removing a part of this line
//startLine.RemovedLinePart(ref deferredEventList, offset - startLineOffset, length);
SetLineLength(startLine, startLine.TotalLength - length);
return;
}
// merge startLine with another line because startLine's delimiter was deleted
// possibly remove lines in between if multiple delimiters were deleted
int charactersRemovedInStartLine = startLineOffset + startLine.TotalLength - offset;
Debug.Assert(charactersRemovedInStartLine > 0);
//startLine.RemovedLinePart(ref deferredEventList, offset - startLineOffset, charactersRemovedInStartLine);
DocumentLine endLine = documentLineTree.GetByOffset(offset + length);
if (endLine == startLine) {
// special case: we are removing a part of the last line up to the
// end of the document
SetLineLength(startLine, startLine.TotalLength - length);
return;
}
int endLineOffset = endLine.Offset;
int charactersLeftInEndLine = endLineOffset + endLine.TotalLength - (offset + length);
//endLine.RemovedLinePart(ref deferredEventList, 0, endLine.TotalLength - charactersLeftInEndLine);
//startLine.MergedWith(endLine, offset - startLineOffset);
// remove all lines between startLine (excl.) and endLine (incl.)
DocumentLine tmp = startLine.NextLine;
DocumentLine lineToRemove;
do {
lineToRemove = tmp;
tmp = tmp.NextLine;
RemoveLine(lineToRemove);
} while (lineToRemove != endLine);
SetLineLength(startLine, startLine.TotalLength - charactersRemovedInStartLine + charactersLeftInEndLine);
}
void RemoveLine(DocumentLine lineToRemove)
{
foreach (ILineTracker lt in lineTrackers)
lt.BeforeRemoveLine(lineToRemove);
documentLineTree.RemoveLine(lineToRemove);
// foreach (ILineTracker lt in lineTracker)
// lt.AfterRemoveLine(lineToRemove);
// deletedLines.Add(lineToRemove);
// deletedOrChangedLines.Add(lineToRemove);
}
#endregion
#region Insert
public void Insert(int offset, ITextSource text)
{
DocumentLine line = documentLineTree.GetByOffset(offset);
int lineOffset = line.Offset;
Debug.Assert(offset <= lineOffset + line.TotalLength);
if (offset > lineOffset + line.Length) {
Debug.Assert(line.DelimiterLength == 2);
// we are inserting in the middle of a delimiter
// shorten line
SetLineLength(line, line.TotalLength - 1);
// add new line
line = InsertLineAfter(line, 1);
line = SetLineLength(line, 1);
}
SimpleSegment ds = NewLineFinder.NextNewLine(text, 0);
if (ds == SimpleSegment.Invalid) {
// no newline is being inserted, all text is inserted in a single line
//line.InsertedLinePart(offset - line.Offset, text.Length);
SetLineLength(line, line.TotalLength + text.TextLength);
return;
}
//DocumentLine firstLine = line;
//firstLine.InsertedLinePart(offset - firstLine.Offset, ds.Offset);
int lastDelimiterEnd = 0;
while (ds != SimpleSegment.Invalid) {
// split line segment at line delimiter
int lineBreakOffset = offset + ds.Offset + ds.Length;
lineOffset = line.Offset;
int lengthAfterInsertionPos = lineOffset + line.TotalLength - (offset + lastDelimiterEnd);
line = SetLineLength(line, lineBreakOffset - lineOffset);
DocumentLine newLine = InsertLineAfter(line, lengthAfterInsertionPos);
newLine = SetLineLength(newLine, lengthAfterInsertionPos);
line = newLine;
lastDelimiterEnd = ds.Offset + ds.Length;
ds = NewLineFinder.NextNewLine(text, lastDelimiterEnd);
}
//firstLine.SplitTo(line);
// insert rest after last delimiter
if (lastDelimiterEnd != text.TextLength) {
//line.InsertedLinePart(0, text.Length - lastDelimiterEnd);
SetLineLength(line, line.TotalLength + text.TextLength - lastDelimiterEnd);
}
}
DocumentLine InsertLineAfter(DocumentLine line, int length)
{
DocumentLine newLine = documentLineTree.InsertLineAfter(line, length);
foreach (ILineTracker lt in lineTrackers)
lt.LineInserted(line, newLine);
return newLine;
}
#endregion
#region SetLineLength
/// <summary>
/// Sets the total line length and checks the delimiter.
/// This method can cause line to be deleted when it contains a single '\n' character
/// and the previous line ends with '\r'.
/// </summary>
/// <returns>Usually returns <paramref name="line"/>, but if line was deleted due to
/// the "\r\n" merge, returns the previous line.</returns>
DocumentLine SetLineLength(DocumentLine line, int newTotalLength)
{
// changedLines.Add(line);
// deletedOrChangedLines.Add(line);
int delta = newTotalLength - line.TotalLength;
if (delta != 0) {
foreach (ILineTracker lt in lineTrackers)
lt.SetLineLength(line, newTotalLength);
line.TotalLength = newTotalLength;
DocumentLineTree.UpdateAfterChildrenChange(line);
}
// determine new DelimiterLength
if (newTotalLength == 0) {
line.DelimiterLength = 0;
} else {
int lineOffset = line.Offset;
char lastChar = document.GetCharAt(lineOffset + newTotalLength - 1);
if (lastChar == '\r') {
line.DelimiterLength = 1;
} else if (lastChar == '\n') {
if (newTotalLength >= 2 && document.GetCharAt(lineOffset + newTotalLength - 2) == '\r') {
line.DelimiterLength = 2;
} else if (newTotalLength == 1 && lineOffset > 0 && document.GetCharAt(lineOffset - 1) == '\r') {
// we need to join this line with the previous line
DocumentLine previousLine = line.PreviousLine;
RemoveLine(line);
return SetLineLength(previousLine, previousLine.TotalLength + 1);
} else {
line.DelimiterLength = 1;
}
} else {
line.DelimiterLength = 0;
}
}
return line;
}
#endregion
#region ChangeComplete
public void ChangeComplete(DocumentChangeEventArgs e)
{
foreach (ILineTracker lt in lineTrackers) {
lt.ChangeComplete(e);
}
}
#endregion
}
}

98
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineNode.cs

@ -1,98 +0,0 @@ @@ -1,98 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
namespace ICSharpCode.AvalonEdit.Document
{
using LineNode = DocumentLine;
// A tree node in the document line tree.
// For the purpose of the invariants, "children", "descendents", "siblings" etc. include the DocumentLine object,
// it is treated as a third child node between left and right.
// Originally, this was a separate class, with a reference to the documentLine. The documentLine had a reference
// back to the node. To save memory, the same object is used for both the documentLine and the line node.
// This saves 16 bytes per line (8 byte object overhead + two pointers).
// sealed class LineNode
// {
// internal readonly DocumentLine documentLine;
partial class DocumentLine
{
internal DocumentLine left, right, parent;
internal bool color;
// optimization note: I tried packing color and isDeleted into a single byte field, but that
// actually increased the memory requirements. The JIT packs two bools and a byte (delimiterSize)
// into a single DWORD, but two bytes get each their own DWORD. Three bytes end up in the same DWORD, so
// apparently the JIT only optimizes for memory when there are at least three small fields.
// Currently, DocumentLine takes 36 bytes on x86 (8 byte object overhead, 3 pointers, 3 ints, and another DWORD
// for the small fields).
// TODO: a possible optimization would be to combine 'totalLength' and the small fields into a single uint.
// delimiterSize takes only two bits, the two bools take another two bits; so there's still
// 28 bits left for totalLength. 268435455 characters per line should be enough for everyone :)
/// <summary>
/// Resets the line to enable its reuse after a document rebuild.
/// </summary>
internal void ResetLine()
{
totalLength = delimiterLength = 0;
isDeleted = color = false;
left = right = parent = null;
}
internal LineNode InitLineNode()
{
this.nodeTotalCount = 1;
this.nodeTotalLength = this.TotalLength;
return this;
}
internal LineNode LeftMost {
get {
LineNode node = this;
while (node.left != null)
node = node.left;
return node;
}
}
internal LineNode RightMost {
get {
LineNode node = this;
while (node.right != null)
node = node.right;
return node;
}
}
/// <summary>
/// The number of lines in this node and its child nodes.
/// Invariant:
/// nodeTotalCount = 1 + left.nodeTotalCount + right.nodeTotalCount
/// </summary>
internal int nodeTotalCount;
/// <summary>
/// The total text length of this node and its child nodes.
/// Invariant:
/// nodeTotalLength = left.nodeTotalLength + documentLine.TotalLength + right.nodeTotalLength
/// </summary>
internal int nodeTotalLength;
}
}

150
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/NewLineFinder.cs

@ -1,150 +0,0 @@ @@ -1,150 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Text;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Document
{
static class NewLineFinder
{
static readonly char[] newline = { '\r', '\n' };
internal static readonly string[] NewlineStrings = { "\r\n", "\r", "\n" };
/// <summary>
/// Gets the location of the next new line character, or SimpleSegment.Invalid
/// if none is found.
/// </summary>
internal static SimpleSegment NextNewLine(string text, int offset)
{
int pos = text.IndexOfAny(newline, offset);
if (pos >= 0) {
if (text[pos] == '\r') {
if (pos + 1 < text.Length && text[pos + 1] == '\n')
return new SimpleSegment(pos, 2);
}
return new SimpleSegment(pos, 1);
}
return SimpleSegment.Invalid;
}
/// <summary>
/// Gets the location of the next new line character, or SimpleSegment.Invalid
/// if none is found.
/// </summary>
internal static SimpleSegment NextNewLine(ITextSource text, int offset)
{
int textLength = text.TextLength;
int pos = text.IndexOfAny(newline, offset, textLength - offset);
if (pos >= 0) {
if (text.GetCharAt(pos) == '\r') {
if (pos + 1 < textLength && text.GetCharAt(pos + 1) == '\n')
return new SimpleSegment(pos, 2);
}
return new SimpleSegment(pos, 1);
}
return SimpleSegment.Invalid;
}
}
partial class TextUtilities
{
/// <summary>
/// Finds the next new line character starting at offset.
/// </summary>
/// <param name="text">The text source to search in.</param>
/// <param name="offset">The starting offset for the search.</param>
/// <param name="newLineType">The string representing the new line that was found, or null if no new line was found.</param>
/// <returns>The position of the first new line starting at or after <paramref name="offset"/>,
/// or -1 if no new line was found.</returns>
public static int FindNextNewLine(ITextSource text, int offset, out string newLineType)
{
if (text == null)
throw new ArgumentNullException("text");
if (offset < 0 || offset > text.TextLength)
throw new ArgumentOutOfRangeException("offset", offset, "offset is outside of text source");
SimpleSegment s = NewLineFinder.NextNewLine(text, offset);
if (s == SimpleSegment.Invalid) {
newLineType = null;
return -1;
} else {
if (s.Length == 2) {
newLineType = "\r\n";
} else if (text.GetCharAt(s.Offset) == '\n') {
newLineType = "\n";
} else {
newLineType = "\r";
}
return s.Offset;
}
}
/// <summary>
/// Gets whether the specified string is a newline sequence.
/// </summary>
public static bool IsNewLine(string newLine)
{
return newLine == "\r\n" || newLine == "\n" || newLine == "\r";
}
/// <summary>
/// Normalizes all new lines in <paramref name="input"/> to be <paramref name="newLine"/>.
/// </summary>
public static string NormalizeNewLines(string input, string newLine)
{
if (input == null)
return null;
if (!IsNewLine(newLine))
throw new ArgumentException("newLine must be one of the known newline sequences");
SimpleSegment ds = NewLineFinder.NextNewLine(input, 0);
if (ds == SimpleSegment.Invalid) // text does not contain any new lines
return input;
StringBuilder b = new StringBuilder(input.Length);
int lastEndOffset = 0;
do {
b.Append(input, lastEndOffset, ds.Offset - lastEndOffset);
b.Append(newLine);
lastEndOffset = ds.EndOffset;
ds = NewLineFinder.NextNewLine(input, lastEndOffset);
} while (ds != SimpleSegment.Invalid);
// remaining string (after last newline)
b.Append(input, lastEndOffset, input.Length - lastEndOffset);
return b.ToString();
}
/// <summary>
/// Gets the newline sequence used in the document at the specified line.
/// </summary>
public static string GetNewLineFromDocument(IDocument document, int lineNumber)
{
IDocumentLine line = document.GetLineByNumber(lineNumber);
if (line.DelimiterLength == 0) {
// at the end of the document, there's no line delimiter, so use the delimiter
// from the previous line
line = line.PreviousLine;
if (line == null)
return Environment.NewLine;
}
return document.GetText(line.Offset + line.Length, line.DelimiterLength);
}
}
}

364
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/OffsetChangeMap.cs

@ -1,364 +0,0 @@ @@ -1,364 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using ICSharpCode.AvalonEdit.Utils;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Contains predefined offset change mapping types.
/// </summary>
public enum OffsetChangeMappingType
{
/// <summary>
/// Normal replace.
/// Anchors in front of the replaced region will stay in front, anchors after the replaced region will stay after.
/// Anchors in the middle of the removed region will be deleted. If they survive deletion,
/// they move depending on their AnchorMovementType.
/// </summary>
/// <remarks>
/// This is the default implementation of DocumentChangeEventArgs when OffsetChangeMap is null,
/// so using this option usually works without creating an OffsetChangeMap instance.
/// This is equivalent to an OffsetChangeMap with a single entry describing the replace operation.
/// </remarks>
Normal,
/// <summary>
/// First the old text is removed, then the new text is inserted.
/// Anchors immediately in front (or after) the replaced region may move to the other side of the insertion,
/// depending on the AnchorMovementType.
/// </summary>
/// <remarks>
/// This is implemented as an OffsetChangeMap with two entries: the removal, and the insertion.
/// </remarks>
RemoveAndInsert,
/// <summary>
/// The text is replaced character-by-character.
/// Anchors keep their position inside the replaced text.
/// Anchors after the replaced region will move accordingly if the replacement text has a different length than the replaced text.
/// If the new text is shorter than the old text, anchors inside the old text that would end up behind the replacement text
/// will be moved so that they point to the end of the replacement text.
/// </summary>
/// <remarks>
/// On the OffsetChangeMap level, growing text is implemented by replacing the last character in the replaced text
/// with itself and the additional text segment. A simple insertion of the additional text would have the undesired
/// effect of moving anchors immediately after the replaced text into the replacement text if they used
/// AnchorMovementStyle.BeforeInsertion.
/// Shrinking text is implemented by removing the text segment that's too long; but in a special mode that
/// causes anchors to always survive irrespective of their <see cref="TextAnchor.SurviveDeletion"/> setting.
/// If the text keeps its old size, this is implemented as OffsetChangeMap.Empty.
/// </remarks>
CharacterReplace,
/// <summary>
/// Like 'Normal', but anchors with <see cref="TextAnchor.MovementType"/> = Default will stay in front of the
/// insertion instead of being moved behind it.
/// </summary>
KeepAnchorBeforeInsertion
}
/// <summary>
/// Describes a series of offset changes.
/// </summary>
[Serializable]
[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix",
Justification="It's a mapping old offsets -> new offsets")]
public sealed class OffsetChangeMap : Collection<OffsetChangeMapEntry>
{
/// <summary>
/// Immutable OffsetChangeMap that is empty.
/// </summary>
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
Justification="The Empty instance is immutable")]
public static readonly OffsetChangeMap Empty = new OffsetChangeMap(Empty<OffsetChangeMapEntry>.Array, true);
/// <summary>
/// Creates a new OffsetChangeMap with a single element.
/// </summary>
/// <param name="entry">The entry.</param>
/// <returns>Returns a frozen OffsetChangeMap with a single entry.</returns>
public static OffsetChangeMap FromSingleElement(OffsetChangeMapEntry entry)
{
return new OffsetChangeMap(new OffsetChangeMapEntry[] { entry }, true);
}
bool isFrozen;
/// <summary>
/// Creates a new OffsetChangeMap instance.
/// </summary>
public OffsetChangeMap()
{
}
internal OffsetChangeMap(int capacity)
: base(new List<OffsetChangeMapEntry>(capacity))
{
}
private OffsetChangeMap(IList<OffsetChangeMapEntry> entries, bool isFrozen)
: base(entries)
{
this.isFrozen = isFrozen;
}
/// <summary>
/// Gets the new offset where the specified offset moves after this document change.
/// </summary>
public int GetNewOffset(int offset, AnchorMovementType movementType = AnchorMovementType.Default)
{
IList<OffsetChangeMapEntry> items = this.Items;
int count = items.Count;
for (int i = 0; i < count; i++) {
offset = items[i].GetNewOffset(offset, movementType);
}
return offset;
}
/// <summary>
/// Gets whether this OffsetChangeMap is a valid explanation for the specified document change.
/// </summary>
public bool IsValidForDocumentChange(int offset, int removalLength, int insertionLength)
{
int endOffset = offset + removalLength;
foreach (OffsetChangeMapEntry entry in this) {
// check that ChangeMapEntry is in valid range for this document change
if (entry.Offset < offset || entry.Offset + entry.RemovalLength > endOffset)
return false;
endOffset += entry.InsertionLength - entry.RemovalLength;
}
// check that the total delta matches
return endOffset == offset + insertionLength;
}
/// <summary>
/// Calculates the inverted OffsetChangeMap (used for the undo operation).
/// </summary>
public OffsetChangeMap Invert()
{
if (this == Empty)
return this;
OffsetChangeMap newMap = new OffsetChangeMap(this.Count);
for (int i = this.Count - 1; i >= 0; i--) {
OffsetChangeMapEntry entry = this[i];
// swap InsertionLength and RemovalLength
newMap.Add(new OffsetChangeMapEntry(entry.Offset, entry.InsertionLength, entry.RemovalLength));
}
return newMap;
}
/// <inheritdoc/>
protected override void ClearItems()
{
CheckFrozen();
base.ClearItems();
}
/// <inheritdoc/>
protected override void InsertItem(int index, OffsetChangeMapEntry item)
{
CheckFrozen();
base.InsertItem(index, item);
}
/// <inheritdoc/>
protected override void RemoveItem(int index)
{
CheckFrozen();
base.RemoveItem(index);
}
/// <inheritdoc/>
protected override void SetItem(int index, OffsetChangeMapEntry item)
{
CheckFrozen();
base.SetItem(index, item);
}
void CheckFrozen()
{
if (isFrozen)
throw new InvalidOperationException("This instance is frozen and cannot be modified.");
}
/// <summary>
/// Gets if this instance is frozen. Frozen instances are immutable and thus thread-safe.
/// </summary>
public bool IsFrozen {
get { return isFrozen; }
}
/// <summary>
/// Freezes this instance.
/// </summary>
public void Freeze()
{
isFrozen = true;
}
}
/// <summary>
/// An entry in the OffsetChangeMap.
/// This represents the offset of a document change (either insertion or removal, not both at once).
/// </summary>
[Serializable]
public struct OffsetChangeMapEntry : IEquatable<OffsetChangeMapEntry>
{
readonly int offset;
// MSB: DefaultAnchorMovementIsBeforeInsertion
readonly uint insertionLengthWithMovementFlag;
// MSB: RemovalNeverCausesAnchorDeletion; other 31 bits: RemovalLength
readonly uint removalLengthWithDeletionFlag;
/// <summary>
/// The offset at which the change occurs.
/// </summary>
public int Offset {
get { return offset; }
}
/// <summary>
/// The number of characters inserted.
/// Returns 0 if this entry represents a removal.
/// </summary>
public int InsertionLength {
get { return (int)(insertionLengthWithMovementFlag & 0x7fffffff); }
}
/// <summary>
/// The number of characters removed.
/// Returns 0 if this entry represents an insertion.
/// </summary>
public int RemovalLength {
get { return (int)(removalLengthWithDeletionFlag & 0x7fffffff); }
}
/// <summary>
/// Gets whether the removal should not cause any anchor deletions.
/// </summary>
public bool RemovalNeverCausesAnchorDeletion {
get { return (removalLengthWithDeletionFlag & 0x80000000) != 0; }
}
/// <summary>
/// Gets whether default anchor movement causes the anchor to stay in front of the caret.
/// </summary>
public bool DefaultAnchorMovementIsBeforeInsertion {
get { return (insertionLengthWithMovementFlag & 0x80000000) != 0; }
}
/// <summary>
/// Gets the new offset where the specified offset moves after this document change.
/// </summary>
public int GetNewOffset(int oldOffset, AnchorMovementType movementType = AnchorMovementType.Default)
{
int insertionLength = this.InsertionLength;
int removalLength = this.RemovalLength;
if (!(removalLength == 0 && oldOffset == offset)) {
// we're getting trouble (both if statements in here would apply)
// if there's no removal and we insert at the offset
// -> we'd need to disambiguate by movementType, which is handled after the if
// offset is before start of change: no movement
if (oldOffset <= offset)
return oldOffset;
// offset is after end of change: movement by normal delta
if (oldOffset >= offset + removalLength)
return oldOffset + insertionLength - removalLength;
}
// we reach this point if
// a) the oldOffset is inside the deleted segment
// b) there was no removal and we insert at the caret position
if (movementType == AnchorMovementType.AfterInsertion)
return offset + insertionLength;
else if (movementType == AnchorMovementType.BeforeInsertion)
return offset;
else
return this.DefaultAnchorMovementIsBeforeInsertion ? offset : offset + insertionLength;
}
/// <summary>
/// Creates a new OffsetChangeMapEntry instance.
/// </summary>
public OffsetChangeMapEntry(int offset, int removalLength, int insertionLength)
{
ThrowUtil.CheckNotNegative(offset, "offset");
ThrowUtil.CheckNotNegative(removalLength, "removalLength");
ThrowUtil.CheckNotNegative(insertionLength, "insertionLength");
this.offset = offset;
this.removalLengthWithDeletionFlag = (uint)removalLength;
this.insertionLengthWithMovementFlag = (uint)insertionLength;
}
/// <summary>
/// Creates a new OffsetChangeMapEntry instance.
/// </summary>
public OffsetChangeMapEntry(int offset, int removalLength, int insertionLength, bool removalNeverCausesAnchorDeletion, bool defaultAnchorMovementIsBeforeInsertion)
: this(offset, removalLength, insertionLength)
{
if (removalNeverCausesAnchorDeletion)
this.removalLengthWithDeletionFlag |= 0x80000000;
if (defaultAnchorMovementIsBeforeInsertion)
this.insertionLengthWithMovementFlag |= 0x80000000;
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked {
return offset + 3559 * (int)insertionLengthWithMovementFlag + 3571 * (int)removalLengthWithDeletionFlag;
}
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is OffsetChangeMapEntry && this.Equals((OffsetChangeMapEntry)obj);
}
/// <inheritdoc/>
public bool Equals(OffsetChangeMapEntry other)
{
return offset == other.offset && insertionLengthWithMovementFlag == other.insertionLengthWithMovementFlag && removalLengthWithDeletionFlag == other.removalLengthWithDeletionFlag;
}
/// <summary>
/// Tests the two entries for equality.
/// </summary>
public static bool operator ==(OffsetChangeMapEntry left, OffsetChangeMapEntry right)
{
return left.Equals(right);
}
/// <summary>
/// Tests the two entries for inequality.
/// </summary>
public static bool operator !=(OffsetChangeMapEntry left, OffsetChangeMapEntry right)
{
return !left.Equals(right);
}
}
}

169
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/RopeTextSource.cs

@ -1,169 +0,0 @@ @@ -1,169 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.IO;
using ICSharpCode.AvalonEdit.Utils;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Implements the ITextSource interface using a rope.
/// </summary>
[Serializable]
public sealed class RopeTextSource : ITextSource
{
readonly Rope<char> rope;
readonly ITextSourceVersion version;
/// <summary>
/// Creates a new RopeTextSource.
/// </summary>
public RopeTextSource(Rope<char> rope)
{
if (rope == null)
throw new ArgumentNullException("rope");
this.rope = rope.Clone();
}
/// <summary>
/// Creates a new RopeTextSource.
/// </summary>
public RopeTextSource(Rope<char> rope, ITextSourceVersion version)
{
if (rope == null)
throw new ArgumentNullException("rope");
this.rope = rope.Clone();
this.version = version;
}
/// <summary>
/// Returns a clone of the rope used for this text source.
/// </summary>
/// <remarks>
/// RopeTextSource only publishes a copy of the contained rope to ensure that the underlying rope cannot be modified.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification="Not a property because it creates a clone")]
public Rope<char> GetRope()
{
return rope.Clone();
}
/// <inheritdoc/>
public string Text {
get { return rope.ToString(); }
}
/// <inheritdoc/>
public int TextLength {
get { return rope.Length; }
}
/// <inheritdoc/>
public char GetCharAt(int offset)
{
return rope[offset];
}
/// <inheritdoc/>
public string GetText(int offset, int length)
{
return rope.ToString(offset, length);
}
/// <inheritdoc/>
public string GetText(ISegment segment)
{
return rope.ToString(segment.Offset, segment.Length);
}
/// <inheritdoc/>
public TextReader CreateReader()
{
return new RopeTextReader(rope);
}
/// <inheritdoc/>
public TextReader CreateReader(int offset, int length)
{
return new RopeTextReader(rope.GetRange(offset, length));
}
/// <inheritdoc/>
public ITextSource CreateSnapshot()
{
return this;
}
/// <inheritdoc/>
public ITextSource CreateSnapshot(int offset, int length)
{
return new RopeTextSource(rope.GetRange(offset, length));
}
/// <inheritdoc/>
public int IndexOf(char c, int startIndex, int count)
{
return rope.IndexOf(c, startIndex, count);
}
/// <inheritdoc/>
public int IndexOfAny(char[] anyOf, int startIndex, int count)
{
return rope.IndexOfAny(anyOf, startIndex, count);
}
/// <inheritdoc/>
public int LastIndexOf(char c, int startIndex, int count)
{
return rope.LastIndexOf(c, startIndex, count);
}
/// <inheritdoc/>
public ITextSourceVersion Version {
get { return version; }
}
/// <inheritdoc/>
public int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType)
{
return rope.IndexOf(searchText, startIndex, count, comparisonType);
}
/// <inheritdoc/>
public int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType)
{
return rope.LastIndexOf(searchText, startIndex, count, comparisonType);
}
/// <inheritdoc/>
public void WriteTextTo(TextWriter writer)
{
rope.WriteTo(writer, 0, rope.Length);
}
/// <inheritdoc/>
public void WriteTextTo(TextWriter writer, int offset, int length)
{
rope.WriteTo(writer, offset, length);
}
}
}

197
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/SimpleSegment.cs

@ -1,197 +0,0 @@ @@ -1,197 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Globalization;
using ICSharpCode.AvalonEdit.Utils;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Represents a simple segment (Offset,Length pair) that is not automatically updated
/// on document changes.
/// </summary>
struct SimpleSegment : IEquatable<SimpleSegment>, ISegment
{
public static readonly SimpleSegment Invalid = new SimpleSegment(-1, -1);
/// <summary>
/// Gets the overlapping portion of the segments.
/// Returns SimpleSegment.Invalid if the segments don't overlap.
/// </summary>
public static SimpleSegment GetOverlap(ISegment segment1, ISegment segment2)
{
int start = Math.Max(segment1.Offset, segment2.Offset);
int end = Math.Min(segment1.EndOffset, segment2.EndOffset);
if (end < start)
return SimpleSegment.Invalid;
else
return new SimpleSegment(start, end - start);
}
public readonly int Offset, Length;
int ISegment.Offset {
get { return Offset; }
}
int ISegment.Length {
get { return Length; }
}
public int EndOffset {
get {
return Offset + Length;
}
}
public SimpleSegment(int offset, int length)
{
this.Offset = offset;
this.Length = length;
}
public SimpleSegment(ISegment segment)
{
Debug.Assert(segment != null);
this.Offset = segment.Offset;
this.Length = segment.Length;
}
public override int GetHashCode()
{
unchecked {
return Offset + 10301 * Length;
}
}
public override bool Equals(object obj)
{
return (obj is SimpleSegment) && Equals((SimpleSegment)obj);
}
public bool Equals(SimpleSegment other)
{
return this.Offset == other.Offset && this.Length == other.Length;
}
public static bool operator ==(SimpleSegment left, SimpleSegment right)
{
return left.Equals(right);
}
public static bool operator !=(SimpleSegment left, SimpleSegment right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override string ToString()
{
return "[Offset=" + Offset.ToString(CultureInfo.InvariantCulture) + ", Length=" + Length.ToString(CultureInfo.InvariantCulture) + "]";
}
}
/// <summary>
/// A segment using <see cref="TextAnchor"/>s as start and end positions.
/// </summary>
/// <remarks>
/// <para>
/// For the constructors creating new anchors, the start position will be AfterInsertion and the end position will be BeforeInsertion.
/// Should the end position move before the start position, the segment will have length 0.
/// </para>
/// </remarks>
/// <seealso cref="ISegment"/>
/// <seealso cref="TextSegment"/>
public sealed class AnchorSegment : ISegment
{
readonly TextAnchor start, end;
/// <inheritdoc/>
public int Offset {
get { return start.Offset; }
}
/// <inheritdoc/>
public int Length {
get {
// Math.Max takes care of the fact that end.Offset might move before start.Offset.
return Math.Max(0, end.Offset - start.Offset);
}
}
/// <inheritdoc/>
public int EndOffset {
get {
// Math.Max takes care of the fact that end.Offset might move before start.Offset.
return Math.Max(start.Offset, end.Offset);
}
}
/// <summary>
/// Creates a new AnchorSegment using the specified anchors.
/// The anchors must have <see cref="TextAnchor.SurviveDeletion"/> set to true.
/// </summary>
public AnchorSegment(TextAnchor start, TextAnchor end)
{
if (start == null)
throw new ArgumentNullException("start");
if (end == null)
throw new ArgumentNullException("end");
if (!start.SurviveDeletion)
throw new ArgumentException("Anchors for AnchorSegment must use SurviveDeletion", "start");
if (!end.SurviveDeletion)
throw new ArgumentException("Anchors for AnchorSegment must use SurviveDeletion", "end");
this.start = start;
this.end = end;
}
/// <summary>
/// Creates a new AnchorSegment that creates new anchors.
/// </summary>
public AnchorSegment(TextDocument document, ISegment segment)
: this(document, ThrowUtil.CheckNotNull(segment, "segment").Offset, segment.Length)
{
}
/// <summary>
/// Creates a new AnchorSegment that creates new anchors.
/// </summary>
public AnchorSegment(TextDocument document, int offset, int length)
{
if (document == null)
throw new ArgumentNullException("document");
this.start = document.CreateAnchor(offset);
this.start.SurviveDeletion = true;
this.start.MovementType = AnchorMovementType.AfterInsertion;
this.end = document.CreateAnchor(offset + length);
this.end.SurviveDeletion = true;
this.end.MovementType = AnchorMovementType.BeforeInsertion;
}
/// <inheritdoc/>
public override string ToString()
{
return "[Offset=" + Offset.ToString(CultureInfo.InvariantCulture) + ", EndOffset=" + EndOffset.ToString(CultureInfo.InvariantCulture) + "]";
}
}
}

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

@ -1,158 +0,0 @@ @@ -1,158 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// The TextAnchor class references an offset (a position between two characters).
/// It automatically updates the offset when text is inserted/removed in front of the anchor.
/// </summary>
/// <remarks>
/// <para>Use the <see cref="Offset"/> property to get the offset from a text anchor.
/// Use the <see cref="TextDocument.CreateAnchor"/> method to create an anchor from an offset.
/// </para>
/// <para>
/// The document will automatically update all text anchors; and because it uses weak references to do so,
/// the garbage collector can simply collect the anchor object when you don't need it anymore.
/// </para>
/// <para>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 <see cref="Offset"/> property also runs in O(lg N).</para>
/// <inheritdoc cref="IsDeleted" />
/// <inheritdoc cref="MovementType" />
/// <para>If you want to track a segment, you can use the <see cref="AnchorSegment"/> class which
/// implements <see cref="ISegment"/> using two text anchors.</para>
/// </remarks>
/// <example>
/// Usage:
/// <code>TextAnchor anchor = document.CreateAnchor(offset);
/// ChangeMyDocument();
/// int newOffset = anchor.Offset;
/// </code>
/// </example>
public sealed class TextAnchor : ITextAnchor
{
readonly TextDocument document;
internal TextAnchorNode node;
internal TextAnchor(TextDocument document)
{
this.document = document;
}
/// <summary>
/// Gets the document owning the anchor.
/// </summary>
public TextDocument Document {
get { return document; }
}
/// <inheritdoc/>
public AnchorMovementType MovementType { get; set; }
/// <inheritdoc/>
public bool SurviveDeletion { get; set; }
/// <inheritdoc/>
public bool IsDeleted {
get {
document.DebugVerifyAccess();
return node == null;
}
}
/// <inheritdoc/>
public event EventHandler Deleted;
internal void OnDeleted(DelayedEvents delayedEvents)
{
node = null;
delayedEvents.DelayedRaise(Deleted, this, EventArgs.Empty);
}
/// <summary>
/// Gets the offset of the text anchor.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
public int Offset {
get {
document.DebugVerifyAccess();
TextAnchorNode n = this.node;
if (n == null)
throw new InvalidOperationException();
int offset = n.length;
if (n.left != null)
offset += n.left.totalLength;
while (n.parent != null) {
if (n == n.parent.right) {
if (n.parent.left != null)
offset += n.parent.left.totalLength;
offset += n.parent.length;
}
n = n.parent;
}
return offset;
}
}
/// <summary>
/// Gets the line number of the anchor.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
public int Line {
get {
return document.GetLineByOffset(this.Offset).LineNumber;
}
}
/// <summary>
/// Gets the column number of this anchor.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
public int Column {
get {
int offset = this.Offset;
return offset - document.GetLineByOffset(offset).Offset + 1;
}
}
/// <summary>
/// Gets the text location of this anchor.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
public TextLocation Location {
get {
return document.GetLocation(this.Offset);
}
}
/// <inheritdoc/>
public override string ToString()
{
return "[TextAnchor Offset=" + Offset + "]";
}
}
}

102
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchorNode.cs

@ -1,102 +0,0 @@ @@ -1,102 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// A TextAnchorNode is placed in the TextAnchorTree.
/// It describes a section of text with a text anchor at the end of the section.
/// A weak reference is used to refer to the TextAnchor. (to save memory, we derive from WeakReference instead of referencing it)
/// </summary>
sealed class TextAnchorNode : WeakReference
{
internal TextAnchorNode left, right, parent;
internal bool color;
internal int length;
internal int totalLength; // totalLength = length + left.totalLength + right.totalLength
public TextAnchorNode(TextAnchor anchor) : base(anchor)
{
}
internal TextAnchorNode LeftMost {
get {
TextAnchorNode node = this;
while (node.left != null)
node = node.left;
return node;
}
}
internal TextAnchorNode RightMost {
get {
TextAnchorNode node = this;
while (node.right != null)
node = node.right;
return node;
}
}
/// <summary>
/// Gets the inorder successor of the node.
/// </summary>
internal TextAnchorNode Successor {
get {
if (right != null) {
return right.LeftMost;
} else {
TextAnchorNode node = this;
TextAnchorNode oldNode;
do {
oldNode = node;
node = node.parent;
// go up until we are coming out of a left subtree
} while (node != null && node.right == oldNode);
return node;
}
}
}
/// <summary>
/// Gets the inorder predecessor of the node.
/// </summary>
internal TextAnchorNode Predecessor {
get {
if (left != null) {
return left.RightMost;
} else {
TextAnchorNode node = this;
TextAnchorNode oldNode;
do {
oldNode = node;
node = node.parent;
// go up until we are coming out of a right subtree
} while (node != null && node.left == oldNode);
return node;
}
}
}
public override string ToString()
{
return "[TextAnchorNode Length=" + length + " TotalLength=" + totalLength + " Target=" + Target + "]";
}
}
}

770
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchorTree.cs

@ -1,770 +0,0 @@ @@ -1,770 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using ICSharpCode.AvalonEdit.Utils;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// A tree of TextAnchorNodes.
/// </summary>
sealed class TextAnchorTree
{
// The text anchor tree has difficult requirements:
// - it must QUICKLY update the offset in all anchors whenever there is a document change
// - it must not reference text anchors directly, using weak references instead
// Clearly, we cannot afford updating an Offset property on all anchors (that would be O(N)).
// So instead, the anchors need to be able to calculate their offset from a data structure
// that can be efficiently updated.
// This implementation is built using an augmented red-black-tree.
// There is a 'TextAnchorNode' for each text anchor.
// Such a node represents a section of text (just the length is stored) with a (weakly referenced) text anchor at the end.
// Basically, you can imagine the list of text anchors as a sorted list of text anchors, where each anchor
// just stores the distance to the previous anchor.
// (next node = TextAnchorNode.Successor, distance = TextAnchorNode.length)
// Distances are never negative, so this representation means anchors are always sorted by offset
// (the order of anchors at the same offset is undefined)
// Of course, a linked list of anchors would be way too slow (one would need to traverse the whole list
// every time the offset of an anchor is being looked up).
// Instead, we use a red-black-tree. We aren't actually using the tree for sorting - it's just a binary tree
// as storage format for what's conceptually a list, the red-black properties are used to keep the tree balanced.
// Other balanced binary trees would work, too.
// What makes the tree-form efficient is that is augments the data by a 'totalLength'. Where 'length'
// represents the distance to the previous node, 'totalLength' is the sum of all 'length' values in the subtree
// under that node.
// This allows computing the Offset from an anchor by walking up the list of parent nodes instead of going
// through all predecessor nodes. So computing the Offset runs in O(log N).
readonly TextDocument document;
readonly List<TextAnchorNode> nodesToDelete = new List<TextAnchorNode>();
TextAnchorNode root;
public TextAnchorTree(TextDocument document)
{
this.document = document;
}
[Conditional("DEBUG")]
static void Log(string text)
{
Debug.WriteLine("TextAnchorTree: " + text);
}
#region Insert Text
void InsertText(int offset, int length, bool defaultAnchorMovementIsBeforeInsertion)
{
if (length == 0 || root == null || offset > root.totalLength)
return;
// find the range of nodes that are placed exactly at offset
// beginNode is inclusive, endNode is exclusive
if (offset == root.totalLength) {
PerformInsertText(FindActualBeginNode(root.RightMost), null, length, defaultAnchorMovementIsBeforeInsertion);
} else {
TextAnchorNode endNode = FindNode(ref offset);
Debug.Assert(endNode.length > 0);
if (offset > 0) {
// there are no nodes exactly at offset
endNode.length += length;
UpdateAugmentedData(endNode);
} else {
PerformInsertText(FindActualBeginNode(endNode.Predecessor), endNode, length, defaultAnchorMovementIsBeforeInsertion);
}
}
DeleteMarkedNodes();
}
TextAnchorNode FindActualBeginNode(TextAnchorNode node)
{
// now find the actual beginNode
while (node != null && node.length == 0)
node = node.Predecessor;
if (node == null) {
// no predecessor = beginNode is first node in tree
node = root.LeftMost;
}
return node;
}
// Sorts the nodes in the range [beginNode, endNode) by MovementType
// and inserts the length between the BeforeInsertion and the AfterInsertion nodes.
void PerformInsertText(TextAnchorNode beginNode, TextAnchorNode endNode, int length, bool defaultAnchorMovementIsBeforeInsertion)
{
Debug.Assert(beginNode != null);
// endNode may be null at the end of the anchor tree
// now we need to sort the nodes in the range [beginNode, endNode); putting those with
// MovementType.BeforeInsertion in front of those with MovementType.AfterInsertion
List<TextAnchorNode> beforeInsert = new List<TextAnchorNode>();
//List<TextAnchorNode> afterInsert = new List<TextAnchorNode>();
TextAnchorNode temp = beginNode;
while (temp != endNode) {
TextAnchor anchor = (TextAnchor)temp.Target;
if (anchor == null) {
// afterInsert.Add(temp);
MarkNodeForDelete(temp);
} else if (defaultAnchorMovementIsBeforeInsertion
? anchor.MovementType != AnchorMovementType.AfterInsertion
: anchor.MovementType == AnchorMovementType.BeforeInsertion)
{
beforeInsert.Add(temp);
// } else {
// afterInsert.Add(temp);
}
temp = temp.Successor;
}
// now again go through the range and swap the nodes with those in the beforeInsert list
temp = beginNode;
foreach (TextAnchorNode node in beforeInsert) {
SwapAnchors(node, temp);
temp = temp.Successor;
}
// now temp is pointing to the first node that is afterInsert,
// or to endNode, if there is no afterInsert node at the offset
// So add the length to temp
if (temp == null) {
// temp might be null if endNode==null and no afterInserts
Debug.Assert(endNode == null);
} else {
temp.length += length;
UpdateAugmentedData(temp);
}
}
/// <summary>
/// Swaps the anchors stored in the two nodes.
/// </summary>
void SwapAnchors(TextAnchorNode n1, TextAnchorNode n2)
{
if (n1 != n2) {
TextAnchor anchor1 = (TextAnchor)n1.Target;
TextAnchor anchor2 = (TextAnchor)n2.Target;
if (anchor1 == null && anchor2 == null) {
// -> no swap required
return;
}
n1.Target = anchor2;
n2.Target = anchor1;
if (anchor1 == null) {
// unmark n1 from deletion, mark n2 for deletion
nodesToDelete.Remove(n1);
MarkNodeForDelete(n2);
anchor2.node = n1;
} else if (anchor2 == null) {
// unmark n2 from deletion, mark n1 for deletion
nodesToDelete.Remove(n2);
MarkNodeForDelete(n1);
anchor1.node = n2;
} else {
anchor1.node = n2;
anchor2.node = n1;
}
}
}
#endregion
#region Remove or Replace text
public void HandleTextChange(OffsetChangeMapEntry entry, DelayedEvents delayedEvents)
{
//Log("HandleTextChange(" + entry + ")");
if (entry.RemovalLength == 0) {
// This is a pure insertion.
// Unlike a replace with removal, a pure insertion can result in nodes at the same location
// to split depending on their MovementType.
// Thus, we handle this case on a separate code path
// (the code below looks like it does something similar, but it can only split
// the set of deletion survivors, not all nodes at an offset)
InsertText(entry.Offset, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion);
return;
}
// When handling a replacing text change, we need to:
// - find all anchors in the deleted segment and delete them / move them to the appropriate
// surviving side.
// - adjust the segment size between the left and right side
int offset = entry.Offset;
int remainingRemovalLength = entry.RemovalLength;
// if the text change is happening after the last anchor, we don't have to do anything
if (root == null || offset >= root.totalLength)
return;
TextAnchorNode node = FindNode(ref offset);
TextAnchorNode firstDeletionSurvivor = null;
// go forward through the tree and delete all nodes in the removal segment
while (node != null && offset + remainingRemovalLength > node.length) {
TextAnchor anchor = (TextAnchor)node.Target;
if (anchor != null && (anchor.SurviveDeletion || entry.RemovalNeverCausesAnchorDeletion)) {
if (firstDeletionSurvivor == null)
firstDeletionSurvivor = node;
// This node should be deleted, but it wants to survive.
// We'll just remove the deleted length segment, so the node will be positioned
// in front of the removed segment.
remainingRemovalLength -= node.length - offset;
node.length = offset;
offset = 0;
UpdateAugmentedData(node);
node = node.Successor;
} else {
// delete node
TextAnchorNode s = node.Successor;
remainingRemovalLength -= node.length;
RemoveNode(node);
// we already deleted the node, don't delete it twice
nodesToDelete.Remove(node);
if (anchor != null)
anchor.OnDeleted(delayedEvents);
node = s;
}
}
// 'node' now is the first anchor after the deleted segment.
// If there are no anchors after the deleted segment, 'node' is null.
// firstDeletionSurvivor was set to the first node surviving deletion.
// Because all non-surviving nodes up to 'node' were deleted, the node range
// [firstDeletionSurvivor, node) now refers to the set of all deletion survivors.
// do the remaining job of the removal
if (node != null) {
node.length -= remainingRemovalLength;
Debug.Assert(node.length >= 0);
}
if (entry.InsertionLength > 0) {
// we are performing a replacement
if (firstDeletionSurvivor != null) {
// We got deletion survivors which need to be split into BeforeInsertion
// and AfterInsertion groups.
// Take care that we don't regroup everything at offset, but only the deletion
// survivors - from firstDeletionSurvivor (inclusive) to node (exclusive).
// This ensures that nodes immediately before or after the replaced segment
// stay where they are (independent from their MovementType)
PerformInsertText(firstDeletionSurvivor, node, entry.InsertionLength, entry.DefaultAnchorMovementIsBeforeInsertion);
} else if (node != null) {
// No deletion survivors:
// just perform the insertion
node.length += entry.InsertionLength;
}
}
if (node != null) {
UpdateAugmentedData(node);
}
DeleteMarkedNodes();
}
#endregion
#region Node removal when TextAnchor was GC'ed
void MarkNodeForDelete(TextAnchorNode node)
{
if (!nodesToDelete.Contains(node))
nodesToDelete.Add(node);
}
void DeleteMarkedNodes()
{
CheckProperties();
while (nodesToDelete.Count > 0) {
int pos = nodesToDelete.Count - 1;
TextAnchorNode n = nodesToDelete[pos];
// combine section of n with the following section
TextAnchorNode s = n.Successor;
if (s != null) {
s.length += n.length;
}
RemoveNode(n);
if (s != null) {
UpdateAugmentedData(s);
}
nodesToDelete.RemoveAt(pos);
CheckProperties();
}
CheckProperties();
}
#endregion
#region FindNode
/// <summary>
/// Finds the node at the specified offset.
/// After the method has run, offset is relative to the beginning of the returned node.
/// </summary>
TextAnchorNode FindNode(ref int offset)
{
TextAnchorNode n = root;
while (true) {
if (n.left != null) {
if (offset < n.left.totalLength) {
n = n.left; // descend into left subtree
continue;
} else {
offset -= n.left.totalLength; // skip left subtree
}
}
if (!n.IsAlive)
MarkNodeForDelete(n);
if (offset < n.length) {
return n; // found correct node
} else {
offset -= n.length; // skip this node
}
if (n.right != null) {
n = n.right; // descend into right subtree
} else {
// didn't find any node containing the offset
return null;
}
}
}
#endregion
#region UpdateAugmentedData
void UpdateAugmentedData(TextAnchorNode n)
{
if (!n.IsAlive)
MarkNodeForDelete(n);
int totalLength = n.length;
if (n.left != null)
totalLength += n.left.totalLength;
if (n.right != null)
totalLength += n.right.totalLength;
if (n.totalLength != totalLength) {
n.totalLength = totalLength;
if (n.parent != null)
UpdateAugmentedData(n.parent);
}
}
#endregion
#region CreateAnchor
public TextAnchor CreateAnchor(int offset)
{
Log("CreateAnchor(" + offset + ")");
TextAnchor anchor = new TextAnchor(document);
anchor.node = new TextAnchorNode(anchor);
if (root == null) {
// creating the first text anchor
root = anchor.node;
root.totalLength = root.length = offset;
} else if (offset >= root.totalLength) {
// append anchor at end of tree
anchor.node.totalLength = anchor.node.length = offset - root.totalLength;
InsertAsRight(root.RightMost, anchor.node);
} else {
// insert anchor in middle of tree
TextAnchorNode n = FindNode(ref offset);
Debug.Assert(offset < n.length);
// split segment 'n' at offset
anchor.node.totalLength = anchor.node.length = offset;
n.length -= offset;
InsertBefore(n, anchor.node);
}
DeleteMarkedNodes();
return anchor;
}
void InsertBefore(TextAnchorNode node, TextAnchorNode newNode)
{
if (node.left == null) {
InsertAsLeft(node, newNode);
} else {
InsertAsRight(node.left.RightMost, newNode);
}
}
#endregion
#region Red/Black Tree
internal const bool RED = true;
internal const bool BLACK = false;
void InsertAsLeft(TextAnchorNode parentNode, TextAnchorNode newNode)
{
Debug.Assert(parentNode.left == null);
parentNode.left = newNode;
newNode.parent = parentNode;
newNode.color = RED;
UpdateAugmentedData(parentNode);
FixTreeOnInsert(newNode);
}
void InsertAsRight(TextAnchorNode parentNode, TextAnchorNode newNode)
{
Debug.Assert(parentNode.right == null);
parentNode.right = newNode;
newNode.parent = parentNode;
newNode.color = RED;
UpdateAugmentedData(parentNode);
FixTreeOnInsert(newNode);
}
void FixTreeOnInsert(TextAnchorNode node)
{
Debug.Assert(node != null);
Debug.Assert(node.color == RED);
Debug.Assert(node.left == null || node.left.color == BLACK);
Debug.Assert(node.right == null || node.right.color == BLACK);
TextAnchorNode parentNode = node.parent;
if (parentNode == null) {
// we inserted in the root -> the node must be black
// since this is a root node, making the node black increments the number of black nodes
// on all paths by one, so it is still the same for all paths.
node.color = BLACK;
return;
}
if (parentNode.color == BLACK) {
// if the parent node where we inserted was black, our red node is placed correctly.
// since we inserted a red node, the number of black nodes on each path is unchanged
// -> the tree is still balanced
return;
}
// parentNode is red, so there is a conflict here!
// because the root is black, parentNode is not the root -> there is a grandparent node
TextAnchorNode grandparentNode = parentNode.parent;
TextAnchorNode uncleNode = Sibling(parentNode);
if (uncleNode != null && uncleNode.color == RED) {
parentNode.color = BLACK;
uncleNode.color = BLACK;
grandparentNode.color = RED;
FixTreeOnInsert(grandparentNode);
return;
}
// now we know: parent is red but uncle is black
// First rotation:
if (node == parentNode.right && parentNode == grandparentNode.left) {
RotateLeft(parentNode);
node = node.left;
} else if (node == parentNode.left && parentNode == grandparentNode.right) {
RotateRight(parentNode);
node = node.right;
}
// because node might have changed, reassign variables:
parentNode = node.parent;
grandparentNode = parentNode.parent;
// Now recolor a bit:
parentNode.color = BLACK;
grandparentNode.color = RED;
// Second rotation:
if (node == parentNode.left && parentNode == grandparentNode.left) {
RotateRight(grandparentNode);
} else {
// because of the first rotation, this is guaranteed:
Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right);
RotateLeft(grandparentNode);
}
}
void RemoveNode(TextAnchorNode removedNode)
{
if (removedNode.left != null && removedNode.right != null) {
// replace removedNode with it's in-order successor
TextAnchorNode leftMost = removedNode.right.LeftMost;
RemoveNode(leftMost); // remove leftMost from its current location
// and overwrite the removedNode with it
ReplaceNode(removedNode, leftMost);
leftMost.left = removedNode.left;
if (leftMost.left != null) leftMost.left.parent = leftMost;
leftMost.right = removedNode.right;
if (leftMost.right != null) leftMost.right.parent = leftMost;
leftMost.color = removedNode.color;
UpdateAugmentedData(leftMost);
if (leftMost.parent != null) UpdateAugmentedData(leftMost.parent);
return;
}
// now either removedNode.left or removedNode.right is null
// get the remaining child
TextAnchorNode parentNode = removedNode.parent;
TextAnchorNode childNode = removedNode.left ?? removedNode.right;
ReplaceNode(removedNode, childNode);
if (parentNode != null) UpdateAugmentedData(parentNode);
if (removedNode.color == BLACK) {
if (childNode != null && childNode.color == RED) {
childNode.color = BLACK;
} else {
FixTreeOnDelete(childNode, parentNode);
}
}
}
void FixTreeOnDelete(TextAnchorNode node, TextAnchorNode parentNode)
{
Debug.Assert(node == null || node.parent == parentNode);
if (parentNode == null)
return;
// warning: node may be null
TextAnchorNode sibling = Sibling(node, parentNode);
if (sibling.color == RED) {
parentNode.color = RED;
sibling.color = BLACK;
if (node == parentNode.left) {
RotateLeft(parentNode);
} else {
RotateRight(parentNode);
}
sibling = Sibling(node, parentNode); // update value of sibling after rotation
}
if (parentNode.color == BLACK
&& sibling.color == BLACK
&& GetColor(sibling.left) == BLACK
&& GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
FixTreeOnDelete(parentNode, parentNode.parent);
return;
}
if (parentNode.color == RED
&& sibling.color == BLACK
&& GetColor(sibling.left) == BLACK
&& GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
parentNode.color = BLACK;
return;
}
if (node == parentNode.left &&
sibling.color == BLACK &&
GetColor(sibling.left) == RED &&
GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
sibling.left.color = BLACK;
RotateRight(sibling);
}
else if (node == parentNode.right &&
sibling.color == BLACK &&
GetColor(sibling.right) == RED &&
GetColor(sibling.left) == BLACK)
{
sibling.color = RED;
sibling.right.color = BLACK;
RotateLeft(sibling);
}
sibling = Sibling(node, parentNode); // update value of sibling after rotation
sibling.color = parentNode.color;
parentNode.color = BLACK;
if (node == parentNode.left) {
if (sibling.right != null) {
Debug.Assert(sibling.right.color == RED);
sibling.right.color = BLACK;
}
RotateLeft(parentNode);
} else {
if (sibling.left != null) {
Debug.Assert(sibling.left.color == RED);
sibling.left.color = BLACK;
}
RotateRight(parentNode);
}
}
void ReplaceNode(TextAnchorNode replacedNode, TextAnchorNode newNode)
{
if (replacedNode.parent == null) {
Debug.Assert(replacedNode == root);
root = newNode;
} else {
if (replacedNode.parent.left == replacedNode)
replacedNode.parent.left = newNode;
else
replacedNode.parent.right = newNode;
}
if (newNode != null) {
newNode.parent = replacedNode.parent;
}
replacedNode.parent = null;
}
void RotateLeft(TextAnchorNode p)
{
// let q be p's right child
TextAnchorNode q = p.right;
Debug.Assert(q != null);
Debug.Assert(q.parent == p);
// set q to be the new root
ReplaceNode(p, q);
// set p's right child to be q's left child
p.right = q.left;
if (p.right != null) p.right.parent = p;
// set q's left child to be p
q.left = p;
p.parent = q;
UpdateAugmentedData(p);
UpdateAugmentedData(q);
}
void RotateRight(TextAnchorNode p)
{
// let q be p's left child
TextAnchorNode q = p.left;
Debug.Assert(q != null);
Debug.Assert(q.parent == p);
// set q to be the new root
ReplaceNode(p, q);
// set p's left child to be q's right child
p.left = q.right;
if (p.left != null) p.left.parent = p;
// set q's right child to be p
q.right = p;
p.parent = q;
UpdateAugmentedData(p);
UpdateAugmentedData(q);
}
static TextAnchorNode Sibling(TextAnchorNode node)
{
if (node == node.parent.left)
return node.parent.right;
else
return node.parent.left;
}
static TextAnchorNode Sibling(TextAnchorNode node, TextAnchorNode parentNode)
{
Debug.Assert(node == null || node.parent == parentNode);
if (node == parentNode.left)
return parentNode.right;
else
return parentNode.left;
}
static bool GetColor(TextAnchorNode node)
{
return node != null ? node.color : BLACK;
}
#endregion
#region CheckProperties
[Conditional("DATACONSISTENCYTEST")]
internal void CheckProperties()
{
#if DEBUG
if (root != null) {
CheckProperties(root);
// check red-black property:
int blackCount = -1;
CheckNodeProperties(root, null, RED, 0, ref blackCount);
}
#endif
}
#if DEBUG
void CheckProperties(TextAnchorNode node)
{
int totalLength = node.length;
if (node.left != null) {
CheckProperties(node.left);
totalLength += node.left.totalLength;
}
if (node.right != null) {
CheckProperties(node.right);
totalLength += node.right.totalLength;
}
Debug.Assert(node.totalLength == totalLength);
}
/*
1. A node is either red or black.
2. The root is black.
3. All leaves are black. (The leaves are the NIL children.)
4. Both children of every red node are black. (So every red node must have a black parent.)
5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.)
*/
void CheckNodeProperties(TextAnchorNode node, TextAnchorNode parentNode, bool parentColor, int blackCount, ref int expectedBlackCount)
{
if (node == null) return;
Debug.Assert(node.parent == parentNode);
if (parentColor == RED) {
Debug.Assert(node.color == BLACK);
}
if (node.color == BLACK) {
blackCount++;
}
if (node.left == null && node.right == null) {
// node is a leaf node:
if (expectedBlackCount == -1)
expectedBlackCount = blackCount;
else
Debug.Assert(expectedBlackCount == blackCount);
}
CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount);
CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount);
}
#endif
#endregion
#region GetTreeAsString
#if DEBUG
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public string GetTreeAsString()
{
if (root == null)
return "<empty tree>";
StringBuilder b = new StringBuilder();
AppendTreeToString(root, b, 0);
return b.ToString();
}
static void AppendTreeToString(TextAnchorNode node, StringBuilder b, int indent)
{
if (node.color == RED)
b.Append("RED ");
else
b.Append("BLACK ");
b.AppendLine(node.ToString());
indent += 2;
if (node.left != null) {
b.Append(' ', indent);
b.Append("L: ");
AppendTreeToString(node.left, b, indent);
}
if (node.right != null) {
b.Append(' ', indent);
b.Append("R: ");
AppendTreeToString(node.right, b, indent);
}
}
#endif
#endregion
}
}

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

File diff suppressed because it is too large Load Diff

167
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocumentWeakEventManager.cs

@ -1,167 +0,0 @@ @@ -1,167 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Contains weak event managers for the TextDocument events.
/// </summary>
public static class TextDocumentWeakEventManager
{
/// <summary>
/// Weak event manager for the <see cref="TextDocument.UpdateStarted"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class UpdateStarted : WeakEventManagerBase<UpdateStarted, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.UpdateStarted += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.UpdateStarted -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.UpdateFinished"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class UpdateFinished : WeakEventManagerBase<UpdateFinished, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.UpdateFinished += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.UpdateFinished -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.Changing"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class Changing : WeakEventManagerBase<Changing, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.Changing += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.Changing -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.Changed"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class Changed : WeakEventManagerBase<Changed, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.Changed += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.Changed -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.LineCountChanged"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
[Obsolete("The TextDocument.LineCountChanged event will be removed in a future version. Use PropertyChangedEventManager instead.")]
public sealed class LineCountChanged : WeakEventManagerBase<LineCountChanged, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.LineCountChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.LineCountChanged -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.TextLengthChanged"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
[Obsolete("The TextDocument.TextLengthChanged event will be removed in a future version. Use PropertyChangedEventManager instead.")]
public sealed class TextLengthChanged : WeakEventManagerBase<TextLengthChanged, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.TextLengthChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.TextLengthChanged -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="TextDocument.TextChanged"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class TextChanged : WeakEventManagerBase<TextChanged, TextDocument>
{
/// <inheritdoc/>
protected override void StartListening(TextDocument source)
{
source.TextChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextDocument source)
{
source.TextChanged -= DeliverEvent;
}
}
}
}

271
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextLocation.cs

@ -1,271 +0,0 @@ @@ -1,271 +0,0 @@
// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.ComponentModel;
using System.Globalization;
namespace ICSharpCode.AvalonEdit.Document
{
#if !NREFACTORY
/// <summary>
/// A line/column position.
/// Text editor lines/columns are counted started from one.
/// </summary>
/// <remarks>
/// The document provides the methods <see cref="IDocument.GetLocation"/> and
/// <see cref="IDocument.GetOffset(TextLocation)"/> to convert between offsets and TextLocations.
/// </remarks>
[Serializable]
[TypeConverter(typeof(TextLocationConverter))]
public struct TextLocation : IComparable<TextLocation>, IEquatable<TextLocation>
{
/// <summary>
/// Represents no text location (0, 0).
/// </summary>
public static readonly TextLocation Empty = new TextLocation(0, 0);
/// <summary>
/// Creates a TextLocation instance.
/// </summary>
public TextLocation(int line, int column)
{
this.line = line;
this.column = column;
}
readonly int column, line;
/// <summary>
/// Gets the line number.
/// </summary>
public int Line {
get { return line; }
}
/// <summary>
/// Gets the column number.
/// </summary>
public int Column {
get { return column; }
}
/// <summary>
/// Gets whether the TextLocation instance is empty.
/// </summary>
public bool IsEmpty {
get {
return column <= 0 && line <= 0;
}
}
/// <summary>
/// Gets a string representation for debugging purposes.
/// </summary>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "(Line {1}, Col {0})", this.column, this.line);
}
/// <summary>
/// Gets a hash code.
/// </summary>
public override int GetHashCode()
{
return unchecked (191 * column.GetHashCode() ^ line.GetHashCode());
}
/// <summary>
/// Equality test.
/// </summary>
public override bool Equals(object obj)
{
if (!(obj is TextLocation)) return false;
return (TextLocation)obj == this;
}
/// <summary>
/// Equality test.
/// </summary>
public bool Equals(TextLocation other)
{
return this == other;
}
/// <summary>
/// Equality test.
/// </summary>
public static bool operator ==(TextLocation left, TextLocation right)
{
return left.column == right.column && left.line == right.line;
}
/// <summary>
/// Inequality test.
/// </summary>
public static bool operator !=(TextLocation left, TextLocation right)
{
return left.column != right.column || left.line != right.line;
}
/// <summary>
/// Compares two text locations.
/// </summary>
public static bool operator <(TextLocation left, TextLocation right)
{
if (left.line < right.line)
return true;
else if (left.line == right.line)
return left.column < right.column;
else
return false;
}
/// <summary>
/// Compares two text locations.
/// </summary>
public static bool operator >(TextLocation left, TextLocation right)
{
if (left.line > right.line)
return true;
else if (left.line == right.line)
return left.column > right.column;
else
return false;
}
/// <summary>
/// Compares two text locations.
/// </summary>
public static bool operator <=(TextLocation left, TextLocation right)
{
return !(left > right);
}
/// <summary>
/// Compares two text locations.
/// </summary>
public static bool operator >=(TextLocation left, TextLocation right)
{
return !(left < right);
}
/// <summary>
/// Compares two text locations.
/// </summary>
public int CompareTo(TextLocation other)
{
if (this == other)
return 0;
if (this < other)
return -1;
else
return 1;
}
}
/// <summary>
/// Converts strings of the form '0+[;,]0+' to a <see cref="TextLocation"/>.
/// </summary>
public class TextLocationConverter : TypeConverter
{
/// <inheritdoc/>
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc/>
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(TextLocation) || base.CanConvertTo(context, destinationType);
}
/// <inheritdoc/>
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string) {
string[] parts = ((string)value).Split(';', ',');
if (parts.Length == 2) {
return new TextLocation(int.Parse(parts[0], culture), int.Parse(parts[1], culture));
}
}
return base.ConvertFrom(context, culture, value);
}
/// <inheritdoc/>
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is TextLocation && destinationType == typeof(string)) {
var loc = (TextLocation)value;
return loc.Line.ToString(culture) + ";" + loc.Column.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
/// <summary>
/// An (Offset,Length)-pair.
/// </summary>
public interface ISegment
{
/// <summary>
/// Gets the start offset of the segment.
/// </summary>
int Offset { get; }
/// <summary>
/// Gets the length of the segment.
/// </summary>
/// <remarks>For line segments (IDocumentLine), the length does not include the line delimeter.</remarks>
int Length { get; }
/// <summary>
/// Gets the end offset of the segment.
/// </summary>
/// <remarks>EndOffset = Offset + Length;</remarks>
int EndOffset { get; }
}
/// <summary>
/// Extension methods for <see cref="ISegment"/>.
/// </summary>
public static class ISegmentExtensions
{
/// <summary>
/// Gets whether <paramref name="segment"/> fully contains the specified segment.
/// </summary>
/// <remarks>
/// Use <c>segment.Contains(offset, 0)</c> to detect whether a segment (end inclusive) contains offset;
/// use <c>segment.Contains(offset, 1)</c> to detect whether a segment (end exclusive) contains offset.
/// </remarks>
public static bool Contains (this ISegment segment, int offset, int length)
{
return segment.Offset <= offset && offset + length <= segment.EndOffset;
}
/// <summary>
/// Gets whether <paramref name="thisSegment"/> fully contains the specified segment.
/// </summary>
public static bool Contains (this ISegment thisSegment, ISegment segment)
{
return segment != null && thisSegment.Offset <= segment.Offset && segment.EndOffset <= thisSegment.EndOffset;
}
}
#endif
}

266
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegment.cs

@ -1,266 +0,0 @@ @@ -1,266 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// A segment that can be put into a <see cref="TextSegmentCollection{T}"/>.
/// </summary>
/// <remarks>
/// <para>
/// A <see cref="TextSegment"/> can be stand-alone or part of a <see cref="TextSegmentCollection{T}"/>.
/// If the segment is stored inside a TextSegmentCollection, its Offset and Length will be updated by that collection.
/// </para>
/// <para>
/// When the document changes, the offsets of all text segments in the TextSegmentCollection will be adjusted accordingly.
/// Start offsets move like <see cref="AnchorMovementType">AnchorMovementType.AfterInsertion</see>,
/// end offsets move like <see cref="AnchorMovementType">AnchorMovementType.BeforeInsertion</see>
/// (i.e. the segment will always stay as small as possible).</para>
/// <para>
/// If a document change causes a segment to be deleted completely, it will be reduced to length 0, but segments are
/// never automatically removed from the collection.
/// Segments with length 0 will never expand due to document changes, and they move as <c>AfterInsertion</c>.
/// </para>
/// <para>
/// Thread-safety: a TextSegmentCollection that is connected to a <see cref="TextDocument"/> may only be used on that document's owner thread.
/// A disconnected TextSegmentCollection is safe for concurrent reads, but concurrent access is not safe when there are writes.
/// Keep in mind that reading the Offset properties of a text segment inside the collection is a read access on the
/// collection; and setting an Offset property of a text segment is a write access on the collection.
/// </para>
/// </remarks>
/// <seealso cref="ISegment"/>
/// <seealso cref="AnchorSegment"/>
/// <seealso cref="TextSegmentCollection{T}"/>
public class TextSegment : ISegment
{
internal ISegmentTree ownerTree;
internal TextSegment left, right, parent;
/// <summary>
/// The color of the segment in the red/black tree.
/// </summary>
internal bool color;
/// <summary>
/// The "length" of the node (distance to previous node)
/// </summary>
internal int nodeLength;
/// <summary>
/// The total "length" of this subtree.
/// </summary>
internal int totalNodeLength; // totalNodeLength = nodeLength + left.totalNodeLength + right.totalNodeLength
/// <summary>
/// The length of the segment (do not confuse with nodeLength).
/// </summary>
internal int segmentLength;
/// <summary>
/// distanceToMaxEnd = Max(segmentLength,
/// left.distanceToMaxEnd + left.Offset - Offset,
/// left.distanceToMaxEnd + right.Offset - Offset)
/// </summary>
internal int distanceToMaxEnd;
int ISegment.Offset {
get { return StartOffset; }
}
/// <summary>
/// Gets whether this segment is connected to a TextSegmentCollection and will automatically
/// update its offsets.
/// </summary>
protected bool IsConnectedToCollection {
get {
return ownerTree != null;
}
}
/// <summary>
/// Gets/Sets the start offset of the segment.
/// </summary>
/// <remarks>
/// When setting the start offset, the end offset will change, too: the Length of the segment will stay constant.
/// </remarks>
public int StartOffset {
get {
// If the segment is not connected to a tree, we store the offset in "nodeLength".
// Otherwise, "nodeLength" contains the distance to the start offset of the previous node
Debug.Assert(!(ownerTree == null && parent != null));
Debug.Assert(!(ownerTree == null && left != null));
TextSegment n = this;
int offset = n.nodeLength;
if (n.left != null)
offset += n.left.totalNodeLength;
while (n.parent != null) {
if (n == n.parent.right) {
if (n.parent.left != null)
offset += n.parent.left.totalNodeLength;
offset += n.parent.nodeLength;
}
n = n.parent;
}
return offset;
}
set {
if (value < 0)
throw new ArgumentOutOfRangeException("value", "Offset must not be negative");
if (this.StartOffset != value) {
// need a copy of the variable because ownerTree.Remove() sets this.ownerTree to null
ISegmentTree ownerTree = this.ownerTree;
if (ownerTree != null) {
ownerTree.Remove(this);
nodeLength = value;
ownerTree.Add(this);
} else {
nodeLength = value;
}
OnSegmentChanged();
}
}
}
/// <summary>
/// Gets/Sets the end offset of the segment.
/// </summary>
/// <remarks>
/// Setting the end offset will change the length, the start offset will stay constant.
/// </remarks>
public int EndOffset {
get {
return StartOffset + Length;
}
set {
int newLength = value - StartOffset;
if (newLength < 0)
throw new ArgumentOutOfRangeException("value", "EndOffset must be greater or equal to StartOffset");
Length = newLength;
}
}
/// <summary>
/// Gets/Sets the length of the segment.
/// </summary>
/// <remarks>
/// Setting the length will change the end offset, the start offset will stay constant.
/// </remarks>
public int Length {
get {
return segmentLength;
}
set {
if (value < 0)
throw new ArgumentOutOfRangeException("value", "Length must not be negative");
if (segmentLength != value) {
segmentLength = value;
if (ownerTree != null)
ownerTree.UpdateAugmentedData(this);
OnSegmentChanged();
}
}
}
/// <summary>
/// This method gets called when the StartOffset/Length/EndOffset properties are set.
/// It is not called when StartOffset/Length/EndOffset change due to document changes
/// </summary>
protected virtual void OnSegmentChanged()
{
}
internal TextSegment LeftMost {
get {
TextSegment node = this;
while (node.left != null)
node = node.left;
return node;
}
}
internal TextSegment RightMost {
get {
TextSegment node = this;
while (node.right != null)
node = node.right;
return node;
}
}
/// <summary>
/// Gets the inorder successor of the node.
/// </summary>
internal TextSegment Successor {
get {
if (right != null) {
return right.LeftMost;
} else {
TextSegment node = this;
TextSegment oldNode;
do {
oldNode = node;
node = node.parent;
// go up until we are coming out of a left subtree
} while (node != null && node.right == oldNode);
return node;
}
}
}
/// <summary>
/// Gets the inorder predecessor of the node.
/// </summary>
internal TextSegment Predecessor {
get {
if (left != null) {
return left.RightMost;
} else {
TextSegment node = this;
TextSegment oldNode;
do {
oldNode = node;
node = node.parent;
// go up until we are coming out of a right subtree
} while (node != null && node.left == oldNode);
return node;
}
}
}
#if DEBUG
internal string ToDebugString()
{
return "[nodeLength=" + nodeLength + " totalNodeLength=" + totalNodeLength
+ " distanceToMaxEnd=" + distanceToMaxEnd + " MaxEndOffset=" + (StartOffset + distanceToMaxEnd) + "]";
}
#endif
/// <inheritdoc/>
public override string ToString()
{
return "[" + GetType().Name + " Offset=" + StartOffset + " Length=" + Length + " EndOffset=" + EndOffset + "]";
}
}
}

989
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs

@ -1,989 +0,0 @@ @@ -1,989 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using ICSharpCode.AvalonEdit.Utils;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Interface to allow TextSegments to access the TextSegmentCollection - we cannot use a direct reference
/// because TextSegmentCollection is generic.
/// </summary>
interface ISegmentTree
{
void Add(TextSegment s);
void Remove(TextSegment s);
void UpdateAugmentedData(TextSegment s);
}
/// <summary>
/// <para>
/// A collection of text segments that supports efficient lookup of segments
/// intersecting with another segment.
/// </para>
/// </summary>
/// <remarks><inheritdoc cref="TextSegment"/></remarks>
/// <see cref="TextSegment"/>
public sealed class TextSegmentCollection<T> : ICollection<T>, ISegmentTree, IWeakEventListener where T : TextSegment
{
// Implementation: this is basically a mixture of an augmented interval tree
// and the TextAnchorTree.
// WARNING: you need to understand interval trees (the version with the augmented 'high'/'max' field)
// and how the TextAnchorTree works before you have any chance of understanding this code.
// This means that every node holds two "segments":
// one like the segments in the text anchor tree to support efficient offset changes
// and another that is the interval as seen by the user
// So basically, the tree contains a list of contiguous node segments of the first kind,
// with interval segments starting at the end of every node segment.
// Performance:
// Add is O(lg n)
// Remove is O(lg n)
// DocumentChanged is O(m * lg n), with m the number of segments that intersect with the changed document section
// FindFirstSegmentWithStartAfter is O(m + lg n) with m being the number of segments at the same offset as the result segment
// FindIntersectingSegments is O(m + lg n) with m being the number of intersecting segments.
int count;
TextSegment root;
bool isConnectedToDocument;
#region Constructor
/// <summary>
/// Creates a new TextSegmentCollection that needs manual calls to <see cref="UpdateOffsets(DocumentChangeEventArgs)"/>.
/// </summary>
public TextSegmentCollection()
{
}
/// <summary>
/// Creates a new TextSegmentCollection that updates the offsets automatically.
/// </summary>
/// <param name="textDocument">The document to which the text segments
/// that will be added to the tree belong. When the document changes, the
/// position of the text segments will be updated accordingly.</param>
public TextSegmentCollection(TextDocument textDocument)
{
if (textDocument == null)
throw new ArgumentNullException("textDocument");
textDocument.VerifyAccess();
isConnectedToDocument = true;
TextDocumentWeakEventManager.Changed.AddListener(textDocument, this);
}
#endregion
#region OnDocumentChanged / UpdateOffsets
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(TextDocumentWeakEventManager.Changed)) {
OnDocumentChanged((DocumentChangeEventArgs)e);
return true;
}
return false;
}
/// <summary>
/// Updates the start and end offsets of all segments stored in this collection.
/// </summary>
/// <param name="e">DocumentChangeEventArgs instance describing the change to the document.</param>
public void UpdateOffsets(DocumentChangeEventArgs e)
{
if (e == null)
throw new ArgumentNullException("e");
if (isConnectedToDocument)
throw new InvalidOperationException("This TextSegmentCollection will automatically update offsets; do not call UpdateOffsets manually!");
OnDocumentChanged(e);
CheckProperties();
}
void OnDocumentChanged(DocumentChangeEventArgs e)
{
OffsetChangeMap map = e.OffsetChangeMapOrNull;
if (map != null) {
foreach (OffsetChangeMapEntry entry in map) {
UpdateOffsetsInternal(entry);
}
} else {
UpdateOffsetsInternal(e.CreateSingleChangeMapEntry());
}
}
/// <summary>
/// Updates the start and end offsets of all segments stored in this collection.
/// </summary>
/// <param name="change">OffsetChangeMapEntry instance describing the change to the document.</param>
public void UpdateOffsets(OffsetChangeMapEntry change)
{
if (isConnectedToDocument)
throw new InvalidOperationException("This TextSegmentCollection will automatically update offsets; do not call UpdateOffsets manually!");
UpdateOffsetsInternal(change);
CheckProperties();
}
#endregion
#region UpdateOffsets (implementation)
void UpdateOffsetsInternal(OffsetChangeMapEntry change)
{
// Special case pure insertions, because they don't always cause a text segment to increase in size when the replaced region
// is inside a segment (when offset is at start or end of a text semgent).
if (change.RemovalLength == 0) {
InsertText(change.Offset, change.InsertionLength);
} else {
ReplaceText(change);
}
}
void InsertText(int offset, int length)
{
if (length == 0)
return;
// enlarge segments that contain offset (excluding those that have offset as endpoint)
foreach (TextSegment segment in FindSegmentsContaining(offset)) {
if (segment.StartOffset < offset && offset < segment.EndOffset) {
segment.Length += length;
}
}
// move start offsets of all segments >= offset
TextSegment node = FindFirstSegmentWithStartAfter(offset);
if (node != null) {
node.nodeLength += length;
UpdateAugmentedData(node);
}
}
void ReplaceText(OffsetChangeMapEntry change)
{
Debug.Assert(change.RemovalLength > 0);
int offset = change.Offset;
foreach (TextSegment segment in FindOverlappingSegments(offset, change.RemovalLength)) {
if (segment.StartOffset <= offset) {
if (segment.EndOffset >= offset + change.RemovalLength) {
// Replacement inside segment: adjust segment length
segment.Length += change.InsertionLength - change.RemovalLength;
} else {
// Replacement starting inside segment and ending after segment end: set segment end to removal position
//segment.EndOffset = offset;
segment.Length = offset - segment.StartOffset;
}
} else {
// Replacement starting in front of text segment and running into segment.
// Keep segment.EndOffset constant and move segment.StartOffset to the end of the replacement
int remainingLength = segment.EndOffset - (offset + change.RemovalLength);
RemoveSegment(segment);
segment.StartOffset = offset + change.RemovalLength;
segment.Length = Math.Max(0, remainingLength);
AddSegment(segment);
}
}
// move start offsets of all segments > offset
TextSegment node = FindFirstSegmentWithStartAfter(offset + 1);
if (node != null) {
Debug.Assert(node.nodeLength >= change.RemovalLength);
node.nodeLength += change.InsertionLength - change.RemovalLength;
UpdateAugmentedData(node);
}
}
#endregion
#region Add
/// <summary>
/// Adds the specified segment to the tree. This will cause the segment to update when the
/// document changes.
/// </summary>
public void Add(T item)
{
if (item == null)
throw new ArgumentNullException("item");
if (item.ownerTree != null)
throw new ArgumentException("The segment is already added to a SegmentCollection.");
AddSegment(item);
}
void ISegmentTree.Add(TextSegment s)
{
AddSegment(s);
}
void AddSegment(TextSegment node)
{
int insertionOffset = node.StartOffset;
node.distanceToMaxEnd = node.segmentLength;
if (root == null) {
root = node;
node.totalNodeLength = node.nodeLength;
} else if (insertionOffset >= root.totalNodeLength) {
// append segment at end of tree
node.nodeLength = node.totalNodeLength = insertionOffset - root.totalNodeLength;
InsertAsRight(root.RightMost, node);
} else {
// insert in middle of tree
TextSegment n = FindNode(ref insertionOffset);
Debug.Assert(insertionOffset < n.nodeLength);
// split node segment 'n' at offset
node.totalNodeLength = node.nodeLength = insertionOffset;
n.nodeLength -= insertionOffset;
InsertBefore(n, node);
}
node.ownerTree = this;
count++;
CheckProperties();
}
void InsertBefore(TextSegment node, TextSegment newNode)
{
if (node.left == null) {
InsertAsLeft(node, newNode);
} else {
InsertAsRight(node.left.RightMost, newNode);
}
}
#endregion
#region GetNextSegment / GetPreviousSegment
/// <summary>
/// Gets the next segment after the specified segment.
/// Segments are sorted by their start offset.
/// Returns null if segment is the last segment.
/// </summary>
public T GetNextSegment(T segment)
{
if (!Contains(segment))
throw new ArgumentException("segment is not inside the segment tree");
return (T)segment.Successor;
}
/// <summary>
/// Gets the previous segment before the specified segment.
/// Segments are sorted by their start offset.
/// Returns null if segment is the first segment.
/// </summary>
public T GetPreviousSegment(T segment)
{
if (!Contains(segment))
throw new ArgumentException("segment is not inside the segment tree");
return (T)segment.Predecessor;
}
#endregion
#region FirstSegment/LastSegment
/// <summary>
/// Returns the first segment in the collection or null, if the collection is empty.
/// </summary>
public T FirstSegment {
get {
return root == null ? null : (T)root.LeftMost;
}
}
/// <summary>
/// Returns the last segment in the collection or null, if the collection is empty.
/// </summary>
public T LastSegment {
get {
return root == null ? null : (T)root.RightMost;
}
}
#endregion
#region FindFirstSegmentWithStartAfter
/// <summary>
/// Gets the first segment with a start offset greater or equal to <paramref name="startOffset"/>.
/// Returns null if no such segment is found.
/// </summary>
public T FindFirstSegmentWithStartAfter(int startOffset)
{
if (root == null)
return null;
if (startOffset <= 0)
return (T)root.LeftMost;
TextSegment s = FindNode(ref startOffset);
// startOffset means that the previous segment is starting at the offset we were looking for
while (startOffset == 0) {
TextSegment p = (s == null) ? root.RightMost : s.Predecessor;
// There must always be a predecessor: if we were looking for the first node, we would have already
// returned it as root.LeftMost above.
Debug.Assert(p != null);
startOffset += p.nodeLength;
s = p;
}
return (T)s;
}
/// <summary>
/// Finds the node at the specified offset.
/// After the method has run, offset is relative to the beginning of the returned node.
/// </summary>
TextSegment FindNode(ref int offset)
{
TextSegment n = root;
while (true) {
if (n.left != null) {
if (offset < n.left.totalNodeLength) {
n = n.left; // descend into left subtree
continue;
} else {
offset -= n.left.totalNodeLength; // skip left subtree
}
}
if (offset < n.nodeLength) {
return n; // found correct node
} else {
offset -= n.nodeLength; // skip this node
}
if (n.right != null) {
n = n.right; // descend into right subtree
} else {
// didn't find any node containing the offset
return null;
}
}
}
#endregion
#region FindOverlappingSegments
/// <summary>
/// Finds all segments that contain the given offset.
/// (StartOffset &lt;= offset &lt;= EndOffset)
/// Segments are returned in the order given by GetNextSegment/GetPreviousSegment.
/// </summary>
/// <returns>Returns a new collection containing the results of the query.
/// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
public ReadOnlyCollection<T> FindSegmentsContaining(int offset)
{
return FindOverlappingSegments(offset, 0);
}
/// <summary>
/// Finds all segments that overlap with the given segment (including touching segments).
/// </summary>
/// <returns>Returns a new collection containing the results of the query.
/// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
public ReadOnlyCollection<T> FindOverlappingSegments(ISegment segment)
{
if (segment == null)
throw new ArgumentNullException("segment");
return FindOverlappingSegments(segment.Offset, segment.Length);
}
/// <summary>
/// Finds all segments that overlap with the given segment (including touching segments).
/// Segments are returned in the order given by GetNextSegment/GetPreviousSegment.
/// </summary>
/// <returns>Returns a new collection containing the results of the query.
/// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
public ReadOnlyCollection<T> FindOverlappingSegments(int offset, int length)
{
ThrowUtil.CheckNotNegative(length, "length");
List<T> results = new List<T>();
if (root != null) {
FindOverlappingSegments(results, root, offset, offset + length);
}
return results.AsReadOnly();
}
void FindOverlappingSegments(List<T> results, TextSegment node, int low, int high)
{
// low and high are relative to node.LeftMost startpos (not node.LeftMost.Offset)
if (high < 0) {
// node is irrelevant for search because all intervals in node are after high
return;
}
// find values relative to node.Offset
int nodeLow = low - node.nodeLength;
int nodeHigh = high - node.nodeLength;
if (node.left != null) {
nodeLow -= node.left.totalNodeLength;
nodeHigh -= node.left.totalNodeLength;
}
if (node.distanceToMaxEnd < nodeLow) {
// node is irrelevant for search because all intervals in node are before low
return;
}
if (node.left != null)
FindOverlappingSegments(results, node.left, low, high);
if (nodeHigh < 0) {
// node and everything in node.right is before low
return;
}
if (nodeLow <= node.segmentLength) {
results.Add((T)node);
}
if (node.right != null)
FindOverlappingSegments(results, node.right, nodeLow, nodeHigh);
}
#endregion
#region UpdateAugmentedData
void UpdateAugmentedData(TextSegment node)
{
int totalLength = node.nodeLength;
int distanceToMaxEnd = node.segmentLength;
if (node.left != null) {
totalLength += node.left.totalNodeLength;
int leftDTME = node.left.distanceToMaxEnd;
// dtme is relative, so convert it to the coordinates of node:
if (node.left.right != null)
leftDTME -= node.left.right.totalNodeLength;
leftDTME -= node.nodeLength;
if (leftDTME > distanceToMaxEnd)
distanceToMaxEnd = leftDTME;
}
if (node.right != null) {
totalLength += node.right.totalNodeLength;
int rightDTME = node.right.distanceToMaxEnd;
// dtme is relative, so convert it to the coordinates of node:
rightDTME += node.right.nodeLength;
if (node.right.left != null)
rightDTME += node.right.left.totalNodeLength;
if (rightDTME > distanceToMaxEnd)
distanceToMaxEnd = rightDTME;
}
if (node.totalNodeLength != totalLength
|| node.distanceToMaxEnd != distanceToMaxEnd)
{
node.totalNodeLength = totalLength;
node.distanceToMaxEnd = distanceToMaxEnd;
if (node.parent != null)
UpdateAugmentedData(node.parent);
}
}
void ISegmentTree.UpdateAugmentedData(TextSegment node)
{
UpdateAugmentedData(node);
}
#endregion
#region Remove
/// <summary>
/// Removes the specified segment from the tree. This will cause the segment to not update
/// anymore when the document changes.
/// </summary>
public bool Remove(T item)
{
if (!Contains(item))
return false;
RemoveSegment(item);
return true;
}
void ISegmentTree.Remove(TextSegment s)
{
RemoveSegment(s);
}
void RemoveSegment(TextSegment s)
{
int oldOffset = s.StartOffset;
TextSegment successor = s.Successor;
if (successor != null)
successor.nodeLength += s.nodeLength;
RemoveNode(s);
if (successor != null)
UpdateAugmentedData(successor);
Disconnect(s, oldOffset);
CheckProperties();
}
void Disconnect(TextSegment s, int offset)
{
s.left = s.right = s.parent = null;
s.ownerTree = null;
s.nodeLength = offset;
count--;
}
/// <summary>
/// Removes all segments from the tree.
/// </summary>
public void Clear()
{
T[] segments = this.ToArray();
root = null;
int offset = 0;
foreach (TextSegment s in segments) {
offset += s.nodeLength;
Disconnect(s, offset);
}
CheckProperties();
}
#endregion
#region CheckProperties
[Conditional("DATACONSISTENCYTEST")]
internal void CheckProperties()
{
#if DEBUG
if (root != null) {
CheckProperties(root);
// check red-black property:
int blackCount = -1;
CheckNodeProperties(root, null, RED, 0, ref blackCount);
}
int expectedCount = 0;
// we cannot trust LINQ not to call ICollection.Count, so we need this loop
// to count the elements in the tree
using (IEnumerator<T> en = GetEnumerator()) {
while (en.MoveNext()) expectedCount++;
}
Debug.Assert(count == expectedCount);
#endif
}
#if DEBUG
void CheckProperties(TextSegment node)
{
int totalLength = node.nodeLength;
int distanceToMaxEnd = node.segmentLength;
if (node.left != null) {
CheckProperties(node.left);
totalLength += node.left.totalNodeLength;
distanceToMaxEnd = Math.Max(distanceToMaxEnd,
node.left.distanceToMaxEnd + node.left.StartOffset - node.StartOffset);
}
if (node.right != null) {
CheckProperties(node.right);
totalLength += node.right.totalNodeLength;
distanceToMaxEnd = Math.Max(distanceToMaxEnd,
node.right.distanceToMaxEnd + node.right.StartOffset - node.StartOffset);
}
Debug.Assert(node.totalNodeLength == totalLength);
Debug.Assert(node.distanceToMaxEnd == distanceToMaxEnd);
}
/*
1. A node is either red or black.
2. The root is black.
3. All leaves are black. (The leaves are the NIL children.)
4. Both children of every red node are black. (So every red node must have a black parent.)
5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.)
*/
void CheckNodeProperties(TextSegment node, TextSegment parentNode, bool parentColor, int blackCount, ref int expectedBlackCount)
{
if (node == null) return;
Debug.Assert(node.parent == parentNode);
if (parentColor == RED) {
Debug.Assert(node.color == BLACK);
}
if (node.color == BLACK) {
blackCount++;
}
if (node.left == null && node.right == null) {
// node is a leaf node:
if (expectedBlackCount == -1)
expectedBlackCount = blackCount;
else
Debug.Assert(expectedBlackCount == blackCount);
}
CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount);
CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount);
}
static void AppendTreeToString(TextSegment node, StringBuilder b, int indent)
{
if (node.color == RED)
b.Append("RED ");
else
b.Append("BLACK ");
b.AppendLine(node.ToString() + node.ToDebugString());
indent += 2;
if (node.left != null) {
b.Append(' ', indent);
b.Append("L: ");
AppendTreeToString(node.left, b, indent);
}
if (node.right != null) {
b.Append(' ', indent);
b.Append("R: ");
AppendTreeToString(node.right, b, indent);
}
}
#endif
internal string GetTreeAsString()
{
#if DEBUG
StringBuilder b = new StringBuilder();
if (root != null)
AppendTreeToString(root, b, 0);
return b.ToString();
#else
return "Not available in release build.";
#endif
}
#endregion
#region Red/Black Tree
internal const bool RED = true;
internal const bool BLACK = false;
void InsertAsLeft(TextSegment parentNode, TextSegment newNode)
{
Debug.Assert(parentNode.left == null);
parentNode.left = newNode;
newNode.parent = parentNode;
newNode.color = RED;
UpdateAugmentedData(parentNode);
FixTreeOnInsert(newNode);
}
void InsertAsRight(TextSegment parentNode, TextSegment newNode)
{
Debug.Assert(parentNode.right == null);
parentNode.right = newNode;
newNode.parent = parentNode;
newNode.color = RED;
UpdateAugmentedData(parentNode);
FixTreeOnInsert(newNode);
}
void FixTreeOnInsert(TextSegment node)
{
Debug.Assert(node != null);
Debug.Assert(node.color == RED);
Debug.Assert(node.left == null || node.left.color == BLACK);
Debug.Assert(node.right == null || node.right.color == BLACK);
TextSegment parentNode = node.parent;
if (parentNode == null) {
// we inserted in the root -> the node must be black
// since this is a root node, making the node black increments the number of black nodes
// on all paths by one, so it is still the same for all paths.
node.color = BLACK;
return;
}
if (parentNode.color == BLACK) {
// if the parent node where we inserted was black, our red node is placed correctly.
// since we inserted a red node, the number of black nodes on each path is unchanged
// -> the tree is still balanced
return;
}
// parentNode is red, so there is a conflict here!
// because the root is black, parentNode is not the root -> there is a grandparent node
TextSegment grandparentNode = parentNode.parent;
TextSegment uncleNode = Sibling(parentNode);
if (uncleNode != null && uncleNode.color == RED) {
parentNode.color = BLACK;
uncleNode.color = BLACK;
grandparentNode.color = RED;
FixTreeOnInsert(grandparentNode);
return;
}
// now we know: parent is red but uncle is black
// First rotation:
if (node == parentNode.right && parentNode == grandparentNode.left) {
RotateLeft(parentNode);
node = node.left;
} else if (node == parentNode.left && parentNode == grandparentNode.right) {
RotateRight(parentNode);
node = node.right;
}
// because node might have changed, reassign variables:
parentNode = node.parent;
grandparentNode = parentNode.parent;
// Now recolor a bit:
parentNode.color = BLACK;
grandparentNode.color = RED;
// Second rotation:
if (node == parentNode.left && parentNode == grandparentNode.left) {
RotateRight(grandparentNode);
} else {
// because of the first rotation, this is guaranteed:
Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right);
RotateLeft(grandparentNode);
}
}
void RemoveNode(TextSegment removedNode)
{
if (removedNode.left != null && removedNode.right != null) {
// replace removedNode with it's in-order successor
TextSegment leftMost = removedNode.right.LeftMost;
RemoveNode(leftMost); // remove leftMost from its current location
// and overwrite the removedNode with it
ReplaceNode(removedNode, leftMost);
leftMost.left = removedNode.left;
if (leftMost.left != null) leftMost.left.parent = leftMost;
leftMost.right = removedNode.right;
if (leftMost.right != null) leftMost.right.parent = leftMost;
leftMost.color = removedNode.color;
UpdateAugmentedData(leftMost);
if (leftMost.parent != null) UpdateAugmentedData(leftMost.parent);
return;
}
// now either removedNode.left or removedNode.right is null
// get the remaining child
TextSegment parentNode = removedNode.parent;
TextSegment childNode = removedNode.left ?? removedNode.right;
ReplaceNode(removedNode, childNode);
if (parentNode != null) UpdateAugmentedData(parentNode);
if (removedNode.color == BLACK) {
if (childNode != null && childNode.color == RED) {
childNode.color = BLACK;
} else {
FixTreeOnDelete(childNode, parentNode);
}
}
}
void FixTreeOnDelete(TextSegment node, TextSegment parentNode)
{
Debug.Assert(node == null || node.parent == parentNode);
if (parentNode == null)
return;
// warning: node may be null
TextSegment sibling = Sibling(node, parentNode);
if (sibling.color == RED) {
parentNode.color = RED;
sibling.color = BLACK;
if (node == parentNode.left) {
RotateLeft(parentNode);
} else {
RotateRight(parentNode);
}
sibling = Sibling(node, parentNode); // update value of sibling after rotation
}
if (parentNode.color == BLACK
&& sibling.color == BLACK
&& GetColor(sibling.left) == BLACK
&& GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
FixTreeOnDelete(parentNode, parentNode.parent);
return;
}
if (parentNode.color == RED
&& sibling.color == BLACK
&& GetColor(sibling.left) == BLACK
&& GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
parentNode.color = BLACK;
return;
}
if (node == parentNode.left &&
sibling.color == BLACK &&
GetColor(sibling.left) == RED &&
GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
sibling.left.color = BLACK;
RotateRight(sibling);
}
else if (node == parentNode.right &&
sibling.color == BLACK &&
GetColor(sibling.right) == RED &&
GetColor(sibling.left) == BLACK)
{
sibling.color = RED;
sibling.right.color = BLACK;
RotateLeft(sibling);
}
sibling = Sibling(node, parentNode); // update value of sibling after rotation
sibling.color = parentNode.color;
parentNode.color = BLACK;
if (node == parentNode.left) {
if (sibling.right != null) {
Debug.Assert(sibling.right.color == RED);
sibling.right.color = BLACK;
}
RotateLeft(parentNode);
} else {
if (sibling.left != null) {
Debug.Assert(sibling.left.color == RED);
sibling.left.color = BLACK;
}
RotateRight(parentNode);
}
}
void ReplaceNode(TextSegment replacedNode, TextSegment newNode)
{
if (replacedNode.parent == null) {
Debug.Assert(replacedNode == root);
root = newNode;
} else {
if (replacedNode.parent.left == replacedNode)
replacedNode.parent.left = newNode;
else
replacedNode.parent.right = newNode;
}
if (newNode != null) {
newNode.parent = replacedNode.parent;
}
replacedNode.parent = null;
}
void RotateLeft(TextSegment p)
{
// let q be p's right child
TextSegment q = p.right;
Debug.Assert(q != null);
Debug.Assert(q.parent == p);
// set q to be the new root
ReplaceNode(p, q);
// set p's right child to be q's left child
p.right = q.left;
if (p.right != null) p.right.parent = p;
// set q's left child to be p
q.left = p;
p.parent = q;
UpdateAugmentedData(p);
UpdateAugmentedData(q);
}
void RotateRight(TextSegment p)
{
// let q be p's left child
TextSegment q = p.left;
Debug.Assert(q != null);
Debug.Assert(q.parent == p);
// set q to be the new root
ReplaceNode(p, q);
// set p's left child to be q's right child
p.left = q.right;
if (p.left != null) p.left.parent = p;
// set q's right child to be p
q.right = p;
p.parent = q;
UpdateAugmentedData(p);
UpdateAugmentedData(q);
}
static TextSegment Sibling(TextSegment node)
{
if (node == node.parent.left)
return node.parent.right;
else
return node.parent.left;
}
static TextSegment Sibling(TextSegment node, TextSegment parentNode)
{
Debug.Assert(node == null || node.parent == parentNode);
if (node == parentNode.left)
return parentNode.right;
else
return parentNode.left;
}
static bool GetColor(TextSegment node)
{
return node != null ? node.color : BLACK;
}
#endregion
#region ICollection<T> implementation
/// <summary>
/// Gets the number of segments in the tree.
/// </summary>
public int Count {
get { return count; }
}
bool ICollection<T>.IsReadOnly {
get { return false; }
}
/// <summary>
/// Gets whether this tree contains the specified item.
/// </summary>
public bool Contains(T item)
{
return item != null && item.ownerTree == this;
}
/// <summary>
/// Copies all segments in this SegmentTree to the specified array.
/// </summary>
public void CopyTo(T[] array, int arrayIndex)
{
if (array == null)
throw new ArgumentNullException("array");
if (array.Length < this.Count)
throw new ArgumentException("The array is too small", "array");
if (arrayIndex < 0 || arrayIndex + count > array.Length)
throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "Value must be between 0 and " + (array.Length - count));
foreach (T s in this) {
array[arrayIndex++] = s;
}
}
/// <summary>
/// Gets an enumerator to enumerate the segments.
/// </summary>
public IEnumerator<T> GetEnumerator()
{
if (root != null) {
TextSegment current = root.LeftMost;
while (current != null) {
yield return (T)current;
// TODO: check if collection was modified during enumeration
current = current.Successor;
}
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
}
}

136
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSourceVersionProvider.cs

@ -1,136 +0,0 @@ @@ -1,136 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Document
{
#if !NREFACTORY
/// <summary>
/// Provides ITextSourceVersion instances.
/// </summary>
public class TextSourceVersionProvider
{
Version currentVersion;
/// <summary>
/// Creates a new TextSourceVersionProvider instance.
/// </summary>
public TextSourceVersionProvider()
{
this.currentVersion = new Version(this);
}
/// <summary>
/// Gets the current version.
/// </summary>
public ITextSourceVersion CurrentVersion {
get { return currentVersion; }
}
/// <summary>
/// Replaces the current version with a new version.
/// </summary>
/// <param name="change">Change from current version to new version</param>
public void AppendChange(TextChangeEventArgs change)
{
if (change == null)
throw new ArgumentNullException("change");
currentVersion.change = change;
currentVersion.next = new Version(currentVersion);
currentVersion = currentVersion.next;
}
[DebuggerDisplay("Version #{id}")]
sealed class Version : ITextSourceVersion
{
// Reference back to the provider.
// Used to determine if two checkpoints belong to the same document.
readonly TextSourceVersionProvider provider;
// ID used for CompareAge()
readonly int id;
// the change from this version to the next version
internal TextChangeEventArgs change;
internal Version next;
internal Version(TextSourceVersionProvider provider)
{
this.provider = provider;
}
internal Version(Version prev)
{
this.provider = prev.provider;
this.id = unchecked( prev.id + 1 );
}
public bool BelongsToSameDocumentAs(ITextSourceVersion other)
{
Version o = other as Version;
return o != null && provider == o.provider;
}
public int CompareAge(ITextSourceVersion other)
{
if (other == null)
throw new ArgumentNullException("other");
Version o = other as Version;
if (o == null || provider != o.provider)
throw new ArgumentException("Versions do not belong to the same document.");
// We will allow overflows, but assume that the maximum distance between checkpoints is 2^31-1.
// This is guaranteed on x86 because so many checkpoints don't fit into memory.
return Math.Sign(unchecked( this.id - o.id ));
}
public IEnumerable<TextChangeEventArgs> GetChangesTo(ITextSourceVersion other)
{
int result = CompareAge(other);
Version o = (Version)other;
if (result < 0)
return GetForwardChanges(o);
else if (result > 0)
return o.GetForwardChanges(this).Reverse().Select(change => change.Invert());
else
return Empty<TextChangeEventArgs>.Array;
}
IEnumerable<TextChangeEventArgs> GetForwardChanges(Version other)
{
// Return changes from this(inclusive) to other(exclusive).
for (Version node = this; node != other; node = node.next) {
yield return node.change;
}
}
public int MoveOffsetTo(ITextSourceVersion other, int oldOffset, AnchorMovementType movement)
{
int offset = oldOffset;
foreach (var e in GetChangesTo(other)) {
offset = e.GetNewOffset(offset, movement);
}
return offset;
}
}
}
#endif
}

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

@ -1,422 +0,0 @@ @@ -1,422 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Globalization;
using System.Windows.Documents;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Specifies the mode for getting the next caret position.
/// </summary>
public enum CaretPositioningMode
{
/// <summary>
/// Normal positioning (stop after every grapheme)
/// </summary>
Normal,
/// <summary>
/// Stop only on word borders.
/// </summary>
WordBorder,
/// <summary>
/// Stop only at the beginning of words. This is used for Ctrl+Left/Ctrl+Right.
/// </summary>
WordStart,
/// <summary>
/// Stop only at the beginning of words, and anywhere in the middle of symbols.
/// </summary>
WordStartOrSymbol,
/// <summary>
/// Stop only on word borders, and anywhere in the middle of symbols.
/// </summary>
WordBorderOrSymbol,
/// <summary>
/// Stop between every Unicode codepoint, even within the same grapheme.
/// This is used to implement deleting the previous grapheme when Backspace is pressed.
/// </summary>
EveryCodepoint
}
/// <summary>
/// Static helper methods for working with text.
/// </summary>
public static partial class TextUtilities
{
#region GetControlCharacterName
// the names of the first 32 ASCII characters = Unicode C0 block
static readonly string[] c0Table = {
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT",
"LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3",
"DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS",
"RS", "US"
};
// DEL (ASCII 127) and
// the names of the control characters in the C1 block (Unicode 128 to 159)
static readonly string[] delAndC1Table = {
"DEL",
"PAD", "HOP", "BPH", "NBH", "IND", "NEL", "SSA", "ESA", "HTS", "HTJ",
"VTS", "PLD", "PLU", "RI", "SS2", "SS3", "DCS", "PU1", "PU2", "STS",
"CCH", "MW", "SPA", "EPA", "SOS", "SGCI", "SCI", "CSI", "ST", "OSC",
"PM", "APC"
};
/// <summary>
/// Gets the name of the control character.
/// For unknown characters, the unicode codepoint is returned as 4-digit hexadecimal value.
/// </summary>
public static string GetControlCharacterName(char controlCharacter)
{
int num = (int)controlCharacter;
if (num < c0Table.Length)
return c0Table[num];
else if (num >= 127 && num <= 159)
return delAndC1Table[num - 127];
else
return num.ToString("x4", CultureInfo.InvariantCulture);
}
#endregion
#region GetWhitespace
/// <summary>
/// Gets all whitespace (' ' and '\t', but no newlines) after offset.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="offset">The offset where the whitespace starts.</param>
/// <returns>The segment containing the whitespace.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static ISegment GetWhitespaceAfter(ITextSource textSource, int offset)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
int pos;
for (pos = offset; pos < textSource.TextLength; pos++) {
char c = textSource.GetCharAt(pos);
if (c != ' ' && c != '\t')
break;
}
return new SimpleSegment(offset, pos - offset);
}
/// <summary>
/// Gets all whitespace (' ' and '\t', but no newlines) before offset.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="offset">The offset where the whitespace ends.</param>
/// <returns>The segment containing the whitespace.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static ISegment GetWhitespaceBefore(ITextSource textSource, int offset)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
int pos;
for (pos = offset - 1; pos >= 0; pos--) {
char c = textSource.GetCharAt(pos);
if (c != ' ' && c != '\t')
break;
}
pos++; // go back the one character that isn't whitespace
return new SimpleSegment(pos, offset - pos);
}
/// <summary>
/// Gets the leading whitespace segment on the document line.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
Justification = "Parameter cannot be ITextSource because it must belong to the DocumentLine")]
public static ISegment GetLeadingWhitespace(TextDocument document, DocumentLine documentLine)
{
if (documentLine == null)
throw new ArgumentNullException("documentLine");
return GetWhitespaceAfter(document, documentLine.Offset);
}
/// <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'")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
Justification = "Parameter cannot be ITextSource because it must belong to the DocumentLine")]
public static ISegment GetTrailingWhitespace(TextDocument document, DocumentLine documentLine)
{
if (documentLine == null)
throw new ArgumentNullException("documentLine");
ISegment segment = GetWhitespaceBefore(document, documentLine.EndOffset);
// If the whole line consists of whitespace, we consider all of it as leading whitespace,
// so return an empty segment as trailing whitespace.
if (segment.Offset == documentLine.Offset)
return new SimpleSegment(documentLine.EndOffset, 0);
else
return segment;
}
#endregion
#region GetSingleIndentationSegment
/// <summary>
/// Gets a single indentation segment starting at <paramref name="offset"/> - at most one tab
/// or <paramref name="indentationSize"/> spaces.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="offset">The offset where the indentation segment starts.</param>
/// <param name="indentationSize">The size of an indentation unit. See <see cref="TextEditorOptions.IndentationSize"/>.</param>
/// <returns>The indentation segment.
/// If there is no indentation character at the specified <paramref name="offset"/>,
/// an empty segment is returned.</returns>
public static ISegment GetSingleIndentationSegment(ITextSource textSource, int offset, int indentationSize)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
int pos = offset;
while (pos < textSource.TextLength) {
char c = textSource.GetCharAt(pos);
if (c == '\t') {
if (pos == offset)
return new SimpleSegment(offset, 1);
else
break;
} else if (c == ' ') {
if (pos - offset >= indentationSize)
break;
} else {
break;
}
// continue only if c==' ' and (pos-offset)<tabSize
pos++;
}
return new SimpleSegment(offset, pos - offset);
}
#endregion
#region GetCharacterClass
/// <summary>
/// Gets whether the character is whitespace, part of an identifier, or line terminator.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c")]
public static CharacterClass GetCharacterClass(char c)
{
if (c == '\r' || c == '\n')
return CharacterClass.LineTerminator;
if (c == '_')
return CharacterClass.IdentifierPart;
return GetCharacterClass(char.GetUnicodeCategory(c));
}
static CharacterClass GetCharacterClass(char highSurrogate, char lowSurrogate)
{
if (char.IsSurrogatePair(highSurrogate, lowSurrogate)) {
return GetCharacterClass(char.GetUnicodeCategory(highSurrogate.ToString() + lowSurrogate.ToString(), 0));
} else {
// malformed surrogate pair
return CharacterClass.Other;
}
}
static CharacterClass GetCharacterClass(UnicodeCategory c)
{
switch (c) {
case UnicodeCategory.SpaceSeparator:
case UnicodeCategory.LineSeparator:
case UnicodeCategory.ParagraphSeparator:
case UnicodeCategory.Control:
return CharacterClass.Whitespace;
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.TitlecaseLetter:
case UnicodeCategory.ModifierLetter:
case UnicodeCategory.OtherLetter:
case UnicodeCategory.DecimalDigitNumber:
return CharacterClass.IdentifierPart;
case UnicodeCategory.NonSpacingMark:
case UnicodeCategory.SpacingCombiningMark:
case UnicodeCategory.EnclosingMark:
return CharacterClass.CombiningMark;
default:
return CharacterClass.Other;
}
}
#endregion
#region GetNextCaretPosition
/// <summary>
/// Gets the next caret position.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="offset">The start offset inside the text source.</param>
/// <param name="direction">The search direction (forwards or backwards).</param>
/// <param name="mode">The mode for caret positioning.</param>
/// <returns>The offset of the next caret position, or -1 if there is no further caret position
/// in the text source.</returns>
/// <remarks>
/// This method is NOT equivalent to the actual caret movement when using VisualLine.GetNextCaretPosition.
/// In real caret movement, there are additional caret stops at line starts and ends. This method
/// treats linefeeds as simple whitespace.
/// </remarks>
public static int GetNextCaretPosition(ITextSource textSource, int offset, LogicalDirection direction, CaretPositioningMode mode)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
switch (mode) {
case CaretPositioningMode.Normal:
case CaretPositioningMode.EveryCodepoint:
case CaretPositioningMode.WordBorder:
case CaretPositioningMode.WordBorderOrSymbol:
case CaretPositioningMode.WordStart:
case CaretPositioningMode.WordStartOrSymbol:
break; // OK
default:
throw new ArgumentException("Unsupported CaretPositioningMode: " + mode, "mode");
}
if (direction != LogicalDirection.Backward
&& direction != LogicalDirection.Forward)
{
throw new ArgumentException("Invalid LogicalDirection: " + direction, "direction");
}
int textLength = textSource.TextLength;
if (textLength <= 0) {
// empty document? has a normal caret position at 0, though no word borders
if (IsNormal(mode)) {
if (offset > 0 && direction == LogicalDirection.Backward) return 0;
if (offset < 0 && direction == LogicalDirection.Forward) return 0;
}
return -1;
}
while (true) {
int nextPos = (direction == LogicalDirection.Backward) ? offset - 1 : offset + 1;
// return -1 if there is no further caret position in the text source
// we also need this to handle offset values outside the valid range
if (nextPos < 0 || nextPos > textLength)
return -1;
// check if we've run against the textSource borders.
// a 'textSource' usually isn't the whole document, but a single VisualLineElement.
if (nextPos == 0) {
// at the document start, there's only a word border
// if the first character is not whitespace
if (IsNormal(mode) || !char.IsWhiteSpace(textSource.GetCharAt(0)))
return nextPos;
} else if (nextPos == textLength) {
// at the document end, there's never a word start
if (mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol) {
// at the document end, there's only a word border
// if the last character is not whitespace
if (IsNormal(mode) || !char.IsWhiteSpace(textSource.GetCharAt(textLength - 1)))
return nextPos;
}
} else {
char charBefore = textSource.GetCharAt(nextPos - 1);
char charAfter = textSource.GetCharAt(nextPos);
// Don't stop in the middle of a surrogate pair
if (!char.IsSurrogatePair(charBefore, charAfter)) {
CharacterClass classBefore = GetCharacterClass(charBefore);
CharacterClass classAfter = GetCharacterClass(charAfter);
// get correct class for characters outside BMP:
if (char.IsLowSurrogate(charBefore) && nextPos >= 2) {
classBefore = GetCharacterClass(textSource.GetCharAt(nextPos - 2), charBefore);
}
if (char.IsHighSurrogate(charAfter) && nextPos + 1 < textLength) {
classAfter = GetCharacterClass(charAfter, textSource.GetCharAt(nextPos + 1));
}
if (StopBetweenCharacters(mode, classBefore, classAfter)) {
return nextPos;
}
}
}
// we'll have to continue searching...
offset = nextPos;
}
}
static bool IsNormal(CaretPositioningMode mode)
{
return mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint;
}
static bool StopBetweenCharacters(CaretPositioningMode mode, CharacterClass charBefore, CharacterClass charAfter)
{
if (mode == CaretPositioningMode.EveryCodepoint)
return true;
// Don't stop in the middle of a grapheme
if (charAfter == CharacterClass.CombiningMark)
return false;
// Stop after every grapheme in normal mode
if (mode == CaretPositioningMode.Normal)
return true;
if (charBefore == charAfter) {
if (charBefore == CharacterClass.Other &&
(mode == CaretPositioningMode.WordBorderOrSymbol || mode == CaretPositioningMode.WordStartOrSymbol))
{
// With the "OrSymbol" modes, there's a word border and start between any two unknown characters
return true;
}
} else {
// this looks like a possible border
// if we're looking for word starts, check that this is a word start (and not a word end)
// if we're just checking for word borders, accept unconditionally
if (!((mode == CaretPositioningMode.WordStart || mode == CaretPositioningMode.WordStartOrSymbol)
&& (charAfter == CharacterClass.Whitespace || charAfter == CharacterClass.LineTerminator)))
{
return true;
}
}
return false;
}
#endregion
}
/// <summary>
/// Classifies a character as whitespace, line terminator, part of an identifier, or other.
/// </summary>
public enum CharacterClass
{
/// <summary>
/// The character is not whitespace, line terminator or part of an identifier.
/// </summary>
Other,
/// <summary>
/// The character is whitespace (but not line terminator).
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
Whitespace,
/// <summary>
/// The character can be part of an identifier (Letter, digit or underscore).
/// </summary>
IdentifierPart,
/// <summary>
/// The character is line terminator (\r or \n).
/// </summary>
LineTerminator,
/// <summary>
/// The character is a unicode combining mark that modifies the previous character.
/// Corresponds to the Unicode designations "Mn", "Mc" and "Me".
/// </summary>
CombiningMark
}
}

76
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoOperationGroup.cs

@ -1,76 +0,0 @@ @@ -1,76 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// This class stacks the last x operations from the undostack and makes
/// one undo/redo operation from it.
/// </summary>
sealed class UndoOperationGroup : IUndoableOperationWithContext
{
IUndoableOperation[] undolist;
public UndoOperationGroup(Deque<IUndoableOperation> stack, int numops)
{
if (stack == null) {
throw new ArgumentNullException("stack");
}
Debug.Assert(numops > 0 , "UndoOperationGroup : numops should be > 0");
Debug.Assert(numops <= stack.Count);
undolist = new IUndoableOperation[numops];
for (int i = 0; i < numops; ++i) {
undolist[i] = stack.PopBack();
}
}
public void Undo()
{
for (int i = 0; i < undolist.Length; ++i) {
undolist[i].Undo();
}
}
public void Undo(UndoStack stack)
{
for (int i = 0; i < undolist.Length; ++i) {
stack.RunUndo(undolist[i]);
}
}
public void Redo()
{
for (int i = undolist.Length - 1; i >= 0; --i) {
undolist[i].Redo();
}
}
public void Redo(UndoStack stack)
{
for (int i = undolist.Length - 1; i >= 0; --i) {
stack.RunRedo(undolist[i]);
}
}
}
}

458
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs

@ -1,458 +0,0 @@ @@ -1,458 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Undo stack implementation.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
public sealed class UndoStack : INotifyPropertyChanged
{
/// undo stack is listening for changes
internal const int StateListen = 0;
/// undo stack is reverting/repeating a set of changes
internal const int StatePlayback = 1;
// undo stack is reverting/repeating a set of changes and modifies the document to do this
internal const int StatePlaybackModifyDocument = 2;
/// state is used for checking that noone but the UndoStack performs changes
/// during Undo events
internal int state = StateListen;
Deque<IUndoableOperation> undostack = new Deque<IUndoableOperation>();
Deque<IUndoableOperation> redostack = new Deque<IUndoableOperation>();
int sizeLimit = int.MaxValue;
int undoGroupDepth;
int actionCountInUndoGroup;
int optionalActionCount;
object lastGroupDescriptor;
bool allowContinue;
#region IsOriginalFile implementation
// implements feature request SD2-784 - File still considered dirty after undoing all changes
/// <summary>
/// Number of times undo must be executed until the original state is reached.
/// Negative: number of times redo must be executed until the original state is reached.
/// Special case: int.MinValue == original state is unreachable
/// </summary>
int elementsOnUndoUntilOriginalFile;
bool isOriginalFile = true;
/// <summary>
/// Gets whether the document is currently in its original state (no modifications).
/// </summary>
public bool IsOriginalFile {
get { return isOriginalFile; }
}
void RecalcIsOriginalFile()
{
bool newIsOriginalFile = (elementsOnUndoUntilOriginalFile == 0);
if (newIsOriginalFile != isOriginalFile) {
isOriginalFile = newIsOriginalFile;
NotifyPropertyChanged("IsOriginalFile");
}
}
/// <summary>
/// Marks the current state as original. Discards any previous "original" markers.
/// </summary>
public void MarkAsOriginalFile()
{
elementsOnUndoUntilOriginalFile = 0;
RecalcIsOriginalFile();
}
/// <summary>
/// Discards the current "original" marker.
/// </summary>
public void DiscardOriginalFileMarker()
{
elementsOnUndoUntilOriginalFile = int.MinValue;
RecalcIsOriginalFile();
}
void FileModified(int newElementsOnUndoStack)
{
if (elementsOnUndoUntilOriginalFile == int.MinValue)
return;
elementsOnUndoUntilOriginalFile += newElementsOnUndoStack;
if (elementsOnUndoUntilOriginalFile > undostack.Count)
elementsOnUndoUntilOriginalFile = int.MinValue;
// don't call RecalcIsOriginalFile(): wait until end of undo group
}
#endregion
/// <summary>
/// Gets if the undo stack currently accepts changes.
/// Is false while an undo action is running.
/// </summary>
public bool AcceptChanges {
get { return state == StateListen; }
}
/// <summary>
/// Gets if there are actions on the undo stack.
/// Use the PropertyChanged event to listen to changes of this property.
/// </summary>
public bool CanUndo {
get { return undostack.Count > 0; }
}
/// <summary>
/// Gets if there are actions on the redo stack.
/// Use the PropertyChanged event to listen to changes of this property.
/// </summary>
public bool CanRedo {
get { return redostack.Count > 0; }
}
/// <summary>
/// Gets/Sets the limit on the number of items on the undo stack.
/// </summary>
/// <remarks>The size limit is enforced only on the number of stored top-level undo groups.
/// Elements within undo groups do not count towards the size limit.</remarks>
public int SizeLimit {
get { return sizeLimit; }
set {
if (value < 0)
ThrowUtil.CheckNotNegative(value, "value");
if (sizeLimit != value) {
sizeLimit = value;
NotifyPropertyChanged("SizeLimit");
if (undoGroupDepth == 0)
EnforceSizeLimit();
}
}
}
void EnforceSizeLimit()
{
Debug.Assert(undoGroupDepth == 0);
while (undostack.Count > sizeLimit)
undostack.PopFront();
while (redostack.Count > sizeLimit)
redostack.PopFront();
}
/// <summary>
/// If an undo group is open, gets the group descriptor of the current top-level
/// undo group.
/// If no undo group is open, gets the group descriptor from the previous undo group.
/// </summary>
/// <remarks>The group descriptor can be used to join adjacent undo groups:
/// use a group descriptor to mark your changes, and on the second action,
/// compare LastGroupDescriptor and use <see cref="StartContinuedUndoGroup"/> if you
/// want to join the undo groups.</remarks>
public object LastGroupDescriptor {
get { return lastGroupDescriptor; }
}
/// <summary>
/// Starts grouping changes.
/// Maintains a counter so that nested calls are possible.
/// </summary>
public void StartUndoGroup()
{
StartUndoGroup(null);
}
/// <summary>
/// Starts grouping changes.
/// Maintains a counter so that nested calls are possible.
/// </summary>
/// <param name="groupDescriptor">An object that is stored with the undo group.
/// If this is not a top-level undo group, the parameter is ignored.</param>
public void StartUndoGroup(object groupDescriptor)
{
if (undoGroupDepth == 0) {
actionCountInUndoGroup = 0;
optionalActionCount = 0;
lastGroupDescriptor = groupDescriptor;
}
undoGroupDepth++;
//Util.LoggingService.Debug("Open undo group (new depth=" + undoGroupDepth + ")");
}
/// <summary>
/// Starts grouping changes, continuing with the previously closed undo group if possible.
/// Maintains a counter so that nested calls are possible.
/// If the call to StartContinuedUndoGroup is a nested call, it behaves exactly
/// as <see cref="StartUndoGroup()"/>, only top-level calls can continue existing undo groups.
/// </summary>
/// <param name="groupDescriptor">An object that is stored with the undo group.
/// If this is not a top-level undo group, the parameter is ignored.</param>
public void StartContinuedUndoGroup(object groupDescriptor = null)
{
if (undoGroupDepth == 0) {
actionCountInUndoGroup = (allowContinue && undostack.Count > 0) ? 1 : 0;
optionalActionCount = 0;
lastGroupDescriptor = groupDescriptor;
}
undoGroupDepth++;
//Util.LoggingService.Debug("Continue undo group (new depth=" + undoGroupDepth + ")");
}
/// <summary>
/// Stops grouping changes.
/// </summary>
public void EndUndoGroup()
{
if (undoGroupDepth == 0) throw new InvalidOperationException("There are no open undo groups");
undoGroupDepth--;
//Util.LoggingService.Debug("Close undo group (new depth=" + undoGroupDepth + ")");
if (undoGroupDepth == 0) {
Debug.Assert(state == StateListen || actionCountInUndoGroup == 0);
allowContinue = true;
if (actionCountInUndoGroup == optionalActionCount) {
// only optional actions: don't store them
for (int i = 0; i < optionalActionCount; i++) {
undostack.PopBack();
}
allowContinue = false;
} else if (actionCountInUndoGroup > 1) {
// combine all actions within the group into a single grouped action
undostack.PushBack(new UndoOperationGroup(undostack, actionCountInUndoGroup));
FileModified(-actionCountInUndoGroup + 1 + optionalActionCount);
}
//if (state == StateListen) {
EnforceSizeLimit();
RecalcIsOriginalFile(); // can raise event
//}
}
}
/// <summary>
/// Throws an InvalidOperationException if an undo group is current open.
/// </summary>
void ThrowIfUndoGroupOpen()
{
if (undoGroupDepth != 0) {
undoGroupDepth = 0;
throw new InvalidOperationException("No undo group should be open at this point");
}
if (state != StateListen) {
throw new InvalidOperationException("This method cannot be called while an undo operation is being performed");
}
}
List<TextDocument> affectedDocuments;
internal void RegisterAffectedDocument(TextDocument document)
{
if (affectedDocuments == null)
affectedDocuments = new List<TextDocument>();
if (!affectedDocuments.Contains(document)) {
affectedDocuments.Add(document);
document.BeginUpdate();
}
}
void CallEndUpdateOnAffectedDocuments()
{
if (affectedDocuments != null) {
foreach (TextDocument doc in affectedDocuments) {
doc.EndUpdate();
}
affectedDocuments = null;
}
}
/// <summary>
/// Call this method to undo the last operation on the stack
/// </summary>
public void Undo()
{
ThrowIfUndoGroupOpen();
if (undostack.Count > 0) {
// disallow continuing undo groups after undo operation
lastGroupDescriptor = null; allowContinue = false;
// fetch operation to undo and move it to redo stack
IUndoableOperation uedit = undostack.PopBack();
redostack.PushBack(uedit);
state = StatePlayback;
try {
RunUndo(uedit);
} finally {
state = StateListen;
FileModified(-1);
CallEndUpdateOnAffectedDocuments();
}
RecalcIsOriginalFile();
if (undostack.Count == 0)
NotifyPropertyChanged("CanUndo");
if (redostack.Count == 1)
NotifyPropertyChanged("CanRedo");
}
}
internal void RunUndo(IUndoableOperation op)
{
IUndoableOperationWithContext opWithCtx = op as IUndoableOperationWithContext;
if (opWithCtx != null)
opWithCtx.Undo(this);
else
op.Undo();
}
/// <summary>
/// Call this method to redo the last undone operation
/// </summary>
public void Redo()
{
ThrowIfUndoGroupOpen();
if (redostack.Count > 0) {
lastGroupDescriptor = null; allowContinue = false;
IUndoableOperation uedit = redostack.PopBack();
undostack.PushBack(uedit);
state = StatePlayback;
try {
RunRedo(uedit);
} finally {
state = StateListen;
FileModified(1);
CallEndUpdateOnAffectedDocuments();
}
RecalcIsOriginalFile();
if (redostack.Count == 0)
NotifyPropertyChanged("CanRedo");
if (undostack.Count == 1)
NotifyPropertyChanged("CanUndo");
}
}
internal void RunRedo(IUndoableOperation op)
{
IUndoableOperationWithContext opWithCtx = op as IUndoableOperationWithContext;
if (opWithCtx != null)
opWithCtx.Redo(this);
else
op.Redo();
}
/// <summary>
/// Call this method to push an UndoableOperation on the undostack.
/// The redostack will be cleared if you use this method.
/// </summary>
public void Push(IUndoableOperation operation)
{
Push(operation, false);
}
/// <summary>
/// Call this method to push an UndoableOperation on the undostack.
/// However, the operation will be only stored if the undo group contains a
/// non-optional operation.
/// Use this method to store the caret position/selection on the undo stack to
/// prevent having only actions that affect only the caret and not the document.
/// </summary>
public void PushOptional(IUndoableOperation operation)
{
if (undoGroupDepth == 0)
throw new InvalidOperationException("Cannot use PushOptional outside of undo group");
Push(operation, true);
}
void Push(IUndoableOperation operation, bool isOptional)
{
if (operation == null) {
throw new ArgumentNullException("operation");
}
if (state == StateListen && sizeLimit > 0) {
bool wasEmpty = undostack.Count == 0;
bool needsUndoGroup = undoGroupDepth == 0;
if (needsUndoGroup) StartUndoGroup();
undostack.PushBack(operation);
actionCountInUndoGroup++;
if (isOptional)
optionalActionCount++;
else
FileModified(1);
if (needsUndoGroup) EndUndoGroup();
if (wasEmpty)
NotifyPropertyChanged("CanUndo");
ClearRedoStack();
}
}
/// <summary>
/// Call this method, if you want to clear the redo stack
/// </summary>
public void ClearRedoStack()
{
if (redostack.Count != 0) {
redostack.Clear();
NotifyPropertyChanged("CanRedo");
// if the "original file" marker is on the redo stack: remove it
if (elementsOnUndoUntilOriginalFile < 0)
elementsOnUndoUntilOriginalFile = int.MinValue;
}
}
/// <summary>
/// Clears both the undo and redo stack.
/// </summary>
public void ClearAll()
{
ThrowIfUndoGroupOpen();
actionCountInUndoGroup = 0;
optionalActionCount = 0;
if (undostack.Count != 0) {
lastGroupDescriptor = null;
allowContinue = false;
undostack.Clear();
NotifyPropertyChanged("CanUndo");
}
ClearRedoStack();
}
internal void Push(TextDocument document, DocumentChangeEventArgs e)
{
if (state == StatePlayback)
throw new InvalidOperationException("Document changes during undo/redo operations are not allowed.");
if (state == StatePlaybackModifyDocument)
state = StatePlayback; // allow only 1 change per expected modification
else
Push(new DocumentChangeOperation(document, e));
}
/// <summary>
/// Is raised when a property (CanUndo, CanRedo) changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

109
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/WeakLineTracker.cs

@ -1,109 +0,0 @@ @@ -1,109 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Allows registering a line tracker on a TextDocument using a weak reference from the document to the line tracker.
/// </summary>
public sealed class WeakLineTracker : ILineTracker
{
TextDocument textDocument;
WeakReference targetObject;
private WeakLineTracker(TextDocument textDocument, ILineTracker targetTracker)
{
this.textDocument = textDocument;
this.targetObject = new WeakReference(targetTracker);
}
/// <summary>
/// Registers the <paramref name="targetTracker"/> as line tracker for the <paramref name="textDocument"/>.
/// A weak reference to the target tracker will be used, and the WeakLineTracker will deregister itself
/// when the target tracker is garbage collected.
/// </summary>
public static WeakLineTracker Register(TextDocument textDocument, ILineTracker targetTracker)
{
if (textDocument == null)
throw new ArgumentNullException("textDocument");
if (targetTracker == null)
throw new ArgumentNullException("targetTracker");
WeakLineTracker wlt = new WeakLineTracker(textDocument, targetTracker);
textDocument.LineTrackers.Add(wlt);
return wlt;
}
/// <summary>
/// Deregisters the weak line tracker.
/// </summary>
public void Deregister()
{
if (textDocument != null) {
textDocument.LineTrackers.Remove(this);
textDocument = null;
}
}
void ILineTracker.BeforeRemoveLine(DocumentLine line)
{
ILineTracker targetTracker = targetObject.Target as ILineTracker;
if (targetTracker != null)
targetTracker.BeforeRemoveLine(line);
else
Deregister();
}
void ILineTracker.SetLineLength(DocumentLine line, int newTotalLength)
{
ILineTracker targetTracker = targetObject.Target as ILineTracker;
if (targetTracker != null)
targetTracker.SetLineLength(line, newTotalLength);
else
Deregister();
}
void ILineTracker.LineInserted(DocumentLine insertionPos, DocumentLine newLine)
{
ILineTracker targetTracker = targetObject.Target as ILineTracker;
if (targetTracker != null)
targetTracker.LineInserted(insertionPos, newLine);
else
Deregister();
}
void ILineTracker.RebuildDocument()
{
ILineTracker targetTracker = targetObject.Target as ILineTracker;
if (targetTracker != null)
targetTracker.RebuildDocument();
else
Deregister();
}
void ILineTracker.ChangeComplete(DocumentChangeEventArgs e)
{
ILineTracker targetTracker = targetObject.Target as ILineTracker;
if (targetTracker != null)
targetTracker.ChangeComplete(e);
else
Deregister();
}
}
}

117
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/AbstractMargin.cs

@ -1,117 +0,0 @@ @@ -1,117 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Windows;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Base class for margins.
/// Margins don't have to derive from this class, it just helps maintaining a reference to the TextView
/// and the TextDocument.
/// AbstractMargin derives from FrameworkElement, so if you don't want to handle visual children and rendering
/// on your own, choose another base class for your margin!
/// </summary>
public abstract class AbstractMargin : FrameworkElement, ITextViewConnect
{
/// <summary>
/// TextView property.
/// </summary>
public static readonly DependencyProperty TextViewProperty =
DependencyProperty.Register("TextView", typeof(TextView), typeof(AbstractMargin),
new FrameworkPropertyMetadata(OnTextViewChanged));
/// <summary>
/// Gets/sets the text view for which line numbers are displayed.
/// </summary>
/// <remarks>Adding a margin to <see cref="TextArea.LeftMargins"/> will automatically set this property to the text area's TextView.</remarks>
public TextView TextView {
get { return (TextView)GetValue(TextViewProperty); }
set { SetValue(TextViewProperty, value); }
}
static void OnTextViewChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
AbstractMargin margin = (AbstractMargin)dp;
margin.wasAutoAddedToTextView = false;
margin.OnTextViewChanged((TextView)e.OldValue, (TextView)e.NewValue);
}
// automatically set/unset TextView property using ITextViewConnect
bool wasAutoAddedToTextView;
void ITextViewConnect.AddToTextView(TextView textView)
{
if (this.TextView == null) {
this.TextView = textView;
wasAutoAddedToTextView = true;
} else if (this.TextView != textView) {
throw new InvalidOperationException("This margin belongs to a different TextView.");
}
}
void ITextViewConnect.RemoveFromTextView(TextView textView)
{
if (wasAutoAddedToTextView && this.TextView == textView) {
this.TextView = null;
Debug.Assert(!wasAutoAddedToTextView); // setting this.TextView should have unset this flag
}
}
TextDocument document;
/// <summary>
/// Gets the document associated with the margin.
/// </summary>
public TextDocument Document {
get { return document; }
}
/// <summary>
/// Called when the <see cref="TextView"/> is changing.
/// </summary>
protected virtual void OnTextViewChanged(TextView oldTextView, TextView newTextView)
{
if (oldTextView != null) {
oldTextView.DocumentChanged -= TextViewDocumentChanged;
}
if (newTextView != null) {
newTextView.DocumentChanged += TextViewDocumentChanged;
}
TextViewDocumentChanged(null, null);
}
void TextViewDocumentChanged(object sender, EventArgs e)
{
OnDocumentChanged(document, TextView != null ? TextView.Document : null);
}
/// <summary>
/// Called when the <see cref="Document"/> is changing.
/// </summary>
protected virtual void OnDocumentChanged(TextDocument oldDocument, TextDocument newDocument)
{
document = newDocument;
}
}
}

538
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs

@ -1,538 +0,0 @@ @@ -1,538 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
#if NREFACTORY
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Helper class with caret-related methods.
/// </summary>
public sealed class Caret
{
readonly TextArea textArea;
readonly TextView textView;
readonly CaretLayer caretAdorner;
bool visible;
internal Caret(TextArea textArea)
{
this.textArea = textArea;
this.textView = textArea.TextView;
position = new TextViewPosition(1, 1, 0);
caretAdorner = new CaretLayer(textArea);
textView.InsertLayer(caretAdorner, KnownLayer.Caret, LayerInsertionPosition.Replace);
textView.VisualLinesChanged += TextView_VisualLinesChanged;
textView.ScrollOffsetChanged += TextView_ScrollOffsetChanged;
}
internal void UpdateIfVisible()
{
if (visible) {
Show();
}
}
void TextView_VisualLinesChanged(object sender, EventArgs e)
{
if (visible) {
Show();
}
// required because the visual columns might have changed if the
// element generators did something differently than on the last run
// (e.g. a FoldingSection was collapsed)
InvalidateVisualColumn();
}
void TextView_ScrollOffsetChanged(object sender, EventArgs e)
{
if (caretAdorner != null) {
caretAdorner.InvalidateVisual();
}
}
double desiredXPos = double.NaN;
TextViewPosition position;
/// <summary>
/// Gets/Sets the position of the caret.
/// Retrieving this property will validate the visual column (which can be expensive).
/// Use the <see cref="Location"/> property instead if you don't need the visual column.
/// </summary>
public TextViewPosition Position {
get {
ValidateVisualColumn();
return position;
}
set {
if (position != value) {
position = value;
storedCaretOffset = -1;
//Debug.WriteLine("Caret position changing to " + value);
ValidatePosition();
InvalidateVisualColumn();
RaisePositionChanged();
Log("Caret position changed to " + value);
if (visible)
Show();
}
}
}
/// <summary>
/// Gets the caret position without validating it.
/// </summary>
internal TextViewPosition NonValidatedPosition {
get {
return position;
}
}
/// <summary>
/// Gets/Sets the location of the caret.
/// The getter of this property is faster than <see cref="Position"/> because it doesn't have
/// to validate the visual column.
/// </summary>
public TextLocation Location {
get {
return position.Location;
}
set {
this.Position = new TextViewPosition(value);
}
}
/// <summary>
/// Gets/Sets the caret line.
/// </summary>
public int Line {
get { return position.Line; }
set {
this.Position = new TextViewPosition(value, position.Column);
}
}
/// <summary>
/// Gets/Sets the caret column.
/// </summary>
public int Column {
get { return position.Column; }
set {
this.Position = new TextViewPosition(position.Line, value);
}
}
/// <summary>
/// Gets/Sets the caret visual column.
/// </summary>
public int VisualColumn {
get {
ValidateVisualColumn();
return position.VisualColumn;
}
set {
this.Position = new TextViewPosition(position.Line, position.Column, value);
}
}
bool isInVirtualSpace;
/// <summary>
/// Gets whether the caret is in virtual space.
/// </summary>
public bool IsInVirtualSpace {
get {
ValidateVisualColumn();
return isInVirtualSpace;
}
}
int storedCaretOffset;
internal void OnDocumentChanging()
{
storedCaretOffset = this.Offset;
InvalidateVisualColumn();
}
internal void OnDocumentChanged(DocumentChangeEventArgs e)
{
InvalidateVisualColumn();
if (storedCaretOffset >= 0) {
// If the caret is at the end of a selection, we don't expand the selection if something
// is inserted at the end. Thus we also need to keep the caret in front of the insertion.
AnchorMovementType caretMovementType;
if (!textArea.Selection.IsEmpty && storedCaretOffset == textArea.Selection.SurroundingSegment.EndOffset)
caretMovementType = AnchorMovementType.BeforeInsertion;
else
caretMovementType = AnchorMovementType.Default;
int newCaretOffset = e.GetNewOffset(storedCaretOffset, caretMovementType);
TextDocument document = textArea.Document;
if (document != null) {
// keep visual column
this.Position = new TextViewPosition(document.GetLocation(newCaretOffset), position.VisualColumn);
}
}
storedCaretOffset = -1;
}
/// <summary>
/// Gets/Sets the caret offset.
/// Setting the caret offset has the side effect of setting the <see cref="DesiredXPos"/> to NaN.
/// </summary>
public int Offset {
get {
TextDocument document = textArea.Document;
if (document == null) {
return 0;
} else {
return document.GetOffset(position.Location);
}
}
set {
TextDocument document = textArea.Document;
if (document != null) {
this.Position = new TextViewPosition(document.GetLocation(value));
this.DesiredXPos = double.NaN;
}
}
}
/// <summary>
/// Gets/Sets the desired x-position of the caret, in device-independent pixels.
/// This property is NaN if the caret has no desired position.
/// </summary>
public double DesiredXPos {
get { return desiredXPos; }
set { desiredXPos = value; }
}
void ValidatePosition()
{
if (position.Line < 1)
position.Line = 1;
if (position.Column < 1)
position.Column = 1;
if (position.VisualColumn < -1)
position.VisualColumn = -1;
TextDocument document = textArea.Document;
if (document != null) {
if (position.Line > document.LineCount) {
position.Line = document.LineCount;
position.Column = document.GetLineByNumber(position.Line).Length + 1;
position.VisualColumn = -1;
} else {
DocumentLine line = document.GetLineByNumber(position.Line);
if (position.Column > line.Length + 1) {
position.Column = line.Length + 1;
position.VisualColumn = -1;
}
}
}
}
/// <summary>
/// Event raised when the caret position has changed.
/// If the caret position is changed inside a document update (between BeginUpdate/EndUpdate calls),
/// the PositionChanged event is raised only once at the end of the document update.
/// </summary>
public event EventHandler PositionChanged;
bool raisePositionChangedOnUpdateFinished;
void RaisePositionChanged()
{
if (textArea.Document != null && textArea.Document.IsInUpdate) {
raisePositionChangedOnUpdateFinished = true;
} else {
if (PositionChanged != null) {
PositionChanged(this, EventArgs.Empty);
}
}
}
internal void OnDocumentUpdateFinished()
{
if (raisePositionChangedOnUpdateFinished) {
if (PositionChanged != null) {
PositionChanged(this, EventArgs.Empty);
}
}
}
bool visualColumnValid;
void ValidateVisualColumn()
{
if (!visualColumnValid) {
TextDocument document = textArea.Document;
if (document != null) {
Debug.WriteLine("Explicit validation of caret column");
var documentLine = document.GetLineByNumber(position.Line);
RevalidateVisualColumn(textView.GetOrConstructVisualLine(documentLine));
}
}
}
void InvalidateVisualColumn()
{
visualColumnValid = false;
}
/// <summary>
/// Validates the visual column of the caret using the specified visual line.
/// The visual line must contain the caret offset.
/// </summary>
void RevalidateVisualColumn(VisualLine visualLine)
{
if (visualLine == null)
throw new ArgumentNullException("visualLine");
// mark column as validated
visualColumnValid = true;
int caretOffset = textView.Document.GetOffset(position.Location);
int firstDocumentLineOffset = visualLine.FirstDocumentLine.Offset;
position.VisualColumn = visualLine.ValidateVisualColumn(position, textArea.Selection.EnableVirtualSpace);
// search possible caret positions
int newVisualColumnForwards = visualLine.GetNextCaretPosition(position.VisualColumn - 1, LogicalDirection.Forward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace);
// If position.VisualColumn was valid, we're done with validation.
if (newVisualColumnForwards != position.VisualColumn) {
// also search backwards so that we can pick the better match
int newVisualColumnBackwards = visualLine.GetNextCaretPosition(position.VisualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace);
if (newVisualColumnForwards < 0 && newVisualColumnBackwards < 0)
throw ThrowUtil.NoValidCaretPosition();
// determine offsets for new visual column positions
int newOffsetForwards;
if (newVisualColumnForwards >= 0)
newOffsetForwards = visualLine.GetRelativeOffset(newVisualColumnForwards) + firstDocumentLineOffset;
else
newOffsetForwards = -1;
int newOffsetBackwards;
if (newVisualColumnBackwards >= 0)
newOffsetBackwards = visualLine.GetRelativeOffset(newVisualColumnBackwards) + firstDocumentLineOffset;
else
newOffsetBackwards = -1;
int newVisualColumn, newOffset;
// if there's only one valid position, use it
if (newVisualColumnForwards < 0) {
newVisualColumn = newVisualColumnBackwards;
newOffset = newOffsetBackwards;
} else if (newVisualColumnBackwards < 0) {
newVisualColumn = newVisualColumnForwards;
newOffset = newOffsetForwards;
} else {
// two valid positions: find the better match
if (Math.Abs(newOffsetBackwards - caretOffset) < Math.Abs(newOffsetForwards - caretOffset)) {
// backwards is better
newVisualColumn = newVisualColumnBackwards;
newOffset = newOffsetBackwards;
} else {
// forwards is better
newVisualColumn = newVisualColumnForwards;
newOffset = newOffsetForwards;
}
}
this.Position = new TextViewPosition(textView.Document.GetLocation(newOffset), newVisualColumn);
}
isInVirtualSpace = (position.VisualColumn > visualLine.VisualLength);
}
Rect CalcCaretRectangle(VisualLine visualLine)
{
if (!visualColumnValid) {
RevalidateVisualColumn(visualLine);
}
TextLine textLine = visualLine.GetTextLine(position.VisualColumn, position.IsAtEndOfLine);
double xPos = visualLine.GetTextLineVisualXPosition(textLine, position.VisualColumn);
double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop);
double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom);
return new Rect(xPos,
lineTop,
SystemParameters.CaretWidth,
lineBottom - lineTop);
}
Rect CalcCaretOverstrikeRectangle(VisualLine visualLine)
{
if (!visualColumnValid) {
RevalidateVisualColumn(visualLine);
}
int currentPos = position.VisualColumn;
// The text being overwritten in overstrike mode is everything up to the next normal caret stop
int nextPos = visualLine.GetNextCaretPosition(currentPos, LogicalDirection.Forward, CaretPositioningMode.Normal, true);
TextLine textLine = visualLine.GetTextLine(currentPos);
Rect r;
if (currentPos < visualLine.VisualLength) {
// If the caret is within the text, use GetTextBounds() for the text being overwritten.
// This is necessary to ensure the rectangle is calculated correctly in bidirectional text.
var textBounds = textLine.GetTextBounds(currentPos, nextPos - currentPos)[0];
r = textBounds.Rectangle;
r.Y += visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineTop);
} else {
// If the caret is at the end of the line (or in virtual space),
// use the visual X position of currentPos and nextPos (one or more of which will be in virtual space)
double xPos = visualLine.GetTextLineVisualXPosition(textLine, currentPos);
double xPos2 = visualLine.GetTextLineVisualXPosition(textLine, nextPos);
double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop);
double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom);
r = new Rect(xPos, lineTop, xPos2 - xPos, lineBottom - lineTop);
}
// If the caret is too small (e.g. in front of zero-width character), ensure it's still visible
if (r.Width < SystemParameters.CaretWidth)
r.Width = SystemParameters.CaretWidth;
return r;
}
/// <summary>
/// Returns the caret rectangle. The coordinate system is in device-independent pixels from the top of the document.
/// </summary>
public Rect CalculateCaretRectangle()
{
if (textView != null && textView.Document != null) {
VisualLine visualLine = textView.GetOrConstructVisualLine(textView.Document.GetLineByNumber(position.Line));
return textArea.OverstrikeMode ? CalcCaretOverstrikeRectangle(visualLine) : CalcCaretRectangle(visualLine);
} else {
return Rect.Empty;
}
}
/// <summary>
/// Minimum distance of the caret to the view border.
/// </summary>
internal const double MinimumDistanceToViewBorder = 30;
/// <summary>
/// Scrolls the text view so that the caret is visible.
/// </summary>
public void BringCaretToView()
{
BringCaretToView(MinimumDistanceToViewBorder);
}
internal void BringCaretToView(double border)
{
Rect caretRectangle = CalculateCaretRectangle();
if (!caretRectangle.IsEmpty) {
caretRectangle.Inflate(border, border);
textView.MakeVisible(caretRectangle);
}
}
/// <summary>
/// Makes the caret visible and updates its on-screen position.
/// </summary>
public void Show()
{
Log("Caret.Show()");
visible = true;
if (!showScheduled) {
showScheduled = true;
textArea.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(ShowInternal));
}
}
bool showScheduled;
bool hasWin32Caret;
void ShowInternal()
{
showScheduled = false;
// if show was scheduled but caret hidden in the meantime
if (!visible)
return;
if (caretAdorner != null && textView != null) {
VisualLine visualLine = textView.GetVisualLine(position.Line);
if (visualLine != null) {
Rect caretRect = this.textArea.OverstrikeMode ? CalcCaretOverstrikeRectangle(visualLine) : CalcCaretRectangle(visualLine);
// Create Win32 caret so that Windows knows where our managed caret is. This is necessary for
// features like 'Follow text editing' in the Windows Magnifier.
if (!hasWin32Caret) {
hasWin32Caret = Win32.CreateCaret(textView, caretRect.Size);
}
if (hasWin32Caret) {
Win32.SetCaretPosition(textView, caretRect.Location - textView.ScrollOffset);
}
caretAdorner.Show(caretRect);
textArea.ime.UpdateCompositionWindow();
} else {
caretAdorner.Hide();
}
}
}
/// <summary>
/// Makes the caret invisible.
/// </summary>
public void Hide()
{
Log("Caret.Hide()");
visible = false;
if (hasWin32Caret) {
Win32.DestroyCaret();
hasWin32Caret = false;
}
if (caretAdorner != null) {
caretAdorner.Hide();
}
}
[Conditional("DEBUG")]
static void Log(string text)
{
// commented out to make debug output less noisy - add back if there are any problems with the caret
//Debug.WriteLine(text);
}
/// <summary>
/// Gets/Sets the color of the caret.
/// </summary>
public Brush CaretBrush {
get { return caretAdorner.CaretBrush; }
set { caretAdorner.CaretBrush = value; }
}
}
}

115
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretLayer.cs

@ -1,115 +0,0 @@ @@ -1,115 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Editing
{
sealed class CaretLayer : Layer
{
TextArea textArea;
bool isVisible;
Rect caretRectangle;
DispatcherTimer caretBlinkTimer = new DispatcherTimer();
bool blink;
public CaretLayer(TextArea textArea) : base(textArea.TextView, KnownLayer.Caret)
{
this.textArea = textArea;
this.IsHitTestVisible = false;
caretBlinkTimer.Tick += new EventHandler(caretBlinkTimer_Tick);
}
void caretBlinkTimer_Tick(object sender, EventArgs e)
{
blink = !blink;
InvalidateVisual();
}
public void Show(Rect caretRectangle)
{
this.caretRectangle = caretRectangle;
this.isVisible = true;
StartBlinkAnimation();
InvalidateVisual();
}
public void Hide()
{
if (isVisible) {
isVisible = false;
StopBlinkAnimation();
InvalidateVisual();
}
}
void StartBlinkAnimation()
{
TimeSpan blinkTime = Win32.CaretBlinkTime;
blink = true; // the caret should visible initially
// This is important if blinking is disabled (system reports a negative blinkTime)
if (blinkTime.TotalMilliseconds > 0) {
caretBlinkTimer.Interval = blinkTime;
caretBlinkTimer.Start();
}
}
void StopBlinkAnimation()
{
caretBlinkTimer.Stop();
}
internal Brush CaretBrush;
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (isVisible && blink) {
Brush caretBrush = this.CaretBrush;
if (caretBrush == null)
caretBrush = (Brush)textView.GetValue(TextBlock.ForegroundProperty);
if (this.textArea.OverstrikeMode) {
SolidColorBrush scBrush = caretBrush as SolidColorBrush;
if (scBrush != null) {
Color brushColor = scBrush.Color;
Color newColor = Color.FromArgb(100, brushColor.R, brushColor.G, brushColor.B);
caretBrush = new SolidColorBrush(newColor);
caretBrush.Freeze();
}
}
Rect r = new Rect(caretRectangle.X - textView.HorizontalOffset,
caretRectangle.Y - textView.VerticalOffset,
caretRectangle.Width,
caretRectangle.Height);
drawingContext.DrawRectangle(caretBrush, null, PixelSnapHelpers.Round(r, PixelSnapHelpers.GetPixelSize(this)));
}
}
}
}

392
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs

@ -1,392 +0,0 @@ @@ -1,392 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Editing
{
enum CaretMovementType
{
None,
CharLeft,
CharRight,
Backspace,
WordLeft,
WordRight,
LineUp,
LineDown,
PageUp,
PageDown,
LineStart,
LineEnd,
DocumentStart,
DocumentEnd
}
static class CaretNavigationCommandHandler
{
/// <summary>
/// Creates a new <see cref="TextAreaInputHandler"/> for the text area.
/// </summary>
public static TextAreaInputHandler Create(TextArea textArea)
{
TextAreaInputHandler handler = new TextAreaInputHandler(textArea);
handler.CommandBindings.AddRange(CommandBindings);
handler.InputBindings.AddRange(InputBindings);
return handler;
}
static readonly List<CommandBinding> CommandBindings = new List<CommandBinding>();
static readonly List<InputBinding> InputBindings = new List<InputBinding>();
static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler)
{
CommandBindings.Add(new CommandBinding(command, handler));
InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key));
}
static CaretNavigationCommandHandler()
{
const ModifierKeys None = ModifierKeys.None;
const ModifierKeys Ctrl = ModifierKeys.Control;
const ModifierKeys Shift = ModifierKeys.Shift;
const ModifierKeys Alt = ModifierKeys.Alt;
AddBinding(EditingCommands.MoveLeftByCharacter, None, Key.Left, OnMoveCaret(CaretMovementType.CharLeft));
AddBinding(EditingCommands.SelectLeftByCharacter, Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.CharLeft));
AddBinding(RectangleSelection.BoxSelectLeftByCharacter, Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.CharLeft));
AddBinding(EditingCommands.MoveRightByCharacter, None, Key.Right, OnMoveCaret(CaretMovementType.CharRight));
AddBinding(EditingCommands.SelectRightByCharacter, Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.CharRight));
AddBinding(RectangleSelection.BoxSelectRightByCharacter, Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.CharRight));
AddBinding(EditingCommands.MoveLeftByWord, Ctrl, Key.Left, OnMoveCaret(CaretMovementType.WordLeft));
AddBinding(EditingCommands.SelectLeftByWord, Ctrl | Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.WordLeft));
AddBinding(RectangleSelection.BoxSelectLeftByWord, Ctrl | Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.WordLeft));
AddBinding(EditingCommands.MoveRightByWord, Ctrl, Key.Right, OnMoveCaret(CaretMovementType.WordRight));
AddBinding(EditingCommands.SelectRightByWord, Ctrl | Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.WordRight));
AddBinding(RectangleSelection.BoxSelectRightByWord, Ctrl | Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.WordRight));
AddBinding(EditingCommands.MoveUpByLine, None, Key.Up, OnMoveCaret(CaretMovementType.LineUp));
AddBinding(EditingCommands.SelectUpByLine, Shift, Key.Up, OnMoveCaretExtendSelection(CaretMovementType.LineUp));
AddBinding(RectangleSelection.BoxSelectUpByLine, Alt | Shift, Key.Up, OnMoveCaretBoxSelection(CaretMovementType.LineUp));
AddBinding(EditingCommands.MoveDownByLine, None, Key.Down, OnMoveCaret(CaretMovementType.LineDown));
AddBinding(EditingCommands.SelectDownByLine, Shift, Key.Down, OnMoveCaretExtendSelection(CaretMovementType.LineDown));
AddBinding(RectangleSelection.BoxSelectDownByLine, Alt | Shift, Key.Down, OnMoveCaretBoxSelection(CaretMovementType.LineDown));
AddBinding(EditingCommands.MoveDownByPage, None, Key.PageDown, OnMoveCaret(CaretMovementType.PageDown));
AddBinding(EditingCommands.SelectDownByPage, Shift, Key.PageDown, OnMoveCaretExtendSelection(CaretMovementType.PageDown));
AddBinding(EditingCommands.MoveUpByPage, None, Key.PageUp, OnMoveCaret(CaretMovementType.PageUp));
AddBinding(EditingCommands.SelectUpByPage, Shift, Key.PageUp, OnMoveCaretExtendSelection(CaretMovementType.PageUp));
AddBinding(EditingCommands.MoveToLineStart, None, Key.Home, OnMoveCaret(CaretMovementType.LineStart));
AddBinding(EditingCommands.SelectToLineStart, Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.LineStart));
AddBinding(RectangleSelection.BoxSelectToLineStart, Alt | Shift, Key.Home, OnMoveCaretBoxSelection(CaretMovementType.LineStart));
AddBinding(EditingCommands.MoveToLineEnd, None, Key.End, OnMoveCaret(CaretMovementType.LineEnd));
AddBinding(EditingCommands.SelectToLineEnd, Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.LineEnd));
AddBinding(RectangleSelection.BoxSelectToLineEnd, Alt | Shift, Key.End, OnMoveCaretBoxSelection(CaretMovementType.LineEnd));
AddBinding(EditingCommands.MoveToDocumentStart, Ctrl, Key.Home, OnMoveCaret(CaretMovementType.DocumentStart));
AddBinding(EditingCommands.SelectToDocumentStart, Ctrl | Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.DocumentStart));
AddBinding(EditingCommands.MoveToDocumentEnd, Ctrl, Key.End, OnMoveCaret(CaretMovementType.DocumentEnd));
AddBinding(EditingCommands.SelectToDocumentEnd, Ctrl | Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.DocumentEnd));
CommandBindings.Add(new CommandBinding(ApplicationCommands.SelectAll, OnSelectAll));
TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings);
}
static void OnSelectAll(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
args.Handled = true;
textArea.Caret.Offset = textArea.Document.TextLength;
textArea.Selection = SimpleSelection.Create(textArea, 0, textArea.Document.TextLength);
}
}
static TextArea GetTextArea(object target)
{
return target as TextArea;
}
static ExecutedRoutedEventHandler OnMoveCaret(CaretMovementType direction)
{
return (target, args) => {
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
args.Handled = true;
textArea.ClearSelection();
MoveCaret(textArea, direction);
textArea.Caret.BringCaretToView();
}
};
}
static ExecutedRoutedEventHandler OnMoveCaretExtendSelection(CaretMovementType direction)
{
return (target, args) => {
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
args.Handled = true;
TextViewPosition oldPosition = textArea.Caret.Position;
MoveCaret(textArea, direction);
textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
textArea.Caret.BringCaretToView();
}
};
}
static ExecutedRoutedEventHandler OnMoveCaretBoxSelection(CaretMovementType direction)
{
return (target, args) => {
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
args.Handled = true;
// First, convert the selection into a rectangle selection
// (this is required so that virtual space gets enabled for the caret movement)
if (textArea.Options.EnableRectangularSelection && !(textArea.Selection is RectangleSelection)) {
if (textArea.Selection.IsEmpty) {
textArea.Selection = new RectangleSelection(textArea, textArea.Caret.Position, textArea.Caret.Position);
} else {
// Convert normal selection to rectangle selection
textArea.Selection = new RectangleSelection(textArea, textArea.Selection.StartPosition, textArea.Caret.Position);
}
}
// Now move the caret and extend the selection
TextViewPosition oldPosition = textArea.Caret.Position;
MoveCaret(textArea, direction);
textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
textArea.Caret.BringCaretToView();
}
};
}
#region Caret movement
internal static void MoveCaret(TextArea textArea, CaretMovementType direction)
{
double desiredXPos = textArea.Caret.DesiredXPos;
textArea.Caret.Position = GetNewCaretPosition(textArea.TextView, textArea.Caret.Position, direction, textArea.Selection.EnableVirtualSpace, ref desiredXPos);
textArea.Caret.DesiredXPos = desiredXPos;
}
internal static TextViewPosition GetNewCaretPosition(TextView textView, TextViewPosition caretPosition, CaretMovementType direction, bool enableVirtualSpace, ref double desiredXPos)
{
switch (direction) {
case CaretMovementType.None:
return caretPosition;
case CaretMovementType.DocumentStart:
desiredXPos = double.NaN;
return new TextViewPosition(0, 0);
case CaretMovementType.DocumentEnd:
desiredXPos = double.NaN;
return new TextViewPosition(textView.Document.GetLocation(textView.Document.TextLength));
}
DocumentLine caretLine = textView.Document.GetLineByNumber(caretPosition.Line);
VisualLine visualLine = textView.GetOrConstructVisualLine(caretLine);
TextLine textLine = visualLine.GetTextLine(caretPosition.VisualColumn, caretPosition.IsAtEndOfLine);
switch (direction) {
case CaretMovementType.CharLeft:
desiredXPos = double.NaN;
return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.Normal, enableVirtualSpace);
case CaretMovementType.Backspace:
desiredXPos = double.NaN;
return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.EveryCodepoint, enableVirtualSpace);
case CaretMovementType.CharRight:
desiredXPos = double.NaN;
return GetNextCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.Normal, enableVirtualSpace);
case CaretMovementType.WordLeft:
desiredXPos = double.NaN;
return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.WordStart, enableVirtualSpace);
case CaretMovementType.WordRight:
desiredXPos = double.NaN;
return GetNextCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.WordStart, enableVirtualSpace);
case CaretMovementType.LineUp:
case CaretMovementType.LineDown:
case CaretMovementType.PageUp:
case CaretMovementType.PageDown:
return GetUpDownCaretPosition(textView, caretPosition, direction, visualLine, textLine, enableVirtualSpace, ref desiredXPos);
case CaretMovementType.LineStart:
desiredXPos = double.NaN;
return GetStartOfLineCaretPosition(caretPosition.VisualColumn, visualLine, textLine, enableVirtualSpace);
case CaretMovementType.LineEnd:
desiredXPos = double.NaN;
return GetEndOfLineCaretPosition(visualLine, textLine);
default:
throw new NotSupportedException(direction.ToString());
}
}
#endregion
#region Home/End
static TextViewPosition GetStartOfLineCaretPosition(int oldVC, VisualLine visualLine, TextLine textLine, bool enableVirtualSpace)
{
int newVC = visualLine.GetTextLineVisualStartColumn(textLine);
if (newVC == 0)
newVC = visualLine.GetNextCaretPosition(newVC - 1, LogicalDirection.Forward, CaretPositioningMode.WordStart, enableVirtualSpace);
if (newVC < 0)
throw ThrowUtil.NoValidCaretPosition();
// when the caret is already at the start of the text, jump to start before whitespace
if (newVC == oldVC)
newVC = 0;
return visualLine.GetTextViewPosition(newVC);
}
static TextViewPosition GetEndOfLineCaretPosition(VisualLine visualLine, TextLine textLine)
{
int newVC = visualLine.GetTextLineVisualStartColumn(textLine) + textLine.Length - textLine.TrailingWhitespaceLength;
TextViewPosition pos = visualLine.GetTextViewPosition(newVC);
pos.IsAtEndOfLine = true;
return pos;
}
#endregion
#region By-character / By-word movement
static TextViewPosition GetNextCaretPosition(TextView textView, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode, bool enableVirtualSpace)
{
int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode, enableVirtualSpace);
if (pos >= 0) {
return visualLine.GetTextViewPosition(pos);
} else {
// move to start of next line
DocumentLine nextDocumentLine = visualLine.LastDocumentLine.NextLine;
if (nextDocumentLine != null) {
VisualLine nextLine = textView.GetOrConstructVisualLine(nextDocumentLine);
pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode, enableVirtualSpace);
if (pos < 0)
throw ThrowUtil.NoValidCaretPosition();
return nextLine.GetTextViewPosition(pos);
} else {
// at end of document
Debug.Assert(visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength == textView.Document.TextLength);
return new TextViewPosition(textView.Document.GetLocation(textView.Document.TextLength));
}
}
}
static TextViewPosition GetPrevCaretPosition(TextView textView, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode, bool enableVirtualSpace)
{
int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode, enableVirtualSpace);
if (pos >= 0) {
return visualLine.GetTextViewPosition(pos);
} else {
// move to end of previous line
DocumentLine previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine;
if (previousDocumentLine != null) {
VisualLine previousLine = textView.GetOrConstructVisualLine(previousDocumentLine);
pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode, enableVirtualSpace);
if (pos < 0)
throw ThrowUtil.NoValidCaretPosition();
return previousLine.GetTextViewPosition(pos);
} else {
// at start of document
Debug.Assert(visualLine.FirstDocumentLine.Offset == 0);
return new TextViewPosition(0, 0);
}
}
}
#endregion
#region Line+Page up/down
static TextViewPosition GetUpDownCaretPosition(TextView textView, TextViewPosition caretPosition, CaretMovementType direction, VisualLine visualLine, TextLine textLine, bool enableVirtualSpace, ref double xPos)
{
// moving up/down happens using the desired visual X position
if (double.IsNaN(xPos))
xPos = visualLine.GetTextLineVisualXPosition(textLine, caretPosition.VisualColumn);
// now find the TextLine+VisualLine where the caret will end up in
VisualLine targetVisualLine = visualLine;
TextLine targetLine;
int textLineIndex = visualLine.TextLines.IndexOf(textLine);
switch (direction) {
case CaretMovementType.LineUp:
{
// Move up: move to the previous TextLine in the same visual line
// or move to the last TextLine of the previous visual line
int prevLineNumber = visualLine.FirstDocumentLine.LineNumber - 1;
if (textLineIndex > 0) {
targetLine = visualLine.TextLines[textLineIndex - 1];
} else if (prevLineNumber >= 1) {
DocumentLine prevLine = textView.Document.GetLineByNumber(prevLineNumber);
targetVisualLine = textView.GetOrConstructVisualLine(prevLine);
targetLine = targetVisualLine.TextLines[targetVisualLine.TextLines.Count - 1];
} else {
targetLine = null;
}
break;
}
case CaretMovementType.LineDown:
{
// Move down: move to the next TextLine in the same visual line
// or move to the first TextLine of the next visual line
int nextLineNumber = visualLine.LastDocumentLine.LineNumber + 1;
if (textLineIndex < visualLine.TextLines.Count - 1) {
targetLine = visualLine.TextLines[textLineIndex + 1];
} else if (nextLineNumber <= textView.Document.LineCount) {
DocumentLine nextLine = textView.Document.GetLineByNumber(nextLineNumber);
targetVisualLine = textView.GetOrConstructVisualLine(nextLine);
targetLine = targetVisualLine.TextLines[0];
} else {
targetLine = null;
}
break;
}
case CaretMovementType.PageUp:
case CaretMovementType.PageDown:
{
// Page up/down: find the target line using its visual position
double yPos = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineMiddle);
if (direction == CaretMovementType.PageUp)
yPos -= textView.RenderSize.Height;
else
yPos += textView.RenderSize.Height;
DocumentLine newLine = textView.GetDocumentLineByVisualTop(yPos);
targetVisualLine = textView.GetOrConstructVisualLine(newLine);
targetLine = targetVisualLine.GetTextLineByVisualYPosition(yPos);
break;
}
default:
throw new NotSupportedException(direction.ToString());
}
if (targetLine != null) {
double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle);
int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos), enableVirtualSpace);
// prevent wrapping to the next line; TODO: could 'IsAtEnd' help here?
int targetLineStartCol = targetVisualLine.GetTextLineVisualStartColumn(targetLine);
if (newVisualColumn >= targetLineStartCol + targetLine.Length) {
if (newVisualColumn <= targetVisualLine.VisualLength)
newVisualColumn = targetLineStartCol + targetLine.Length - 1;
}
return targetVisualLine.GetTextViewPosition(newVisualColumn);
} else {
return caretPosition;
}
}
#endregion
}
}

48
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretWeakEventHandler.cs

@ -1,48 +0,0 @@ @@ -1,48 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using ICSharpCode.AvalonEdit.Utils;
using System;
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Contains classes for handling weak events on the Caret class.
/// </summary>
public static class CaretWeakEventManager
{
/// <summary>
/// Handles the Caret.PositionChanged event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class PositionChanged : WeakEventManagerBase<PositionChanged, Caret>
{
/// <inheritdoc/>
protected override void StartListening(Caret source)
{
source.PositionChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(Caret source)
{
source.PositionChanged -= DeliverEvent;
}
}
}
}

78
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/DottedLineMargin.cs

@ -1,78 +0,0 @@ @@ -1,78 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Shapes;
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Margin for use with the text area.
/// A vertical dotted line to separate the line numbers from the text view.
/// </summary>
public static class DottedLineMargin
{
static readonly object tag = new object();
/// <summary>
/// Creates a vertical dotted line to separate the line numbers from the text view.
/// </summary>
public static UIElement Create()
{
Line line = new Line {
X1 = 0, Y1 = 0, X2 = 0, Y2 = 1,
StrokeDashArray = { 0, 2 },
Stretch = Stretch.Fill,
StrokeThickness = 1,
StrokeDashCap = PenLineCap.Round,
Margin = new Thickness(2, 0, 2, 0),
Tag = tag
};
return line;
}
/// <summary>
/// Creates a vertical dotted line to separate the line numbers from the text view.
/// </summary>
[Obsolete("This method got published accidentally; and will be removed again in a future version. Use the parameterless overload instead.")]
public static UIElement Create(TextEditor editor)
{
Line line = (Line)Create();
line.SetBinding(
Line.StrokeProperty,
new Binding("LineNumbersForeground") { Source = editor }
);
return line;
}
/// <summary>
/// Gets whether the specified UIElement is the result of a DottedLineMargin.Create call.
/// </summary>
public static bool IsDottedLineMargin(UIElement element)
{
Line l = element as Line;
return l != null && l.Tag == tag;
}
}
}

61
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/DragDropException.cs

@ -1,61 +0,0 @@ @@ -1,61 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Runtime.Serialization;
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Wraps exceptions that occur during drag'n'drop.
/// Exceptions during drag'n'drop might
/// get swallowed by WPF/COM, so AvalonEdit catches them and re-throws them later
/// wrapped in a DragDropException.
/// </summary>
[Serializable()]
public class DragDropException : Exception
{
/// <summary>
/// Creates a new DragDropException.
/// </summary>
public DragDropException() : base()
{
}
/// <summary>
/// Creates a new DragDropException.
/// </summary>
public DragDropException(string message) : base(message)
{
}
/// <summary>
/// Creates a new DragDropException.
/// </summary>
public DragDropException(string message, Exception innerException) : base(message, innerException)
{
}
/// <summary>
/// Deserializes a DragDropException.
/// </summary>
protected DragDropException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

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

@ -1,643 +0,0 @@ @@ -1,643 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// We re-use the CommandBinding and InputBinding instances between multiple text areas,
/// so this class is static.
/// </summary>
static class EditingCommandHandler
{
/// <summary>
/// Creates a new <see cref="TextAreaInputHandler"/> for the text area.
/// </summary>
public static TextAreaInputHandler Create(TextArea textArea)
{
TextAreaInputHandler handler = new TextAreaInputHandler(textArea);
handler.CommandBindings.AddRange(CommandBindings);
handler.InputBindings.AddRange(InputBindings);
return handler;
}
static readonly List<CommandBinding> CommandBindings = new List<CommandBinding>();
static readonly List<InputBinding> InputBindings = new List<InputBinding>();
static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler)
{
CommandBindings.Add(new CommandBinding(command, handler));
InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key));
}
static EditingCommandHandler()
{
CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, OnDelete(CaretMovementType.None), CanDelete));
AddBinding(EditingCommands.Delete, ModifierKeys.None, Key.Delete, OnDelete(CaretMovementType.CharRight));
AddBinding(EditingCommands.DeleteNextWord, ModifierKeys.Control, Key.Delete, OnDelete(CaretMovementType.WordRight));
AddBinding(EditingCommands.Backspace, ModifierKeys.None, Key.Back, OnDelete(CaretMovementType.Backspace));
InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(EditingCommands.Backspace, ModifierKeys.Shift, Key.Back)); // make Shift-Backspace do the same as plain backspace
AddBinding(EditingCommands.DeletePreviousWord, ModifierKeys.Control, Key.Back, OnDelete(CaretMovementType.WordLeft));
AddBinding(EditingCommands.EnterParagraphBreak, ModifierKeys.None, Key.Enter, OnEnter);
AddBinding(EditingCommands.EnterLineBreak, ModifierKeys.Shift, Key.Enter, OnEnter);
AddBinding(EditingCommands.TabForward, ModifierKeys.None, Key.Tab, OnTab);
AddBinding(EditingCommands.TabBackward, ModifierKeys.Shift, Key.Tab, OnShiftTab);
CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopy, CanCutOrCopy));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCut, CanCutOrCopy));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPaste, CanPaste));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.DeleteLine, OnDeleteLine));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveLeadingWhitespace, OnRemoveLeadingWhitespace));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveTrailingWhitespace, OnRemoveTrailingWhitespace));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToUppercase, OnConvertToUpperCase));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToLowercase, OnConvertToLowerCase));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToTitleCase, OnConvertToTitleCase));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.InvertCase, OnInvertCase));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertTabsToSpaces, OnConvertTabsToSpaces));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertSpacesToTabs, OnConvertSpacesToTabs));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingTabsToSpaces, OnConvertLeadingTabsToSpaces));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingSpacesToTabs, OnConvertLeadingSpacesToTabs));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.IndentSelection, OnIndentSelection));
TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings);
}
static TextArea GetTextArea(object target)
{
return target as TextArea;
}
#region Text Transformation Helpers
enum DefaultSegmentType
{
None,
WholeDocument,
CurrentLine
}
/// <summary>
/// Calls transformLine on all lines in the selected range.
/// transformLine needs to handle read-only segments!
/// </summary>
static void TransformSelectedLines(Action<TextArea, DocumentLine> transformLine, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
using (textArea.Document.RunUpdate()) {
DocumentLine start, end;
if (textArea.Selection.IsEmpty) {
if (defaultSegmentType == DefaultSegmentType.CurrentLine) {
start = end = textArea.Document.GetLineByNumber(textArea.Caret.Line);
} else if (defaultSegmentType == DefaultSegmentType.WholeDocument) {
start = textArea.Document.Lines.First();
end = textArea.Document.Lines.Last();
} else {
start = end = null;
}
} else {
ISegment segment = textArea.Selection.SurroundingSegment;
start = textArea.Document.GetLineByOffset(segment.Offset);
end = textArea.Document.GetLineByOffset(segment.EndOffset);
// don't include the last line if no characters on it are selected
if (start != end && end.Offset == segment.EndOffset)
end = end.PreviousLine;
}
if (start != null) {
transformLine(textArea, start);
while (start != end) {
start = start.NextLine;
transformLine(textArea, start);
}
}
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
}
/// <summary>
/// Calls transformLine on all writable segment in the selected range.
/// </summary>
static void TransformSelectedSegments(Action<TextArea, ISegment> transformSegment, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
using (textArea.Document.RunUpdate()) {
IEnumerable<ISegment> segments;
if (textArea.Selection.IsEmpty) {
if (defaultSegmentType == DefaultSegmentType.CurrentLine) {
segments = new ISegment[] { textArea.Document.GetLineByNumber(textArea.Caret.Line) };
} else if (defaultSegmentType == DefaultSegmentType.WholeDocument) {
segments = textArea.Document.Lines.Cast<ISegment>();
} else {
segments = null;
}
} else {
segments = textArea.Selection.Segments.Cast<ISegment>();
}
if (segments != null) {
foreach (ISegment segment in segments.Reverse()) {
foreach (ISegment writableSegment in textArea.GetDeletableSegments(segment).Reverse()) {
transformSegment(textArea, writableSegment);
}
}
}
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
}
#endregion
#region EnterLineBreak
static void OnEnter(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.IsKeyboardFocused) {
textArea.PerformTextInput("\n");
args.Handled = true;
}
}
#endregion
#region Tab
static void OnTab(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
using (textArea.Document.RunUpdate()) {
if (textArea.Selection.IsMultiline) {
var segment = textArea.Selection.SurroundingSegment;
DocumentLine start = textArea.Document.GetLineByOffset(segment.Offset);
DocumentLine end = textArea.Document.GetLineByOffset(segment.EndOffset);
// don't include the last line if no characters on it are selected
if (start != end && end.Offset == segment.EndOffset)
end = end.PreviousLine;
DocumentLine current = start;
while (true) {
int offset = current.Offset;
if (textArea.ReadOnlySectionProvider.CanInsert(offset))
textArea.Document.Replace(offset, 0, textArea.Options.IndentationString, OffsetChangeMappingType.KeepAnchorBeforeInsertion);
if (current == end)
break;
current = current.NextLine;
}
} else {
string indentationString = textArea.Options.GetIndentationString(textArea.Caret.Column);
textArea.ReplaceSelectionWithText(indentationString);
}
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
}
static void OnShiftTab(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) {
int offset = line.Offset;
ISegment s = TextUtilities.GetSingleIndentationSegment(textArea.Document, offset, textArea.Options.IndentationSize);
if (s.Length > 0) {
s = textArea.GetDeletableSegments(s).FirstOrDefault();
if (s != null && s.Length > 0) {
textArea.Document.Remove(s.Offset, s.Length);
}
}
}, target, args, DefaultSegmentType.CurrentLine);
}
#endregion
#region Delete
static ExecutedRoutedEventHandler OnDelete(CaretMovementType caretMovement)
{
return (target, args) => {
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
if (textArea.Selection.IsEmpty) {
TextViewPosition startPos = textArea.Caret.Position;
bool enableVirtualSpace = textArea.Options.EnableVirtualSpace;
// When pressing delete; don't move the caret further into virtual space - instead delete the newline
if (caretMovement == CaretMovementType.CharRight)
enableVirtualSpace = false;
double desiredXPos = textArea.Caret.DesiredXPos;
TextViewPosition endPos = CaretNavigationCommandHandler.GetNewCaretPosition(
textArea.TextView, startPos, caretMovement, enableVirtualSpace, ref desiredXPos);
// GetNewCaretPosition may return (0,0) as new position,
// thus we need to validate endPos before using it in the selection.
if (endPos.Line < 1 || endPos.Column < 1)
endPos = new TextViewPosition(Math.Max(endPos.Line, 1), Math.Max(endPos.Column, 1));
// Don't select the text to be deleted; just reuse the ReplaceSelectionWithText logic
var sel = new SimpleSelection(textArea, startPos, endPos);
sel.ReplaceSelectionWithText(string.Empty);
} else {
textArea.RemoveSelectedText();
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
};
}
static void CanDelete(object target, CanExecuteRoutedEventArgs args)
{
// HasSomethingSelected for delete command
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
args.CanExecute = !textArea.Selection.IsEmpty;
args.Handled = true;
}
}
#endregion
#region Clipboard commands
static void CanCutOrCopy(object target, CanExecuteRoutedEventArgs args)
{
// HasSomethingSelected for copy and cut commands
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
args.CanExecute = textArea.Options.CutCopyWholeLine || !textArea.Selection.IsEmpty;
args.Handled = true;
}
}
static void OnCopy(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine) {
DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
CopyWholeLine(textArea, currentLine);
} else {
CopySelectedText(textArea);
}
args.Handled = true;
}
}
static void OnCut(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine) {
DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
if (CopyWholeLine(textArea, currentLine)) {
ISegment[] segmentsToDelete = textArea.GetDeletableSegments(new SimpleSegment(currentLine.Offset, currentLine.TotalLength));
for (int i = segmentsToDelete.Length - 1; i >= 0; i--) {
textArea.Document.Remove(segmentsToDelete[i]);
}
}
} else {
if (CopySelectedText(textArea))
textArea.RemoveSelectedText();
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
}
static bool CopySelectedText(TextArea textArea)
{
var data = textArea.Selection.CreateDataObject(textArea);
var copyingEventArgs = new DataObjectCopyingEventArgs(data, false);
textArea.RaiseEvent(copyingEventArgs);
if (copyingEventArgs.CommandCancelled)
return false;
try {
Clipboard.SetDataObject(data, true);
} catch (ExternalException) {
// Apparently this exception sometimes happens randomly.
// The MS controls just ignore it, so we'll do the same.
}
string text = textArea.Selection.GetText();
text = TextUtilities.NormalizeNewLines(text, Environment.NewLine);
textArea.OnTextCopied(new TextEventArgs(text));
return true;
}
const string LineSelectedType = "MSDEVLineSelect"; // This is the type VS 2003 and 2005 use for flagging a whole line copy
public static bool ConfirmDataFormat(TextArea textArea, DataObject dataObject, string format)
{
var e = new DataObjectSettingDataEventArgs(dataObject, format);
textArea.RaiseEvent(e);
return !e.CommandCancelled;
}
static bool CopyWholeLine(TextArea textArea, DocumentLine line)
{
ISegment wholeLine = new SimpleSegment(line.Offset, line.TotalLength);
string text = textArea.Document.GetText(wholeLine);
// Ensure we use the appropriate newline sequence for the OS
text = TextUtilities.NormalizeNewLines(text, Environment.NewLine);
DataObject data = new DataObject();
if (ConfirmDataFormat(textArea, data, DataFormats.UnicodeText))
data.SetText(text);
// Also copy text in HTML format to clipboard - good for pasting text into Word
// or to the SharpDevelop forums.
if (ConfirmDataFormat(textArea, data, DataFormats.Html)) {
IHighlighter highlighter = textArea.GetService(typeof(IHighlighter)) as IHighlighter;
HtmlClipboard.SetHtml(data, HtmlClipboard.CreateHtmlFragment(textArea.Document, highlighter, wholeLine, new HtmlOptions(textArea.Options)));
}
if (ConfirmDataFormat(textArea, data, LineSelectedType)) {
MemoryStream lineSelected = new MemoryStream(1);
lineSelected.WriteByte(1);
data.SetData(LineSelectedType, lineSelected, false);
}
var copyingEventArgs = new DataObjectCopyingEventArgs(data, false);
textArea.RaiseEvent(copyingEventArgs);
if (copyingEventArgs.CommandCancelled)
return false;
try {
Clipboard.SetDataObject(data, true);
} catch (ExternalException) {
// Apparently this exception sometimes happens randomly.
// The MS controls just ignore it, so we'll do the same.
return false;
}
textArea.OnTextCopied(new TextEventArgs(text));
return true;
}
static void CanPaste(object target, CanExecuteRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
args.CanExecute = textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset)
&& Clipboard.ContainsText();
// WPF Clipboard.ContainsText() is safe to call without catching ExternalExceptions
// because it doesn't try to lock the clipboard - it just peeks inside with IsClipboardFormatAvailable().
args.Handled = true;
}
}
static void OnPaste(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
IDataObject dataObject;
try {
dataObject = Clipboard.GetDataObject();
} catch (ExternalException) {
return;
}
if (dataObject == null)
return;
Debug.WriteLine(dataObject.GetData(DataFormats.Html) as string);
// convert text back to correct newlines for this document
string newLine = TextUtilities.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line);
string text;
try {
text = (string)dataObject.GetData(DataFormats.UnicodeText);
text = TextUtilities.NormalizeNewLines(text, newLine);
} catch (OutOfMemoryException) {
return;
}
if (!string.IsNullOrEmpty(text)) {
bool fullLine = textArea.Options.CutCopyWholeLine && dataObject.GetDataPresent(LineSelectedType);
bool rectangular = dataObject.GetDataPresent(RectangleSelection.RectangularSelectionDataType);
string pasteFormat;
// fill the suggested DataFormat used for the paste action:
if (fullLine)
pasteFormat = LineSelectedType;
else if (rectangular && textArea.Selection.IsEmpty && !(textArea.Selection is RectangleSelection))
pasteFormat = RectangleSelection.RectangularSelectionDataType;
else
pasteFormat = DataFormats.UnicodeText;
var pastingEventArgs = new DataObjectPastingEventArgs(dataObject, false, pasteFormat);
textArea.RaiseEvent(pastingEventArgs);
if (pastingEventArgs.CommandCancelled)
return;
// DataObject.PastingEvent handlers might have changed the format to apply.
pasteFormat = pastingEventArgs.FormatToApply;
fullLine = pasteFormat == LineSelectedType;
rectangular = pasteFormat == RectangleSelection.RectangularSelectionDataType;
if (fullLine) {
DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
if (textArea.ReadOnlySectionProvider.CanInsert(currentLine.Offset)) {
textArea.Document.Insert(currentLine.Offset, text);
}
} else if (rectangular && textArea.Selection.IsEmpty && !(textArea.Selection is RectangleSelection)) {
if (!RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, false))
textArea.ReplaceSelectionWithText(text);
} else {
textArea.ReplaceSelectionWithText(text);
}
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
}
#endregion
#region DeleteLine
static void OnDeleteLine(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
int firstLineIndex, lastLineIndex;
if (textArea.Selection.Length == 0) {
// There is no selection, simply delete current line
firstLineIndex = lastLineIndex = textArea.Caret.Line;
} else {
// There is a selection, remove all lines affected by it (use Min/Max to be independent from selection direction)
firstLineIndex = Math.Min(textArea.Selection.StartPosition.Line, textArea.Selection.EndPosition.Line);
lastLineIndex = Math.Max(textArea.Selection.StartPosition.Line, textArea.Selection.EndPosition.Line);
}
DocumentLine startLine = textArea.Document.GetLineByNumber(firstLineIndex);
DocumentLine endLine = textArea.Document.GetLineByNumber(lastLineIndex);
textArea.Selection = Selection.Create(textArea, startLine.Offset, endLine.Offset + endLine.TotalLength);
textArea.RemoveSelectedText();
args.Handled = true;
}
}
#endregion
#region Remove..Whitespace / Convert Tabs-Spaces
static void OnRemoveLeadingWhitespace(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) {
textArea.Document.Remove(TextUtilities.GetLeadingWhitespace(textArea.Document, line));
}, target, args, DefaultSegmentType.WholeDocument);
}
static void OnRemoveTrailingWhitespace(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) {
textArea.Document.Remove(TextUtilities.GetTrailingWhitespace(textArea.Document, line));
}, target, args, DefaultSegmentType.WholeDocument);
}
static void OnConvertTabsToSpaces(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedSegments(ConvertTabsToSpaces, target, args, DefaultSegmentType.WholeDocument);
}
static void OnConvertLeadingTabsToSpaces(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) {
ConvertTabsToSpaces(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line));
}, target, args, DefaultSegmentType.WholeDocument);
}
static void ConvertTabsToSpaces(TextArea textArea, ISegment segment)
{
TextDocument document = textArea.Document;
int endOffset = segment.EndOffset;
string indentationString = new string(' ', textArea.Options.IndentationSize);
for (int offset = segment.Offset; offset < endOffset; offset++) {
if (document.GetCharAt(offset) == '\t') {
document.Replace(offset, 1, indentationString, OffsetChangeMappingType.CharacterReplace);
endOffset += indentationString.Length - 1;
}
}
}
static void OnConvertSpacesToTabs(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedSegments(ConvertSpacesToTabs, target, args, DefaultSegmentType.WholeDocument);
}
static void OnConvertLeadingSpacesToTabs(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) {
ConvertSpacesToTabs(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line));
}, target, args, DefaultSegmentType.WholeDocument);
}
static void ConvertSpacesToTabs(TextArea textArea, ISegment segment)
{
TextDocument document = textArea.Document;
int endOffset = segment.EndOffset;
int indentationSize = textArea.Options.IndentationSize;
int spacesCount = 0;
for (int offset = segment.Offset; offset < endOffset; offset++) {
if (document.GetCharAt(offset) == ' ') {
spacesCount++;
if (spacesCount == indentationSize) {
document.Replace(offset - (indentationSize - 1), indentationSize, "\t", OffsetChangeMappingType.CharacterReplace);
spacesCount = 0;
offset -= indentationSize - 1;
endOffset -= indentationSize - 1;
}
} else {
spacesCount = 0;
}
}
}
#endregion
#region Convert...Case
static void ConvertCase(Func<string, string> transformText, object target, ExecutedRoutedEventArgs args)
{
TransformSelectedSegments(
delegate (TextArea textArea, ISegment segment) {
string oldText = textArea.Document.GetText(segment);
string newText = transformText(oldText);
textArea.Document.Replace(segment.Offset, segment.Length, newText, OffsetChangeMappingType.CharacterReplace);
}, target, args, DefaultSegmentType.WholeDocument);
}
static void OnConvertToUpperCase(object target, ExecutedRoutedEventArgs args)
{
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToUpper, target, args);
}
static void OnConvertToLowerCase(object target, ExecutedRoutedEventArgs args)
{
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToLower, target, args);
}
static void OnConvertToTitleCase(object target, ExecutedRoutedEventArgs args)
{
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToTitleCase, target, args);
}
static void OnInvertCase(object target, ExecutedRoutedEventArgs args)
{
ConvertCase(InvertCase, target, args);
}
static string InvertCase(string text)
{
CultureInfo culture = CultureInfo.CurrentCulture;
char[] buffer = text.ToCharArray();
for (int i = 0; i < buffer.Length; ++i) {
char c = buffer[i];
buffer[i] = char.IsUpper(c) ? char.ToLower(c, culture) : char.ToUpper(c, culture);
}
return new string(buffer);
}
#endregion
static void OnIndentSelection(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
using (textArea.Document.RunUpdate()) {
int start, end;
if (textArea.Selection.IsEmpty) {
start = 1;
end = textArea.Document.LineCount;
} else {
start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset).LineNumber;
end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.EndOffset).LineNumber;
}
textArea.IndentationStrategy.IndentLines(textArea.Document, start, end);
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
}
}
}

105
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EmptySelection.cs

@ -1,105 +0,0 @@ @@ -1,105 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Utils;
#if NREFACTORY
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Editing
{
sealed class EmptySelection : Selection
{
public EmptySelection(TextArea textArea) : base(textArea)
{
}
public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e)
{
return this;
}
public override TextViewPosition StartPosition {
get { return new TextViewPosition(TextLocation.Empty); }
}
public override TextViewPosition EndPosition {
get { return new TextViewPosition(TextLocation.Empty); }
}
public override ISegment SurroundingSegment {
get { return null; }
}
public override Selection SetEndpoint(TextViewPosition endPosition)
{
throw new NotSupportedException();
}
public override Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition)
{
var document = textArea.Document;
if (document == null)
throw ThrowUtil.NoDocumentAssigned();
return Create(textArea, startPosition, endPosition);
}
public override IEnumerable<SelectionSegment> Segments {
get { return Empty<SelectionSegment>.Array; }
}
public override string GetText()
{
return string.Empty;
}
public override void ReplaceSelectionWithText(string newText)
{
if (newText == null)
throw new ArgumentNullException("newText");
newText = AddSpacesIfRequired(newText, textArea.Caret.Position, textArea.Caret.Position);
if (newText.Length > 0) {
if (textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset)) {
textArea.Document.Insert(textArea.Caret.Offset, newText);
}
}
textArea.Caret.VisualColumn = -1;
}
public override int Length {
get { return 0; }
}
// Use reference equality because there's only one EmptySelection per text area.
public override int GetHashCode()
{
return RuntimeHelpers.GetHashCode(this);
}
public override bool Equals(object obj)
{
return this == obj;
}
}
}

51
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/IReadOnlySectionProvider.cs

@ -1,51 +0,0 @@ @@ -1,51 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#else
using ICSharpCode.AvalonEdit.Document;
#endif
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Determines whether the document can be modified.
/// </summary>
public interface IReadOnlySectionProvider
{
/// <summary>
/// Gets whether insertion is possible at the specified offset.
/// </summary>
bool CanInsert(int offset);
/// <summary>
/// Gets the deletable segments inside the given segment.
/// </summary>
/// <remarks>
/// All segments in the result must be within the given segment, and they must be returned in order
/// (e.g. if two segments are returned, EndOffset of first segment must be less than StartOffset of second segment).
///
/// For replacements, the last segment being returned will be replaced with the new text. If an empty list is returned,
/// no replacement will be done.
/// </remarks>
IEnumerable<ISegment> GetDeletableSegments(ISegment segment);
}
}

221
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/ImeNativeWrapper.cs

@ -1,221 +0,0 @@ @@ -1,221 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
using Draw = System.Drawing;
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Native API required for IME support.
/// </summary>
static class ImeNativeWrapper
{
[StructLayout(LayoutKind.Sequential)]
struct CompositionForm
{
public int dwStyle;
public POINT ptCurrentPos;
public RECT rcArea;
}
[StructLayout(LayoutKind.Sequential)]
struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
struct LOGFONT
{
public int lfHeight;
public int lfWidth;
public int lfEscapement;
public int lfOrientation;
public int lfWeight;
public byte lfItalic;
public byte lfUnderline;
public byte lfStrikeOut;
public byte lfCharSet;
public byte lfOutPrecision;
public byte lfClipPrecision;
public byte lfQuality;
public byte lfPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string lfFaceName;
}
const int CPS_CANCEL = 0x4;
const int NI_COMPOSITIONSTR = 0x15;
const int GCS_COMPSTR = 0x0008;
public const int WM_IME_COMPOSITION = 0x10F;
public const int WM_IME_SETCONTEXT = 0x281;
public const int WM_INPUTLANGCHANGE = 0x51;
[DllImport("imm32.dll")]
public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll")]
internal static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("imm32.dll")]
internal static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd);
[DllImport("imm32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ImmNotifyIME(IntPtr hIMC, int dwAction, int dwIndex, int dwValue = 0);
[DllImport("imm32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref CompositionForm form);
[DllImport("imm32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT font);
[DllImport("imm32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ImmGetCompositionFont(IntPtr hIMC, out LOGFONT font);
[DllImport("msctf.dll")]
static extern int TF_CreateThreadMgr(out ITfThreadMgr threadMgr);
[ThreadStatic] static bool textFrameworkThreadMgrInitialized;
[ThreadStatic] static ITfThreadMgr textFrameworkThreadMgr;
public static ITfThreadMgr GetTextFrameworkThreadManager()
{
if (!textFrameworkThreadMgrInitialized) {
textFrameworkThreadMgrInitialized = true;
TF_CreateThreadMgr(out textFrameworkThreadMgr);
}
return textFrameworkThreadMgr;
}
public static bool NotifyIme(IntPtr hIMC)
{
return ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL);
}
public static bool SetCompositionWindow(HwndSource source, IntPtr hIMC, TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
Rect textViewBounds = textArea.TextView.GetBounds(source);
Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source);
CompositionForm form = new CompositionForm();
form.dwStyle = 0x0020;
form.ptCurrentPos.x = (int)Math.Max(characterBounds.Left, textViewBounds.Left);
form.ptCurrentPos.y = (int)Math.Max(characterBounds.Top, textViewBounds.Top);
form.rcArea.left = (int)textViewBounds.Left;
form.rcArea.top = (int)textViewBounds.Top;
form.rcArea.right = (int)textViewBounds.Right;
form.rcArea.bottom = (int)textViewBounds.Bottom;
return ImmSetCompositionWindow(hIMC, ref form);
}
public static bool SetCompositionFont(HwndSource source, IntPtr hIMC, TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
LOGFONT lf = new LOGFONT();
Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source);
lf.lfFaceName = textArea.FontFamily.Source;
lf.lfHeight = (int)characterBounds.Height;
return ImmSetCompositionFont(hIMC, ref lf);
}
static Rect GetBounds(this TextView textView, HwndSource source)
{
// this may happen during layout changes in AvalonDock, so we just return an empty rectangle
// in those cases. It should be refreshed immediately.
if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView))
return EMPTY_RECT;
Rect displayRect = new Rect(0, 0, textView.ActualWidth, textView.ActualHeight);
return textView
.TransformToAncestor(source.RootVisual).TransformBounds(displayRect) // rect on root visual
.TransformToDevice(source.RootVisual); // rect on HWND
}
static readonly Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
static Rect GetCharacterBounds(this TextView textView, TextViewPosition pos, HwndSource source)
{
VisualLine vl = textView.GetVisualLine(pos.Line);
if (vl == null)
return EMPTY_RECT;
// this may happen during layout changes in AvalonDock, so we just return an empty rectangle
// in those cases. It should be refreshed immediately.
if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView))
return EMPTY_RECT;
TextLine line = vl.GetTextLine(pos.VisualColumn, pos.IsAtEndOfLine);
Rect displayRect;
// calculate the display rect for the current character
if (pos.VisualColumn < vl.VisualLengthWithEndOfLineMarker) {
displayRect = line.GetTextBounds(pos.VisualColumn, 1).First().Rectangle;
displayRect.Offset(0, vl.GetTextLineVisualYPosition(line, VisualYPosition.LineTop));
} else {
// if we are in virtual space, we just use one wide-space as character width
displayRect = new Rect(vl.GetVisualPosition(pos.VisualColumn, VisualYPosition.TextTop),
new Size(textView.WideSpaceWidth, textView.DefaultLineHeight));
}
// adjust to current scrolling
displayRect.Offset(-textView.ScrollOffset);
return textView
.TransformToAncestor(source.RootVisual).TransformBounds(displayRect) // rect on root visual
.TransformToDevice(source.RootVisual); // rect on HWND
}
}
[ComImport, Guid("aa80e801-2021-11d2-93e0-0060b067b86e"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface ITfThreadMgr
{
void Activate(out int clientId);
void Deactivate();
void CreateDocumentMgr(out IntPtr docMgr);
void EnumDocumentMgrs(out IntPtr enumDocMgrs);
void GetFocus(out IntPtr docMgr);
void SetFocus(IntPtr docMgr);
void AssociateFocus(IntPtr hwnd, IntPtr newDocMgr, out IntPtr prevDocMgr);
void IsThreadFocus([MarshalAs(UnmanagedType.Bool)] out bool isFocus);
void GetFunctionProvider(ref Guid classId, out IntPtr funcProvider);
void EnumFunctionProviders(out IntPtr enumProviders);
void GetGlobalCompartment(out IntPtr compartmentMgr);
}
}

165
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/ImeSupport.cs

@ -1,165 +0,0 @@ @@ -1,165 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
namespace ICSharpCode.AvalonEdit.Editing
{
class ImeSupport
{
readonly TextArea textArea;
IntPtr currentContext;
IntPtr previousContext;
IntPtr defaultImeWnd;
HwndSource hwndSource;
EventHandler requerySuggestedHandler; // we need to keep the event handler instance alive because CommandManager.RequerySuggested uses weak references
bool isReadOnly;
public ImeSupport(TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
this.textArea = textArea;
InputMethod.SetIsInputMethodSuspended(this.textArea, textArea.Options.EnableImeSupport);
// We listen to CommandManager.RequerySuggested for both caret offset changes and changes to the set of read-only sections.
// This is because there's no dedicated event for read-only section changes; but RequerySuggested needs to be raised anyways
// to invalidate the Paste command.
requerySuggestedHandler = OnRequerySuggested;
CommandManager.RequerySuggested += requerySuggestedHandler;
textArea.OptionChanged += TextAreaOptionChanged;
}
void OnRequerySuggested(object sender, EventArgs e)
{
UpdateImeEnabled();
}
void TextAreaOptionChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "EnableImeSupport") {
InputMethod.SetIsInputMethodSuspended(this.textArea, textArea.Options.EnableImeSupport);
UpdateImeEnabled();
}
}
public void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
UpdateImeEnabled();
}
public void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
if (e.OldFocus == textArea && currentContext != IntPtr.Zero)
ImeNativeWrapper.NotifyIme(currentContext);
ClearContext();
}
void UpdateImeEnabled()
{
if (textArea.Options.EnableImeSupport && textArea.IsKeyboardFocused) {
bool newReadOnly = !textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset);
if (hwndSource == null || isReadOnly != newReadOnly) {
ClearContext(); // clear existing context (on read-only change)
isReadOnly = newReadOnly;
CreateContext();
}
} else {
ClearContext();
}
}
void ClearContext()
{
if (hwndSource != null) {
ImeNativeWrapper.ImmAssociateContext(hwndSource.Handle, previousContext);
ImeNativeWrapper.ImmReleaseContext(defaultImeWnd, currentContext);
currentContext = IntPtr.Zero;
defaultImeWnd = IntPtr.Zero;
hwndSource.RemoveHook(WndProc);
hwndSource = null;
}
}
void CreateContext()
{
hwndSource = (HwndSource)PresentationSource.FromVisual(this.textArea);
if (hwndSource != null) {
if (isReadOnly) {
defaultImeWnd = IntPtr.Zero;
currentContext = IntPtr.Zero;
} else {
defaultImeWnd = ImeNativeWrapper.ImmGetDefaultIMEWnd(IntPtr.Zero);
currentContext = ImeNativeWrapper.ImmGetContext(defaultImeWnd);
}
previousContext = ImeNativeWrapper.ImmAssociateContext(hwndSource.Handle, currentContext);
hwndSource.AddHook(WndProc);
// UpdateCompositionWindow() will be called by the caret becoming visible
var threadMgr = ImeNativeWrapper.GetTextFrameworkThreadManager();
if (threadMgr != null) {
// Even though the docu says passing null is invalid, this seems to help
// activating the IME on the default input context that is shared with WPF
threadMgr.SetFocus(IntPtr.Zero);
}
}
}
IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg) {
case ImeNativeWrapper.WM_INPUTLANGCHANGE:
// Don't mark the message as handled; other windows
// might want to handle it as well.
// If we have a context, recreate it
if (hwndSource != null) {
ClearContext();
CreateContext();
}
break;
case ImeNativeWrapper.WM_IME_COMPOSITION:
UpdateCompositionWindow();
break;
}
return IntPtr.Zero;
}
public void UpdateCompositionWindow()
{
if (currentContext != IntPtr.Zero) {
ImeNativeWrapper.SetCompositionFont(hwndSource, currentContext, textArea);
ImeNativeWrapper.SetCompositionWindow(hwndSource, currentContext, textArea);
}
}
}
}

260
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/LineNumberMargin.cs

@ -1,260 +0,0 @@ @@ -1,260 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Margin showing line numbers.
/// </summary>
public class LineNumberMargin : AbstractMargin, IWeakEventListener
{
static LineNumberMargin()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LineNumberMargin),
new FrameworkPropertyMetadata(typeof(LineNumberMargin)));
}
TextArea textArea;
/// <summary>
/// The typeface used for rendering the line number margin.
/// This field is calculated in MeasureOverride() based on the FontFamily etc. properties.
/// </summary>
protected Typeface typeface;
/// <summary>
/// The font size used for rendering the line number margin.
/// This field is calculated in MeasureOverride() based on the FontFamily etc. properties.
/// </summary>
protected double emSize;
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
typeface = this.CreateTypeface();
emSize = (double)GetValue(TextBlock.FontSizeProperty);
FormattedText text = TextFormatterFactory.CreateFormattedText(
this,
new string('9', maxLineNumberLength),
typeface,
emSize,
(Brush)GetValue(Control.ForegroundProperty)
);
return new Size(text.Width, 0);
}
/// <inheritdoc/>
protected override void OnRender(DrawingContext drawingContext)
{
TextView textView = this.TextView;
Size renderSize = this.RenderSize;
if (textView != null && textView.VisualLinesValid) {
var foreground = (Brush)GetValue(Control.ForegroundProperty);
foreach (VisualLine line in textView.VisualLines) {
int lineNumber = line.FirstDocumentLine.LineNumber;
FormattedText text = TextFormatterFactory.CreateFormattedText(
this,
lineNumber.ToString(CultureInfo.CurrentCulture),
typeface, emSize, foreground
);
double y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop);
drawingContext.DrawText(text, new Point(renderSize.Width - text.Width, y - textView.VerticalOffset));
}
}
}
/// <inheritdoc/>
protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView)
{
if (oldTextView != null) {
oldTextView.VisualLinesChanged -= TextViewVisualLinesChanged;
}
base.OnTextViewChanged(oldTextView, newTextView);
if (newTextView != null) {
newTextView.VisualLinesChanged += TextViewVisualLinesChanged;
// find the text area belonging to the new text view
textArea = newTextView.GetService(typeof(TextArea)) as TextArea;
} else {
textArea = null;
}
InvalidateVisual();
}
/// <inheritdoc/>
protected override void OnDocumentChanged(TextDocument oldDocument, TextDocument newDocument)
{
if (oldDocument != null) {
PropertyChangedEventManager.RemoveListener(oldDocument, this, "LineCount");
}
base.OnDocumentChanged(oldDocument, newDocument);
if (newDocument != null) {
PropertyChangedEventManager.AddListener(newDocument, this, "LineCount");
}
OnDocumentLineCountChanged();
}
/// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(PropertyChangedEventManager)) {
OnDocumentLineCountChanged();
return true;
}
return false;
}
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return ReceiveWeakEvent(managerType, sender, e);
}
/// <summary>
/// Maximum length of a line number, in characters
/// </summary>
protected int maxLineNumberLength = 1;
void OnDocumentLineCountChanged()
{
int documentLineCount = Document != null ? Document.LineCount : 1;
int newLength = documentLineCount.ToString(CultureInfo.CurrentCulture).Length;
// The margin looks too small when there is only one digit, so always reserve space for
// at least two digits
if (newLength < 2)
newLength = 2;
if (newLength != maxLineNumberLength) {
maxLineNumberLength = newLength;
InvalidateMeasure();
}
}
void TextViewVisualLinesChanged(object sender, EventArgs e)
{
InvalidateVisual();
}
AnchorSegment selectionStart;
bool selecting;
/// <inheritdoc/>
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (!e.Handled && TextView != null && textArea != null) {
e.Handled = true;
textArea.Focus();
SimpleSegment currentSeg = GetTextLineSegment(e);
if (currentSeg == SimpleSegment.Invalid)
return;
textArea.Caret.Offset = currentSeg.Offset + currentSeg.Length;
if (CaptureMouse()) {
selecting = true;
selectionStart = new AnchorSegment(Document, currentSeg.Offset, currentSeg.Length);
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) {
SimpleSelection simpleSelection = textArea.Selection as SimpleSelection;
if (simpleSelection != null)
selectionStart = new AnchorSegment(Document, simpleSelection.SurroundingSegment);
}
textArea.Selection = Selection.Create(textArea, selectionStart);
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) {
ExtendSelection(currentSeg);
}
}
}
}
SimpleSegment GetTextLineSegment(MouseEventArgs e)
{
Point pos = e.GetPosition(TextView);
pos.X = 0;
pos.Y += TextView.VerticalOffset;
VisualLine vl = TextView.GetVisualLineFromVisualTop(pos.Y);
if (vl == null)
return SimpleSegment.Invalid;
TextLine tl = vl.GetTextLineByVisualYPosition(pos.Y);
int visualStartColumn = vl.GetTextLineVisualStartColumn(tl);
int visualEndColumn = visualStartColumn + tl.Length;
int relStart = vl.FirstDocumentLine.Offset;
int startOffset = vl.GetRelativeOffset(visualStartColumn) + relStart;
int endOffset = vl.GetRelativeOffset(visualEndColumn) + relStart;
if (endOffset == vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length)
endOffset += vl.LastDocumentLine.DelimiterLength;
return new SimpleSegment(startOffset, endOffset - startOffset);
}
void ExtendSelection(SimpleSegment currentSeg)
{
if (currentSeg.Offset < selectionStart.Offset) {
textArea.Caret.Offset = currentSeg.Offset;
textArea.Selection = Selection.Create(textArea, currentSeg.Offset, selectionStart.Offset + selectionStart.Length);
} else {
textArea.Caret.Offset = currentSeg.Offset + currentSeg.Length;
textArea.Selection = Selection.Create(textArea, selectionStart.Offset, currentSeg.Offset + currentSeg.Length);
}
}
/// <inheritdoc/>
protected override void OnMouseMove(MouseEventArgs e)
{
if (selecting && textArea != null && TextView != null) {
e.Handled = true;
SimpleSegment currentSeg = GetTextLineSegment(e);
if (currentSeg == SimpleSegment.Invalid)
return;
ExtendSelection(currentSeg);
}
base.OnMouseMove(e);
}
/// <inheritdoc/>
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
if (selecting) {
selecting = false;
selectionStart = null;
ReleaseMouseCapture();
e.Handled = true;
}
base.OnMouseLeftButtonUp(e);
}
/// <inheritdoc/>
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
// accept clicks even when clicking on the background
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
}
}

66
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/NoReadOnlySections.cs

@ -1,66 +0,0 @@ @@ -1,66 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// <see cref="IReadOnlySectionProvider"/> that has no read-only sections; all text is editable.
/// </summary>
sealed class NoReadOnlySections : IReadOnlySectionProvider
{
public static readonly NoReadOnlySections Instance = new NoReadOnlySections();
public bool CanInsert(int offset)
{
return true;
}
public IEnumerable<ISegment> GetDeletableSegments(ISegment segment)
{
if (segment == null)
throw new ArgumentNullException("segment");
// the segment is always deletable
return ExtensionMethods.Sequence(segment);
}
}
/// <summary>
/// <see cref="IReadOnlySectionProvider"/> that completely disables editing.
/// </summary>
sealed class ReadOnlySectionDocument : IReadOnlySectionProvider
{
public static readonly ReadOnlySectionDocument Instance = new ReadOnlySectionDocument();
public bool CanInsert(int offset)
{
return false;
}
public IEnumerable<ISegment> GetDeletableSegments(ISegment segment)
{
return Enumerable.Empty<ISegment>();
}
}
}

417
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs

@ -1,417 +0,0 @@ @@ -1,417 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
#if NREFACTORY
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Rectangular selection ("box selection").
/// </summary>
public sealed class RectangleSelection : Selection
{
#region Commands
/// <summary>
/// Expands the selection left by one character, creating a rectangular selection.
/// Key gesture: Alt+Shift+Left
/// </summary>
public static readonly RoutedUICommand BoxSelectLeftByCharacter = Command("BoxSelectLeftByCharacter");
/// <summary>
/// Expands the selection right by one character, creating a rectangular selection.
/// Key gesture: Alt+Shift+Right
/// </summary>
public static readonly RoutedUICommand BoxSelectRightByCharacter = Command("BoxSelectRightByCharacter");
/// <summary>
/// Expands the selection left by one word, creating a rectangular selection.
/// Key gesture: Ctrl+Alt+Shift+Left
/// </summary>
public static readonly RoutedUICommand BoxSelectLeftByWord = Command("BoxSelectLeftByWord");
/// <summary>
/// Expands the selection left by one word, creating a rectangular selection.
/// Key gesture: Ctrl+Alt+Shift+Right
/// </summary>
public static readonly RoutedUICommand BoxSelectRightByWord = Command("BoxSelectRightByWord");
/// <summary>
/// Expands the selection up by one line, creating a rectangular selection.
/// Key gesture: Alt+Shift+Up
/// </summary>
public static readonly RoutedUICommand BoxSelectUpByLine = Command("BoxSelectUpByLine");
/// <summary>
/// Expands the selection up by one line, creating a rectangular selection.
/// Key gesture: Alt+Shift+Down
/// </summary>
public static readonly RoutedUICommand BoxSelectDownByLine = Command("BoxSelectDownByLine");
/// <summary>
/// Expands the selection to the start of the line, creating a rectangular selection.
/// Key gesture: Alt+Shift+Home
/// </summary>
public static readonly RoutedUICommand BoxSelectToLineStart = Command("BoxSelectToLineStart");
/// <summary>
/// Expands the selection to the end of the line, creating a rectangular selection.
/// Key gesture: Alt+Shift+End
/// </summary>
public static readonly RoutedUICommand BoxSelectToLineEnd = Command("BoxSelectToLineEnd");
static RoutedUICommand Command(string name)
{
return new RoutedUICommand(name, name, typeof(RectangleSelection));
}
#endregion
TextDocument document;
readonly int startLine, endLine;
readonly double startXPos, endXPos;
readonly int topLeftOffset, bottomRightOffset;
readonly TextViewPosition start, end;
readonly List<SelectionSegment> segments = new List<SelectionSegment>();
#region Constructors
/// <summary>
/// Creates a new rectangular selection.
/// </summary>
public RectangleSelection(TextArea textArea, TextViewPosition start, TextViewPosition end)
: base(textArea)
{
InitDocument();
this.startLine = start.Line;
this.endLine = end.Line;
this.startXPos = GetXPos(textArea, start);
this.endXPos = GetXPos(textArea, end);
CalculateSegments();
this.topLeftOffset = this.segments.First().StartOffset;
this.bottomRightOffset = this.segments.Last().EndOffset;
this.start = start;
this.end = end;
}
private RectangleSelection(TextArea textArea, int startLine, double startXPos, TextViewPosition end)
: base(textArea)
{
InitDocument();
this.startLine = startLine;
this.endLine = end.Line;
this.startXPos = startXPos;
this.endXPos = GetXPos(textArea, end);
CalculateSegments();
this.topLeftOffset = this.segments.First().StartOffset;
this.bottomRightOffset = this.segments.Last().EndOffset;
this.start = GetStart();
this.end = end;
}
private RectangleSelection(TextArea textArea, TextViewPosition start, int endLine, double endXPos)
: base(textArea)
{
InitDocument();
this.startLine = start.Line;
this.endLine = endLine;
this.startXPos = GetXPos(textArea, start);
this.endXPos = endXPos;
CalculateSegments();
this.topLeftOffset = this.segments.First().StartOffset;
this.bottomRightOffset = this.segments.Last().EndOffset;
this.start = start;
this.end = GetEnd();
}
void InitDocument()
{
document = textArea.Document;
if (document == null)
throw ThrowUtil.NoDocumentAssigned();
}
static double GetXPos(TextArea textArea, TextViewPosition pos)
{
DocumentLine documentLine = textArea.Document.GetLineByNumber(pos.Line);
VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(documentLine);
int vc = visualLine.ValidateVisualColumn(pos, true);
TextLine textLine = visualLine.GetTextLine(vc, pos.IsAtEndOfLine);
return visualLine.GetTextLineVisualXPosition(textLine, vc);
}
void CalculateSegments()
{
DocumentLine nextLine = document.GetLineByNumber(Math.Min(startLine, endLine));
do {
VisualLine vl = textArea.TextView.GetOrConstructVisualLine(nextLine);
int startVC = vl.GetVisualColumn(new Point(startXPos, 0), true);
int endVC = vl.GetVisualColumn(new Point(endXPos, 0), true);
int baseOffset = vl.FirstDocumentLine.Offset;
int startOffset = baseOffset + vl.GetRelativeOffset(startVC);
int endOffset = baseOffset + vl.GetRelativeOffset(endVC);
segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC));
nextLine = vl.LastDocumentLine.NextLine;
} while (nextLine != null && nextLine.LineNumber <= Math.Max(startLine, endLine));
}
TextViewPosition GetStart()
{
SelectionSegment segment = (startLine < endLine ? segments.First() : segments.Last());
if (startXPos < endXPos) {
return new TextViewPosition(document.GetLocation(segment.StartOffset), segment.StartVisualColumn);
} else {
return new TextViewPosition(document.GetLocation(segment.EndOffset), segment.EndVisualColumn);
}
}
TextViewPosition GetEnd()
{
SelectionSegment segment = (startLine < endLine ? segments.Last() : segments.First());
if (startXPos < endXPos) {
return new TextViewPosition(document.GetLocation(segment.EndOffset), segment.EndVisualColumn);
} else {
return new TextViewPosition(document.GetLocation(segment.StartOffset), segment.StartVisualColumn);
}
}
#endregion
/// <inheritdoc/>
public override string GetText()
{
StringBuilder b = new StringBuilder();
foreach (ISegment s in this.Segments) {
if (b.Length > 0)
b.AppendLine();
b.Append(document.GetText(s));
}
return b.ToString();
}
/// <inheritdoc/>
public override Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition)
{
return SetEndpoint(endPosition);
}
/// <inheritdoc/>
public override int Length {
get {
return this.Segments.Sum(s => s.Length);
}
}
/// <inheritdoc/>
public override bool EnableVirtualSpace {
get { return true; }
}
/// <inheritdoc/>
public override ISegment SurroundingSegment {
get {
return new SimpleSegment(topLeftOffset, bottomRightOffset - topLeftOffset);
}
}
/// <inheritdoc/>
public override IEnumerable<SelectionSegment> Segments {
get { return segments; }
}
/// <inheritdoc/>
public override TextViewPosition StartPosition {
get { return start; }
}
/// <inheritdoc/>
public override TextViewPosition EndPosition {
get { return end; }
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
RectangleSelection r = obj as RectangleSelection;
return r != null && r.textArea == this.textArea
&& r.topLeftOffset == this.topLeftOffset && r.bottomRightOffset == this.bottomRightOffset
&& r.startLine == this.startLine && r.endLine == this.endLine
&& r.startXPos == this.startXPos && r.endXPos == this.endXPos;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return topLeftOffset ^ bottomRightOffset;
}
/// <inheritdoc/>
public override Selection SetEndpoint(TextViewPosition endPosition)
{
return new RectangleSelection(textArea, startLine, startXPos, endPosition);
}
int GetVisualColumnFromXPos(int line, double xPos)
{
var vl = textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(line));
return vl.GetVisualColumn(new Point(xPos, 0), true);
}
/// <inheritdoc/>
public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e)
{
TextLocation newStartLocation = textArea.Document.GetLocation(e.GetNewOffset(topLeftOffset, AnchorMovementType.AfterInsertion));
TextLocation newEndLocation = textArea.Document.GetLocation(e.GetNewOffset(bottomRightOffset, AnchorMovementType.BeforeInsertion));
return new RectangleSelection(textArea,
new TextViewPosition(newStartLocation, GetVisualColumnFromXPos(newStartLocation.Line, startXPos)),
new TextViewPosition(newEndLocation, GetVisualColumnFromXPos(newEndLocation.Line, endXPos)));
}
/// <inheritdoc/>
public override void ReplaceSelectionWithText(string newText)
{
if (newText == null)
throw new ArgumentNullException("newText");
using (textArea.Document.RunUpdate()) {
TextViewPosition start = new TextViewPosition(document.GetLocation(topLeftOffset), GetVisualColumnFromXPos(startLine, startXPos));
TextViewPosition end = new TextViewPosition(document.GetLocation(bottomRightOffset), GetVisualColumnFromXPos(endLine, endXPos));
int insertionLength;
int totalInsertionLength = 0;
int firstInsertionLength = 0;
int editOffset = Math.Min(topLeftOffset, bottomRightOffset);
TextViewPosition pos;
if (NewLineFinder.NextNewLine(newText, 0) == SimpleSegment.Invalid) {
// insert same text into every line
foreach (SelectionSegment lineSegment in this.Segments.Reverse()) {
ReplaceSingleLineText(textArea, lineSegment, newText, out insertionLength);
totalInsertionLength += insertionLength;
firstInsertionLength = insertionLength;
}
int newEndOffset = editOffset + totalInsertionLength;
pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength));
textArea.Selection = new RectangleSelection(textArea, pos, Math.Max(startLine, endLine), GetXPos(textArea, pos));
} else {
string[] lines = newText.Split(NewLineFinder.NewlineStrings, segments.Count, StringSplitOptions.None);
int line = Math.Min(startLine, endLine);
for (int i = lines.Length - 1; i >= 0; i--) {
ReplaceSingleLineText(textArea, segments[i], lines[i], out insertionLength);
firstInsertionLength = insertionLength;
}
pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength));
textArea.ClearSelection();
}
textArea.Caret.Position = textArea.TextView.GetPosition(new Point(GetXPos(textArea, pos), textArea.TextView.GetVisualTopByDocumentLine(Math.Max(startLine, endLine)))).GetValueOrDefault();
}
}
void ReplaceSingleLineText(TextArea textArea, SelectionSegment lineSegment, string newText, out int insertionLength)
{
if (lineSegment.Length == 0) {
if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.StartOffset)) {
newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(document.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn));
textArea.Document.Insert(lineSegment.StartOffset, newText);
}
} else {
ISegment[] segmentsToDelete = textArea.GetDeletableSegments(lineSegment);
for (int i = segmentsToDelete.Length - 1; i >= 0; i--) {
if (i == segmentsToDelete.Length - 1) {
if (segmentsToDelete[i].Offset == SurroundingSegment.Offset && segmentsToDelete[i].Length == SurroundingSegment.Length) {
newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(document.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn));
}
textArea.Document.Replace(segmentsToDelete[i], newText);
} else {
textArea.Document.Remove(segmentsToDelete[i]);
}
}
}
insertionLength = newText.Length;
}
/// <summary>
/// Performs a rectangular paste operation.
/// </summary>
public static bool PerformRectangularPaste(TextArea textArea, TextViewPosition startPosition, string text, bool selectInsertedText)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
if (text == null)
throw new ArgumentNullException("text");
int newLineCount = text.Count(c => c == '\n'); // TODO might not work in all cases, but single \r line endings are really rare today.
TextLocation endLocation = new TextLocation(startPosition.Line + newLineCount, startPosition.Column);
if (endLocation.Line <= textArea.Document.LineCount) {
int endOffset = textArea.Document.GetOffset(endLocation);
if (textArea.Selection.EnableVirtualSpace || textArea.Document.GetLocation(endOffset) == endLocation) {
RectangleSelection rsel = new RectangleSelection(textArea, startPosition, endLocation.Line, GetXPos(textArea, startPosition));
rsel.ReplaceSelectionWithText(text);
if (selectInsertedText && textArea.Selection is RectangleSelection) {
RectangleSelection sel = (RectangleSelection)textArea.Selection;
textArea.Selection = new RectangleSelection(textArea, startPosition, sel.endLine, sel.endXPos);
}
return true;
}
}
return false;
}
/// <summary>
/// Gets the name of the entry in the DataObject that signals rectangle selections.
/// </summary>
public const string RectangularSelectionDataType = "AvalonEditRectangularSelection";
/// <inheritdoc/>
public override System.Windows.DataObject CreateDataObject(TextArea textArea)
{
var data = base.CreateDataObject(textArea);
if (EditingCommandHandler.ConfirmDataFormat(textArea, data, RectangularSelectionDataType)) {
MemoryStream isRectangle = new MemoryStream(1);
isRectangle.WriteByte(1);
data.SetData(RectangularSelectionDataType, isRectangle, false);
}
return data;
}
/// <inheritdoc/>
public override string ToString()
{
// It's possible that ToString() gets called on old (invalid) selections, e.g. for "change from... to..." debug message
// make sure we don't crash even when the desired locations don't exist anymore.
return string.Format("[RectangleSelection {0} {1} {2} to {3} {4} {5}]", startLine, topLeftOffset, startXPos, endLine, bottomRightOffset, endXPos);
}
}
}

302
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Selection.cs

@ -1,302 +0,0 @@ @@ -1,302 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Base class for selections.
/// </summary>
public abstract class Selection
{
/// <summary>
/// Creates a new simple selection that selects the text from startOffset to endOffset.
/// </summary>
public static Selection Create(TextArea textArea, int startOffset, int endOffset)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
if (startOffset == endOffset)
return textArea.emptySelection;
else
return new SimpleSelection(textArea,
new TextViewPosition(textArea.Document.GetLocation(startOffset)),
new TextViewPosition(textArea.Document.GetLocation(endOffset)));
}
internal static Selection Create(TextArea textArea, TextViewPosition start, TextViewPosition end)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
if (textArea.Document.GetOffset(start.Location) == textArea.Document.GetOffset(end.Location) && start.VisualColumn == end.VisualColumn)
return textArea.emptySelection;
else
return new SimpleSelection(textArea, start, end);
}
/// <summary>
/// Creates a new simple selection that selects the text in the specified segment.
/// </summary>
public static Selection Create(TextArea textArea, ISegment segment)
{
if (segment == null)
throw new ArgumentNullException("segment");
return Create(textArea, segment.Offset, segment.EndOffset);
}
internal readonly TextArea textArea;
/// <summary>
/// Constructor for Selection.
/// </summary>
protected Selection(TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
this.textArea = textArea;
}
/// <summary>
/// Gets the start position of the selection.
/// </summary>
public abstract TextViewPosition StartPosition { get; }
/// <summary>
/// Gets the end position of the selection.
/// </summary>
public abstract TextViewPosition EndPosition { get; }
/// <summary>
/// Gets the selected text segments.
/// </summary>
public abstract IEnumerable<SelectionSegment> Segments { get; }
/// <summary>
/// Gets the smallest segment that contains all segments in this selection.
/// May return null if the selection is empty.
/// </summary>
public abstract ISegment SurroundingSegment { get; }
/// <summary>
/// Replaces the selection with the specified text.
/// </summary>
public abstract void ReplaceSelectionWithText(string newText);
internal string AddSpacesIfRequired(string newText, TextViewPosition start, TextViewPosition end)
{
if (EnableVirtualSpace && InsertVirtualSpaces(newText, start, end)) {
var line = textArea.Document.GetLineByNumber(start.Line);
string lineText = textArea.Document.GetText(line);
var vLine = textArea.TextView.GetOrConstructVisualLine(line);
int colDiff = start.VisualColumn - vLine.VisualLengthWithEndOfLineMarker;
if (colDiff > 0) {
string additionalSpaces = "";
if (!textArea.Options.ConvertTabsToSpaces && lineText.Trim('\t').Length == 0) {
int tabCount = (int)(colDiff / textArea.Options.IndentationSize);
additionalSpaces = new string('\t', tabCount);
colDiff -= tabCount * textArea.Options.IndentationSize;
}
additionalSpaces += new string(' ', colDiff);
return additionalSpaces + newText;
}
}
return newText;
}
bool InsertVirtualSpaces(string newText, TextViewPosition start, TextViewPosition end)
{
return (!string.IsNullOrEmpty(newText) || !(IsInVirtualSpace(start) && IsInVirtualSpace(end)))
&& newText != "\r\n"
&& newText != "\n"
&& newText != "\r";
}
bool IsInVirtualSpace(TextViewPosition pos)
{
return pos.VisualColumn > textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(pos.Line)).VisualLength;
}
/// <summary>
/// Updates the selection when the document changes.
/// </summary>
public abstract Selection UpdateOnDocumentChange(DocumentChangeEventArgs e);
/// <summary>
/// Gets whether the selection is empty.
/// </summary>
public virtual bool IsEmpty {
get { return Length == 0; }
}
/// <summary>
/// Gets whether virtual space is enabled for this selection.
/// </summary>
public virtual bool EnableVirtualSpace {
get { return textArea.Options.EnableVirtualSpace; }
}
/// <summary>
/// Gets the selection length.
/// </summary>
public abstract int Length { get; }
/// <summary>
/// Returns a new selection with the changed end point.
/// </summary>
/// <exception cref="NotSupportedException">Cannot set endpoint for empty selection</exception>
public abstract Selection SetEndpoint(TextViewPosition endPosition);
/// <summary>
/// If this selection is empty, starts a new selection from <paramref name="startPosition"/> to
/// <paramref name="endPosition"/>, otherwise, changes the endpoint of this selection.
/// </summary>
public abstract Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition);
/// <summary>
/// Gets whether the selection is multi-line.
/// </summary>
public virtual bool IsMultiline {
get {
ISegment surroundingSegment = this.SurroundingSegment;
if (surroundingSegment == null)
return false;
int start = surroundingSegment.Offset;
int end = start + surroundingSegment.Length;
var document = textArea.Document;
if (document == null)
throw ThrowUtil.NoDocumentAssigned();
return document.GetLineByOffset(start) != document.GetLineByOffset(end);
}
}
/// <summary>
/// Gets the selected text.
/// </summary>
public virtual string GetText()
{
var document = textArea.Document;
if (document == null)
throw ThrowUtil.NoDocumentAssigned();
StringBuilder b = null;
string text = null;
foreach (ISegment s in Segments) {
if (text != null) {
if (b == null)
b = new StringBuilder(text);
else
b.Append(text);
}
text = document.GetText(s);
}
if (b != null) {
if (text != null) b.Append(text);
return b.ToString();
} else {
return text ?? string.Empty;
}
}
/// <summary>
/// Creates a HTML fragment for the selected text.
/// </summary>
public string CreateHtmlFragment(HtmlOptions options)
{
if (options == null)
throw new ArgumentNullException("options");
IHighlighter highlighter = textArea.GetService(typeof(IHighlighter)) as IHighlighter;
StringBuilder html = new StringBuilder();
bool first = true;
foreach (ISegment selectedSegment in this.Segments) {
if (first)
first = false;
else
html.AppendLine("<br>");
html.Append(HtmlClipboard.CreateHtmlFragment(textArea.Document, highlighter, selectedSegment, options));
}
return html.ToString();
}
/// <inheritdoc/>
public abstract override bool Equals(object obj);
/// <inheritdoc/>
public abstract override int GetHashCode();
/// <summary>
/// Gets whether the specified offset is included in the selection.
/// </summary>
/// <returns>True, if the selection contains the offset (selection borders inclusive);
/// otherwise, false.</returns>
public virtual bool Contains(int offset)
{
if (this.IsEmpty)
return false;
if (this.SurroundingSegment.Contains(offset, 0)) {
foreach (ISegment s in this.Segments) {
if (s.Contains(offset, 0)) {
return true;
}
}
}
return false;
}
/// <summary>
/// Creates a data object containing the selection's text.
/// </summary>
public virtual DataObject CreateDataObject(TextArea textArea)
{
DataObject data = new DataObject();
// Ensure we use the appropriate newline sequence for the OS
string text = TextUtilities.NormalizeNewLines(GetText(), Environment.NewLine);
// Enable drag/drop to Word, Notepad++ and others
if (EditingCommandHandler.ConfirmDataFormat(textArea, data, DataFormats.UnicodeText)) {
data.SetText(text);
}
// Enable drag/drop to SciTe:
// We cannot use SetText, thus we need to use typeof(string).FullName as data format.
// new DataObject(object) calls SetData(object), which in turn calls SetData(Type, data),
// which then uses Type.FullName as format.
// We immitate that behavior here as well:
if (EditingCommandHandler.ConfirmDataFormat(textArea, data, typeof(string).FullName)) {
data.SetData(typeof(string).FullName, text);
}
// Also copy text in HTML format to clipboard - good for pasting text into Word
// or to the SharpDevelop forums.
if (EditingCommandHandler.ConfirmDataFormat(textArea, data, DataFormats.Html)) {
HtmlClipboard.SetHtml(data, CreateHtmlFragment(new HtmlOptions(textArea.Options)));
}
return data;
}
}
}

74
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionColorizer.cs

@ -1,74 +0,0 @@ @@ -1,74 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using ICSharpCode.AvalonEdit.Rendering;
#if NREFACTORY
using ICSharpCode.NRefactory.Editor;
#endif
namespace ICSharpCode.AvalonEdit.Editing
{
sealed class SelectionColorizer : ColorizingTransformer
{
TextArea textArea;
public SelectionColorizer(TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
this.textArea = textArea;
}
protected override void Colorize(ITextRunConstructionContext context)
{
// if SelectionForeground is null, keep the existing foreground color
if (textArea.SelectionForeground == null)
return;
int lineStartOffset = context.VisualLine.FirstDocumentLine.Offset;
int lineEndOffset = context.VisualLine.LastDocumentLine.Offset + context.VisualLine.LastDocumentLine.TotalLength;
foreach (SelectionSegment segment in textArea.Selection.Segments) {
int segmentStart = segment.StartOffset;
int segmentEnd = segment.EndOffset;
if (segmentEnd <= lineStartOffset)
continue;
if (segmentStart >= lineEndOffset)
continue;
int startColumn;
if (segmentStart < lineStartOffset)
startColumn = 0;
else
startColumn = context.VisualLine.ValidateVisualColumn(segment.StartOffset, segment.StartVisualColumn, textArea.Selection.EnableVirtualSpace);
int endColumn;
if (segmentEnd > lineEndOffset)
endColumn = textArea.Selection.EnableVirtualSpace ? int.MaxValue : context.VisualLine.VisualLengthWithEndOfLineMarker;
else
endColumn = context.VisualLine.ValidateVisualColumn(segment.EndOffset, segment.EndVisualColumn, textArea.Selection.EnableVirtualSpace);
ChangeVisualElements(
startColumn, endColumn,
element => {
element.TextRunProperties.SetForegroundBrush(textArea.SelectionForeground);
});
}
}
}
}

68
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionLayer.cs

@ -1,68 +0,0 @@ @@ -1,68 +0,0 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Windows;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Rendering;
namespace ICSharpCode.AvalonEdit.Editing
{
sealed class SelectionLayer : Layer, IWeakEventListener
{
readonly TextArea textArea;
public SelectionLayer(TextArea textArea) : base(textArea.TextView, KnownLayer.Selection)
{
this.IsHitTestVisible = false;
this.textArea = textArea;
TextViewWeakEventManager.VisualLinesChanged.AddListener(textView, this);
TextViewWeakEventManager.ScrollOffsetChanged.AddListener(textView, this);
}
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(TextViewWeakEventManager.VisualLinesChanged)
|| managerType == typeof(TextViewWeakEventManager.ScrollOffsetChanged))
{
InvalidateVisual();
return true;
}
return false;
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
geoBuilder.AlignToMiddleOfPixels = true;
geoBuilder.ExtendToFullWidthAtLineEnd = textArea.Selection.EnableVirtualSpace;
geoBuilder.CornerRadius = textArea.SelectionCornerRadius;
foreach (var segment in textArea.Selection.Segments) {
geoBuilder.AddSegment(textView, segment);
}
Geometry geometry = geoBuilder.CreateGeometry();
if (geometry != null) {
drawingContext.DrawGeometry(textArea.SelectionBrush, textArea.SelectionBorder, geometry);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save