- Switched Line, LineTag and Document classes to internal
[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 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 to support wrapping (ie have a 'newline' tag), for images, etc.
34 // - Wrap and recalculate lines
35 // - Implement CaretPgUp/PgDown
36 // - Finish selection calculations (invalidate only changed, more ways to select)
37 // - Implement C&P
38
39
40 #undef Debug
41
42 using System;
43 using System.Collections;
44 using System.Drawing;
45 using System.Drawing.Text;
46 using System.Text;
47
48 namespace System.Windows.Forms {
49         internal enum LineColor {
50                 Red     = 0,
51                 Black   = 1
52         }
53
54         internal enum CaretDirection {
55                 CharForward,    // Move a char to the right
56                 CharBack,       // Move a char to the left
57                 LineUp,         // Move a line up
58                 LineDown,       // Move a line down
59                 Home,           // Move to the beginning of the line
60                 End,            // Move to the end of the line
61                 PgUp,           // Move one page up
62                 PgDn,           // Move one page down
63                 CtrlHome,       // Move to the beginning of the document
64                 CtrlEnd,        // Move to the end of the document
65                 WordBack,       // Move to the beginning of the previous word (or beginning of line)
66                 WordForward     // Move to the beginning of the next word (or end of line)
67         }
68
69         // Being cloneable should allow for nice line and document copies...
70         internal class Line : ICloneable, IComparable {
71                 #region Local Variables
72                 // Stuff that matters for our line
73                 internal StringBuilder          text;                   // Characters for the line
74                 internal float[]                widths;                 // Width of each character; always one larger than text.Length
75                 internal int                    space;                  // Number of elements in text and widths
76                 internal int                    line_no;                // Line number
77                 internal LineTag                tags;                   // Tags describing the text
78                 internal int                    Y;                      // Baseline
79                 internal int                    height;                 // Height of the line (height of tallest tag)
80                 internal int                    ascent;                 // Ascent of the line (ascent of the tallest tag)
81                 internal HorizontalAlignment    alignment;              // Alignment of the line
82                 internal int                    align_shift;            // Pixel shift caused by the alignment
83
84                 // Stuff that's important for the tree
85                 internal Line                   parent;                 // Our parent line
86                 internal Line                   left;                   // Line with smaller line number
87                 internal Line                   right;                  // Line with higher line number
88                 internal LineColor              color;                  // We're doing a black/red tree. this is the node color
89                 internal int                    DEFAULT_TEXT_LEN;       // 
90                 internal static StringFormat    string_format;          // For calculating widths/heights
91                 internal bool                   recalc;                 // Line changed
92                 #endregion      // Local Variables
93
94                 #region Constructors
95                 internal Line() {
96                         color = LineColor.Red;
97                         left = null;
98                         right = null;
99                         parent = null;
100                         text = null;
101                         recalc = true;
102                         alignment = HorizontalAlignment.Left;
103
104                         if (string_format == null) {
105                                 string_format = new StringFormat(StringFormat.GenericTypographic);
106                                 string_format.Trimming = StringTrimming.None;
107                                 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
108                         }
109                 }
110
111                 internal Line(int LineNo, string Text, Font font, Brush color) : this() {
112                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
113
114                         text = new StringBuilder(Text, space);
115                         line_no = LineNo;
116
117                         widths = new float[space + 1];
118                         tags = new LineTag(this, 1, text.Length);
119                         tags.font = font;
120                         tags.color = color;
121                 }
122
123                 internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) : this() {
124                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
125
126                         text = new StringBuilder(Text, space);
127                         line_no = LineNo;
128                         alignment = align;
129
130                         widths = new float[space + 1];
131                         tags = new LineTag(this, 1, text.Length);
132                         tags.font = font;
133                         tags.color = color;
134                 }
135
136                 internal Line(int LineNo, string Text, LineTag tag) : this() {
137                         space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
138
139                         text = new StringBuilder(Text, space);
140                         line_no = LineNo;
141
142                         widths = new float[space + 1];
143                         tags = tag;
144                 }
145
146                 #endregion      // Constructors
147
148                 #region Internal Properties
149                 internal int Height {
150                         get {
151                                 return height;
152                         }
153
154                         set {
155                                 height = value;
156                         }
157                 }
158
159                 internal int LineNo {
160                         get {
161                                 return line_no;
162                         }
163
164                         set {
165                                 line_no = value;
166                         }
167                 }
168
169                 internal string Text {
170                         get {
171                                 return text.ToString();
172                         }
173
174                         set {
175                                 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
176                         }
177                 }
178
179                 internal HorizontalAlignment Alignment {
180                         get {
181                                 return alignment;
182                         }
183
184                         set {
185                                 if (alignment != value) {
186                                         alignment = value;
187                                         recalc = true;
188                                 }
189                         }
190                 }
191 #if no
192                 internal StringBuilder Text {
193                         get {
194                                 return text;
195                         }
196
197                         set {
198                                 text = value;
199                         }
200                 }
201 #endif
202                 #endregion      // Internal Properties
203
204                 #region Internal Methods
205                 // Make sure we always have enoughs space in text and widths
206                 internal void Grow(int minimum) {
207                         int     length;
208                         float[] new_widths;
209
210                         length = text.Length;
211
212                         if ((length + minimum) > space) {
213                                 // We need to grow; double the size
214
215                                 if ((length + minimum) > (space * 2)) {
216                                         new_widths = new float[length + minimum * 2 + 1];
217                                         space = length + minimum * 2;
218                                 } else {                                
219                                         new_widths = new float[space * 2 + 1];
220                                         space *= 2;
221                                 }
222                                 widths.CopyTo(new_widths, 0);
223
224                                 widths = new_widths;
225                         }
226                 }
227
228                 internal void Streamline() {
229                         LineTag current;
230                         LineTag next;
231
232                         current = this.tags;
233                         next = current.next;
234
235                         // Catch what the loop below wont; eliminate 0 length 
236                         // tags, but only if there are other tags after us
237                         while ((current.length == 0) && (next != null)) {
238                                 tags = next;
239                                 current = next;
240                                 next = current.next;
241                         }
242                         
243                         if (next == null) {
244                                 return;
245                         }
246
247                         while (next != null) {
248                                 // Take out 0 length tags
249                                 if (next.length == 0) {
250                                         current.next = next.next;
251                                         if (current.next != null) {
252                                                 current.next.previous = current;
253                                         }
254                                         next = current.next;
255                                         continue;
256                                 }
257
258                                 if (current.Combine(next)) {
259                                         next = current.next;
260                                         continue;
261                                 }
262
263                                 current = current.next;
264                                 next = current.next;
265                         }
266                 }
267
268                 // Find the tag on a line based on the character position
269                 internal LineTag FindTag(int pos) {
270                         LineTag tag;
271
272                         if (pos == 0) {
273                                 return tags;
274                         }
275
276                         tag = this.tags;
277
278                         if (pos > text.Length) {
279                                 pos = text.Length;
280                         }
281
282                         while (tag != null) {
283                                 if ((tag.start <= pos) && (pos < (tag.start + tag.length))) {
284                                         return tag;
285                                 }
286                                 tag = tag.next;
287                         }
288                         return null;
289                 }
290
291
292                 //
293                 // Go through all tags on a line and recalculate all size-related values
294                 // returns true if lineheight changed
295                 //
296                 internal bool RecalculateLine(Graphics g) {
297                         LineTag tag;
298                         int     pos;
299                         int     len;
300                         SizeF   size;
301                         float   w;
302                         int     prev_height;
303
304                         pos = 0;
305                         len = this.text.Length;
306                         tag = this.tags;
307                         prev_height = this.height;      // For drawing optimization calculations
308                         this.height = 0;                // Reset line height
309                         this.ascent = 0;                // Reset the ascent for the line
310                         tag.shift = 0;
311                         tag.width = 0;
312                         widths[0] = 0;
313                         this.recalc = false;
314
315                         while (pos < len) {
316                                 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
317
318                                 w = size.Width;
319
320                                 tag.width += w;
321
322                                 pos++;
323
324                                 widths[pos] = widths[pos-1] + w;
325
326                                 if (pos == (tag.start-1 + tag.length)) {
327                                         // We just found the end of our current tag
328                                         tag.height = (int)tag.font.Height;
329
330                                         // Check if we're the tallest on the line (so far)
331                                         if (tag.height > this.height) {
332                                                 this.height = tag.height;               // Yep; make sure the line knows
333                                         }
334
335                                         if (tag.ascent == 0) {
336                                                 int     descent;
337
338                                                 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
339                                         }
340
341                                         if (tag.ascent > this.ascent) {
342                                                 LineTag         t;
343
344                                                 // We have a tag that has a taller ascent than the line;
345
346                                                 t = tags;
347                                                 while (t != tag) {
348                                                         t.shift = tag.ascent - t.ascent;
349                                                         t = t.next;
350                                                 }
351
352                                                 // Save on our line
353                                                 this.ascent = tag.ascent;
354                                         } else {
355                                                 tag.shift = this.ascent - tag.ascent;
356                                         }
357
358                                         // Update our horizontal starting pixel position
359                                         if (tag.previous == null) {
360                                                 tag.X = 0;
361                                         } else {
362                                                 tag.X = tag.previous.X + (int)tag.previous.width;
363                                         }
364
365                                         tag = tag.next;
366                                         if (tag != null) {
367                                                 tag.width = 0;
368                                                 tag.shift = 0;
369                                         }
370                                 }
371                         }
372
373                         if (this.height == 0) {
374                                 this.height = tags.font.Height;
375                                 tag.height = this.height;
376                         }
377
378                         if (prev_height != this.height) {
379                                 return true;
380                         }
381                         return false;
382                 }
383                 #endregion      // Internal Methods
384
385                 #region Administrative
386                 public int CompareTo(object obj) {
387                         if (obj == null) {
388                                 return 1;
389                         }
390
391                         if (! (obj is Line)) {
392                                 throw new ArgumentException("Object is not of type Line", "obj");
393                         }
394
395                         if (line_no < ((Line)obj).line_no) {
396                                 return -1;
397                         } else if (line_no > ((Line)obj).line_no) {
398                                 return 1;
399                         } else {
400                                 return 0;
401                         }
402                 }
403
404                 public object Clone() {
405                         Line    clone;
406
407                         clone = new Line();
408
409                         clone.text = text;
410
411                         if (left != null) {
412                                 clone.left = (Line)left.Clone();
413                         }
414
415                         if (left != null) {
416                                 clone.left = (Line)left.Clone();
417                         }
418
419                         return clone;
420                 }
421
422                 internal object CloneLine() {
423                         Line    clone;
424
425                         clone = new Line();
426
427                         clone.text = text;
428
429                         return clone;
430                 }
431
432                 public override bool Equals(object obj) {
433                         if (obj == null) {
434                                 return false;
435                         }
436
437                         if (!(obj is Line)) {
438                                 return false;
439                         }
440
441                         if (obj == this) {
442                                 return true;
443                         }
444
445                         if (line_no == ((Line)obj).line_no) {
446                                 return true;
447                         }
448
449                         return false;
450                 }
451
452                 public override int GetHashCode() {\r
453                         return base.GetHashCode ();\r
454                 }\r
455
456                 public override string ToString() {
457                         return "Line " + line_no;
458                 }
459
460                 #endregion      // Administrative
461         }
462
463         internal class Document : ICloneable, IEnumerable {
464                 #region Structures
465                 internal struct Marker {
466                         internal Line           line;
467                         internal LineTag        tag;
468                         internal int            pos;
469                         internal int            height;
470                 }
471                 #endregion Structures
472
473                 #region Local Variables
474                 private Line            document;
475                 private int             lines;
476                 private static Line     sentinel;
477                 private Line            last_found;
478                 private int             document_id;
479                 private Random          random = new Random();
480
481                 internal bool           multiline;
482                 internal bool           wrap;
483
484                 internal Marker         caret;
485                 internal Marker         selection_start;
486                 internal Marker         selection_end;
487                 internal bool           selection_visible;
488
489                 internal int            viewport_x;
490                 internal int            viewport_y;             // The visible area of the document
491
492                 internal int            document_x;             // Width of the document
493                 internal int            document_y;             // Height of the document
494
495                 internal Control        owner;                  // Who's owning us?
496                 #endregion      // Local Variables
497
498                 #region Constructors
499                 internal Document(Control owner) {
500                         lines = 0;
501
502                         this.owner = owner;
503
504                         multiline = true;
505
506                         // Tree related stuff
507                         sentinel = new Line();
508                         sentinel.color = LineColor.Black;
509
510                         document = sentinel;
511                         last_found = sentinel;
512
513                         // We always have a blank line
514                         Add(1, "", owner.Font, new SolidBrush(owner.ForeColor));
515                         this.RecalculateDocument(owner.CreateGraphics());
516                         PositionCaret(0, 0);
517                         lines=1;
518
519                         selection_visible = false;
520                         selection_start.line = this.document;
521                         selection_start.pos = 0;
522                         selection_end.line = this.document;
523                         selection_end.pos = 0;
524
525
526
527                         // Default selection is empty
528
529                         document_id = random.Next();
530                 }
531                 #endregion
532
533                 #region Internal Properties
534                 internal Line Root {
535                         get {
536                                 return document;
537                         }
538
539                         set {
540                                 document = value;
541                         }
542                 }
543
544                 internal int Lines {
545                         get {
546                                 return lines;
547                         }
548                 }
549
550                 internal Line CaretLine {
551                         get {
552                                 return caret.line;
553                         }
554                 }
555
556                 internal int CaretPosition {
557                         get {
558                                 return caret.pos;
559                         }
560                 }
561
562                 internal LineTag CaretTag {
563                         get {
564                                 return caret.tag;
565                         }
566                 }
567
568                 internal int ViewPortX {
569                         get {
570                                 return viewport_x;
571                         }
572
573                         set {
574                                 viewport_x = value;
575                         }
576                 }
577
578                 internal int ViewPortY {
579                         get {
580                                 return viewport_y;
581                         }
582
583                         set {
584                                 viewport_y = value;
585                         }
586                 }
587
588                 internal int Width {
589                         get {
590                                 return this.document_x;
591                         }
592                 }
593
594                 internal int Height {
595                         get {
596                                 return this.document_y;
597                         }
598                 }
599
600                 #endregion      // Internal Properties
601
602                 #region Private Methods
603                 // For debugging
604                 internal void DumpTree(Line line, bool with_tags) {
605                         Console.Write("Line {0}, Y: {1} Text {2}", line.line_no, line.Y, line.text != null ? line.text.ToString() : "undefined");
606
607                         if (line.left == sentinel) {
608                                 Console.Write(", left = sentinel");
609                         } else if (line.left == null) {
610                                 Console.Write(", left = NULL");
611                         }
612
613                         if (line.right == sentinel) {
614                                 Console.Write(", right = sentinel");
615                         } else if (line.right == null) {
616                                 Console.Write(", right = NULL");
617                         }
618
619                         Console.WriteLine("");
620
621                         if (with_tags) {
622                                 LineTag tag;
623                                 int     count;
624
625                                 tag = line.tags;
626                                 count = 1;
627                                 Console.Write("   Tags: ");
628                                 while (tag != null) {
629                                         Console.Write("{0} <{1}>-<{2}> ", count++, tag.start, tag.length);
630                                         if (tag.line != line) {
631                                                 Console.Write("BAD line link");
632                                                 throw new Exception("Bad line link in tree");
633                                         }
634                                         tag = tag.next;
635                                         if (tag != null) {
636                                                 Console.Write(", ");
637                                         }
638                                 }
639                                 Console.WriteLine("");
640                         }
641                         if (line.left != null) {
642                                 if (line.left != sentinel) {
643                                         DumpTree(line.left, with_tags);
644                                 }
645                         } else {
646                                 if (line != sentinel) {
647                                         throw new Exception("Left should not be NULL");
648                                 }
649                         }
650
651                         if (line.right != null) {
652                                 if (line.right != sentinel) {
653                                         DumpTree(line.right, with_tags);
654                                 }
655                         } else {
656                                 if (line != sentinel) {
657                                         throw new Exception("Right should not be NULL");
658                                 }
659                         }
660                 }
661
662                 private void DecrementLines(int line_no) {
663                         int     current;
664
665                         current = line_no;
666                         while (current <= lines) {
667                                 GetLine(current).line_no--;
668                                 current++;
669                         }
670                         return;
671                 }
672
673                 private void IncrementLines(int line_no) {
674                         int     current;
675
676                         current = this.lines;
677                         while (current >= line_no) {
678                                 GetLine(current).line_no++;
679                                 current--;
680                         }
681                         return;
682                 }
683
684                 private void RebalanceAfterAdd(Line line1) {
685                         Line    line2;
686
687                         while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
688                                 if (line1.parent == line1.parent.parent.left) {
689                                         line2 = line1.parent.parent.right;
690
691                                         if ((line2 != null) && (line2.color == LineColor.Red)) {
692                                                 line1.parent.color = LineColor.Black;
693                                                 line2.color = LineColor.Black;
694                                                 line1.parent.parent.color = LineColor.Red;
695                                                 line1 = line1.parent.parent;
696                                         } else {
697                                                 if (line1 == line1.parent.right) {
698                                                         line1 = line1.parent;
699                                                         RotateLeft(line1);
700                                                 }
701
702                                                 line1.parent.color = LineColor.Black;
703                                                 line1.parent.parent.color = LineColor.Red;
704
705                                                 RotateRight(line1.parent.parent);
706                                         }
707                                 } else {
708                                         line2 = line1.parent.parent.left;
709
710                                         if ((line2 != null) && (line2.color == LineColor.Red)) {
711                                                 line1.parent.color = LineColor.Black;
712                                                 line2.color = LineColor.Black;
713                                                 line1.parent.parent.color = LineColor.Red;
714                                                 line1 = line1.parent.parent;
715                                         } else {
716                                                 if (line1 == line1.parent.left) {
717                                                         line1 = line1.parent;
718                                                         RotateRight(line1);
719                                                 }
720
721                                                 line1.parent.color = LineColor.Black;
722                                                 line1.parent.parent.color = LineColor.Red;
723                                                 RotateLeft(line1.parent.parent);
724                                         }
725                                 }
726                         }
727                         document.color = LineColor.Black;
728                 }
729
730                 private void RebalanceAfterDelete(Line line1) {
731                         Line line2;
732
733                         while ((line1 != document) && (line1.color == LineColor.Black)) {
734                                 if (line1 == line1.parent.left) {
735                                         line2 = line1.parent.right;
736                                         if (line2.color == LineColor.Red) { 
737                                                 line2.color = LineColor.Black;
738                                                 line1.parent.color = LineColor.Red;
739                                                 RotateLeft(line1.parent);
740                                                 line2 = line1.parent.right;
741                                         }
742                                         if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) { 
743                                                 line2.color = LineColor.Red;
744                                                 line1 = line1.parent;
745                                         } else {
746                                                 if (line2.right.color == LineColor.Black) {
747                                                         line2.left.color = LineColor.Black;
748                                                         line2.color = LineColor.Red;
749                                                         RotateRight(line2);
750                                                         line2 = line1.parent.right;
751                                                 }
752                                                 line2.color = line1.parent.color;
753                                                 line1.parent.color = LineColor.Black;
754                                                 line2.right.color = LineColor.Black;
755                                                 RotateLeft(line1.parent);
756                                                 line1 = document;
757                                         }
758                                 } else { 
759                                         line2 = line1.parent.left;
760                                         if (line2.color == LineColor.Red) {
761                                                 line2.color = LineColor.Black;
762                                                 line1.parent.color = LineColor.Red;
763                                                 RotateRight(line1.parent);
764                                                 line2 = line1.parent.left;
765                                         }
766                                         if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
767                                                 line2.color = LineColor.Red;
768                                                 line1 = line1.parent;
769                                         } else {
770                                                 if (line2.left.color == LineColor.Black) {
771                                                         line2.right.color = LineColor.Black;
772                                                         line2.color = LineColor.Red;
773                                                         RotateLeft(line2);
774                                                         line2 = line1.parent.left;
775                                                 }
776                                                 line2.color = line1.parent.color;
777                                                 line1.parent.color = LineColor.Black;
778                                                 line2.left.color = LineColor.Black;
779                                                 RotateRight(line1.parent);
780                                                 line1 = document;
781                                         }
782                                 }
783                         }
784                         line1.color = LineColor.Black;
785                 }
786
787                 private void RotateLeft(Line line1) {
788                         Line    line2 = line1.right;
789
790                         line1.right = line2.left;
791
792                         if (line2.left != sentinel) {
793                                 line2.left.parent = line1;
794                         }
795
796                         if (line2 != sentinel) {
797                                 line2.parent = line1.parent;
798                         }
799
800                         if (line1.parent != null) {
801                                 if (line1 == line1.parent.left) {
802                                         line1.parent.left = line2;
803                                 } else {
804                                         line1.parent.right = line2;
805                                 }
806                         } else {
807                                 document = line2;
808                         }
809
810                         line2.left = line1;
811                         if (line1 != sentinel) {
812                                 line1.parent = line2;
813                         }
814                 }
815
816                 private void RotateRight(Line line1) {
817                         Line line2 = line1.left;
818
819                         line1.left = line2.right;
820
821                         if (line2.right != sentinel) {
822                                 line2.right.parent = line1;
823                         }
824
825                         if (line2 != sentinel) {
826                                 line2.parent = line1.parent;
827                         }
828
829                         if (line1.parent != null) {
830                                 if (line1 == line1.parent.right) {
831                                         line1.parent.right = line2;
832                                 } else {
833                                         line1.parent.left = line2;
834                                 }
835                         } else {
836                                 document = line2;
837                         }
838
839                         line2.right = line1;
840                         if (line1 != sentinel) {
841                                 line1.parent = line2;
842                         }
843                 }        
844
845
846                 internal void UpdateView(Line line, int pos) {
847                         if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no, true)) {
848                                 // Lineheight changed, invalidate the rest of the document
849                                 if ((line.Y - viewport_y) >=0 ) {
850                                         // We formatted something that's in view, only draw parts of the screen
851                                         owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
852                                 } else {
853                                         // The tag was above the visible area, draw everything
854                                         owner.Invalidate();
855                                 }
856                         } else {
857                                 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, (int)owner.Width, line.height));
858                         }
859                 }
860
861
862                 // Update display from line, down line_count lines; pos is unused, but required for the signature
863                 internal void UpdateView(Line line, int line_count, int pos) {
864                         if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no + line_count - 1, true)) {
865                                 // Lineheight changed, invalidate the rest of the document
866                                 if ((line.Y - viewport_y) >=0 ) {
867                                         // We formatted something that's in view, only draw parts of the screen
868                                         owner.Invalidate(new Rectangle(0, line.Y - viewport_y, owner.Width, owner.Height - line.Y - viewport_y));
869                                 } else {
870                                         // The tag was above the visible area, draw everything
871                                         owner.Invalidate();
872                                 }
873                         } else {
874                                 Line    end_line;
875
876                                 end_line = GetLine(line.line_no + line_count -1);
877                                 if (end_line == null) {
878                                         end_line = line;
879                                 }
880
881                                 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
882                         }
883                 }
884                 #endregion      // Private Methods
885
886                 #region Internal Methods
887                 // Clear the document and reset state
888                 internal void Empty() {
889
890                         document = sentinel;
891                         last_found = sentinel;
892                         lines = 0;
893
894                         // We always have a blank line
895                         Add(1, "", owner.Font, new SolidBrush(owner.ForeColor));
896                         this.RecalculateDocument(owner.CreateGraphics());
897                         PositionCaret(0, 0);
898
899                         selection_visible = false;
900                         selection_start.line = this.document;
901                         selection_start.pos = 0;
902                         selection_end.line = this.document;
903                         selection_end.pos = 0;
904
905                         viewport_x = 0;
906                         viewport_y = 0;
907
908                         document_x = 0;
909                         document_y = 0;
910                 }
911
912                 internal void PositionCaret(Line line, int pos) {
913                         caret.tag = line.FindTag(pos);
914                         caret.line = line;
915                         caret.pos = pos;
916                         caret.height = caret.tag.height;
917
918                         XplatUI.DestroyCaret(owner.Handle);
919                         XplatUI.CreateCaret(owner.Handle, 2, caret.height);
920                         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);
921                 }
922
923                 internal void PositionCaret(int x, int y) {
924                         caret.tag = FindCursor(x + viewport_x, y + viewport_y, out caret.pos);
925                         caret.line = caret.tag.line;
926                         caret.height = caret.tag.height;
927
928                         XplatUI.DestroyCaret(owner.Handle);
929                         XplatUI.CreateCaret(owner.Handle, 2, caret.height);
930                         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);
931                 }
932
933                 internal void CaretHasFocus() {
934                         if (caret.tag != null) {
935                                 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
936                                 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);
937                                 XplatUI.CaretVisible(owner.Handle, true);
938                         }
939                 }
940
941                 internal void CaretLostFocus() {
942                         XplatUI.DestroyCaret(owner.Handle);
943                 }
944
945                 internal void AlignCaret() {
946                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
947                         caret.height = caret.tag.height;
948
949                         XplatUI.CreateCaret(owner.Handle, 2, caret.height);
950                         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);
951                         XplatUI.CaretVisible(owner.Handle, true);
952                 }
953
954                 internal void UpdateCaret() {
955                         if (caret.tag.height != caret.height) {
956                                 caret.height = caret.tag.height;
957                                 XplatUI.CreateCaret(owner.Handle, 2, caret.height);
958                         }
959                         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);
960                         XplatUI.CaretVisible(owner.Handle, true);
961                 }
962
963                 internal void DisplayCaret() {
964                         XplatUI.CaretVisible(owner.Handle, true);
965                 }
966
967                 internal void HideCaret() {
968                         XplatUI.CaretVisible(owner.Handle, false);
969                 }
970
971                 internal void MoveCaret(CaretDirection direction) {
972                         switch(direction) {
973                                 case CaretDirection.CharForward: {
974                                         caret.pos++;
975                                         if (caret.pos > caret.line.text.Length) {
976                                                 if (multiline) {
977                                                         // Go into next line
978                                                         if (caret.line.line_no < this.lines) {
979                                                                 caret.line = GetLine(caret.line.line_no+1);
980                                                                 caret.pos = 0;
981                                                                 caret.tag = caret.line.tags;
982                                                         } else {
983                                                                 caret.pos--;
984                                                         }
985                                                 } else {
986                                                         // Single line; we stay where we are
987                                                         caret.pos--;
988                                                 }
989                                         } else {
990                                                 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
991                                                         caret.tag = caret.tag.next;
992                                                 }
993                                         }
994                                         UpdateCaret();
995                                         return;
996                                 }
997
998                                 case CaretDirection.CharBack: {
999                                         if (caret.pos > 0) {
1000                                                 // caret.pos--; // folded into the if below
1001                                                 if (--caret.pos > 0) {
1002                                                         if (caret.tag.start > caret.pos) {
1003                                                                 caret.tag = caret.tag.previous;
1004                                                         }
1005                                                 }
1006                                         } else {
1007                                                 if (caret.line.line_no > 1) {
1008                                                         caret.line = GetLine(caret.line.line_no - 1);
1009                                                         caret.pos = caret.line.text.Length;
1010                                                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
1011                                                 }
1012                                         }
1013                                         UpdateCaret();
1014                                         return;
1015                                 }
1016
1017                                 case CaretDirection.WordForward: {
1018                                         int len;
1019
1020                                         len = caret.line.text.Length;
1021                                         if (caret.pos < len) {
1022                                                 while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
1023                                                         caret.pos++;
1024                                                 }
1025                                                 if (caret.pos < len) {
1026                                                         // Skip any whitespace
1027                                                         while ((caret.pos < len) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
1028                                                                 caret.pos++;
1029                                                         }
1030                                                 }
1031                                         } else {
1032                                                 if (caret.line.line_no < this.lines) {
1033                                                         caret.line = GetLine(caret.line.line_no+1);
1034                                                         caret.pos = 0;
1035                                                         caret.tag = caret.line.tags;
1036                                                 }
1037                                         }
1038                                         UpdateCaret();
1039                                         return;
1040                                 }
1041
1042                                 case CaretDirection.WordBack: {
1043                                         if (caret.pos > 0) {
1044                                                 caret.pos--;
1045
1046                                                 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
1047                                                         caret.pos--;
1048                                                 }
1049
1050                                                 while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) != " ")) {
1051                                                         caret.pos--;
1052                                                 }
1053
1054                                                 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1055                                                         if (caret.pos != 0) {
1056                                                                 caret.pos++;
1057                                                         } else {
1058                                                                 caret.line = GetLine(caret.line.line_no - 1);
1059                                                                 caret.pos = caret.line.text.Length;
1060                                                                 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1061                                                         }
1062                                                 }
1063                                         } else {
1064                                                 if (caret.line.line_no > 1) {
1065                                                         caret.line = GetLine(caret.line.line_no - 1);
1066                                                         caret.pos = caret.line.text.Length;
1067                                                         caret.tag = LineTag.FindTag(caret.line, caret.pos);
1068                                                 }
1069                                         }
1070                                         UpdateCaret();
1071                                         return;
1072                                 }
1073
1074                                 case CaretDirection.LineUp: {
1075                                         if (caret.line.line_no > 1) {
1076                                                 int     pixel;
1077
1078                                                 pixel = (int)caret.line.widths[caret.pos];
1079                                                 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1080                                                 XplatUI.CaretVisible(owner.Handle, true);
1081                                         }
1082                                         return;
1083                                 }
1084
1085                                 case CaretDirection.LineDown: {
1086                                         if (caret.line.line_no < lines) {
1087                                                 int     pixel;
1088
1089                                                 pixel = (int)caret.line.widths[caret.pos];
1090                                                 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1091                                                 XplatUI.CaretVisible(owner.Handle, true);
1092                                         }
1093                                         return;
1094                                 }
1095
1096                                 case CaretDirection.Home: {
1097                                         if (caret.pos > 0) {
1098                                                 caret.pos = 0;
1099                                                 caret.tag = caret.line.tags;
1100                                                 UpdateCaret();
1101                                         }
1102                                         return;
1103                                 }
1104
1105                                 case CaretDirection.End: {
1106                                         if (caret.pos < caret.line.text.Length) {
1107                                                 caret.pos = caret.line.text.Length;
1108                                                 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1109                                                 UpdateCaret();
1110                                         }
1111                                         return;
1112                                 }
1113
1114                                 case CaretDirection.PgUp: {
1115                                         return;
1116                                 }
1117
1118                                 case CaretDirection.PgDn: {
1119                                         return;
1120                                 }
1121
1122                                 case CaretDirection.CtrlHome: {
1123                                         caret.line = GetLine(1);
1124                                         caret.pos = 0;
1125                                         caret.tag = caret.line.tags;
1126
1127                                         UpdateCaret();
1128                                         return;
1129                                 }
1130
1131                                 case CaretDirection.CtrlEnd: {
1132                                         caret.line = GetLine(lines);
1133                                         caret.pos = 0;
1134                                         caret.tag = caret.line.tags;
1135
1136                                         UpdateCaret();
1137                                         return;
1138                                 }
1139                         }
1140                 }
1141
1142                 // Draw the document
1143                 internal void Draw(Graphics g, Rectangle clip) {
1144                         Line    line;           // Current line being drawn
1145                         LineTag tag;            // Current tag being drawn
1146                         int     start;          // First line to draw
1147                         int     end;            // Last line to draw
1148                         string  s;              // String representing the current line
1149                         int     line_no;        //
1150
1151                         // First, figure out from what line to what line we need to draw
1152                         start = GetLineByPixel(clip.Top - viewport_y, false).line_no;
1153                         end = GetLineByPixel(clip.Bottom - viewport_y, false).line_no;
1154
1155                         // Now draw our elements; try to only draw those that are visible
1156                         line_no = start;
1157
1158                         #if Debug
1159                                 DateTime        n = DateTime.Now;
1160                                 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1161                         #endif
1162
1163                         while (line_no <= end) {
1164                                 line = GetLine(line_no);
1165                                 tag = line.tags;
1166                                 s = line.text.ToString();
1167                                 while (tag != null) {
1168                                         if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1169                                                 // Check for selection
1170                                                 if ((!selection_visible) || (line_no < selection_start.line.line_no) || (line_no > selection_end.line.line_no)) {
1171                                                         // regular drawing
1172                                                         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);
1173                                                 } else {
1174                                                         // we might have to draw our selection
1175                                                         if ((line_no != selection_start.line.line_no) && (line_no != selection_end.line.line_no)) {
1176                                                                 g.FillRectangle(tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
1177                                                                 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), tag.X + line.align_shift - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1178                                                         } else {
1179                                                                 bool    highlight;
1180                                                                 bool    partial;
1181
1182                                                                 highlight = false;
1183                                                                 partial = false;
1184
1185                                                                 // Check the partial drawings first
1186                                                                 if ((selection_start.tag == tag) && (selection_end.tag == tag)) {
1187                                                                         partial = true;
1188
1189                                                                         // First, the regular part
1190                                                                         g.DrawString(s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1191
1192                                                                         // Now the highlight
1193                                                                         g.FillRectangle(tag.color, line.widths[selection_start.pos] + line.align_shift, line.Y + tag.shift - viewport_y, line.widths[selection_end.pos] - line.widths[selection_start.pos], tag.height);
1194                                                                         g.DrawString(s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), line.widths[selection_start.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1195
1196                                                                         // And back to the regular
1197                                                                         g.DrawString(s.Substring(selection_end.pos, tag.length - selection_end.pos), tag.font, tag.color, line.widths[selection_end.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1198                                                                 } else if (selection_start.tag == tag) {
1199                                                                         partial = true;
1200
1201                                                                         // The highlighted part
1202                                                                         g.FillRectangle(tag.color, line.widths[selection_start.pos] + line.align_shift, line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1] - line.widths[selection_start.pos], tag.height);
1203                                                                         g.DrawString(s.Substring(selection_start.pos, tag.length - selection_start.pos), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), line.widths[selection_start.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1204
1205                                                                         // The regular part
1206                                                                         g.DrawString(s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1207                                                                 } else if (selection_end.tag == tag) {
1208                                                                         partial = true;
1209
1210                                                                         // The highlighted part
1211                                                                         g.FillRectangle(tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[selection_end.pos], tag.height);
1212                                                                         g.DrawString(s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), tag.X + line.align_shift - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1213
1214                                                                         // The regular part
1215                                                                         g.DrawString(s.Substring(selection_end.pos, tag.length - selection_end.pos), tag.font, tag.color, line.widths[selection_end.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1216                                                                 } else {
1217                                                                         // no partially selected tags here, simple checks...
1218                                                                         if (selection_start.line == line) {
1219                                                                                 if ((tag.start + tag.length - 1) > selection_start.pos) {
1220                                                                                         highlight = true;
1221                                                                                 }
1222                                                                         }
1223                                                                         if (selection_end.line == line) {
1224                                                                                 if ((tag.start + tag.length - 1) < selection_start.pos) {
1225                                                                                         highlight = true;
1226                                                                                 }
1227                                                                         }
1228                                                                 }
1229
1230                                                                 if (!partial) {
1231                                                                         if (highlight) {
1232                                                                                 g.FillRectangle(tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift  - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
1233                                                                                 g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.BackColor), tag.X + line.align_shift - viewport_x, line.Y + tag.shift  - viewport_y, StringFormat.GenericTypographic);
1234                                                                         } else {
1235                                                                                 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);
1236                                                                         }
1237                                                                 }
1238                                                         }
1239
1240                                                 }
1241                                         }
1242
1243                                         tag = tag.next;
1244                                 }
1245
1246                                 line_no++;
1247                         }
1248                         #if Debug
1249                                 n = DateTime.Now;
1250                                 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
1251                         #endif
1252
1253                 }
1254
1255
1256                 // Inserts a character at the given position
1257                 internal void InsertString(Line line, int pos, string s) {
1258                         InsertString(line.FindTag(pos), pos, s);
1259                 }
1260
1261                 // Inserts a string at the given position
1262                 internal void InsertString(LineTag tag, int pos, string s) {
1263                         Line    line;
1264                         int     len;
1265
1266                         len = s.Length;
1267
1268                         line = tag.line;
1269                         line.text.Insert(pos, s);
1270                         tag.length += len;
1271
1272                         tag = tag.next;
1273                         while (tag != null) {
1274                                 tag.start += len;
1275                                 tag = tag.next;
1276                         }
1277                         line.Grow(len);
1278                         line.recalc = true;
1279
1280                         UpdateView(line, pos);
1281                 }
1282
1283                 // Inserts a string at the caret position
1284                 internal void InsertStringAtCaret(string s, bool move_caret) {
1285                         LineTag tag;
1286                         int     len;
1287
1288                         len = s.Length;
1289
1290                         caret.line.text.Insert(caret.pos, s);
1291                         caret.tag.length += len;
1292                         
1293                         if (caret.tag.next != null) {
1294                                 tag = caret.tag.next;
1295                                 while (tag != null) {
1296                                         tag.start += len;
1297                                         tag = tag.next;
1298                                 }
1299                         }
1300                         caret.line.Grow(len);
1301                         caret.line.recalc = true;
1302
1303                         UpdateView(caret.line, caret.pos);
1304                         if (move_caret) {
1305                                 caret.pos += len;
1306                                 UpdateCaret();
1307                         }
1308                 }
1309
1310
1311
1312                 // Inserts a character at the given position
1313                 internal void InsertChar(Line line, int pos, char ch) {
1314                         InsertChar(line.FindTag(pos), pos, ch);
1315                 }
1316
1317                 // Inserts a character at the given position
1318                 internal void InsertChar(LineTag tag, int pos, char ch) {
1319                         Line    line;
1320
1321                         line = tag.line;
1322                         line.text.Insert(pos, ch);
1323                         tag.length++;
1324
1325                         tag = tag.next;
1326                         while (tag != null) {
1327                                 tag.start++;
1328                                 tag = tag.next;
1329                         }
1330                         line.Grow(1);
1331                         line.recalc = true;
1332
1333                         UpdateView(line, pos);
1334                 }
1335
1336                 // Inserts a character at the current caret position
1337                 internal void InsertCharAtCaret(char ch, bool move_caret) {
1338                         LineTag tag;
1339
1340                         caret.line.text.Insert(caret.pos, ch);
1341                         caret.tag.length++;
1342                         
1343                         if (caret.tag.next != null) {
1344                                 tag = caret.tag.next;
1345                                 while (tag != null) {
1346                                         tag.start++;
1347                                         tag = tag.next;
1348                                 }
1349                         }
1350                         caret.line.Grow(1);
1351                         caret.line.recalc = true;
1352
1353                         UpdateView(caret.line, caret.pos);
1354                         if (move_caret) {
1355                                 caret.pos++;
1356                                 UpdateCaret();
1357                         }
1358                 }
1359
1360                 // Inserts n characters at the given position; it will not delete past line limits
1361                 internal void DeleteChars(LineTag tag, int pos, int count) {
1362                         Line    line;
1363                         bool    streamline;
1364
1365
1366                         streamline = false;
1367                         line = tag.line;
1368
1369                         if (pos == line.text.Length) {
1370                                 return;
1371                         }
1372
1373                         line.text.Remove(pos, count);
1374
1375                         // Check if we're crossing tag boundaries
1376                         if ((pos + count) > (tag.start + tag.length)) {
1377                                 int     left;
1378
1379                                 // We have to delete cross tag boundaries
1380                                 streamline = true;
1381                                 left = count;
1382
1383                                 left -= pos - tag.start;
1384                                 tag.length -= pos - tag.start;
1385
1386                                 tag = tag.next;
1387                                 while ((tag != null) && (left > 0)) {
1388                                         if (tag.length > left) {
1389                                                 tag.length -= left;
1390                                                 left = 0;
1391                                         } else {
1392                                                 left -= tag.length;
1393                                                 tag.length = 0;
1394         
1395                                                 tag = tag.next;
1396                                         }
1397                                 }
1398                         } else {
1399                                 // We got off easy, same tag
1400
1401                                 tag.length -= count;
1402                         }
1403
1404                         tag = tag.next;
1405                         while (tag != null) {
1406                                 tag.start -= count;
1407                                 tag = tag.next;
1408                         }
1409
1410                         line.recalc = true;
1411                         if (streamline) {
1412                                 line.Streamline();
1413                         }
1414
1415                         UpdateView(line, pos);
1416                 }
1417
1418
1419                 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
1420                 internal void DeleteChar(LineTag tag, int pos, bool forward) {
1421                         Line    line;
1422                         bool    streamline;
1423
1424                         streamline = false;
1425                         line = tag.line;
1426
1427                         if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
1428                                 return;
1429                         }
1430
1431                         if (forward) {
1432                                 line.text.Remove(pos, 1);
1433                                 tag.length--;
1434
1435                                 if (tag.length == 0) {
1436                                         streamline = true;
1437                                 }
1438                         } else {
1439                                 pos--;
1440                                 line.text.Remove(pos, 1);
1441                                 if (pos >= (tag.start - 1)) {
1442                                         tag.length--;
1443                                         if (tag.length == 0) {
1444                                                 streamline = true;
1445                                         }
1446                                 } else if (tag.previous != null) {
1447                                         tag.previous.length--;
1448                                         if (tag.previous.length == 0) {
1449                                                 streamline = true;
1450                                         }
1451                                 }
1452                         }
1453
1454                         tag = tag.next;
1455                         while (tag != null) {
1456                                 tag.start--;
1457                                 tag = tag.next;
1458                         }
1459                         line.recalc = true;
1460                         if (streamline) {
1461                                 line.Streamline();
1462                         }
1463
1464                         UpdateView(line, pos);
1465                 }
1466
1467                 // Combine two lines
1468                 internal void Combine(int FirstLine, int SecondLine) {
1469                         Combine(GetLine(FirstLine), GetLine(SecondLine));
1470                 }
1471
1472                 internal void Combine(Line first, Line second) {
1473                         LineTag last;
1474                         int     shift;
1475
1476                         // Combine the two tag chains into one
1477                         last = first.tags;
1478
1479                         while (last.next != null) {
1480                                 last = last.next;
1481                         }
1482
1483                         last.next = second.tags;
1484                         last.next.previous = last;
1485
1486                         shift = last.start + last.length - 1;
1487
1488                         // Fix up references within the chain
1489                         last = last.next;
1490                         while (last != null) {
1491                                 last.line = first;
1492                                 last.start += shift;
1493                                 last = last.next;
1494                         }
1495
1496                         // Combine both lines' strings
1497                         first.text.Insert(first.text.Length, second.text.ToString());
1498                         first.Grow(first.text.Length);
1499
1500                         // Remove the reference to our (now combined) tags from the doomed line
1501                         second.tags = null;
1502
1503                         // Renumber lines
1504                         DecrementLines(first.line_no + 2);      // first.line_no + 1 will be deleted, so we need to start renumbering one later
1505
1506                         // Mop up
1507                         first.recalc = true;
1508                         first.height = 0;       // This forces RecalcDocument/UpdateView to redraw from this line on
1509                         first.Streamline();
1510
1511                         #if Debug
1512                                 Line    check_first;
1513                                 Line    check_second;
1514
1515                                 check_first = GetLine(first.line_no);
1516                                 check_second = GetLine(check_first.line_no + 1);
1517
1518                                 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
1519                         #endif
1520
1521                         this.Delete(second);
1522
1523                         #if Debug
1524                                 check_first = GetLine(first.line_no);
1525                                 check_second = GetLine(check_first.line_no + 1);
1526
1527                                 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
1528                         #endif
1529
1530                 }
1531
1532                 // Split the line at the position into two
1533                 internal void Split(int LineNo, int pos) {
1534                         Line    line;
1535                         LineTag tag;
1536
1537                         line = GetLine(LineNo);
1538                         tag = LineTag.FindTag(line, pos);
1539                         Split(line, tag, pos);
1540                 }
1541
1542                 internal void Split(Line line, int pos) {
1543                         LineTag tag;
1544
1545                         tag = LineTag.FindTag(line, pos);
1546                         Split(line, tag, pos);
1547                 }
1548
1549                 internal void Split(Line line, LineTag tag, int pos) {
1550                         LineTag new_tag;
1551                         Line    new_line;
1552
1553                         // cover the easy case first
1554                         if (pos == line.text.Length) {
1555                                 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
1556                                 return;
1557                         }
1558
1559                         // We need to move the rest of the text into the new line
1560                         Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
1561
1562                         // Now transfer our tags from this line to the next
1563                         new_line = GetLine(line.line_no + 1);
1564                         line.recalc = true;
1565
1566                         if ((tag.start - 1) == pos) {
1567                                 int     shift;
1568
1569                                 // We can simply break the chain and move the tag into the next line
1570                                 if (tag == line.tags) {
1571                                         new_tag = new LineTag(line, 1, 0);
1572                                         new_tag.font = tag.font;
1573                                         new_tag.color = tag.color;
1574                                         line.tags = new_tag;
1575                                 }
1576
1577                                 if (tag.previous != null) {
1578                                         tag.previous.next = null;
1579                                 }
1580                                 new_line.tags = tag;
1581                                 tag.previous = null;
1582                                 tag.line = new_line;
1583
1584                                 // Walk the list and correct the start location of the tags we just bumped into the next line
1585                                 shift = tag.start - 1;
1586
1587                                 new_tag = tag;
1588                                 while (new_tag != null) {
1589                                         new_tag.start -= shift;
1590                                         new_tag.line = new_line;
1591                                         new_tag = new_tag.next;
1592                                 }
1593                         } else {
1594                                 int     shift;
1595
1596                                 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
1597                                 new_tag.next = tag.next;
1598                                 new_tag.font = tag.font;
1599                                 new_tag.color = tag.color;
1600                                 new_line.tags = new_tag;
1601                                 if (new_tag.next != null) {
1602                                         new_tag.next.previous = new_tag;
1603                                 }
1604                                 tag.next = null;
1605                                 tag.length = pos - tag.start + 1;
1606
1607                                 shift = pos;
1608                                 new_tag = new_tag.next;
1609                                 while (new_tag != null) {
1610                                         new_tag.start -= shift;
1611                                         new_tag.line = new_line;
1612                                         new_tag = new_tag.next;
1613
1614                                 }
1615                         }
1616                         line.text.Remove(pos, line.text.Length - pos);
1617                 }
1618
1619                 // Adds a line of text, with given font.
1620                 // Bumps any line at that line number that already exists down
1621                 internal void Add(int LineNo, string Text, Font font, Brush color) {
1622                         Add(LineNo, Text, HorizontalAlignment.Left, font, color);
1623                 }
1624
1625                 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) {
1626                         Line    add;
1627                         Line    line;
1628                         int     line_no;
1629
1630                         if (LineNo<1 || Text == null) {
1631                                 if (LineNo<1) {
1632                                         throw new ArgumentNullException("LineNo", "Line numbers must be positive");
1633                                 } else {
1634                                         throw new ArgumentNullException("Text", "Cannot insert NULL line");
1635                                 }
1636                         }
1637
1638                         add = new Line(LineNo, Text, align, font, color);
1639
1640                         line = document;
1641                         while (line != sentinel) {
1642                                 add.parent = line;
1643                                 line_no = line.line_no;
1644
1645                                 if (LineNo > line_no) {
1646                                         line = line.right;
1647                                 } else if (LineNo < line_no) {
1648                                         line = line.left;
1649                                 } else {
1650                                         // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
1651                                         IncrementLines(line.line_no);
1652                                         line = line.left;
1653                                 }
1654                         }
1655
1656                         add.left = sentinel;
1657                         add.right = sentinel;
1658
1659                         if (add.parent != null) {
1660                                 if (LineNo > add.parent.line_no) {
1661                                         add.parent.right = add;
1662                                 } else {
1663                                         add.parent.left = add;
1664                                 }
1665                         } else {
1666                                 // Root node
1667                                 document = add;
1668                         }
1669
1670                         RebalanceAfterAdd(add);
1671
1672                         lines++;
1673                 }
1674
1675                 internal virtual void Clear() {
1676                         lines = 0;
1677                         document = sentinel;
1678                 }
1679
1680                 public virtual object Clone() {
1681                         Document clone;
1682
1683                         clone = new Document(null);
1684
1685                         clone.lines = this.lines;
1686                         clone.document = (Line)document.Clone();
1687
1688                         return clone;
1689                 }
1690
1691                 internal void Delete(int LineNo) {
1692                         if (LineNo>lines) {
1693                                 return;
1694                         }
1695
1696                         Delete(GetLine(LineNo));
1697                 }
1698
1699                 internal void Delete(Line line1) {
1700                         Line    line2;// = new Line();
1701                         Line    line3;
1702
1703                         if ((line1.left == sentinel) || (line1.right == sentinel)) {
1704                                 line3 = line1;
1705                         } else {
1706                                 line3 = line1.right;
1707                                 while (line3.left != sentinel) {
1708                                         line3 = line3.left;
1709                                 }
1710                         }
1711
1712                         if (line3.left != sentinel) {
1713                                 line2 = line3.left;
1714                         } else {
1715                                 line2 = line3.right;
1716                         }
1717
1718                         line2.parent = line3.parent;
1719                         if (line3.parent != null) {
1720                                 if(line3 == line3.parent.left) {
1721                                         line3.parent.left = line2;
1722                                 } else {
1723                                         line3.parent.right = line2;
1724                                 }
1725                         } else {
1726                                 document = line2;
1727                         }
1728
1729                         if (line3 != line1) {
1730                                 LineTag tag;
1731
1732                                 line1.ascent = line3.ascent;
1733                                 line1.height = line3.height;
1734                                 line1.line_no = line3.line_no;
1735                                 line1.recalc = line3.recalc;
1736                                 line1.space = line3.space;
1737                                 line1.tags = line3.tags;
1738                                 line1.text = line3.text;
1739                                 line1.widths = line3.widths;
1740                                 line1.Y = line3.Y;
1741
1742                                 tag = line1.tags;
1743                                 while (tag != null) {
1744                                         tag.line = line1;
1745                                         tag = tag.next;
1746                                 }
1747                         }
1748
1749                         if (line3.color == LineColor.Black)
1750                                 RebalanceAfterDelete(line2);
1751
1752                         this.lines--;
1753
1754                         last_found = sentinel;
1755                 }
1756
1757                 // Set our selection markers
1758                 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
1759                         Line    l1;
1760                         Line    l2;
1761                         int     p1;
1762                         int     p2;
1763         
1764                         // figure out what's before what so the logic below is straightforward
1765                         if (start.line_no < end.line_no) {
1766                                 l1 = start;
1767                                 p1 = start_pos;
1768
1769                                 l2 = end;
1770                                 p2 = end_pos;
1771                         } else if (start.line_no > end.line_no) {
1772                                 l1 = end;
1773                                 p1 = end_pos;
1774
1775                                 l2 = start;
1776                                 p2 = start_pos;
1777                         } else {
1778                                 if (start_pos < end_pos) {
1779                                         l1 = start;
1780                                         p1 = start_pos;
1781
1782                                         l2 = end;
1783                                         p2 = end_pos;
1784                                 } else {
1785                                         l1 = end;
1786                                         p1 = end_pos;
1787
1788                                         l2 = start;
1789                                         p2 = start_pos;
1790                                 }
1791
1792                                 owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, (int)l2.widths[p2] - (int)l1.widths[p1], l1.height));
1793                                 return;
1794                         }
1795
1796                         // Three invalidates:
1797                         // First line from start
1798                         owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, owner.Width, l1.height));
1799
1800                         // lines inbetween
1801                         if ((l1.line_no + 1) < l2.line_no) {
1802                                 int     y;
1803
1804                                 y = GetLine(l1.line_no + 1).Y;
1805                                 owner.Invalidate(new Rectangle(0 - viewport_x, y - viewport_y, owner.Width, GetLine(l2.line_no - 1).Y - y - viewport_y));
1806                         }
1807
1808                         // Last line to end
1809                         owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, owner.Width, l1.height));
1810
1811
1812                 }
1813
1814                 // It's nothing short of pathetic to always invalidate the whole control
1815                 // I will find time to finish the optimization and make it invalidate deltas only
1816                 internal void SetSelectionToCaret(bool start) {
1817                         if (start) {
1818                                 selection_start.line = caret.line;
1819                                 selection_start.tag = caret.tag;
1820                                 selection_start.pos = caret.pos;
1821
1822                                 // start always also selects end
1823                                 selection_end.line = caret.line;
1824                                 selection_end.tag = caret.tag;
1825                                 selection_end.pos = caret.pos;
1826                         } else {
1827                                 if (caret.line.line_no <= selection_end.line.line_no) {
1828                                         if ((caret.line != selection_end.line) || (caret.pos < selection_end.pos)) {
1829                                                 selection_start.line = caret.line;
1830                                                 selection_start.tag = caret.tag;
1831                                                 selection_start.pos = caret.pos;
1832                                         } else {
1833                                                 selection_end.line = caret.line;
1834                                                 selection_end.tag = caret.tag;
1835                                                 selection_end.pos = caret.pos;
1836                                         }
1837                                 } else {
1838                                         selection_end.line = caret.line;
1839                                         selection_end.tag = caret.tag;
1840                                         selection_end.pos = caret.pos;
1841                                 }
1842                         }
1843
1844
1845                         if ((selection_start.line == selection_end.line) && (selection_start.pos == selection_end.pos)) {
1846                                 selection_visible = false;
1847                         } else {
1848                                 selection_visible = true;
1849                         }
1850                         owner.Invalidate();
1851                 }
1852
1853 #if buggy
1854                 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
1855                         // Ok, this is ugly, bad and slow, but I don't have time right now to optimize it.
1856                         if (selection_visible) {
1857                                 // Try to only invalidate what's changed so we don't redraw the whole thing
1858                                 if ((start != selection_start.line) || (start_pos != selection_start.pos) && ((end == selection_end.line) && (end_pos == selection_end.pos))) {
1859                                         Invalidate(start, start_pos, selection_start.line, selection_start.pos);
1860                                 } else if ((end != selection_end.line) || (end_pos != selection_end.pos) && ((start == selection_start.line) && (start_pos == selection_start.pos))) {
1861                                         Invalidate(end, end_pos, selection_end.line, selection_end.pos);
1862                                 } else {
1863                                         // both start and end changed
1864                                         Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1865                                 }
1866                         }
1867                         selection_start.line = start;
1868                         selection_start.pos = start_pos;
1869 if (start != null) {
1870                         selection_start.tag = LineTag.FindTag(start, start_pos);
1871 }
1872
1873                         selection_end.line = end;
1874                         selection_end.pos = end_pos;
1875 if (end != null) {
1876                         selection_end.tag = LineTag.FindTag(end, end_pos);
1877 }
1878
1879                         if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
1880                                 selection_visible = false;
1881                         } else {
1882                                 selection_visible = true;
1883                                 
1884                         }
1885                 }
1886 #endif
1887                 // Make sure that start is always before end
1888                 private void FixupSelection() {
1889                         if (selection_start.line.line_no > selection_end.line.line_no) {
1890                                 Line    line;
1891                                 int     pos;
1892                                 LineTag tag;
1893
1894                                 line = selection_start.line;
1895                                 tag = selection_start.tag;
1896                                 pos = selection_start.pos;
1897
1898                                 selection_start.line = selection_end.line;
1899                                 selection_start.tag =  selection_end.tag;
1900                                 selection_start.pos = selection_end.pos;
1901                                 
1902                                 selection_end.line = line;
1903                                 selection_end.tag =  tag;
1904                                 selection_end.pos = pos;
1905
1906                                 return;
1907                         }
1908
1909                         if ((selection_start.line == selection_end.line) && (selection_start.pos > selection_end.pos)) {
1910                                 int     pos;
1911
1912                                 pos = selection_start.pos;
1913                                 selection_start.pos = selection_end.pos;
1914                                 selection_end.pos = pos;
1915                                 Console.WriteLine("flipped: sel start: {0} end: {1}", selection_start.pos, selection_end.pos);
1916                                 return;
1917                         }
1918
1919                         return;
1920                 }
1921
1922                 internal void SetSelectionStart(Line start, int start_pos) {
1923                         selection_start.line = start;
1924                         selection_start.pos = start_pos;
1925                         selection_start.tag = LineTag.FindTag(start, start_pos);
1926
1927                         FixupSelection();
1928
1929                         if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
1930                                 selection_visible = true;
1931                         }
1932
1933                         if (selection_visible) {
1934                                 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1935                         }
1936
1937                 }
1938
1939                 internal void SetSelectionEnd(Line end, int end_pos) {
1940                         selection_end.line = end;
1941                         selection_end.pos = end_pos;
1942                         selection_end.tag = LineTag.FindTag(end, end_pos);
1943
1944                         FixupSelection();
1945
1946                         if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
1947                                 selection_visible = true;
1948                         }
1949
1950                         if (selection_visible) {
1951                                 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1952                         }
1953                 }
1954
1955                 internal void SetSelection(Line start, int start_pos) {
1956                         if (selection_visible) {
1957                                 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
1958                         }
1959
1960
1961                         selection_start.line = start;
1962                         selection_start.pos = start_pos;
1963                         selection_start.tag = LineTag.FindTag(start, start_pos);
1964
1965                         selection_end.line = start;
1966                         selection_end.tag = selection_start.tag;
1967                         selection_end.pos = start_pos;
1968
1969                         selection_visible = false;
1970                 }
1971
1972                 internal void InvalidateSelectionArea() {
1973                         // implement me
1974                 }
1975
1976                 // Return the current selection, as string
1977                 internal string GetSelection() {
1978                         // We return String.Empty if there is no selection
1979                         if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
1980                                 return string.Empty;
1981                         }
1982
1983                         if (!multiline || (selection_start.line == selection_end.line)) {
1984                                 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
1985                         } else {
1986                                 StringBuilder   sb;
1987                                 int             i;
1988                                 int             start;
1989                                 int             end;
1990
1991                                 sb = new StringBuilder();
1992                                 start = selection_start.line.line_no;
1993                                 end = selection_end.line.line_no;
1994
1995                                 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
1996
1997                                 if ((start + 1) < end) {
1998                                         for (i = start + 1; i < end; i++) {
1999                                                 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
2000                                         }
2001                                 }
2002
2003                                 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
2004
2005                                 return sb.ToString();
2006                         }
2007                 }
2008
2009                 internal void ReplaceSelection(string s) {
2010                         // The easiest is to break the lines where the selection tags are and delete those lines
2011                         if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2012                                 // Nothing to delete, simply insert
2013                                 InsertString(selection_start.tag, selection_start.pos, s);
2014                         }
2015
2016                         if (!multiline || (selection_start.line == selection_end.line)) {
2017                                 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
2018
2019                                 // The tag might have been removed, we need to recalc it
2020                                 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
2021
2022                                 InsertString(selection_start.tag, selection_start.pos, s);
2023                         } else {
2024                                 int             i;
2025                                 int             start;
2026                                 int             end;
2027                                 int             base_line;
2028                                 string[]        ins;
2029                                 int             insert_lines;
2030
2031                                 start = selection_start.line.line_no;
2032                                 end = selection_end.line.line_no;
2033
2034                                 // Delete first line
2035                                 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
2036
2037                                 start++;
2038                                 if (start < end) {
2039                                         for (i = end - 1; i >= start; i++) {
2040                                                 Delete(i);
2041                                         }
2042                                 }
2043
2044                                 // Delete last line
2045                                 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
2046
2047                                 ins = s.Split(new char[] {'\n'});
2048
2049                                 for (int j = 0; j < ins.Length; j++) {
2050                                         if (ins[j].EndsWith("\r")) {
2051                                                 ins[j] = ins[j].Substring(0, ins[j].Length - 1);
2052                                         }
2053                                 }
2054
2055                                 insert_lines = ins.Length;
2056
2057                                 // Bump the text at insertion point a line down if we're inserting more than one line
2058                                 if (insert_lines > 1) {
2059                                         Split(selection_start.line, selection_start.pos);
2060
2061                                         // Reminder of start line is now in startline+1
2062
2063                                         // if the last line does not end with a \n we will insert the last line in front of the just moved text
2064                                         if (s.EndsWith("\n")) {
2065                                                 insert_lines--; // We don't want to insert the last line as part of the loop anymore
2066
2067                                                 InsertString(GetLine(selection_start.line.line_no + 1), 0, ins[insert_lines - 1]);
2068                                         }
2069                                 }
2070
2071                                 // Insert the first line
2072                                 InsertString(selection_start.line, selection_start.pos, ins[0]);
2073
2074                                 if (insert_lines > 1) {
2075                                         base_line = selection_start.line.line_no + 1;
2076
2077                                         for (i = 1; i < insert_lines; i++) {
2078                                                 Add(base_line + i, ins[i], selection_start.line.alignment, selection_start.tag.font, selection_start.tag.color);
2079                                         }
2080                                 }
2081                         }
2082                         selection_end.line = selection_start.line;
2083                         selection_end.pos = selection_start.pos;
2084                         selection_end.tag = selection_start.tag;
2085                         selection_visible = false;
2086                         InvalidateSelectionArea();
2087                 }
2088
2089                 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
2090                         Line    line;
2091                         LineTag tag;
2092                         int     i;
2093                         int     chars;
2094                         int     start;
2095
2096                         chars = 0;
2097
2098                         for (i = 1; i < lines; i++) {
2099                                 line = GetLine(i);
2100
2101                                 start = chars;
2102                                 chars += line.text.Length + 2;  // We count the trailing \n as a char
2103
2104                                 if (index <= chars) {
2105                                         // we found the line
2106                                         tag = line.tags;
2107
2108                                         while (tag != null) {
2109                                                 if (index < (start + tag.start + tag.length)) {
2110                                                         line_out = line;
2111                                                         tag_out = tag;
2112                                                         pos = index - start;
2113                                                         return;
2114                                                 }
2115                                                 if (tag.next == null) {
2116                                                         Line    next_line;
2117
2118                                                         next_line = GetLine(line.line_no + 1);
2119
2120                                                         if (next_line != null) {
2121                                                                 line_out = next_line;
2122                                                                 tag_out = next_line.tags;
2123                                                                 pos = 0;
2124                                                                 return;
2125                                                         } else {
2126                                                                 line_out = line;
2127                                                                 tag_out = tag;
2128                                                                 pos = line_out.text.Length;
2129                                                                 return;
2130                                                         }
2131                                                 }
2132                                                 tag = tag.next;
2133                                         }
2134                                 }
2135                         }
2136
2137                         line_out = GetLine(lines);
2138                         tag = line_out.tags;
2139                         while (tag.next != null) {
2140                                 tag = tag.next;
2141                         }
2142                         tag_out = tag;
2143                         pos = line_out.text.Length;
2144                 }
2145
2146                 internal int LineTagToCharIndex(Line line, int pos) {
2147                         int     i;
2148                         int     length;
2149
2150                         // Count first and last line
2151                         length = 0;
2152
2153                         // Count the lines in the middle
2154
2155                         for (i = 1; i < line.line_no; i++) {
2156                                 length += GetLine(i).text.Length + 2;   // Add one for the \n at the end of the line
2157                         }
2158
2159                         length += pos;
2160
2161                         return length;
2162                 }
2163
2164                 internal int SelectionLength() {
2165                         if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
2166                                 return 0;
2167                         }
2168
2169                         if (!multiline || (selection_start.line == selection_end.line)) {
2170                                 return selection_end.pos - selection_start.pos;
2171                         } else {
2172                                 int     i;
2173                                 int     start;
2174                                 int     end;
2175                                 int     length;
2176
2177                                 // Count first and last line
2178                                 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos;
2179
2180                                 // Count the lines in the middle
2181                                 start = selection_start.line.line_no + 1;
2182                                 end = selection_end.line.line_no;
2183
2184                                 if (start < end) {
2185                                         for (i = start; i < end; i++) {
2186                                                 length += GetLine(i).text.Length;
2187                                         }
2188                                 }
2189
2190                                 return length;
2191                         }
2192
2193                         
2194                 }
2195
2196
2197                 // Give it a Line number and it returns the Line object at with that line number
2198                 internal Line GetLine(int LineNo) {
2199                         Line    line = document;
2200
2201                         while (line != sentinel) {
2202                                 if (LineNo == line.line_no) {
2203                                         return line;
2204                                 } else if (LineNo < line.line_no) {
2205                                         line = line.left;
2206                                 } else {
2207                                         line = line.right;
2208                                 }
2209                         }
2210
2211                         return null;
2212                 }
2213
2214                 // Give it a Y pixel coordinate and it returns the Line covering that Y coordinate
2215                 ///
2216                 internal Line GetLineByPixel(int y, bool exact) {
2217                         Line    line = document;
2218                         Line    last = null;
2219
2220                         while (line != sentinel) {
2221                                 last = line;
2222                                 if ((y >= line.Y) && (y < (line.Y+line.height))) {
2223                                         return line;
2224                                 } else if (y < line.Y) {
2225                                         line = line.left;
2226                                 } else {
2227                                         line = line.right;
2228                                 }
2229                         }
2230
2231                         if (exact) {
2232                                 return null;
2233                         }
2234                         return last;
2235                 }
2236
2237                 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
2238                 internal LineTag FindTag(int x, int y, out int index, bool exact) {
2239                         Line    line;
2240                         LineTag tag;
2241
2242                         line = GetLineByPixel(y, exact);
2243                         if (line == null) {
2244                                 index = 0;
2245                                 return null;
2246                         }
2247                         tag = line.tags;
2248
2249                         // Alignment adjustment
2250                         x += line.align_shift;
2251
2252                         while (true) {
2253                                 if (x >= tag.X && x < (tag.X+tag.width)) {
2254                                         int     end;
2255
2256                                         end = tag.start + tag.length - 1;
2257
2258                                         for (int pos = tag.start; pos < end; pos++) {
2259                                                 if (x < line.widths[pos]) {
2260                                                         index = pos;
2261                                                         return tag;
2262                                                 }
2263                                         }
2264                                         index=end;
2265                                         return tag;
2266                                 }
2267                                 if (tag.next != null) {
2268                                         tag = tag.next;
2269                                 } else {
2270                                         if (exact) {
2271                                                 index = 0;
2272                                                 return null;
2273                                         }
2274
2275                                         index = line.text.Length;
2276                                         return tag;
2277                                 }
2278                         }
2279                 }
2280
2281                 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
2282                 internal LineTag FindCursor(int x, int y, out int index) {
2283                         Line    line;
2284                         LineTag tag;
2285
2286                         line = GetLineByPixel(y, false);
2287                         tag = line.tags;
2288
2289                         // Adjust for alignment
2290                         x += line.align_shift;
2291
2292                         while (true) {
2293                                 if (x >= tag.X && x < (tag.X+tag.width)) {
2294                                         int     end;
2295
2296                                         end = tag.start + tag.length - 1;
2297
2298                                         for (int pos = tag.start-1; pos < end; pos++) {
2299                                                 // When clicking on a character, we position the cursor to whatever edge
2300                                                 // of the character the click was closer
2301                                                 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
2302                                                         index = pos;
2303                                                         return tag;
2304                                                 }
2305                                         }
2306                                         index=end;
2307                                         return tag;
2308                                 }
2309                                 if (tag.next != null) {
2310                                         tag = tag.next;
2311                                 } else {
2312                                         index = line.text.Length;
2313                                         return tag;
2314                                 }
2315                         }
2316                 }
2317
2318                 internal void RecalculateAlignments() {
2319                         Line    line;
2320                         int     line_no;
2321
2322                         line_no = 1;
2323
2324                         while (line_no <= lines) {
2325                                 line = GetLine(line_no);
2326
2327                                 if (line.alignment != HorizontalAlignment.Left) {
2328                                         if (line.alignment == HorizontalAlignment.Center) {
2329                                                 line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
2330                                         } else {
2331                                                 line.align_shift = document_x - (int)line.widths[line.text.Length];
2332                                         }
2333                                 }
2334
2335                                 line_no++;
2336                         }
2337                         return;
2338                 }
2339
2340                 // Calculate formatting for the whole document
2341                 internal bool RecalculateDocument(Graphics g) {
2342                         return RecalculateDocument(g, 1, this.lines, false);
2343                 }
2344
2345                 // Calculate formatting starting at a certain line
2346                 internal bool RecalculateDocument(Graphics g, int start) {
2347                         return RecalculateDocument(g, start, this.lines, false);
2348                 }
2349
2350                 // Calculate formatting within two given line numbers
2351                 internal bool RecalculateDocument(Graphics g, int start, int end) {
2352                         return RecalculateDocument(g, start, end, false);
2353                 }
2354
2355                 // With optimize on, returns true if line heights changed
2356                 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
2357                         Line    line;
2358                         int     line_no;
2359                         int     Y;
2360
2361                         Y = GetLine(start).Y;
2362                         line_no = start;
2363                         if (optimize) {
2364                                 bool    changed;
2365                                 bool    alignment_recalc;
2366
2367                                 changed = false;
2368                                 alignment_recalc = false;
2369
2370                                 while (line_no <= end) {
2371                                         line = GetLine(line_no++);
2372                                         line.Y = Y;
2373                                         if (line.recalc) {
2374                                                 if (line.RecalculateLine(g)) {
2375                                                         changed = true;
2376                                                         // If the height changed, all subsequent lines change
2377                                                         end = this.lines;
2378                                                 }
2379
2380                                                 if (line.widths[line.text.Length] > this.document_x) {
2381                                                         this.document_x = (int)line.widths[line.text.Length];
2382                                                         alignment_recalc = true;
2383                                                 }
2384
2385                                                 // Calculate alignment
2386                                                 if (line.alignment != HorizontalAlignment.Left) {
2387                                                         if (line.alignment == HorizontalAlignment.Center) {
2388                                                                 line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
2389                                                         } else {
2390                                                                 line.align_shift = document_x - (int)line.widths[line.text.Length];
2391                                                         }
2392                                                 }
2393                                         }
2394
2395                                         Y += line.height;
2396                                 }
2397
2398                                 if (alignment_recalc) {
2399                                         RecalculateAlignments();
2400                                 }
2401
2402                                 return changed;
2403                         } else {
2404                                 while (line_no <= end) {
2405                                         line = GetLine(line_no++);
2406                                         line.Y = Y;
2407                                         line.RecalculateLine(g);
2408                                         if (line.widths[line.text.Length] > this.document_x) {
2409                                                 this.document_x = (int)line.widths[line.text.Length];
2410                                         }
2411
2412                                         // Calculate alignment
2413                                         if (line.alignment != HorizontalAlignment.Left) {
2414                                                 if (line.alignment == HorizontalAlignment.Center) {
2415                                                         line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
2416                                                 } else {
2417                                                         line.align_shift = document_x - (int)line.widths[line.text.Length];
2418                                                 }
2419                                         }
2420
2421                                         Y += line.height;
2422                                 }
2423                                 RecalculateAlignments();
2424                                 return true;
2425                         }
2426                 }
2427
2428                 internal bool SetCursor(int x, int y) {
2429                         return true;
2430                 }
2431
2432                 internal int Size() {
2433                         return lines;
2434                 }
2435                 #endregion      // Internal Methods
2436
2437                 #region Administrative
2438                 public IEnumerator GetEnumerator() {
2439                         // FIXME
2440                         return null;
2441                 }
2442
2443                 public override bool Equals(object obj) {
2444                         if (obj == null) {
2445                                 return false;
2446                         }
2447
2448                         if (!(obj is Document)) {
2449                                 return false;
2450                         }
2451
2452                         if (obj == this) {
2453                                 return true;
2454                         }
2455
2456                         if (ToString().Equals(((Document)obj).ToString())) {
2457                                 return true;
2458                         }
2459
2460                         return false;
2461                 }
2462
2463                 public override int GetHashCode() {
2464                         return document_id;
2465                 }
2466
2467                 public override string ToString() {
2468                         return "document " + this.document_id;
2469                 }
2470
2471                 #endregion      // Administrative
2472         }
2473
2474         internal class LineTag {
2475                 #region Local Variables;
2476                 // Payload; formatting
2477                 internal Font           font;           // System.Drawing.Font object for this tag
2478                 internal Brush          color;          // System.Drawing.Brush object
2479
2480                 // Payload; text
2481                 internal int            start;          // start, in chars; index into Line.text
2482                 internal int            length;         // length, in chars
2483                 internal bool           r_to_l;         // Which way is the font
2484
2485                 // Drawing support
2486                 internal int            height;         // Height in pixels of the text this tag describes
2487                 internal int            X;              // X location of the text this tag describes
2488                 internal float          width;          // Width in pixels of the text this tag describes
2489                 internal int            ascent;         // Ascent of the font for this tag
2490                 internal int            shift;          // Shift down for this tag, to stay on baseline
2491
2492                 internal int            soft_break;     // Tag is 'broken soft' and continues in the next line
2493
2494                 // Administrative
2495                 internal Line           line;           // The line we're on
2496                 internal LineTag        next;           // Next tag on the same line
2497                 internal LineTag        previous;       // Previous tag on the same line
2498                 #endregion;
2499
2500                 #region Constructors
2501                 internal LineTag(Line line, int start, int length) {
2502                         this.line = line;
2503                         this.start = start;
2504                         this.length = length;
2505                         this.X = 0;
2506                         this.width = 0;
2507                 }
2508                 #endregion      // Constructors
2509
2510                 #region Internal Methods
2511                 //
2512                 // Applies 'font' to characters starting at 'start' for 'length' chars
2513                 // Removes any previous tags overlapping the same area
2514                 // returns true if lineheight has changed
2515                 //
2516                 internal static bool FormatText(Line line, int start, int length, Font font, Brush color) {
2517                         LineTag tag;
2518                         LineTag start_tag;
2519                         int     end;
2520                         bool    retval = false;         // Assume line-height doesn't change
2521
2522                         // Too simple?
2523                         if (font.Height != line.height) {
2524                                 retval = true;
2525                         }
2526                         line.recalc = true;             // This forces recalculation of the line in RecalculateDocument
2527
2528                         // A little sanity, not sure if it's needed, might be able to remove for speed
2529                         if (length > line.text.Length) {
2530                                 length = line.text.Length;
2531                         }
2532
2533                         tag = line.tags;
2534                         end = start + length;
2535
2536                         // Common special case
2537                         if ((start == 1) && (length == tag.length)) {
2538                                 tag.ascent = 0;
2539                                 tag.font = font;
2540                                 tag.color = color;
2541                                 return retval;
2542                         }
2543
2544                         start_tag = FindTag(line, start);
2545
2546                         tag = new LineTag(line, start, length);
2547                         tag.font = font;
2548                         tag.color = color;
2549
2550                         if (start == 1) {
2551                                 line.tags = tag;
2552                         }
2553
2554                         if (start_tag.start == start) {
2555                                 tag.next = start_tag;
2556                                 tag.previous = start_tag.previous;
2557                                 if (start_tag.previous != null) {
2558                                         start_tag.previous.next = tag;
2559                                 }
2560                                 start_tag.previous = tag;
2561                         } else {
2562                                 // Insert ourselves 'in the middle'
2563                                 if ((start_tag.next != null) && (start_tag.next.start < end)) {
2564                                         tag.next = start_tag.next;
2565                                 } else {
2566                                         tag.next = new LineTag(line, start_tag.start, start_tag.length);
2567                                         tag.next.font = start_tag.font;
2568                                         tag.next.color = start_tag.color;
2569
2570                                         if (start_tag.next != null) {
2571                                                 tag.next.next = start_tag.next;
2572                                                 tag.next.next.previous = tag.next;
2573                                         }
2574                                 }
2575                                 tag.next.previous = tag;
2576
2577                                 start_tag.length = start - start_tag.start;
2578
2579                                 tag.previous = start_tag;
2580                                 start_tag.next = tag;
2581 #if nope
2582                                 if (tag.next.start > (tag.start + tag.length)) {
2583                                         tag.next.length  += tag.next.start - (tag.start + tag.length);
2584                                         tag.next.start = tag.start + tag.length;
2585                                 }
2586 #endif
2587                         }
2588
2589                         // Elimination loop
2590                         tag = tag.next;
2591                         while ((tag != null) && (tag.start < end)) {
2592                                 if ((tag.start + tag.length) <= end) {
2593                                         // remove the tag
2594                                         tag.previous.next = tag.next;
2595                                         if (tag.next != null) {
2596                                                 tag.next.previous = tag.previous;
2597                                         }
2598                                         tag = tag.previous;
2599                                 } else {
2600                                         // Adjust the length of the tag
2601                                         tag.length = (tag.start + tag.length) - end;
2602                                         tag.start = end;
2603                                 }
2604                                 tag = tag.next;
2605                         }
2606
2607                         return retval;
2608                 }
2609
2610
2611                 //
2612                 // Finds the tag that describes the character at position 'pos' on 'line'
2613                 //
2614                 internal static LineTag FindTag(Line line, int pos) {
2615                         LineTag tag = line.tags;
2616
2617                         // Beginning of line is a bit special
2618                         if (pos == 0) {
2619                                 return tag;
2620                         }
2621
2622                         while (tag != null) {
2623                                 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
2624                                         return tag;
2625                                 }
2626
2627                                 tag = tag.next;
2628                         }
2629
2630                         return null;
2631                 }
2632
2633                 //
2634                 // Combines 'this' tag with 'other' tag.
2635                 //
2636                 internal bool Combine(LineTag other) {
2637                         if (!this.Equals(other)) {
2638                                 return false;
2639                         }
2640
2641                         this.width += other.width;
2642                         this.length += other.length;
2643                         this.next = other.next;
2644                         if (this.next != null) {
2645                                 this.next.previous = this;
2646                         }
2647
2648                         return true;
2649                 }
2650
2651
2652                 //
2653                 // Remove 'this' tag ; to be called when formatting is to be removed
2654                 //
2655                 internal bool Remove() {
2656                         if ((this.start == 1) && (this.next == null)) {
2657                                 // We cannot remove the only tag
2658                                 return false;
2659                         }
2660                         if (this.start != 1) {
2661                                 this.previous.length += this.length;
2662                                 this.previous.width = -1;
2663                                 this.previous.next = this.next;
2664                                 this.next.previous = this.previous;
2665                         } else {
2666                                 this.next.start = 1;
2667                                 this.next.length += this.length;
2668                                 this.next.width = -1;
2669                                 this.line.tags = this.next;
2670                                 this.next.previous = null;
2671                         }
2672                         return true;
2673                 }
2674
2675
2676                 //
2677                 // Checks if 'this' tag describes the same formatting options as 'obj'
2678                 //
2679                 public override bool Equals(object obj) {
2680                         LineTag other;
2681
2682                         if (obj == null) {
2683                                 return false;
2684                         }
2685
2686                         if (!(obj is LineTag)) {
2687                                 return false;
2688                         }
2689
2690                         if (obj == this) {
2691                                 return true;
2692                         }
2693
2694                         other = (LineTag)obj;
2695
2696                         if (this.font.Equals(other.font) && this.color.Equals(other.color)) {   // FIXME add checking for things like link or type later
2697                                 return true;
2698                         }
2699
2700                         return false;
2701                 }
2702
2703                 public override int GetHashCode() {\r
2704                         return base.GetHashCode ();\r
2705                 }\r
2706
2707                 public override string ToString() {
2708                         return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
2709                 }
2710
2711                 #endregion      // Internal Methods
2712         }
2713 }