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
;
76 internal enum FormatSpecified
{
84 internal enum CaretDirection
{
85 CharForward
, // Move a char to the right
86 CharBack
, // Move a char to the left
87 LineUp
, // Move a line up
88 LineDown
, // Move a line down
89 Home
, // Move to the beginning of the line
90 End
, // Move to the end of the line
91 PgUp
, // Move one page up
92 PgDn
, // Move one page down
93 CtrlPgUp
, // Move caret to the first visible char in the viewport
94 CtrlPgDn
, // Move caret to the last visible char in the viewport
95 CtrlHome
, // Move to the beginning of the document
96 CtrlEnd
, // Move to the end of the document
97 WordBack
, // Move to the beginning of the previous word (or beginning of line)
98 WordForward
, // Move to the beginning of the next word (or end of line)
99 SelectionStart
, // Move to the beginning of the current selection
100 SelectionEnd
, // Move to the end of the current selection
101 CharForwardNoWrap
, // Move a char forward, but don't wrap onto the next line
102 CharBackNoWrap
// Move a char backward, but don't wrap onto the previous line
105 // Being cloneable should allow for nice line and document copies...
106 internal class Line
: ICloneable
, IComparable
{
107 #region Local Variables
108 // Stuff that matters for our line
109 internal StringBuilder text
; // Characters for the line
110 internal float[] widths
; // Width of each character; always one larger than text.Length
111 internal int space
; // Number of elements in text and widths
112 internal int line_no
; // Line number
113 internal LineTag tags
; // Tags describing the text
114 internal int Y
; // Baseline
115 internal int height
; // Height of the line (height of tallest tag)
116 internal int ascent
; // Ascent of the line (ascent of the tallest tag)
117 internal HorizontalAlignment alignment
; // Alignment of the line
118 internal int align_shift
; // Pixel shift caused by the alignment
119 internal bool soft_break
; // Tag is 'broken soft' and continuation from previous line
120 internal int indent
; // Left indent for the first line
121 internal int hanging_indent
; // Hanging indent (left indent for all but the first line)
122 internal int right_indent
; // Right indent for all lines
123 internal bool carriage_return
;
126 // Stuff that's important for the tree
127 internal Line parent
; // Our parent line
128 internal Line left
; // Line with smaller line number
129 internal Line right
; // Line with higher line number
130 internal LineColor color
; // We're doing a black/red tree. this is the node color
131 internal int DEFAULT_TEXT_LEN
; //
132 internal bool recalc
; // Line changed
133 #endregion // Local Variables
137 color
= LineColor
.Red
;
144 alignment
= HorizontalAlignment
.Left
;
147 internal Line(int LineNo
, string Text
, Font font
, SolidBrush color
) : this() {
148 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
150 text
= new StringBuilder(Text
, space
);
153 widths
= new float[space
+ 1];
154 tags
= new LineTag(this, 1);
159 internal Line(int LineNo
, string Text
, HorizontalAlignment align
, Font font
, SolidBrush color
) : this() {
160 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
162 text
= new StringBuilder(Text
, space
);
166 widths
= new float[space
+ 1];
167 tags
= new LineTag(this, 1);
172 internal Line(int LineNo
, string Text
, LineTag tag
) : this() {
173 space
= Text
.Length
> DEFAULT_TEXT_LEN
? Text
.Length
+1 : DEFAULT_TEXT_LEN
;
175 text
= new StringBuilder(Text
, space
);
178 widths
= new float[space
+ 1];
182 #endregion // Constructors
184 #region Internal Properties
185 internal int Indent
{
196 internal int HangingIndent
{
198 return hanging_indent
;
202 hanging_indent
= value;
207 internal int RightIndent
{
213 right_indent
= value;
219 internal int Height
{
229 internal int LineNo
{
239 internal string Text
{
241 return text
.ToString();
245 text
= new StringBuilder(value, value.Length
> DEFAULT_TEXT_LEN
? value.Length
: DEFAULT_TEXT_LEN
);
249 internal HorizontalAlignment Alignment
{
255 if (alignment
!= value) {
262 internal StringBuilder Text
{
272 #endregion // Internal Properties
274 #region Internal Methods
275 // Make sure we always have enoughs space in text and widths
276 internal void Grow(int minimum
) {
280 length
= text
.Length
;
282 if ((length
+ minimum
) > space
) {
283 // We need to grow; double the size
285 if ((length
+ minimum
) > (space
* 2)) {
286 new_widths
= new float[length
+ minimum
* 2 + 1];
287 space
= length
+ minimum
* 2;
289 new_widths
= new float[space
* 2 + 1];
292 widths
.CopyTo(new_widths
, 0);
298 internal void Streamline(int lines
) {
305 // Catch what the loop below wont; eliminate 0 length
306 // tags, but only if there are other tags after us
307 while ((current
.length
== 0) && (next
!= null)) {
309 tags
.previous
= null;
318 while (next
!= null) {
319 // Take out 0 length tags unless it's the last tag in the document
320 if (next
.length
== 0) {
321 if ((next
.next
!= null) || (line_no
!= lines
)) {
322 current
.next
= next
.next
;
323 if (current
.next
!= null) {
324 current
.next
.previous
= current
;
330 if (current
.Combine(next
)) {
335 current
= current
.next
;
340 /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
341 internal LineTag
FindTag(int pos
) {
350 if (pos
>= text
.Length
) {
351 pos
= text
.Length
- 1;
354 while (tag
!= null) {
355 if (((tag
.start
- 1) <= pos
) && (pos
< (tag
.start
+ tag
.length
- 1))) {
356 return LineTag
.GetFinalTag (tag
);
364 /// Recalculate a single line using the same char for every character in the line
367 internal bool RecalculatePasswordLine(Graphics g
, Document doc
) {
376 len
= this.text
.Length
;
385 w
= g
.MeasureString(doc
.password_char
, tags
.font
, 10000, Document
.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
;
401 widths
[pos
] = widths
[pos
-1] + w
;
408 /// Go through all tags on a line and recalculate all size-related values;
409 /// returns true if lineheight changed
411 internal bool RecalculateLine(Graphics g
, Document doc
) {
424 len
= this.text
.Length
;
426 prev_height
= this.height
; // For drawing optimization calculations
427 this.height
= 0; // Reset line height
428 this.ascent
= 0; // Reset the ascent for the line
431 if (this.soft_break
) {
432 widths
[0] = hanging_indent
;
445 while (tag
.length
== 0) { // We should always have tags after a tag.length==0 unless len==0
447 if (tag
.previous
!= null) {
448 tag
.X
= tag
.previous
.X
;
450 tag
.X
= (int)widths
[pos
];
456 size
= tag
.SizeOfPosition (g
, pos
);
459 if (Char
.IsWhiteSpace(text
[pos
])) {
464 if ((wrap_pos
> 0) && (wrap_pos
!= len
) && (widths
[pos
] + w
) + 5 > (doc
.viewport_width
- this.right_indent
)) {
465 // Make sure to set the last width of the line before wrapping
466 widths
[pos
+ 1] = widths
[pos
] + w
;
470 doc
.Split(this, tag
, pos
, this.soft_break
);
471 this.soft_break
= true;
472 len
= this.text
.Length
;
476 } else if (pos
> 1 && (widths
[pos
] + w
) > (doc
.viewport_width
- this.right_indent
)) {
477 // No suitable wrap position was found so break right in the middle of a word
479 // Make sure to set the last width of the line before wrapping
480 widths
[pos
+ 1] = widths
[pos
] + w
;
482 doc
.Split(this, tag
, pos
, this.soft_break
);
483 this.soft_break
= true;
484 len
= this.text
.Length
;
490 // Contract all soft lines that follow back into our line
494 widths
[pos
] = widths
[pos
-1] + w
;
497 line
= doc
.GetLine(this.line_no
+ 1);
498 if ((line
!= null) && soft_break
) {
499 // Pull the two lines together
500 doc
.Combine(this.line_no
, this.line_no
+ 1);
501 len
= this.text
.Length
;
507 if (pos
== (tag
.start
-1 + tag
.length
)) {
508 // We just found the end of our current tag
509 tag
.height
= tag
.MaxHeight ();
511 // Check if we're the tallest on the line (so far)
512 if (tag
.height
> this.height
) {
513 this.height
= tag
.height
; // Yep; make sure the line knows
516 if (tag
.ascent
== 0) {
519 XplatUI
.GetFontMetrics(g
, tag
.font
, out tag
.ascent
, out descent
);
522 if (tag
.ascent
> this.ascent
) {
525 // We have a tag that has a taller ascent than the line;
527 while (t
!= null && t
!= tag
) {
528 t
.shift
= tag
.ascent
- t
.ascent
;
533 this.ascent
= tag
.ascent
;
535 tag
.shift
= this.ascent
- tag
.ascent
;
538 // Update our horizontal starting pixel position
539 if (tag
.previous
== null) {
540 tag
.X
= (int)widths
[0];
542 tag
.X
= tag
.previous
.X
+ (int)tag
.previous
.width
;
553 if (this.height
== 0) {
554 this.height
= tags
.font
.Height
;
555 tag
.height
= this.height
;
558 if (prev_height
!= this.height
) {
563 #endregion // Internal Methods
565 #region Administrative
566 public int CompareTo(object obj
) {
571 if (! (obj
is Line
)) {
572 throw new ArgumentException("Object is not of type Line", "obj");
575 if (line_no
< ((Line
)obj
).line_no
) {
577 } else if (line_no
> ((Line
)obj
).line_no
) {
584 public object Clone() {
592 clone
.left
= (Line
)left
.Clone();
596 clone
.left
= (Line
)left
.Clone();
602 internal object CloneLine() {
612 public override bool Equals(object obj
) {
617 if (!(obj
is Line
)) {
625 if (line_no
== ((Line
)obj
).line_no
) {
632 public override int GetHashCode() {
633 return base.GetHashCode ();
636 public override string ToString() {
637 return "Line " + line_no
;
640 #endregion // Administrative
643 internal class Document
: ICloneable
, IEnumerable
{
645 // FIXME - go through code and check for places where
646 // we do explicit comparisons instead of using the compare overloads
647 internal struct Marker
{
649 internal LineTag tag
;
653 public static bool operator<(Marker lhs
, Marker rhs
) {
654 if (lhs
.line
.line_no
< rhs
.line
.line_no
) {
658 if (lhs
.line
.line_no
== rhs
.line
.line_no
) {
659 if (lhs
.pos
< rhs
.pos
) {
666 public static bool operator>(Marker lhs
, Marker rhs
) {
667 if (lhs
.line
.line_no
> rhs
.line
.line_no
) {
671 if (lhs
.line
.line_no
== rhs
.line
.line_no
) {
672 if (lhs
.pos
> rhs
.pos
) {
679 public static bool operator==(Marker lhs
, Marker rhs
) {
680 if ((lhs
.line
.line_no
== rhs
.line
.line_no
) && (lhs
.pos
== rhs
.pos
)) {
686 public static bool operator!=(Marker lhs
, Marker rhs
) {
687 if ((lhs
.line
.line_no
!= rhs
.line
.line_no
) || (lhs
.pos
!= rhs
.pos
)) {
693 public void Combine(Line move_to_line
, int move_to_line_length
) {
695 pos
+= move_to_line_length
;
696 tag
= LineTag
.FindTag(line
, pos
);
699 // This is for future use, right now Document.Split does it by hand, with some added shortcut logic
700 public void Split(Line move_to_line
, int split_at
) {
703 tag
= LineTag
.FindTag(line
, pos
);
706 public override bool Equals(object obj
) {
707 return this==(Marker
)obj
;
710 public override int GetHashCode() {
711 return base.GetHashCode ();
714 public override string ToString() {
715 return "Marker Line " + line
+ ", Position " + pos
;
719 #endregion Structures
721 #region Local Variables
722 private Line document
;
724 private Line sentinel
;
725 private int document_id
;
726 private Random random
= new Random();
727 internal string password_char
;
728 private StringBuilder password_cache
;
729 private bool calc_pass
;
730 private int char_count
;
732 // For calculating widths/heights
733 public static readonly StringFormat string_format
= new StringFormat (StringFormat
.GenericTypographic
);
735 private int recalc_suspended
;
736 private bool recalc_pending
;
737 private int recalc_start
= 1; // This starts at one, since lines are 1 based
738 private int recalc_end
;
739 private bool recalc_optimize
;
741 internal bool multiline
;
744 internal UndoClass undo
;
746 internal Marker caret
;
747 internal Marker selection_start
;
748 internal Marker selection_end
;
749 internal bool selection_visible
;
750 internal Marker selection_anchor
;
751 internal Marker selection_prev
;
752 internal bool selection_end_anchor
;
754 internal int viewport_x
;
755 internal int viewport_y
; // The visible area of the document
756 internal int viewport_width
;
757 internal int viewport_height
;
759 internal int document_x
; // Width of the document
760 internal int document_y
; // Height of the document
762 internal Rectangle invalid
;
764 internal int crlf_size
; // 1 or 2, depending on whether we use \r\n or just \n
766 internal TextBoxBase owner
; // Who's owning us?
767 static internal int caret_width
= 1;
768 static internal int caret_shift
= 1;
769 #endregion // Local Variables
772 internal Document(TextBoxBase owner
) {
780 recalc_pending
= false;
782 // Tree related stuff
783 sentinel
= new Line();
784 sentinel
.color
= LineColor
.Black
;
788 // We always have a blank line
789 owner
.HandleCreated
+= new EventHandler(owner_HandleCreated
);
790 owner
.VisibleChanged
+= new EventHandler(owner_VisibleChanged
);
792 Add(1, "", owner
.Font
, ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.ForeColor
));
793 Line l
= GetLine (1);
796 undo
= new UndoClass(this);
798 selection_visible
= false;
799 selection_start
.line
= this.document
;
800 selection_start
.pos
= 0;
801 selection_start
.tag
= selection_start
.line
.tags
;
802 selection_end
.line
= this.document
;
803 selection_end
.pos
= 0;
804 selection_end
.tag
= selection_end
.line
.tags
;
805 selection_anchor
.line
= this.document
;
806 selection_anchor
.pos
= 0;
807 selection_anchor
.tag
= selection_anchor
.line
.tags
;
808 caret
.line
= this.document
;
810 caret
.tag
= caret
.line
.tags
;
817 // Default selection is empty
819 document_id
= random
.Next();
821 string_format
.Trimming
= StringTrimming
.None
;
822 string_format
.FormatFlags
= StringFormatFlags
.MeasureTrailingSpaces
;
826 #region Internal Properties
843 internal Line CaretLine
{
849 internal int CaretPosition
{
855 internal Point Caret
{
857 return new Point((int)caret
.tag
.line
.widths
[caret
.pos
] + caret
.line
.align_shift
, caret
.line
.Y
);
861 internal LineTag CaretTag
{
871 internal int CRLFSize
{
881 internal string PasswordChar
{
883 return password_char
;
887 password_char
= value;
888 if ((password_char
.Length
!= 0) && (password_char
[0] != '\0')) {
893 password_cache
= new StringBuilder(1024);
894 for (int i
= 0; i
< 1024; i
++) {
895 password_cache
.Append(ch
);
899 password_cache
= null;
904 internal int ViewPortX
{
914 internal int Length
{
916 return char_count
+ lines
- 1; // Add \n for each line but the last
920 private int CharCount
{
928 if (LengthChanged
!= null) {
929 LengthChanged(this, EventArgs
.Empty
);
934 internal int ViewPortY
{
944 internal int ViewPortWidth
{
946 return viewport_width
;
950 viewport_width
= value;
954 internal int ViewPortHeight
{
956 return viewport_height
;
960 viewport_height
= value;
967 return this.document_x
;
971 internal int Height
{
973 return this.document_y
;
977 internal bool SelectionVisible
{
979 return selection_visible
;
993 #endregion // Internal Properties
995 #region Private Methods
997 internal void SuspendRecalc ()
1002 internal void ResumeRecalc (bool immediate_update
)
1004 if (recalc_suspended
> 0)
1007 if (immediate_update
&& recalc_suspended
== 0 && recalc_pending
) {
1008 RecalculateDocument (owner
.CreateGraphicsInternal(), recalc_start
, recalc_end
, recalc_optimize
);
1009 recalc_pending
= false;
1014 internal int DumpTree(Line line
, bool with_tags
) {
1019 Console
.Write("Line {0} [# {1}], Y: {2}, soft: {3}, Text: '{4}'",
1020 line
.line_no
, line
.GetHashCode(), line
.Y
, line
.soft_break
,
1021 line
.text
!= null ? line
.text
.ToString() : "undefined");
1023 if (line
.left
== sentinel
) {
1024 Console
.Write(", left = sentinel");
1025 } else if (line
.left
== null) {
1026 Console
.Write(", left = NULL");
1029 if (line
.right
== sentinel
) {
1030 Console
.Write(", right = sentinel");
1031 } else if (line
.right
== null) {
1032 Console
.Write(", right = NULL");
1035 Console
.WriteLine("");
1045 Console
.Write(" Tags: ");
1046 while (tag
!= null) {
1047 Console
.Write("{0} <{1}>-<{2}>", count
++, tag
.start
, tag
.end
1048 /*line.text.ToString (tag.start - 1, tag.length)*/);
1049 length
+= tag
.length
;
1051 if (tag
.line
!= line
) {
1052 Console
.Write("BAD line link");
1053 throw new Exception("Bad line link in tree");
1057 Console
.Write(", ");
1060 if (length
> line
.text
.Length
) {
1061 throw new Exception(String
.Format("Length of tags more than length of text on line (expected {0} calculated {1})", line
.text
.Length
, length
));
1062 } else if (length
< line
.text
.Length
) {
1063 throw new Exception(String
.Format("Length of tags less than length of text on line (expected {0} calculated {1})", line
.text
.Length
, length
));
1065 Console
.WriteLine("");
1067 if (line
.left
!= null) {
1068 if (line
.left
!= sentinel
) {
1069 total
+= DumpTree(line
.left
, with_tags
);
1072 if (line
!= sentinel
) {
1073 throw new Exception("Left should not be NULL");
1077 if (line
.right
!= null) {
1078 if (line
.right
!= sentinel
) {
1079 total
+= DumpTree(line
.right
, with_tags
);
1082 if (line
!= sentinel
) {
1083 throw new Exception("Right should not be NULL");
1087 for (int i
= 1; i
<= this.lines
; i
++) {
1088 if (GetLine(i
) == null) {
1089 throw new Exception(String
.Format("Hole in line order, missing {0}", i
));
1093 if (line
== this.Root
) {
1094 if (total
< this.lines
) {
1095 throw new Exception(String
.Format("Not enough nodes in tree, found {0}, expected {1}", total
, this.lines
));
1096 } else if (total
> this.lines
) {
1097 throw new Exception(String
.Format("Too many nodes in tree, found {0}, expected {1}", total
, this.lines
));
1104 private void SetSelectionVisible (bool value)
1106 selection_visible
= value;
1108 // cursor and selection are enemies, we can't have both in the same room at the same time
1109 if (owner
.IsHandleCreated
&& !owner
.show_caret_w_selection
)
1110 XplatUI
.CaretVisible (owner
.Handle
, !selection_visible
);
1113 private void DecrementLines(int line_no
) {
1117 while (current
<= lines
) {
1118 GetLine(current
).line_no
--;
1124 private void IncrementLines(int line_no
) {
1127 current
= this.lines
;
1128 while (current
>= line_no
) {
1129 GetLine(current
).line_no
++;
1135 private void RebalanceAfterAdd(Line line1
) {
1138 while ((line1
!= document
) && (line1
.parent
.color
== LineColor
.Red
)) {
1139 if (line1
.parent
== line1
.parent
.parent
.left
) {
1140 line2
= line1
.parent
.parent
.right
;
1142 if ((line2
!= null) && (line2
.color
== LineColor
.Red
)) {
1143 line1
.parent
.color
= LineColor
.Black
;
1144 line2
.color
= LineColor
.Black
;
1145 line1
.parent
.parent
.color
= LineColor
.Red
;
1146 line1
= line1
.parent
.parent
;
1148 if (line1
== line1
.parent
.right
) {
1149 line1
= line1
.parent
;
1153 line1
.parent
.color
= LineColor
.Black
;
1154 line1
.parent
.parent
.color
= LineColor
.Red
;
1156 RotateRight(line1
.parent
.parent
);
1159 line2
= line1
.parent
.parent
.left
;
1161 if ((line2
!= null) && (line2
.color
== LineColor
.Red
)) {
1162 line1
.parent
.color
= LineColor
.Black
;
1163 line2
.color
= LineColor
.Black
;
1164 line1
.parent
.parent
.color
= LineColor
.Red
;
1165 line1
= line1
.parent
.parent
;
1167 if (line1
== line1
.parent
.left
) {
1168 line1
= line1
.parent
;
1172 line1
.parent
.color
= LineColor
.Black
;
1173 line1
.parent
.parent
.color
= LineColor
.Red
;
1174 RotateLeft(line1
.parent
.parent
);
1178 document
.color
= LineColor
.Black
;
1181 private void RebalanceAfterDelete(Line line1
) {
1184 while ((line1
!= document
) && (line1
.color
== LineColor
.Black
)) {
1185 if (line1
== line1
.parent
.left
) {
1186 line2
= line1
.parent
.right
;
1187 if (line2
.color
== LineColor
.Red
) {
1188 line2
.color
= LineColor
.Black
;
1189 line1
.parent
.color
= LineColor
.Red
;
1190 RotateLeft(line1
.parent
);
1191 line2
= line1
.parent
.right
;
1193 if ((line2
.left
.color
== LineColor
.Black
) && (line2
.right
.color
== LineColor
.Black
)) {
1194 line2
.color
= LineColor
.Red
;
1195 line1
= line1
.parent
;
1197 if (line2
.right
.color
== LineColor
.Black
) {
1198 line2
.left
.color
= LineColor
.Black
;
1199 line2
.color
= LineColor
.Red
;
1201 line2
= line1
.parent
.right
;
1203 line2
.color
= line1
.parent
.color
;
1204 line1
.parent
.color
= LineColor
.Black
;
1205 line2
.right
.color
= LineColor
.Black
;
1206 RotateLeft(line1
.parent
);
1210 line2
= line1
.parent
.left
;
1211 if (line2
.color
== LineColor
.Red
) {
1212 line2
.color
= LineColor
.Black
;
1213 line1
.parent
.color
= LineColor
.Red
;
1214 RotateRight(line1
.parent
);
1215 line2
= line1
.parent
.left
;
1217 if ((line2
.right
.color
== LineColor
.Black
) && (line2
.left
.color
== LineColor
.Black
)) {
1218 line2
.color
= LineColor
.Red
;
1219 line1
= line1
.parent
;
1221 if (line2
.left
.color
== LineColor
.Black
) {
1222 line2
.right
.color
= LineColor
.Black
;
1223 line2
.color
= LineColor
.Red
;
1225 line2
= line1
.parent
.left
;
1227 line2
.color
= line1
.parent
.color
;
1228 line1
.parent
.color
= LineColor
.Black
;
1229 line2
.left
.color
= LineColor
.Black
;
1230 RotateRight(line1
.parent
);
1235 line1
.color
= LineColor
.Black
;
1238 private void RotateLeft(Line line1
) {
1239 Line line2
= line1
.right
;
1241 line1
.right
= line2
.left
;
1243 if (line2
.left
!= sentinel
) {
1244 line2
.left
.parent
= line1
;
1247 if (line2
!= sentinel
) {
1248 line2
.parent
= line1
.parent
;
1251 if (line1
.parent
!= null) {
1252 if (line1
== line1
.parent
.left
) {
1253 line1
.parent
.left
= line2
;
1255 line1
.parent
.right
= line2
;
1262 if (line1
!= sentinel
) {
1263 line1
.parent
= line2
;
1267 private void RotateRight(Line line1
) {
1268 Line line2
= line1
.left
;
1270 line1
.left
= line2
.right
;
1272 if (line2
.right
!= sentinel
) {
1273 line2
.right
.parent
= line1
;
1276 if (line2
!= sentinel
) {
1277 line2
.parent
= line1
.parent
;
1280 if (line1
.parent
!= null) {
1281 if (line1
== line1
.parent
.right
) {
1282 line1
.parent
.right
= line2
;
1284 line1
.parent
.left
= line2
;
1290 line2
.right
= line1
;
1291 if (line1
!= sentinel
) {
1292 line1
.parent
= line2
;
1297 internal void UpdateView(Line line
, int pos
) {
1298 if (!owner
.IsHandleCreated
) {
1302 if (recalc_suspended
> 0) {
1303 recalc_start
= Math
.Min (recalc_start
, line
.line_no
);
1304 recalc_end
= Math
.Max (recalc_end
, line
.line_no
);
1305 recalc_optimize
= true;
1306 recalc_pending
= true;
1310 // Optimize invalidation based on Line alignment
1311 if (RecalculateDocument(owner
.CreateGraphicsInternal(), line
.line_no
, line
.line_no
, true)) {
1312 // Lineheight changed, invalidate the rest of the document
1313 if ((line
.Y
- viewport_y
) >=0 ) {
1314 // We formatted something that's in view, only draw parts of the screen
1315 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, owner
.Height
- line
.Y
- viewport_y
));
1317 // The tag was above the visible area, draw everything
1321 switch(line
.alignment
) {
1322 case HorizontalAlignment
.Left
: {
1323 owner
.Invalidate(new Rectangle((int)line
.widths
[pos
] - viewport_x
- 1, line
.Y
- viewport_y
, viewport_width
, line
.height
+ 1));
1327 case HorizontalAlignment
.Center
: {
1328 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, line
.height
+ 1));
1332 case HorizontalAlignment
.Right
: {
1333 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, (int)line
.widths
[pos
+ 1] - viewport_x
+ line
.align_shift
, line
.height
+ 1));
1341 // Update display from line, down line_count lines; pos is unused, but required for the signature
1342 internal void UpdateView(Line line
, int line_count
, int pos
) {
1343 if (!owner
.IsHandleCreated
) {
1347 if (recalc_suspended
> 0) {
1348 recalc_start
= Math
.Min (recalc_start
, line
.line_no
);
1349 recalc_end
= Math
.Max (recalc_end
, line
.line_no
+ line_count
- 1);
1350 recalc_optimize
= true;
1351 recalc_pending
= true;
1355 if (RecalculateDocument(owner
.CreateGraphicsInternal(), line
.line_no
, line
.line_no
+ line_count
- 1, true)) {
1356 // Lineheight changed, invalidate the rest of the document
1357 if ((line
.Y
- viewport_y
) >=0 ) {
1358 // We formatted something that's in view, only draw parts of the screen
1359 //blah Console.WriteLine("TextControl.cs(981) Invalidate called in UpdateView(line, line_count, pos)");
1360 owner
.Invalidate(new Rectangle(0, line
.Y
- viewport_y
, viewport_width
, owner
.Height
- line
.Y
- viewport_y
));
1362 // The tag was above the visible area, draw everything
1363 //blah Console.WriteLine("TextControl.cs(985) Invalidate called in UpdateView(line, line_count, pos)");
1369 end_line
= GetLine(line
.line_no
+ line_count
-1);
1370 if (end_line
== null) {
1374 //blah Console.WriteLine("TextControl.cs(996) Invalidate called in UpdateView(line, line_count, pos)");
1375 owner
.Invalidate(new Rectangle(0 - viewport_x
, line
.Y
- viewport_y
, (int)line
.widths
[line
.text
.Length
], end_line
.Y
+ end_line
.height
));
1378 #endregion // Private Methods
1380 #region Internal Methods
1381 // Clear the document and reset state
1382 internal void Empty() {
1384 document
= sentinel
;
1387 // We always have a blank line
1388 Add(1, "", owner
.Font
, ThemeEngine
.Current
.ResPool
.GetSolidBrush(owner
.ForeColor
));
1389 Line l
= GetLine (1);
1390 l
.soft_break
= true;
1392 this.RecalculateDocument(owner
.CreateGraphicsInternal());
1393 PositionCaret(0, 0);
1395 SetSelectionVisible (false);
1397 selection_start
.line
= this.document
;
1398 selection_start
.pos
= 0;
1399 selection_start
.tag
= selection_start
.line
.tags
;
1400 selection_end
.line
= this.document
;
1401 selection_end
.pos
= 0;
1402 selection_end
.tag
= selection_end
.line
.tags
;
1411 if (owner
.IsHandleCreated
)
1412 owner
.Invalidate ();
1415 internal void PositionCaret(Line line
, int pos
) {
1416 if (owner
.IsHandleCreated
) {
1417 undo
.RecordCursor();
1420 caret
.tag
= line
.FindTag(pos
);
1424 if (owner
.IsHandleCreated
) {
1425 if (owner
.Focused
) {
1426 if (caret
.height
!= caret
.tag
.height
)
1427 XplatUI
.CreateCaret (owner
.Handle
, caret_width
, caret
.height
);
1428 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
);
1431 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1434 // We set this at the end because we use the heights to determine whether or
1435 // not we need to recreate the caret
1436 caret
.height
= caret
.tag
.height
;
1440 internal void PositionCaret(int x
, int y
) {
1441 if (!owner
.IsHandleCreated
) {
1445 undo
.RecordCursor();
1447 caret
.tag
= FindCursor(x
, y
, out caret
.pos
);
1448 caret
.line
= caret
.tag
.line
;
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
);
1456 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1459 internal void CaretHasFocus() {
1460 if ((caret
.tag
!= null) && owner
.IsHandleCreated
) {
1461 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1462 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
);
1467 if (owner
.IsHandleCreated
&& selection_visible
) {
1468 InvalidateSelectionArea ();
1472 internal void CaretLostFocus() {
1473 if (!owner
.IsHandleCreated
) {
1476 XplatUI
.DestroyCaret(owner
.Handle
);
1479 internal void AlignCaret() {
1480 if (!owner
.IsHandleCreated
) {
1484 undo
.RecordCursor();
1486 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1487 caret
.height
= caret
.tag
.height
;
1489 if (owner
.Focused
) {
1490 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1491 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
);
1495 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1498 internal void UpdateCaret() {
1499 if (!owner
.IsHandleCreated
|| caret
.tag
== null) {
1503 undo
.RecordCursor();
1505 if (caret
.tag
.height
!= caret
.height
) {
1506 caret
.height
= caret
.tag
.height
;
1507 if (owner
.Focused
) {
1508 XplatUI
.CreateCaret(owner
.Handle
, caret_width
, caret
.height
);
1512 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
);
1516 if (CaretMoved
!= null) CaretMoved(this, EventArgs
.Empty
);
1519 internal void DisplayCaret() {
1520 if (!owner
.IsHandleCreated
) {
1524 if (owner
.Focused
&& (!selection_visible
|| owner
.show_caret_w_selection
)) {
1525 XplatUI
.CaretVisible(owner
.Handle
, true);
1529 internal void HideCaret() {
1530 if (!owner
.IsHandleCreated
) {
1534 if (owner
.Focused
) {
1535 XplatUI
.CaretVisible(owner
.Handle
, false);
1539 internal void MoveCaret(CaretDirection direction
) {
1540 // FIXME should we use IsWordSeparator to detect whitespace, instead
1541 // of looking for actual spaces in the Word move cases?
1543 bool nowrap
= false;
1545 case CaretDirection
.CharForwardNoWrap
:
1547 goto case CaretDirection
.CharForward
;
1548 case CaretDirection
.CharForward
: {
1550 if (caret
.pos
> caret
.line
.text
.Length
) {
1551 if (multiline
&& !nowrap
) {
1552 // Go into next line
1553 if (caret
.line
.line_no
< this.lines
) {
1554 caret
.line
= GetLine(caret
.line
.line_no
+1);
1556 caret
.tag
= caret
.line
.tags
;
1561 // Single line; we stay where we are
1565 if ((caret
.tag
.start
- 1 + caret
.tag
.length
) < caret
.pos
) {
1566 caret
.tag
= caret
.tag
.next
;
1573 case CaretDirection
.CharBackNoWrap
:
1575 goto case CaretDirection
.CharBack
;
1576 case CaretDirection
.CharBack
: {
1577 if (caret
.pos
> 0) {
1578 // caret.pos--; // folded into the if below
1579 if (--caret
.pos
> 0) {
1580 if (caret
.tag
.start
> caret
.pos
) {
1581 caret
.tag
= caret
.tag
.previous
;
1585 if (caret
.line
.line_no
> 1 && !nowrap
) {
1586 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1587 caret
.pos
= caret
.line
.text
.Length
;
1588 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1595 case CaretDirection
.WordForward
: {
1598 len
= caret
.line
.text
.Length
;
1599 if (caret
.pos
< len
) {
1600 while ((caret
.pos
< len
) && (caret
.line
.text
[caret
.pos
] != ' ')) {
1603 if (caret
.pos
< len
) {
1604 // Skip any whitespace
1605 while ((caret
.pos
< len
) && (caret
.line
.text
[caret
.pos
] == ' ')) {
1609 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1611 if (caret
.line
.line_no
< this.lines
) {
1612 caret
.line
= GetLine(caret
.line
.line_no
+ 1);
1614 caret
.tag
= caret
.line
.tags
;
1621 case CaretDirection
.WordBack
: {
1622 if (caret
.pos
> 0) {
1625 while ((caret
.pos
> 0) && (caret
.line
.text
[caret
.pos
] == ' ')) {
1629 while ((caret
.pos
> 0) && (caret
.line
.text
[caret
.pos
] != ' ')) {
1633 if (caret
.line
.text
.ToString(caret
.pos
, 1) == " ") {
1634 if (caret
.pos
!= 0) {
1637 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1638 caret
.pos
= caret
.line
.text
.Length
;
1641 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1643 if (caret
.line
.line_no
> 1) {
1644 caret
.line
= GetLine(caret
.line
.line_no
- 1);
1645 caret
.pos
= caret
.line
.text
.Length
;
1646 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1653 case CaretDirection
.LineUp
: {
1654 if (caret
.line
.line_no
> 1) {
1657 pixel
= (int)caret
.line
.widths
[caret
.pos
];
1658 PositionCaret(pixel
, GetLine(caret
.line
.line_no
- 1).Y
);
1665 case CaretDirection
.LineDown
: {
1666 if (caret
.line
.line_no
< lines
) {
1669 pixel
= (int)caret
.line
.widths
[caret
.pos
];
1670 PositionCaret(pixel
, GetLine(caret
.line
.line_no
+ 1).Y
);
1677 case CaretDirection
.Home
: {
1678 if (caret
.pos
> 0) {
1680 caret
.tag
= caret
.line
.tags
;
1686 case CaretDirection
.End
: {
1687 if (caret
.pos
< caret
.line
.text
.Length
) {
1688 caret
.pos
= caret
.line
.text
.Length
;
1689 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1695 case CaretDirection
.PgUp
: {
1697 int new_y
, y_offset
;
1699 if (viewport_y
== 0) {
1701 // This should probably be handled elsewhere
1702 if (!(owner
is RichTextBox
)) {
1703 // Page down doesn't do anything in a regular TextBox
1704 // if the bottom of the document
1705 // is already visible, the page and the caret stay still
1709 // We're just placing the caret at the end of the document, no scrolling needed
1710 owner
.vscroll
.Value
= 0;
1711 Line line
= GetLine (1);
1712 PositionCaret (line
, 0);
1715 y_offset
= caret
.line
.Y
- viewport_y
;
1716 new_y
= caret
.line
.Y
- viewport_height
;
1718 owner
.vscroll
.Value
= Math
.Max (new_y
, 0);
1719 PositionCaret ((int)caret
.line
.widths
[caret
.pos
], y_offset
+ viewport_y
);
1723 case CaretDirection
.PgDn
: {
1724 int new_y
, y_offset
;
1726 if ((viewport_y
+ viewport_height
) > document_y
) {
1728 // This should probably be handled elsewhere
1729 if (!(owner
is RichTextBox
)) {
1730 // Page up doesn't do anything in a regular TextBox
1731 // if the bottom of the document
1732 // is already visible, the page and the caret stay still
1736 // We're just placing the caret at the end of the document, no scrolling needed
1737 owner
.vscroll
.Value
= owner
.vscroll
.Maximum
- viewport_height
+ 1;
1738 Line line
= GetLine (lines
);
1739 PositionCaret (line
, line
.Text
.Length
);
1742 y_offset
= caret
.line
.Y
- viewport_y
;
1743 new_y
= caret
.line
.Y
+ viewport_height
;
1745 owner
.vscroll
.Value
= Math
.Min (new_y
, owner
.vscroll
.Maximum
- viewport_height
+ 1);
1746 PositionCaret ((int)caret
.line
.widths
[caret
.pos
], y_offset
+ viewport_y
);
1751 case CaretDirection
.CtrlPgUp
: {
1752 PositionCaret(0, viewport_y
);
1757 case CaretDirection
.CtrlPgDn
: {
1762 tag
= FindTag(0, viewport_y
+ viewport_height
, out index
, false);
1763 if (tag
.line
.line_no
> 1) {
1764 line
= GetLine(tag
.line
.line_no
- 1);
1768 PositionCaret(line
, line
.Text
.Length
);
1773 case CaretDirection
.CtrlHome
: {
1774 caret
.line
= GetLine(1);
1776 caret
.tag
= caret
.line
.tags
;
1782 case CaretDirection
.CtrlEnd
: {
1783 caret
.line
= GetLine(lines
);
1784 caret
.pos
= caret
.line
.text
.Length
;
1785 caret
.tag
= LineTag
.FindTag(caret
.line
, caret
.pos
);
1791 case CaretDirection
.SelectionStart
: {
1792 caret
.line
= selection_start
.line
;
1793 caret
.pos
= selection_start
.pos
;
1794 caret
.tag
= selection_start
.tag
;
1800 case CaretDirection
.SelectionEnd
: {
1801 caret
.line
= selection_end
.line
;
1802 caret
.pos
= selection_end
.pos
;
1803 caret
.tag
= selection_end
.tag
;
1811 internal void DumpDoc ()
1813 Console
.WriteLine ("<doc>");
1814 for (int i
= 1; i
< lines
; i
++) {
1815 Line line
= GetLine (i
);
1816 Console
.WriteLine ("<line no='{0}'>", line
.line_no
);
1818 LineTag tag
= line
.tags
;
1819 while (tag
!= null) {
1820 Console
.Write ("\t<tag color='{0}'>", tag
.color
.Color
);
1821 Console
.Write (tag
.Text ());
1822 Console
.WriteLine ("\t</tag>");
1825 Console
.WriteLine ("</line>");
1827 Console
.WriteLine ("</doc>");
1830 internal void Draw (Graphics g
, Rectangle clip
)
1832 Line line
; // Current line being drawn
1833 LineTag tag
; // Current tag being drawn
1834 int start
; // First line to draw
1835 int end
; // Last line to draw
1836 StringBuilder text
; // String representing the current line
1839 Brush current_brush
;
1840 Brush disabled_brush
;
1844 // First, figure out from what line to what line we need to draw
1845 start
= GetLineByPixel(clip
.Top
+ viewport_y
, false).line_no
;
1846 end
= GetLineByPixel(clip
.Bottom
+ viewport_y
, false).line_no
;
1848 /// Make sure that we aren't drawing one more line then we need to
1849 line
= GetLine (end
- 1);
1850 if (line
!= null && clip
.Bottom
== line
.Y
+ line
.height
+ viewport_y
)
1855 g
.FillRectangle (new SolidBrush (Color
.White
), clip
);
1857 DateTime n
= DateTime
.Now
;
1858 Console
.WriteLine ("Started drawing: {0}s {1}ms", n
.Second
, n
.Millisecond
);
1859 Console
.WriteLine ("CLIP: {0}", clip
);
1860 Console
.WriteLine ("S: {0}", GetLine (start
).text
);
1861 Console
.WriteLine ("E: {0}", GetLine (end
).text
);
1864 disabled_brush
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorGrayText
);
1865 hilight
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorHighlight
);
1866 hilight_text
= ThemeEngine
.Current
.ResPool
.GetSolidBrush(ThemeEngine
.Current
.ColorHighlightText
);
1868 while (line_no
<= end
) {
1869 line
= GetLine (line_no
);
1875 // This fails if there's a password > 1024 chars...
1876 text
= this.password_cache
;
1879 int line_selection_start
= text
.Length
+ 1;
1880 int line_selection_end
= text
.Length
+ 1;
1881 if (selection_visible
&& owner
.ShowSelection
&&
1882 (line_no
>= selection_start
.line
.line_no
) &&
1883 (line_no
<= selection_end
.line
.line_no
)) {
1885 if (line_no
== selection_start
.line
.line_no
)
1886 line_selection_start
= selection_start
.pos
+ 1;
1888 line_selection_start
= 1;
1890 if (line_no
== selection_end
.line
.line_no
)
1891 line_selection_end
= selection_end
.pos
+ 1;
1893 line_selection_end
= text
.Length
+ 1;
1895 if (line_selection_end
== line_selection_start
) {
1896 // There isn't really selection
1897 line_selection_start
= text
.Length
+ 1;
1898 line_selection_end
= line_selection_start
;
1900 // lets draw some selection baby!!
1902 g
.FillRectangle (hilight
,
1903 line
.widths
[line_selection_start
- 1] + line
.align_shift
- viewport_x
,
1904 line
.Y
- viewport_y
,
1905 line
.widths
[line_selection_end
- 1] - line
.widths
[line_selection_start
- 1],
1910 current_brush
= line
.tags
.color
;
1911 while (tag
!= null) {
1914 if (tag
.length
== 0) {
1919 if (((tag
.X
+ tag
.width
) < (clip
.Left
- viewport_x
)) || (tag
.X
> (clip
.Right
- viewport_x
))) {
1924 if (tag
.back_color
!= null) {
1925 g
.FillRectangle (tag
.back_color
, tag
.X
+ line
.align_shift
- viewport_x
,
1926 line
.Y
+ tag
.shift
- viewport_y
, tag
.width
, line
.height
);
1929 tag_brush
= tag
.color
;
1930 current_brush
= tag_brush
;
1932 if (!owner
.is_enabled
) {
1933 Color a
= ((SolidBrush
) tag
.color
).Color
;
1934 Color b
= ThemeEngine
.Current
.ColorWindowText
;
1936 if ((a
.R
== b
.R
) && (a
.G
== b
.G
) && (a
.B
== b
.B
)) {
1937 tag_brush
= disabled_brush
;
1941 int tag_pos
= tag
.start
;
1942 current_brush
= tag_brush
;
1943 while (tag_pos
< tag
.start
+ tag
.length
) {
1944 int old_tag_pos
= tag_pos
;
1946 if (tag_pos
>= line_selection_start
&& tag_pos
< line_selection_end
) {
1947 current_brush
= hilight_text
;
1948 tag_pos
= Math
.Min (tag
.end
, line_selection_end
);
1949 } else if (tag_pos
< line_selection_start
) {
1950 current_brush
= tag
.color
;
1951 tag_pos
= Math
.Min (tag
.end
, line_selection_start
);
1953 current_brush
= tag
.color
;
1957 tag
.Draw (g
, current_brush
,
1958 line
.widths
[old_tag_pos
- 1] + line
.align_shift
- viewport_x
,
1959 line
.Y
+ tag
.shift
- viewport_y
,
1960 old_tag_pos
- 1, Math
.Min (tag
.length
, tag_pos
- old_tag_pos
));
1968 private void InsertLineString (Line line
, int pos
, string s
)
1970 bool carriage_return
= false;
1972 if (s
.EndsWith ("\r")) {
1973 s
= s
.Substring (0, s
.Length
- 1);
1974 carriage_return
= true;
1977 InsertString (line
, pos
, s
);
1979 if (carriage_return
) {
1980 Line l
= GetLine (line
.line_no
);
1981 l
.carriage_return
= true;
1985 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
1986 internal void Insert(Line line
, int pos
, bool update_caret
, string s
) {
1991 LineTag tag
= LineTag
.FindTag (line
, pos
);
1994 undo
.BeginCompoundAction ();
1996 base_line
= line
.line_no
;
1997 old_line_count
= lines
;
1999 break_index
= s
.IndexOf ('\n');
2001 // Bump the text at insertion point a line down if we're inserting more than one line
2002 if (break_index
> -1) {
2004 line
.soft_break
= false;
2005 // Remainder of start line is now in base_line + 1
2008 if (break_index
== -1)
2009 break_index
= s
.Length
;
2011 InsertLineString (line
, pos
, s
.Substring (0, break_index
));
2014 while (break_index
< s
.Length
) {
2016 int next_break
= s
.IndexOf ('\n', break_index
);
2017 int adjusted_next_break
;
2018 bool carriage_return
= false;
2020 if (next_break
== -1) {
2021 next_break
= s
.Length
;
2025 adjusted_next_break
= next_break
;
2026 if (s
[next_break
- 1] == '\r') {
2027 adjusted_next_break
--;
2028 carriage_return
= true;
2031 string line_text
= s
.Substring (break_index
, adjusted_next_break
- break_index
);
2032 Add (base_line
+ count
, line_text
, line
.alignment
, tag
.font
, tag
.color
);
2034 if (carriage_return
) {
2035 Line last
= GetLine (base_line
+ count
);
2036 last
.carriage_return
= true;
2039 last
.soft_break
= true;
2041 Line last
= GetLine (base_line
+ count
);
2042 last
.soft_break
= true;
2046 break_index
= next_break
+ 1;
2049 ResumeRecalc (true);
2051 UpdateView(line
, lines
- old_line_count
+ 1, pos
);
2054 // Move caret to the end of the inserted text
2055 Line l
= GetLine (line
.line_no
+ lines
- old_line_count
);
2056 PositionCaret(l
, l
.text
.Length
);
2060 undo
.EndCompoundAction ();
2063 // Inserts a character at the given position
2064 internal void InsertString(Line line
, int pos
, string s
) {
2065 InsertString(line
.FindTag(pos
), pos
, s
);
2068 // Inserts a string at the given position
2069 internal void InsertString(LineTag tag
, int pos
, string s
) {
2078 line
.text
.Insert(pos
, s
);
2081 while (tag
!= null) {
2088 UpdateView(line
, pos
);
2091 // Inserts a string at the caret position
2092 internal void InsertStringAtCaret(string s
, bool move_caret
) {
2094 InsertString (caret
.tag
, caret
.pos
, s
);
2096 UpdateView(caret
.line
, caret
.pos
);
2098 caret
.pos
+= s
.Length
;
2105 // Inserts a character at the given position
2106 internal void InsertChar(Line line
, int pos
, char ch
) {
2107 InsertChar(line
.FindTag(pos
), pos
, ch
);
2110 // Inserts a character at the given position
2111 internal void InsertChar(LineTag tag
, int pos
, char ch
) {
2117 line
.text
.Insert(pos
, ch
);
2121 while (tag
!= null) {
2128 UpdateView(line
, pos
);
2131 // Inserts a character at the current caret position
2132 internal void InsertCharAtCaret(char ch
, bool move_caret
) {
2138 caret.line.text.Insert(caret.pos, ch);
2141 if (caret.tag.next != null) {
2142 tag = caret.tag.next;
2143 while (tag != null) {
2149 caret.line.recalc = true;
2151 InsertChar (caret
.tag
, caret
.pos
, ch
);
2153 UpdateView(caret
.line
, caret
.pos
);
2157 SetSelectionToCaret(true);
2161 internal void InsertImage (LineTag tag
, int pos
, Image image
)
2169 line
.text
.Insert (pos
, "I");
2171 LineTag next_tag
= tag
.Break (pos
);
2172 ImageTag image_tag
= new ImageTag (line
, pos
, image
);
2173 image_tag
.CopyFormattingFrom (tag
);
2174 image_tag
.next
= next_tag
;
2175 image_tag
.previous
= tag
;
2176 tag
.next
= image_tag
;
2178 tag
= image_tag
.next
;
2179 while (tag
!= null) {
2188 UpdateView (line
, pos
);
2191 internal void DeleteMultiline (Line start_line
, int pos
, int length
)
2193 Marker start
= new Marker ();
2194 Marker end
= new Marker ();
2195 int start_index
= LineTagToCharIndex (start_line
, pos
);
2197 start
.line
= start_line
;
2199 start
.tag
= LineTag
.FindTag (start_line
, pos
);
2201 CharIndexToLineTag (start_index
+ length
, out end
.line
,
2202 out end
.tag
, out end
.pos
);
2204 if (start
.line
== end
.line
) {
2205 DeleteChars (start
.tag
, pos
, end
.pos
- pos
);
2208 // Delete first and last lines
2209 DeleteChars (start
.tag
, start
.pos
, start
.line
.text
.Length
- start
.pos
);
2210 DeleteChars (end
.line
.tags
, 0, end
.pos
);
2212 int current
= start
.line
.line_no
+ 1;
2213 if (current
< end
.line
.line_no
) {
2214 for (int i
= end
.line
.line_no
- 1; i
>= current
; i
--) {
2219 // BIG FAT WARNING - selection_end.line might be stale due
2220 // to the above Delete() call. DONT USE IT before hitting the end of this method!
2222 // Join start and end
2223 Combine (start
.line
.line_no
, current
);
2228 // Deletes n characters at the given position; it will not delete past line limits
2230 internal void DeleteChars(LineTag tag
, int pos
, int count
) {
2239 if (pos
== line
.text
.Length
) {
2243 line
.text
.Remove(pos
, count
);
2245 // Make sure the tag points to the right spot
2246 while ((tag
!= null) && (tag
.start
+ tag
.length
- 1) <= pos
) {
2254 // Check if we're crossing tag boundaries
2255 if ((pos
+ count
) > (tag
.start
+ tag
.length
- 1)) {
2258 // We have to delete cross tag boundaries
2262 left
-= tag
.start
+ tag
.length
- pos
- 1;
2265 while ((tag
!= null) && (left
> 0)) {
2266 tag
.start
-= count
- left
;
2268 if (tag
.length
> left
) {
2277 // We got off easy, same tag
2279 if (tag
.length
== 0) {
2284 // Delete empty orphaned tags at the end
2286 while (walk
!= null && walk
.next
!= null && walk
.next
.length
== 0) {
2288 walk
.next
= walk
.next
.next
;
2289 if (walk
.next
!= null)
2290 walk
.next
.previous
= t
;
2294 // Adjust the start point of any tags following
2297 while (tag
!= null) {
2305 line
.Streamline(lines
);
2308 UpdateView(line
, pos
);
2311 // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
2312 internal void DeleteChar(LineTag tag
, int pos
, bool forward
) {
2321 if ((pos
== 0 && forward
== false) || (pos
== line
.text
.Length
&& forward
== true)) {
2327 line
.text
.Remove(pos
, 1);
2329 while ((tag
!= null) && (tag
.start
+ tag
.length
- 1) <= pos
) {
2339 if (tag
.length
== 0) {
2344 line
.text
.Remove(pos
, 1);
2345 if (pos
>= (tag
.start
- 1)) {
2347 if (tag
.length
== 0) {
2350 } else if (tag
.previous
!= null) {
2351 // tag.previous.length--;
2352 if (tag
.previous
.length
== 0) {
2358 // Delete empty orphaned tags at the end
2360 while (walk
!= null && walk
.next
!= null && walk
.next
.length
== 0) {
2362 walk
.next
= walk
.next
.next
;
2363 if (walk
.next
!= null)
2364 walk
.next
.previous
= t
;
2369 while (tag
!= null) {
2375 line
.Streamline(lines
);
2378 UpdateView(line
, pos
);
2381 // Combine two lines
2382 internal void Combine(int FirstLine
, int SecondLine
) {
2383 Combine(GetLine(FirstLine
), GetLine(SecondLine
));
2386 internal void Combine(Line first
, Line second
) {
2390 // Combine the two tag chains into one
2393 // Maintain the line ending style
2394 first
.soft_break
= second
.soft_break
;
2396 while (last
.next
!= null) {
2400 // need to get the shift before setting the next tag since that effects length
2401 shift
= last
.start
+ last
.length
- 1;
2402 last
.next
= second
.tags
;
2403 last
.next
.previous
= last
;
2405 // Fix up references within the chain
2407 while (last
!= null) {
2409 last
.start
+= shift
;
2413 // Combine both lines' strings
2414 first
.text
.Insert(first
.text
.Length
, second
.text
.ToString());
2415 first
.Grow(first
.text
.Length
);
2417 // Remove the reference to our (now combined) tags from the doomed line
2421 DecrementLines(first
.line_no
+ 2); // first.line_no + 1 will be deleted, so we need to start renumbering one later
2424 first
.recalc
= true;
2425 first
.height
= 0; // This forces RecalcDocument/UpdateView to redraw from this line on
2426 first
.Streamline(lines
);
2428 // Update Caret, Selection, etc
2429 if (caret
.line
== second
) {
2430 caret
.Combine(first
, shift
);
2432 if (selection_anchor
.line
== second
) {
2433 selection_anchor
.Combine(first
, shift
);
2435 if (selection_start
.line
== second
) {
2436 selection_start
.Combine(first
, shift
);
2438 if (selection_end
.line
== second
) {
2439 selection_end
.Combine(first
, shift
);
2446 check_first
= GetLine(first
.line_no
);
2447 check_second
= GetLine(check_first
.line_no
+ 1);
2449 Console
.WriteLine("Pre-delete: Y of first line: {0}, second line: {1}", check_first
.Y
, check_second
.Y
);
2452 this.Delete(second
);
2455 check_first
= GetLine(first
.line_no
);
2456 check_second
= GetLine(check_first
.line_no
+ 1);
2458 Console
.WriteLine("Post-delete Y of first line: {0}, second line: {1}", check_first
.Y
, check_second
.Y
);
2462 // Split the line at the position into two
2463 internal void Split(int LineNo
, int pos
) {
2467 line
= GetLine(LineNo
);
2468 tag
= LineTag
.FindTag(line
, pos
);
2469 Split(line
, tag
, pos
, false);
2472 internal void Split(Line line
, int pos
) {
2475 tag
= LineTag
.FindTag(line
, pos
);
2476 Split(line
, tag
, pos
, false);
2479 ///<summary>Split line at given tag and position into two lines</summary>
2480 ///<param name="soft">True if the split should be marked as 'soft', indicating that it can be contracted
2481 ///if more space becomes available on previous line</param>
2482 internal void Split(Line line
, LineTag tag
, int pos
, bool soft
) {
2486 bool move_sel_start
;
2490 move_sel_start
= false;
2491 move_sel_end
= false;
2493 // Adjust selection and cursors
2494 if (caret
.line
== line
&& caret
.pos
>= pos
) {
2497 if (selection_start
.line
== line
&& selection_start
.pos
> pos
) {
2498 move_sel_start
= true;
2501 if (selection_end
.line
== line
&& selection_end
.pos
> pos
) {
2502 move_sel_end
= true;
2505 // cover the easy case first
2506 if (pos
== line
.text
.Length
) {
2507 Add(line
.line_no
+ 1, "", line
.alignment
, tag
.font
, tag
.color
);
2509 new_line
= GetLine(line
.line_no
+ 1);
2511 line
.carriage_return
= false;
2512 new_line
.carriage_return
= line
.carriage_return
;
2513 new_line
.soft_break
= soft
;
2516 caret
.line
= new_line
;
2517 caret
.tag
= new_line
.tags
;
2521 if (move_sel_start
) {
2522 selection_start
.line
= new_line
;
2523 selection_start
.pos
= 0;
2524 selection_start
.tag
= new_line
.tags
;
2528 selection_end
.line
= new_line
;
2529 selection_end
.pos
= 0;
2530 selection_end
.tag
= new_line
.tags
;
2535 // We need to move the rest of the text into the new line
2536 Add (line
.line_no
+ 1, line
.text
.ToString (pos
, line
.text
.Length
- pos
), line
.alignment
, tag
.font
, tag
.color
);
2538 // Now transfer our tags from this line to the next
2539 new_line
= GetLine(line
.line_no
+ 1);
2541 line
.carriage_return
= false;
2542 new_line
.carriage_return
= line
.carriage_return
;
2543 new_line
.soft_break
= soft
;
2546 new_line
.recalc
= true;
2548 if ((tag
.start
- 1) == pos
) {
2551 // We can simply break the chain and move the tag into the next line
2552 if (tag
== line
.tags
) {
2553 new_tag
= new LineTag(line
, 1);
2554 new_tag
.CopyFormattingFrom (tag
);
2555 line
.tags
= new_tag
;
2558 if (tag
.previous
!= null) {
2559 tag
.previous
.next
= null;
2561 new_line
.tags
= tag
;
2562 tag
.previous
= null;
2563 tag
.line
= new_line
;
2565 // Walk the list and correct the start location of the tags we just bumped into the next line
2566 shift
= tag
.start
- 1;
2569 while (new_tag
!= null) {
2570 new_tag
.start
-= shift
;
2571 new_tag
.line
= new_line
;
2572 new_tag
= new_tag
.next
;
2577 new_tag
= new LineTag (new_line
, 1);
2578 new_tag
.next
= tag
.next
;
2579 new_tag
.CopyFormattingFrom (tag
);
2580 new_line
.tags
= new_tag
;
2581 if (new_tag
.next
!= null) {
2582 new_tag
.next
.previous
= new_tag
;
2587 new_tag
= new_tag
.next
;
2588 while (new_tag
!= null) {
2589 new_tag
.start
-= shift
;
2590 new_tag
.line
= new_line
;
2591 new_tag
= new_tag
.next
;
2597 caret
.line
= new_line
;
2598 caret
.pos
= caret
.pos
- pos
;
2599 caret
.tag
= caret
.line
.FindTag(caret
.pos
);
2602 if (move_sel_start
) {
2603 selection_start
.line
= new_line
;
2604 selection_start
.pos
= selection_start
.pos
- pos
;
2605 selection_start
.tag
= new_line
.FindTag(selection_start
.pos
);
2609 selection_end
.line
= new_line
;
2610 selection_end
.pos
= selection_end
.pos
- pos
;
2611 selection_end
.tag
= new_line
.FindTag(selection_end
.pos
);
2614 CharCount
-= line
.text
.Length
- pos
;
2615 line
.text
.Remove(pos
, line
.text
.Length
- pos
);
2618 // Adds a line of text, with given font.
2619 // Bumps any line at that line number that already exists down
2620 internal void Add(int LineNo
, string Text
, Font font
, SolidBrush color
) {
2621 Add(LineNo
, Text
, HorizontalAlignment
.Left
, font
, color
);
2624 internal void Add(int LineNo
, string Text
, HorizontalAlignment align
, Font font
, SolidBrush color
) {
2629 CharCount
+= Text
.Length
;
2631 if (LineNo
<1 || Text
== null) {
2633 throw new ArgumentNullException("LineNo", "Line numbers must be positive");
2635 throw new ArgumentNullException("Text", "Cannot insert NULL line");
2639 add = new Line(LineNo
, Text
, align
, font
, color
);
2642 while (line
!= sentinel
) {
2644 line_no
= line
.line_no
;
2646 if (LineNo
> line_no
) {
2648 } else if (LineNo
< line_no
) {
2651 // Bump existing line numbers; walk all nodes to the right of this one and increment line_no
2652 IncrementLines(line
.line_no
);
2657 add.left
= sentinel
;
2658 add.right
= sentinel
;
2660 if (add.parent
!= null) {
2661 if (LineNo
> add.parent
.line_no
) {
2662 add.parent
.right
= add;
2664 add.parent
.left
= add;
2671 RebalanceAfterAdd(add);
2676 internal virtual void Clear() {
2679 document
= sentinel
;
2682 public virtual object Clone() {
2685 clone
= new Document(null);
2687 clone
.lines
= this.lines
;
2688 clone
.document
= (Line
)document
.Clone();
2693 internal void Delete(int LineNo
) {
2700 line
= GetLine(LineNo
);
2702 CharCount
-= line
.text
.Length
;
2704 DecrementLines(LineNo
+ 1);
2708 internal void Delete(Line line1
) {
2709 Line line2
;// = new Line();
2712 if ((line1
.left
== sentinel
) || (line1
.right
== sentinel
)) {
2715 line3
= line1
.right
;
2716 while (line3
.left
!= sentinel
) {
2721 if (line3
.left
!= sentinel
) {
2724 line2
= line3
.right
;
2727 line2
.parent
= line3
.parent
;
2728 if (line3
.parent
!= null) {
2729 if(line3
== line3
.parent
.left
) {
2730 line3
.parent
.left
= line2
;
2732 line3
.parent
.right
= line2
;
2738 if (line3
!= line1
) {
2741 if (selection_start
.line
== line3
) {
2742 selection_start
.line
= line1
;
2745 if (selection_end
.line
== line3
) {
2746 selection_end
.line
= line1
;
2749 if (selection_anchor
.line
== line3
) {
2750 selection_anchor
.line
= line1
;
2753 if (caret
.line
== line3
) {
2758 line1
.alignment
= line3
.alignment
;
2759 line1
.ascent
= line3
.ascent
;
2760 line1
.hanging_indent
= line3
.hanging_indent
;
2761 line1
.height
= line3
.height
;
2762 line1
.indent
= line3
.indent
;
2763 line1
.line_no
= line3
.line_no
;
2764 line1
.recalc
= line3
.recalc
;
2765 line1
.right_indent
= line3
.right_indent
;
2766 line1
.soft_break
= line3
.soft_break
;
2767 line1
.space
= line3
.space
;
2768 line1
.tags
= line3
.tags
;
2769 line1
.text
= line3
.text
;
2770 line1
.widths
= line3
.widths
;
2774 while (tag
!= null) {
2780 if (line3
.color
== LineColor
.Black
)
2781 RebalanceAfterDelete(line2
);
2786 // Invalidate a section of the document to trigger redraw
2787 internal void Invalidate(Line start
, int start_pos
, Line end
, int end_pos
) {
2793 if ((start
== end
) && (start_pos
== end_pos
)) {
2797 if (end_pos
== -1) {
2798 end_pos
= end
.text
.Length
;
2801 // figure out what's before what so the logic below is straightforward
2802 if (start
.line_no
< end
.line_no
) {
2808 } else if (start
.line_no
> end
.line_no
) {
2815 if (start_pos
< end_pos
) {
2830 Console
.WriteLine("Invaliding from {0}:{1} to {2}:{3} {4}",
2831 l1
.line_no
, p1
, l2
.line_no
, p2
,
2833 (int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
,
2835 (int)l2
.widths
[p2
] - (int)l1
.widths
[p1
] + 1,
2843 (int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
,
2845 (int)l2
.widths
[p2
] - (int)l1
.widths
[p1
] + 1,
2853 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
);
2854 Console
.WriteLine ("invalidate start line: {0} position: {1}", l1
.text
, p1
);
2857 // Three invalidates:
2858 // First line from start
2859 owner
.Invalidate(new Rectangle((int)l1
.widths
[p1
] + l1
.align_shift
- viewport_x
, l1
.Y
- viewport_y
, viewport_width
, l1
.height
));
2863 if ((l1
.line_no
+ 1) < l2
.line_no
) {
2866 y
= GetLine(l1
.line_no
+ 1).Y
;
2867 owner
.Invalidate(new Rectangle(0, y
- viewport_y
, viewport_width
, l2
.Y
- y
));
2870 Console
.WriteLine("Invaliding from {0}:{1} to {2}:{3} Middle => x={4}, y={5}, {6}x{7}", l1
.line_no
, p1
, l2
.line_no
, p2
, 0, y
- viewport_y
, viewport_width
, l2
.Y
- y
);
2876 owner
.Invalidate(new Rectangle((int)l2
.widths
[0] + l2
.align_shift
- viewport_x
, l2
.Y
- viewport_y
, (int)l2
.widths
[p2
] + 1, l2
.height
));
2878 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
);
2883 /// <summary>Select text around caret</summary>
2884 internal void ExpandSelection(CaretSelection mode
, bool to_caret
) {
2886 // We're expanding the selection to the caret position
2888 case CaretSelection
.Line
: {
2889 // Invalidate the selection delta
2890 if (caret
> selection_prev
) {
2891 Invalidate(selection_prev
.line
, 0, caret
.line
, caret
.line
.text
.Length
);
2893 Invalidate(selection_prev
.line
, selection_prev
.line
.text
.Length
, caret
.line
, 0);
2896 if (caret
.line
.line_no
<= selection_anchor
.line
.line_no
) {
2897 selection_start
.line
= caret
.line
;
2898 selection_start
.tag
= caret
.line
.tags
;
2899 selection_start
.pos
= 0;
2901 selection_end
.line
= selection_anchor
.line
;
2902 selection_end
.tag
= selection_anchor
.tag
;
2903 selection_end
.pos
= selection_anchor
.pos
;
2905 selection_end_anchor
= true;
2907 selection_start
.line
= selection_anchor
.line
;
2908 selection_start
.pos
= selection_anchor
.height
;
2909 selection_start
.tag
= selection_anchor
.line
.FindTag(selection_anchor
.height
);
2911 selection_end
.line
= caret
.line
;
2912 selection_end
.tag
= caret
.line
.tags
;
2913 selection_end
.pos
= caret
.line
.text
.Length
;
2915 selection_end_anchor
= false;
2917 selection_prev
.line
= caret
.line
;
2918 selection_prev
.tag
= caret
.tag
;
2919 selection_prev
.pos
= caret
.pos
;
2924 case CaretSelection
.Word
: {
2928 start_pos
= FindWordSeparator(caret
.line
, caret
.pos
, false);
2929 end_pos
= FindWordSeparator(caret
.line
, caret
.pos
, true);
2932 // Invalidate the selection delta
2933 if (caret
> selection_prev
) {
2934 Invalidate(selection_prev
.line
, selection_prev
.pos
, caret
.line
, end_pos
);
2936 Invalidate(selection_prev
.line
, selection_prev
.pos
, caret
.line
, start_pos
);
2938 if (caret
< selection_anchor
) {
2939 selection_start
.line
= caret
.line
;
2940 selection_start
.tag
= caret
.line
.FindTag(start_pos
);
2941 selection_start
.pos
= start_pos
;
2943 selection_end
.line
= selection_anchor
.line
;
2944 selection_end
.tag
= selection_anchor
.tag
;
2945 selection_end
.pos
= selection_anchor
.pos
;
2947 selection_prev
.line
= caret
.line
;
2948 selection_prev
.tag
= caret
.tag
;
2949 selection_prev
.pos
= start_pos
;
2951 selection_end_anchor
= true;
2953 selection_start
.line
= selection_anchor
.line
;
2954 selection_start
.pos
= selection_anchor
.height
;
2955 selection_start
.tag
= selection_anchor
.line
.FindTag(selection_anchor
.height
);
2957 selection_end
.line
= caret
.line
;
2958 selection_end
.tag
= caret
.line
.FindTag(end_pos
);
2959 selection_end
.pos
= end_pos
;
2961 selection_prev
.line
= caret
.line
;
2962 selection_prev
.tag
= caret
.tag
;
2963 selection_prev
.pos
= end_pos
;
2965 selection_end_anchor
= false;
2970 case CaretSelection
.Position
: {
2971 SetSelectionToCaret(false);
2976 // We're setting the selection 'around' the caret position
2978 case CaretSelection
.Line
: {
2979 this.Invalidate(caret
.line
, 0, caret
.line
, caret
.line
.text
.Length
);
2981 selection_start
.line
= caret
.line
;
2982 selection_start
.tag
= caret
.line
.tags
;
2983 selection_start
.pos
= 0;
2985 selection_end
.line
= caret
.line
;
2986 selection_end
.pos
= caret
.line
.text
.Length
;
2987 selection_end
.tag
= caret
.line
.FindTag(selection_end
.pos
);
2989 selection_anchor
.line
= selection_end
.line
;
2990 selection_anchor
.tag
= selection_end
.tag
;
2991 selection_anchor
.pos
= selection_end
.pos
;
2992 selection_anchor
.height
= 0;
2994 selection_prev
.line
= caret
.line
;
2995 selection_prev
.tag
= caret
.tag
;
2996 selection_prev
.pos
= caret
.pos
;
2998 this.selection_end_anchor
= true;
3003 case CaretSelection
.Word
: {
3007 start_pos
= FindWordSeparator(caret
.line
, caret
.pos
, false);
3008 end_pos
= FindWordSeparator(caret
.line
, caret
.pos
, true);
3010 this.Invalidate(selection_start
.line
, start_pos
, caret
.line
, end_pos
);
3012 selection_start
.line
= caret
.line
;
3013 selection_start
.tag
= caret
.line
.FindTag(start_pos
);
3014 selection_start
.pos
= start_pos
;
3016 selection_end
.line
= caret
.line
;
3017 selection_end
.tag
= caret
.line
.FindTag(end_pos
);
3018 selection_end
.pos
= end_pos
;
3020 selection_anchor
.line
= selection_end
.line
;
3021 selection_anchor
.tag
= selection_end
.tag
;
3022 selection_anchor
.pos
= selection_end
.pos
;
3023 selection_anchor
.height
= start_pos
;
3025 selection_prev
.line
= caret
.line
;
3026 selection_prev
.tag
= caret
.tag
;
3027 selection_prev
.pos
= caret
.pos
;
3029 this.selection_end_anchor
= true;
3036 SetSelectionVisible (!(selection_start
== selection_end
));
3039 internal void SetSelectionToCaret(bool start
) {
3041 // Invalidate old selection; selection is being reset to empty
3042 this.Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3044 selection_start
.line
= caret
.line
;
3045 selection_start
.tag
= caret
.tag
;
3046 selection_start
.pos
= caret
.pos
;
3048 // start always also selects end
3049 selection_end
.line
= caret
.line
;
3050 selection_end
.tag
= caret
.tag
;
3051 selection_end
.pos
= caret
.pos
;
3053 selection_anchor
.line
= caret
.line
;
3054 selection_anchor
.tag
= caret
.tag
;
3055 selection_anchor
.pos
= caret
.pos
;
3057 // Invalidate from previous end to caret (aka new end)
3058 if (selection_end_anchor
) {
3059 if (selection_start
!= caret
) {
3060 this.Invalidate(selection_start
.line
, selection_start
.pos
, caret
.line
, caret
.pos
);
3063 if (selection_end
!= caret
) {
3064 this.Invalidate(selection_end
.line
, selection_end
.pos
, caret
.line
, caret
.pos
);
3068 if (caret
< selection_anchor
) {
3069 selection_start
.line
= caret
.line
;
3070 selection_start
.tag
= caret
.tag
;
3071 selection_start
.pos
= caret
.pos
;
3073 selection_end
.line
= selection_anchor
.line
;
3074 selection_end
.tag
= selection_anchor
.tag
;
3075 selection_end
.pos
= selection_anchor
.pos
;
3077 selection_end_anchor
= true;
3079 selection_start
.line
= selection_anchor
.line
;
3080 selection_start
.tag
= selection_anchor
.tag
;
3081 selection_start
.pos
= selection_anchor
.pos
;
3083 selection_end
.line
= caret
.line
;
3084 selection_end
.tag
= caret
.tag
;
3085 selection_end
.pos
= caret
.pos
;
3087 selection_end_anchor
= false;
3091 SetSelectionVisible (!(selection_start
== selection_end
));
3094 internal void SetSelection(Line start
, int start_pos
, Line end
, int end_pos
) {
3095 if (selection_visible
) {
3096 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3099 if ((end
.line_no
< start
.line_no
) || ((end
== start
) && (end_pos
<= start_pos
))) {
3100 selection_start
.line
= end
;
3101 selection_start
.tag
= LineTag
.FindTag(end
, end_pos
);
3102 selection_start
.pos
= end_pos
;
3104 selection_end
.line
= start
;
3105 selection_end
.tag
= LineTag
.FindTag(start
, start_pos
);
3106 selection_end
.pos
= start_pos
;
3108 selection_end_anchor
= true;
3110 selection_start
.line
= start
;
3111 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3112 selection_start
.pos
= start_pos
;
3114 selection_end
.line
= end
;
3115 selection_end
.tag
= LineTag
.FindTag(end
, end_pos
);
3116 selection_end
.pos
= end_pos
;
3118 selection_end_anchor
= false;
3121 selection_anchor
.line
= start
;
3122 selection_anchor
.tag
= selection_start
.tag
;
3123 selection_anchor
.pos
= start_pos
;
3125 if (((start
== end
) && (start_pos
== end_pos
)) || start
== null || end
== null) {
3126 SetSelectionVisible (false);
3128 SetSelectionVisible (true);
3129 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3133 internal void SetSelectionStart(Line start
, int start_pos
) {
3134 // Invalidate from the previous to the new start pos
3135 Invalidate(selection_start
.line
, selection_start
.pos
, start
, start_pos
);
3137 selection_start
.line
= start
;
3138 selection_start
.pos
= start_pos
;
3139 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3141 selection_anchor
.line
= start
;
3142 selection_anchor
.pos
= start_pos
;
3143 selection_anchor
.tag
= selection_start
.tag
;
3145 selection_end_anchor
= false;
3148 if ((selection_end
.line
!= selection_start
.line
) || (selection_end
.pos
!= selection_start
.pos
)) {
3149 SetSelectionVisible (true);
3151 SetSelectionVisible (false);
3154 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3157 internal void SetSelectionStart(int character_index
) {
3162 if (character_index
< 0) {
3166 CharIndexToLineTag(character_index
, out line
, out tag
, out pos
);
3167 SetSelectionStart(line
, pos
);
3170 internal void SetSelectionEnd(Line end
, int end_pos
) {
3172 if (end
== selection_end
.line
&& end_pos
== selection_start
.pos
) {
3173 selection_anchor
.line
= selection_start
.line
;
3174 selection_anchor
.tag
= selection_start
.tag
;
3175 selection_anchor
.pos
= selection_start
.pos
;
3177 selection_end
.line
= selection_start
.line
;
3178 selection_end
.tag
= selection_start
.tag
;
3179 selection_end
.pos
= selection_start
.pos
;
3181 selection_end_anchor
= false;
3182 } else if ((end
.line_no
< selection_anchor
.line
.line_no
) || ((end
== selection_anchor
.line
) && (end_pos
<= selection_anchor
.pos
))) {
3183 selection_start
.line
= end
;
3184 selection_start
.tag
= LineTag
.FindTag(end
, end_pos
);
3185 selection_start
.pos
= end_pos
;
3187 selection_end
.line
= selection_anchor
.line
;
3188 selection_end
.tag
= selection_anchor
.tag
;
3189 selection_end
.pos
= selection_anchor
.pos
;
3191 selection_end_anchor
= true;
3193 selection_start
.line
= selection_anchor
.line
;
3194 selection_start
.tag
= selection_anchor
.tag
;
3195 selection_start
.pos
= selection_anchor
.pos
;
3197 selection_end
.line
= end
;
3198 selection_end
.tag
= LineTag
.FindTag(end
, end_pos
);
3199 selection_end
.pos
= end_pos
;
3201 selection_end_anchor
= false;
3204 if ((selection_end
.line
!= selection_start
.line
) || (selection_end
.pos
!= selection_start
.pos
)) {
3205 SetSelectionVisible (true);
3206 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3208 SetSelectionVisible (false);
3209 // ?? Do I need to invalidate here, tests seem to work without it, but I don't think they should :-s
3213 internal void SetSelectionEnd(int character_index
) {
3218 if (character_index
< 0) {
3222 CharIndexToLineTag(character_index
, out line
, out tag
, out pos
);
3223 SetSelectionEnd(line
, pos
);
3226 internal void SetSelection(Line start
, int start_pos
) {
3227 if (selection_visible
) {
3228 Invalidate(selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3231 selection_start
.line
= start
;
3232 selection_start
.pos
= start_pos
;
3233 selection_start
.tag
= LineTag
.FindTag(start
, start_pos
);
3235 selection_end
.line
= start
;
3236 selection_end
.tag
= selection_start
.tag
;
3237 selection_end
.pos
= start_pos
;
3239 selection_anchor
.line
= start
;
3240 selection_anchor
.tag
= selection_start
.tag
;
3241 selection_anchor
.pos
= start_pos
;
3243 selection_end_anchor
= false;
3244 SetSelectionVisible (false);
3247 internal void InvalidateSelectionArea() {
3248 Invalidate (selection_start
.line
, selection_start
.pos
, selection_end
.line
, selection_end
.pos
);
3251 // Return the current selection, as string
3252 internal string GetSelection() {
3253 // We return String.Empty if there is no selection
3254 if ((selection_start
.pos
== selection_end
.pos
) && (selection_start
.line
== selection_end
.line
)) {
3255 return string.Empty
;
3258 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3259 return selection_start
.line
.text
.ToString(selection_start
.pos
, selection_end
.pos
- selection_start
.pos
);
3266 sb
= new StringBuilder();
3267 start
= selection_start
.line
.line_no
;
3268 end
= selection_end
.line
.line_no
;
3270 sb
.Append(selection_start
.line
.text
.ToString(selection_start
.pos
, selection_start
.line
.text
.Length
- selection_start
.pos
) + Environment
.NewLine
);
3272 if ((start
+ 1) < end
) {
3273 for (i
= start
+ 1; i
< end
; i
++) {
3274 sb
.Append(GetLine(i
).text
.ToString() + Environment
.NewLine
);
3278 sb
.Append(selection_end
.line
.text
.ToString(0, selection_end
.pos
));
3280 return sb
.ToString();
3284 internal void ReplaceSelection(string s
, bool select_new
) {
3287 undo
.BeginCompoundAction ();
3289 int selection_start_pos
= LineTagToCharIndex (selection_start
.line
, selection_start
.pos
);
3292 // First, delete any selected text
3293 if ((selection_start
.pos
!= selection_end
.pos
) || (selection_start
.line
!= selection_end
.line
)) {
3294 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3295 undo
.RecordDeleteChars(selection_start
.line
, selection_start
.pos
+ 1, selection_end
.pos
- selection_start
.pos
);
3297 DeleteChars(selection_start
.tag
, selection_start
.pos
, selection_end
.pos
- selection_start
.pos
);
3299 // The tag might have been removed, we need to recalc it
3300 selection_start
.tag
= selection_start
.line
.FindTag(selection_start
.pos
);
3305 start
= selection_start
.line
.line_no
;
3306 end
= selection_end
.line
.line_no
;
3308 undo
.RecordDelete(selection_start
.line
, selection_start
.pos
+ 1, selection_end
.line
, selection_end
.pos
);
3310 // Delete first line
3311 DeleteChars(selection_start
.tag
, selection_start
.pos
, selection_start
.line
.text
.Length
- selection_start
.pos
);
3314 DeleteChars(selection_end
.line
.tags
, 0, selection_end
.pos
);
3318 for (i
= end
- 1; i
>= start
; i
--) {
3323 // BIG FAT WARNING - selection_end.line might be stale due
3324 // to the above Delete() call. DONT USE IT before hitting the end of this method!
3326 // Join start and end
3327 Combine(selection_start
.line
.line_no
, start
);
3332 Insert(selection_start
.line
, selection_start
.pos
, false, s
);
3333 undo
.RecordInsertString (selection_start
.line
, selection_start
.pos
, s
);
3334 ResumeRecalc (false);
3337 CharIndexToLineTag(selection_start_pos
+ s
.Length
, out selection_start
.line
,
3338 out selection_start
.tag
, out selection_start
.pos
);
3340 selection_end
.line
= selection_start
.line
;
3341 selection_end
.pos
= selection_start
.pos
;
3342 selection_end
.tag
= selection_start
.tag
;
3343 selection_anchor
.line
= selection_start
.line
;
3344 selection_anchor
.pos
= selection_start
.pos
;
3345 selection_anchor
.tag
= selection_start
.tag
;
3347 SetSelectionVisible (false);
3349 CharIndexToLineTag(selection_start_pos
, out selection_start
.line
,
3350 out selection_start
.tag
, out selection_start
.pos
);
3352 CharIndexToLineTag(selection_start_pos
+ s
.Length
, out selection_end
.line
,
3353 out selection_end
.tag
, out selection_end
.pos
);
3355 selection_anchor
.line
= selection_start
.line
;
3356 selection_anchor
.pos
= selection_start
.pos
;
3357 selection_anchor
.tag
= selection_start
.tag
;
3359 SetSelectionVisible (true);
3362 PositionCaret (selection_start
.line
, selection_start
.pos
);
3363 UpdateView (selection_start
.line
, selection_start
.pos
);
3365 undo
.EndCompoundAction ();
3368 internal void CharIndexToLineTag(int index
, out Line line_out
, out LineTag tag_out
, out int pos
) {
3377 for (i
= 1; i
<= lines
; i
++) {
3381 chars
+= line
.text
.Length
+ (line
.soft_break
? 0 : crlf_size
);
3383 if (index
<= chars
) {
3384 // we found the line
3387 while (tag
!= null) {
3388 if (index
< (start
+ tag
.start
+ tag
.length
)) {
3390 tag_out
= LineTag
.GetFinalTag (tag
);
3391 pos
= index
- start
;
3394 if (tag
.next
== null) {
3397 next_line
= GetLine(line
.line_no
+ 1);
3399 if (next_line
!= null) {
3400 line_out
= next_line
;
3401 tag_out
= LineTag
.GetFinalTag (next_line
.tags
);
3406 tag_out
= LineTag
.GetFinalTag (tag
);
3407 pos
= line_out
.text
.Length
;
3416 line_out
= GetLine(lines
);
3417 tag
= line_out
.tags
;
3418 while (tag
.next
!= null) {
3422 pos
= line_out
.text
.Length
;
3425 internal int LineTagToCharIndex(Line line
, int pos
) {
3429 // Count first and last line
3432 // Count the lines in the middle
3434 for (i
= 1; i
< line
.line_no
; i
++) {
3435 length
+= GetLine(i
).text
.Length
+ crlf_size
;
3443 internal int SelectionLength() {
3444 if ((selection_start
.pos
== selection_end
.pos
) && (selection_start
.line
== selection_end
.line
)) {
3448 if (!multiline
|| (selection_start
.line
== selection_end
.line
)) {
3449 return selection_end
.pos
- selection_start
.pos
;
3456 // Count first and last line
3457 length
= selection_start
.line
.text
.Length
- selection_start
.pos
+ selection_end
.pos
+ crlf_size
;
3459 // Count the lines in the middle
3460 start
= selection_start
.line
.line_no
+ 1;
3461 end
= selection_end
.line
.line_no
;
3464 for (i
= start
; i
< end
; i
++) {
3465 length
+= GetLine(i
).text
.Length
+ crlf_size
;
3476 /// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
3477 internal Line
GetLine(int LineNo
) {
3478 Line line
= document
;
3480 while (line
!= sentinel
) {
3481 if (LineNo
== line
.line_no
) {
3483 } else if (LineNo
< line
.line_no
) {
3493 /// <summary>Retrieve the previous tag; walks line boundaries</summary>
3494 internal LineTag
PreviousTag(LineTag tag
) {
3497 if (tag
.previous
!= null) {
3498 return tag
.previous
;
3502 if (tag
.line
.line_no
== 1) {
3506 l
= GetLine(tag
.line
.line_no
- 1);
3511 while (t
.next
!= null) {
3520 /// <summary>Retrieve the next tag; walks line boundaries</summary>
3521 internal LineTag
NextTag(LineTag tag
) {
3524 if (tag
.next
!= null) {
3529 l
= GetLine(tag
.line
.line_no
+ 1);
3537 internal Line
ParagraphStart(Line line
) {
3538 while (line
.soft_break
) {
3539 line
= GetLine(line
.line_no
- 1);
3544 internal Line
ParagraphEnd(Line line
) {
3547 while (line
.soft_break
) {
3548 l
= GetLine(line
.line_no
+ 1);
3549 if ((l
== null) || (!l
.soft_break
)) {
3557 /// <summary>Give it a Y pixel coordinate and it returns the Line covering that Y coordinate</summary>
3558 internal Line
GetLineByPixel(int y
, bool exact
) {
3559 Line line
= document
;
3562 while (line
!= sentinel
) {
3564 if ((y
>= line
.Y
) && (y
< (line
.Y
+line
.height
))) {
3566 } else if (y
< line
.Y
) {
3579 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3580 internal LineTag
FindTag(int x
, int y
, out int index
, bool exact
) {
3584 line
= GetLineByPixel(y
, exact
);
3591 // Alignment adjustment
3592 x
+= line
.align_shift
;
3595 if (x
>= tag
.X
&& x
< (tag
.X
+tag
.width
)) {
3598 end
= tag
.start
+ tag
.length
- 1;
3600 for (int pos
= tag
.start
; pos
< end
; pos
++) {
3601 if (x
< line
.widths
[pos
]) {
3603 return LineTag
.GetFinalTag (tag
);
3607 return LineTag
.GetFinalTag (tag
);
3609 if (tag
.next
!= null) {
3617 index
= line
.text
.Length
;
3618 return LineTag
.GetFinalTag (tag
);
3623 // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
3624 internal LineTag
FindCursor(int x
, int y
, out int index
) {
3628 line
= GetLineByPixel(y
, false);
3631 // Adjust for alignment
3632 x
-= line
.align_shift
;
3635 if (x
>= tag
.X
&& x
< (tag
.X
+tag
.width
)) {
3638 end
= tag
.start
+ tag
.length
- 1;
3640 for (int pos
= tag
.start
-1; pos
< end
; pos
++) {
3641 // When clicking on a character, we position the cursor to whatever edge
3642 // of the character the click was closer
3643 if (x
< (line
.widths
[pos
] + ((line
.widths
[pos
+1]-line
.widths
[pos
])/2))) {
3651 if (tag
.next
!= null) {
3654 index
= line
.text
.Length
;
3660 /// <summary>Format area of document in specified font and color</summary>
3661 /// <param name="start_pos">1-based start position on start_line</param>
3662 /// <param name="end_pos">1-based end position on end_line </param>
3663 internal void FormatText (Line start_line
, int start_pos
, Line end_line
, int end_pos
, Font font
,
3664 SolidBrush color
, SolidBrush back_color
, FormatSpecified specified
)
3668 // First, format the first line
3669 if (start_line
!= end_line
) {
3671 LineTag
.FormatText(start_line
, start_pos
, start_line
.text
.Length
- start_pos
+ 1, font
, color
, back_color
, specified
);
3674 LineTag
.FormatText(end_line
, 1, end_pos
, font
, color
, back_color
, specified
);
3676 // Now all the lines inbetween
3677 for (int i
= start_line
.line_no
+ 1; i
< end_line
.line_no
; i
++) {
3679 LineTag
.FormatText(l
, 1, l
.text
.Length
, font
, color
, back_color
, specified
);
3682 // Special case, single line
3683 LineTag
.FormatText(start_line
, start_pos
, end_pos
- start_pos
, font
, color
, back_color
, specified
);
3687 /// <summary>Re-format areas of the document in specified font and color</summary>
3688 /// <param name="start_pos">1-based start position on start_line</param>
3689 /// <param name="end_pos">1-based end position on end_line </param>
3690 /// <param name="font">Font specifying attributes</param>
3691 /// <param name="color">Color (or NULL) to apply</param>
3692 /// <param name="apply">Attributes from font and color to apply</param>
3693 internal void FormatText(Line start_line
, int start_pos
, Line end_line
, int end_pos
, FontDefinition attributes
) {
3696 // First, format the first line
3697 if (start_line
!= end_line
) {
3699 LineTag
.FormatText(start_line
, start_pos
, start_line
.text
.Length
- start_pos
+ 1, attributes
);
3702 LineTag
.FormatText(end_line
, 1, end_pos
- 1, attributes
);
3704 // Now all the lines inbetween
3705 for (int i
= start_line
.line_no
+ 1; i
< end_line
.line_no
; i
++) {
3707 LineTag
.FormatText(l
, 1, l
.text
.Length
, attributes
);
3710 // Special case, single line
3711 LineTag
.FormatText(start_line
, start_pos
, end_pos
- start_pos
, attributes
);
3715 internal void RecalculateAlignments() {
3721 while (line_no
<= lines
) {
3722 line
= GetLine(line_no
);
3725 switch (line
.alignment
) {
3726 case HorizontalAlignment
.Left
:
3727 line
.align_shift
= 0;
3729 case HorizontalAlignment
.Center
:
3730 line
.align_shift
= (viewport_width
- (int)line
.widths
[line
.text
.Length
]) / 2;
3732 case HorizontalAlignment
.Right
:
3733 line
.align_shift
= viewport_width
- (int)line
.widths
[line
.text
.Length
];
3743 /// <summary>Calculate formatting for the whole document</summary>
3744 internal bool RecalculateDocument(Graphics g
) {
3745 return RecalculateDocument(g
, 1, this.lines
, false);
3748 /// <summary>Calculate formatting starting at a certain line</summary>
3749 internal bool RecalculateDocument(Graphics g
, int start
) {
3750 return RecalculateDocument(g
, start
, this.lines
, false);
3753 /// <summary>Calculate formatting within two given line numbers</summary>
3754 internal bool RecalculateDocument(Graphics g
, int start
, int end
) {
3755 return RecalculateDocument(g
, start
, end
, false);
3758 /// <summary>With optimize on, returns true if line heights changed</summary>
3759 internal bool RecalculateDocument(Graphics g
, int start
, int end
, bool optimize
) {
3767 if (recalc_suspended
> 0) {
3768 recalc_pending
= true;
3769 recalc_start
= Math
.Min (recalc_start
, start
);
3770 recalc_end
= Math
.Max (recalc_end
, end
);
3771 recalc_optimize
= optimize
;
3775 // Fixup the positions, they can go kinda nuts
3776 start
= Math
.Max (start
, 1);
3777 end
= Math
.Min (end
, lines
);
3779 Y
= GetLine(start
).Y
;
3784 changed
= true; // We always return true if we run non-optimized
3789 while (line_no
<= (end
+ this.lines
- shift
)) {
3790 line
= GetLine(line_no
++);
3795 line
.RecalculateLine(g
, this);
3797 if (line
.recalc
&& line
.RecalculateLine(g
, this)) {
3799 // If the height changed, all subsequent lines change
3806 line
.RecalculatePasswordLine(g
, this);
3808 if (line
.recalc
&& line
.RecalculatePasswordLine(g
, this)) {
3810 // If the height changed, all subsequent lines change
3817 if (line
.widths
[line
.text
.Length
] > new_width
) {
3818 new_width
= (int)line
.widths
[line
.text
.Length
];
3821 // Calculate alignment
3822 if (line
.alignment
!= HorizontalAlignment
.Left
) {
3823 if (line
.alignment
== HorizontalAlignment
.Center
) {
3824 line
.align_shift
= (viewport_width
- (int)line
.widths
[line
.text
.Length
]) / 2;
3826 line
.align_shift
= viewport_width
- (int)line
.widths
[line
.text
.Length
] - 1;
3832 if (line_no
> lines
) {
3837 if (document_x
!= new_width
) {
3838 document_x
= new_width
;
3839 if (WidthChanged
!= null) {
3840 WidthChanged(this, null);
3844 RecalculateAlignments();
3846 line
= GetLine(lines
);
3848 if (document_y
!= line
.Y
+ line
.height
) {
3849 document_y
= line
.Y
+ line
.height
;
3850 if (HeightChanged
!= null) {
3851 HeightChanged(this, null);
3858 internal int Size() {
3862 private void owner_HandleCreated(object sender
, EventArgs e
) {
3863 RecalculateDocument(owner
.CreateGraphicsInternal());
3867 private void owner_VisibleChanged(object sender
, EventArgs e
) {
3868 if (owner
.Visible
) {
3869 RecalculateDocument(owner
.CreateGraphicsInternal());
3873 internal static bool IsWordSeparator(char ch
) {
3887 internal int FindWordSeparator(Line line
, int pos
, bool forward
) {
3890 len
= line
.text
.Length
;
3893 for (int i
= pos
+ 1; i
< len
; i
++) {
3894 if (IsWordSeparator(line
.Text
[i
])) {
3900 for (int i
= pos
- 1; i
> 0; i
--) {
3901 if (IsWordSeparator(line
.Text
[i
- 1])) {
3909 /* Search document for text */
3910 internal bool FindChars(char[] chars
, Marker start
, Marker end
, out Marker result
) {
3916 // Search for occurence of any char in the chars array
3917 result
= new Marker();
3920 line_no
= start
.line
.line_no
;
3922 while (line_no
<= end
.line
.line_no
) {
3923 line_len
= line
.text
.Length
;
3924 while (pos
< line_len
) {
3925 for (int i
= 0; i
< chars
.Length
; i
++) {
3926 if (line
.text
[pos
] == chars
[i
]) {
3928 if ((line
.line_no
== end
.line
.line_no
) && (pos
>= end
.pos
)) {
3942 line
= GetLine(line_no
);
3948 // This version does not build one big string for searching, instead it handles
3949 // line-boundaries, which is faster and less memory intensive
3950 // FIXME - Depending on culture stuff we might have to create a big string and use culturespecific
3951 // search stuff and change it to accept and return positions instead of Markers (which would match
3952 // RichTextBox behaviour better but would be inconsistent with the rest of TextControl)
3953 internal bool Find(string search
, Marker start
, Marker end
, out Marker result
, RichTextBoxFinds options
) {
3955 string search_string
;
3967 result
= new Marker();
3968 word_option
= ((options
& RichTextBoxFinds
.WholeWord
) != 0);
3969 ignore_case
= ((options
& RichTextBoxFinds
.MatchCase
) == 0);
3970 reverse
= ((options
& RichTextBoxFinds
.Reverse
) != 0);
3973 line_no
= start
.line
.line_no
;
3977 // Prep our search string, lowercasing it if we do case-independent matching
3980 sb
= new StringBuilder(search
);
3981 for (int i
= 0; i
< sb
.Length
; i
++) {
3982 sb
[i
] = Char
.ToLower(sb
[i
]);
3984 search_string
= sb
.ToString();
3986 search_string
= search
;
3989 // We need to check if the character before our start position is a wordbreak
3992 if ((pos
== 0) || (IsWordSeparator(line
.text
[pos
- 1]))) {
3999 if (IsWordSeparator(line
.text
[pos
- 1])) {
4005 // Need to check the end of the previous line
4008 prev_line
= GetLine(line_no
- 1);
4009 if (prev_line
.soft_break
) {
4010 if (IsWordSeparator(prev_line
.text
[prev_line
.text
.Length
- 1])) {
4024 // To avoid duplication of this loop with reverse logic, we search
4025 // through the document, remembering the last match and when returning
4026 // report that last remembered match
4028 last
= new Marker();
4029 last
.height
= -1; // Abused - we use it to track change
4031 while (line_no
<= end
.line
.line_no
) {
4032 if (line_no
!= end
.line
.line_no
) {
4033 line_len
= line
.text
.Length
;
4038 while (pos
< line_len
) {
4039 if (word_option
&& (current
== search_string
.Length
)) {
4040 if (IsWordSeparator(line
.text
[pos
])) {
4053 c
= Char
.ToLower(line
.text
[pos
]);
4058 if (c
== search_string
[current
]) {
4063 if (!word_option
|| (word_option
&& (word
|| (current
> 0)))) {
4067 if (!word_option
&& (current
== search_string
.Length
)) {
4084 if (IsWordSeparator(c
)) {
4092 // Mark that we just saw a word boundary
4093 if (!line
.soft_break
) {
4097 if (current
== search_string
.Length
) {
4113 line
= GetLine(line_no
);
4117 if (last
.height
!= -1) {
4127 // if ((line.line_no == end.line.line_no) && (pos >= end.pos)) {
4139 internal void GetMarker(out Marker mark
, bool start
) {
4140 mark
= new Marker();
4143 mark
.line
= GetLine(1);
4144 mark
.tag
= mark
.line
.tags
;
4147 mark
.line
= GetLine(lines
);
4148 mark
.tag
= mark
.line
.tags
;
4149 while (mark
.tag
.next
!= null) {
4150 mark
.tag
= mark
.tag
.next
;
4152 mark
.pos
= mark
.line
.text
.Length
;
4155 #endregion // Internal Methods
4158 internal event EventHandler CaretMoved
;
4159 internal event EventHandler WidthChanged
;
4160 internal event EventHandler HeightChanged
;
4161 internal event EventHandler LengthChanged
;
4162 #endregion // Events
4164 #region Administrative
4165 public IEnumerator
GetEnumerator() {
4170 public override bool Equals(object obj
) {
4175 if (!(obj
is Document
)) {
4183 if (ToString().Equals(((Document
)obj
).ToString())) {
4190 public override int GetHashCode() {
4194 public override string ToString() {
4195 return "document " + this.document_id
;
4197 #endregion // Administrative
4200 internal class ImageTag
: LineTag
{
4202 internal Image image
;
4204 internal ImageTag (Line line
, int start
, Image image
) : base (line
, start
)
4209 internal override SizeF
SizeOfPosition (Graphics dc
, int pos
)
4214 internal override int MaxHeight ()
4216 return image
.Height
;
4219 internal override void Draw (Graphics dc
, Brush brush
, float x
, float y
, int start
, int end
)
4221 dc
.DrawImage (image
, x
, y
);
4225 internal class LineTag
{
4226 #region Local Variables;
4227 // Payload; formatting
4228 internal Font font
; // System.Drawing.Font object for this tag
4229 internal SolidBrush color
; // The font color for this tag
4231 // In 2.0 tags can have background colours. I'm not going to #ifdef
4232 // at this level though since I want to reduce code paths
4233 internal SolidBrush back_color
;
4236 internal int start
; // start, in chars; index into Line.text
4237 internal bool r_to_l
; // Which way is the font
4240 internal int height
; // Height in pixels of the text this tag describes
4241 internal int X
; // X location of the text this tag describes
4243 internal int ascent
; // Ascent of the font for this tag
4244 internal int shift
; // Shift down for this tag, to stay on baseline
4247 internal Line line
; // The line we're on
4248 internal LineTag next
; // Next tag on the same line
4249 internal LineTag previous
; // Previous tag on the same line
4252 #region Constructors
4253 internal LineTag(Line line
, int start
) {
4258 #endregion // Constructors
4260 #region Internal Methods
4263 get { return start + length; }
4266 public float width
{
4270 return line
.widths
[start
+ length
- 1];
4278 res
= next
.start
- start
;
4280 res
= line
.text
.Length
- (start
- 1);
4282 return res
> 0 ? res
: 0;
4286 internal virtual SizeF
SizeOfPosition (Graphics dc
, int pos
)
4288 return dc
.MeasureString (line
.text
.ToString (pos
, 1), font
, 10000, Document
.string_format
);
4291 internal virtual int MaxHeight ()
4296 internal virtual void Draw (Graphics dc
, Brush brush
, float x
, float y
, int start
, int end
)
4298 dc
.DrawString (line
.text
.ToString (start
, end
), font
, brush
, x
, y
, StringFormat
.GenericTypographic
);
4301 ///<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>
4302 internal LineTag
Break(int pos
) {
4307 if (pos
== this.start
) {
4309 } else if (pos
>= (start
+ length
)) {
4313 new_tag
= new LineTag(line
, pos
);
4314 new_tag
.CopyFormattingFrom (this);
4316 new_tag
.next
= this.next
;
4317 this.next
= new_tag
;
4318 new_tag
.previous
= this;
4320 if (new_tag
.next
!= null) {
4321 new_tag
.next
.previous
= new_tag
;
4327 public string Text ()
4329 return line
.text
.ToString (start
- 1, length
);
4332 public void CopyFormattingFrom (LineTag other
)
4334 height
= other
.height
;
4336 color
= other
.color
;
4337 back_color
= other
.back_color
;
4340 ///<summary>Create new font and brush from existing font and given new attributes. Returns true if fontheight changes</summary>
4341 internal static bool GenerateTextFormat(Font font_from
, SolidBrush color_from
, FontDefinition attributes
, out Font new_font
, out SolidBrush new_color
) {
4347 if (attributes
.font_obj
== null) {
4348 size
= font_from
.SizeInPoints
;
4349 unit
= font_from
.Unit
;
4350 face
= font_from
.Name
;
4351 style
= font_from
.Style
;
4353 if (attributes
.face
!= null) {
4354 face
= attributes
.face
;
4357 if (attributes
.size
!= 0) {
4358 size
= attributes
.size
;
4361 style
|= attributes
.add_style
;
4362 style
&= ~attributes
.remove_style
;
4365 new_font
= new Font(face
, size
, style
, unit
);
4367 new_font
= attributes
.font_obj
;
4370 // Create 'new' color brush
4371 if (attributes
.color
!= Color
.Empty
) {
4372 new_color
= new SolidBrush(attributes
.color
);
4374 new_color
= color_from
;
4377 if (new_font
.Height
== font_from
.Height
) {
4383 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
4384 /// Removes any previous tags overlapping the same area;
4385 /// returns true if lineheight has changed</summary>
4386 /// <param name="start">1-based character position on line</param>
4387 internal static bool FormatText(Line line
, int start
, int length
, Font font
, SolidBrush color
, SolidBrush back_color
, FormatSpecified specified
)
4393 bool retval
= false; // Assume line-height doesn't change
4396 if (((FormatSpecified
.Font
& specified
) == FormatSpecified
.Font
) && font
.Height
!= line
.height
) {
4399 line
.recalc
= true; // This forces recalculation of the line in RecalculateDocument
4401 // A little sanity, not sure if it's needed, might be able to remove for speed
4402 if (length
> line
.text
.Length
) {
4403 length
= line
.text
.Length
;
4407 end
= start
+ length
;
4409 // Common special case
4410 if ((start
== 1) && (length
== tag
.length
)) {
4412 SetFormat (tag
, font
, color
, back_color
, specified
);
4416 start_tag
= FindTag (line
, start
);
4418 tag
= start_tag
.Break (start
);
4420 while (tag
!= null && tag
.end
<= end
) {
4421 SetFormat (tag
, font
, color
, back_color
, specified
);
4425 if (end
!= line
.text
.Length
) {
4426 /// Now do the last tag
4427 end_tag
= FindTag (line
, end
);
4429 if (end_tag
!= null) {
4430 end_tag
.Break (end
);
4431 SetFormat (end_tag
, font
, color
, back_color
, specified
);
4438 private static void SetFormat (LineTag tag
, Font font
, SolidBrush color
, SolidBrush back_color
, FormatSpecified specified
)
4440 if ((FormatSpecified
.Font
& specified
) == FormatSpecified
.Font
)
4442 if ((FormatSpecified
.Color
& specified
) == FormatSpecified
.Color
)
4444 if ((FormatSpecified
.BackColor
& specified
) == FormatSpecified
.BackColor
) {
4445 tag
.back_color
= back_color
;
4447 // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
4450 /// <summary>Applies font attributes specified to characters starting at 'start' for 'length' chars;
4451 /// Breaks tags at start and end point, keeping middle tags with altered attributes.
4452 /// Returns true if lineheight has changed</summary>
4453 /// <param name="start">1-based character position on line</param>
4454 internal static bool FormatText(Line line
, int start
, int length
, FontDefinition attributes
) {
4458 bool retval
= false; // Assume line-height doesn't change
4460 line
.recalc
= true; // This forces recalculation of the line in RecalculateDocument
4462 // A little sanity, not sure if it's needed, might be able to remove for speed
4463 if (length
> line
.text
.Length
) {
4464 length
= line
.text
.Length
;
4469 // Common special case
4470 if ((start
== 1) && (length
== tag
.length
)) {
4472 GenerateTextFormat(tag
.font
, tag
.color
, attributes
, out tag
.font
, out tag
.color
);
4476 start_tag
= FindTag(line
, start
);
4478 if (start_tag
== null) {
4480 // We are 'starting' after all valid tags; create a new tag with the right attributes
4481 start_tag
= FindTag(line
, line
.text
.Length
- 1);
4482 start_tag
.next
= new LineTag(line
, line
.text
.Length
+ 1);
4483 start_tag
.next
.CopyFormattingFrom (start_tag
);
4484 start_tag
.next
.previous
= start_tag
;
4485 start_tag
= start_tag
.next
;
4487 throw new Exception(String
.Format("Could not find start_tag in document at line {0} position {1}", line
.line_no
, start
));
4490 start_tag
= start_tag
.Break(start
);
4493 end_tag
= FindTag(line
, start
+ length
);
4494 if (end_tag
!= null) {
4495 end_tag
= end_tag
.Break(start
+ length
);
4498 // start_tag or end_tag might be null; we're cool with that
4499 // we now walk from start_tag to end_tag, applying new attributes
4501 while ((tag
!= null) && tag
!= end_tag
) {
4502 if (LineTag
.GenerateTextFormat(tag
.font
, tag
.color
, attributes
, out tag
.font
, out tag
.color
)) {
4511 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
4512 internal static LineTag
FindTag(Line line
, int pos
) {
4513 LineTag tag
= line
.tags
;
4515 // Beginning of line is a bit special
4517 // Not sure if we should get the final tag here
4521 while (tag
!= null) {
4522 if ((tag
.start
<= pos
) && (pos
<= tag
.end
)) {
4523 return GetFinalTag (tag
);
4532 // There can be multiple tags at the same position, we want to make
4533 // sure we are using the very last tag at the given position
4534 internal static LineTag
GetFinalTag (LineTag tag
)
4538 while (res
.next
!= null && res
.next
.length
== 0)
4543 /// <summary>Combines 'this' tag with 'other' tag</summary>
4544 internal bool Combine(LineTag other
) {
4545 if (!this.Equals(other
)) {
4549 this.next
= other
.next
;
4550 if (this.next
!= null) {
4551 this.next
.previous
= this;
4558 /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
4559 internal bool Remove() {
4560 if ((this.start
== 1) && (this.next
== null)) {
4561 // We cannot remove the only tag
4564 if (this.start
!= 1) {
4565 this.previous
.next
= this.next
;
4566 this.next
.previous
= this.previous
;
4568 this.next
.start
= 1;
4569 this.line
.tags
= this.next
;
4570 this.next
.previous
= null;
4576 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
4577 public override bool Equals(object obj
) {
4584 if (!(obj
is LineTag
)) {
4592 other
= (LineTag
)obj
;
4594 if (this.font
.Equals(other
.font
) && this.color
.Equals(other
.color
)) { // FIXME add checking for things like link or type later
4601 public override int GetHashCode() {
4602 return base.GetHashCode ();
4605 public override string ToString() {
4607 return "Tag starts at index " + this.start
+ " length " + this.length
+ " text: " + Text () + "Font " + this.font
.ToString();
4608 return "Zero Lengthed tag at index " + this.start
;
4611 #endregion // Internal Methods
4614 internal class UndoClass
{
4615 internal enum ActionType
{
4626 internal class Action
{
4627 internal ActionType type
;
4628 internal int line_no
;
4630 internal object data
;
4633 #region Local Variables
4634 private Document document
;
4635 private Stack undo_actions
;
4636 private Stack redo_actions
;
4638 private int undo_levels
;
4639 private int redo_levels
;
4640 private int caret_line
;
4641 private int caret_pos
;
4642 #endregion // Local Variables
4644 #region Constructors
4645 internal UndoClass(Document doc
) {
4647 undo_actions
= new Stack(50);
4648 redo_actions
= new Stack(50);
4650 #endregion // Constructors
4653 internal int UndoLevels
{
4659 internal int RedoLevels
{
4665 internal string UndoName
{
4669 action
= (Action
)undo_actions
.Peek();
4671 if (action
.type
== ActionType
.CompoundEnd
)
4672 return (string) action
.data
;
4674 switch(action
.type
) {
4675 case ActionType
.InsertChar
: {
4676 Locale
.GetText("Insert character");
4680 case ActionType
.DeleteChar
: {
4681 Locale
.GetText("Delete character");
4685 case ActionType
.InsertString
: {
4686 Locale
.GetText("Insert string");
4690 case ActionType
.DeleteChars
: {
4691 Locale
.GetText("Delete string");
4695 case ActionType
.CursorMove
: {
4696 Locale
.GetText("Cursor move");
4704 internal string RedoName() {
4707 #endregion // Properties
4709 #region Internal Methods
4710 internal void Clear() {
4711 undo_actions
.Clear();
4712 redo_actions
.Clear();
4717 internal void Undo() {
4719 int compound_stack
= 0;
4721 if (undo_actions
.Count
== 0) {
4728 action
= (Action
)undo_actions
.Pop();
4730 // Put onto redo stack
4731 redo_actions
.Push(action
);
4734 switch(action
.type
) {
4735 case ActionType
.CompoundEnd
:
4739 case ActionType
.CompoundBegin
:
4745 case ActionType
.InsertString
:
4746 document
.DeleteMultiline (document
.GetLine (action
.line_no
),
4747 action
.pos
, ((string) action
.data
).Length
+ 1);
4750 case ActionType
.InsertChar
: {
4751 // FIXME - implement me
4755 case ActionType
.DeleteChars
: {
4756 this.Insert(document
.GetLine(action
.line_no
), action
.pos
, (Line
)action
.data
);
4757 Undo(); // Grab the cursor location
4761 case ActionType
.CursorMove
: {
4762 document
.caret
.line
= document
.GetLine(action
.line_no
);
4763 if (document
.caret
.line
== null) {
4768 document
.caret
.tag
= document
.caret
.line
.FindTag(action
.pos
);
4769 document
.caret
.pos
= action
.pos
;
4770 document
.caret
.height
= document
.caret
.tag
.height
;
4772 if (document
.owner
.IsHandleCreated
) {
4773 XplatUI
.DestroyCaret(document
.owner
.Handle
);
4774 XplatUI
.CreateCaret(document
.owner
.Handle
, 2, document
.caret
.height
);
4775 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
);
4777 document
.DisplayCaret ();
4780 // FIXME - enable call
4781 //if (document.CaretMoved != null) document.CaretMoved(this, EventArgs.Empty);
4785 } while (compound_stack
> 0);
4788 internal void Redo() {
4789 if (redo_actions
.Count
== 0) {
4793 #endregion // Internal Methods
4795 #region Private Methods
4797 public void BeginCompoundAction ()
4799 Action cb
= new Action ();
4800 cb
.type
= ActionType
.CompoundBegin
;
4802 undo_actions
.Push (cb
);
4805 public void EndCompoundAction ()
4807 Action ce
= new Action ();
4808 ce
.type
= ActionType
.CompoundEnd
;
4810 undo_actions
.Push (ce
);
4815 public void RecordDeleteChars(Line line
, int pos
, int length
) {
4816 RecordDelete(line
, pos
, line
, pos
+ length
- 1);
4819 // start_pos, end_pos = 1 based
4820 public void RecordDelete(Line start_line
, int start_pos
, Line end_line
, int end_pos
) {
4824 l
= Duplicate(start_line
, start_pos
, end_line
, end_pos
);
4827 a
.type
= ActionType
.DeleteChars
;
4829 a
.line_no
= start_line
.line_no
;
4830 a
.pos
= start_pos
- 1;
4832 // Record the cursor position before, since the actions will occur in reverse order
4834 undo_actions
.Push(a
);
4837 public void RecordInsertString (Line line
, int pos
, string str
)
4839 Action a
= new Action ();
4841 a
.type
= ActionType
.InsertString
;
4843 a
.line_no
= line
.line_no
;
4846 undo_actions
.Push (a
);
4849 public void RecordCursor() {
4850 if (document
.caret
.line
== null) {
4854 RecordCursor(document
.caret
.line
, document
.caret
.pos
);
4857 public void RecordCursor(Line line
, int pos
) {
4860 if ((line
.line_no
== caret_line
) && (pos
== caret_pos
)) {
4864 caret_line
= line
.line_no
;
4868 a
.type
= ActionType
.CursorMove
;
4869 a
.line_no
= line
.line_no
;
4872 undo_actions
.Push(a
);
4875 // start_pos = 1-based
4876 // end_pos = 1-based
4877 public Line
Duplicate(Line start_line
, int start_pos
, Line end_line
, int end_pos
) {
4882 LineTag current_tag
;
4890 for (int i
= start_line
.line_no
; i
<= end_line
.line_no
; i
++) {
4891 current
= document
.GetLine(i
);
4893 if (start_line
.line_no
== i
) {
4899 if (end_line
.line_no
== i
) {
4902 end
= current
.text
.Length
;
4906 line
.text
= new StringBuilder(current
.text
.ToString(start
- 1, end
- start
+ 1));
4908 // Copy tags from start to start+length onto new line
4909 current_tag
= current
.FindTag(start
- 1);
4910 while ((current_tag
!= null) && (current_tag
.start
< end
)) {
4911 if ((current_tag
.start
<= start
) && (start
< (current_tag
.start
+ current_tag
.length
))) {
4912 // start tag is within this tag
4915 tag_start
= current_tag
.start
;
4918 tag
= new LineTag(line
, tag_start
- start
+ 1);
4919 tag
.CopyFormattingFrom (current_tag
);
4921 current_tag
= current_tag
.next
;
4923 // Add the new tag to the line
4924 if (line
.tags
== null) {
4930 while (tail
.next
!= null) {
4934 tag
.previous
= tail
;
4938 if ((i
+ 1) <= end_line
.line_no
) {
4939 line
.soft_break
= current
.soft_break
;
4941 // Chain them (we use right/left as next/previous)
4942 line
.right
= new Line();
4943 line
.right
.left
= line
;
4951 // Insert multi-line text at the given position; use formatting at insertion point for inserted text
4952 internal void Insert(Line line
, int pos
, Line insert
) {
4959 // Handle special case first
4960 if (insert
.right
== null) {
4962 // Single line insert
4963 document
.Split(line
, pos
);
4965 if (insert
.tags
== null) {
4966 return; // Blank line
4969 //Insert our tags at the end
4972 while (tag
.next
!= null) {
4976 offset
= tag
.start
+ tag
.length
- 1;
4978 tag
.next
= insert
.tags
;
4979 line
.text
.Insert(offset
, insert
.text
.ToString());
4981 // Adjust start locations
4983 while (tag
!= null) {
4984 tag
.start
+= offset
;
4988 // Put it back together
4989 document
.Combine(line
.line_no
, line
.line_no
+ 1);
4990 document
.UpdateView(line
, pos
);
4997 while (current
!= null) {
4998 if (current
== insert
) {
4999 // Inserting the first line we split the line (and make space)
5000 document
.Split(line
, pos
);
5001 //Insert our tags at the end of the line
5005 while (tag
.next
!= null) {
5008 offset
= tag
.start
+ tag
.length
- 1;
5009 tag
.next
= current
.tags
;
5010 tag
.next
.previous
= tag
;
5016 line
.tags
= current
.tags
;
5017 line
.tags
.previous
= null;
5021 document
.Split(line
.line_no
, 0);
5023 line
.tags
= current
.tags
;
5024 line
.tags
.previous
= null;
5027 // Adjust start locations and line pointers
5028 while (tag
!= null) {
5029 tag
.start
+= offset
;
5034 line
.text
.Insert(offset
, current
.text
.ToString());
5035 line
.Grow(line
.text
.Length
);
5038 line
= document
.GetLine(line
.line_no
+ 1);
5040 // FIXME? Test undo of line-boundaries
5041 if ((current
.right
== null) && (current
.tags
.length
!= 0)) {
5042 document
.Combine(line
.line_no
- 1, line
.line_no
);
5044 current
= current
.right
;
5049 // Recalculate our document
5050 document
.UpdateView(first
, lines
, pos
);
5053 #endregion // Private Methods