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