aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Utils/CharRope.cs
blob: 844ab95447c276c1e35ab528365c3a9fc9a6f56d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// 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.Globalization;
using System.Text;

namespace Tango.Scripting.Editors.Utils
{
	/// <summary>
	/// Poor man's template specialization: extension methods for Rope&lt;char&gt;.
	/// </summary>
	public static class CharRope
	{
		/// <summary>
		/// Creates a new rope from the specified text.
		/// </summary>
		public static Rope<char> Create(string text)
		{
			if (text == null)
				throw new ArgumentNullException("text");
			return new Rope<char>(InitFromString(text));
		}
		
		/// <summary>
		/// Retrieves the text for a portion of the rope.
		/// Runs in O(lg N + M), where M=<paramref name="length"/>.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception>
		/// <remarks>
		/// This method counts as a read access and may be called concurrently to other read accesses.
		/// </remarks>
		public static string ToString(this Rope<char> rope, int startIndex, int length)
		{
			if (rope == null)
				throw new ArgumentNullException("rope");
			if (length == 0)
				return string.Empty;
			char[] buffer = new char[length];
			rope.CopyTo(startIndex, buffer, 0, length);
			return new string(buffer);
		}
		
		/// <summary>
		/// Retrieves the text for a portion of the rope and writes it to the specified string builder.
		/// Runs in O(lg N + M), where M=<paramref name="length"/>.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception>
		/// <remarks>
		/// This method counts as a read access and may be called concurrently to other read accesses.
		/// </remarks>
		public static void WriteTo(this Rope<char> rope, StringBuilder output, int startIndex, int length)
		{
			if (rope == null)
				throw new ArgumentNullException("rope");
			if (output == null)
				throw new ArgumentNullException("output");
			rope.VerifyRange(startIndex, length);
			rope.root.WriteTo(startIndex, output, length);
		}
		
		/// <summary>
		/// Appends text to this rope.
		/// Runs in O(lg N + M).
		/// </summary>
		/// <exception cref="ArgumentNullException">newElements is null.</exception>
		public static void AddText(this Rope<char> rope, string text)
		{
			InsertText(rope, rope.Length, text);
		}
		
		/// <summary>
		/// Inserts text into this rope.
		/// Runs in O(lg N + M).
		/// </summary>
		/// <exception cref="ArgumentNullException">newElements is null.</exception>
		/// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception>
		public static void InsertText(this Rope<char> rope, int index, string text)
		{
			if (rope == null)
				throw new ArgumentNullException("rope");
			rope.InsertRange(index, text.ToCharArray(), 0, text.Length);
			/*if (index < 0 || index > rope.Length) {
				throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + rope.Length.ToString(CultureInfo.InvariantCulture));
			}
			if (text == null)
				throw new ArgumentNullException("text");
			if (text.Length == 0)
				return;
			rope.root = rope.root.Insert(index, text);
			rope.OnChanged();*/
		}
		
		internal static RopeNode<char> InitFromString(string text)
		{
			if (text.Length == 0) {
				return RopeNode<char>.emptyRopeNode;
			}
			RopeNode<char> node = RopeNode<char>.CreateNodes(text.Length);
			FillNode(node, text, 0);
			return node;
		}
		
		static void FillNode(RopeNode<char> node, string text, int start)
		{
			if (node.contents != null) {
				text.CopyTo(start, node.contents, 0, node.length);
			} else {
				FillNode(node.left, text, start);
				FillNode(node.right, text, start + node.left.length);
			}
		}
		
		internal static void WriteTo(this RopeNode<char> node, int index, StringBuilder output, int count)
		{
			if (node.height == 0) {
				if (node.contents == null) {
					// function node
					node.GetContentNode().WriteTo(index, output, count);
				} else {
					// leaf node: append data
					output.Append(node.contents, index, count);
				}
			} else {
				// concat node: do recursive calls
				if (index + count <= node.left.length) {
					node.left.WriteTo(index, output, count);
				} else if (index >= node.left.length) {
					node.right.WriteTo(index - node.left.length, output, count);
				} else {
					int amountInLeft = node.left.length - index;
					node.left.WriteTo(index, output, amountInLeft);
					node.right.WriteTo(0, output, count - amountInLeft);
				}
			}
		}
		
		/// <summary>
		/// Gets the index of the first occurrence of any element in the specified array.
		/// </summary>
		/// <param name="rope">The target rope.</param>
		/// <param name="anyOf">Array of characters being searched.</param>
		/// <param name="startIndex">Start index of the search.</param>
		/// <param name="length">Length of the area to search.</param>
		/// <returns>The first index where any character was found; or -1 if no occurrence was found.</returns>
		public static int IndexOfAny(this Rope<char> rope, char[] anyOf, int startIndex, int length)
		{
			if (rope == null)
				throw new ArgumentNullException("rope");
			if (anyOf == null)
				throw new ArgumentNullException("anyOf");
			rope.VerifyRange(startIndex, length);
			
			while (length > 0) {
				var entry = rope.FindNodeUsingCache(startIndex).UnsafePeek();
				char[] contents = entry.node.contents;
				int startWithinNode = startIndex - entry.nodeStartIndex;
				int nodeLength = Math.Min(entry.node.length, startWithinNode + length);
				for (int i = startIndex - entry.nodeStartIndex; i < nodeLength; i++) {
					char element = contents[i];
					foreach (char needle in anyOf) {
						if (element == needle)
							return entry.nodeStartIndex + i;
					}
				}
				length -= nodeLength - startWithinNode;
				startIndex = entry.nodeStartIndex + nodeLength;
			}
			return -1;
		}
	}
}
="w"> { 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); 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); } /// <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 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 = 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; } } } }