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:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
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.
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
23 // Peter Bartok pbartok@novell.com
29 // There's still plenty of things missing, I've got most of it planned, just hadn't had
30 // the time to write it all yet.
31 // Stuff missing (in no particular order):
32 // - Align text after RecalculateLine
33 // - Implement tag types for hotlinks, images, etc.
34 // - Implement CaretPgUp/PgDown
37 // selection_start.pos and selection_end.pos are 0-based
38 // selection_start.pos = first selected char
39 // selection_end.pos = first NOT-selected char
41 // FormatText methods are 1-based (as are all tags, LineTag.Start is 1 for
42 // the first character on a line; the reason is that 0 is the position
43 // *before* the first character on a line
49 using System.Collections;
51 using System.Drawing.Text;
54 namespace System.Windows.Forms {
55 internal enum LineColor {
60 internal enum CaretSelection {
61 Position, // Selection=Caret
62 Word, // Selection=Word under caret
63 Line // Selection=Line under caret
66 internal class FontDefinition {
69 internal FontStyle add_style;
70 internal FontStyle remove_style;
72 internal Font font_obj;
74 internal FontDefinition() {
81 internal enum CaretDirection {
82 CharForward, // Move a char to the right
83 CharBack, // Move a char to the left
84 LineUp, // Move a line up
85 LineDown, // Move a line down
86 Home, // Move to the beginning of the line
87 End, // Move to the end of the line
88 PgUp, // Move one page up
89 PgDn, // Move one page down
90 CtrlHome, // Move to the beginning of the document
91 CtrlEnd, // Move to the end of the document
92 WordBack, // Move to the beginning of the previous word (or beginning of line)
93 WordForward, // Move to the beginning of the next word (or end of line)
94 SelectionStart, // Move to the beginning of the current selection
95 SelectionEnd // Move to the end of the current selection
98 // Being cloneable should allow for nice line and document copies...
99 internal class Line : ICloneable, IComparable {
100 #region Local Variables
101 // Stuff that matters for our line
102 internal StringBuilder text; // Characters for the line
103 internal float[] widths; // Width of each character; always one larger than text.Length
104 internal int space; // Number of elements in text and widths
105 internal int line_no; // Line number
106 internal LineTag tags; // Tags describing the text
107 internal int Y; // Baseline
108 internal int height; // Height of the line (height of tallest tag)
109 internal int ascent; // Ascent of the line (ascent of the tallest tag)
110 internal HorizontalAlignment alignment; // Alignment of the line
111 internal int align_shift; // Pixel shift caused by the alignment
112 internal bool soft_break; // Tag is 'broken soft' and continuation from previous line
113 internal int indent; // Left indent for the first line
114 internal int hanging_indent; // Hanging indent (left indent for all but the first line)
115 internal int right_indent; // Right indent for all lines
118 // Stuff that's important for the tree
119 internal Line parent; // Our parent line
120 internal Line left; // Line with smaller line number
121 internal Line right; // Line with higher line number
122 internal LineColor color; // We're doing a black/red tree. this is the node color
123 internal int DEFAULT_TEXT_LEN; //
124 internal static StringFormat string_format; // For calculating widths/heights
125 internal bool recalc; // Line changed
126 #endregion // Local Variables
130 color = LineColor.Red;
137 alignment = HorizontalAlignment.Left;
139 if (string_format == null) {
140 string_format = new StringFormat(StringFormat.GenericTypographic);
141 string_format.Trimming = StringTrimming.None;
142 string_format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
146 internal Line(int LineNo, string Text, Font font, Brush color) : this() {
147 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
149 text = new StringBuilder(Text, space);
152 widths = new float[space + 1];
153 tags = new LineTag(this, 1, text.Length);
158 internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) : this() {
159 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
161 text = new StringBuilder(Text, space);
165 widths = new float[space + 1];
166 tags = new LineTag(this, 1, text.Length);
171 internal Line(int LineNo, string Text, LineTag tag) : this() {
172 space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
174 text = new StringBuilder(Text, space);
177 widths = new float[space + 1];
181 #endregion // Constructors
183 #region Internal Properties
184 internal int Indent {
195 internal int HangingIndent {
197 return hanging_indent;
201 hanging_indent = value;
206 internal int RightIndent {
212 right_indent = value;
218 internal int Height {
228 internal int LineNo {
238 internal string Text {
240 return text.ToString();
244 text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
248 internal HorizontalAlignment Alignment {
254 if (alignment != value) {
261 internal StringBuilder Text {
271 #endregion // Internal Properties
273 #region Internal Methods
274 // Make sure we always have enoughs space in text and widths
275 internal void Grow(int minimum) {
279 length = text.Length;
281 if ((length + minimum) > space) {
282 // We need to grow; double the size
284 if ((length + minimum) > (space * 2)) {
285 new_widths = new float[length + minimum * 2 + 1];
286 space = length + minimum * 2;
288 new_widths = new float[space * 2 + 1];
291 widths.CopyTo(new_widths, 0);
297 internal void Streamline(int lines) {
304 // Catch what the loop below wont; eliminate 0 length
305 // tags, but only if there are other tags after us
306 while ((current.length == 0) && (next != null)) {
308 tags.previous = null;
317 while (next != null) {
318 // Take out 0 length tags unless it's the last tag in the document
319 if (next.length == 0) {
320 if ((next.next != null) || (line_no != lines)) {
321 current.next = next.next;
322 if (current.next != null) {
323 current.next.previous = current;
329 if (current.Combine(next)) {
334 current = current.next;
339 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
340 internal LineTag FindTag(int pos) {
349 if (pos >= text.Length) {
350 pos = text.Length - 1;
353 while (tag != null) {
354 if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
363 /// Recalculate a single line using the same char for every character in the line
366 internal bool RecalculatePasswordLine(Graphics g, Document doc) {
375 len = this.text.Length;
385 w = g.MeasureString(doc.password_char, tags.font, 10000, string_format).Width;
387 if (this.height != (int)tag.font.Height) {
393 this.height = (int)tag.font.Height;
394 tag.height = this.height;
396 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
397 this.ascent = tag.ascent;
402 widths[pos] = widths[pos-1] + w;
409 /// Go through all tags on a line and recalculate all size-related values;
410 /// returns true if lineheight changed
412 internal bool RecalculateLine(Graphics g, Document doc) {
426 len = this.text.Length;
428 prev_height = this.height; // For drawing optimization calculations
429 this.height = 0; // Reset line height
430 this.ascent = 0; // Reset the ascent for the line
434 if (this.soft_break) {
435 widths[0] = hanging_indent;
448 size = g.MeasureString(this.text.ToString(pos, 1), tag.font, 10000, string_format);
450 while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
453 if (tag.previous != null) {
454 tag.X = tag.previous.X;
456 tag.X = (int)widths[pos];
465 if (Char.IsWhiteSpace(text[pos])) {
467 wrap_width = tag.width + w;
471 if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 27 > (doc.viewport_width - this.right_indent)) {
473 tag.width = wrap_width;
474 doc.Split(this, tag, pos, true);
475 len = this.text.Length;
481 // Contract all soft lines that follow back into our line
487 widths[pos] = widths[pos-1] + w;
490 line = doc.GetLine(this.line_no + 1);
491 if ((line != null) && (line.soft_break)) {
492 // Pull the previous line back into this one
493 doc.Combine(this.line_no, this.line_no + 1);
494 len = this.text.Length;
500 if (pos == (tag.start-1 + tag.length)) {
501 // We just found the end of our current tag
502 tag.height = (int)tag.font.Height;
504 // Check if we're the tallest on the line (so far)
505 if (tag.height > this.height) {
506 this.height = tag.height; // Yep; make sure the line knows
509 if (tag.ascent == 0) {
512 XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
515 if (tag.ascent > this.ascent) {
518 // We have a tag that has a taller ascent than the line;
522 t.shift = tag.ascent - t.ascent;
527 this.ascent = tag.ascent;
529 tag.shift = this.ascent - tag.ascent;
532 // Update our horizontal starting pixel position
533 if (tag.previous == null) {
534 tag.X = (int)widths[0];
536 tag.X = tag.previous.X + (int)tag.previous.width;
544 wrap_width = tag.width;
549 if (this.height == 0) {
550 this.height = tags.font.Height;
551 tag.height = this.height;
554 if (prev_height != this.height) {
559 #endregion // Internal Methods
561 #region Administrative
562 public int CompareTo(object obj) {
567 if (! (obj is Line)) {
568 throw new ArgumentException("Object is not of type Line", "obj");
571 if (line_no < ((Line)obj).line_no) {
573 } else if (line_no > ((Line)obj).line_no) {
580 public object Clone() {
588 clone.left = (Line)left.Clone();
592 clone.left = (Line)left.Clone();
598 internal object CloneLine() {
608 public override bool Equals(object obj) {
613 if (!(obj is Line)) {
621 if (line_no == ((Line)obj).line_no) {
628 public override int GetHashCode() {
629 return base.GetHashCode ();
632 public override string ToString() {
633 return "Line " + line_no;
636 #endregion // Administrative
639 internal class Document : ICloneable, IEnumerable {
641 // FIXME - go through code and check for places where
642 // we do explicit comparisons instead of using the compare overloads
643 internal struct Marker {
645 internal LineTag tag;
649 public static bool operator<(Marker lhs, Marker rhs) {
650 if (lhs.line.line_no < rhs.line.line_no) {
654 if (lhs.line.line_no == rhs.line.line_no) {
655 if (lhs.pos < rhs.pos) {
662 public static bool operator>(Marker lhs, Marker rhs) {
663 if (lhs.line.line_no > rhs.line.line_no) {
667 if (lhs.line.line_no == rhs.line.line_no) {
668 if (lhs.pos > rhs.pos) {
675 public static bool operator==(Marker lhs, Marker rhs) {
676 if ((lhs.line.line_no == rhs.line.line_no) && (lhs.pos == rhs.pos)) {
682 public static bool operator!=(Marker lhs, Marker rhs) {
683 if ((lhs.line.line_no != rhs.line.line_no) || (lhs.pos != rhs.pos)) {
689 public void Combine(Line move_to_line, int move_to_line_length) {
691 pos += move_to_line_length;
692 tag = LineTag.FindTag(line, pos);
695 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
696 public void Split(Line move_to_line, int split_at) {
699 tag = LineTag.FindTag(line, pos);
702 public override bool Equals(object obj) {
703 return this==(Marker)obj;
706 public override int GetHashCode() {
707 return base.GetHashCode ();
710 public override string ToString() {
711 return "Marker Line " + line + ", Position " + pos;
715 #endregion Structures
717 #region Local Variables
718 private Line document;
720 private Line sentinel;
721 private Line last_found;
722 private int document_id;
723 private Random random = new Random();
724 internal string password_char;
725 private StringBuilder password_cache;
726 private bool calc_pass;
727 private int char_count;
729 private bool no_recalc;
730 private bool recalc_pending;
731 private int recalc_start;
732 private int recalc_end;
733 private bool recalc_optimize;
735 internal bool multiline;
738 internal UndoClass undo;
740 internal Marker caret;
741 internal Marker selection_start;
742 internal Marker selection_end;
743 internal bool selection_visible;
744 internal Marker selection_anchor;
745 internal Marker selection_prev;
746 internal bool selection_end_anchor;
748 internal int viewport_x;
749 internal int viewport_y; // The visible area of the document
750 internal int viewport_width;
751 internal int viewport_height;
753 internal int document_x; // Width of the document
754 internal int document_y; // Height of the document
756 internal Rectangle invalid;
758 internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
760 internal TextBoxBase owner; // Who's owning us?
761 static internal int caret_width = 1;
762 static internal int caret_shift = 1;
763 #endregion // Local Variables
766 internal Document(TextBoxBase owner) {
775 recalc_pending = false;
777 // Tree related stuff
778 sentinel = new Line();
779 sentinel.color = LineColor.Black;
782 last_found = sentinel;
784 // We always have a blank line
785 owner.HandleCreated += new EventHandler(owner_HandleCreated);
786 owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
787 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
790 undo = new UndoClass(this);
792 selection_visible = false;
793 selection_start.line = this.document;
794 selection_start.pos = 0;
795 selection_start.tag = selection_start.line.tags;
796 selection_end.line = this.document;
797 selection_end.pos = 0;
798 selection_end.tag = selection_end.line.tags;
799 selection_anchor.line = this.document;
800 selection_anchor.pos = 0;
801 selection_anchor.tag = selection_anchor.line.tags;
808 // Default selection is empty
810 document_id = random.Next();
814 #region Internal Properties
831 internal Line CaretLine {
837 internal int CaretPosition {
843 internal Point Caret {
845 return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.align_shift, caret.line.Y);
849 internal LineTag CaretTag {
859 internal int CRLFSize {
869 internal string PasswordChar {
871 return password_char;
875 password_char = value;
876 if ((password_char.Length != 0) && (password_char[0] != '\0')) {
881 password_cache = new StringBuilder(1024);
882 for (int i = 0; i < 1024; i++) {
883 password_cache.Append(ch);
887 password_cache = null;
892 internal int ViewPortX {
902 internal int Length {
904 return char_count + lines - 1; // Add \n for each line but the last
908 private int CharCount {
916 if (LengthChanged != null) {
917 LengthChanged(this, EventArgs.Empty);
922 ///<summary>Setting NoRecalc to true will prevent the document from being recalculated.
923 ///This ensures that coordinates of added text are predictable after adding the text even with wrapped view</summary>
924 internal bool NoRecalc {
931 if (!no_recalc && recalc_pending) {
932 RecalculateDocument(owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
933 recalc_pending = false;
938 internal int ViewPortY {
948 internal int ViewPortWidth {
950 return viewport_width;
954 viewport_width = value - 2;
958 internal int ViewPortHeight {
960 return viewport_height;
964 viewport_height = value;
971 return this.document_x;
975 internal int Height {
977 return this.document_y;
981 internal bool SelectionVisible {
983 return selection_visible;
997 #endregion // Internal Properties
999 #region Private Methods
1001 internal int DumpTree(Line line, bool with_tags) {
1006 Console.Write("Line {0}, Y: {1} Text {2}", line.line_no, line.Y, line.text != null ? line.text.ToString() : "undefined");
1008 if (line.left == sentinel) {
1009 Console.Write(", left = sentinel");
1010 } else if (line.left == null) {
1011 Console.Write(", left = NULL");
1014 if (line.right == sentinel) {
1015 Console.Write(", right = sentinel");
1016 } else if (line.right == null) {
1017 Console.Write(", right = NULL");
1020 Console.WriteLine("");
1030 Console.Write(" Tags: ");
1031 while (tag != null) {
1032 Console.Write("{0} <{1}>-<{2}> ", count++, tag.start, tag.length);
1033 length += tag.length;
1035 if (tag.line != line) {
1036 Console.Write("BAD line link");
1037 throw new Exception("Bad line link in tree");
1041 Console.Write(", ");
1044 if (length > line.text.Length) {
1045 throw new Exception(String.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1046 } else if (length < line.text.Length) {
1047 throw new Exception(String.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line.text.Length, length));
1049 Console.WriteLine("");
1051 if (line.left != null) {
1052 if (line.left != sentinel) {
1053 total += DumpTree(line.left, with_tags);
1056 if (line != sentinel) {
1057 throw new Exception("Left should not be NULL");
1061 if (line.right != null) {
1062 if (line.right != sentinel) {
1063 total += DumpTree(line.right, with_tags);
1066 if (line != sentinel) {
1067 throw new Exception("Right should not be NULL");
1071 for (int i = 1; i <= this.lines; i++) {
1072 if (GetLine(i) == null) {
1073 throw new Exception(String.Format("Hole in line order, missing {0}", i));
1077 if (line == this.Root) {
1078 if (total < this.lines) {
1079 throw new Exception(String.Format("Not enough nodes in tree, found {0}, expected {1}", total, this.lines));
1080 } else if (total > this.lines) {
1081 throw new Exception(String.Format("Too many nodes in tree, found {0}, expected {1}", total, this.lines));
1088 private void DecrementLines(int line_no) {
1092 while (current <= lines) {
1093 GetLine(current).line_no--;
1099 private void IncrementLines(int line_no) {
1102 current = this.lines;
1103 while (current >= line_no) {
1104 GetLine(current).line_no++;
1110 private void RebalanceAfterAdd(Line line1) {
1113 while ((line1 != document) && (line1.parent.color == LineColor.Red)) {
1114 if (line1.parent == line1.parent.parent.left) {
1115 line2 = line1.parent.parent.right;
1117 if ((line2 != null) && (line2.color == LineColor.Red)) {
1118 line1.parent.color = LineColor.Black;
1119 line2.color = LineColor.Black;
1120 line1.parent.parent.color = LineColor.Red;
1121 line1 = line1.parent.parent;
1123 if (line1 == line1.parent.right) {
1124 line1 = line1.parent;
1128 line1.parent.color = LineColor.Black;
1129 line1.parent.parent.color = LineColor.Red;
1131 RotateRight(line1.parent.parent);
1134 line2 = line1.parent.parent.left;
1136 if ((line2 != null) && (line2.color == LineColor.Red)) {
1137 line1.parent.color = LineColor.Black;
1138 line2.color = LineColor.Black;
1139 line1.parent.parent.color = LineColor.Red;
1140 line1 = line1.parent.parent;
1142 if (line1 == line1.parent.left) {
1143 line1 = line1.parent;
1147 line1.parent.color = LineColor.Black;
1148 line1.parent.parent.color = LineColor.Red;
1149 RotateLeft(line1.parent.parent);
1153 document.color = LineColor.Black;
1156 private void RebalanceAfterDelete(Line line1) {
1159 while ((line1 != document) && (line1.color == LineColor.Black)) {
1160 if (line1 == line1.parent.left) {
1161 line2 = line1.parent.right;
1162 if (line2.color == LineColor.Red) {
1163 line2.color = LineColor.Black;
1164 line1.parent.color = LineColor.Red;
1165 RotateLeft(line1.parent);
1166 line2 = line1.parent.right;
1168 if ((line2.left.color == LineColor.Black) && (line2.right.color == LineColor.Black)) {
1169 line2.color = LineColor.Red;
1170 line1 = line1.parent;
1172 if (line2.right.color == LineColor.Black) {
1173 line2.left.color = LineColor.Black;
1174 line2.color = LineColor.Red;
1176 line2 = line1.parent.right;
1178 line2.color = line1.parent.color;
1179 line1.parent.color = LineColor.Black;
1180 line2.right.color = LineColor.Black;
1181 RotateLeft(line1.parent);
1185 line2 = line1.parent.left;
1186 if (line2.color == LineColor.Red) {
1187 line2.color = LineColor.Black;
1188 line1.parent.color = LineColor.Red;
1189 RotateRight(line1.parent);
1190 line2 = line1.parent.left;
1192 if ((line2.right.color == LineColor.Black) && (line2.left.color == LineColor.Black)) {
1193 line2.color = LineColor.Red;
1194 line1 = line1.parent;
1196 if (line2.left.color == LineColor.Black) {
1197 line2.right.color = LineColor.Black;
1198 line2.color = LineColor.Red;
1200 line2 = line1.parent.left;
1202 line2.color = line1.parent.color;
1203 line1.parent.color = LineColor.Black;
1204 line2.left.color = LineColor.Black;
1205 RotateRight(line1.parent);
1210 line1.color = LineColor.Black;
1213 private void RotateLeft(Line line1) {
1214 Line line2 = line1.right;
1216 line1.right = line2.left;
1218 if (line2.left != sentinel) {
1219 line2.left.parent = line1;
1222 if (line2 != sentinel) {
1223 line2.parent = line1.parent;
1226 if (line1.parent != null) {
1227 if (line1 == line1.parent.left) {
1228 line1.parent.left = line2;
1230 line1.parent.right = line2;
1237 if (line1 != sentinel) {
1238 line1.parent = line2;
1242 private void RotateRight(Line line1) {
1243 Line line2 = line1.left;
1245 line1.left = line2.right;
1247 if (line2.right != sentinel) {
1248 line2.right.parent = line1;
1251 if (line2 != sentinel) {
1252 line2.parent = line1.parent;
1255 if (line1.parent != null) {
1256 if (line1 == line1.parent.right) {
1257 line1.parent.right = line2;
1259 line1.parent.left = line2;
1265 line2.right = line1;
1266 if (line1 != sentinel) {
1267 line1.parent = line2;
1272 internal void UpdateView(Line line, int pos) {
1273 if (!owner.IsHandleCreated) {
1278 recalc_start = line.line_no;
1279 recalc_end = line.line_no;
1280 recalc_optimize = true;
1281 recalc_pending = true;
1285 // Optimize invalidation based on Line alignment
1286 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no, true)) {
1287 // Lineheight changed, invalidate the rest of the document
1288 if ((line.Y - viewport_y) >=0 ) {
1289 // We formatted something that's in view, only draw parts of the screen
1290 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1292 // The tag was above the visible area, draw everything
1296 switch(line.alignment) {
1297 case HorizontalAlignment.Left: {
1298 owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
1302 case HorizontalAlignment.Center: {
1303 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, line.height + 1));
1307 case HorizontalAlignment.Right: {
1308 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.align_shift, line.height + 1));
1316 // Update display from line, down line_count lines; pos is unused, but required for the signature
1317 internal void UpdateView(Line line, int line_count, int pos) {
1318 if (!owner.IsHandleCreated) {
1323 recalc_start = line.line_no;
1324 recalc_end = line.line_no + line_count - 1;
1325 recalc_optimize = true;
1326 recalc_pending = true;
1330 if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count - 1, true)) {
1331 // Lineheight changed, invalidate the rest of the document
1332 if ((line.Y - viewport_y) >=0 ) {
1333 // We formatted something that's in view, only draw parts of the screen
1334 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1335 owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
1337 // The tag was above the visible area, draw everything
1338 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1344 end_line = GetLine(line.line_no + line_count -1);
1345 if (end_line == null) {
1349 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1350 owner.Invalidate(new Rectangle(0 - viewport_x, line.Y - viewport_y, (int)line.widths[line.text.Length], end_line.Y + end_line.height));
1353 #endregion // Private Methods
1355 #region Internal Methods
1356 // Clear the document and reset state
1357 internal void Empty() {
1359 document = sentinel;
1360 last_found = sentinel;
1363 // We always have a blank line
1364 Add(1, "", owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush(owner.ForeColor));
1365 this.RecalculateDocument(owner.CreateGraphicsInternal());
1366 PositionCaret(0, 0);
1368 selection_visible = false;
1369 selection_start.line = this.document;
1370 selection_start.pos = 0;
1371 selection_start.tag = selection_start.line.tags;
1372 selection_end.line = this.document;
1373 selection_end.pos = 0;
1374 selection_end.tag = selection_end.line.tags;
1384 internal void PositionCaret(Line line, int pos) {
1385 if (!owner.IsHandleCreated) {
1389 undo.RecordCursor();
1391 caret.tag = line.FindTag(pos);
1394 caret.height = caret.tag.height;
1396 if (owner.Focused) {
1397 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1400 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1403 internal void PositionCaret(int x, int y) {
1404 if (!owner.IsHandleCreated) {
1408 undo.RecordCursor();
1410 caret.tag = FindCursor(x, y, out caret.pos);
1411 caret.line = caret.tag.line;
1412 caret.height = caret.tag.height;
1414 if (owner.Focused) {
1415 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1418 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1421 internal void CaretHasFocus() {
1422 if ((caret.tag != null) && owner.IsHandleCreated) {
1423 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1424 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1426 if (!selection_visible) {
1427 XplatUI.CaretVisible(owner.Handle, true);
1429 XplatUI.CaretVisible(owner.Handle, false);
1434 internal void CaretLostFocus() {
1435 if (!owner.IsHandleCreated) {
1438 XplatUI.DestroyCaret(owner.Handle);
1441 internal void AlignCaret() {
1442 if (!owner.IsHandleCreated) {
1446 undo.RecordCursor();
1448 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1449 caret.height = caret.tag.height;
1451 if (owner.Focused) {
1452 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1453 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1454 XplatUI.CaretVisible(owner.Handle, true);
1457 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1460 internal void UpdateCaret() {
1461 if (!owner.IsHandleCreated) {
1465 undo.RecordCursor();
1467 if (caret.tag.height != caret.height) {
1468 caret.height = caret.tag.height;
1469 if (owner.Focused) {
1470 XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
1474 if (owner.Focused) {
1475 XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
1476 XplatUI.CaretVisible(owner.Handle, true);
1479 if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
1482 internal void DisplayCaret() {
1483 if (!owner.IsHandleCreated) {
1487 if (owner.Focused) {
1488 XplatUI.CaretVisible(owner.Handle, true);
1492 internal void HideCaret() {
1493 if (!owner.IsHandleCreated) {
1497 if (owner.Focused) {
1498 XplatUI.CaretVisible(owner.Handle, false);
1502 internal void MoveCaret(CaretDirection direction) {
1503 // FIXME should we use IsWordSeparator to detect whitespace, instead
1504 // of looking for actual spaces in the Word move cases?
1506 case CaretDirection.CharForward: {
1508 if (caret.pos > caret.line.text.Length) {
1510 // Go into next line
1511 if (caret.line.line_no < this.lines) {
1512 caret.line = GetLine(caret.line.line_no+1);
1514 caret.tag = caret.line.tags;
1519 // Single line; we stay where we are
1523 if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
1524 caret.tag = caret.tag.next;
1531 case CaretDirection.CharBack: {
1532 if (caret.pos > 0) {
1533 // caret.pos--; // folded into the if below
1534 if (--caret.pos > 0) {
1535 if (caret.tag.start > caret.pos) {
1536 caret.tag = caret.tag.previous;
1540 if (caret.line.line_no > 1) {
1541 caret.line = GetLine(caret.line.line_no - 1);
1542 caret.pos = caret.line.text.Length;
1543 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1550 case CaretDirection.WordForward: {
1553 len = caret.line.text.Length;
1554 if (caret.pos < len) {
1555 while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) {
1558 if (caret.pos < len) {
1559 // Skip any whitespace
1560 while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) {
1564 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1566 if (caret.line.line_no < this.lines) {
1567 caret.line = GetLine(caret.line.line_no + 1);
1569 caret.tag = caret.line.tags;
1576 case CaretDirection.WordBack: {
1577 if (caret.pos > 0) {
1580 while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) {
1584 while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) {
1588 if (caret.line.text.ToString(caret.pos, 1) == " ") {
1589 if (caret.pos != 0) {
1592 caret.line = GetLine(caret.line.line_no - 1);
1593 caret.pos = caret.line.text.Length;
1596 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1598 if (caret.line.line_no > 1) {
1599 caret.line = GetLine(caret.line.line_no - 1);
1600 caret.pos = caret.line.text.Length;
1601 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1608 case CaretDirection.LineUp: {
1609 if (caret.line.line_no > 1) {
1612 pixel = (int)caret.line.widths[caret.pos];
1613 PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y);
1614 if (!owner.IsHandleCreated) {
1617 XplatUI.CaretVisible(owner.Handle, true);
1622 case CaretDirection.LineDown: {
1623 if (caret.line.line_no < lines) {
1626 pixel = (int)caret.line.widths[caret.pos];
1627 PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y);
1628 if (!owner.IsHandleCreated) {
1631 XplatUI.CaretVisible(owner.Handle, true);
1636 case CaretDirection.Home: {
1637 if (caret.pos > 0) {
1639 caret.tag = caret.line.tags;
1645 case CaretDirection.End: {
1646 if (caret.pos < caret.line.text.Length) {
1647 caret.pos = caret.line.text.Length;
1648 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1654 case CaretDirection.PgUp: {
1658 case CaretDirection.PgDn: {
1662 case CaretDirection.CtrlHome: {
1663 caret.line = GetLine(1);
1665 caret.tag = caret.line.tags;
1671 case CaretDirection.CtrlEnd: {
1672 caret.line = GetLine(lines);
1673 caret.pos = caret.line.text.Length;
1674 caret.tag = LineTag.FindTag(caret.line, caret.pos);
1680 case CaretDirection.SelectionStart: {
1681 caret.line = selection_start.line;
1682 caret.pos = selection_start.pos;
1683 caret.tag = selection_start.tag;
1689 case CaretDirection.SelectionEnd: {
1690 caret.line = selection_end.line;
1691 caret.pos = selection_end.pos;
1692 caret.tag = selection_end.tag;
1700 // Draw the document
1701 internal void Draw(Graphics g, Rectangle clip) {
1702 Line line; // Current line being drawn
1703 LineTag tag; // Current tag being drawn
1704 int start; // First line to draw
1705 int end; // Last line to draw
1706 StringBuilder text; // String representing the current line
1712 // First, figure out from what line to what line we need to draw
1713 start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
1714 end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
1715 //Console.WriteLine("Starting drawing at line {0}, ending at line {1} (clip-bottom:{2})", start, end, clip.Bottom);
1717 // Now draw our elements; try to only draw those that are visible
1721 DateTime n = DateTime.Now;
1722 Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
1725 disabled = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
1726 hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
1727 hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
1729 while (line_no <= end) {
1730 line = GetLine(line_no);
1735 // This fails if there's a password > 1024 chars...
1736 text = this.password_cache;
1738 while (tag != null) {
1739 if (tag.length == 0) {
1744 if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
1745 // Check for selection
1746 if ((!selection_visible) || (!owner.has_focus) || (line_no < selection_start.line.line_no) || (line_no > selection_end.line.line_no)) {
1747 // regular drawing, no selection to deal with
1748 //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);
1749 if (owner.is_enabled) {
1750 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1755 a = ((SolidBrush)tag.color).Color;
1756 b = ThemeEngine.Current.ColorWindowText;
1758 if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
1759 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, disabled, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1761 g.DrawString(text.ToString(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
1765 // we might have to draw our selection
1766 if ((line_no != selection_start.line.line_no) && (line_no != selection_end.line.line_no)) {
1767 // Special case, whole line is selected, draw this tag selected
1770 tag.X + line.align_shift - viewport_x, // X
1771 line.Y + tag.shift - viewport_y, // Y
1772 line.widths[tag.start + tag.length - 1], // width
1773 tag.height // Height
1777 //s.Substring(tag.start-1, tag.length), // String
1778 text.ToString(tag.start-1, tag.length), // String
1780 hilight_text, // Brush
1781 tag.X + line.align_shift - viewport_x, // X
1782 line.Y + tag.shift - viewport_y, // Y
1783 StringFormat.GenericTypographic);
1791 // One or more, but not all tags on the line are selected
1792 if ((selection_start.tag == tag) && (selection_end.tag == tag)) {
1793 // Single tag selected, draw "normalSELECTEDnormal"
1795 // First, the regular part
1797 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1798 text.ToString(tag.start - 1, selection_start.pos - tag.start + 1), // String
1801 tag.X + line.align_shift - viewport_x, // X
1802 line.Y + tag.shift - viewport_y, // Y
1803 StringFormat.GenericTypographic);
1805 // Now the highlight
1808 line.widths[selection_start.pos] + line.align_shift, // X
1809 line.Y + tag.shift - viewport_y, // Y
1810 line.widths[selection_end.pos] - line.widths[selection_start.pos], // Width
1811 tag.height); // Height
1814 //s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), // String
1815 text.ToString(selection_start.pos, selection_end.pos - selection_start.pos), // String
1817 hilight_text, // Brush
1818 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1819 line.Y + tag.shift - viewport_y, // Y
1820 StringFormat.GenericTypographic);
1822 // And back to the regular
1824 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1825 text.ToString(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1828 line.widths[selection_end.pos] + line.align_shift - viewport_x, // X
1829 line.Y + tag.shift - viewport_y, // Y
1830 StringFormat.GenericTypographic);
1832 } else if (selection_start.tag == tag) {
1835 // The highlighted part
1838 line.widths[selection_start.pos] + line.align_shift,
1839 line.Y + tag.shift - viewport_y,
1840 line.widths[tag.start + tag.length - 1] - line.widths[selection_start.pos],
1844 //s.Substring(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1845 text.ToString(selection_start.pos, tag.start + tag.length - selection_start.pos - 1), // String
1847 hilight_text, // Brush
1848 line.widths[selection_start.pos] + line.align_shift - viewport_x, // X
1849 line.Y + tag.shift - viewport_y, // Y
1850 StringFormat.GenericTypographic);
1854 //s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), // String
1855 text.ToString(tag.start - 1, selection_start.pos - tag.start + 1), // String
1858 tag.X + line.align_shift - viewport_x, // X
1859 line.Y + tag.shift - viewport_y, // Y
1860 StringFormat.GenericTypographic);
1861 } else if (selection_end.tag == tag) {
1864 // The highlighted part
1867 tag.X + line.align_shift - viewport_x,
1868 line.Y + tag.shift - viewport_y,
1869 line.widths[selection_end.pos] - line.widths[tag.start - 1],
1873 //s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), // String
1874 text.ToString(tag.start - 1, selection_end.pos - tag.start + 1), // String
1876 hilight_text, // Brush
1877 tag.X + line.align_shift - viewport_x, // X
1878 line.Y + tag.shift - viewport_y, // Y
1879 StringFormat.GenericTypographic);
1883 //s.Substring(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1884 text.ToString(selection_end.pos, tag.start + tag.length - selection_end.pos - 1), // String
1887 line.widths[selection_end.pos] + line.align_shift - viewport_x, // X
1888 line.Y + tag.shift - viewport_y, // Y
1889 StringFormat.GenericTypographic);
1891 // no partially selected tags here, simple checks...
1892 if (selection_start.line == line) {
1896 begin = tag.start - 1;
1897 stop = tag.start + tag.length - 1;
1898 if (selection_end.line == line) {
1899 if ((begin >= selection_start.pos) && (stop < selection_end.pos)) {
1903 if (stop > selection_start.pos) {
1907 } else if (selection_end.line == line) {
1908 if ((tag.start - 1) < selection_end.pos) {
1918 tag.X + line.align_shift - viewport_x,
1919 line.Y + tag.shift - viewport_y,
1920 line.widths[tag.start + tag.length - 1] - line.widths[tag.start - 1],
1924 //s.Substring(tag.start-1, tag.length), // String
1925 text.ToString(tag.start-1, tag.length), // String
1927 hilight_text, // Brush
1928 tag.X + line.align_shift - viewport_x, // X
1929 line.Y + tag.shift - viewport_y, // Y
1930 StringFormat.GenericTypographic);
1933 //s.Substring(tag.start-1, tag.length), // String
1934 text.ToString(tag.start-1, tag.length), // String
1937 tag.X + line.align_shift - viewport_x, // X
1938 line.Y + tag.shift - viewport_y, // Y
1939 StringFormat.GenericTypographic);
1954 Console.WriteLine("Finished drawing: {0}s {1}ms", n.Second, n.Millisecond);
1959 internal void Insert(Line line, int pos, string s) {
1960 Insert(line, null, pos, false, s);
1963 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
1964 internal void Insert(Line line, LineTag tag, int pos, bool update_caret, string s) {
1971 // The formatting at the insertion point is used for the inserted text
1973 tag = LineTag.FindTag(line, pos);
1976 base_line = line.line_no;
1978 ins = s.Split(new char[] {'\n'});
1980 for (int j = 0; j < ins.Length; j++) {
1981 if (ins[j].EndsWith("\r")) {
1982 ins[j] = ins[j].Substring(0, ins[j].Length - 1);
1986 insert_lines = ins.Length;
1988 // Bump the text at insertion point a line down if we're inserting more than one line
1989 if (insert_lines > 1) {
1991 // Remainder of start line is now in base_line + 1
1994 // Insert the first line
1995 InsertString(tag, pos, ins[0]);
1997 if (insert_lines > 1) {
1998 for (i = 1; i < insert_lines; i++) {
1999 Add(base_line + i, ins[i], line.alignment, tag.font, tag.color);
2001 if (!s.EndsWith("\n\n")) {
2002 this.Combine(base_line + insert_lines - 1, base_line + insert_lines);
2006 UpdateView(line, insert_lines + 1, pos);
2008 if (update_caret && owner.IsHandleCreated) {
2009 // Move caret to the end of the inserted text
2010 if (insert_lines > 1) {
2011 PositionCaret(GetLine(line.line_no + insert_lines - 1), ins[ins.Length - 1].Length);
2013 PositionCaret(line, pos + ins[0].Length);
2015 XplatUI.CaretVisible(owner.Handle, true);
2019 // Inserts a character at the given position
2020 internal void InsertString(Line line, int pos, string s) {
2021 InsertString(line.FindTag(pos), pos, s);
2024 // Inserts a string at the given position
2025 internal void InsertString(LineTag tag, int pos, string s) {
2034 line.text.Insert(pos, s);
2038 while (tag != null) {
2045 UpdateView(line, pos);
2048 // Inserts a string at the caret position
2049 internal void InsertStringAtCaret(string s, bool move_caret) {
2057 caret.line.text.Insert(caret.pos, s);
2058 caret.tag.length += len;
2060 if (caret.tag.next != null) {
2061 tag = caret.tag.next;
2062 while (tag != null) {
2067 caret.line.Grow(len);
2068 caret.line.recalc = true;
2070 UpdateView(caret.line, caret.pos);
2079 // Inserts a character at the given position
2080 internal void InsertChar(Line line, int pos, char ch) {
2081 InsertChar(line.FindTag(pos), pos, ch);
2084 // Inserts a character at the given position
2085 internal void InsertChar(LineTag tag, int pos, char ch) {
2091 line.text.Insert(pos, ch);
2095 while (tag != null) {
2102 UpdateView(line, pos);
2105 // Inserts a character at the current caret position
2106 internal void InsertCharAtCaret(char ch, bool move_caret) {
2111 caret.line.text.Insert(caret.pos, ch);
2114 if (caret.tag.next != null) {
2115 tag = caret.tag.next;
2116 while (tag != null) {
2122 caret.line.recalc = true;
2124 UpdateView(caret.line, caret.pos);
2128 SetSelectionToCaret(true);
2132 // Deletes n characters at the given position; it will not delete past line limits
2134 internal void DeleteChars(LineTag tag, int pos, int count) {
2143 if (pos == line.text.Length) {
2147 line.text.Remove(pos, count);
2149 // Make sure the tag points to the right spot
2150 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2158 // Check if we're crossing tag boundaries
2159 if ((pos + count) > (tag.start + tag.length - 1)) {
2162 // We have to delete cross tag boundaries
2166 left -= tag.start + tag.length - pos - 1;
2167 tag.length -= tag.start + tag.length - pos - 1;
2170 while ((tag != null) && (left > 0)) {
2171 tag.start -= count - left;
2172 if (tag.length > left) {
2183 // We got off easy, same tag
2185 tag.length -= count;
2187 if (tag.length == 0) {
2192 // Adjust the start point of any tags following
2195 while (tag != null) {
2203 line.Streamline(lines);
2206 UpdateView(line, pos);
2209 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2210 internal void DeleteChar(LineTag tag, int pos, bool forward) {
2219 if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
2225 line.text.Remove(pos, 1);
2227 while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
2237 if (tag.length == 0) {
2242 line.text.Remove(pos, 1);
2243 if (pos >= (tag.start - 1)) {
2245 if (tag.length == 0) {
2248 } else if (tag.previous != null) {
2249 tag.previous.length--;
2250 if (tag.previous.length == 0) {
2257 while (tag != null) {
2263 line.Streamline(lines);
2266 UpdateView(line, pos);
2269 // Combine two lines
2270 internal void Combine(int FirstLine, int SecondLine) {
2271 Combine(GetLine(FirstLine), GetLine(SecondLine));
2274 internal void Combine(Line first, Line second) {
2278 // Combine the two tag chains into one
2281 while (last.next != null) {
2285 last.next = second.tags;
2286 last.next.previous = last;
2288 shift = last.start + last.length - 1;
2290 // Fix up references within the chain
2292 while (last != null) {
2294 last.start += shift;
2298 // Combine both lines' strings
2299 first.text.Insert(first.text.Length, second.text.ToString());
2300 first.Grow(first.text.Length);
2302 // Remove the reference to our (now combined) tags from the doomed line
2306 DecrementLines(first.line_no + 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2309 first.recalc = true;
2310 first.height = 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2311 first.Streamline(lines);
2313 // Update Caret, Selection, etc
2314 if (caret.line == second) {
2315 caret.Combine(first, shift);
2317 if (selection_anchor.line == second) {
2318 selection_anchor.Combine(first, shift);
2320 if (selection_start.line == second) {
2321 selection_start.Combine(first, shift);
2323 if (selection_end.line == second) {
2324 selection_end.Combine(first, shift);
2331 check_first = GetLine(first.line_no);
2332 check_second = GetLine(check_first.line_no + 1);
2334 Console.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2337 this.Delete(second);
2340 check_first = GetLine(first.line_no);
2341 check_second = GetLine(check_first.line_no + 1);
2343 Console.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first.Y, check_second.Y);
2348 // Split the line at the position into two
2349 internal void Split(int LineNo, int pos) {
2353 line = GetLine(LineNo);
2354 tag = LineTag.FindTag(line, pos);
2355 Split(line, tag, pos, false);
2358 internal void Split(Line line, int pos) {
2361 tag = LineTag.FindTag(line, pos);
2362 Split(line, tag, pos, false);
2365 ///<summary>Split line at given tag and position into two lines</summary>
2366 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2367 ///if more space becomes available on previous line</param>
2368 internal void Split(Line line, LineTag tag, int pos, bool soft) {
2372 bool move_sel_start;
2376 move_sel_start = false;
2377 move_sel_end = false;
2379 // Adjust selection and cursors
2380 if (soft && (caret.line == line) && (caret.pos >= pos)) {
2383 if (selection_start.line == line && selection_start.pos > pos) {
2384 move_sel_start = true;
2387 if (selection_end.line == line && selection_end.pos > pos) {
2388 move_sel_end = true;
2391 // cover the easy case first
2392 if (pos == line.text.Length) {
2393 Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
2395 new_line = GetLine(line.line_no + 1);
2399 caret.line = new_line;
2400 caret.line.soft_break = true;
2401 caret.tag = new_line.tags;
2404 new_line.soft_break = true;
2408 if (move_sel_start) {
2409 selection_start.line = new_line;
2410 selection_start.pos = 0;
2411 selection_start.tag = new_line.tags;
2415 selection_end.line = new_line;
2416 selection_end.pos = 0;
2417 selection_end.tag = new_line.tags;
2422 // We need to move the rest of the text into the new line
2423 Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
2425 // Now transfer our tags from this line to the next
2426 new_line = GetLine(line.line_no + 1);
2428 new_line.recalc = true;
2430 if ((tag.start - 1) == pos) {
2433 // We can simply break the chain and move the tag into the next line
2434 if (tag == line.tags) {
2435 new_tag = new LineTag(line, 1, 0);
2436 new_tag.font = tag.font;
2437 new_tag.color = tag.color;
2438 line.tags = new_tag;
2441 if (tag.previous != null) {
2442 tag.previous.next = null;
2444 new_line.tags = tag;
2445 tag.previous = null;
2446 tag.line = new_line;
2448 // Walk the list and correct the start location of the tags we just bumped into the next line
2449 shift = tag.start - 1;
2452 while (new_tag != null) {
2453 new_tag.start -= shift;
2454 new_tag.line = new_line;
2455 new_tag = new_tag.next;
2460 new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
2461 new_tag.next = tag.next;
2462 new_tag.font = tag.font;
2463 new_tag.color = tag.color;
2464 new_line.tags = new_tag;
2465 if (new_tag.next != null) {
2466 new_tag.next.previous = new_tag;
2469 tag.length = pos - tag.start + 1;
2472 new_tag = new_tag.next;
2473 while (new_tag != null) {
2474 new_tag.start -= shift;
2475 new_tag.line = new_line;
2476 new_tag = new_tag.next;
2483 caret.line = new_line;
2484 caret.pos = caret.pos - pos;
2485 caret.tag = caret.line.FindTag(caret.pos);
2487 new_line.soft_break = true;
2490 if (move_sel_start) {
2491 selection_start.line = new_line;
2492 selection_start.pos = selection_start.pos - pos;
2493 selection_start.tag = new_line.FindTag(selection_start.pos);
2497 selection_end.line = new_line;
2498 selection_end.pos = selection_end.pos - pos;
2499 selection_end.tag = new_line.FindTag(selection_end.pos);
2502 CharCount -= line.text.Length - pos;
2503 line.text.Remove(pos, line.text.Length - pos);
2506 // Adds a line of text, with given font.
2507 // Bumps any line at that line number that already exists down
2508 internal void Add(int LineNo, string Text, Font font, Brush color) {
2509 Add(LineNo, Text, HorizontalAlignment.Left, font, color);
2512 internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) {
2517 CharCount += Text.Length;
2519 if (LineNo<1 || Text == null) {
2521 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2523 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2527 add = new Line(LineNo, Text, align, font, color);
2530 while (line != sentinel) {
2532 line_no = line.line_no;
2534 if (LineNo > line_no) {
2536 } else if (LineNo < line_no) {
2539 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2540 IncrementLines(line.line_no);
2545 add.left = sentinel;
2546 add.right = sentinel;
2548 if (add.parent != null) {
2549 if (LineNo > add.parent.line_no) {
2550 add.parent.right = add;
2552 add.parent.left = add;
2559 RebalanceAfterAdd(add);
2564 internal virtual void Clear() {
2567 document = sentinel;
2570 public virtual object Clone() {
2573 clone = new Document(null);
2575 clone.lines = this.lines;
2576 clone.document = (Line)document.Clone();
2581 internal void Delete(int LineNo) {
2588 line = GetLine(LineNo);
2590 CharCount -= line.text.Length;
2592 DecrementLines(LineNo + 1);
2596 internal void Delete(Line line1) {
2597 Line line2;// = new Line();
2600 if ((line1.left == sentinel) || (line1.right == sentinel)) {
2603 line3 = line1.right;
2604 while (line3.left != sentinel) {
2609 if (line3.left != sentinel) {
2612 line2 = line3.right;
2615 line2.parent = line3.parent;
2616 if (line3.parent != null) {
2617 if(line3 == line3.parent.left) {
2618 line3.parent.left = line2;
2620 line3.parent.right = line2;
2626 if (line3 != line1) {
2629 line1.ascent = line3.ascent;
2630 line1.height = line3.height;
2631 line1.line_no = line3.line_no;
2632 line1.recalc = line3.recalc;
2633 line1.space = line3.space;
2634 line1.tags = line3.tags;
2635 line1.text = line3.text;
2636 line1.widths = line3.widths;
2638 line1.soft_break = line3.soft_break;
2641 while (tag != null) {
2647 if (line3.color == LineColor.Black)
2648 RebalanceAfterDelete(line2);
2652 last_found = sentinel;
2655 // Invalidate a section of the document to trigger redraw
2656 internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
2662 if ((start == end) && (start_pos == end_pos)) {
2666 if (end_pos == -1) {
2667 end_pos = end.text.Length;
2670 // figure out what's before what so the logic below is straightforward
2671 if (start.line_no < end.line_no) {
2677 } else if (start.line_no > end.line_no) {
2684 if (start_pos < end_pos) {
2699 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3}", l1.line_no, p1, l2.line_no, p2);
2704 (int)l1.widths[p1] + l1.align_shift - viewport_x,
2706 (int)l2.widths[p2] - (int)l1.widths[p1] + 1,
2714 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Start => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height);
2717 // Three invalidates:
2718 // First line from start
2719 owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.align_shift - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
2722 if ((l1.line_no + 1) < l2.line_no) {
2725 y = GetLine(l1.line_no + 1).Y;
2726 owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, GetLine(l2.line_no).Y - y - viewport_y));
2729 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, 0, y - viewport_y, viewport_width, GetLine(l2.line_no).Y - y - viewport_y);
2734 owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
2736 Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} End => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l2.widths[0] + l2.align_shift - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height);
2740 /// <summary>Select text around caret</summary>
2741 internal void ExpandSelection(CaretSelection mode, bool to_caret) {
2743 // We're expanding the selection to the caret position
2745 case CaretSelection.Line: {
2746 // Invalidate the selection delta
2747 if (caret > selection_prev) {
2748 Invalidate(selection_prev.line, 0, caret.line, caret.line.text.Length);
2750 Invalidate(selection_prev.line, selection_prev.line.text.Length, caret.line, 0);
2753 if (caret.line.line_no <= selection_anchor.line.line_no) {
2754 selection_start.line = caret.line;
2755 selection_start.tag = caret.line.tags;
2756 selection_start.pos = 0;
2758 selection_end.line = selection_anchor.line;
2759 selection_end.tag = selection_anchor.tag;
2760 selection_end.pos = selection_anchor.pos;
2762 selection_end_anchor = true;
2764 selection_start.line = selection_anchor.line;
2765 selection_start.pos = selection_anchor.height;
2766 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2768 selection_end.line = caret.line;
2769 selection_end.tag = caret.line.tags;
2770 selection_end.pos = caret.line.text.Length;
2772 selection_end_anchor = false;
2774 selection_prev.line = caret.line;
2775 selection_prev.tag = caret.tag;
2776 selection_prev.pos = caret.pos;
2781 case CaretSelection.Word: {
2785 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2786 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2789 // Invalidate the selection delta
2790 if (caret > selection_prev) {
2791 Invalidate(selection_prev.line, selection_prev.pos, caret.line, end_pos);
2793 Invalidate(selection_prev.line, selection_prev.pos, caret.line, start_pos);
2795 if (caret < selection_anchor) {
2796 selection_start.line = caret.line;
2797 selection_start.tag = caret.line.FindTag(start_pos);
2798 selection_start.pos = start_pos;
2800 selection_end.line = selection_anchor.line;
2801 selection_end.tag = selection_anchor.tag;
2802 selection_end.pos = selection_anchor.pos;
2804 selection_prev.line = caret.line;
2805 selection_prev.tag = caret.tag;
2806 selection_prev.pos = start_pos;
2808 selection_end_anchor = true;
2810 selection_start.line = selection_anchor.line;
2811 selection_start.pos = selection_anchor.height;
2812 selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
2814 selection_end.line = caret.line;
2815 selection_end.tag = caret.line.FindTag(end_pos);
2816 selection_end.pos = end_pos;
2818 selection_prev.line = caret.line;
2819 selection_prev.tag = caret.tag;
2820 selection_prev.pos = end_pos;
2822 selection_end_anchor = false;
2827 case CaretSelection.Position: {
2828 SetSelectionToCaret(false);
2833 // We're setting the selection 'around' the caret position
2835 case CaretSelection.Line: {
2836 this.Invalidate(caret.line, 0, caret.line, caret.line.text.Length);
2838 selection_start.line = caret.line;
2839 selection_start.tag = caret.line.tags;
2840 selection_start.pos = 0;
2842 selection_end.line = caret.line;
2843 selection_end.pos = caret.line.text.Length;
2844 selection_end.tag = caret.line.FindTag(selection_end.pos);
2846 selection_anchor.line = selection_end.line;
2847 selection_anchor.tag = selection_end.tag;
2848 selection_anchor.pos = selection_end.pos;
2849 selection_anchor.height = 0;
2851 selection_prev.line = caret.line;
2852 selection_prev.tag = caret.tag;
2853 selection_prev.pos = caret.pos;
2855 this.selection_end_anchor = true;
2860 case CaretSelection.Word: {
2864 start_pos = FindWordSeparator(caret.line, caret.pos, false);
2865 end_pos = FindWordSeparator(caret.line, caret.pos, true);
2867 this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
2869 selection_start.line = caret.line;
2870 selection_start.tag = caret.line.FindTag(start_pos);
2871 selection_start.pos = start_pos;
2873 selection_end.line = caret.line;
2874 selection_end.tag = caret.line.FindTag(end_pos);
2875 selection_end.pos = end_pos;
2877 selection_anchor.line = selection_end.line;
2878 selection_anchor.tag = selection_end.tag;
2879 selection_anchor.pos = selection_end.pos;
2880 selection_anchor.height = start_pos;
2882 selection_prev.line = caret.line;
2883 selection_prev.tag = caret.tag;
2884 selection_prev.pos = caret.pos;
2886 this.selection_end_anchor = true;
2893 if (selection_start == selection_end) {
2894 selection_visible = false;
2896 selection_visible = true;
2900 internal void SetSelectionToCaret(bool start) {
2902 // Invalidate old selection; selection is being reset to empty
2903 this.Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2905 selection_start.line = caret.line;
2906 selection_start.tag = caret.tag;
2907 selection_start.pos = caret.pos;
2909 // start always also selects end
2910 selection_end.line = caret.line;
2911 selection_end.tag = caret.tag;
2912 selection_end.pos = caret.pos;
2914 selection_anchor.line = caret.line;
2915 selection_anchor.tag = caret.tag;
2916 selection_anchor.pos = caret.pos;
2918 // Invalidate from previous end to caret (aka new end)
2919 if (selection_end_anchor) {
2920 if (selection_start != caret) {
2921 this.Invalidate(selection_start.line, selection_start.pos, caret.line, caret.pos);
2924 if (selection_end != caret) {
2925 this.Invalidate(selection_end.line, selection_end.pos, caret.line, caret.pos);
2929 if (caret < selection_anchor) {
2930 selection_start.line = caret.line;
2931 selection_start.tag = caret.tag;
2932 selection_start.pos = caret.pos;
2934 selection_end.line = selection_anchor.line;
2935 selection_end.tag = selection_anchor.tag;
2936 selection_end.pos = selection_anchor.pos;
2938 selection_end_anchor = true;
2940 selection_start.line = selection_anchor.line;
2941 selection_start.tag = selection_anchor.tag;
2942 selection_start.pos = selection_anchor.pos;
2944 selection_end.line = caret.line;
2945 selection_end.tag = caret.tag;
2946 selection_end.pos = caret.pos;
2948 selection_end_anchor = false;
2952 if (selection_start == selection_end) {
2953 selection_visible = false;
2955 selection_visible = true;
2959 internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
2960 if (selection_visible) {
2961 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
2964 if ((end.line_no < start.line_no) || ((end == start) && (end_pos <= start_pos))) {
2965 selection_start.line = end;
2966 selection_start.tag = LineTag.FindTag(end, end_pos);
2967 selection_start.pos = end_pos;
2969 selection_end.line = start;
2970 selection_end.tag = LineTag.FindTag(start, start_pos);
2971 selection_end.pos = start_pos;
2973 selection_end_anchor = true;
2975 selection_start.line = start;
2976 selection_start.tag = LineTag.FindTag(start, start_pos);
2977 selection_start.pos = start_pos;
2979 selection_end.line = end;
2980 selection_end.tag = LineTag.FindTag(end, end_pos);
2981 selection_end.pos = end_pos;
2983 selection_end_anchor = false;
2986 selection_anchor.line = start;
2987 selection_anchor.tag = selection_start.tag;
2988 selection_anchor.pos = start_pos;
2990 if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
2991 selection_visible = false;
2993 selection_visible = true;
2998 internal void SetSelectionStart(Line start, int start_pos) {
2999 // Invalidate from the previous to the new start pos
3000 Invalidate(selection_start.line, selection_start.pos, start, start_pos);
3002 selection_start.line = start;
3003 selection_start.pos = start_pos;
3004 selection_start.tag = LineTag.FindTag(start, start_pos);
3006 selection_anchor.line = start;
3007 selection_anchor.pos = start_pos;
3008 selection_anchor.tag = selection_start.tag;
3010 selection_end_anchor = false;
3012 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3013 selection_visible = true;
3015 // This could be calculated better
3016 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3021 internal void SetSelectionEnd(Line end, int end_pos) {
3022 if ((end.line_no < selection_anchor.line.line_no) || ((end == selection_anchor.line) && (end_pos <= selection_anchor.pos))) {
3023 selection_start.line = end;
3024 selection_start.tag = LineTag.FindTag(end, end_pos);
3025 selection_start.pos = end_pos;
3027 selection_end.line = selection_anchor.line;
3028 selection_end.tag = selection_anchor.tag;
3029 selection_end.pos = selection_anchor.pos;
3031 selection_end_anchor = true;
3033 selection_start.line = selection_anchor.line;
3034 selection_start.tag = selection_anchor.tag;
3035 selection_start.pos = selection_anchor.pos;
3037 selection_end.line = end;
3038 selection_end.tag = LineTag.FindTag(end, end_pos);
3039 selection_end.pos = end_pos;
3041 selection_end_anchor = false;
3044 if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
3045 selection_visible = true;
3046 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3050 internal void SetSelection(Line start, int start_pos) {
3051 if (selection_visible) {
3052 Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3055 selection_start.line = start;
3056 selection_start.pos = start_pos;
3057 selection_start.tag = LineTag.FindTag(start, start_pos);
3059 selection_end.line = start;
3060 selection_end.tag = selection_start.tag;
3061 selection_end.pos = start_pos;
3063 selection_anchor.line = start;
3064 selection_anchor.tag = selection_start.tag;
3065 selection_anchor.pos = start_pos;
3067 selection_end_anchor = false;
3068 selection_visible = false;
3071 internal void InvalidateSelectionArea() {
3072 // FIXME - the only place that calls this right now should really calculate the redraw itself; if done this function can go
3073 // Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
3076 // Return the current selection, as string
3077 internal string GetSelection() {
3078 // We return String.Empty if there is no selection
3079 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3080 return string.Empty;
3083 if (!multiline || (selection_start.line == selection_end.line)) {
3084 return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
3091 sb = new StringBuilder();
3092 start = selection_start.line.line_no;
3093 end = selection_end.line.line_no;
3095 sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
3097 if ((start + 1) < end) {
3098 for (i = start + 1; i < end; i++) {
3099 sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
3103 sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
3105 return sb.ToString();
3109 internal void ReplaceSelection(string s) {
3112 // First, delete any selected text
3113 if ((selection_start.pos != selection_end.pos) || (selection_start.line != selection_end.line)) {
3114 if (!multiline || (selection_start.line == selection_end.line)) {
3115 undo.RecordDeleteChars(selection_start.line, selection_start.pos + 1, selection_end.pos - selection_start.pos);
3117 DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
3119 // The tag might have been removed, we need to recalc it
3120 selection_start.tag = selection_start.line.FindTag(selection_start.pos);
3125 start = selection_start.line.line_no;
3126 end = selection_end.line.line_no;
3128 undo.RecordDelete(selection_start.line, selection_start.pos + 1, selection_end.line, selection_end.pos);
3130 // Delete first line
3131 DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
3134 DeleteChars(selection_end.line.tags, 0, selection_end.pos);
3138 for (i = end - 1; i >= start; i--) {
3143 // BIG FAT WARNING - selection_end.line might be stale due
3144 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3146 // Join start and end
3147 Combine(selection_start.line.line_no, start);
3151 Insert(selection_start.line, null, selection_start.pos, true, s);
3153 selection_end.line = selection_start.line;
3154 selection_end.pos = selection_start.pos;
3155 selection_end.tag = selection_start.tag;
3157 selection_visible = false;
3160 internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
3169 for (i = 1; i < lines; i++) {
3173 chars += line.text.Length + crlf_size;
3175 if (index <= chars) {
3176 // we found the line
3179 while (tag != null) {
3180 if (index < (start + tag.start + tag.length)) {
3183 pos = index - start;
3186 if (tag.next == null) {
3189 next_line = GetLine(line.line_no + 1);
3191 if (next_line != null) {
3192 line_out = next_line;
3193 tag_out = next_line.tags;
3199 pos = line_out.text.Length;
3208 line_out = GetLine(lines);
3209 tag = line_out.tags;
3210 while (tag.next != null) {
3214 pos = line_out.text.Length;
3217 internal int LineTagToCharIndex(Line line, int pos) {
3221 // Count first and last line
3224 // Count the lines in the middle
3226 for (i = 1; i < line.line_no; i++) {
3227 length += GetLine(i).text.Length + crlf_size;
3235 internal int SelectionLength() {
3236 if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
3240 if (!multiline || (selection_start.line == selection_end.line)) {
3241 return selection_end.pos - selection_start.pos;
3248 // Count first and last line
3249 length = selection_start.line.text.Length - selection_start.pos + selection_end.pos + crlf_size;
3251 // Count the lines in the middle
3252 start = selection_start.line.line_no + 1;
3253 end = selection_end.line.line_no;
3256 for (i = start; i < end; i++) {
3257 length += GetLine(i).text.Length + crlf_size;
3268 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3269 internal Line GetLine(int LineNo) {
3270 Line line = document;
3272 while (line != sentinel) {
3273 if (LineNo == line.line_no) {
3275 } else if (LineNo < line.line_no) {
3285 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3286 internal LineTag PreviousTag(LineTag tag) {
3289 if (tag.previous != null) {
3290 return tag.previous;
3294 if (tag.line.line_no == 1) {
3298 l = GetLine(tag.line.line_no - 1);
3303 while (t.next != null) {
3312 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3313 internal LineTag NextTag(LineTag tag) {
3316 if (tag.next != null) {
3321 l = GetLine(tag.line.line_no + 1);
3329 internal Line ParagraphStart(Line line) {
3330 while (line.soft_break) {
3331 line = GetLine(line.line_no - 1);
3336 internal Line ParagraphEnd(Line line) {
3339 while (line.soft_break) {
3340 l = GetLine(line.line_no + 1);
3341 if ((l == null) || (!l.soft_break)) {
3349 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3350 internal Line GetLineByPixel(int y, bool exact) {
3351 Line line = document;
3354 while (line != sentinel) {
3356 if ((y >= line.Y) && (y < (line.Y+line.height))) {
3358 } else if (y < line.Y) {
3371 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3372 internal LineTag FindTag(int x, int y, out int index, bool exact) {
3376 line = GetLineByPixel(y, exact);
3383 // Alignment adjustment
3384 x += line.align_shift;
3387 if (x >= tag.X && x < (tag.X+tag.width)) {
3390 end = tag.start + tag.length - 1;
3392 for (int pos = tag.start; pos < end; pos++) {
3393 if (x < line.widths[pos]) {
3401 if (tag.next != null) {
3409 index = line.text.Length;
3415 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3416 internal LineTag FindCursor(int x, int y, out int index) {
3420 line = GetLineByPixel(y, false);
3423 // Adjust for alignment
3424 x += line.align_shift;
3427 if (x >= tag.X && x < (tag.X+tag.width)) {
3430 end = tag.start + tag.length - 1;
3432 for (int pos = tag.start-1; pos < end; pos++) {
3433 // When clicking on a character, we position the cursor to whatever edge
3434 // of the character the click was closer
3435 if (x < (line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
3443 if (tag.next != null) {
3446 index = line.text.Length;
3452 /// <summary>Format area of document in specified font and color</summary>
3453 /// <param name="start_pos">1-based start position on start_line</param>
3454 /// <param name="end_pos">1-based end position on end_line </param>
3455 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, Font font, Brush color) {
3458 // First, format the first line
3459 if (start_line != end_line) {
3461 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, font, color);
3464 LineTag.FormatText(end_line, 1, end_pos, font, color);
3466 // Now all the lines inbetween
3467 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3469 LineTag.FormatText(l, 1, l.text.Length, font, color);
3472 // Special case, single line
3473 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color);
3477 /// <summary>Re-format areas of the document in specified font and color</summary>
3478 /// <param name="start_pos">1-based start position on start_line</param>
3479 /// <param name="end_pos">1-based end position on end_line </param>
3480 /// <param name="font">Font specifying attributes</param>
3481 /// <param name="color">Color (or NULL) to apply</param>
3482 /// <param name="apply">Attributes from font and color to apply</param>
3483 internal void FormatText(Line start_line, int start_pos, Line end_line, int end_pos, FontDefinition attributes) {
3486 // First, format the first line
3487 if (start_line != end_line) {
3489 LineTag.FormatText(start_line, start_pos, start_line.text.Length - start_pos + 1, attributes);
3492 LineTag.FormatText(end_line, 1, end_pos - 1, attributes);
3494 // Now all the lines inbetween
3495 for (int i = start_line.line_no + 1; i < end_line.line_no; i++) {
3497 LineTag.FormatText(l, 1, l.text.Length, attributes);
3500 // Special case, single line
3501 LineTag.FormatText(start_line, start_pos, end_pos - start_pos, attributes);
3505 internal void RecalculateAlignments() {
3511 while (line_no <= lines) {
3512 line = GetLine(line_no);
3514 if (line != null && line.alignment != HorizontalAlignment.Left) {
3515 if (line.alignment == HorizontalAlignment.Center) {
3516 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3518 line.align_shift = viewport_width - (int)line.widths[line.text.Length];
3527 /// <summary>Calculate formatting for the whole document</summary>
3528 internal bool RecalculateDocument(Graphics g) {
3529 return RecalculateDocument(g, 1, this.lines, false);
3532 /// <summary>Calculate formatting starting at a certain line</summary>
3533 internal bool RecalculateDocument(Graphics g, int start) {
3534 return RecalculateDocument(g, start, this.lines, false);
3537 /// <summary>Calculate formatting within two given line numbers</summary>
3538 internal bool RecalculateDocument(Graphics g, int start, int end) {
3539 return RecalculateDocument(g, start, end, false);
3542 /// <summary>With optimize on, returns true if line heights changed</summary>
3543 internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
3552 recalc_pending = true;
3553 recalc_start = start;
3555 recalc_optimize = optimize;
3559 Y = GetLine(start).Y;
3564 changed = true; // We always return true if we run non-optimized
3569 while (line_no <= (end + this.lines - shift)) {
3570 line = GetLine(line_no++);
3575 line.RecalculateLine(g, this);
3577 if (line.recalc && line.RecalculateLine(g, this)) {
3579 // If the height changed, all subsequent lines change
3586 line.RecalculatePasswordLine(g, this);
3588 if (line.recalc && line.RecalculatePasswordLine(g, this)) {
3590 // If the height changed, all subsequent lines change
3597 if (line.widths[line.text.Length] > new_width) {
3598 new_width = (int)line.widths[line.text.Length];
3601 // Calculate alignment
3602 if (line.alignment != HorizontalAlignment.Left) {
3603 if (line.alignment == HorizontalAlignment.Center) {
3604 line.align_shift = (viewport_width - (int)line.widths[line.text.Length]) / 2;
3606 line.align_shift = viewport_width - (int)line.widths[line.text.Length];
3612 if (line_no > lines) {
3617 if (document_x != new_width) {
3618 document_x = new_width;
3619 if (WidthChanged != null) {
3620 WidthChanged(this, null);
3624 RecalculateAlignments();
3626 line = GetLine(lines);
3628 if (document_y != line.Y + line.height) {
3629 document_y = line.Y + line.height;
3630 if (HeightChanged != null) {
3631 HeightChanged(this, null);
3638 internal int Size() {
3642 private void owner_HandleCreated(object sender, EventArgs e) {
3643 RecalculateDocument(owner.CreateGraphicsInternal());
3644 PositionCaret(0, 0);
3647 private void owner_VisibleChanged(object sender, EventArgs e) {
3648 if (owner.Visible) {
3649 RecalculateDocument(owner.CreateGraphicsInternal());
3653 internal static bool IsWordSeparator(char ch) {
3667 internal int FindWordSeparator(Line line, int pos, bool forward) {
3670 len = line.text.Length;
3673 for (int i = pos + 1; i < len; i++) {
3674 if (IsWordSeparator(line.Text[i])) {
3680 for (int i = pos - 1; i > 0; i--) {
3681 if (IsWordSeparator(line.Text[i - 1])) {
3689 /* Search document for text */
3690 internal bool FindChars(char[] chars, Marker start, Marker end, out Marker result) {
3696 // Search for occurence of any char in the chars array
3697 result = new Marker();
3700 line_no = start.line.line_no;
3702 while (line_no <= end.line.line_no) {
3703 line_len = line.text.Length;
3704 while (pos < line_len) {
3705 for (int i = 0; i < chars.Length; i++) {
3706 if (line.text[pos] == chars[i]) {
3708 if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3722 line = GetLine(line_no);
3728 // This version does not build one big string for searching, instead it handles
3729 // line-boundaries, which is faster and less memory intensive
3730 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3731 // search stuff and change it to accept and return positions instead of Markers (which would match
3732 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3733 internal bool Find(string search, Marker start, Marker end, out Marker result, RichTextBoxFinds options) {
3735 string search_string;
3747 result = new Marker();
3748 word_option = ((options & RichTextBoxFinds.WholeWord) != 0);
3749 ignore_case = ((options & RichTextBoxFinds.MatchCase) == 0);
3750 reverse = ((options & RichTextBoxFinds.Reverse) != 0);
3753 line_no = start.line.line_no;
3757 // Prep our search string, lowercasing it if we do case-independent matching
3760 sb = new StringBuilder(search);
3761 for (int i = 0; i < sb.Length; i++) {
3762 sb[i] = Char.ToLower(sb[i]);
3764 search_string = sb.ToString();
3766 search_string = search;
3769 // We need to check if the character before our start position is a wordbreak
3772 if ((pos == 0) || (IsWordSeparator(line.text[pos - 1]))) {
3779 if (IsWordSeparator(line.text[pos - 1])) {
3785 // Need to check the end of the previous line
3788 prev_line = GetLine(line_no - 1);
3789 if (prev_line.soft_break) {
3790 if (IsWordSeparator(prev_line.text[prev_line.text.Length - 1])) {
3804 // To avoid duplication of this loop with reverse logic, we search
3805 // through the document, remembering the last match and when returning
3806 // report that last remembered match
3808 last = new Marker();
3809 last.height = -1; // Abused - we use it to track change
3811 while (line_no <= end.line.line_no) {
3812 if (line_no != end.line.line_no) {
3813 line_len = line.text.Length;
3818 while (pos < line_len) {
3819 if (word_option && (current == search_string.Length)) {
3820 if (IsWordSeparator(line.text[pos])) {
3833 c = Char.ToLower(line.text[pos]);
3838 if (c == search_string[current]) {
3843 if (!word_option || (word_option && (word || (current > 0)))) {
3847 if (!word_option && (current == search_string.Length)) {
3864 if (IsWordSeparator(c)) {
3872 // Mark that we just saw a word boundary
3873 if (!line.soft_break) {
3877 if (current == search_string.Length) {
3893 line = GetLine(line_no);
3897 if (last.height != -1) {
3907 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
3919 internal void GetMarker(out Marker mark, bool start) {
3920 mark = new Marker();
3923 mark.line = GetLine(1);
3924 mark.tag = mark.line.tags;
3927 mark.line = GetLine(lines);
3928 mark.tag = mark.line.tags;
3929 while (mark.tag.next != null) {
3930 mark.tag = mark.tag.next;
3932 mark.pos = mark.line.text.Length;
3935 #endregion // Internal Methods
3938 internal event EventHandler CaretMoved;
3939 internal event EventHandler WidthChanged;
3940 internal event EventHandler HeightChanged;
3941 internal event EventHandler LengthChanged;
3942 #endregion // Events
3944 #region Administrative
3945 public IEnumerator GetEnumerator() {
3950 public override bool Equals(object obj) {
3955 if (!(obj is Document)) {
3963 if (ToString().Equals(((Document)obj).ToString())) {
3970 public override int GetHashCode() {
3974 public override string ToString() {
3975 return "document " + this.document_id;
3977 #endregion // Administrative
3980 internal class LineTag {
3981 #region Local Variables;
3982 // Payload; formatting
3983 internal Font font; // System.Drawing.Font object for this tag
3984 internal Brush color; // System.Drawing.Brush object
3987 internal int start; // start, in chars; index into Line.text
3988 internal int length; // length, in chars
3989 internal bool r_to_l; // Which way is the font
3992 internal int height; // Height in pixels of the text this tag describes
3993 internal int X; // X location of the text this tag describes
3994 internal float width; // Width in pixels of the text this tag describes
3995 internal int ascent; // Ascent of the font for this tag
3996 internal int shift; // Shift down for this tag, to stay on baseline
3999 internal Line line; // The line we're on
4000 internal LineTag next; // Next tag on the same line
4001 internal LineTag previous; // Previous tag on the same line
4004 #region Constructors
4005 internal LineTag(Line line, int start, int length) {
4008 this.length = length;
4012 #endregion // Constructors
4014 #region Internal Methods
4015 ///<summary>Break a tag into two with identical attributes; pos is 1-based; returns tag starting at >pos< or null if end-of-line</summary>
4016 internal LineTag Break(int pos) {
4020 if (pos == this.start) {
4022 } else if (pos >= (start + length)) {
4026 new_tag = new LineTag(line, pos, start + length - pos);
4027 new_tag.color = color;
4028 new_tag.font = font;
4029 this.length -= new_tag.length;
4030 new_tag.next = this.next;
4031 this.next = new_tag;
4032 new_tag.previous = this;
4033 if (new_tag.next != null) {
4034 new_tag.next.previous = new_tag;
4040 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4041 internal static bool GenerateTextFormat(Font font_from, Brush color_from, FontDefinition attributes, out Font new_font, out Brush new_color) {
4047 if (attributes.font_obj == null) {
4048 size = font_from.SizeInPoints;
4049 unit = font_from.Unit;
4050 face = font_from.Name;
4051 style = font_from.Style;
4053 if (attributes.face != null) {
4054 face = attributes.face;
4057 if (attributes.size != 0) {
4058 size = attributes.size;
4061 style |= attributes.add_style;
4062 style &= ~attributes.remove_style;
4065 new_font = new Font(face, size, style, unit);
4067 new_font = attributes.font_obj;
4070 // Create 'new' color brush
4071 if (attributes.color != Color.Empty) {
4072 new_color = new SolidBrush(attributes.color);
4074 new_color = color_from;
4077 if (new_font.Height == font_from.Height) {
4083 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4084 /// Removes any previous tags overlapping the same area;
4085 /// returns true if lineheight has changed</summary>
4086 /// <param name="start">1-based character position on line</param>
4087 internal static bool FormatText(Line line, int start, int length, Font font, Brush color) {
4091 bool retval = false; // Assume line-height doesn't change
4094 if (font.Height != line.height) {
4097 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4099 // A little sanity, not sure if it's needed, might be able to remove for speed
4100 if (length > line.text.Length) {
4101 length = line.text.Length;
4105 end = start + length;
4107 // Common special case
4108 if ((start == 1) && (length == tag.length)) {
4115 //Console.WriteLine("Finding tag for {0} {1}", line, start);
4116 start_tag = FindTag(line, start);
4117 if (start_tag == null) { // FIXME - is there a better way to handle this, or do we even need it?
4118 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4121 tag = new LineTag(line, start, length);
4128 //Console.WriteLine("Start tag: '{0}'", start_tag!=null ? start_tag.ToString() : "NULL");
4129 if (start_tag.start == start) {
4130 tag.next = start_tag;
4131 tag.previous = start_tag.previous;
4132 if (start_tag.previous != null) {
4133 start_tag.previous.next = tag;
4135 start_tag.previous = tag;
4137 // Insert ourselves 'in the middle'
4138 if ((start_tag.next != null) && (start_tag.next.start < end)) {
4139 tag.next = start_tag.next;
4141 tag.next = new LineTag(line, start_tag.start, start_tag.length);
4142 tag.next.font = start_tag.font;
4143 tag.next.color = start_tag.color;
4145 if (start_tag.next != null) {
4146 tag.next.next = start_tag.next;
4147 tag.next.next.previous = tag.next;
4150 tag.next.previous = tag;
4152 start_tag.length = start - start_tag.start;
4154 tag.previous = start_tag;
4155 start_tag.next = tag;
4157 if (tag.next.start > (tag.start + tag.length)) {
4158 tag.next.length += tag.next.start - (tag.start + tag.length);
4159 tag.next.start = tag.start + tag.length;
4166 while ((tag != null) && (tag.start < end)) {
4167 if ((tag.start + tag.length) <= end) {
4169 tag.previous.next = tag.next;
4170 if (tag.next != null) {
4171 tag.next.previous = tag.previous;
4175 // Adjust the length of the tag
4176 tag.length = (tag.start + tag.length) - end;
4185 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4186 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4187 /// Returns true if lineheight has changed</summary>
4188 /// <param name="start">1-based character position on line</param>
4189 internal static bool FormatText(Line line, int start, int length, FontDefinition attributes) {
4193 bool retval = false; // Assume line-height doesn't change
4195 line.recalc = true; // This forces recalculation of the line in RecalculateDocument
4197 // A little sanity, not sure if it's needed, might be able to remove for speed
4198 if (length > line.text.Length) {
4199 length = line.text.Length;
4204 // Common special case
4205 if ((start == 1) && (length == tag.length)) {
4207 GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color);
4211 start_tag = FindTag(line, start);
4213 if (start_tag == null) {
4215 // We are 'starting' after all valid tags; create a new tag with the right attributes
4216 start_tag = FindTag(line, line.text.Length - 1);
4217 start_tag.next = new LineTag(line, line.text.Length + 1, 0);
4218 start_tag.next.font = start_tag.font;
4219 start_tag.next.color = start_tag.color;
4220 start_tag.next.previous = start_tag;
4221 start_tag = start_tag.next;
4223 throw new Exception(String.Format("Could not find start_tag in document at line {0} position {1}", line.line_no, start));
4226 start_tag = start_tag.Break(start);
4229 end_tag = FindTag(line, start + length);
4230 if (end_tag != null) {
4231 end_tag = end_tag.Break(start + length);
4234 // start_tag or end_tag might be null; we're cool with that
4235 // we now walk from start_tag to end_tag, applying new attributes
4237 while ((tag != null) && tag != end_tag) {
4238 if (LineTag.GenerateTextFormat(tag.font, tag.color, attributes, out tag.font, out tag.color)) {
4247 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4248 internal static LineTag FindTag(Line line, int pos) {
4249 LineTag tag = line.tags;
4251 // Beginning of line is a bit special
4256 while (tag != null) {
4257 if ((tag.start <= pos) && (pos < (tag.start+tag.length))) {
4267 /// <summary>Combines 'this' tag with 'other' tag</summary>
4268 internal bool Combine(LineTag other) {
4269 if (!this.Equals(other)) {
4273 this.width += other.width;
4274 this.length += other.length;
4275 this.next = other.next;
4276 if (this.next != null) {
4277 this.next.previous = this;
4284 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4285 internal bool Remove() {
4286 if ((this.start == 1) && (this.next == null)) {
4287 // We cannot remove the only tag
4290 if (this.start != 1) {
4291 this.previous.length += this.length;
4292 this.previous.width = -1;
4293 this.previous.next = this.next;
4294 this.next.previous = this.previous;
4296 this.next.start = 1;
4297 this.next.length += this.length;
4298 this.next.width = -1;
4299 this.line.tags = this.next;
4300 this.next.previous = null;
4306 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4307 public override bool Equals(object obj) {
4314 if (!(obj is LineTag)) {
4322 other = (LineTag)obj;
4324 if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
4331 public override int GetHashCode() {
4332 return base.GetHashCode ();
4335 public override string ToString() {
4336 return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
4339 #endregion // Internal Methods
4342 internal class UndoClass {
4343 internal enum ActionType {
4352 internal class Action {
4353 internal ActionType type;
4354 internal int line_no;
4356 internal object data;
4359 #region Local Variables
4360 private Document document;
4361 private Stack undo_actions;
4362 private Stack redo_actions;
4363 private int caret_line;
4364 private int caret_pos;
4365 #endregion // Local Variables
4367 #region Constructors
4368 internal UndoClass(Document doc) {
4370 undo_actions = new Stack(50);
4371 redo_actions = new Stack(50);
4373 #endregion // Constructors
4376 [MonoTODO("Change this to be configurable")]
4377 internal int UndoLevels {
4379 return undo_actions.Count;
4383 [MonoTODO("Change this to be configurable")]
4384 internal int RedoLevels {
4386 return redo_actions.Count;
4390 [MonoTODO("Come up with good naming and localization")]
4391 internal string UndoName {
4395 action = (Action)undo_actions.Peek();
4396 switch(action.type) {
4397 case ActionType.InsertChar: {
4398 Locale.GetText("Insert character");
4402 case ActionType.DeleteChar: {
4403 Locale.GetText("Delete character");
4407 case ActionType.InsertString: {
4408 Locale.GetText("Insert string");
4412 case ActionType.DeleteChars: {
4413 Locale.GetText("Delete string");
4417 case ActionType.CursorMove: {
4418 Locale.GetText("Cursor move");
4426 internal string RedoName() {
4429 #endregion // Properties
4431 #region Internal Methods
4432 internal void Clear() {
4433 undo_actions.Clear();
4434 redo_actions.Clear();
4437 internal void Undo() {
4440 if (undo_actions.Count == 0) {
4444 action = (Action)undo_actions.Pop();
4446 // Put onto redo stack
4447 redo_actions.Push(action);
4450 switch(action.type) {
4451 case ActionType.InsertChar: {
4455 case ActionType.DeleteChars: {
4456 this.Insert(document.GetLine(action.line_no), action.pos, (Line)action.data);
4460 case ActionType.CursorMove: {
4461 document.caret.line = document.GetLine(action.line_no);
4462 document.caret.tag = document.caret.line.FindTag(action.pos);
4463 document.caret.pos = action.pos;
4464 document.caret.height = document.caret.tag.height;
4466 if (document.owner.IsHandleCreated) {
4467 XplatUI.DestroyCaret(document.owner.Handle);
4468 XplatUI.CreateCaret(document.owner.Handle, 2, document.caret.height);
4469 XplatUI.SetCaretPos(document.owner.Handle, (int)document.caret.tag.line.widths[document.caret.pos] + document.caret.line.align_shift - document.viewport_x, document.caret.line.Y + document.caret.tag.shift - document.viewport_y + Document.caret_shift);
4470 XplatUI.CaretVisible(document.owner.Handle, true);
4473 // FIXME - enable call
4474 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4480 internal void Redo() {
4481 if (redo_actions.Count == 0) {
4485 #endregion // Internal Methods
4487 #region Private Methods
4489 public void RecordDeleteChars(Line line, int pos, int length) {
4490 RecordDelete(line, pos, line, pos + length - 1);
4493 // start_pos, end_pos = 1 based
4494 public void RecordDelete(Line start_line, int start_pos, Line end_line, int end_pos) {
4498 l = Duplicate(start_line, start_pos, end_line, end_pos);
4501 a.type = ActionType.DeleteChars;
4503 a.line_no = start_line.line_no;
4504 a.pos = start_pos - 1;
4506 undo_actions.Push(a);
4509 public void RecordCursor() {
4510 if (document.caret.line == null) {
4514 RecordCursor(document.caret.line, document.caret.pos);
4517 public void RecordCursor(Line line, int pos) {
4520 if ((line.line_no == caret_line) && (pos == caret_pos)) {
4524 caret_line = line.line_no;
4528 a.type = ActionType.CursorMove;
4529 a.line_no = line.line_no;
4532 undo_actions.Push(a);
4535 // start_pos = 1-based
4536 // end_pos = 1-based
4537 public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) {
4542 LineTag current_tag;
4551 for (int i = start_line.line_no; i <= end_line.line_no; i++) {
4552 current = document.GetLine(i);
4554 if (start_line.line_no == i) {
4560 if (end_line.line_no == i) {
4563 end = current.text.Length;
4567 line.text = new StringBuilder(current.text.ToString(start - 1, end - start + 1));
4569 // Copy tags from start to start+length onto new line
4570 current_tag = current.FindTag(start - 1);
4571 while ((current_tag != null) && (current_tag.start < end)) {
4572 if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
4573 // start tag is within this tag
4576 tag_start = current_tag.start;
4579 if (end < (current_tag.start + current_tag.length)) {
4580 tag_length = end - tag_start + 1;
4582 tag_length = current_tag.start + current_tag.length - tag_start;
4584 tag = new LineTag(line, tag_start - start + 1, tag_length);
4585 tag.color = current_tag.color;
4586 tag.font = current_tag.font;
4588 current_tag = current_tag.next;
4590 // Add the new tag to the line
4591 if (line.tags == null) {
4597 while (tail.next != null) {
4601 tag.previous = tail;
4605 if ((i + 1) <= end_line.line_no) {
4606 line.soft_break = current.soft_break;
4608 // Chain them (we use right/left as next/previous)
4609 line.right = new Line();
4610 line.right.left = line;
4618 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4619 internal void Insert(Line line, int pos, Line insert) {
4626 // Handle special case first
4627 if (insert.right == null) {
4629 // Single line insert
4630 document.Split(line, pos);
4632 if (insert.tags == null) {
4633 return; // Blank line
4636 //Insert our tags at the end
4639 while (tag.next != null) {
4643 offset = tag.start + tag.length - 1;
4645 tag.next = insert.tags;
4646 line.text.Insert(offset, insert.text.ToString());
4648 // Adjust start locations
4650 while (tag != null) {
4651 tag.start += offset;
4655 // Put it back together
4656 document.Combine(line.line_no, line.line_no + 1);
4657 document.UpdateView(line, pos);
4664 while (current != null) {
4665 if (current == insert) {
4666 // Inserting the first line we split the line (and make space)
4667 document.Split(line, pos);
4668 //Insert our tags at the end of the line
4672 while (tag.next != null) {
4675 offset = tag.start + tag.length - 1;
4676 tag.next = current.tags;
4677 tag.next.previous = tag;
4683 line.tags = current.tags;
4684 line.tags.previous = null;
4688 document.Split(line.line_no, 0);
4690 line.tags = current.tags;
4691 line.tags.previous = null;
4694 // Adjust start locations and line pointers
4695 while (tag != null) {
4696 tag.start += offset;
4701 line.text.Insert(offset, current.text.ToString());
4702 line.Grow(line.text.Length);
4705 line = document.GetLine(line.line_no + 1);
4707 // FIXME? Test undo of line-boundaries
4708 if ((current.right == null) && (current.tags.length != 0)) {
4709 document.Combine(line.line_no - 1, line.line_no);
4711 current = current.right;
4716 // Recalculate our document
4717 document.UpdateView(first, lines, pos);
4720 #endregion // Private Methods