aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/TEMP/Tango.Scripting/Tango.Scripting.Editors/Document/TextSegment.cs
blob: d1834c2144ed130b606510ac073e5d5a2c5fc44f (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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// 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.Diagnostics;

namespace Tango.Scripting.Editors.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 + "]";
		}
	}
}