Merge branch 'BigIntegerParse'
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TextControl.cs
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
117                         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
130                         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
143                         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
150                         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
157                         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
164                         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
170                         public override bool Equals(object obj) {
171                                    return this==(Marker)obj;
172                         }
173
174                         public override int GetHashCode() {
175                                 return base.GetHashCode ();
176                         }
177
178                         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
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
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
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
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
553                 internal void SuspendUpdate ()
554                 {
555                         update_suspended++;
556                 }
557
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
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
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
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
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
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
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
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
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
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
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>
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
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
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
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
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
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
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
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
1302                 internal void CaretLostFocus() {
1303                         if (!owner.IsHandleCreated) {
1304                                 return;
1305                         }
1306                         XplatUI.DestroyCaret(owner.Handle);
1307                 }
1308
1309                 internal void AlignCaret ()
1310                 {
1311                         AlignCaret (true);
1312                 }
1313
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
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
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
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                 
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
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
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
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
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
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
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                 
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
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
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                 
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
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
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
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                 
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
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
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
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
2208                 internal void Combine(int FirstLine, int SecondLine) {
2209                         Combine(GetLine(FirstLine), GetLine(SecondLine));
2210                 }
2211
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
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
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
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
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
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
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
2559                 internal virtual void Clear() {
2560                         lines = 0;
2561                         CharCount = 0;
2562                         document = sentinel;
2563                 }
2564
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
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
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
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
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>
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
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
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
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
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
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
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
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
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
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
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
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
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
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>
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>
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>
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
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
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>
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
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>
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
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>
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>
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>
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>
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
3719                 internal int Size() {
3720                         return lines;
3721                 }
3722
3723                 private void owner_HandleCreated(object sender, EventArgs e) {
3724                         RecalculateDocument(owner.CreateGraphicsInternal());
3725                         AlignCaret();
3726                 }
3727
3728                 private void owner_VisibleChanged(object sender, EventArgs e) {
3729                         if (owner.Visible) {
3730                                 RecalculateDocument(owner.CreateGraphicsInternal());
3731                         }
3732                 }
3733
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
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 */
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)
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 */
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
4030                 public IEnumerator GetEnumerator() {
4031                         // FIXME
4032                         return null;
4033                 }
4034
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
4055                 public override int GetHashCode() {
4056                         return document_id;
4057                 }
4058
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
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
4078                 public override SizeF SizeOfPosition (Graphics dc, int pos)
4079                 {
4080                         return picture.Size;
4081                 }
4082
4083                 internal override int MaxHeight ()
4084                 {
4085                         return (int) (picture.Height + 0.5F);
4086                 }
4087
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
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
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
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
4181                 internal void Clear ()
4182                 {
4183                         undo_actions.Clear();
4184                         redo_actions.Clear();
4185                 }
4186
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
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
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
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
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
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
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
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
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 }