1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 //
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
21 //
22 // Authors:
23 //	Peter Bartok	pbartok@novell.com
24 //
25 //
26 
27 // NOT COMPLETE
28 
29 // There's still plenty of things missing, I've got most of it planned, just hadn't had
30 // the time to write it all yet.
31 // Stuff missing (in no particular order):
32 // - Align text after RecalculateLine
33 // - Implement tag types for hotlinks, etc.
34 // - Implement CaretPgUp/PgDown
35 
36 // NOTE:
37 // selection_start.pos and selection_end.pos are 0-based
38 // selection_start.pos = first selected char
39 // selection_end.pos = first NOT-selected char
40 //
41 // FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for
42 // the first character on a line; the reason is that 0 is the position
43 // *before* the first character on a line
44 
45 
46 #undef Debug
47 
48 using System;
49 using System.Collections;
50 using System.Drawing;
51 using System.Drawing.Text;
52 using System.Text;
53 using RTF=System.Windows.Forms.RTF;
54 
55 namespace System.Windows.Forms {
56 	internal enum LineColor {
57 		Red	= 0,
58 		Black	= 1
59 	}
60 
61 	internal enum CaretSelection {
62 		Position,	// Selection=Caret
63 		Word,		// Selection=Word under caret
64 		Line		// Selection=Line under caret
65 	}
66 
67 	[Flags]
68 	internal enum FormatSpecified {
69 		None,
70 
71 		BackColor = 2,
72 		Font = 4,
73 		Color = 8,
74 	}
75 
76 	internal enum CaretDirection {
77 		CharForward,	// Move a char to the right
78 		CharBack,	// Move a char to the left
79 		LineUp,		// Move a line up
80 		LineDown,	// Move a line down
81 		Home,		// Move to the beginning of the line
82 		End,		// Move to the end of the line
83 		PgUp,		// Move one page up
84 		PgDn,		// Move one page down
85 		CtrlPgUp,	// Move caret to the first visible char in the viewport
86 		CtrlPgDn,	// Move caret to the last visible char in the viewport
87 		CtrlHome,	// Move to the beginning of the document
88 		CtrlEnd,	// Move to the end of the document
89 		WordBack,	// Move to the beginning of the previous word (or beginning of line)
90 		WordForward,	// Move to the beginning of the next word (or end of line)
91 		SelectionStart,	// Move to the beginning of the current selection
92 		SelectionEnd,	// Move to the end of the current selection
93 		CharForwardNoWrap,   // Move a char forward, but don't wrap onto the next line
94 		CharBackNoWrap      // Move a char backward, but don't wrap onto the previous line
95 	}
96 
97 	internal enum LineEnding {
98 		Wrap = 1,    // line wraps to the next line
99 		Limp = 2,    // \r
100 		Hard = 4,    // \r\n
101 		Soft = 8,    // \r\r\n
102 		Rich = 16,    // \n
103 
104 		None = 0
105 	}
106 
107 	internal class Document : ICloneable, IEnumerable {
108 		#region Structures
109 		// FIXME - go through code and check for places where
110 		// we do explicit comparisons instead of using the compare overloads
111 		internal struct Marker {
112 			internal Line		line;
113 			internal LineTag	tag;
114 			internal int		pos;
115 			internal int		height;
116 
operator <System.Windows.Forms.Document.Marker117 			public static bool operator<(Marker lhs, Marker rhs) {
118 				if (lhs.line.line_no < rhs.line.line_no) {
119 					return true;
120 				}
121 
122 				if (lhs.line.line_no == rhs.line.line_no) {
123 					if (lhs.pos < rhs.pos) {
124 						return true;
125 					}
126 				}
127 				return false;
128 			}
129 
operator >System.Windows.Forms.Document.Marker130 			public static bool operator>(Marker lhs, Marker rhs) {
131 				if (lhs.line.line_no > rhs.line.line_no) {
132 					return true;
133 				}
134 
135 				if (lhs.line.line_no == rhs.line.line_no) {
136 					if (lhs.pos > rhs.pos) {
137 						return true;
138 					}
139 				}
140 				return false;
141 			}
142 
operator ==System.Windows.Forms.Document.Marker143 			public static bool operator==(Marker lhs, Marker rhs) {
144 				if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
145 					return true;
146 				}
147 				return false;
148 			}
149 
operator !=System.Windows.Forms.Document.Marker150 			public static bool operator!=(Marker lhs, Marker rhs) {
151 				if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
152 					return true;
153 				}
154 				return false;
155 			}
156 
CombineSystem.Windows.Forms.Document.Marker157 			public void Combine(Line move_to_line, int move_to_line_length) {
158 				line = move_to_line;
159 				pos += move_to_line_length;
160 				tag = LineTag.FindTag(line, pos);
161 			}
162 
163 			// This is for future use, right now Document.Split does it by hand, with some added shortcut logic
SplitSystem.Windows.Forms.Document.Marker164 			public void Split(Line move_to_line, int split_at) {
165 				line = move_to_line;
166 				pos -= split_at;
167 				tag = LineTag.FindTag(line, pos);
168 			}
169 
EqualsSystem.Windows.Forms.Document.Marker170 			public override bool Equals(object obj) {
171 				   return this==(Marker)obj;
172 			}
173 
GetHashCodeSystem.Windows.Forms.Document.Marker174 			public override int GetHashCode() {
175 				return base.GetHashCode ();
176 			}
177 
ToStringSystem.Windows.Forms.Document.Marker178 			public override string ToString() {
179 				return "Marker Line " + line + ", Position " + pos;
180 			}
181 
182 		}
183 		#endregion Structures
184 
185 		#region Local Variables
186 		private Line		document;
187 		private int		lines;
188 		private Line		sentinel;
189 		private int		document_id;
190 		private Random		random = new Random();
191 		internal string		password_char;
192 		private StringBuilder	password_cache;
193 		private bool		calc_pass;
194 		private int		char_count;
195 		private bool		enable_links;
196 
197 		// For calculating widths/heights
198 		public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
199 
200 		private int 		recalc_suspended;
201 		private bool		recalc_pending;
202 		private int		recalc_start = 1;   // This starts at one, since lines are 1 based
203 		private int		recalc_end;
204 		private bool		recalc_optimize;
205 
206 		private int             update_suspended;
207 		private bool update_pending;
208 		private int update_start = 1;
209 
210 		internal bool		multiline;
211 		internal HorizontalAlignment alignment;
212 		internal bool		wrap;
213 
214 		internal UndoManager	undo;
215 
216 		internal Marker		caret;
217 		internal Marker		selection_start;
218 		internal Marker		selection_end;
219 		internal bool		selection_visible;
220 		internal Marker		selection_anchor;
221 		internal Marker		selection_prev;
222 		internal bool		selection_end_anchor;
223 
224 		internal int		viewport_x;
225 		internal int		viewport_y;		// The visible area of the document
226 		internal int		offset_x;
227 		internal int		offset_y;
228 		internal int		viewport_width;
229 		internal int		viewport_height;
230 
231 		internal int		document_x;		// Width of the document
232 		internal int		document_y;		// Height of the document
233 
234 		internal int		crlf_size;		// 1 or 2, depending on whether we use \r\n or just \n
235 
236 		internal TextBoxBase	owner;			// Who's owning us?
237 		static internal int	caret_width = 1;
238 		static internal int	caret_shift = 1;
239 
240 		internal int left_margin = 2;  // A left margin for all lines
241 		internal int top_margin = 2;
242 		internal int right_margin = 2;
243 		#endregion	// Local Variables
244 
245 		#region Constructors
Document(TextBoxBase owner)246 		internal Document (TextBoxBase owner)
247 		{
248 			lines = 0;
249 
250 			this.owner = owner;
251 
252 			multiline = true;
253 			password_char = "";
254 			calc_pass = false;
255 			recalc_pending = false;
256 
257 			// Tree related stuff
258 			sentinel = new Line (this, LineEnding.None);
259 			sentinel.color = LineColor.Black;
260 
261 			document = sentinel;
262 
263 			// We always have a blank line
264 			owner.HandleCreated += new EventHandler(owner_HandleCreated);
265 			owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
266 
267 			Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
268 
269 			undo = new UndoManager (this);
270 
271 			selection_visible = false;
272 			selection_start.line = this.document;
273 			selection_start.pos = 0;
274 			selection_start.tag = selection_start.line.tags;
275 			selection_end.line = this.document;
276 			selection_end.pos = 0;
277 			selection_end.tag = selection_end.line.tags;
278 			selection_anchor.line = this.document;
279 			selection_anchor.pos = 0;
280 			selection_anchor.tag = selection_anchor.line.tags;
281 			caret.line = this.document;
282 			caret.pos = 0;
283 			caret.tag = caret.line.tags;
284 
285 			viewport_x = 0;
286 			viewport_y = 0;
287 
288 			offset_x = 0;
289 			offset_y = 0;
290 
291 			crlf_size = 2;
292 
293 			// Default selection is empty
294 
295 			document_id = random.Next();
296 
297 			string_format.Trimming = StringTrimming.None;
298 			string_format.FormatFlags = StringFormatFlags.DisplayFormatControl;
299 
300 			UpdateMargins ();
301 		}
302 		#endregion
303 
304 		#region Internal Properties
305 		internal Line Root {
306 			get {
307 				return document;
308 			}
309 
310 			set {
311 				document = value;
312 			}
313 		}
314 
315 		// UIA: Method used via reflection in TextRangeProvider
316 		internal int Lines {
317 			get {
318 				return lines;
319 			}
320 		}
321 
322 		internal Line CaretLine {
323 			get {
324 				return caret.line;
325 			}
326 		}
327 
328 		internal int CaretPosition {
329 			get {
330 				return caret.pos;
331 			}
332 		}
333 
334 		internal Point Caret {
335 			get {
336 				return new Point((int)caret.tag.Line.widths[caret.pos] + caret.line.X, caret.line.Y);
337 			}
338 		}
339 
340 		internal LineTag CaretTag {
341 			get {
342 				return caret.tag;
343 			}
344 
345 			set {
346 				caret.tag = value;
347 			}
348 		}
349 
350 		internal int CRLFSize {
351 			get {
352 				return crlf_size;
353 			}
354 
355 			set {
356 				crlf_size = value;
357 			}
358 		}
359 
360 		/// <summary>
361 		///  Whether text is scanned for links
362 		/// </summary>
363 		internal bool EnableLinks {
364 			get { return enable_links; }
365 			set { enable_links = value; }
366 		}
367 
368 		internal string PasswordChar {
369 			get {
370 				return password_char;
371 			}
372 
373 			set {
374 				password_char = value;
375 				PasswordCache.Length = 0;
376 				if ((password_char.Length != 0) && (password_char[0] != '\0')) {
377 					calc_pass = true;
378 				} else {
379 					calc_pass = false;
380 				}
381 			}
382 		}
383 
384 		private StringBuilder PasswordCache {
385 			get {
386 				if (password_cache == null)
387 					  password_cache = new StringBuilder();
388 				return password_cache;
389 			}
390 		}
391 
392 		internal int ViewPortX {
393 			get {
394 				return viewport_x;
395 			}
396 
397 			set {
398 				viewport_x = value;
399 			}
400 		}
401 
402 		internal int Length {
403 			get {
404 				return char_count + lines - 1;	// Add \n for each line but the last
405 			}
406 		}
407 
408 		private int CharCount {
409 			get {
410 				return char_count;
411 			}
412 
413 			set {
414 				char_count = value;
415 
416 				if (LengthChanged != null) {
417 					LengthChanged(this, EventArgs.Empty);
418 				}
419 			}
420 		}
421 
422 		internal int ViewPortY {
423 			get {
424 				return viewport_y;
425 			}
426 
427 			set {
428 				viewport_y = value;
429 			}
430 		}
431 
432 		internal int OffsetX
433 		{
434 			get
435 			{
436 				return offset_x;
437 			}
438 
439 			set
440 			{
441 				offset_x = value;
442 			}
443 		}
444 
445 		internal int OffsetY
446 		{
447 			get
448 			{
449 				return offset_y;
450 			}
451 
452 			set
453 			{
454 				offset_y = value;
455 			}
456 		}
457 
458 		internal int ViewPortWidth {
459 			get {
460 				return viewport_width;
461 			}
462 
463 			set {
464 				viewport_width = value;
465 			}
466 		}
467 
468 		internal int ViewPortHeight {
469 			get {
470 				return viewport_height;
471 			}
472 
473 			set {
474 				viewport_height = value;
475 			}
476 		}
477 
478 
479 		internal int Width {
480 			get {
481 				return this.document_x;
482 			}
483 		}
484 
485 		internal int Height {
486 			get {
487 				return this.document_y;
488 			}
489 		}
490 
491 		internal bool SelectionVisible {
492 			get {
493 				return selection_visible;
494 			}
495 		}
496 
497 		internal bool Wrap {
498 			get {
499 				return wrap;
500 			}
501 
502 			set {
503 				wrap = value;
504 			}
505 		}
506 
507 		#endregion	// Internal Properties
508 
509 		#region Private Methods
510 
UpdateMargins()511 		internal void UpdateMargins ()
512 		{
513 			switch (owner.actual_border_style) {
514 				case BorderStyle.None:
515 					left_margin = 0;
516 					top_margin = 0;
517 					right_margin = 1;
518 					break;
519 				case BorderStyle.FixedSingle:
520 					left_margin = 2;
521 					top_margin = 2;
522 					right_margin = 3;
523 					break;
524 				case BorderStyle.Fixed3D:
525 					left_margin = 1;
526 					top_margin = 1;
527 					right_margin = 2;
528 					break;
529 			}
530 		}
531 
SuspendRecalc()532 		internal void SuspendRecalc ()
533 		{
534 			if (recalc_suspended == 0) {
535 				recalc_start = int.MaxValue;
536 				recalc_end = int.MinValue;
537 			}
538 
539 			recalc_suspended++;
540 		}
541 
ResumeRecalc(bool immediate_update)542 		internal void ResumeRecalc (bool immediate_update)
543 		{
544 			if (recalc_suspended > 0)
545 				recalc_suspended--;
546 
547 			if (recalc_suspended == 0 && (immediate_update || recalc_pending) && !(recalc_start == int.MaxValue && recalc_end == int.MinValue)) {
548 				RecalculateDocument (owner.CreateGraphicsInternal (), recalc_start, recalc_end, recalc_optimize);
549 				recalc_pending = false;
550 			}
551 		}
552 
SuspendUpdate()553 		internal void SuspendUpdate ()
554 		{
555 			update_suspended++;
556 		}
557 
ResumeUpdate(bool immediate_update)558 		internal void ResumeUpdate (bool immediate_update)
559 		{
560 			if (update_suspended > 0)
561 				update_suspended--;
562 
563 			if (immediate_update && update_suspended == 0 && update_pending) {
564 				UpdateView (GetLine (update_start), 0);
565 				update_pending = false;
566 			}
567 		}
568 
569 		// For debugging
DumpTree(Line line, bool with_tags)570 		internal int DumpTree(Line line, bool with_tags) {
571 			int	total;
572 
573 			total = 1;
574 
575 			Console.Write("Line {0} [# {1}], Y: {2}, ending style: {3},  Text: '{4}'",
576 					line.line_no, line.GetHashCode(), line.Y, line.ending,
577 					line.text != null ? line.text.ToString() : "undefined");
578 
579 			if (line.left == sentinel) {
580 				Console.Write(", left = sentinel");
581 			} else if (line.left == null) {
582 				Console.Write(", left = NULL");
583 			}
584 
585 			if (line.right == sentinel) {
586 				Console.Write(", right = sentinel");
587 			} else if (line.right == null) {
588 				Console.Write(", right = NULL");
589 			}
590 
591 			Console.WriteLine("");
592 
593 			if (with_tags) {
594 				LineTag	tag;
595 				int	count;
596 				int	length;
597 
598 				tag = line.tags;
599 				count = 1;
600 				length = 0;
601 				Console.Write("   Tags: ");
602 				while (tag != null) {
603 					Console.Write("{0} <{1}>-<{2}>", count++, tag.Start, tag.End
604 							/*line.text.ToString (tag.start - 1, tag.length)*/);
605 					length += tag.Length;
606 
607 					if (tag.Line != line) {
608 						Console.Write("BAD line link");
609 						throw new Exception("Bad line link in tree");
610 					}
611 					tag = tag.Next;
612 					if (tag != null) {
613 						Console.Write(", ");
614 					}
615 				}
616 				if (length > line.text.Length) {
617 					throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
618 				} else if (length < line.text.Length) {
619 					throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
620 				}
621 				Console.WriteLine("");
622 			}
623 			if (line.left != null) {
624 				if (line.left != sentinel) {
625 					total += DumpTree(line.left, with_tags);
626 				}
627 			} else {
628 				if (line != sentinel) {
629 					throw new Exception("Left should not be NULL");
630 				}
631 			}
632 
633 			if (line.right != null) {
634 				if (line.right != sentinel) {
635 					total += DumpTree(line.right, with_tags);
636 				}
637 			} else {
638 				if (line != sentinel) {
639 					throw new Exception("Right should not be NULL");
640 				}
641 			}
642 
643 			for (int i = 1; i <= this.lines; i++) {
644 				if (GetLine(i) == null) {
645 					throw new Exception(String.Format("Hole in line order, missing {0}", i));
646 				}
647 			}
648 
649 			if (line == this.Root) {
650 				if (total < this.lines) {
651 					throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
652 				} else if (total > this.lines) {
653 					throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
654 				}
655 			}
656 
657 			return total;
658 		}
659 
SetSelectionVisible(bool value)660 		private void SetSelectionVisible (bool value)
661 		{
662 			bool old_selection_visible = selection_visible;
663 			selection_visible = value;
664 
665 			// cursor and selection are enemies, we can't have both in the same room at the same time
666 			if (owner.IsHandleCreated && !owner.show_caret_w_selection)
667 				XplatUI.CaretVisible (owner.Handle, !selection_visible);
668 			if (UIASelectionChanged != null && (selection_visible || old_selection_visible))
669 				UIASelectionChanged (this, EventArgs.Empty);
670 		}
671 
DecrementLines(int line_no)672 		private void DecrementLines(int line_no) {
673 			int	current;
674 
675 			current = line_no;
676 			while (current <= lines) {
677 				GetLine(current).line_no--;
678 				current++;
679 			}
680 			return;
681 		}
682 
IncrementLines(int line_no)683 		private void IncrementLines(int line_no) {
684 			int	current;
685 
686 			current = this.lines;
687 			while (current >= line_no) {
688 				GetLine(current).line_no++;
689 				current--;
690 			}
691 			return;
692 		}
693 
RebalanceAfterAdd(Line line1)694 		private void RebalanceAfterAdd(Line line1) {
695 			Line	line2;
696 
697 			while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
698 				if (line1.parent == line1.parent.parent.left) {
699 					line2 = line1.parent.parent.right;
700 
701 					if ((line2 != null) && (line2.color == LineColor.Red)) {
702 						line1.parent.color = LineColor.Black;
703 						line2.color = LineColor.Black;
704 						line1.parent.parent.color = LineColor.Red;
705 						line1 = line1.parent.parent;
706 					} else {
707 						if (line1 == line1.parent.right) {
708 							line1 = line1.parent;
709 							RotateLeft(line1);
710 						}
711 
712 						line1.parent.color = LineColor.Black;
713 						line1.parent.parent.color = LineColor.Red;
714 
715 						RotateRight(line1.parent.parent);
716 					}
717 				} else {
718 					line2 = line1.parent.parent.left;
719 
720 					if ((line2 != null) && (line2.color == LineColor.Red)) {
721 						line1.parent.color = LineColor.Black;
722 						line2.color = LineColor.Black;
723 						line1.parent.parent.color = LineColor.Red;
724 						line1 = line1.parent.parent;
725 					} else {
726 						if (line1 == line1.parent.left) {
727 							line1 = line1.parent;
728 							RotateRight(line1);
729 						}
730 
731 						line1.parent.color = LineColor.Black;
732 						line1.parent.parent.color = LineColor.Red;
733 						RotateLeft(line1.parent.parent);
734 					}
735 				}
736 			}
737 			document.color = LineColor.Black;
738 		}
739 
RebalanceAfterDelete(Line line1)740 		private void RebalanceAfterDelete(Line line1) {
741 			Line line2;
742 
743 			while ((line1 != document) && (line1.color == LineColor.Black)) {
744 				if (line1 == line1.parent.left) {
745 					line2 = line1.parent.right;
746 					if (line2.color == LineColor.Red) {
747 						line2.color = LineColor.Black;
748 						line1.parent.color = LineColor.Red;
749 						RotateLeft(line1.parent);
750 						line2 = line1.parent.right;
751 					}
752 					if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
753 						line2.color = LineColor.Red;
754 						line1 = line1.parent;
755 					} else {
756 						if (line2.right.color == LineColor.Black) {
757 							line2.left.color = LineColor.Black;
758 							line2.color = LineColor.Red;
759 							RotateRight(line2);
760 							line2 = line1.parent.right;
761 						}
762 						line2.color = line1.parent.color;
763 						line1.parent.color = LineColor.Black;
764 						line2.right.color = LineColor.Black;
765 						RotateLeft(line1.parent);
766 						line1 = document;
767 					}
768 				} else {
769 					line2 = line1.parent.left;
770 					if (line2.color == LineColor.Red) {
771 						line2.color = LineColor.Black;
772 						line1.parent.color = LineColor.Red;
773 						RotateRight(line1.parent);
774 						line2 = line1.parent.left;
775 					}
776 					if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
777 						line2.color = LineColor.Red;
778 						line1 = line1.parent;
779 					} else {
780 						if (line2.left.color == LineColor.Black) {
781 							line2.right.color = LineColor.Black;
782 							line2.color = LineColor.Red;
783 							RotateLeft(line2);
784 							line2 = line1.parent.left;
785 						}
786 						line2.color = line1.parent.color;
787 						line1.parent.color = LineColor.Black;
788 						line2.left.color = LineColor.Black;
789 						RotateRight(line1.parent);
790 						line1 = document;
791 					}
792 				}
793 			}
794 			line1.color = LineColor.Black;
795 		}
796 
RotateLeft(Line line1)797 		private void RotateLeft(Line line1) {
798 			Line	line2 = line1.right;
799 
800 			line1.right = line2.left;
801 
802 			if (line2.left != sentinel) {
803 				line2.left.parent = line1;
804 			}
805 
806 			if (line2 != sentinel) {
807 				line2.parent = line1.parent;
808 			}
809 
810 			if (line1.parent != null) {
811 				if (line1 == line1.parent.left) {
812 					line1.parent.left = line2;
813 				} else {
814 					line1.parent.right = line2;
815 				}
816 			} else {
817 				document = line2;
818 			}
819 
820 			line2.left = line1;
821 			if (line1 != sentinel) {
822 				line1.parent = line2;
823 			}
824 		}
825 
RotateRight(Line line1)826 		private void RotateRight(Line line1) {
827 			Line line2 = line1.left;
828 
829 			line1.left = line2.right;
830 
831 			if (line2.right != sentinel) {
832 				line2.right.parent = line1;
833 			}
834 
835 			if (line2 != sentinel) {
836 				line2.parent = line1.parent;
837 			}
838 
839 			if (line1.parent != null) {
840 				if (line1 == line1.parent.right) {
841 					line1.parent.right = line2;
842 				} else {
843 					line1.parent.left = line2;
844 				}
845 			} else {
846 				document = line2;
847 			}
848 
849 			line2.right = line1;
850 			if (line1 != sentinel) {
851 				line1.parent = line2;
852 			}
853 		}
854 
855 
UpdateView(Line line, int pos)856 		internal void UpdateView(Line line, int pos) {
857 			if (!owner.IsHandleCreated) {
858 				return;
859 			}
860 
861 			if (update_suspended > 0) {
862 				update_start = Math.Min (update_start, line.line_no);
863 				// update_end = Math.Max (update_end, line.line_no);
864 				// recalc_optimize = true;
865 				update_pending = true;
866 				return;
867 			}
868 
869 			// Optimize invalidation based on Line alignment
870 			if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
871 				// Lineheight changed, invalidate the rest of the document
872 				if ((line.Y - viewport_y) >=0 ) {
873 					// We formatted something that's in view, only draw parts of the screen
874 					owner.Invalidate(new Rectangle(
875 						offset_x,
876 						line.Y - viewport_y + offset_y,
877 						viewport_width,
878 						owner.Height - (line.Y - viewport_y)));
879 				} else {
880 					// The tag was above the visible area, draw everything
881 					owner.Invalidate();
882 				}
883 			} else {
884 				switch(line.alignment) {
885 					case HorizontalAlignment.Left: {
886 						owner.Invalidate(new Rectangle(
887 							line.X + ((int)line.widths[pos] - viewport_x - 1) + offset_x,
888 							line.Y - viewport_y + offset_y,
889 							viewport_width,
890 							line.height + 1));
891 						break;
892 					}
893 
894 					case HorizontalAlignment.Center: {
895 						owner.Invalidate(new Rectangle(
896 							line.X + offset_x,
897 							line.Y - viewport_y + offset_y,
898 							viewport_width,
899 							line.height + 1));
900 						break;
901 					}
902 
903 					case HorizontalAlignment.Right: {
904 						owner.Invalidate(new Rectangle(
905 							line.X + offset_x,
906 							line.Y - viewport_y + offset_y,
907 							(int)line.widths[pos + 1] - viewport_x + line.X,
908 							line.height + 1));
909 						break;
910 					}
911 				}
912 			}
913 		}
914 
915 
916 		// Update display from line, down line_count lines; pos is unused, but required for the signature
UpdateView(Line line, int line_count, int pos)917 		internal void UpdateView(Line line, int line_count, int pos) {
918 			if (!owner.IsHandleCreated) {
919 				return;
920 			}
921 
922 			if (recalc_suspended > 0) {
923 				recalc_start = Math.Min (recalc_start, line.line_no);
924 				recalc_end = Math.Max (recalc_end, line.line_no + line_count);
925 				recalc_optimize = true;
926 				recalc_pending = true;
927 				return;
928 			}
929 
930 			int start_line_top = line.Y;
931 
932 			Line end_line = GetLine (line.line_no + line_count);
933 			if (end_line == null)
934 				end_line = GetLine (lines);
935 
936 			if (end_line == null)
937 				return;
938 
939 			int end_line_bottom = end_line.Y + end_line.height;
940 
941 			if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
942 				// Lineheight changed, invalidate the rest of the document
943 				if ((line.Y - viewport_y) >=0 ) {
944 					// We formatted something that's in view, only draw parts of the screen
945 					owner.Invalidate(new Rectangle(
946 						offset_x,
947 						line.Y - viewport_y + offset_y,
948 						viewport_width,
949 						owner.Height - (line.Y - viewport_y)));
950 				} else {
951 					// The tag was above the visible area, draw everything
952 					owner.Invalidate();
953 				}
954 			} else {
955 				int x = 0 - viewport_x + offset_x;
956 				int w = viewport_width;
957 				int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y) + offset_y;
958 				int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
959 
960 				owner.Invalidate (new Rectangle (x, y, w, h));
961 			}
962 		}
963 
964 		/// <summary>
965 		///  Scans the next paragraph for http:/ ftp:/ www. https:/ etc and marks the tags
966 		///  as links.
967 		/// </summary>
968 		/// <param name="start_line">The line to start on</param>
969 		/// <param name="link_changed">marks as true if something is changed</param>
ScanForLinks(Line start_line, ref bool link_changed)970 		private void ScanForLinks (Line start_line, ref bool link_changed)
971 		{
972 			Line current_line = start_line;
973 			StringBuilder line_no_breaks = new StringBuilder ();
974 			StringBuilder line_link_record = new StringBuilder ();
975 			ArrayList cumulative_length_list = new ArrayList ();
976 			bool update_caret_tag = false;
977 
978 			cumulative_length_list.Add (0);
979 
980 			while (current_line != null) {
981 				line_no_breaks.Append (current_line.text);
982 
983 				if (link_changed == false)
984 					current_line.LinkRecord (line_link_record);
985 
986 				current_line.ClearLinks ();
987 
988 				cumulative_length_list.Add (line_no_breaks.Length);
989 
990 				if (current_line.ending == LineEnding.Wrap)
991 					current_line = GetLine (current_line.LineNo + 1);
992 				else
993 					break;
994 			}
995 
996 			// search for protocols.. make sure www. is first!
997 			string [] search_terms = new string [] { "www.", "http:/", "ftp:/", "https:/" };
998 			int search_found = 0;
999 			int index_found = 0;
1000 			string line_no_breaks_string = line_no_breaks.ToString ();
1001 			int line_no_breaks_index = 0;
1002 			int link_end = 0;
1003 
1004 			while (true) {
1005 				if (line_no_breaks_index >= line_no_breaks_string.Length)
1006 					break;
1007 
1008 				index_found = FirstIndexOfAny (line_no_breaks_string, search_terms, line_no_breaks_index, out search_found);
1009 
1010 				//no links found on this line
1011 				if (index_found == -1)
1012 					break;
1013 
1014 				if (search_found == 0) {
1015 					// if we are at the end of the line to analyse and the end of the line
1016 					// is "www." then there are no links here
1017 					if (line_no_breaks_string.Length == index_found + search_terms [0].Length)
1018 						break;
1019 
1020 					// if after www. we don't have a letter a digit or a @ or - or /
1021 					// then it is not a web address, we should continue searching
1022 					if (char.IsLetterOrDigit (line_no_breaks_string [index_found + search_terms [0].Length]) == false &&
1023 						"@/~".IndexOf (line_no_breaks_string [index_found + search_terms [0].Length].ToString ()) == -1) {
1024 						line_no_breaks_index = index_found + search_terms [0].Length;
1025 						continue;
1026 					}
1027 				}
1028 
1029 				link_end = line_no_breaks_string.Length - 1;
1030 				line_no_breaks_index = line_no_breaks_string.Length;
1031 
1032 				// we've found a link, we just need to find where it ends now
1033 				for (int i = index_found + search_terms [search_found].Length; i < line_no_breaks_string.Length; i++) {
1034 					if (line_no_breaks_string [i - 1] == '.') {
1035 						if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
1036 							"@/~".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
1037 							link_end = i - 1;
1038 							line_no_breaks_index = i;
1039 							break;
1040 						}
1041 					} else {
1042 						if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
1043 							"@-/:~.?=_&".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
1044 							link_end = i - 1;
1045 							line_no_breaks_index = i;
1046 							break;
1047 						}
1048 					}
1049 				}
1050 
1051 				string link_text = line_no_breaks_string.Substring (index_found, link_end - index_found + 1);
1052 				int current_cumulative = 0;
1053 
1054 				// we've found a link - index_found -> link_end
1055 				// now we just make all the tags as containing link and
1056 				// point them to the text for the whole link
1057 
1058 				current_line = start_line;
1059 
1060 				//find the line we start on
1061 				for (current_cumulative = 1; current_cumulative < cumulative_length_list.Count; current_cumulative++)
1062 					if ((int)cumulative_length_list [current_cumulative] > index_found)
1063 						break;
1064 
1065 				current_line = GetLine (start_line.LineNo + current_cumulative - 1);
1066 
1067 				// find the tag we start on
1068 				LineTag current_tag = current_line.FindTag (index_found - (int)cumulative_length_list [current_cumulative - 1] + 1);
1069 
1070 				if (current_tag.Start != (index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1) {
1071 					if (current_tag == CaretTag)
1072 						update_caret_tag = true;
1073 
1074 					current_tag = current_tag.Break ((index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1);
1075 				}
1076 
1077 				// set the tag
1078 				current_tag.IsLink = true;
1079 				current_tag.LinkText = link_text;
1080 
1081 				//go through each character
1082 				// find the tag we are in
1083 				// skip the number of characters in the tag
1084 				for (int i = 1; i < link_text.Length; i++) {
1085 					// on to a new word-wrapped line
1086 					if ((int)cumulative_length_list [current_cumulative] <= index_found + i) {
1087 
1088 						current_line = GetLine (start_line.LineNo + current_cumulative++);
1089 						current_tag = current_line.FindTag (index_found + i - (int)cumulative_length_list [current_cumulative - 1] + 1);
1090 
1091 						current_tag.IsLink = true;
1092 						current_tag.LinkText = link_text;
1093 
1094 						continue;
1095 					}
1096 
1097 					if (current_tag.End < index_found + 1 + i - (int)cumulative_length_list [current_cumulative - 1]) {
1098 						// skip empty tags in the middle of the URL
1099 						do {
1100 							current_tag = current_tag.Next;
1101 						} while (current_tag.Length == 0);
1102 
1103 						current_tag.IsLink = true;
1104 						current_tag.LinkText = link_text;
1105 					}
1106 				}
1107 
1108 				//if there are characters left in the tag after the link
1109 				// split the tag
1110 				// make the second part a non link
1111 				if (current_tag.End > (index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]) {
1112 					if (current_tag == CaretTag)
1113 						update_caret_tag = true;
1114 
1115 					current_tag.Break ((index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]);
1116 				}
1117 			}
1118 
1119 			if (update_caret_tag) {
1120 				CaretTag = LineTag.FindTag (CaretLine, CaretPosition);
1121 				link_changed = true;
1122 			} else {
1123 				if (link_changed == false) {
1124 					current_line = start_line;
1125 					StringBuilder new_link_record = new StringBuilder ();
1126 
1127 					while (current_line != null) {
1128 						current_line.LinkRecord (new_link_record);
1129 
1130 						if (current_line.ending == LineEnding.Wrap)
1131 							current_line = GetLine (current_line.LineNo + 1);
1132 						else
1133 							break;
1134 					}
1135 
1136 					if (new_link_record.Equals (line_link_record) == false)
1137 						link_changed = true;
1138 				}
1139 			}
1140 		}
1141 
FirstIndexOfAny(string haystack, string [] needles, int start_index, out int term_found)1142 		private int FirstIndexOfAny (string haystack, string [] needles, int start_index, out int term_found)
1143 		{
1144 			term_found = -1;
1145 			int best_index = -1;
1146 
1147 			for (int i = 0; i < needles.Length; i++) {
1148 				int index = haystack.IndexOf (needles [i], start_index,	StringComparison.InvariantCultureIgnoreCase);
1149 
1150 				if (index > -1) {
1151 					if (term_found > -1) {
1152 						if (index < best_index) {
1153 							best_index = index;
1154 							term_found = i;
1155 						}
1156 					} else {
1157 						best_index = index;
1158 						term_found = i;
1159 					}
1160 				}
1161 			}
1162 
1163 			return best_index;
1164 		}
1165 
1166 
1167 
InvalidateLinks(Rectangle clip)1168 		private void InvalidateLinks (Rectangle clip)
1169 		{
1170 			for (int i = (owner.list_links.Count - 1); i >= 0; i--) {
1171 				TextBoxBase.LinkRectangle link = (TextBoxBase.LinkRectangle) owner.list_links [i];
1172 
1173 				if (clip.IntersectsWith (link.LinkAreaRectangle))
1174 					owner.list_links.RemoveAt (i);
1175 			}
1176 		}
1177 		#endregion	// Private Methods
1178 
1179 		#region Internal Methods
1180 
ScanForLinks(int start, int end, ref bool link_changed)1181 		internal void ScanForLinks (int start, int end, ref bool link_changed)
1182 		{
1183 			Line line = null;
1184 			LineEnding lastending = LineEnding.Rich;
1185 
1186 			// make sure we start scanning at the real begining of the line
1187 			while (true) {
1188 				if (start != 1 && GetLine (start - 1).ending == LineEnding.Wrap)
1189 					start--;
1190 				else
1191 					break;
1192 			}
1193 
1194 			for (int i = start; i <= end && i <= lines; i++) {
1195 				line = GetLine (i);
1196 
1197 				if (lastending != LineEnding.Wrap)
1198 					ScanForLinks (line, ref link_changed);
1199 
1200 				lastending = line.ending;
1201 
1202 				if (lastending == LineEnding.Wrap && (i + 1) <= end)
1203 					end++;
1204 			}
1205 		}
1206 
1207 		// Clear the document and reset state
Empty()1208 		internal void Empty() {
1209 
1210 			document = sentinel;
1211 			lines = 0;
1212 
1213 			// We always have a blank line
1214 			Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
1215 
1216 			this.RecalculateDocument(owner.CreateGraphicsInternal());
1217 			PositionCaret(0, 0);
1218 
1219 			SetSelectionVisible (false);
1220 
1221 			selection_start.line = this.document;
1222 			selection_start.pos = 0;
1223 			selection_start.tag = selection_start.line.tags;
1224 			selection_end.line = this.document;
1225 			selection_end.pos = 0;
1226 			selection_end.tag = selection_end.line.tags;
1227 			char_count = 0;
1228 
1229 			viewport_x = 0;
1230 			viewport_y = 0;
1231 
1232 			document_x = 0;
1233 			document_y = 0;
1234 
1235 			if (owner.IsHandleCreated)
1236 				owner.Invalidate ();
1237 		}
1238 
PositionCaret(Line line, int pos)1239 		internal void PositionCaret(Line line, int pos) {
1240 			caret.tag = line.FindTag (pos);
1241 
1242 			MoveCaretToTextTag ();
1243 
1244 			caret.line = line;
1245 			caret.pos = pos;
1246 
1247 			if (owner.IsHandleCreated) {
1248 				if (owner.Focused) {
1249 					if (caret.height != caret.tag.Height)
1250 						XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1251 					XplatUI.SetCaretPos(owner.Handle,
1252 						offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1253 						offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1254 				}
1255 
1256 				if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1257 			}
1258 
1259 			// We set this at the end because we use the heights to determine whether or
1260 			// not we need to recreate the caret
1261 			caret.height = caret.tag.Height;
1262 
1263 		}
1264 
PositionCaret(int x, int y)1265 		internal void PositionCaret(int x, int y) {
1266 			if (!owner.IsHandleCreated) {
1267 				return;
1268 			}
1269 
1270 			caret.tag = FindCursor(x, y, out caret.pos);
1271 
1272 			MoveCaretToTextTag ();
1273 
1274 			caret.line = caret.tag.Line;
1275 			caret.height = caret.tag.Height;
1276 
1277 			if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1278 				XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
1279 				XplatUI.SetCaretPos(owner.Handle,
1280 					(int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x + offset_x,
1281 					offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1282 			}
1283 
1284 			if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1285 		}
1286 
CaretHasFocus()1287 		internal void CaretHasFocus() {
1288 			if ((caret.tag != null) && owner.IsHandleCreated) {
1289 				XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1290 				XplatUI.SetCaretPos(owner.Handle,
1291 					offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1292 					offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1293 
1294 				DisplayCaret ();
1295 			}
1296 
1297 			if (owner.IsHandleCreated && SelectionLength () > 0) {
1298 				InvalidateSelectionArea ();
1299 			}
1300 		}
1301 
CaretLostFocus()1302 		internal void CaretLostFocus() {
1303 			if (!owner.IsHandleCreated) {
1304 				return;
1305 			}
1306 			XplatUI.DestroyCaret(owner.Handle);
1307 		}
1308 
AlignCaret()1309 		internal void AlignCaret ()
1310 		{
1311 			AlignCaret (true);
1312 		}
1313 
AlignCaret(bool changeCaretTag)1314 		internal void AlignCaret(bool changeCaretTag) {
1315 			if (!owner.IsHandleCreated) {
1316 				return;
1317 			}
1318 
1319 			if (changeCaretTag) {
1320 				caret.tag = LineTag.FindTag (caret.line, caret.pos);
1321 
1322 				MoveCaretToTextTag ();
1323 			}
1324 
1325 			// if the caret has had SelectionFont changed to a
1326 			// different height, we reflect changes unless the new
1327 			// font is larger than the line (line recalculations
1328 			// ignore empty tags) in which case we make it equal
1329 			// the line height and then when text is entered
1330 			if (caret.tag.Height > caret.tag.Line.Height) {
1331 				caret.height = caret.line.height;
1332 			} else {
1333 				caret.height = caret.tag.Height;
1334 			}
1335 
1336 			if (owner.Focused) {
1337 				XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1338 				XplatUI.SetCaretPos (owner.Handle,
1339 					offset_x + (int) caret.tag.Line.widths [caret.pos] + caret.line.X - viewport_x,
1340 					offset_y + caret.line.Y + viewport_y + caret_shift);
1341 				DisplayCaret ();
1342 			}
1343 
1344 			if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1345 		}
1346 
UpdateCaret()1347 		internal void UpdateCaret() {
1348 			if (!owner.IsHandleCreated || caret.tag == null) {
1349 				return;
1350 			}
1351 
1352 			MoveCaretToTextTag ();
1353 
1354 			if (caret.tag.Height != caret.height) {
1355 				caret.height = caret.tag.Height;
1356 				if (owner.Focused) {
1357 					XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1358 				}
1359 			}
1360 
1361 			if (owner.Focused) {
1362 				XplatUI.SetCaretPos(owner.Handle,
1363 					offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
1364 					offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
1365 				DisplayCaret ();
1366 			}
1367 
1368 			if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1369 		}
1370 
DisplayCaret()1371 		internal void DisplayCaret() {
1372 			if (!owner.IsHandleCreated) {
1373 				return;
1374 			}
1375 
1376 			if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
1377 				XplatUI.CaretVisible(owner.Handle, true);
1378 			}
1379 		}
1380 
HideCaret()1381 		internal void HideCaret() {
1382 			if (!owner.IsHandleCreated) {
1383 				return;
1384 			}
1385 
1386 			if (owner.Focused) {
1387 				XplatUI.CaretVisible(owner.Handle, false);
1388 			}
1389 		}
1390 
1391 
MoveCaretToTextTag()1392 		internal void MoveCaretToTextTag ()
1393 		{
1394 			if (caret.tag == null || caret.tag.IsTextTag)
1395 				return;
1396 
1397 
1398 
1399 			if (caret.pos < caret.tag.Start) {
1400 				caret.tag = caret.tag.Previous;
1401 			} else {
1402 				caret.tag = caret.tag.Next;
1403 			}
1404 		}
1405 
MoveCaret(CaretDirection direction)1406 		internal void MoveCaret(CaretDirection direction) {
1407 			// FIXME should we use IsWordSeparator to detect whitespace, instead
1408 			// of looking for actual spaces in the Word move cases?
1409 
1410 			bool nowrap = false;
1411 			switch(direction) {
1412 				case CaretDirection.CharForwardNoWrap:
1413 					nowrap = true;
1414 					goto case CaretDirection.CharForward;
1415 				case CaretDirection.CharForward: {
1416 					caret.pos++;
1417 					if (caret.pos > caret.line.TextLengthWithoutEnding ()) {
1418 						if (!nowrap) {
1419 							// Go into next line
1420 							if (caret.line.line_no < this.lines) {
1421 								caret.line = GetLine(caret.line.line_no+1);
1422 								caret.pos = 0;
1423 								caret.tag = caret.line.tags;
1424 							} else {
1425 								caret.pos--;
1426 							}
1427 						} else {
1428 							// Single line; we stay where we are
1429 							caret.pos--;
1430 						}
1431 					} else {
1432 						if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) {
1433 							caret.tag = caret.tag.Next;
1434 						}
1435 					}
1436 					UpdateCaret();
1437 					return;
1438 				}
1439 
1440 				case CaretDirection.CharBackNoWrap:
1441 					nowrap = true;
1442 					goto case CaretDirection.CharBack;
1443 				case CaretDirection.CharBack: {
1444 					if (caret.pos > 0) {
1445 						// caret.pos--; // folded into the if below
1446 
1447 						if (--caret.pos > 0) {
1448 							if (caret.tag.Start > caret.pos) {
1449 								caret.tag = caret.tag.Previous;
1450 							}
1451 						}
1452 					} else {
1453 						if (caret.line.line_no > 1 && !nowrap) {
1454 							caret.line = GetLine(caret.line.line_no - 1);
1455 							caret.pos = caret.line.TextLengthWithoutEnding ();
1456 							caret.tag = LineTag.FindTag(caret.line, caret.pos);
1457 						}
1458 					}
1459 					UpdateCaret();
1460 					return;
1461 				}
1462 
1463 				case CaretDirection.WordForward: {
1464 					int len;
1465 
1466 					len = caret.line.text.Length;
1467 					if (caret.pos < len) {
1468 						while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1469 							caret.pos++;
1470 						}
1471 						if (caret.pos < len) {
1472 							// Skip any whitespace
1473 							while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1474 								caret.pos++;
1475 							}
1476 						}
1477 						caret.tag = LineTag.FindTag(caret.line, caret.pos);
1478 					} else {
1479 						if (caret.line.line_no < this.lines) {
1480 							caret.line = GetLine(caret.line.line_no + 1);
1481 							caret.pos = 0;
1482 							caret.tag = caret.line.tags;
1483 						}
1484 					}
1485 					UpdateCaret();
1486 					return;
1487 				}
1488 
1489 				case CaretDirection.WordBack: {
1490 					if (caret.pos > 0) {
1491 						caret.pos--;
1492 
1493 						while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1494 							caret.pos--;
1495 						}
1496 
1497 						while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1498 							caret.pos--;
1499 						}
1500 
1501 						if (caret.line.text.ToString(caret.pos, 1) == " ") {
1502 							if (caret.pos != 0) {
1503 								caret.pos++;
1504 							} else {
1505 								caret.line = GetLine(caret.line.line_no - 1);
1506 								caret.pos = caret.line.text.Length;
1507 							}
1508 						}
1509 						caret.tag = LineTag.FindTag(caret.line, caret.pos);
1510 					} else {
1511 						if (caret.line.line_no > 1) {
1512 							caret.line = GetLine(caret.line.line_no - 1);
1513 							caret.pos = caret.line.text.Length;
1514 							caret.tag = LineTag.FindTag(caret.line, caret.pos);
1515 						}
1516 					}
1517 					UpdateCaret();
1518 					return;
1519 				}
1520 
1521 				case CaretDirection.LineUp: {
1522 					if (caret.line.line_no > 1) {
1523 						int	pixel;
1524 
1525 						pixel = (int)caret.line.widths[caret.pos];
1526 						PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1527 
1528 						DisplayCaret ();
1529 					}
1530 					return;
1531 				}
1532 
1533 				case CaretDirection.LineDown: {
1534 					if (caret.line.line_no < lines) {
1535 						int	pixel;
1536 
1537 						pixel = (int)caret.line.widths[caret.pos];
1538 						PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1539 
1540 						DisplayCaret ();
1541 					}
1542 					return;
1543 				}
1544 
1545 				case CaretDirection.Home: {
1546 					if (caret.pos > 0) {
1547 						caret.pos = 0;
1548 						caret.tag = caret.line.tags;
1549 						UpdateCaret();
1550 					}
1551 					return;
1552 				}
1553 
1554 				case CaretDirection.End: {
1555 					if (caret.pos < caret.line.TextLengthWithoutEnding ()) {
1556 						caret.pos = caret.line.TextLengthWithoutEnding ();
1557 						caret.tag = LineTag.FindTag(caret.line, caret.pos);
1558 						UpdateCaret();
1559 					}
1560 					return;
1561 				}
1562 
1563 				case CaretDirection.PgUp: {
1564 
1565 					if (caret.line.line_no == 1 && owner.richtext) {
1566 						owner.vscroll.Value = 0;
1567 						Line line = GetLine (1);
1568 						PositionCaret (line, 0);
1569 					}
1570 
1571 					int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y;
1572 					int index;
1573 					LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1574 							viewport_y - viewport_height, out index);
1575 
1576 					owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1577 					PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1578 
1579 					return;
1580 				}
1581 
1582 				case CaretDirection.PgDn: {
1583 
1584 					if (caret.line.line_no == lines && owner.richtext) {
1585 						owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
1586 						Line line = GetLine (lines);
1587 						PositionCaret (line, line.TextLengthWithoutEnding());
1588 					}
1589 
1590 					int y_offset = caret.line.Y - viewport_y;
1591 					int index;
1592 					LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
1593 							viewport_y + viewport_height, out index);
1594 
1595 					owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
1596 					PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
1597 
1598 					return;
1599 				}
1600 
1601 				case CaretDirection.CtrlPgUp: {
1602 					PositionCaret(0, viewport_y);
1603 					DisplayCaret ();
1604 					return;
1605 				}
1606 
1607 				case CaretDirection.CtrlPgDn: {
1608 					Line	line;
1609 					LineTag	tag;
1610 					int	index;
1611 
1612 					tag = FindCursor (0, viewport_y + viewport_height, out index);
1613 					if (tag.Line.line_no > 1) {
1614 						line = GetLine(tag.Line.line_no - 1);
1615 					} else {
1616 						line = tag.Line;
1617 					}
1618 					PositionCaret(line, line.Text.Length);
1619 					DisplayCaret ();
1620 					return;
1621 				}
1622 
1623 				case CaretDirection.CtrlHome: {
1624 					caret.line = GetLine(1);
1625 					caret.pos = 0;
1626 					caret.tag = caret.line.tags;
1627 
1628 					UpdateCaret();
1629 					return;
1630 				}
1631 
1632 				case CaretDirection.CtrlEnd: {
1633 					caret.line = GetLine(lines);
1634 					caret.pos = caret.line.TextLengthWithoutEnding ();
1635 					caret.tag = LineTag.FindTag(caret.line, caret.pos);
1636 
1637 					UpdateCaret();
1638 					return;
1639 				}
1640 
1641 				case CaretDirection.SelectionStart: {
1642 					caret.line = selection_start.line;
1643 					caret.pos = selection_start.pos;
1644 					caret.tag = selection_start.tag;
1645 
1646 					UpdateCaret();
1647 					return;
1648 				}
1649 
1650 				case CaretDirection.SelectionEnd: {
1651 					caret.line = selection_end.line;
1652 					caret.pos = selection_end.pos;
1653 					caret.tag = selection_end.tag;
1654 
1655 					UpdateCaret();
1656 					return;
1657 				}
1658 			}
1659 		}
1660 
DumpDoc()1661 		internal void DumpDoc ()
1662 		{
1663 			Console.WriteLine ("<doc lines='{0}'>", lines);
1664 			for (int i = 1; i <= lines ; i++) {
1665 				Line line = GetLine (i);
1666 				Console.WriteLine ("<line no='{0}' ending='{1}'>", line.line_no, line.ending);
1667 
1668 				LineTag tag = line.tags;
1669 				while (tag != null) {
1670 					Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
1671 							tag.GetType (), tag.Start, tag.Length, tag.Font, tag.Color);
1672 					Console.Write (tag.Text ());
1673 					Console.WriteLine ("</tag>");
1674 					tag = tag.Next;
1675 				}
1676 				Console.WriteLine ("</line>");
1677 			}
1678 			Console.WriteLine ("</doc>");
1679 		}
1680 
1681 		// UIA: Used via reflection by TextProviderBehavior
GetVisibleLineIndexes(Rectangle clip, out int start, out int end)1682 		internal void GetVisibleLineIndexes (Rectangle clip, out int start, out int end)
1683 		{
1684 			if (multiline) {
1685 				/* Expand the region slightly to be sure to
1686 				 * paint the full extent of the line of text.
1687 				 * See bug 464464.
1688 				 */
1689 				start = GetLineByPixel(clip.Top + viewport_y - offset_y - 1, false).line_no;
1690 				end = GetLineByPixel(clip.Bottom + viewport_y - offset_y + 1, false).line_no;
1691 			} else {
1692 				start = GetLineByPixel(clip.Left + viewport_x - offset_x, false).line_no;
1693 				end = GetLineByPixel(clip.Right + viewport_x - offset_x, false).line_no;
1694 			}
1695 		}
1696 
Draw(Graphics g, Rectangle clip)1697 		internal void Draw (Graphics g, Rectangle clip)
1698 		{
1699 			Line line;		// Current line being drawn
1700 			LineTag	tag;		// Current tag being drawn
1701 			int start;		// First line to draw
1702 			int end;		// Last line to draw
1703 			StringBuilder text;	// String representing the current line
1704 			int line_no;
1705 			Color tag_color;
1706 			Color current_color;
1707 
1708 			// First, figure out from what line to what line we need to draw
1709 			GetVisibleLineIndexes (clip, out start, out end);
1710 
1711 			// remove links in the list (used for mouse down events) that are within the clip area.
1712 			InvalidateLinks (clip);
1713 
1714 			///
1715 			/// We draw the single border ourself
1716 			///
1717 			if (owner.actual_border_style == BorderStyle.FixedSingle) {
1718 				ControlPaint.DrawBorder (g, owner.ClientRectangle, Color.Black, ButtonBorderStyle.Solid);
1719 			}
1720 
1721 			/// Make sure that we aren't drawing one more line then we need to
1722 			line = GetLine (end - 1);
1723 			if (line != null && clip.Bottom == offset_y + line.Y + line.height - viewport_y)
1724 				end--;
1725 
1726 			line_no = start;
1727 
1728 			#if Debug
1729 				DateTime	n = DateTime.Now;
1730 				Console.WriteLine ("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1731 				Console.WriteLine ("CLIP:  {0}", clip);
1732 				Console.WriteLine ("S: {0}", GetLine (start).text);
1733 				Console.WriteLine ("E: {0}", GetLine (end).text);
1734 			#endif
1735 
1736 			// Non multiline selection can be handled outside of the loop
1737 			if (!multiline && selection_visible && owner.ShowSelection) {
1738 				g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1739 						offset_x + selection_start.line.widths [selection_start.pos] +
1740 						selection_start.line.X - viewport_x,
1741 						offset_y + selection_start.line.Y,
1742 						(selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
1743 						(selection_start.line.X + selection_start.line.widths [selection_start.pos]),
1744 						selection_start.line.height);
1745 			}
1746 
1747 			while (line_no <= end) {
1748 				line = GetLine (line_no);
1749 				float line_y = line.Y - viewport_y + offset_y;
1750 
1751 				tag = line.tags;
1752 				if (!calc_pass) {
1753 					text = line.text;
1754 				} else {
1755 					if (PasswordCache.Length < line.text.Length)
1756 						PasswordCache.Append(Char.Parse(password_char), line.text.Length - PasswordCache.Length);
1757 					else if (PasswordCache.Length > line.text.Length)
1758 						PasswordCache.Remove(line.text.Length, PasswordCache.Length - line.text.Length);
1759 					text = PasswordCache;
1760 				}
1761 
1762 				int line_selection_start = text.Length + 1;
1763 				int line_selection_end = text.Length + 1;
1764 				if (selection_visible && owner.ShowSelection &&
1765 						(line_no >= selection_start.line.line_no) &&
1766 						(line_no <= selection_end.line.line_no)) {
1767 
1768 					if (line_no == selection_start.line.line_no)
1769 						line_selection_start = selection_start.pos + 1;
1770 					else
1771 						line_selection_start = 1;
1772 
1773 					if (line_no == selection_end.line.line_no)
1774 						line_selection_end = selection_end.pos + 1;
1775 					else
1776 						line_selection_end = text.Length + 1;
1777 
1778 					if (line_selection_end == line_selection_start) {
1779 						// There isn't really selection
1780 						line_selection_start = text.Length + 1;
1781 						line_selection_end = line_selection_start;
1782 					} else if (multiline) {
1783 						// lets draw some selection baby!!  (non multiline selection is drawn outside the loop)
1784 						g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
1785 								offset_x + line.widths [line_selection_start - 1] + line.X - viewport_x,
1786 								line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
1787 								line.height);
1788 					}
1789 				}
1790 
1791 				current_color = line.tags.ColorToDisplay;
1792 				while (tag != null) {
1793 
1794 					// Skip empty tags
1795 					if (tag.Length == 0) {
1796 						tag = tag.Next;
1797 						continue;
1798 					}
1799 
1800 					if (((tag.X + tag.Width) < (clip.Left - viewport_x - offset_x)) &&
1801 					     (tag.X > (clip.Right - viewport_x - offset_x))) {
1802 						tag = tag.Next;
1803 						continue;
1804 					}
1805 
1806 					if (tag.BackColor != Color.Empty) {
1807 						g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor),
1808 								offset_x + tag.X + line.X - viewport_x,
1809 								line_y + tag.Shift, tag.Width, line.height);
1810 					}
1811 
1812 					tag_color = tag.ColorToDisplay;
1813 					current_color = tag_color;
1814 
1815 					if (!owner.Enabled) {
1816 						Color a = tag.Color;
1817 						Color b = ThemeEngine.Current.ColorWindowText;
1818 
1819 						if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B))
1820 							tag_color = ThemeEngine.Current.ColorGrayText;
1821 
1822 					}
1823 
1824 					int tag_pos = tag.Start;
1825 					current_color = tag_color;
1826 					while (tag_pos < tag.Start + tag.Length) {
1827 						int old_tag_pos = tag_pos;
1828 
1829 						if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
1830 							current_color = ThemeEngine.Current.ColorHighlightText;
1831 							tag_pos = Math.Min (tag.End, line_selection_end);
1832 						} else if (tag_pos < line_selection_start) {
1833 							current_color = tag_color;
1834 							tag_pos = Math.Min (tag.End, line_selection_start);
1835 						} else {
1836 							current_color = tag_color;
1837 							tag_pos = tag.End;
1838 						}
1839 
1840 						Rectangle text_size;
1841 
1842 						tag.Draw (g, current_color,
1843 								offset_x + line.X - viewport_x,
1844 								line_y + tag.Shift,
1845 								old_tag_pos - 1, Math.Min (tag.Start + tag.Length, tag_pos) - 1,
1846 								text.ToString (), out text_size, tag.IsLink);
1847 
1848 						if (tag.IsLink) {
1849 							TextBoxBase.LinkRectangle link = new TextBoxBase.LinkRectangle (text_size);
1850 							link.LinkTag = tag;
1851 							owner.list_links.Add (link);
1852 						}
1853 					}
1854 					tag = tag.Next;
1855 				}
1856 
1857 				line.DrawEnding (g, line_y);
1858 				line_no++;
1859 			}
1860 		}
1861 
GetLineEnding(string line, int start, out LineEnding ending)1862 		private int GetLineEnding (string line, int start, out LineEnding ending)
1863 		{
1864 			int res;
1865 			int rich_index;
1866 
1867 			if (start >= line.Length) {
1868 				ending = LineEnding.Wrap;
1869 				return -1;
1870 			}
1871 
1872 			res = line.IndexOf ('\r', start);
1873 			rich_index = line.IndexOf ('\n', start);
1874 
1875 			// Handle the case where we find both of them, and the \n is before the \r
1876 			if (res != -1 && rich_index != -1)
1877 				if (rich_index < res) {
1878 					ending = LineEnding.Rich;
1879 					return rich_index;
1880 				}
1881 
1882 			if (res != -1) {
1883 				if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
1884 					ending = LineEnding.Soft;
1885 					return res;
1886 				}
1887 				if (res + 1 < line.Length && line [res + 1] == '\n') {
1888 					ending = LineEnding.Hard;
1889 					return res;
1890 				}
1891 				ending = LineEnding.Limp;
1892 				return res;
1893 			}
1894 
1895 			if (rich_index != -1) {
1896 				ending = LineEnding.Rich;
1897 				return rich_index;
1898 			}
1899 
1900 			ending = LineEnding.Wrap;
1901 			return line.Length;
1902 		}
1903 
1904 		// Get the line ending, but only of the types specified
GetLineEnding(string line, int start, out LineEnding ending, LineEnding type)1905 		private int GetLineEnding (string line, int start, out LineEnding ending, LineEnding type)
1906 		{
1907 			int index = start;
1908 			int last_length = 0;
1909 
1910 			do {
1911 				index = GetLineEnding (line, index + last_length, out ending);
1912 				last_length = LineEndingLength (ending);
1913 			} while
1914 				((ending & type) != ending && index != -1);
1915 
1916 			return index == -1 ? line.Length : index;
1917 		}
1918 
LineEndingLength(LineEnding ending)1919 		internal int LineEndingLength (LineEnding ending)
1920 		{
1921 			switch (ending) {
1922 				case LineEnding.Limp:
1923 				case LineEnding.Rich:
1924 					return 1;
1925 				case LineEnding.Hard:
1926 					return 2;
1927 				case LineEnding.Soft:
1928 					return 3;
1929 			}
1930 
1931 			return 0;
1932 		}
1933 
LineEndingToString(LineEnding ending)1934 		internal string LineEndingToString (LineEnding ending)
1935 		{
1936 			switch (ending) {
1937 				case LineEnding.Limp:
1938 					return "\r";
1939 				case LineEnding.Hard:
1940 					return "\r\n";
1941 				case LineEnding.Soft:
1942 					return "\r\r\n";
1943 				case LineEnding.Rich:
1944 					return "\n";
1945 			}
1946 
1947 			return string.Empty;
1948 		}
1949 
StringToLineEnding(string ending)1950 		internal LineEnding StringToLineEnding (string ending)
1951 		{
1952 			switch (ending) {
1953 				case "\r":
1954 					return LineEnding.Limp;
1955 				case "\r\n":
1956 					return LineEnding.Hard;
1957 				case "\r\r\n":
1958 					return LineEnding.Soft;
1959 				case "\n":
1960 					return LineEnding.Rich;
1961 				default:
1962 					return LineEnding.None;
1963 			}
1964 		}
1965 
Insert(Line line, int pos, bool update_caret, string s)1966 		internal void Insert (Line line, int pos, bool update_caret, string s)
1967 		{
1968 			Insert (line, pos, update_caret, s, line.FindTag (pos));
1969 		}
1970 
1971 		// Insert text at the given position; use formatting at insertion point for inserted text
Insert(Line line, int pos, bool update_caret, string s, LineTag tag)1972 		internal void Insert (Line line, int pos, bool update_caret, string s, LineTag tag)
1973 		{
1974 			int break_index;
1975 			int base_line;
1976 			int old_line_count;
1977 			int count = 1;
1978 			LineEnding ending;
1979 			Line split_line;
1980 
1981 			// Don't recalculate while we mess around
1982 			SuspendRecalc ();
1983 
1984 			base_line = line.line_no;
1985 			old_line_count = lines;
1986 
1987 			// Discard chars after any possible -unlikely- end of file
1988 			int eof_index = s.IndexOf ('\0');
1989 			if (eof_index != -1)
1990 				s = s.Substring (0, eof_index);
1991 
1992 			break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich);
1993 
1994 			// There are no line feeds in our text to be pasted
1995 			if (break_index == s.Length) {
1996 				line.InsertString (pos, s, tag);
1997 			} else {
1998 				// Add up to the first line feed to our current position
1999 				line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)), tag);
2000 
2001 				// Split the rest of the original line to a new line
2002 				Split (line, pos + (break_index + LineEndingLength (ending)));
2003 				line.ending = ending;
2004 				break_index += LineEndingLength (ending);
2005 				split_line = GetLine (line.line_no + 1);
2006 
2007 				// Insert brand new lines for any more line feeds in the inserted string
2008 				while (true) {
2009 					int next_break = GetLineEnding (s, break_index, out ending, LineEnding.Hard | LineEnding.Rich);
2010 
2011 					if (next_break == s.Length)
2012 						break;
2013 
2014 					string line_text = s.Substring (break_index, next_break - break_index +
2015 							LineEndingLength (ending));
2016 
2017 					Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending);
2018 
2019 					Line last = GetLine (base_line + count);
2020 					last.ending = ending;
2021 
2022 					count++;
2023 					break_index = next_break + LineEndingLength (ending);
2024 				}
2025 
2026 				// Add the remainder of the insert text to the split
2027 				// part of the original line
2028 				split_line.InsertString (0, s.Substring (break_index));
2029 			}
2030 
2031 			// Allow the document to recalculate things
2032 			ResumeRecalc (false);
2033 
2034 			// Update our character count
2035 			CharCount += s.Length;
2036 
2037 			UpdateView (line, lines - old_line_count + 1, pos);
2038 
2039 			// Move the caret to the end of the inserted text if requested
2040 			if (update_caret) {
2041 				Line l = GetLine (line.line_no + lines - old_line_count);
2042 				PositionCaret (l, l.text.Length);
2043 				DisplayCaret ();
2044 			}
2045 		}
2046 
2047 		// Inserts a string at the given position
InsertString(Line line, int pos, string s)2048 		internal void InsertString (Line line, int pos, string s)
2049 		{
2050 			// Update our character count
2051 			CharCount += s.Length;
2052 
2053 			// Insert the text into the Line
2054 			line.InsertString (pos, s);
2055 		}
2056 
2057 		// Inserts a character at the current caret position
InsertCharAtCaret(char ch, bool move_caret)2058 		internal void InsertCharAtCaret (char ch, bool move_caret)
2059 		{
2060 			caret.line.InsertString (caret.pos, ch.ToString(), caret.tag);
2061 
2062 			// Update our character count
2063 			CharCount++;
2064 
2065 			undo.RecordTyping (caret.line, caret.pos, ch);
2066 
2067 			UpdateView (caret.line, caret.pos);
2068 
2069 			if (move_caret) {
2070 				caret.pos++;
2071 				UpdateCaret ();
2072 				SetSelectionToCaret (true);
2073 			}
2074 		}
2075 
InsertPicture(Line line, int pos, RTF.Picture picture)2076 		internal void InsertPicture (Line line, int pos, RTF.Picture picture)
2077 		{
2078 			//LineTag next_tag;
2079 			LineTag tag;
2080 			int len;
2081 
2082 			len = 1;
2083 
2084 			// Just a place holder basically
2085 			line.text.Insert (pos, "I");
2086 
2087 			PictureTag picture_tag = new PictureTag (line, pos + 1, picture);
2088 
2089 			tag = LineTag.FindTag (line, pos);
2090 			picture_tag.CopyFormattingFrom (tag);
2091 			/*next_tag = */tag.Break (pos + 1);
2092 			picture_tag.Previous = tag;
2093 			picture_tag.Next = tag.Next;
2094 			tag.Next = picture_tag;
2095 
2096 			//
2097 			// Picture tags need to be surrounded by text tags
2098 			//
2099 			if (picture_tag.Next == null) {
2100 				picture_tag.Next = new LineTag (line, pos + 1);
2101 				picture_tag.Next.CopyFormattingFrom (tag);
2102 				picture_tag.Next.Previous = picture_tag;
2103 			}
2104 
2105 			tag = picture_tag.Next;
2106 			while (tag != null) {
2107 				tag.Start += len;
2108 				tag = tag.Next;
2109 			}
2110 
2111 			line.Grow (len);
2112 			line.recalc = true;
2113 
2114 			UpdateView (line, pos);
2115 		}
2116 
DeleteMultiline(Line start_line, int pos, int length)2117 		internal void DeleteMultiline (Line start_line, int pos, int length)
2118 		{
2119 			Marker start = new Marker ();
2120 			Marker end = new Marker ();
2121 			int start_index = LineTagToCharIndex (start_line, pos);
2122 
2123 			start.line = start_line;
2124 			start.pos = pos;
2125 			start.tag = LineTag.FindTag (start_line, pos);
2126 
2127 			CharIndexToLineTag (start_index + length, out end.line,
2128 					out end.tag, out end.pos);
2129 
2130 			SuspendUpdate ();
2131 
2132 			if (start.line == end.line) {
2133 				DeleteChars (start.line, pos, end.pos - pos);
2134 			} else {
2135 
2136 				// Delete first and last lines
2137 				DeleteChars (start.line, start.pos, start.line.text.Length - start.pos);
2138 				DeleteChars (end.line, 0, end.pos);
2139 
2140 				int current = start.line.line_no + 1;
2141 				if (current < end.line.line_no) {
2142 					for (int i = end.line.line_no - 1; i >= current; i--) {
2143 						Delete (i);
2144 					}
2145 				}
2146 
2147 				// BIG FAT WARNING - selection_end.line might be stale due
2148 				// to the above Delete() call. DONT USE IT before hitting the end of this method!
2149 
2150 				// Join start and end
2151 				Combine (start.line.line_no, current);
2152 			}
2153 
2154 			ResumeUpdate (true);
2155 		}
2156 
2157 
2158 		// Deletes n characters at the given position; it will not delete past line limits
2159 		// pos is 0-based
DeleteChars(Line line, int pos, int count)2160 		public void DeleteChars (Line line, int pos, int count)
2161 		{
2162 			// Reduce our character count
2163 			CharCount -= count;
2164 
2165 			line.DeleteCharacters (pos, count);
2166 
2167 			if (pos >= line.TextLengthWithoutEnding ()) {
2168 				LineEnding ending = line.ending;
2169 				GetLineEnding (line.text.ToString (), 0, out ending);
2170 
2171 				if (ending != line.ending) {
2172 					line.ending = ending;
2173 
2174 					if (!multiline) {
2175 						UpdateView (line, lines, pos);
2176 						owner.Invalidate ();
2177 						return;
2178 					}
2179 				}
2180 			}
2181 			if (!multiline) {
2182 				UpdateView (line, lines, pos);
2183 				owner.Invalidate ();
2184 			} else
2185 				UpdateView (line, pos);
2186 		}
2187 
2188 		// Deletes a character at or after the given position (depending on forward); it will not delete past line limits
DeleteChar(Line line, int pos, bool forward)2189 		public void DeleteChar (Line line, int pos, bool forward)
2190 		{
2191 			if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true))
2192 				return;
2193 
2194 			undo.BeginUserAction ("Delete");
2195 
2196 			if (forward) {
2197 				undo.RecordDeleteString (line, pos, line, pos + 1);
2198 				DeleteChars (line, pos, 1);
2199 			} else {
2200 				undo.RecordDeleteString (line, pos - 1, line, pos);
2201 				DeleteChars (line, pos - 1, 1);
2202 			}
2203 
2204 			undo.EndUserAction ();
2205 		}
2206 
2207 		// Combine two lines
Combine(int FirstLine, int SecondLine)2208 		internal void Combine(int FirstLine, int SecondLine) {
2209 			Combine(GetLine(FirstLine), GetLine(SecondLine));
2210 		}
2211 
Combine(Line first, Line second)2212 		internal void Combine(Line first, Line second) {
2213 			LineTag	last;
2214 			int	shift;
2215 
2216 			// strip the ending off of the first lines text
2217 			first.text.Length = first.text.Length - LineEndingLength (first.ending);
2218 
2219 			// Combine the two tag chains into one
2220 			last = first.tags;
2221 
2222 			// Maintain the line ending style
2223 			first.ending = second.ending;
2224 
2225 			while (last.Next != null) {
2226 				last = last.Next;
2227 			}
2228 
2229 			// need to get the shift before setting the next tag since that effects length
2230 			shift = last.Start + last.Length - 1;
2231 			last.Next = second.tags;
2232 			last.Next.Previous = last;
2233 
2234 			// Fix up references within the chain
2235 			last = last.Next;
2236 			while (last != null) {
2237 				last.Line = first;
2238 				last.Start += shift;
2239 				last = last.Next;
2240 			}
2241 
2242 			// Combine both lines' strings
2243 			first.text.Insert(first.text.Length, second.text.ToString());
2244 			first.Grow(first.text.Length);
2245 
2246 			// Remove the reference to our (now combined) tags from the doomed line
2247 			second.tags = null;
2248 
2249 			// Renumber lines
2250 			DecrementLines(first.line_no + 2);	// first.line_no + 1 will be deleted, so we need to start renumbering one later
2251 
2252 			// Mop up
2253 			first.recalc = true;
2254 			first.height = 0;	// This forces RecalcDocument/UpdateView to redraw from this line on
2255 			first.Streamline(lines);
2256 
2257 			// Update Caret, Selection, etc
2258 			if (caret.line == second) {
2259 				caret.Combine(first, shift);
2260 			}
2261 			if (selection_anchor.line == second) {
2262 				selection_anchor.Combine(first, shift);
2263 			}
2264 			if (selection_start.line == second) {
2265 				selection_start.Combine(first, shift);
2266 			}
2267 			if (selection_end.line == second) {
2268 				selection_end.Combine(first, shift);
2269 			}
2270 
2271 			#if Debug
2272 				Line	check_first;
2273 				Line	check_second;
2274 
2275 				check_first = GetLine(first.line_no);
2276 				check_second = GetLine(check_first.line_no + 1);
2277 
2278 				Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2279 			#endif
2280 
2281 			this.Delete(second);
2282 
2283 			#if Debug
2284 				check_first = GetLine(first.line_no);
2285 				check_second = GetLine(check_first.line_no + 1);
2286 
2287 				Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2288 			#endif
2289 		}
2290 
2291 		// Split the line at the position into two
Split(int LineNo, int pos)2292 		internal void Split(int LineNo, int pos) {
2293 			Line	line;
2294 			LineTag	tag;
2295 
2296 			line = GetLine(LineNo);
2297 			tag = LineTag.FindTag(line, pos);
2298 			Split(line, tag, pos);
2299 		}
2300 
Split(Line line, int pos)2301 		internal void Split(Line line, int pos) {
2302 			LineTag	tag;
2303 
2304 			tag = LineTag.FindTag(line, pos);
2305 			Split(line, tag, pos);
2306 		}
2307 
2308 		///<summary>Split line at given tag and position into two lines</summary>
2309 		///if more space becomes available on previous line
Split(Line line, LineTag tag, int pos)2310 		internal void Split(Line line, LineTag tag, int pos) {
2311 			LineTag	new_tag;
2312 			Line	new_line;
2313 			bool	move_caret;
2314 			bool	move_sel_start;
2315 			bool	move_sel_end;
2316 
2317 			move_caret = false;
2318 			move_sel_start = false;
2319 			move_sel_end = false;
2320 
2321 #if DEBUG
2322 			SanityCheck();
2323 
2324 			if (tag.End < pos)
2325 				throw new Exception ("Split called with the wrong tag");
2326 #endif
2327 
2328 			// Adjust selection and cursors
2329 			if (caret.line == line && caret.pos >= pos) {
2330 				move_caret = true;
2331 			}
2332 			if (selection_start.line == line && selection_start.pos > pos) {
2333 				move_sel_start = true;
2334 			}
2335 
2336 			if (selection_end.line == line && selection_end.pos > pos) {
2337 				move_sel_end = true;
2338 			}
2339 
2340 			// cover the easy case first
2341 			if (pos == line.text.Length) {
2342 				Add (line.line_no + 1, String.Empty, line.alignment, tag.Font, tag.Color, line.ending);
2343 
2344 				new_line = GetLine (line.line_no + 1);
2345 
2346 				if (move_caret) {
2347 					caret.line = new_line;
2348 					caret.tag = new_line.tags;
2349 					caret.pos = 0;
2350 
2351 					if (selection_visible == false) {
2352 						SetSelectionToCaret (true);
2353 					}
2354 				}
2355 
2356 				if (move_sel_start) {
2357 					selection_start.line = new_line;
2358 					selection_start.pos = 0;
2359 					selection_start.tag = new_line.tags;
2360 				}
2361 
2362 				if (move_sel_end) {
2363 					selection_end.line = new_line;
2364 					selection_end.pos = 0;
2365 					selection_end.tag = new_line.tags;
2366 				}
2367 
2368 #if DEBUG
2369 				SanityCheck ();
2370 #endif
2371 				return;
2372 			}
2373 
2374 			// We need to move the rest of the text into the new line
2375 			Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.Font, tag.Color, line.ending);
2376 
2377 			// Now transfer our tags from this line to the next
2378 			new_line = GetLine(line.line_no + 1);
2379 
2380 			line.recalc = true;
2381 			new_line.recalc = true;
2382 
2383 			//make sure that if we are at the end of a tag, we start on the begining
2384 			//of a new one, if one exists... Stops us creating an empty tag and
2385 			//make the operation easier.
2386 			if (tag.Next != null && (tag.Next.Start - 1) == pos)
2387 				tag = tag.Next;
2388 
2389 			if ((tag.Start - 1) == pos) {
2390 				int	shift;
2391 
2392 				// We can simply break the chain and move the tag into the next line
2393 
2394 				// if the tag we are moving is the first, create an empty tag
2395 				// for the line we are leaving behind
2396 				if (tag == line.tags) {
2397 					new_tag = new LineTag(line, 1);
2398 					new_tag.CopyFormattingFrom (tag);
2399 					line.tags = new_tag;
2400 				}
2401 
2402 				if (tag.Previous != null) {
2403 					tag.Previous.Next = null;
2404 				}
2405 				new_line.tags = tag;
2406 				tag.Previous = null;
2407 				tag.Line = new_line;
2408 
2409 				// Walk the list and correct the start location of the tags we just bumped into the next line
2410 				shift = tag.Start - 1;
2411 
2412 				new_tag = tag;
2413 				while (new_tag != null) {
2414 					new_tag.Start -= shift;
2415 					new_tag.Line = new_line;
2416 					new_tag = new_tag.Next;
2417 				}
2418 			} else {
2419 				int	shift;
2420 
2421 				new_tag = new LineTag (new_line, 1);
2422 				new_tag.Next = tag.Next;
2423 				new_tag.CopyFormattingFrom (tag);
2424 				new_line.tags = new_tag;
2425 				if (new_tag.Next != null) {
2426 					new_tag.Next.Previous = new_tag;
2427 				}
2428 				tag.Next = null;
2429 
2430 				shift = pos;
2431 				new_tag = new_tag.Next;
2432 				while (new_tag != null) {
2433 					new_tag.Start -= shift;
2434 					new_tag.Line = new_line;
2435 					new_tag = new_tag.Next;
2436 
2437 				}
2438 			}
2439 
2440 			if (move_caret) {
2441 				caret.line = new_line;
2442 				caret.pos = caret.pos - pos;
2443 				caret.tag = caret.line.FindTag(caret.pos);
2444 
2445 				if (selection_visible == false) {
2446 					SetSelectionToCaret (true);
2447 					move_sel_start = false;
2448 					move_sel_end = false;
2449 				}
2450 			}
2451 
2452 			if (move_sel_start) {
2453 				selection_start.line = new_line;
2454 				selection_start.pos = selection_start.pos - pos;
2455 				if  (selection_start.Equals(selection_end))
2456 					selection_start.tag = new_line.FindTag(selection_start.pos);
2457 				else
2458 					selection_start.tag = new_line.FindTag (selection_start.pos + 1);
2459 			}
2460 
2461 			if (move_sel_end) {
2462 				selection_end.line = new_line;
2463 				selection_end.pos = selection_end.pos - pos;
2464 				selection_end.tag = new_line.FindTag(selection_end.pos);
2465 			}
2466 
2467 			CharCount -= line.text.Length - pos;
2468 			line.text.Remove(pos, line.text.Length - pos);
2469 #if DEBUG
2470 			SanityCheck ();
2471 #endif
2472 		}
2473 
2474 #if DEBUG
SanityCheck()2475 		private void SanityCheck () {
2476 			for (int i = 1; i < lines; i++) {
2477 				LineTag tag = GetLine (i).tags;
2478 
2479 				if (tag.Start != 1)
2480 					throw new Exception ("Line doesn't start at the begining");
2481 
2482 				int start = 1;
2483 				tag = tag.Next;
2484 
2485 				while (tag != null) {
2486 					if (tag.Start == start)
2487 						throw new Exception ("Empty tag!");
2488 
2489 					if (tag.Start < start)
2490 						throw new Exception ("Insane!!");
2491 
2492 					start = tag.Start;
2493 					tag = tag.Next;
2494 				}
2495 			}
2496 		}
2497 #endif
2498 
2499 		// Adds a line of text, with given font.
2500 		// Bumps any line at that line number that already exists down
Add(int LineNo, string Text, Font font, Color color, LineEnding ending)2501 		internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending)
2502 		{
2503 			Add (LineNo, Text, alignment, font, color, ending);
2504 		}
2505 
Add(int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)2506 		internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)
2507 		{
2508 			Line	add;
2509 			Line	line;
2510 			int	line_no;
2511 
2512 			CharCount += Text.Length;
2513 
2514 			if (LineNo<1 || Text == null) {
2515 				if (LineNo<1) {
2516 					throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2517 				} else {
2518 					throw new ArgumentNullException("Text", "Cannot insert NULL line");
2519 				}
2520 			}
2521 
2522 			add = new Line (this, LineNo, Text, align, font, color, ending);
2523 
2524 			line = document;
2525 			while (line != sentinel) {
2526 				add.parent = line;
2527 				line_no = line.line_no;
2528 
2529 				if (LineNo > line_no) {
2530 					line = line.right;
2531 				} else if (LineNo < line_no) {
2532 					line = line.left;
2533 				} else {
2534 					// Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2535 					IncrementLines(line.line_no);
2536 					line = line.left;
2537 				}
2538 			}
2539 
2540 			add.left = sentinel;
2541 			add.right = sentinel;
2542 
2543 			if (add.parent != null) {
2544 				if (LineNo > add.parent.line_no) {
2545 					add.parent.right = add;
2546 				} else {
2547 					add.parent.left = add;
2548 				}
2549 			} else {
2550 				// Root node
2551 				document = add;
2552 			}
2553 
2554 			RebalanceAfterAdd(add);
2555 
2556 			lines++;
2557 		}
2558 
Clear()2559 		internal virtual void Clear() {
2560 			lines = 0;
2561 			CharCount = 0;
2562 			document = sentinel;
2563 		}
2564 
Clone()2565 		public virtual object Clone() {
2566 			Document clone;
2567 
2568 			clone = new Document(null);
2569 
2570 			clone.lines = this.lines;
2571 			clone.document = (Line)document.Clone();
2572 
2573 			return clone;
2574 		}
2575 
Delete(int LineNo)2576 		private void Delete (int LineNo)
2577 		{
2578 			Line	line;
2579 
2580 			if (LineNo > lines)
2581 				return;
2582 
2583 			line = GetLine (LineNo);
2584 
2585 			CharCount -= line.text.Length;
2586 
2587 			DecrementLines (LineNo + 1);
2588 			Delete (line);
2589 		}
2590 
Delete(Line line1)2591 		private void Delete(Line line1) {
2592 			Line	line2;// = new Line();
2593 			Line	line3;
2594 
2595 			if ((line1.left == sentinel) || (line1.right == sentinel)) {
2596 				line3 = line1;
2597 			} else {
2598 				line3 = line1.right;
2599 				while (line3.left != sentinel) {
2600 					line3 = line3.left;
2601 				}
2602 			}
2603 
2604 			if (line3.left != sentinel) {
2605 				line2 = line3.left;
2606 			} else {
2607 				line2 = line3.right;
2608 			}
2609 
2610 			line2.parent = line3.parent;
2611 			if (line3.parent != null) {
2612 				if(line3 == line3.parent.left) {
2613 					line3.parent.left = line2;
2614 				} else {
2615 					line3.parent.right = line2;
2616 				}
2617 			} else {
2618 				document = line2;
2619 			}
2620 
2621 			if (line3 != line1) {
2622 				LineTag	tag;
2623 
2624 				if (selection_start.line == line3) {
2625 					selection_start.line = line1;
2626 				}
2627 
2628 				if (selection_end.line == line3) {
2629 					selection_end.line = line1;
2630 				}
2631 
2632 				if (selection_anchor.line == line3) {
2633 					selection_anchor.line = line1;
2634 				}
2635 
2636 				if (caret.line == line3) {
2637 					caret.line = line1;
2638 				}
2639 
2640 
2641 				line1.alignment = line3.alignment;
2642 				line1.ascent = line3.ascent;
2643 				line1.hanging_indent = line3.hanging_indent;
2644 				line1.height = line3.height;
2645 				line1.indent = line3.indent;
2646 				line1.line_no = line3.line_no;
2647 				line1.recalc = line3.recalc;
2648 				line1.right_indent = line3.right_indent;
2649 				line1.ending = line3.ending;
2650 				line1.space = line3.space;
2651 				line1.tags = line3.tags;
2652 				line1.text = line3.text;
2653 				line1.widths = line3.widths;
2654 				line1.offset = line3.offset;
2655 
2656 				tag = line1.tags;
2657 				while (tag != null) {
2658 					tag.Line = line1;
2659 					tag = tag.Next;
2660 				}
2661 			}
2662 
2663 			if (line3.color == LineColor.Black)
2664 				RebalanceAfterDelete(line2);
2665 
2666 			this.lines--;
2667 		}
2668 
2669 		// Invalidates the start line until the end of the viewstate
InvalidateLinesAfter(Line start)2670 		internal void InvalidateLinesAfter (Line start) {
2671 			owner.Invalidate (new Rectangle (0, start.Y - viewport_y, viewport_width, viewport_height - start.Y));
2672 		}
2673 
2674 		// Invalidate a section of the document to trigger redraw
Invalidate(Line start, int start_pos, Line end, int end_pos)2675 		internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2676 			Line	l1;
2677 			Line	l2;
2678 			int	p1;
2679 			int	p2;
2680 
2681 			if ((start == end) && (start_pos == end_pos)) {
2682 				return;
2683 			}
2684 
2685 			if (end_pos == -1) {
2686 				end_pos = end.text.Length;
2687 			}
2688 
2689 			// figure out what's before what so the logic below is straightforward
2690 			if (start.line_no < end.line_no) {
2691 				l1 = start;
2692 				p1 = start_pos;
2693 
2694 				l2 = end;
2695 				p2 = end_pos;
2696 			} else if (start.line_no > end.line_no) {
2697 				l1 = end;
2698 				p1 = end_pos;
2699 
2700 				l2 = start;
2701 				p2 = start_pos;
2702 			} else {
2703 				if (start_pos < end_pos) {
2704 					l1 = start;
2705 					p1 = start_pos;
2706 
2707 					l2 = end;
2708 					p2 = end_pos;
2709 				} else {
2710 					l1 = end;
2711 					p1 = end_pos;
2712 
2713 					l2 = start;
2714 					p2 = start_pos;
2715 				}
2716 
2717 				int endpoint = (int) l1.widths [p2];
2718 				if (p2 == l1.text.Length + 1) {
2719 					endpoint = (int) viewport_width;
2720 				}
2721 
2722 				#if Debug
2723 					Console.WriteLine("Invaliding backwards from {0}:{1} to {2}:{3}   {4}",
2724 							l1.line_no, p1, l2.line_no, p2,
2725 							new Rectangle(
2726 								(int)l1.widths[p1] + l1.X - viewport_x,
2727 								l1.Y - viewport_y,
2728 								(int)l1.widths[p2],
2729 								l1.height
2730 								)
2731 						);
2732 				#endif
2733 
2734 				owner.Invalidate(new Rectangle (
2735 					offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
2736 					offset_y + l1.Y - viewport_y,
2737 					endpoint - (int) l1.widths [p1] + 1,
2738 					l1.height));
2739 				return;
2740 			}
2741 
2742 			#if Debug
2743 				Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Start  => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height);
2744 				Console.WriteLine ("invalidate start line:  {0}  position:  {1}", l1.text, p1);
2745 			#endif
2746 
2747 			// Three invalidates:
2748 			// First line from start
2749 			owner.Invalidate(new Rectangle(
2750 				offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
2751 				offset_y + l1.Y - viewport_y,
2752 				viewport_width,
2753 				l1.height));
2754 
2755 
2756 			// lines inbetween
2757 			if ((l1.line_no + 1) < l2.line_no) {
2758 				int	y;
2759 
2760 				y = GetLine(l1.line_no + 1).Y;
2761 				owner.Invalidate(new Rectangle(
2762 					offset_x,
2763 					offset_y + y - viewport_y,
2764 					viewport_width,
2765 					l2.Y - y));
2766 
2767 				#if Debug
2768 					Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, 0, y - viewport_y, viewport_width, l2.Y - y);
2769 				#endif
2770 			}
2771 
2772 
2773 			// Last line to end
2774 			owner.Invalidate(new Rectangle(
2775 				offset_x + (int)l2.widths[0] + l2.X - viewport_x,
2776 				offset_y + l2.Y - viewport_y,
2777 				(int)l2.widths[p2] + 1,
2778 				l2.height));
2779 
2780 			#if Debug
2781 				Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} End    => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height);
2782 			#endif
2783 		}
2784 
2785 		/// <summary>Select text around caret</summary>
ExpandSelection(CaretSelection mode, bool to_caret)2786 		internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2787 			if (to_caret) {
2788 				// We're expanding the selection to the caret position
2789 				switch(mode) {
2790 					case CaretSelection.Line: {
2791 						// Invalidate the selection delta
2792 						if (caret > selection_prev) {
2793 							Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2794 						} else {
2795 							Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2796 						}
2797 
2798 						if (caret.line.line_no <= selection_anchor.line.line_no) {
2799 							selection_start.line = caret.line;
2800 							selection_start.tag = caret.line.tags;
2801 							selection_start.pos = 0;
2802 
2803 							selection_end.line = selection_anchor.line;
2804 							selection_end.tag = selection_anchor.tag;
2805 							selection_end.pos = selection_anchor.pos;
2806 
2807 							selection_end_anchor = true;
2808 						} else {
2809 							selection_start.line = selection_anchor.line;
2810 							selection_start.pos = selection_anchor.height;
2811 							selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2812 
2813 							selection_end.line = caret.line;
2814 							selection_end.tag = caret.line.tags;
2815 							selection_end.pos = caret.line.text.Length;
2816 
2817 							selection_end_anchor = false;
2818 						}
2819 						selection_prev.line = caret.line;
2820 						selection_prev.tag = caret.tag;
2821 						selection_prev.pos = caret.pos;
2822 
2823 						break;
2824 					}
2825 
2826 					case CaretSelection.Word: {
2827 						int	start_pos;
2828 						int	end_pos;
2829 
2830 						start_pos = FindWordSeparator(caret.line, caret.pos, false);
2831 						end_pos = FindWordSeparator(caret.line, caret.pos, true);
2832 
2833 
2834 						// Invalidate the selection delta
2835 						if (caret > selection_prev) {
2836 							Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2837 						} else {
2838 							Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2839 						}
2840 						if (caret < selection_anchor) {
2841 							selection_start.line = caret.line;
2842 							selection_start.tag = caret.line.FindTag(start_pos + 1);
2843 							selection_start.pos = start_pos;
2844 
2845 							selection_end.line = selection_anchor.line;
2846 							selection_end.tag = selection_anchor.tag;
2847 							selection_end.pos = selection_anchor.pos;
2848 
2849 							selection_prev.line = caret.line;
2850 							selection_prev.tag = caret.tag;
2851 							selection_prev.pos = start_pos;
2852 
2853 							selection_end_anchor = true;
2854 						} else {
2855 							selection_start.line = selection_anchor.line;
2856 							selection_start.pos = selection_anchor.height;
2857 							selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
2858 
2859 							selection_end.line = caret.line;
2860 							selection_end.tag = caret.line.FindTag(end_pos);
2861 							selection_end.pos = end_pos;
2862 
2863 							selection_prev.line = caret.line;
2864 							selection_prev.tag = caret.tag;
2865 							selection_prev.pos = end_pos;
2866 
2867 							selection_end_anchor = false;
2868 						}
2869 						break;
2870 					}
2871 
2872 					case CaretSelection.Position: {
2873 						SetSelectionToCaret(false);
2874 						return;
2875 					}
2876 				}
2877 			} else {
2878 				// We're setting the selection 'around' the caret position
2879 				switch(mode) {
2880 					case CaretSelection.Line: {
2881 						this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2882 
2883 						selection_start.line = caret.line;
2884 						selection_start.tag = caret.line.tags;
2885 						selection_start.pos = 0;
2886 
2887 						selection_end.line = caret.line;
2888 						selection_end.pos = caret.line.text.Length;
2889 						selection_end.tag = caret.line.FindTag(selection_end.pos);
2890 
2891 						selection_anchor.line = selection_end.line;
2892 						selection_anchor.tag = selection_end.tag;
2893 						selection_anchor.pos = selection_end.pos;
2894 						selection_anchor.height = 0;
2895 
2896 						selection_prev.line = caret.line;
2897 						selection_prev.tag = caret.tag;
2898 						selection_prev.pos = caret.pos;
2899 
2900 						this.selection_end_anchor = true;
2901 
2902 						break;
2903 					}
2904 
2905 					case CaretSelection.Word: {
2906 						int	start_pos;
2907 						int	end_pos;
2908 
2909 						start_pos = FindWordSeparator(caret.line, caret.pos, false);
2910 						end_pos = FindWordSeparator(caret.line, caret.pos, true);
2911 
2912 						this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2913 
2914 						selection_start.line = caret.line;
2915 						selection_start.tag = caret.line.FindTag(start_pos + 1);
2916 						selection_start.pos = start_pos;
2917 
2918 						selection_end.line = caret.line;
2919 						selection_end.tag = caret.line.FindTag(end_pos);
2920 						selection_end.pos = end_pos;
2921 
2922 						selection_anchor.line = selection_end.line;
2923 						selection_anchor.tag = selection_end.tag;
2924 						selection_anchor.pos = selection_end.pos;
2925 						selection_anchor.height = start_pos;
2926 
2927 						selection_prev.line = caret.line;
2928 						selection_prev.tag = caret.tag;
2929 						selection_prev.pos = caret.pos;
2930 
2931 						this.selection_end_anchor = true;
2932 
2933 						break;
2934 					}
2935 				}
2936 			}
2937 
2938 			SetSelectionVisible (!(selection_start == selection_end));
2939 		}
2940 
SetSelectionToCaret(bool start)2941 		internal void SetSelectionToCaret(bool start) {
2942 			if (start) {
2943 				// Invalidate old selection; selection is being reset to empty
2944 				this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2945 
2946 				selection_start.line = caret.line;
2947 				selection_start.tag = caret.tag;
2948 				selection_start.pos = caret.pos;
2949 
2950 				// start always also selects end
2951 				selection_end.line = caret.line;
2952 				selection_end.tag = caret.tag;
2953 				selection_end.pos = caret.pos;
2954 
2955 				selection_anchor.line = caret.line;
2956 				selection_anchor.tag = caret.tag;
2957 				selection_anchor.pos = caret.pos;
2958 			} else {
2959 				// Invalidate from previous end to caret (aka new end)
2960 				if (selection_end_anchor) {
2961 					if (selection_start != caret) {
2962 						this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
2963 					}
2964 				} else {
2965 					if (selection_end != caret) {
2966 						this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
2967 					}
2968 				}
2969 
2970 				if (caret < selection_anchor) {
2971 					selection_start.line = caret.line;
2972 					selection_start.tag = caret.tag;
2973 					selection_start.pos = caret.pos;
2974 
2975 					selection_end.line = selection_anchor.line;
2976 					selection_end.tag = selection_anchor.tag;
2977 					selection_end.pos = selection_anchor.pos;
2978 
2979 					selection_end_anchor = true;
2980 				} else {
2981 					selection_start.line = selection_anchor.line;
2982 					selection_start.tag = selection_anchor.tag;
2983 					selection_start.pos = selection_anchor.pos;
2984 
2985 					selection_end.line = caret.line;
2986 					selection_end.tag = caret.tag;
2987 					selection_end.pos = caret.pos;
2988 
2989 					selection_end_anchor = false;
2990 				}
2991 			}
2992 
2993 			SetSelectionVisible (!(selection_start == selection_end));
2994 		}
2995 
SetSelection(Line start, int start_pos, Line end, int end_pos)2996 		internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
2997 			if (selection_visible) {
2998 				Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2999 			}
3000 
3001 			if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
3002 				selection_start.line = end;
3003 				selection_start.tag = LineTag.FindTag(end, end_pos);
3004 				selection_start.pos = end_pos;
3005 
3006 				selection_end.line = start;
3007 				selection_end.tag = LineTag.FindTag(start, start_pos);
3008 				selection_end.pos = start_pos;
3009 
3010 				selection_end_anchor = true;
3011 			} else {
3012 				selection_start.line = start;
3013 				selection_start.tag = LineTag.FindTag(start, start_pos);
3014 				selection_start.pos = start_pos;
3015 
3016 				selection_end.line = end;
3017 				selection_end.tag = LineTag.FindTag(end, end_pos);
3018 				selection_end.pos = end_pos;
3019 
3020 				selection_end_anchor = false;
3021 			}
3022 
3023 			selection_anchor.line = start;
3024 			selection_anchor.tag = selection_start.tag;
3025 			selection_anchor.pos = start_pos;
3026 
3027 			if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
3028 				SetSelectionVisible (false);
3029 			} else {
3030 				SetSelectionVisible (true);
3031 				Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3032 			}
3033 		}
3034 
SetSelectionStart(Line start, int start_pos, bool invalidate)3035 		internal void SetSelectionStart(Line start, int start_pos, bool invalidate) {
3036 			// Invalidate from the previous to the new start pos
3037 			if (invalidate)
3038 				Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3039 
3040 			selection_start.line = start;
3041 			selection_start.pos = start_pos;
3042 			selection_start.tag = LineTag.FindTag(start, start_pos);
3043 
3044 			selection_anchor.line = start;
3045 			selection_anchor.pos = start_pos;
3046 			selection_anchor.tag = selection_start.tag;
3047 
3048 			selection_end_anchor = false;
3049 
3050 
3051 			if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3052 				SetSelectionVisible (true);
3053 			} else {
3054 				SetSelectionVisible (false);
3055 			}
3056 
3057 			if (invalidate)
3058 				Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3059 		}
3060 
SetSelectionStart(int character_index, bool invalidate)3061 		internal void SetSelectionStart(int character_index, bool invalidate) {
3062 			Line	line;
3063 			LineTag	tag;
3064 			int	pos;
3065 
3066 			if (character_index < 0) {
3067 				return;
3068 			}
3069 
3070 			CharIndexToLineTag(character_index, out line, out tag, out pos);
3071 			SetSelectionStart(line, pos, invalidate);
3072 		}
3073 
SetSelectionEnd(Line end, int end_pos, bool invalidate)3074 		internal void SetSelectionEnd(Line end, int end_pos, bool invalidate) {
3075 
3076 			if (end == selection_end.line && end_pos == selection_start.pos) {
3077 				selection_anchor.line = selection_start.line;
3078 				selection_anchor.tag = selection_start.tag;
3079 				selection_anchor.pos = selection_start.pos;
3080 
3081 				selection_end.line = selection_start.line;
3082 				selection_end.tag = selection_start.tag;
3083 				selection_end.pos = selection_start.pos;
3084 
3085 				selection_end_anchor = false;
3086 			} else if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3087 				selection_start.line = end;
3088 				selection_start.tag = LineTag.FindTag(end, end_pos);
3089 				selection_start.pos = end_pos;
3090 
3091 				selection_end.line = selection_anchor.line;
3092 				selection_end.tag = selection_anchor.tag;
3093 				selection_end.pos = selection_anchor.pos;
3094 
3095 				selection_end_anchor = true;
3096 			} else {
3097 				selection_start.line = selection_anchor.line;
3098 				selection_start.tag = selection_anchor.tag;
3099 				selection_start.pos = selection_anchor.pos;
3100 
3101 				selection_end.line = end;
3102 				selection_end.tag = LineTag.FindTag(end, end_pos);
3103 				selection_end.pos = end_pos;
3104 
3105 				selection_end_anchor = false;
3106 			}
3107 
3108 			if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3109 				SetSelectionVisible (true);
3110 				if (invalidate)
3111 					Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3112 			} else {
3113 				SetSelectionVisible (false);
3114 				// ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3115 			}
3116 		}
3117 
SetSelectionEnd(int character_index, bool invalidate)3118 		internal void SetSelectionEnd(int character_index, bool invalidate) {
3119 			Line	line;
3120 			LineTag	tag;
3121 			int	pos;
3122 
3123 			if (character_index < 0) {
3124 				return;
3125 			}
3126 
3127 			CharIndexToLineTag(character_index, out line, out tag, out pos);
3128 			SetSelectionEnd(line, pos, invalidate);
3129 		}
3130 
SetSelection(Line start, int start_pos)3131 		internal void SetSelection(Line start, int start_pos) {
3132 			if (selection_visible) {
3133 				Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3134 			}
3135 
3136 			selection_start.line = start;
3137 			selection_start.pos = start_pos;
3138 			selection_start.tag = LineTag.FindTag(start, start_pos);
3139 
3140 			selection_end.line = start;
3141 			selection_end.tag = selection_start.tag;
3142 			selection_end.pos = start_pos;
3143 
3144 			selection_anchor.line = start;
3145 			selection_anchor.tag = selection_start.tag;
3146 			selection_anchor.pos = start_pos;
3147 
3148 			selection_end_anchor = false;
3149 			SetSelectionVisible (false);
3150 		}
3151 
InvalidateSelectionArea()3152 		internal void InvalidateSelectionArea() {
3153 			Invalidate (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3154 		}
3155 
3156 		// Return the current selection, as string
GetSelection()3157 		internal string GetSelection() {
3158 			// We return String.Empty if there is no selection
3159 			if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3160 				return string.Empty;
3161 			}
3162 
3163 			if (selection_start.line == selection_end.line) {
3164 				return selection_start.line.text.ToString (selection_start.pos, selection_end.pos - selection_start.pos);
3165 			} else {
3166 				StringBuilder	sb;
3167 				int		i;
3168 				int		start;
3169 				int		end;
3170 
3171 				sb = new StringBuilder();
3172 				start = selection_start.line.line_no;
3173 				end = selection_end.line.line_no;
3174 
3175 				sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos));
3176 
3177 				if ((start + 1) < end) {
3178 					for (i = start + 1; i < end; i++) {
3179 						sb.Append(GetLine(i).text.ToString());
3180 					}
3181 				}
3182 
3183 				sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3184 
3185 				return sb.ToString();
3186 			}
3187 		}
3188 
ReplaceSelection(string s, bool select_new)3189 		internal void ReplaceSelection(string s, bool select_new) {
3190 			int		i;
3191 
3192 			int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
3193 			SuspendRecalc ();
3194 
3195 			// First, delete any selected text
3196 			if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3197 				if (selection_start.line == selection_end.line) {
3198 					undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3199 
3200 					DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos);
3201 
3202 					// The tag might have been removed, we need to recalc it
3203 					selection_start.tag = selection_start.line.FindTag(selection_start.pos + 1);
3204 				} else {
3205 					int		start;
3206 					int		end;
3207 
3208 					start = selection_start.line.line_no;
3209 					end = selection_end.line.line_no;
3210 
3211 					undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3212 
3213 					InvalidateLinesAfter(selection_start.line);
3214 
3215 					// Delete first line
3216 					DeleteChars (selection_start.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3217 					selection_start.line.recalc = true;
3218 
3219 					// Delete last line
3220 					DeleteChars(selection_end.line, 0, selection_end.pos);
3221 
3222 					start++;
3223 					if (start < end) {
3224 						for (i = end - 1; i >= start; i--) {
3225 							Delete(i);
3226 						}
3227 					}
3228 
3229 					// BIG FAT WARNING - selection_end.line might be stale due
3230 					// to the above Delete() call. DONT USE IT before hitting the end of this method!
3231 
3232 					// Join start and end
3233 					Combine(selection_start.line.line_no, start);
3234 				}
3235 			}
3236 
3237 
3238 			Insert(selection_start.line, selection_start.pos, false, s);
3239 			undo.RecordInsertString (selection_start.line, selection_start.pos, s);
3240 			ResumeRecalc (false);
3241 
3242 			Line begin_update_line = selection_start.line;
3243 			int begin_update_pos = selection_start.pos;
3244 
3245 			if (!select_new) {
3246 				CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
3247 						out selection_start.tag, out selection_start.pos);
3248 
3249 				selection_end.line = selection_start.line;
3250 				selection_end.pos = selection_start.pos;
3251 				selection_end.tag = selection_start.tag;
3252 				selection_anchor.line = selection_start.line;
3253 				selection_anchor.pos = selection_start.pos;
3254 				selection_anchor.tag = selection_start.tag;
3255 
3256 				SetSelectionVisible (false);
3257 			} else {
3258 				CharIndexToLineTag(selection_start_pos, out selection_start.line,
3259 						out selection_start.tag, out selection_start.pos);
3260 
3261 				CharIndexToLineTag(selection_start_pos + s.Length, out selection_end.line,
3262 						out selection_end.tag, out selection_end.pos);
3263 
3264 				selection_anchor.line = selection_start.line;
3265 				selection_anchor.pos = selection_start.pos;
3266 				selection_anchor.tag = selection_start.tag;
3267 
3268 				SetSelectionVisible (true);
3269 			}
3270 
3271 			PositionCaret (selection_start.line, selection_start.pos);
3272 			UpdateView (begin_update_line, selection_end.line.line_no - begin_update_line.line_no, begin_update_pos);
3273 		}
3274 
CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos)3275 		internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3276 			Line	line;
3277 			LineTag	tag;
3278 			int	i;
3279 			int	chars;
3280 			int	start;
3281 
3282 			chars = 0;
3283 
3284 			for (i = 1; i <= lines; i++) {
3285 				line = GetLine(i);
3286 
3287 				start = chars;
3288 				chars += line.text.Length;
3289 
3290 				if (index <= chars) {
3291 					// we found the line
3292 					tag = line.tags;
3293 
3294 					while (tag != null) {
3295 						if (index < (start + tag.Start + tag.Length - 1)) {
3296 							line_out = line;
3297 							tag_out = LineTag.GetFinalTag (tag);
3298 							pos = index - start;
3299 							return;
3300 						}
3301 						if (tag.Next == null) {
3302 							Line	next_line;
3303 
3304 							next_line = GetLine(line.line_no + 1);
3305 
3306 							if (next_line != null) {
3307 								line_out = next_line;
3308 								tag_out = LineTag.GetFinalTag (next_line.tags);
3309 								pos = 0;
3310 								return;
3311 							} else {
3312 								line_out = line;
3313 								tag_out = LineTag.GetFinalTag (tag);
3314 								pos = line_out.text.Length;
3315 								return;
3316 							}
3317 						}
3318 						tag = tag.Next;
3319 					}
3320 				}
3321 			}
3322 
3323 			line_out = GetLine(lines);
3324 			tag = line_out.tags;
3325 			while (tag.Next != null) {
3326 				tag = tag.Next;
3327 			}
3328 			tag_out = tag;
3329 			pos = line_out.text.Length;
3330 		}
3331 
LineTagToCharIndex(Line line, int pos)3332 		internal int LineTagToCharIndex(Line line, int pos) {
3333 			int	i;
3334 			int	length;
3335 
3336 			// Count first and last line
3337 			length = 0;
3338 
3339 			// Count the lines in the middle
3340 
3341 			for (i = 1; i < line.line_no; i++) {
3342 				length += GetLine(i).text.Length;
3343 			}
3344 
3345 			length += pos;
3346 
3347 			return length;
3348 		}
3349 
SelectionLength()3350 		internal int SelectionLength() {
3351 			if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3352 				return 0;
3353 			}
3354 
3355 			if (selection_start.line == selection_end.line) {
3356 				return selection_end.pos - selection_start.pos;
3357 			} else {
3358 				int	i;
3359 				int	start;
3360 				int	end;
3361 				int	length;
3362 
3363 				// Count first and last line
3364 				length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3365 
3366 				// Count the lines in the middle
3367 				start = selection_start.line.line_no + 1;
3368 				end = selection_end.line.line_no;
3369 
3370 				if (start < end) {
3371 					for (i = start; i < end; i++) {
3372 						Line line = GetLine (i);
3373 						length += line.text.Length + LineEndingLength (line.ending);
3374 					}
3375 				}
3376 
3377 				return length;
3378 			}
3379 
3380 
3381 		}
3382 
3383 
3384 		// UIA: Method used via reflection in TextRangeProvider
3385 
3386 		/// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
GetLine(int LineNo)3387 		internal Line GetLine(int LineNo) {
3388 			Line	line = document;
3389 
3390 			while (line != sentinel) {
3391 				if (LineNo == line.line_no) {
3392 					return line;
3393 				} else if (LineNo < line.line_no) {
3394 					line = line.left;
3395 				} else {
3396 					line = line.right;
3397 				}
3398 			}
3399 
3400 			return null;
3401 		}
3402 
3403 		/// <summary>Retrieve the previous tag; walks line boundaries</summary>
PreviousTag(LineTag tag)3404 		internal LineTag PreviousTag(LineTag tag) {
3405 			Line l;
3406 
3407 			if (tag.Previous != null) {
3408 				return tag.Previous;
3409 			}
3410 
3411 			// Next line
3412 			if (tag.Line.line_no == 1) {
3413 				return null;
3414 			}
3415 
3416 			l = GetLine(tag.Line.line_no - 1);
3417 			if (l != null) {
3418 				LineTag t;
3419 
3420 				t = l.tags;
3421 				while (t.Next != null) {
3422 					t = t.Next;
3423 				}
3424 				return t;
3425 			}
3426 
3427 			return null;
3428 		}
3429 
3430 		/// <summary>Retrieve the next tag; walks line boundaries</summary>
NextTag(LineTag tag)3431 		internal LineTag NextTag(LineTag tag) {
3432 			Line l;
3433 
3434 			if (tag.Next != null) {
3435 				return tag.Next;
3436 			}
3437 
3438 			// Next line
3439 			l = GetLine(tag.Line.line_no + 1);
3440 			if (l != null) {
3441 				return l.tags;
3442 			}
3443 
3444 			return null;
3445 		}
3446 
ParagraphStart(Line line)3447 		internal Line ParagraphStart(Line line) {
3448 			Line lastline = line;
3449 			do {
3450 				if (line.line_no <= 1)
3451 					break;
3452 
3453 				line = lastline;
3454 				lastline = GetLine (line.line_no - 1);
3455 			} while (lastline.ending == LineEnding.Wrap);
3456 
3457 			return line;
3458 		}
3459 
ParagraphEnd(Line line)3460 		internal Line ParagraphEnd(Line line) {
3461 			Line    l;
3462 
3463 			while (line.ending == LineEnding.Wrap) {
3464 				l = GetLine(line.line_no + 1);
3465 				if ((l == null) || (l.ending != LineEnding.Wrap)) {
3466 					break;
3467 				}
3468 				line = l;
3469 			}
3470 			return line;
3471 		}
3472 
3473 		/// <summary>Give it a pixel offset coordinate and it returns the Line covering that are (offset
3474 		/// is either X or Y depending on if we are multiline
3475 		/// </summary>
GetLineByPixel(int offset, bool exact)3476 		internal Line GetLineByPixel (int offset, bool exact)
3477 		{
3478 			Line	line = document;
3479 			Line	last = null;
3480 
3481 			if (multiline) {
3482 				while (line != sentinel) {
3483 					last = line;
3484 					if ((offset >= line.Y) && (offset < (line.Y+line.height))) {
3485 						return line;
3486 					} else if (offset < line.Y) {
3487 						line = line.left;
3488 					} else {
3489 						line = line.right;
3490 					}
3491 				}
3492 			} else {
3493 				while (line != sentinel) {
3494 					last = line;
3495 					if ((offset >= line.X) && (offset < (line.X + line.Width)))
3496 						return line;
3497 					else if (offset < line.X)
3498 						line = line.left;
3499 					else
3500 						line = line.right;
3501 				}
3502 			}
3503 
3504 			if (exact) {
3505 				return null;
3506 			}
3507 			return last;
3508 		}
3509 
3510 		// UIA: Method used via reflection in TextProviderBehavior
3511 
3512 		// Give it x/y pixel coordinates and it returns the Tag at that position
FindCursor(int x, int y, out int index)3513 		internal LineTag FindCursor (int x, int y, out int index)
3514 		{
3515 			Line line;
3516 
3517 			x -= offset_x;
3518 			y -= offset_y;
3519 
3520 			line = GetLineByPixel (multiline ? y : x, false);
3521 
3522 			LineTag tag = line.GetTag (x);
3523 
3524 			if (tag.Length == 0 && tag.Start == 1)
3525 				index = 0;
3526 			else
3527 				index = tag.GetCharIndex (x - line.align_shift);
3528 
3529 			return tag;
3530 		}
3531 
3532 		/// <summary>Format area of document in specified font and color</summary>
3533 		/// <param name="start_pos">1-based start position on start_line</param>
3534 		/// <param name="end_pos">1-based end position on end_line </param>
FormatText(Line start_line, int start_pos, Line end_line, int end_pos, Font font, Color color, Color back_color, FormatSpecified specified)3535 		internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
3536 				Color color, Color back_color, FormatSpecified specified)
3537 		{
3538 			Line    l;
3539 
3540 			// First, format the first line
3541 			if (start_line != end_line) {
3542 				// First line
3543 				LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color, back_color, specified);
3544 
3545 				// Format last line
3546 				LineTag.FormatText(end_line, 1, end_pos, font, color, back_color, specified);
3547 
3548 				// Now all the lines inbetween
3549 				for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3550 					l = GetLine(i);
3551 					LineTag.FormatText(l, 1, l.text.Length, font, color, back_color, specified);
3552 				}
3553 			} else {
3554 				// Special case, single line
3555 				LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
3556 
3557 				if ((end_pos - start_pos) == 0 && CaretTag.Length != 0)
3558 					CaretTag = CaretTag.Next;
3559 			}
3560 		}
3561 
RecalculateAlignments()3562 		internal void RecalculateAlignments ()
3563 		{
3564 			Line	line;
3565 			int	line_no;
3566 
3567 			line_no = 1;
3568 
3569 
3570 
3571 			while (line_no <= lines) {
3572 				line = GetLine(line_no);
3573 
3574 				if (line != null) {
3575 					switch (line.alignment) {
3576 					case HorizontalAlignment.Left:
3577 						line.align_shift = 0;
3578 						break;
3579 					case HorizontalAlignment.Center:
3580  						line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3581 						break;
3582 					case HorizontalAlignment.Right:
3583  						line.align_shift = viewport_width - (int)line.widths[line.text.Length] - right_margin;
3584 						break;
3585 					}
3586 				}
3587 
3588 				line_no++;
3589 			}
3590 			return;
3591 		}
3592 
3593 		/// <summary>Calculate formatting for the whole document</summary>
RecalculateDocument(Graphics g)3594 		internal bool RecalculateDocument(Graphics g) {
3595 			return RecalculateDocument(g, 1, this.lines, false);
3596 		}
3597 
3598 		/// <summary>Calculate formatting starting at a certain line</summary>
RecalculateDocument(Graphics g, int start)3599 		internal bool RecalculateDocument(Graphics g, int start) {
3600 			return RecalculateDocument(g, start, this.lines, false);
3601 		}
3602 
3603 		/// <summary>Calculate formatting within two given line numbers</summary>
RecalculateDocument(Graphics g, int start, int end)3604 		internal bool RecalculateDocument(Graphics g, int start, int end) {
3605 			return RecalculateDocument(g, start, end, false);
3606 		}
3607 
3608 		/// <summary>With optimize on, returns true if line heights changed</summary>
RecalculateDocument(Graphics g, int start, int end, bool optimize)3609 		internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3610 			Line	line;
3611 			int	line_no;
3612 			int	offset;
3613 			int	new_width;
3614 			bool	changed;
3615 			int	shift;
3616 
3617 			if (recalc_suspended > 0) {
3618 				recalc_pending = true;
3619 				recalc_start = Math.Min (recalc_start, start);
3620 				recalc_end = Math.Max (recalc_end, end);
3621 				recalc_optimize = optimize;
3622 				return false;
3623 			}
3624 
3625 			// Fixup the positions, they can go kinda nuts
3626 			// (this is suspend and resume recalc - they set them to 1 and max)
3627 			start = Math.Max (start, 1);
3628 			end = Math.Min (end, lines);
3629 
3630 			offset = GetLine(start).offset;
3631 			line_no = start;
3632 			new_width = 0;
3633 			shift = this.lines;
3634 			if (!optimize) {
3635 				changed = true;		// We always return true if we run non-optimized
3636 			} else {
3637 				changed = false;
3638 			}
3639 
3640 			while (line_no <= (end + this.lines - shift)) {
3641 				line = GetLine(line_no++);
3642 				line.offset = offset;
3643 
3644 				// if we are not calculating a password
3645 				if (!calc_pass) {
3646 					if (!optimize) {
3647 						line.RecalculateLine(g, this);
3648 					} else {
3649 						if (line.recalc && line.RecalculateLine(g, this)) {
3650 							changed = true;
3651 							// If the height changed, all subsequent lines change
3652 							end = this.lines;
3653 							shift = this.lines;
3654 						}
3655 					}
3656 				} else {
3657 					if (!optimize) {
3658 						line.RecalculatePasswordLine(g, this);
3659 					} else {
3660 						if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3661 							changed = true;
3662 							// If the height changed, all subsequent lines change
3663 							end = this.lines;
3664 							shift = this.lines;
3665 						}
3666 					}
3667 				}
3668 
3669 				if (line.widths[line.text.Length] > new_width) {
3670 					new_width = (int)line.widths[line.text.Length];
3671 				}
3672 
3673 				// Calculate alignment
3674 				if (line.alignment != HorizontalAlignment.Left) {
3675 					if (line.alignment == HorizontalAlignment.Center) {
3676 						line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3677 					} else {
3678 						line.align_shift = viewport_width - (int)line.widths[line.text.Length] - 1;
3679 					}
3680 				}
3681 
3682 				if (multiline)
3683 					offset += line.height;
3684 				else
3685 					offset += (int) line.widths [line.text.Length];
3686 
3687 				if (line_no > lines) {
3688 					break;
3689 				}
3690 			}
3691 
3692 			if (document_x != new_width) {
3693 				document_x = new_width;
3694 				if (WidthChanged != null) {
3695 					WidthChanged(this, null);
3696 				}
3697 			}
3698 
3699 			RecalculateAlignments();
3700 
3701 			line = GetLine(lines);
3702 
3703 			if (document_y != line.Y + line.height) {
3704 				document_y = line.Y + line.height;
3705 				if (HeightChanged != null) {
3706 					HeightChanged(this, null);
3707 				}
3708 			}
3709 
3710 			// scan for links and tell us if its all
3711 			// changed, so we can update everything
3712 			if (EnableLinks)
3713 				ScanForLinks (start, end, ref changed);
3714 
3715 			UpdateCaret();
3716 			return changed;
3717 		}
3718 
Size()3719 		internal int Size() {
3720 			return lines;
3721 		}
3722 
owner_HandleCreated(object sender, EventArgs e)3723 		private void owner_HandleCreated(object sender, EventArgs e) {
3724 			RecalculateDocument(owner.CreateGraphicsInternal());
3725 			AlignCaret();
3726 		}
3727 
owner_VisibleChanged(object sender, EventArgs e)3728 		private void owner_VisibleChanged(object sender, EventArgs e) {
3729 			if (owner.Visible) {
3730 				RecalculateDocument(owner.CreateGraphicsInternal());
3731 			}
3732 		}
3733 
IsWordSeparator(char ch)3734 		internal static bool IsWordSeparator (char ch)
3735 		{
3736 			switch (ch) {
3737 			case ' ':
3738 			case '\t':
3739 			case '(':
3740 			case ')':
3741 			case '\r':
3742 			case '\n':
3743 				return true;
3744 			default:
3745 				return false;
3746 			}
3747 		}
3748 
FindWordSeparator(Line line, int pos, bool forward)3749 		internal int FindWordSeparator(Line line, int pos, bool forward) {
3750 			int len;
3751 
3752 			len = line.text.Length;
3753 
3754 			if (forward) {
3755 				for (int i = pos + 1; i < len; i++) {
3756 					if (IsWordSeparator(line.Text[i])) {
3757 						return i + 1;
3758 					}
3759 				}
3760 				return len;
3761 			} else {
3762 				for (int i = pos - 1; i > 0; i--) {
3763 					if (IsWordSeparator(line.Text[i - 1])) {
3764 						return i;
3765 					}
3766 				}
3767 				return 0;
3768 			}
3769 		}
3770 
3771 		/* Search document for text */
FindChars(char[] chars, Marker start, Marker end, out Marker result)3772 		internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3773 			Line	line;
3774 			int	line_no;
3775 			int	pos;
3776 			int	line_len;
3777 
3778 			// Search for occurence of any char in the chars array
3779 			result = new Marker();
3780 
3781 			line = start.line;
3782 			line_no = start.line.line_no;
3783 			pos = start.pos;
3784 			while (line_no <= end.line.line_no) {
3785 				line_len = line.text.Length;
3786 				while (pos < line_len) {
3787 					for (int i = 0; i < chars.Length; i++) {
3788 						if (line.text[pos] == chars[i]) {
3789 							// Special case
3790 							if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3791 								return false;
3792 							}
3793 
3794 							result.line = line;
3795 							result.pos = pos;
3796 							return true;
3797 						}
3798 					}
3799 					pos++;
3800 				}
3801 
3802 				pos = 0;
3803 				line_no++;
3804 				line = GetLine(line_no);
3805 			}
3806 
3807 			return false;
3808 		}
3809 
3810 		// This version does not build one big string for searching, instead it handles
3811 		// line-boundaries, which is faster and less memory intensive
3812 		// FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3813 		// search stuff and change it to accept and return positions instead of Markers (which would match
3814 		// RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options)3815 		internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3816 			Marker	last;
3817 			string	search_string;
3818 			Line	line;
3819 			int	line_no;
3820 			int	pos;
3821 			int	line_len;
3822 			int	current;
3823 			bool	word;
3824 			bool	word_option;
3825 			bool	ignore_case;
3826 			bool	reverse;
3827 			char	c;
3828 
3829 			result = new Marker();
3830 			word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3831 			ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3832 			reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3833 
3834 			line = start.line;
3835 			line_no = start.line.line_no;
3836 			pos = start.pos;
3837 			current = 0;
3838 
3839 			// Prep our search string, lowercasing it if we do case-independent matching
3840 			if (ignore_case) {
3841 				StringBuilder	sb;
3842 				sb = new StringBuilder(search);
3843 				for (int i = 0; i < sb.Length; i++) {
3844 					sb[i] = Char.ToLower(sb[i]);
3845 				}
3846 				search_string = sb.ToString();
3847 			} else {
3848 				search_string = search;
3849 			}
3850 
3851 			// We need to check if the character before our start position is a wordbreak
3852 			if (word_option) {
3853 				if (line_no == 1) {
3854 					if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3855 						word = true;
3856 					} else {
3857 						word = false;
3858 					}
3859 				} else {
3860 					if (pos > 0) {
3861 						if (IsWordSeparator(line.text[pos - 1])) {
3862 							word = true;
3863 						} else {
3864 							word = false;
3865 						}
3866 					} else {
3867 						// Need to check the end of the previous line
3868 						Line	prev_line;
3869 
3870 						prev_line = GetLine(line_no - 1);
3871 						if (prev_line.ending == LineEnding.Wrap) {
3872 							if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3873 								word = true;
3874 							} else {
3875 								word = false;
3876 							}
3877 						} else {
3878 							word = true;
3879 						}
3880 					}
3881 				}
3882 			} else {
3883 				word = false;
3884 			}
3885 
3886 			// To avoid duplication of this loop with reverse logic, we search
3887 			// through the document, remembering the last match and when returning
3888 			// report that last remembered match
3889 
3890 			last = new Marker();
3891 			last.height = -1;	// Abused - we use it to track change
3892 
3893 			while (line_no <= end.line.line_no) {
3894 				if (line_no != end.line.line_no) {
3895 					line_len = line.text.Length;
3896 				} else {
3897 					line_len = end.pos;
3898 				}
3899 
3900 				while (pos < line_len) {
3901 
3902 					if (word_option && (current == search_string.Length)) {
3903 						if (IsWordSeparator(line.text[pos])) {
3904 							if (!reverse) {
3905 								goto FindFound;
3906 							} else {
3907 								last = result;
3908 								current = 0;
3909 							}
3910 						} else {
3911 							current = 0;
3912 						}
3913 					}
3914 
3915 					if (ignore_case) {
3916 						c = Char.ToLower(line.text[pos]);
3917 					} else {
3918 						c = line.text[pos];
3919 					}
3920 
3921 					if (c == search_string[current]) {
3922 
3923 						if (current == 0) {
3924 							result.line = line;
3925 							result.pos = pos;
3926 						}
3927 						if (!word_option || (word_option && (word || (current > 0)))) {
3928 							current++;
3929 						}
3930 
3931 						if (!word_option && (current == search_string.Length)) {
3932 							if (!reverse) {
3933 								goto FindFound;
3934 							} else {
3935 								last = result;
3936 								current = 0;
3937 							}
3938 						}
3939 					} else {
3940 						current = 0;
3941 					}
3942 					pos++;
3943 
3944 					if (!word_option) {
3945 						continue;
3946 					}
3947 
3948 					if (IsWordSeparator(c)) {
3949 						word = true;
3950 					} else {
3951 						word = false;
3952 					}
3953 				}
3954 
3955 				if (word_option) {
3956 					// Mark that we just saw a word boundary
3957 					if (line.ending != LineEnding.Wrap || line.line_no == lines - 1) {
3958 						word = true;
3959 					}
3960 
3961 					if (current == search_string.Length) {
3962 						if (word) {
3963 							if (!reverse) {
3964 								goto FindFound;
3965 							} else {
3966 								last = result;
3967 								current = 0;
3968 							}
3969 						} else {
3970 							current = 0;
3971 						}
3972 					}
3973 				}
3974 
3975 				pos = 0;
3976 				line_no++;
3977 				line = GetLine(line_no);
3978 			}
3979 
3980 			if (reverse) {
3981 				if (last.height != -1) {
3982 					result = last;
3983 					return true;
3984 				}
3985 			}
3986 
3987 			return false;
3988 
3989 			FindFound:
3990 			if (!reverse) {
3991 //				if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3992 //					return false;
3993 //				}
3994 				return true;
3995 			}
3996 
3997 			result = last;
3998 			return true;
3999 
4000 		}
4001 
4002 		/* Marker stuff */
GetMarker(out Marker mark, bool start)4003 		internal void GetMarker(out Marker mark, bool start) {
4004 			mark = new Marker();
4005 
4006 			if (start) {
4007 				mark.line = GetLine(1);
4008 				mark.tag = mark.line.tags;
4009 				mark.pos = 0;
4010 			} else {
4011 				mark.line = GetLine(lines);
4012 				mark.tag = mark.line.tags;
4013 				while (mark.tag.Next != null) {
4014 					mark.tag = mark.tag.Next;
4015 				}
4016 				mark.pos = mark.line.text.Length;
4017 			}
4018 		}
4019 		#endregion	// Internal Methods
4020 
4021 		#region Events
4022 		internal event EventHandler CaretMoved;
4023 		internal event EventHandler WidthChanged;
4024 		internal event EventHandler HeightChanged;
4025 		internal event EventHandler LengthChanged;
4026 		internal event EventHandler UIASelectionChanged;
4027 		#endregion	// Events
4028 
4029 		#region Administrative
GetEnumerator()4030 		public IEnumerator GetEnumerator() {
4031 			// FIXME
4032 			return null;
4033 		}
4034 
Equals(object obj)4035 		public override bool Equals(object obj) {
4036 			if (obj == null) {
4037 				return false;
4038 			}
4039 
4040 			if (!(obj is Document)) {
4041 				return false;
4042 			}
4043 
4044 			if (obj == this) {
4045 				return true;
4046 			}
4047 
4048 			if (ToString().Equals(((Document)obj).ToString())) {
4049 				return true;
4050 			}
4051 
4052 			return false;
4053 		}
4054 
GetHashCode()4055 		public override int GetHashCode() {
4056 			return document_id;
4057 		}
4058 
ToString()4059 		public override string ToString() {
4060 			return "document " + this.document_id;
4061 		}
4062 		#endregion	// Administrative
4063 	}
4064 
4065 	internal class PictureTag : LineTag {
4066 
4067 		internal RTF.Picture picture;
4068 
PictureTag(Line line, int start, RTF.Picture picture)4069 		internal PictureTag (Line line, int start, RTF.Picture picture) : base (line, start)
4070 		{
4071 			this.picture = picture;
4072 		}
4073 
4074 		public override bool IsTextTag {
4075 			get { return false; }
4076 		}
4077 
SizeOfPosition(Graphics dc, int pos)4078 		public override SizeF SizeOfPosition (Graphics dc, int pos)
4079 		{
4080 			return picture.Size;
4081 		}
4082 
MaxHeight()4083 		internal override int MaxHeight ()
4084 		{
4085 			return (int) (picture.Height + 0.5F);
4086 		}
4087 
Draw(Graphics dc, Color color, float xoff, float y, int start, int end)4088 		public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end)
4089 		{
4090 			picture.DrawImage (dc, xoff + Line.widths [start], y, false);
4091 		}
4092 
Draw(Graphics dc, Color color, float xoff, float y, int start, int end, string text)4093 		public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)
4094 		{
4095 			picture.DrawImage (dc, xoff + + Line.widths [start], y, false);
4096 		}
4097 
Text()4098 		public override string Text ()
4099 		{
4100 			return "I";
4101 		}
4102 	}
4103 
4104 	internal class UndoManager {
4105 
4106 		internal enum ActionType {
4107 
4108 			Typing,
4109 
4110 			// This is basically just cut & paste
4111 			InsertString,
4112 			DeleteString,
4113 
4114 			UserActionBegin,
4115 			UserActionEnd
4116 		}
4117 
4118 		internal class Action {
4119 			internal ActionType	type;
4120 			internal int		line_no;
4121 			internal int		pos;
4122 			internal object		data;
4123 		}
4124 
4125 		#region Local Variables
4126 		private Document	document;
4127 		private Stack		undo_actions;
4128 		private Stack		redo_actions;
4129 
4130 		//private int		caret_line;
4131 		//private int		caret_pos;
4132 
4133 		// When performing an action, we lock the queue, so that the action can't be undone
4134 		private bool locked;
4135 		#endregion	// Local Variables
4136 
4137 		#region Constructors
UndoManager(Document document)4138 		internal UndoManager (Document document)
4139 		{
4140 			this.document = document;
4141 			undo_actions = new Stack (50);
4142 			redo_actions = new Stack (50);
4143 		}
4144 		#endregion	// Constructors
4145 
4146 		#region Properties
4147 		internal bool CanUndo {
4148 			get { return undo_actions.Count > 0; }
4149 		}
4150 
4151 		internal bool CanRedo {
4152 			get { return redo_actions.Count > 0; }
4153 		}
4154 
4155 		internal string UndoActionName {
4156 			get {
4157 				foreach (Action action in undo_actions) {
4158 					if (action.type == ActionType.UserActionBegin)
4159 						return (string) action.data;
4160 					if (action.type == ActionType.Typing)
4161 						return Locale.GetText ("Typing");
4162 				}
4163 				return String.Empty;
4164 			}
4165 		}
4166 
4167 		internal string RedoActionName {
4168 			get {
4169 				foreach (Action action in redo_actions) {
4170 					if (action.type == ActionType.UserActionBegin)
4171 						return (string) action.data;
4172 					if (action.type == ActionType.Typing)
4173 						return Locale.GetText ("Typing");
4174 				}
4175 				return String.Empty;
4176 			}
4177 		}
4178 		#endregion	// Properties
4179 
4180 		#region Internal Methods
Clear()4181 		internal void Clear ()
4182 		{
4183 			undo_actions.Clear();
4184 			redo_actions.Clear();
4185 		}
4186 
Undo()4187 		internal bool Undo ()
4188 		{
4189 			Action action;
4190 			bool user_action_finished = false;
4191 
4192 			if (undo_actions.Count == 0)
4193 				return false;
4194 
4195 			locked = true;
4196 			do {
4197 				Line start;
4198 				action = (Action) undo_actions.Pop ();
4199 
4200 				// Put onto redo stack
4201 				redo_actions.Push(action);
4202 
4203 				// Do the thing
4204 				switch(action.type) {
4205 
4206 				case ActionType.UserActionBegin:
4207 					user_action_finished = true;
4208 					break;
4209 
4210 				case ActionType.UserActionEnd:
4211 					// noop
4212 					break;
4213 
4214 				case ActionType.InsertString:
4215 					start = document.GetLine (action.line_no);
4216 					document.SuspendUpdate ();
4217 					document.DeleteMultiline (start, action.pos, ((string) action.data).Length + 1);
4218 					document.PositionCaret (start, action.pos);
4219 					document.SetSelectionToCaret (true);
4220 					document.ResumeUpdate (true);
4221 					break;
4222 
4223 				case ActionType.Typing:
4224 					start = document.GetLine (action.line_no);
4225 					document.SuspendUpdate ();
4226 					document.DeleteMultiline (start, action.pos, ((StringBuilder) action.data).Length);
4227 					document.PositionCaret (start, action.pos);
4228 					document.SetSelectionToCaret (true);
4229 					document.ResumeUpdate (true);
4230 
4231 					// This is an open ended operation, so only a single typing operation can be undone at once
4232 					user_action_finished = true;
4233 					break;
4234 
4235 				case ActionType.DeleteString:
4236 					start = document.GetLine (action.line_no);
4237 					document.SuspendUpdate ();
4238 					Insert (start, action.pos, (Line) action.data, true);
4239 					document.ResumeUpdate (true);
4240 					break;
4241 				}
4242 			} while (!user_action_finished && undo_actions.Count > 0);
4243 
4244 			locked = false;
4245 
4246 			return true;
4247 		}
4248 
Redo()4249 		internal bool Redo ()
4250 		{
4251 			Action action;
4252 			bool user_action_finished = false;
4253 
4254 			if (redo_actions.Count == 0)
4255 				return false;
4256 
4257 			locked = true;
4258 			do {
4259 				Line start;
4260 				int start_index;
4261 
4262 				action = (Action) redo_actions.Pop ();
4263 				undo_actions.Push (action);
4264 
4265 				switch (action.type) {
4266 
4267 				case ActionType.UserActionBegin:
4268 					//  Noop
4269 					break;
4270 
4271 				case ActionType.UserActionEnd:
4272 					user_action_finished = true;
4273 					break;
4274 
4275 				case ActionType.InsertString:
4276 					start = document.GetLine (action.line_no);
4277 					document.SuspendUpdate ();
4278 					start_index = document.LineTagToCharIndex (start, action.pos);
4279 					document.InsertString (start, action.pos, (string) action.data);
4280 					document.CharIndexToLineTag (start_index + ((string) action.data).Length,
4281 							out document.caret.line, out document.caret.tag,
4282 							out document.caret.pos);
4283 					document.UpdateCaret ();
4284 					document.SetSelectionToCaret (true);
4285 					document.ResumeUpdate (true);
4286 					break;
4287 
4288 				case ActionType.Typing:
4289 					start = document.GetLine (action.line_no);
4290 					document.SuspendUpdate ();
4291 					start_index = document.LineTagToCharIndex (start, action.pos);
4292 					document.InsertString (start, action.pos, ((StringBuilder) action.data).ToString ());
4293 					document.CharIndexToLineTag (start_index + ((StringBuilder) action.data).Length,
4294 							out document.caret.line, out document.caret.tag,
4295 							out document.caret.pos);
4296 					document.UpdateCaret ();
4297 					document.SetSelectionToCaret (true);
4298 					document.ResumeUpdate (true);
4299 
4300 					// This is an open ended operation, so only a single typing operation can be undone at once
4301 					user_action_finished = true;
4302 					break;
4303 
4304 				case ActionType.DeleteString:
4305 					start = document.GetLine (action.line_no);
4306 					document.SuspendUpdate ();
4307 					document.DeleteMultiline (start, action.pos, ((Line) action.data).text.Length);
4308 					document.PositionCaret (start, action.pos);
4309 					document.SetSelectionToCaret (true);
4310 					document.ResumeUpdate (true);
4311 
4312 					break;
4313 				}
4314 			} while (!user_action_finished && redo_actions.Count > 0);
4315 
4316 			locked = false;
4317 
4318 			return true;
4319 		}
4320 		#endregion	// Internal Methods
4321 
4322 		#region Private Methods
4323 
BeginUserAction(string name)4324 		public void BeginUserAction (string name)
4325 		{
4326 			if (locked)
4327 				return;
4328 
4329 			// Nuke the redo queue
4330 			redo_actions.Clear ();
4331 
4332 			Action ua = new Action ();
4333 			ua.type = ActionType.UserActionBegin;
4334 			ua.data = name;
4335 
4336 			undo_actions.Push (ua);
4337 		}
4338 
EndUserAction()4339 		public void EndUserAction ()
4340 		{
4341 			if (locked)
4342 				return;
4343 
4344 			Action ua = new Action ();
4345 			ua.type = ActionType.UserActionEnd;
4346 
4347 			undo_actions.Push (ua);
4348 		}
4349 
4350 		// start_pos, end_pos = 1 based
RecordDeleteString(Line start_line, int start_pos, Line end_line, int end_pos)4351 		public void RecordDeleteString (Line start_line, int start_pos, Line end_line, int end_pos)
4352 		{
4353 			if (locked)
4354 				return;
4355 
4356 			// Nuke the redo queue
4357 			redo_actions.Clear ();
4358 
4359 			Action	a = new Action ();
4360 
4361 			// We cant simply store the string, because then formatting would be lost
4362 			a.type = ActionType.DeleteString;
4363 			a.line_no = start_line.line_no;
4364 			a.pos = start_pos;
4365 			a.data = Duplicate (start_line, start_pos, end_line, end_pos);
4366 
4367 			undo_actions.Push(a);
4368 		}
4369 
RecordInsertString(Line line, int pos, string str)4370 		public void RecordInsertString (Line line, int pos, string str)
4371 		{
4372 			if (locked || str.Length == 0)
4373 				return;
4374 
4375 			// Nuke the redo queue
4376 			redo_actions.Clear ();
4377 
4378 			Action a = new Action ();
4379 
4380 			a.type = ActionType.InsertString;
4381 			a.data = str;
4382 			a.line_no = line.line_no;
4383 			a.pos = pos;
4384 
4385 			undo_actions.Push (a);
4386 		}
4387 
RecordTyping(Line line, int pos, char ch)4388 		public void RecordTyping (Line line, int pos, char ch)
4389 		{
4390 			if (locked)
4391 				return;
4392 
4393 			// Nuke the redo queue
4394 			redo_actions.Clear ();
4395 
4396 			Action a = null;
4397 
4398 			if (undo_actions.Count > 0)
4399 				a = (Action) undo_actions.Peek ();
4400 
4401 			if (a == null || a.type != ActionType.Typing) {
4402 				a = new Action ();
4403 				a.type = ActionType.Typing;
4404 				a.data = new StringBuilder ();
4405 				a.line_no = line.line_no;
4406 				a.pos = pos;
4407 
4408 				undo_actions.Push (a);
4409 			}
4410 
4411 			StringBuilder data = (StringBuilder) a.data;
4412 			data.Append (ch);
4413 		}
4414 
4415 		// start_pos = 1-based
4416 		// end_pos = 1-based
Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)4417 		public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos)
4418 		{
4419 			Line	ret;
4420 			Line	line;
4421 			Line	current;
4422 			LineTag	tag;
4423 			LineTag	current_tag;
4424 			int	start;
4425 			int	end;
4426 			int	tag_start;
4427 
4428 			line = new Line (start_line.document, start_line.ending);
4429 			ret = line;
4430 
4431 			for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4432 				current = document.GetLine(i);
4433 
4434 				if (start_line.line_no == i) {
4435 					start = start_pos;
4436 				} else {
4437 					start = 0;
4438 				}
4439 
4440 				if (end_line.line_no == i) {
4441 					end = end_pos;
4442 				} else {
4443 					end = current.text.Length;
4444 				}
4445 
4446 				if (end_pos == 0)
4447 					continue;
4448 
4449 				// Text for the tag
4450 				line.text = new StringBuilder (current.text.ToString (start, end - start));
4451 
4452 				// Copy tags from start to start+length onto new line
4453 				current_tag = current.FindTag (start + 1);
4454 				while ((current_tag != null) && (current_tag.Start <= end)) {
4455 					if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) {
4456 						// start tag is within this tag
4457 						tag_start = start;
4458 					} else {
4459 						tag_start = current_tag.Start;
4460 					}
4461 
4462 					tag = new LineTag(line, tag_start - start + 1);
4463 					tag.CopyFormattingFrom (current_tag);
4464 
4465 					current_tag = current_tag.Next;
4466 
4467 					// Add the new tag to the line
4468 					if (line.tags == null) {
4469 						line.tags = tag;
4470 					} else {
4471 						LineTag tail;
4472 						tail = line.tags;
4473 
4474 						while (tail.Next != null) {
4475 							tail = tail.Next;
4476 						}
4477 						tail.Next = tag;
4478 						tag.Previous = tail;
4479 					}
4480 				}
4481 
4482 				if ((i + 1) <= end_line.line_no) {
4483 					line.ending = current.ending;
4484 
4485 					// Chain them (we use right/left as next/previous)
4486 					line.right = new Line (start_line.document, start_line.ending);
4487 					line.right.left = line;
4488 					line = line.right;
4489 				}
4490 			}
4491 
4492 			return ret;
4493 		}
4494 
4495 		// Insert multi-line text at the given position; use formatting at insertion point for inserted text
Insert(Line line, int pos, Line insert, bool select)4496 		internal void Insert(Line line, int pos, Line insert, bool select)
4497 		{
4498 			Line	current;
4499 			LineTag	tag;
4500 			int	offset;
4501 			int	lines;
4502 			Line	first;
4503 
4504 			// Handle special case first
4505 			if (insert.right == null) {
4506 
4507 				// Single line insert
4508 				document.Split(line, pos);
4509 
4510 				if (insert.tags == null) {
4511 					return;	// Blank line
4512 				}
4513 
4514 				//Insert our tags at the end
4515 				tag = line.tags;
4516 
4517 				while (tag.Next != null) {
4518 					tag = tag.Next;
4519 				}
4520 
4521 				offset = tag.Start + tag.Length - 1;
4522 
4523 				tag.Next = insert.tags;
4524 				line.text.Insert(offset, insert.text.ToString());
4525 
4526 				// Adjust start locations
4527 				tag = tag.Next;
4528 				while (tag != null) {
4529 					tag.Start += offset;
4530 					tag.Line = line;
4531 					tag = tag.Next;
4532 				}
4533 				// Put it back together
4534 				document.Combine(line.line_no, line.line_no + 1);
4535 
4536 				if (select) {
4537 					document.SetSelectionStart (line, pos, false);
4538 					document.SetSelectionEnd (line, pos + insert.text.Length, false);
4539 				}
4540 
4541 				document.UpdateView(line, pos);
4542 				return;
4543 			}
4544 
4545 			first = line;
4546 			lines = 1;
4547 			current = insert;
4548 
4549 			while (current != null) {
4550 
4551 				if (current == insert) {
4552 					// Inserting the first line we split the line (and make space)
4553 					document.Split(line.line_no, pos);
4554 					//Insert our tags at the end of the line
4555 					tag = line.tags;
4556 
4557 
4558 					if (tag != null && tag.Length != 0) {
4559 						while (tag.Next != null) {
4560 							tag = tag.Next;
4561 						}
4562 						offset = tag.Start + tag.Length - 1;
4563 						tag.Next = current.tags;
4564 						tag.Next.Previous = tag;
4565 
4566 						tag = tag.Next;
4567 
4568 					} else {
4569 						offset = 0;
4570 						line.tags = current.tags;
4571 						line.tags.Previous = null;
4572 						tag = line.tags;
4573 					}
4574 
4575 					line.ending = current.ending;
4576 				} else {
4577 					document.Split(line.line_no, 0);
4578 					offset = 0;
4579 					line.tags = current.tags;
4580 					line.tags.Previous = null;
4581 					line.ending = current.ending;
4582 					tag = line.tags;
4583 				}
4584 
4585 				// Adjust start locations and line pointers
4586 				while (tag != null) {
4587 					tag.Start += offset - 1;
4588 					tag.Line = line;
4589 					tag = tag.Next;
4590 				}
4591 
4592 				line.text.Insert(offset, current.text.ToString());
4593 				line.Grow(line.text.Length);
4594 
4595 				line.recalc = true;
4596 				line = document.GetLine(line.line_no + 1);
4597 
4598 				// FIXME? Test undo of line-boundaries
4599 				if ((current.right == null) && (current.tags.Length != 0)) {
4600 					document.Combine(line.line_no - 1, line.line_no);
4601 				}
4602 				current = current.right;
4603 				lines++;
4604 
4605 			}
4606 
4607 			// Recalculate our document
4608 			document.UpdateView(first, lines, pos);
4609 			return;
4610 		}
4611 		#endregion	// Private Methods
4612 	}
4613 }
4614