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