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