}
internal enum LineEnding {
- Wrap, // line wraps to the next line
- Limp, // \r
- Hard, // \r\n
- Soft, // \r\r\n
- Rich, // \n
+ Wrap = 1, // line wraps to the next line
+ Limp = 2, // \r
+ Hard = 4, // \r\n
+ Soft = 8, // \r\r\n
+ Rich = 16, // \n
- None
+ None = 0
}
- // Being cloneable should allow for nice line and document copies...
- internal class Line : ICloneable, IComparable {
- #region Local Variables
-
- internal Document document;
-
- // Stuff that matters for our line
- internal StringBuilder text; // Characters for the line
- internal float[] widths; // Width of each character; always one larger than text.Length
- internal int space; // Number of elements in text and widths
- internal int line_no; // Line number
- internal LineTag tags; // Tags describing the text
- internal int offset; // Baseline can be on the X or Y axis depending if we are in multiline mode or not
- internal int height; // Height of the line (height of tallest tag)
- internal int ascent; // Ascent of the line (ascent of the tallest tag)
- internal HorizontalAlignment alignment; // Alignment of the line
- internal int align_shift; // Pixel shift caused by the alignment
- internal int indent; // Left indent for the first line
- internal int hanging_indent; // Hanging indent (left indent for all but the first line)
- internal int right_indent; // Right indent for all lines
- internal LineEnding ending;
-
-
- // Stuff that's important for the tree
- internal Line parent; // Our parent line
- internal Line left; // Line with smaller line number
- internal Line right; // Line with higher line number
- internal LineColor color; // We're doing a black/red tree. this is the node color
- internal int DEFAULT_TEXT_LEN; //
- internal bool recalc; // Line changed
- #endregion // Local Variables
-
- #region Constructors
- internal Line (Document document, LineEnding ending)
- {
- this.document = document;
- color = LineColor.Red;
- left = null;
- right = null;
- parent = null;
- text = null;
- recalc = true;
- alignment = document.alignment;
-
- this.ending = ending;
- }
-
- internal Line(Document document, int LineNo, string Text, Font font, SolidBrush color, LineEnding ending) : this (document, ending)
- {
- space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
-
- text = new StringBuilder(Text, space);
- line_no = LineNo;
- this.ending = ending;
-
- widths = new float[space + 1];
-
-
- tags = new LineTag(this, 1);
- tags.font = font;
- tags.color = color;
- }
-
- internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending) : this(document, ending)
- {
- space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
-
- text = new StringBuilder(Text, space);
- line_no = LineNo;
- this.ending = ending;
- alignment = align;
-
- widths = new float[space + 1];
-
-
- tags = new LineTag(this, 1);
- tags.font = font;
- tags.color = color;
- }
-
- internal Line(Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending)
- {
- space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
-
- text = new StringBuilder(Text, space);
- this.ending = ending;
- line_no = LineNo;
-
- widths = new float[space + 1];
- tags = tag;
- }
-
- #endregion // Constructors
-
- #region Internal Properties
-
- internal int Y {
- get {
- if (!document.multiline)
- return document.top_margin;
- return document.top_margin + offset;
- }
- }
-
- internal int X {
- get {
- if (document.multiline)
- return align_shift;
- return offset + align_shift;
- }
- }
-
- internal int Width {
- get {
- int res = (int) widths [text.Length];
- if (!document.multiline) {
-
- }
- return res;
- }
- }
-
- internal int Indent {
- get {
- return indent;
- }
-
- set {
- indent = value;
- recalc = true;
- }
- }
-
- internal int HangingIndent {
- get {
- return hanging_indent;
- }
-
- set {
- hanging_indent = value;
- recalc = true;
- }
- }
-
- internal int RightIndent {
- get {
- return right_indent;
- }
-
- set {
- right_indent = value;
- recalc = true;
- }
- }
-
-
- internal int Height {
- get {
- return height;
- }
-
- set {
- height = value;
- }
- }
-
- internal int LineNo {
- get {
- return line_no;
- }
-
- set {
- line_no = value;
- }
- }
-
- internal string Text {
- get {
- return text.ToString();
- }
-
- set {
- text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
- }
- }
-
- internal HorizontalAlignment Alignment {
- get {
- return alignment;
- }
-
- set {
- if (alignment != value) {
- alignment = value;
- recalc = true;
- }
- }
- }
-#if no
- internal StringBuilder Text {
- get {
- return text;
- }
-
- set {
- text = value;
- }
- }
-#endif
- #endregion // Internal Properties
-
- #region Internal Methods
-
- // This doesn't do exactly what you would think, it just pulls of the \n part of the ending
- internal string TextWithoutEnding ()
- {
- return text.ToString (0, text.Length - document.LineEndingLength (ending));
- }
-
- internal int TextLengthWithoutEnding ()
- {
- return text.Length - document.LineEndingLength (ending);
- }
-
- internal void DrawEnding (Graphics dc, float y)
- {
- if (document.multiline)
- return;
- LineTag last = tags;
- while (last.next != null)
- last = last.next;
-
- string end_str = null;
- switch (document.LineEndingLength (ending)) {
- case 0:
- return;
- case 1:
- end_str = "\u0013";
- break;
- case 2:
- end_str = "\u0013\u0013";
- break;
- case 3:
- end_str = "\u0013\u0013\u0013";
- break;
- }
- dc.DrawString (end_str, last.font, last.color, X + widths [TextLengthWithoutEnding ()] - document.viewport_x,
- y, Document.string_format);
- }
-
-
- // Make sure we always have enoughs space in text and widths
- internal void Grow(int minimum) {
- int length;
- float[] new_widths;
-
- length = text.Length;
-
- if ((length + minimum) > space) {
- // We need to grow; double the size
-
- if ((length + minimum) > (space * 2)) {
- new_widths = new float[length + minimum * 2 + 1];
- space = length + minimum * 2;
- } else {
- new_widths = new float[space * 2 + 1];
- space *= 2;
- }
- widths.CopyTo(new_widths, 0);
-
- widths = new_widths;
- }
- }
-
- internal void Streamline(int lines) {
- LineTag current;
- LineTag next;
-
- current = this.tags;
- next = current.next;
-
- //
- // Catch what the loop below wont; eliminate 0 length
- // tags, but only if there are other tags after us
- // We only eliminate text tags if there is another text tag
- // after it. Otherwise we wind up trying to type on picture tags
- //
- while ((current.length == 0) && (next != null) && (next.IsTextTag)) {
- tags = next;
- tags.previous = null;
- current = next;
- next = current.next;
- }
-
-
- if (next == null) {
- return;
- }
-
- while (next != null) {
- // Take out 0 length tags unless it's the last tag in the document
- if (current.IsTextTag && next.length == 0 && next.IsTextTag) {
- if ((next.next != null) || (line_no != lines)) {
- current.next = next.next;
- if (current.next != null) {
- current.next.previous = current;
- }
- next = current.next;
- continue;
- }
- }
- if (current.Combine(next)) {
- next = current.next;
- continue;
- }
-
- current = current.next;
- next = current.next;
- }
- }
-
- /// <summary> Find the tag on a line based on the character position, pos is 0-based</summary>
- internal LineTag FindTag(int pos) {
- LineTag tag;
-
- if (pos == 0) {
- return tags;
- }
-
- tag = this.tags;
-
- if (pos >= text.Length) {
- pos = text.Length - 1;
- }
-
- while (tag != null) {
- if (((tag.start - 1) <= pos) && (pos < (tag.start + tag.length - 1))) {
- return LineTag.GetFinalTag (tag);
- }
- tag = tag.next;
- }
- return null;
- }
-
- /// <summary>
- /// Recalculate a single line using the same char for every character in the line
- /// </summary>
-
- internal bool RecalculatePasswordLine(Graphics g, Document doc) {
- LineTag tag;
- int pos;
- int len;
- float w;
- bool ret;
- int descent;
-
- pos = 0;
- len = this.text.Length;
- tag = this.tags;
- ascent = 0;
- tag.shift = 0;
-
- this.recalc = false;
- widths[0] = document.left_margin + indent;
-
- w = g.MeasureString(doc.password_char, tags.font, 10000, Document.string_format).Width;
-
- if (this.height != (int)tag.font.Height) {
- ret = true;
- } else {
- ret = false;
- }
-
- this.height = (int)tag.font.Height;
- tag.height = this.height;
-
- XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
- this.ascent = tag.ascent;
-
- while (pos < len) {
- pos++;
- widths[pos] = widths[pos-1] + w;
- }
-
- return ret;
- }
-
- /// <summary>
- /// Go through all tags on a line and recalculate all size-related values;
- /// returns true if lineheight changed
- /// </summary>
- internal bool RecalculateLine(Graphics g, Document doc) {
- LineTag tag;
- int pos;
- int len;
- SizeF size;
- float w;
- int prev_offset;
- bool retval;
- bool wrapped;
- Line line;
- int wrap_pos;
-
- pos = 0;
- len = this.text.Length;
- tag = this.tags;
- prev_offset = this.offset; // For drawing optimization calculations
- this.height = 0; // Reset line height
- this.ascent = 0; // Reset the ascent for the line
- tag.shift = 0;
-
- if (ending == LineEnding.Wrap) {
- widths[0] = document.left_margin + hanging_indent;
- } else {
- widths[0] = document.left_margin + indent;
- }
-
- this.recalc = false;
- retval = false;
- wrapped = false;
-
- wrap_pos = 0;
-
- while (pos < len) {
-
- while (tag.length == 0) { // We should always have tags after a tag.length==0 unless len==0
- tag.ascent = 0;
- tag.shift = 0;
- tag = tag.next;
- }
-
- size = tag.SizeOfPosition (g, pos);
- w = size.Width;
-
- if (Char.IsWhiteSpace(text[pos])) {
- wrap_pos = pos + 1;
- }
-
- if (doc.wrap) {
- if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) {
- // Make sure to set the last width of the line before wrapping
- widths [pos + 1] = widths [pos] + w;
-
- pos = wrap_pos;
- len = text.Length;
- doc.Split(this, tag, pos);
- ending = LineEnding.Wrap;
- len = this.text.Length;
-
- retval = true;
- wrapped = true;
- } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) {
- // No suitable wrap position was found so break right in the middle of a word
-
- // Make sure to set the last width of the line before wrapping
- widths [pos + 1] = widths [pos] + w;
-
- doc.Split(this, tag, pos);
- ending = LineEnding.Wrap;
- len = this.text.Length;
- retval = true;
- wrapped = true;
- }
- }
-
- // Contract all wrapped lines that follow back into our line
- if (!wrapped) {
- pos++;
-
- widths[pos] = widths[pos-1] + w;
-
- if (pos == len) {
- line = doc.GetLine(this.line_no + 1);
- if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) {
- // Pull the two lines together
- doc.Combine(this.line_no, this.line_no + 1);
- len = this.text.Length;
- retval = true;
- }
- }
- }
-
- if (pos == (tag.start-1 + tag.length)) {
- // We just found the end of our current tag
- tag.height = tag.MaxHeight ();
-
- // Check if we're the tallest on the line (so far)
- if (tag.height > this.height) {
- this.height = tag.height; // Yep; make sure the line knows
- }
-
- if (tag.ascent == 0) {
- int descent;
-
- XplatUI.GetFontMetrics(g, tag.font, out tag.ascent, out descent);
- }
-
- if (tag.ascent > this.ascent) {
- LineTag t;
-
- // We have a tag that has a taller ascent than the line;
- t = tags;
- while (t != null && t != tag) {
- t.shift = tag.ascent - t.ascent;
- t = t.next;
- }
-
- // Save on our line
- this.ascent = tag.ascent;
- } else {
- tag.shift = this.ascent - tag.ascent;
- }
-
- tag = tag.next;
- if (tag != null) {
- tag.shift = 0;
- wrap_pos = pos;
- }
- }
- }
-
- if (this.height == 0) {
- this.height = tags.font.Height;
- tag.height = this.height;
- }
-
- if (prev_offset != offset) {
- retval = true;
- }
- return retval;
- }
- #endregion // Internal Methods
-
- #region Administrative
- public int CompareTo(object obj) {
- if (obj == null) {
- return 1;
- }
-
- if (! (obj is Line)) {
- throw new ArgumentException("Object is not of type Line", "obj");
- }
-
- if (line_no < ((Line)obj).line_no) {
- return -1;
- } else if (line_no > ((Line)obj).line_no) {
- return 1;
- } else {
- return 0;
- }
- }
-
- public object Clone() {
- Line clone;
-
- clone = new Line (document, ending);
-
- clone.text = text;
-
- if (left != null) {
- clone.left = (Line)left.Clone();
- }
-
- if (left != null) {
- clone.left = (Line)left.Clone();
- }
-
- return clone;
- }
-
- internal object CloneLine() {
- Line clone;
-
- clone = new Line (document, ending);
-
- clone.text = text;
-
- return clone;
- }
-
- public override bool Equals(object obj) {
- if (obj == null) {
- return false;
- }
-
- if (!(obj is Line)) {
- return false;
- }
-
- if (obj == this) {
- return true;
- }
-
- if (line_no == ((Line)obj).line_no) {
- return true;
- }
-
- return false;
- }
-
- public override int GetHashCode() {
- return base.GetHashCode ();
- }
-
- public override string ToString() {
- return "Line " + line_no;
- }
-
- #endregion // Administrative
- }
-
internal class Document : ICloneable, IEnumerable {
#region Structures
// FIXME - go through code and check for places where
private StringBuilder password_cache;
private bool calc_pass;
private int char_count;
+ private bool enable_links;
// For calculating widths/heights
public static readonly StringFormat string_format = new StringFormat (StringFormat.GenericTypographic);
internal int viewport_x;
internal int viewport_y; // The visible area of the document
+ internal int offset_x;
+ internal int offset_y;
internal int viewport_width;
internal int viewport_height;
internal int document_x; // Width of the document
internal int document_y; // Height of the document
- internal Rectangle invalid;
-
internal int crlf_size; // 1 or 2, depending on whether we use \r\n or just \n
internal TextBoxBase owner; // Who's owning us?
owner.HandleCreated += new EventHandler(owner_HandleCreated);
owner.VisibleChanged += new EventHandler(owner_VisibleChanged);
- Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
+ Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
undo = new UndoManager (this);
viewport_x = 0;
viewport_y = 0;
+ offset_x = 0;
+ offset_y = 0;
+
crlf_size = 2;
// Default selection is empty
}
}
+ // UIA: Method used via reflection in TextRangeProvider
internal int Lines {
get {
return lines;
internal Point Caret {
get {
- return new Point((int)caret.tag.line.widths[caret.pos] + caret.line.X, caret.line.Y);
+ return new Point((int)caret.tag.Line.widths[caret.pos] + caret.line.X, caret.line.Y);
}
}
}
}
+ /// <summary>
+ /// Whether text is scanned for links
+ /// </summary>
+ internal bool EnableLinks {
+ get { return enable_links; }
+ set { enable_links = value; }
+ }
+
internal string PasswordChar {
get {
return password_char;
}
}
+ internal int OffsetX
+ {
+ get
+ {
+ return offset_x;
+ }
+
+ set
+ {
+ offset_x = value;
+ }
+ }
+
+ internal int OffsetY
+ {
+ get
+ {
+ return offset_y;
+ }
+
+ set
+ {
+ offset_y = value;
+ }
+ }
+
internal int ViewPortWidth {
get {
return viewport_width;
internal void UpdateMargins ()
{
- if (owner.actual_border_style == BorderStyle.FixedSingle) {
- left_margin = 0;
- top_margin = 0;
- right_margin = 0;
- } else {
- left_margin = 2;
- top_margin = 2;
- right_margin = 2;
+ switch (owner.actual_border_style) {
+ case BorderStyle.None:
+ left_margin = 0;
+ top_margin = 0;
+ right_margin = 1;
+ break;
+ case BorderStyle.FixedSingle:
+ left_margin = 2;
+ top_margin = 2;
+ right_margin = 3;
+ break;
+ case BorderStyle.Fixed3D:
+ left_margin = 1;
+ top_margin = 1;
+ right_margin = 2;
+ break;
}
}
internal void SuspendRecalc ()
{
+ if (recalc_suspended == 0) {
+ recalc_start = int.MaxValue;
+ recalc_end = int.MinValue;
+ }
+
recalc_suspended++;
}
if (recalc_suspended > 0)
recalc_suspended--;
- if (immediate_update && recalc_suspended == 0 && recalc_pending) {
- RecalculateDocument (owner.CreateGraphicsInternal(), recalc_start, recalc_end, recalc_optimize);
+ if (recalc_suspended == 0 && (immediate_update || recalc_pending) && !(recalc_start == int.MaxValue && recalc_end == int.MinValue)) {
+ RecalculateDocument (owner.CreateGraphicsInternal (), recalc_start, recalc_end, recalc_optimize);
recalc_pending = false;
}
}
length = 0;
Console.Write(" Tags: ");
while (tag != null) {
- Console.Write("{0} <{1}>-<{2}>", count++, tag.start, tag.end
+ Console.Write("{0} <{1}>-<{2}>", count++, tag.Start, tag.End
/*line.text.ToString (tag.start - 1, tag.length)*/);
- length += tag.length;
+ length += tag.Length;
- if (tag.line != line) {
+ if (tag.Line != line) {
Console.Write("BAD line link");
throw new Exception("Bad line link in tree");
}
- tag = tag.next;
+ tag = tag.Next;
if (tag != null) {
Console.Write(", ");
}
private void SetSelectionVisible (bool value)
{
+ bool old_selection_visible = selection_visible;
selection_visible = value;
// cursor and selection are enemies, we can't have both in the same room at the same time
if (owner.IsHandleCreated && !owner.show_caret_w_selection)
XplatUI.CaretVisible (owner.Handle, !selection_visible);
+ if (UIASelectionChanged != null && (selection_visible || old_selection_visible))
+ UIASelectionChanged (this, EventArgs.Empty);
}
private void DecrementLines(int line_no) {
// Lineheight changed, invalidate the rest of the document
if ((line.Y - viewport_y) >=0 ) {
// We formatted something that's in view, only draw parts of the screen
- owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
+ owner.Invalidate(new Rectangle(
+ offset_x,
+ line.Y - viewport_y + offset_y,
+ viewport_width,
+ owner.Height - (line.Y - viewport_y)));
} else {
// The tag was above the visible area, draw everything
owner.Invalidate();
} else {
switch(line.alignment) {
case HorizontalAlignment.Left: {
- owner.Invalidate(new Rectangle(line.X + (int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, viewport_width, line.height + 1));
+ owner.Invalidate(new Rectangle(
+ line.X + ((int)line.widths[pos] - viewport_x - 1) + offset_x,
+ line.Y - viewport_y + offset_y,
+ viewport_width,
+ line.height + 1));
+ break;
+ }
+
+ case HorizontalAlignment.Center: {
+ owner.Invalidate(new Rectangle(
+ line.X + offset_x,
+ line.Y - viewport_y + offset_y,
+ viewport_width,
+ line.height + 1));
+ break;
+ }
+
+ case HorizontalAlignment.Right: {
+ owner.Invalidate(new Rectangle(
+ line.X + offset_x,
+ line.Y - viewport_y + offset_y,
+ (int)line.widths[pos + 1] - viewport_x + line.X,
+ line.height + 1));
break;
}
+ }
+ }
+ }
+
+
+ // Update display from line, down line_count lines; pos is unused, but required for the signature
+ internal void UpdateView(Line line, int line_count, int pos) {
+ if (!owner.IsHandleCreated) {
+ return;
+ }
+
+ if (recalc_suspended > 0) {
+ recalc_start = Math.Min (recalc_start, line.line_no);
+ recalc_end = Math.Max (recalc_end, line.line_no + line_count);
+ recalc_optimize = true;
+ recalc_pending = true;
+ return;
+ }
+
+ int start_line_top = line.Y;
+
+ Line end_line = GetLine (line.line_no + line_count);
+ if (end_line == null)
+ end_line = GetLine (lines);
+
+ if (end_line == null)
+ return;
+
+ int end_line_bottom = end_line.Y + end_line.height;
+
+ if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
+ // Lineheight changed, invalidate the rest of the document
+ if ((line.Y - viewport_y) >=0 ) {
+ // We formatted something that's in view, only draw parts of the screen
+ owner.Invalidate(new Rectangle(
+ offset_x,
+ line.Y - viewport_y + offset_y,
+ viewport_width,
+ owner.Height - (line.Y - viewport_y)));
+ } else {
+ // The tag was above the visible area, draw everything
+ owner.Invalidate();
+ }
+ } else {
+ int x = 0 - viewport_x + offset_x;
+ int w = viewport_width;
+ int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y) + offset_y;
+ int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
+
+ owner.Invalidate (new Rectangle (x, y, w, h));
+ }
+ }
+
+ /// <summary>
+ /// Scans the next paragraph for http:/ ftp:/ www. https:/ etc and marks the tags
+ /// as links.
+ /// </summary>
+ /// <param name="start_line">The line to start on</param>
+ /// <param name="link_changed">marks as true if something is changed</param>
+ private void ScanForLinks (Line start_line, ref bool link_changed)
+ {
+ Line current_line = start_line;
+ StringBuilder line_no_breaks = new StringBuilder ();
+ StringBuilder line_link_record = new StringBuilder ();
+ ArrayList cumulative_length_list = new ArrayList ();
+ bool update_caret_tag = false;
+
+ cumulative_length_list.Add (0);
+
+ while (current_line != null) {
+ line_no_breaks.Append (current_line.text);
+
+ if (link_changed == false)
+ current_line.LinkRecord (line_link_record);
+
+ current_line.ClearLinks ();
+
+ cumulative_length_list.Add (line_no_breaks.Length);
+
+ if (current_line.ending == LineEnding.Wrap)
+ current_line = GetLine (current_line.LineNo + 1);
+ else
+ break;
+ }
+
+ // search for protocols.. make sure www. is first!
+ string [] search_terms = new string [] { "www.", "http:/", "ftp:/", "https:/" };
+ int search_found = 0;
+ int index_found = 0;
+ string line_no_breaks_string = line_no_breaks.ToString ();
+ int line_no_breaks_index = 0;
+ int link_end = 0;
+
+ while (true) {
+ if (line_no_breaks_index >= line_no_breaks_string.Length)
+ break;
+
+ index_found = FirstIndexOfAny (line_no_breaks_string, search_terms, line_no_breaks_index, out search_found);
+
+ //no links found on this line
+ if (index_found == -1)
+ break;
+
+ if (search_found == 0) {
+ // if we are at the end of the line to analyse and the end of the line
+ // is "www." then there are no links here
+ if (line_no_breaks_string.Length == index_found + search_terms [0].Length)
+ break;
+
+ // if after www. we don't have a letter a digit or a @ or - or /
+ // then it is not a web address, we should continue searching
+ if (char.IsLetterOrDigit (line_no_breaks_string [index_found + search_terms [0].Length]) == false &&
+ "@/~".IndexOf (line_no_breaks_string [index_found + search_terms [0].Length].ToString ()) == -1) {
+ line_no_breaks_index = index_found + search_terms [0].Length;
+ continue;
+ }
+ }
+
+ link_end = line_no_breaks_string.Length - 1;
+ line_no_breaks_index = line_no_breaks_string.Length;
+
+ // we've found a link, we just need to find where it ends now
+ for (int i = index_found + search_terms [search_found].Length; i < line_no_breaks_string.Length; i++) {
+ if (line_no_breaks_string [i - 1] == '.') {
+ if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
+ "@/~".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
+ link_end = i - 1;
+ line_no_breaks_index = i;
+ break;
+ }
+ } else {
+ if (char.IsLetterOrDigit (line_no_breaks_string [i]) == false &&
+ "@-/:~.?=_&".IndexOf (line_no_breaks_string [i].ToString ()) == -1) {
+ link_end = i - 1;
+ line_no_breaks_index = i;
+ break;
+ }
+ }
+ }
+
+ string link_text = line_no_breaks_string.Substring (index_found, link_end - index_found + 1);
+ int current_cumulative = 0;
+
+ // we've found a link - index_found -> link_end
+ // now we just make all the tags as containing link and
+ // point them to the text for the whole link
+
+ current_line = start_line;
+
+ //find the line we start on
+ for (current_cumulative = 1; current_cumulative < cumulative_length_list.Count; current_cumulative++)
+ if ((int)cumulative_length_list [current_cumulative] > index_found)
+ break;
+
+ current_line = GetLine (start_line.LineNo + current_cumulative - 1);
+
+ // find the tag we start on
+ LineTag current_tag = current_line.FindTag (index_found - (int)cumulative_length_list [current_cumulative - 1] + 1);
+
+ if (current_tag.Start != (index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1) {
+ if (current_tag == CaretTag)
+ update_caret_tag = true;
+
+ current_tag = current_tag.Break ((index_found - (int)cumulative_length_list [current_cumulative - 1]) + 1);
+ }
+
+ // set the tag
+ current_tag.IsLink = true;
+ current_tag.LinkText = link_text;
+
+ //go through each character
+ // find the tag we are in
+ // skip the number of characters in the tag
+ for (int i = 1; i < link_text.Length; i++) {
+ // on to a new word-wrapped line
+ if ((int)cumulative_length_list [current_cumulative] <= index_found + i) {
+
+ current_line = GetLine (start_line.LineNo + current_cumulative++);
+ current_tag = current_line.FindTag (index_found + i - (int)cumulative_length_list [current_cumulative - 1] + 1);
+
+ current_tag.IsLink = true;
+ current_tag.LinkText = link_text;
+
+ continue;
+ }
- case HorizontalAlignment.Center: {
- owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, viewport_width, line.height + 1));
- break;
+ if (current_tag.End < index_found + 1 + i - (int)cumulative_length_list [current_cumulative - 1]) {
+ // skip empty tags in the middle of the URL
+ do {
+ current_tag = current_tag.Next;
+ } while (current_tag.Length == 0);
+
+ current_tag.IsLink = true;
+ current_tag.LinkText = link_text;
}
+ }
- case HorizontalAlignment.Right: {
- owner.Invalidate(new Rectangle(line.X, line.Y - viewport_y, (int)line.widths[pos + 1] - viewport_x + line.X, line.height + 1));
- break;
+ //if there are characters left in the tag after the link
+ // split the tag
+ // make the second part a non link
+ if (current_tag.End > (index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]) {
+ if (current_tag == CaretTag)
+ update_caret_tag = true;
+
+ current_tag.Break ((index_found + link_text.Length + 1) - (int)cumulative_length_list [current_cumulative - 1]);
+ }
+ }
+
+ if (update_caret_tag) {
+ CaretTag = LineTag.FindTag (CaretLine, CaretPosition);
+ link_changed = true;
+ } else {
+ if (link_changed == false) {
+ current_line = start_line;
+ StringBuilder new_link_record = new StringBuilder ();
+
+ while (current_line != null) {
+ current_line.LinkRecord (new_link_record);
+
+ if (current_line.ending == LineEnding.Wrap)
+ current_line = GetLine (current_line.LineNo + 1);
+ else
+ break;
}
+
+ if (new_link_record.Equals (line_link_record) == false)
+ link_changed = true;
}
}
}
+ private int FirstIndexOfAny (string haystack, string [] needles, int start_index, out int term_found)
+ {
+ term_found = -1;
+ int best_index = -1;
- // Update display from line, down line_count lines; pos is unused, but required for the signature
- internal void UpdateView(Line line, int line_count, int pos) {
- if (!owner.IsHandleCreated) {
- return;
- }
+ for (int i = 0; i < needles.Length; i++) {
+ int index = haystack.IndexOf (needles [i], start_index, StringComparison.InvariantCultureIgnoreCase);
- if (recalc_suspended > 0) {
- recalc_start = Math.Min (recalc_start, line.line_no);
- recalc_end = Math.Max (recalc_end, line.line_no + line_count);
- recalc_optimize = true;
- recalc_pending = true;
- return;
+ if (index > -1) {
+ if (term_found > -1) {
+ if (index < best_index) {
+ best_index = index;
+ term_found = i;
+ }
+ } else {
+ best_index = index;
+ term_found = i;
+ }
+ }
}
- int start_line_top = line.Y;
-
- int end_line_bottom;
- Line end_line;
+ return best_index;
+ }
- end_line = GetLine (line.line_no + line_count);
- if (end_line == null)
- end_line = GetLine (lines);
- end_line_bottom = end_line.Y + end_line.height;
-
- if (RecalculateDocument(owner.CreateGraphicsInternal(), line.line_no, line.line_no + line_count, true)) {
- // Lineheight changed, invalidate the rest of the document
- if ((line.Y - viewport_y) >=0 ) {
- // We formatted something that's in view, only draw parts of the screen
- owner.Invalidate(new Rectangle(0, line.Y - viewport_y, viewport_width, owner.Height - line.Y - viewport_y));
- } else {
- // The tag was above the visible area, draw everything
- owner.Invalidate();
- }
- } else {
- int x = 0 - viewport_x;
- int w = viewport_width;
- int y = Math.Min (start_line_top - viewport_y, line.Y - viewport_y);
- int h = Math.Max (end_line_bottom - y, end_line.Y + end_line.height - y);
+ private void InvalidateLinks (Rectangle clip)
+ {
+ for (int i = (owner.list_links.Count - 1); i >= 0; i--) {
+ TextBoxBase.LinkRectangle link = (TextBoxBase.LinkRectangle) owner.list_links [i];
- owner.Invalidate (new Rectangle (x, y, w, h));
+ if (clip.IntersectsWith (link.LinkAreaRectangle))
+ owner.list_links.RemoveAt (i);
}
}
#endregion // Private Methods
#region Internal Methods
+
+ internal void ScanForLinks (int start, int end, ref bool link_changed)
+ {
+ Line line = null;
+ LineEnding lastending = LineEnding.Rich;
+
+ // make sure we start scanning at the real begining of the line
+ while (true) {
+ if (start != 1 && GetLine (start - 1).ending == LineEnding.Wrap)
+ start--;
+ else
+ break;
+ }
+
+ for (int i = start; i <= end && i <= lines; i++) {
+ line = GetLine (i);
+
+ if (lastending != LineEnding.Wrap)
+ ScanForLinks (line, ref link_changed);
+
+ lastending = line.ending;
+
+ if (lastending == LineEnding.Wrap && (i + 1) <= end)
+ end++;
+ }
+ }
+
// Clear the document and reset state
internal void Empty() {
lines = 0;
// We always have a blank line
- Add (1, String.Empty, owner.Font, ThemeEngine.Current.ResPool.GetSolidBrush (owner.ForeColor), LineEnding.None);
+ Add (1, String.Empty, owner.Font, owner.ForeColor, LineEnding.None);
this.RecalculateDocument(owner.CreateGraphicsInternal());
PositionCaret(0, 0);
if (owner.IsHandleCreated) {
if (owner.Focused) {
- if (caret.height != caret.tag.height)
+ if (caret.height != caret.tag.Height)
XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
+ XplatUI.SetCaretPos(owner.Handle,
+ offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
+ offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
}
if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
// We set this at the end because we use the heights to determine whether or
// not we need to recreate the caret
- caret.height = caret.tag.height;
+ caret.height = caret.tag.Height;
}
}
caret.tag = FindCursor(x, y, out caret.pos);
-
+
MoveCaretToTextTag ();
- caret.line = caret.tag.line;
- caret.height = caret.tag.height;
+ caret.line = caret.tag.Line;
+ caret.height = caret.tag.Height;
if (owner.ShowSelection && (!selection_visible || owner.show_caret_w_selection)) {
XplatUI.CreateCaret (owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
+ XplatUI.SetCaretPos(owner.Handle,
+ (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x + offset_x,
+ offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
}
if (CaretMoved != null) CaretMoved(this, EventArgs.Empty);
internal void CaretHasFocus() {
if ((caret.tag != null) && owner.IsHandleCreated) {
XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
+ XplatUI.SetCaretPos(owner.Handle,
+ offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
+ offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
DisplayCaret ();
}
XplatUI.DestroyCaret(owner.Handle);
}
- internal void AlignCaret() {
+ internal void AlignCaret ()
+ {
+ AlignCaret (true);
+ }
+
+ internal void AlignCaret(bool changeCaretTag) {
if (!owner.IsHandleCreated) {
return;
}
- caret.tag = LineTag.FindTag (caret.line, caret.pos);
+ if (changeCaretTag) {
+ caret.tag = LineTag.FindTag (caret.line, caret.pos);
- MoveCaretToTextTag ();
+ MoveCaretToTextTag ();
+ }
- caret.height = caret.tag.height;
+ // if the caret has had SelectionFont changed to a
+ // different height, we reflect changes unless the new
+ // font is larger than the line (line recalculations
+ // ignore empty tags) in which case we make it equal
+ // the line height and then when text is entered
+ if (caret.tag.Height > caret.tag.Line.Height) {
+ caret.height = caret.line.height;
+ } else {
+ caret.height = caret.tag.Height;
+ }
if (owner.Focused) {
XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
+ XplatUI.SetCaretPos (owner.Handle,
+ offset_x + (int) caret.tag.Line.widths [caret.pos] + caret.line.X - viewport_x,
+ offset_y + caret.line.Y + viewport_y + caret_shift);
DisplayCaret ();
}
MoveCaretToTextTag ();
- if (caret.tag.height != caret.height) {
- caret.height = caret.tag.height;
+ if (caret.tag.Height != caret.height) {
+ caret.height = caret.tag.Height;
if (owner.Focused) {
XplatUI.CreateCaret(owner.Handle, caret_width, caret.height);
}
}
if (owner.Focused) {
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.X - viewport_x, caret.line.Y + caret.tag.shift - viewport_y + caret_shift);
+ XplatUI.SetCaretPos(owner.Handle,
+ offset_x + (int)caret.tag.Line.widths[caret.pos] + caret.line.X - viewport_x,
+ offset_y + caret.line.Y + caret.tag.Shift - viewport_y + caret_shift);
DisplayCaret ();
}
- if (caret.pos < caret.tag.start) {
- caret.tag = caret.tag.previous;
+ if (caret.pos < caret.tag.Start) {
+ caret.tag = caret.tag.Previous;
} else {
- caret.tag = caret.tag.next;
+ caret.tag = caret.tag.Next;
}
}
caret.pos--;
}
} else {
- if ((caret.tag.start - 1 + caret.tag.length) < caret.pos) {
- caret.tag = caret.tag.next;
+ if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) {
+ caret.tag = caret.tag.Next;
}
}
UpdateCaret();
// caret.pos--; // folded into the if below
if (--caret.pos > 0) {
- if (caret.tag.start > caret.pos) {
- caret.tag = caret.tag.previous;
+ if (caret.tag.Start > caret.pos) {
+ caret.tag = caret.tag.Previous;
}
}
} else {
case CaretDirection.PgUp: {
- if (viewport_y == 0 && owner.richtext) {
+ if (caret.line.line_no == 1 && owner.richtext) {
owner.vscroll.Value = 0;
Line line = GetLine (1);
PositionCaret (line, 0);
LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
viewport_y - viewport_height, out index);
- owner.vscroll.Value = Math.Min (top.line.Y, owner.vscroll.Maximum - viewport_height);
+ owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
return;
case CaretDirection.PgDn: {
- if (viewport_y + viewport_height >= document_y && owner.richtext) {
+ if (caret.line.line_no == lines && owner.richtext) {
owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1;
Line line = GetLine (lines);
- PositionCaret (line, line.Text.Length);
+ PositionCaret (line, line.TextLengthWithoutEnding());
}
int y_offset = caret.line.Y - viewport_y;
LineTag top = FindCursor ((int) caret.line.widths [caret.pos],
viewport_y + viewport_height, out index);
- owner.vscroll.Value = Math.Min (top.line.Y, owner.vscroll.Maximum - viewport_height);
+ owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height);
PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y);
return;
LineTag tag;
int index;
- tag = FindTag(0, viewport_y + viewport_height, out index, false);
- if (tag.line.line_no > 1) {
- line = GetLine(tag.line.line_no - 1);
+ tag = FindCursor (0, viewport_y + viewport_height, out index);
+ if (tag.Line.line_no > 1) {
+ line = GetLine(tag.Line.line_no - 1);
} else {
- line = tag.line;
+ line = tag.Line;
}
PositionCaret(line, line.Text.Length);
DisplayCaret ();
LineTag tag = line.tags;
while (tag != null) {
Console.Write ("\t<tag type='{0}' span='{1}->{2}' font='{3}' color='{4}'>",
- tag.GetType (), tag.start, tag.length, tag.font, tag.color.Color);
+ tag.GetType (), tag.Start, tag.Length, tag.Font, tag.Color);
Console.Write (tag.Text ());
Console.WriteLine ("</tag>");
- tag = tag.next;
+ tag = tag.Next;
}
Console.WriteLine ("</line>");
}
Console.WriteLine ("</doc>");
}
+ // UIA: Used via reflection by TextProviderBehavior
+ internal void GetVisibleLineIndexes (Rectangle clip, out int start, out int end)
+ {
+ if (multiline) {
+ /* Expand the region slightly to be sure to
+ * paint the full extent of the line of text.
+ * See bug 464464.
+ */
+ start = GetLineByPixel(clip.Top + viewport_y - offset_y - 1, false).line_no;
+ end = GetLineByPixel(clip.Bottom + viewport_y - offset_y + 1, false).line_no;
+ } else {
+ start = GetLineByPixel(clip.Left + viewport_x - offset_x, false).line_no;
+ end = GetLineByPixel(clip.Right + viewport_x - offset_x, false).line_no;
+ }
+ }
+
internal void Draw (Graphics g, Rectangle clip)
{
Line line; // Current line being drawn
int end; // Last line to draw
StringBuilder text; // String representing the current line
int line_no;
- Brush tag_brush;
- Brush current_brush;
- Brush disabled_brush;
- Brush readonly_brush;
- Brush hilight;
- Brush hilight_text;
+ Color tag_color;
+ Color current_color;
// First, figure out from what line to what line we need to draw
+ GetVisibleLineIndexes (clip, out start, out end);
- if (multiline) {
- start = GetLineByPixel(clip.Top + viewport_y, false).line_no;
- end = GetLineByPixel(clip.Bottom + viewport_y, false).line_no;
- } else {
- start = GetLineByPixel(clip.Left + viewport_x, false).line_no;
- end = GetLineByPixel(clip.Right + viewport_x, false).line_no;
- }
+ // remove links in the list (used for mouse down events) that are within the clip area.
+ InvalidateLinks (clip);
///
/// We draw the single border ourself
/// Make sure that we aren't drawing one more line then we need to
line = GetLine (end - 1);
- if (line != null && clip.Bottom == line.Y + line.height + viewport_y)
- end--;
+ if (line != null && clip.Bottom == offset_y + line.Y + line.height - viewport_y)
+ end--;
line_no = start;
Console.WriteLine ("E: {0}", GetLine (end).text);
#endif
- disabled_brush = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorGrayText);
- readonly_brush = ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorControlText);
- hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlight);
- hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHighlightText);
-
// Non multiline selection can be handled outside of the loop
if (!multiline && selection_visible && owner.ShowSelection) {
- g.FillRectangle (hilight,
- selection_start.line.widths [selection_start.pos] +
+ g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
+ offset_x + selection_start.line.widths [selection_start.pos] +
selection_start.line.X - viewport_x,
- selection_start.line.Y,
+ offset_y + selection_start.line.Y,
(selection_end.line.X + selection_end.line.widths [selection_end.pos]) -
(selection_start.line.X + selection_start.line.widths [selection_start.pos]),
selection_start.line.height);
while (line_no <= end) {
line = GetLine (line_no);
- float line_y = line.Y - viewport_y;
+ float line_y = line.Y - viewport_y + offset_y;
tag = line.tags;
if (!calc_pass) {
line_selection_end = line_selection_start;
} else if (multiline) {
// lets draw some selection baby!! (non multiline selection is drawn outside the loop)
- g.FillRectangle (hilight,
- line.widths [line_selection_start - 1] + line.X - viewport_x,
+ g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorHighlight),
+ offset_x + line.widths [line_selection_start - 1] + line.X - viewport_x,
line_y, line.widths [line_selection_end - 1] - line.widths [line_selection_start - 1],
line.height);
}
}
- current_brush = line.tags.color;
+ current_color = line.tags.ColorToDisplay;
while (tag != null) {
// Skip empty tags
- if (tag.length == 0) {
- tag = tag.next;
+ if (tag.Length == 0) {
+ tag = tag.Next;
continue;
}
- if (((tag.X + tag.width) < (clip.Left - viewport_x)) && (tag.X > (clip.Right - viewport_x))) {
- tag = tag.next;
+ if (((tag.X + tag.Width) < (clip.Left - viewport_x - offset_x)) &&
+ (tag.X > (clip.Right - viewport_x - offset_x))) {
+ tag = tag.Next;
continue;
}
- if (tag.back_color != null) {
- g.FillRectangle (tag.back_color, tag.X + line.X - viewport_x,
- line_y + tag.shift, tag.width, line.height);
+ if (tag.BackColor != Color.Empty) {
+ g.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (tag.BackColor),
+ offset_x + tag.X + line.X - viewport_x,
+ line_y + tag.Shift, tag.Width, line.height);
}
- tag_brush = tag.color;
- current_brush = tag_brush;
+ tag_color = tag.ColorToDisplay;
+ current_color = tag_color;
- if (!owner.is_enabled) {
- Color a = ((SolidBrush) tag.color).Color;
+ if (!owner.Enabled) {
+ Color a = tag.Color;
Color b = ThemeEngine.Current.ColorWindowText;
- if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B)) {
- tag_brush = disabled_brush;
- }
- } else if (owner.read_only && !owner.backcolor_set) {
- tag_brush = readonly_brush;
- }
+ if ((a.R == b.R) && (a.G == b.G) && (a.B == b.B))
+ tag_color = ThemeEngine.Current.ColorGrayText;
+
+ }
- int tag_pos = tag.start;
- current_brush = tag_brush;
- while (tag_pos < tag.start + tag.length) {
+ int tag_pos = tag.Start;
+ current_color = tag_color;
+ while (tag_pos < tag.Start + tag.Length) {
int old_tag_pos = tag_pos;
if (tag_pos >= line_selection_start && tag_pos < line_selection_end) {
- current_brush = hilight_text;
- tag_pos = Math.Min (tag.end, line_selection_end);
+ current_color = ThemeEngine.Current.ColorHighlightText;
+ tag_pos = Math.Min (tag.End, line_selection_end);
} else if (tag_pos < line_selection_start) {
- current_brush = tag_brush;
- tag_pos = Math.Min (tag.end, line_selection_start);
+ current_color = tag_color;
+ tag_pos = Math.Min (tag.End, line_selection_start);
} else {
- current_brush = tag_brush;
- tag_pos = tag.end;
+ current_color = tag_color;
+ tag_pos = tag.End;
}
- tag.Draw (g, current_brush,
- line.X - viewport_x,
- line_y + tag.shift,
- old_tag_pos - 1, Math.Max (tag.length, tag_pos - old_tag_pos),
- text.ToString() );
+ Rectangle text_size;
+
+ tag.Draw (g, current_color,
+ offset_x + line.X - viewport_x,
+ line_y + tag.Shift,
+ old_tag_pos - 1, Math.Min (tag.Start + tag.Length, tag_pos) - 1,
+ text.ToString (), out text_size, tag.IsLink);
+
+ if (tag.IsLink) {
+ TextBoxBase.LinkRectangle link = new TextBoxBase.LinkRectangle (text_size);
+ link.LinkTag = tag;
+ owner.list_links.Add (link);
+ }
}
- tag = tag.next;
+ tag = tag.Next;
}
line.DrawEnding (g, line_y);
}
}
- internal int GetLineEnding (string line, int start, out LineEnding ending)
+ private int GetLineEnding (string line, int start, out LineEnding ending)
{
int res;
+ int rich_index;
+ if (start >= line.Length) {
+ ending = LineEnding.Wrap;
+ return -1;
+ }
+
res = line.IndexOf ('\r', start);
+ rich_index = line.IndexOf ('\n', start);
+
+ // Handle the case where we find both of them, and the \n is before the \r
+ if (res != -1 && rich_index != -1)
+ if (rich_index < res) {
+ ending = LineEnding.Rich;
+ return rich_index;
+ }
+
if (res != -1) {
if (res + 2 < line.Length && line [res + 1] == '\r' && line [res + 2] == '\n') {
ending = LineEnding.Soft;
return res;
}
- res = line.IndexOf ('\n', start);
- if (res != -1) {
+ if (rich_index != -1) {
ending = LineEnding.Rich;
- return res;
+ return rich_index;
}
ending = LineEnding.Wrap;
return line.Length;
}
- internal int LineEndingLength (LineEnding ending)
+ // Get the line ending, but only of the types specified
+ private int GetLineEnding (string line, int start, out LineEnding ending, LineEnding type)
{
- int res = 0;
+ int index = start;
+ int last_length = 0;
+ do {
+ index = GetLineEnding (line, index + last_length, out ending);
+ last_length = LineEndingLength (ending);
+ } while
+ ((ending & type) != ending && index != -1);
+
+ return index == -1 ? line.Length : index;
+ }
+
+ internal int LineEndingLength (LineEnding ending)
+ {
switch (ending) {
- case LineEnding.Limp:
- case LineEnding.Rich:
- res = 1;
- break;
- case LineEnding.Hard:
- res = 2;
- break;
- case LineEnding.Soft:
- res = 3;
- break;
+ case LineEnding.Limp:
+ case LineEnding.Rich:
+ return 1;
+ case LineEnding.Hard:
+ return 2;
+ case LineEnding.Soft:
+ return 3;
}
- return res;
+ return 0;
}
internal string LineEndingToString (LineEnding ending)
{
- string res = String.Empty;
switch (ending) {
- case LineEnding.Limp:
- res = "\r";
- break;
- case LineEnding.Hard:
- res = "\r\n";
- break;
- case LineEnding.Soft:
- res = "\r\r\n";
- break;
- case LineEnding.Rich:
- res = "\n";
- break;
- }
- return res;
+ case LineEnding.Limp:
+ return "\r";
+ case LineEnding.Hard:
+ return "\r\n";
+ case LineEnding.Soft:
+ return "\r\r\n";
+ case LineEnding.Rich:
+ return "\n";
+ }
+
+ return string.Empty;
}
+ internal LineEnding StringToLineEnding (string ending)
+ {
+ switch (ending) {
+ case "\r":
+ return LineEnding.Limp;
+ case "\r\n":
+ return LineEnding.Hard;
+ case "\r\r\n":
+ return LineEnding.Soft;
+ case "\n":
+ return LineEnding.Rich;
+ default:
+ return LineEnding.None;
+ }
+ }
- // Insert multi-line text at the given position; use formatting at insertion point for inserted text
- internal void Insert(Line line, int pos, bool update_caret, string s) {
+ internal void Insert (Line line, int pos, bool update_caret, string s)
+ {
+ Insert (line, pos, update_caret, s, line.FindTag (pos));
+ }
+
+ // Insert text at the given position; use formatting at insertion point for inserted text
+ internal void Insert (Line line, int pos, bool update_caret, string s, LineTag tag)
+ {
int break_index;
int base_line;
int old_line_count;
int count = 1;
LineEnding ending;
- LineTag tag = LineTag.FindTag (line, pos);
+ Line split_line;
+ // Don't recalculate while we mess around
SuspendRecalc ();
base_line = line.line_no;
old_line_count = lines;
- break_index = GetLineEnding (s, 0, out ending);
+ // Discard chars after any possible -unlikely- end of file
+ int eof_index = s.IndexOf ('\0');
+ if (eof_index != -1)
+ s = s.Substring (0, eof_index);
+
+ break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich);
- // Bump the text at insertion point a line down if we're inserting more than one line
- if (break_index != s.Length) {
- Split (line, pos);
+ // There are no line feeds in our text to be pasted
+ if (break_index == s.Length) {
+ line.InsertString (pos, s, tag);
+ } else {
+ // Add up to the first line feed to our current position
+ line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)), tag);
+
+ // Split the rest of the original line to a new line
+ Split (line, pos + (break_index + LineEndingLength (ending)));
line.ending = ending;
- // Remainder of start line is now in base_line + 1
- }
+ break_index += LineEndingLength (ending);
+ split_line = GetLine (line.line_no + 1);
+
+ // Insert brand new lines for any more line feeds in the inserted string
+ while (true) {
+ int next_break = GetLineEnding (s, break_index, out ending, LineEnding.Hard | LineEnding.Rich);
+
+ if (next_break == s.Length)
+ break;
+
+ string line_text = s.Substring (break_index, next_break - break_index +
+ LineEndingLength (ending));
- InsertString (line, pos, s.Substring (0, break_index + LineEndingLength (ending)));
-
- break_index += LineEndingLength (ending);
- while (break_index < s.Length) {
- int next_break = GetLineEnding (s, break_index, out ending);
- string line_text = s.Substring (break_index, next_break - break_index +
- LineEndingLength (ending));
+ Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending);
- Add (base_line + count, line_text, line.alignment, tag.font, tag.color, ending);
+ Line last = GetLine (base_line + count);
+ last.ending = ending;
- Line last = GetLine (base_line + count);
- last.ending = ending;
+ count++;
+ break_index = next_break + LineEndingLength (ending);
+ }
- count++;
- break_index = next_break + LineEndingLength (ending);
+ // Add the remainder of the insert text to the split
+ // part of the original line
+ split_line.InsertString (0, s.Substring (break_index));
}
+
+ // Allow the document to recalculate things
+ ResumeRecalc (false);
- ResumeRecalc (true);
+ // Update our character count
+ CharCount += s.Length;
- UpdateView(line, lines - old_line_count + 1, pos);
+ UpdateView (line, lines - old_line_count + 1, pos);
+ // Move the caret to the end of the inserted text if requested
if (update_caret) {
- // Move caret to the end of the inserted text
Line l = GetLine (line.line_no + lines - old_line_count);
- PositionCaret(l, l.text.Length);
+ PositionCaret (l, l.text.Length);
DisplayCaret ();
}
}
- // Inserts a character at the given position
- internal void InsertString(Line line, int pos, string s) {
- InsertString(line.FindTag(pos), pos, s);
- }
-
// Inserts a string at the given position
- internal void InsertString(LineTag tag, int pos, string s) {
- Line line;
- int len;
-
- len = s.Length;
-
- CharCount += len;
-
- line = tag.line;
- line.text.Insert(pos, s);
-
- tag = tag.next;
- while (tag != null) {
- tag.start += len;
- tag = tag.next;
- }
- line.Grow(len);
- line.recalc = true;
-
- UpdateView(line, pos);
- }
-
- // Inserts a string at the caret position
- internal void InsertStringAtCaret(string s, bool move_caret) {
-
- InsertString (caret.tag, caret.pos, s);
-
- UpdateView(caret.line, caret.pos);
- if (move_caret) {
- caret.pos += s.Length;
- UpdateCaret();
- }
- }
-
-
-
- // Inserts a character at the given position
- internal void InsertChar(Line line, int pos, char ch) {
- InsertChar(line.FindTag(pos), pos, ch);
- }
-
- // Inserts a character at the given position
- internal void InsertChar(LineTag tag, int pos, char ch) {
- Line line;
-
- CharCount++;
-
- line = tag.line;
- line.text.Insert(pos, ch);
-
- tag = tag.next;
- while (tag != null) {
- tag.start++;
- tag = tag.next;
- }
- line.Grow(1);
- line.recalc = true;
+ internal void InsertString (Line line, int pos, string s)
+ {
+ // Update our character count
+ CharCount += s.Length;
- undo.RecordTyping (line, pos, ch);
- UpdateView(line, pos);
+ // Insert the text into the Line
+ line.InsertString (pos, s);
}
// Inserts a character at the current caret position
- internal void InsertCharAtCaret(char ch, bool move_caret) {
- /*
- LineTag tag;
+ internal void InsertCharAtCaret (char ch, bool move_caret)
+ {
+ caret.line.InsertString (caret.pos, ch.ToString(), caret.tag);
+ // Update our character count
CharCount++;
-
- caret.line.text.Insert(caret.pos, ch);
- caret.tag.length++;
- if (caret.tag.next != null) {
- tag = caret.tag.next;
- while (tag != null) {
- tag.start++;
- tag = tag.next;
- }
- }
- caret.line.Grow(1);
- caret.line.recalc = true;
- */
- InsertChar (caret.tag, caret.pos, ch);
+ undo.RecordTyping (caret.line, caret.pos, ch);
- UpdateView(caret.line, caret.pos);
+ UpdateView (caret.line, caret.pos);
+
if (move_caret) {
caret.pos++;
- UpdateCaret();
- SetSelectionToCaret(true);
+ UpdateCaret ();
+ SetSelectionToCaret (true);
}
-
}
internal void InsertPicture (Line line, int pos, RTF.Picture picture)
tag = LineTag.FindTag (line, pos);
picture_tag.CopyFormattingFrom (tag);
/*next_tag = */tag.Break (pos + 1);
- picture_tag.previous = tag;
- picture_tag.next = tag.next;
- tag.next = picture_tag;
+ picture_tag.Previous = tag;
+ picture_tag.Next = tag.Next;
+ tag.Next = picture_tag;
//
// Picture tags need to be surrounded by text tags
//
- if (picture_tag.next == null) {
- picture_tag.next = new LineTag (line, pos + 1);
- picture_tag.next.CopyFormattingFrom (tag);
- picture_tag.next.previous = picture_tag;
+ if (picture_tag.Next == null) {
+ picture_tag.Next = new LineTag (line, pos + 1);
+ picture_tag.Next.CopyFormattingFrom (tag);
+ picture_tag.Next.Previous = picture_tag;
}
- tag = picture_tag.next;
+ tag = picture_tag.Next;
while (tag != null) {
- tag.start += len;
- tag = tag.next;
+ tag.Start += len;
+ tag = tag.Next;
}
line.Grow (len);
SuspendUpdate ();
if (start.line == end.line) {
- DeleteChars (start.tag, pos, end.pos - pos);
+ DeleteChars (start.line, pos, end.pos - pos);
} else {
// Delete first and last lines
- DeleteChars (start.tag, start.pos, start.line.text.Length - start.pos);
- DeleteChars (end.line.tags, 0, end.pos);
+ DeleteChars (start.line, start.pos, start.line.text.Length - start.pos);
+ DeleteChars (end.line, 0, end.pos);
int current = start.line.line_no + 1;
if (current < end.line.line_no) {
// Deletes n characters at the given position; it will not delete past line limits
// pos is 0-based
- internal void DeleteChars(LineTag tag, int pos, int count) {
- Line line;
- bool streamline;
-
- streamline = false;
- line = tag.line;
-
+ public void DeleteChars (Line line, int pos, int count)
+ {
+ // Reduce our character count
CharCount -= count;
+
+ line.DeleteCharacters (pos, count);
- if (pos == line.text.Length) {
- return;
- }
-
- line.text.Remove(pos, count);
-
- // Make sure the tag points to the right spot
- while ((tag != null) && (tag.end) < pos) {
- tag = tag.next;
- }
-
- if (tag == null) {
- goto Cleanup;
- }
-
- // Check if we're crossing tag boundaries
- if ((pos + count) > (tag.start + tag.length - 1)) {
- int left;
-
- // We have to delete cross tag boundaries
- streamline = true;
- left = count;
-
- left -= tag.start + tag.length - pos - 1;
-
- tag = tag.next;
- while ((tag != null) && (left > 0)) {
- tag.start -= count - left;
-
- if (tag.length > left) {
- left = 0;
- } else {
- left -= tag.length;
- tag = tag.next;
- }
-
- }
- } else {
- // We got off easy, same tag
-
- if (tag.length == 0) {
- streamline = true;
- }
- }
-
- // Delete empty orphaned tags at the end
- LineTag walk = tag;
- while (walk != null && walk.next != null && walk.next.length == 0) {
- LineTag t = walk;
- walk.next = walk.next.next;
- if (walk.next != null)
- walk.next.previous = t;
- walk = walk.next;
- }
-
- // Adjust the start point of any tags following
- if (tag != null) {
- tag = tag.next;
- while (tag != null) {
- tag.start -= count;
- tag = tag.next;
- }
- }
-
- line.recalc = true;
- if (streamline) {
- line.Streamline(lines);
- }
-
- Cleanup:
if (pos >= line.TextLengthWithoutEnding ()) {
LineEnding ending = line.ending;
GetLineEnding (line.text.ToString (), 0, out ending);
+
if (ending != line.ending) {
line.ending = ending;
UpdateView (line, lines, pos);
owner.Invalidate ();
} else
- UpdateView(line, pos);
+ UpdateView (line, pos);
}
// Deletes a character at or after the given position (depending on forward); it will not delete past line limits
- internal void DeleteChar(LineTag tag, int pos, bool forward) {
- Line line;
- bool streamline;
-
- CharCount--;
-
- streamline = false;
- line = tag.line;
-
- if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true)) {
+ public void DeleteChar (Line line, int pos, bool forward)
+ {
+ if ((pos == 0 && forward == false) || (pos == line.text.Length && forward == true))
return;
- }
-
+
+ undo.BeginUserAction ("Delete");
if (forward) {
- line.text.Remove(pos, 1);
-
- while ((tag != null) && (tag.start + tag.length - 1) <= pos) {
- tag = tag.next;
- }
-
- if (tag == null) {
- goto Cleanup;
- }
-
- // tag.length--;
-
- if (tag.length == 0) {
- streamline = true;
- }
+ undo.RecordDeleteString (line, pos, line, pos + 1);
+ DeleteChars (line, pos, 1);
} else {
- pos--;
- line.text.Remove(pos, 1);
- if (pos >= (tag.start - 1)) {
- // tag.length--;
- if (tag.length == 0) {
- streamline = true;
- }
- } else if (tag.previous != null) {
- // tag.previous.length--;
- if (tag.previous.length == 0) {
- streamline = true;
- }
- }
+ undo.RecordDeleteString (line, pos - 1, line, pos);
+ DeleteChars (line, pos - 1, 1);
}
- // Delete empty orphaned tags at the end
- LineTag walk = tag;
- while (walk != null && walk.next != null && walk.next.length == 0) {
- LineTag t = walk;
- walk.next = walk.next.next;
- if (walk.next != null)
- walk.next.previous = t;
- walk = walk.next;
- }
-
- tag = tag.next;
- while (tag != null) {
- tag.start--;
- tag = tag.next;
- }
- line.recalc = true;
- if (streamline) {
- line.Streamline(lines);
- }
-
- Cleanup:
- if (pos >= line.TextLengthWithoutEnding ()) {
- LineEnding ending = line.ending;
- GetLineEnding (line.text.ToString (), 0, out ending);
- if (ending != line.ending) {
- line.ending = ending;
-
- if (!multiline) {
- UpdateView (line, lines, pos);
- owner.Invalidate ();
- return;
- }
- }
- }
- if (!multiline) {
- UpdateView (line, lines, pos);
- owner.Invalidate ();
- } else
- UpdateView(line, pos);
+ undo.EndUserAction ();
}
// Combine two lines
// Maintain the line ending style
first.ending = second.ending;
- while (last.next != null) {
- last = last.next;
+ while (last.Next != null) {
+ last = last.Next;
}
// need to get the shift before setting the next tag since that effects length
- shift = last.start + last.length - 1;
- last.next = second.tags;
- last.next.previous = last;
+ shift = last.Start + last.Length - 1;
+ last.Next = second.tags;
+ last.Next.Previous = last;
// Fix up references within the chain
- last = last.next;
+ last = last.Next;
while (last != null) {
- last.line = first;
- last.start += shift;
- last = last.next;
+ last.Line = first;
+ last.Start += shift;
+ last = last.Next;
}
// Combine both lines' strings
}
///<summary>Split line at given tag and position into two lines</summary>
- ///if more space becomes available on previous line</param>
+ ///if more space becomes available on previous line
internal void Split(Line line, LineTag tag, int pos) {
LineTag new_tag;
Line new_line;
move_sel_start = false;
move_sel_end = false;
+#if DEBUG
+ SanityCheck();
+
+ if (tag.End < pos)
+ throw new Exception ("Split called with the wrong tag");
+#endif
+
// Adjust selection and cursors
if (caret.line == line && caret.pos >= pos) {
move_caret = true;
// cover the easy case first
if (pos == line.text.Length) {
- Add (line.line_no + 1, String.Empty, line.alignment, tag.font, tag.color, line.ending);
+ Add (line.line_no + 1, String.Empty, line.alignment, tag.Font, tag.Color, line.ending);
new_line = GetLine (line.line_no + 1);
caret.line = new_line;
caret.tag = new_line.tags;
caret.pos = 0;
+
+ if (selection_visible == false) {
+ SetSelectionToCaret (true);
+ }
}
if (move_sel_start) {
selection_end.pos = 0;
selection_end.tag = new_line.tags;
}
+
+#if DEBUG
+ SanityCheck ();
+#endif
return;
}
// We need to move the rest of the text into the new line
- Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.font, tag.color, line.ending);
+ Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.Font, tag.Color, line.ending);
// Now transfer our tags from this line to the next
new_line = GetLine(line.line_no + 1);
line.recalc = true;
new_line.recalc = true;
- if ((tag.start - 1) == pos) {
+ //make sure that if we are at the end of a tag, we start on the begining
+ //of a new one, if one exists... Stops us creating an empty tag and
+ //make the operation easier.
+ if (tag.Next != null && (tag.Next.Start - 1) == pos)
+ tag = tag.Next;
+
+ if ((tag.Start - 1) == pos) {
int shift;
// We can simply break the chain and move the tag into the next line
+
+ // if the tag we are moving is the first, create an empty tag
+ // for the line we are leaving behind
if (tag == line.tags) {
new_tag = new LineTag(line, 1);
new_tag.CopyFormattingFrom (tag);
line.tags = new_tag;
}
- if (tag.previous != null) {
- tag.previous.next = null;
+ if (tag.Previous != null) {
+ tag.Previous.Next = null;
}
new_line.tags = tag;
- tag.previous = null;
- tag.line = new_line;
+ tag.Previous = null;
+ tag.Line = new_line;
// Walk the list and correct the start location of the tags we just bumped into the next line
- shift = tag.start - 1;
+ shift = tag.Start - 1;
new_tag = tag;
while (new_tag != null) {
- new_tag.start -= shift;
- new_tag.line = new_line;
- new_tag = new_tag.next;
+ new_tag.Start -= shift;
+ new_tag.Line = new_line;
+ new_tag = new_tag.Next;
}
} else {
int shift;
new_tag = new LineTag (new_line, 1);
- new_tag.next = tag.next;
+ new_tag.Next = tag.Next;
new_tag.CopyFormattingFrom (tag);
new_line.tags = new_tag;
- if (new_tag.next != null) {
- new_tag.next.previous = new_tag;
+ if (new_tag.Next != null) {
+ new_tag.Next.Previous = new_tag;
}
- tag.next = null;
+ tag.Next = null;
shift = pos;
- new_tag = new_tag.next;
+ new_tag = new_tag.Next;
while (new_tag != null) {
- new_tag.start -= shift;
- new_tag.line = new_line;
- new_tag = new_tag.next;
+ new_tag.Start -= shift;
+ new_tag.Line = new_line;
+ new_tag = new_tag.Next;
}
}
caret.line = new_line;
caret.pos = caret.pos - pos;
caret.tag = caret.line.FindTag(caret.pos);
+
+ if (selection_visible == false) {
+ SetSelectionToCaret (true);
+ move_sel_start = false;
+ move_sel_end = false;
+ }
}
if (move_sel_start) {
selection_start.line = new_line;
selection_start.pos = selection_start.pos - pos;
- selection_start.tag = new_line.FindTag(selection_start.pos);
+ if (selection_start.Equals(selection_end))
+ selection_start.tag = new_line.FindTag(selection_start.pos);
+ else
+ selection_start.tag = new_line.FindTag (selection_start.pos + 1);
}
if (move_sel_end) {
CharCount -= line.text.Length - pos;
line.text.Remove(pos, line.text.Length - pos);
+#if DEBUG
+ SanityCheck ();
+#endif
+ }
+
+#if DEBUG
+ private void SanityCheck () {
+ for (int i = 1; i < lines; i++) {
+ LineTag tag = GetLine (i).tags;
+
+ if (tag.Start != 1)
+ throw new Exception ("Line doesn't start at the begining");
+
+ int start = 1;
+ tag = tag.Next;
+
+ while (tag != null) {
+ if (tag.Start == start)
+ throw new Exception ("Empty tag!");
+
+ if (tag.Start < start)
+ throw new Exception ("Insane!!");
+
+ start = tag.Start;
+ tag = tag.Next;
+ }
+ }
}
+#endif
// Adds a line of text, with given font.
// Bumps any line at that line number that already exists down
- internal void Add (int LineNo, string Text, Font font, SolidBrush color, LineEnding ending)
+ internal void Add (int LineNo, string Text, Font font, Color color, LineEnding ending)
{
Add (LineNo, Text, alignment, font, color, ending);
}
- internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, SolidBrush color, LineEnding ending)
+ internal void Add (int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending)
{
Line add;
Line line;
return clone;
}
- internal void Delete(int LineNo) {
+ private void Delete (int LineNo)
+ {
Line line;
- if (LineNo>lines) {
+ if (LineNo > lines)
return;
- }
- line = GetLine(LineNo);
+ line = GetLine (LineNo);
CharCount -= line.text.Length;
- DecrementLines(LineNo + 1);
- Delete(line);
+ DecrementLines (LineNo + 1);
+ Delete (line);
}
- internal void Delete(Line line1) {
+ private void Delete(Line line1) {
Line line2;// = new Line();
Line line3;
tag = line1.tags;
while (tag != null) {
- tag.line = line1;
- tag = tag.next;
+ tag.Line = line1;
+ tag = tag.Next;
}
}
this.lines--;
}
+ // Invalidates the start line until the end of the viewstate
+ internal void InvalidateLinesAfter (Line start) {
+ owner.Invalidate (new Rectangle (0, start.Y - viewport_y, viewport_width, viewport_height - start.Y));
+ }
+
// Invalidate a section of the document to trigger redraw
internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
Line l1;
#endif
owner.Invalidate(new Rectangle (
- (int)l1.widths[p1] + l1.X - viewport_x,
- l1.Y - viewport_y,
- endpoint - (int)l1.widths[p1] + 1,
+ offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
+ offset_y + l1.Y - viewport_y,
+ endpoint - (int) l1.widths [p1] + 1,
l1.height));
return;
}
// Three invalidates:
// First line from start
- owner.Invalidate(new Rectangle((int)l1.widths[p1] + l1.X - viewport_x, l1.Y - viewport_y, viewport_width, l1.height));
+ owner.Invalidate(new Rectangle(
+ offset_x + (int)l1.widths[p1] + l1.X - viewport_x,
+ offset_y + l1.Y - viewport_y,
+ viewport_width,
+ l1.height));
// lines inbetween
int y;
y = GetLine(l1.line_no + 1).Y;
- owner.Invalidate(new Rectangle(0, y - viewport_y, viewport_width, l2.Y - y));
+ owner.Invalidate(new Rectangle(
+ offset_x,
+ offset_y + y - viewport_y,
+ viewport_width,
+ l2.Y - y));
#if Debug
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);
// Last line to end
- owner.Invalidate(new Rectangle((int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height));
+ owner.Invalidate(new Rectangle(
+ offset_x + (int)l2.widths[0] + l2.X - viewport_x,
+ offset_y + l2.Y - viewport_y,
+ (int)l2.widths[p2] + 1,
+ l2.height));
+
#if Debug
Console.WriteLine("Invaliding from {0}:{1} to {2}:{3} End => x={4}, y={5}, {6}x{7}", l1.line_no, p1, l2.line_no, p2, (int)l2.widths[0] + l2.X - viewport_x, l2.Y - viewport_y, (int)l2.widths[p2] + 1, l2.height);
-
#endif
}
} else {
selection_start.line = selection_anchor.line;
selection_start.pos = selection_anchor.height;
- selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
+ selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
selection_end.line = caret.line;
selection_end.tag = caret.line.tags;
}
if (caret < selection_anchor) {
selection_start.line = caret.line;
- selection_start.tag = caret.line.FindTag(start_pos);
+ selection_start.tag = caret.line.FindTag(start_pos + 1);
selection_start.pos = start_pos;
selection_end.line = selection_anchor.line;
} else {
selection_start.line = selection_anchor.line;
selection_start.pos = selection_anchor.height;
- selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height);
+ selection_start.tag = selection_anchor.line.FindTag(selection_anchor.height + 1);
selection_end.line = caret.line;
selection_end.tag = caret.line.FindTag(end_pos);
this.Invalidate(selection_start.line, start_pos, caret.line, end_pos);
selection_start.line = caret.line;
- selection_start.tag = caret.line.FindTag(start_pos);
+ selection_start.tag = caret.line.FindTag(start_pos + 1);
selection_start.pos = start_pos;
selection_end.line = caret.line;
start = selection_start.line.line_no;
end = selection_end.line.line_no;
- sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
+ sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos));
if ((start + 1) < end) {
for (i = start + 1; i < end; i++) {
- sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
+ sb.Append(GetLine(i).text.ToString());
}
}
internal void ReplaceSelection(string s, bool select_new) {
int i;
- int selection_pos_on_line = selection_start.pos;
int selection_start_pos = LineTagToCharIndex (selection_start.line, selection_start.pos);
SuspendRecalc ();
if (selection_start.line == selection_end.line) {
undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
- DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
+ DeleteChars (selection_start.line, selection_start.pos, selection_end.pos - selection_start.pos);
// The tag might have been removed, we need to recalc it
- selection_start.tag = selection_start.line.FindTag(selection_start.pos);
+ selection_start.tag = selection_start.line.FindTag(selection_start.pos + 1);
} else {
int start;
int end;
undo.RecordDeleteString (selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
- InvalidateSelectionArea ();
+ InvalidateLinesAfter(selection_start.line);
// Delete first line
- DeleteChars(selection_start.tag, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
+ DeleteChars (selection_start.line, selection_start.pos, selection_start.line.text.Length - selection_start.pos);
selection_start.line.recalc = true;
// Delete last line
- DeleteChars(selection_end.line.tags, 0, selection_end.pos);
+ DeleteChars(selection_end.line, 0, selection_end.pos);
start++;
if (start < end) {
undo.RecordInsertString (selection_start.line, selection_start.pos, s);
ResumeRecalc (false);
+ Line begin_update_line = selection_start.line;
+ int begin_update_pos = selection_start.pos;
+
if (!select_new) {
CharIndexToLineTag(selection_start_pos + s.Length, out selection_start.line,
out selection_start.tag, out selection_start.pos);
}
PositionCaret (selection_start.line, selection_start.pos);
- UpdateView (selection_start.line, selection_pos_on_line);
+ UpdateView (begin_update_line, selection_end.line.line_no - begin_update_line.line_no, begin_update_pos);
}
internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
tag = line.tags;
while (tag != null) {
- if (index < (start + tag.start + tag.length - 1)) {
+ if (index < (start + tag.Start + tag.Length - 1)) {
line_out = line;
tag_out = LineTag.GetFinalTag (tag);
pos = index - start;
return;
}
- if (tag.next == null) {
+ if (tag.Next == null) {
Line next_line;
next_line = GetLine(line.line_no + 1);
return;
}
}
- tag = tag.next;
+ tag = tag.Next;
}
}
}
line_out = GetLine(lines);
tag = line_out.tags;
- while (tag.next != null) {
- tag = tag.next;
+ while (tag.Next != null) {
+ tag = tag.Next;
}
tag_out = tag;
pos = line_out.text.Length;
}
+ // UIA: Method used via reflection in TextRangeProvider
+
/// <summary>Give it a Line number and it returns the Line object at with that line number</summary>
internal Line GetLine(int LineNo) {
Line line = document;
internal LineTag PreviousTag(LineTag tag) {
Line l;
- if (tag.previous != null) {
- return tag.previous;
+ if (tag.Previous != null) {
+ return tag.Previous;
}
// Next line
- if (tag.line.line_no == 1) {
+ if (tag.Line.line_no == 1) {
return null;
}
- l = GetLine(tag.line.line_no - 1);
+ l = GetLine(tag.Line.line_no - 1);
if (l != null) {
LineTag t;
t = l.tags;
- while (t.next != null) {
- t = t.next;
+ while (t.Next != null) {
+ t = t.Next;
}
return t;
}
internal LineTag NextTag(LineTag tag) {
Line l;
- if (tag.next != null) {
- return tag.next;
+ if (tag.Next != null) {
+ return tag.Next;
}
// Next line
- l = GetLine(tag.line.line_no + 1);
+ l = GetLine(tag.Line.line_no + 1);
if (l != null) {
return l.tags;
}
}
internal Line ParagraphStart(Line line) {
- while (line.ending == LineEnding.Wrap) {
- line = GetLine(line.line_no - 1);
- }
+ Line lastline = line;
+ do {
+ if (line.line_no <= 1)
+ break;
+
+ line = lastline;
+ lastline = GetLine (line.line_no - 1);
+ } while (lastline.ending == LineEnding.Wrap);
+
return line;
}
return last;
}
- // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
- internal LineTag FindTag(int x, int y, out int index, bool exact) {
- Line line;
- LineTag tag;
-
- line = GetLineByPixel(y, exact);
- if (line == null) {
- index = 0;
- return null;
- }
- tag = line.tags;
-
- // Alignment adjustment
- x += line.X;
-
- while (true) {
- if (x >= tag.X && x < (tag.X+tag.width)) {
- int end;
-
- end = tag.start + tag.length - 1;
-
- for (int pos = tag.start; pos < end; pos++) {
- if (x < line.widths[pos]) {
- index = pos;
- return LineTag.GetFinalTag (tag);
- }
- }
- index=end;
- return LineTag.GetFinalTag (tag);
- }
- if (tag.next != null) {
- tag = tag.next;
- } else {
- if (exact) {
- index = 0;
- return null;
- }
+ // UIA: Method used via reflection in TextProviderBehavior
- index = line.text.Length;
- return LineTag.GetFinalTag (tag);
- }
- }
- }
+ // Give it x/y pixel coordinates and it returns the Tag at that position
+ internal LineTag FindCursor (int x, int y, out int index)
+ {
+ Line line;
- // Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
- internal LineTag FindCursor(int x, int y, out int index) {
- Line line;
- LineTag tag;
+ x -= offset_x;
+ y -= offset_y;
- line = GetLineByPixel(multiline ? y : x, false);
- tag = line.tags;
+ line = GetLineByPixel (multiline ? y : x, false);
- /// Special case going leftwards of the first tag
- if (x < tag.X) {
+ LineTag tag = line.GetTag (x);
+
+ if (tag.Length == 0 && tag.Start == 1)
index = 0;
- return LineTag.GetFinalTag (tag);
- }
-
- while (true) {
- if (x >= tag.X && x < (tag.X+tag.width)) {
- int end;
-
- end = tag.TextEnd;
-
- for (int pos = tag.start - 1; pos < end; pos++) {
- // When clicking on a character, we position the cursor to whatever edge
- // of the character the click was closer
- if (x < (line.X + line.widths[pos] + ((line.widths[pos+1]-line.widths[pos])/2))) {
- index = pos;
- return LineTag.GetFinalTag (tag);
- }
- }
- index=end;
- return LineTag.GetFinalTag (tag);
- }
- if (tag.next != null) {
- tag = tag.next;
- } else {
- index = line.TextLengthWithoutEnding ();
- return LineTag.GetFinalTag (tag);
- }
- }
+ else
+ index = tag.GetCharIndex (x - line.align_shift);
+
+ return tag;
}
/// <summary>Format area of document in specified font and color</summary>
/// <param name="start_pos">1-based start position on start_line</param>
/// <param name="end_pos">1-based end position on end_line </param>
internal void FormatText (Line start_line, int start_pos, Line end_line, int end_pos, Font font,
- SolidBrush color, SolidBrush back_color, FormatSpecified specified)
+ Color color, Color back_color, FormatSpecified specified)
{
Line l;
} else {
// Special case, single line
LineTag.FormatText(start_line, start_pos, end_pos - start_pos, font, color, back_color, specified);
+
+ if ((end_pos - start_pos) == 0 && CaretTag.Length != 0)
+ CaretTag = CaretTag.Next;
}
}
}
// Fixup the positions, they can go kinda nuts
+ // (this is suspend and resume recalc - they set them to 1 and max)
start = Math.Max (start, 1);
end = Math.Min (end, lines);
line = GetLine(line_no++);
line.offset = offset;
+ // if we are not calculating a password
if (!calc_pass) {
if (!optimize) {
line.RecalculateLine(g, this);
HeightChanged(this, null);
}
}
+
+ // scan for links and tell us if its all
+ // changed, so we can update everything
+ if (EnableLinks)
+ ScanForLinks (start, end, ref changed);
+
UpdateCaret();
return changed;
}
} else {
mark.line = GetLine(lines);
mark.tag = mark.line.tags;
- while (mark.tag.next != null) {
- mark.tag = mark.tag.next;
+ while (mark.tag.Next != null) {
+ mark.tag = mark.tag.Next;
}
mark.pos = mark.line.text.Length;
}
internal event EventHandler WidthChanged;
internal event EventHandler HeightChanged;
internal event EventHandler LengthChanged;
+ internal event EventHandler UIASelectionChanged;
#endregion // Events
#region Administrative
get { return false; }
}
- internal override SizeF SizeOfPosition (Graphics dc, int pos)
+ public override SizeF SizeOfPosition (Graphics dc, int pos)
{
return picture.Size;
}
return (int) (picture.Height + 0.5F);
}
- internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end)
+ public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end)
{
- picture.DrawImage (dc, xoff + line.widths [start], y, false);
+ picture.DrawImage (dc, xoff + Line.widths [start], y, false);
}
- internal override void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
+ public override void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)
{
- picture.DrawImage (dc, xoff + + line.widths [start], y, false);
+ picture.DrawImage (dc, xoff + + Line.widths [start], y, false);
}
public override string Text ()
}
}
- internal class LineTag {
- #region Local Variables;
- // Payload; formatting
- internal Font font; // System.Drawing.Font object for this tag
- internal SolidBrush color; // The font color for this tag
-
- // In 2.0 tags can have background colours. I'm not going to #ifdef
- // at this level though since I want to reduce code paths
- internal SolidBrush back_color;
-
- // Payload; text
- internal int start; // start, in chars; index into Line.text
- internal bool r_to_l; // Which way is the font
-
- // Drawing support
- internal int height; // Height in pixels of the text this tag describes
-
- internal int ascent; // Ascent of the font for this tag
- internal int shift; // Shift down for this tag, to stay on baseline
-
- // Administrative
- internal Line line; // The line we're on
- internal LineTag next; // Next tag on the same line
- internal LineTag previous; // Previous tag on the same line
- #endregion;
-
- #region Constructors
- internal LineTag(Line line, int start) {
- this.line = line;
- this.start = start;
- }
- #endregion // Constructors
-
- #region Internal Methods
-
- public float X {
- get {
- if (start == 0)
- return line.X;
- return line.X + line.widths [start - 1];
- }
- }
-
- public int end {
- get { return start + length; }
- }
-
- public int TextEnd {
- get { return start + TextLength; }
- }
-
- public float width {
- get {
- if (length == 0)
- return 0;
- return line.widths [start + length - 1] - (start != 0 ? line.widths [start - 1] : 0);
- }
- }
-
- public int length {
- get {
- int res = 0;
- if (next != null)
- res = next.start - start;
- else
- res = line.text.Length - (start - 1);
-
- return res > 0 ? res : 0;
- }
- }
-
- public int TextLength {
- get {
- int res = 0;
- if (next != null)
- res = next.start - start;
- else
- res = line.TextLengthWithoutEnding () - (start - 1);
-
- return res > 0 ? res : 0;
- }
- }
-
- public virtual bool IsTextTag {
- get { return true; }
- }
-
- internal virtual SizeF SizeOfPosition (Graphics dc, int pos)
- {
- if (pos >= line.TextLengthWithoutEnding () && line.document.multiline)
- return SizeF.Empty;
-
- string text = line.text.ToString (pos, 1);
- switch ((int) text [0]) {
- case '\t':
- if (!line.document.multiline)
- goto case 10;
- SizeF res = dc.MeasureString (" ", font, 10000, Document.string_format);
- res.Width *= 8.0F;
- return res;
- case 10:
- case 13:
- return dc.MeasureString ("\u0013", font, 10000, Document.string_format);
- }
-
- return dc.MeasureString (text, font, 10000, Document.string_format);
- }
-
- internal virtual int MaxHeight ()
- {
- return font.Height;
- }
-
- internal virtual void Draw (Graphics dc, Brush brush, float x, float y, int start, int end)
- {
- dc.DrawString (line.text.ToString (start, end), font, brush, x, y, StringFormat.GenericTypographic);
- }
-
- internal virtual void Draw (Graphics dc, Brush brush, float xoff, float y, int start, int end, string text)
- {
- while (start < end) {
- int tab_index = text.IndexOf ("\t", start);
- if (tab_index == -1)
- tab_index = end;
- dc.DrawString (text.Substring (start, tab_index - start), font, brush, xoff + line.widths [start],
- y, StringFormat.GenericTypographic);
-
- // non multilines get the unknown char
- if (!line.document.multiline && tab_index != end)
- dc.DrawString ("\u0013", font, brush, xoff + line.widths [tab_index], y, Document.string_format);
-
- start = tab_index + 1;
- }
- }
-
- ///<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>
- internal LineTag Break(int pos) {
-
- LineTag new_tag;
-
- // Sanity
- if (pos == this.start) {
- return this;
- } else if (pos >= (start + length)) {
- return null;
- }
-
- new_tag = new LineTag(line, pos);
- new_tag.CopyFormattingFrom (this);
-
- new_tag.next = this.next;
- this.next = new_tag;
- new_tag.previous = this;
-
- if (new_tag.next != null) {
- new_tag.next.previous = new_tag;
- }
-
- return new_tag;
- }
-
- public virtual string Text ()
- {
- return line.text.ToString (start - 1, length);
- }
-
- public void CopyFormattingFrom (LineTag other)
- {
- height = other.height;
- font = other.font;
- color = other.color;
- back_color = other.back_color;
- }
-
- /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars;
- /// Removes any previous tags overlapping the same area;
- /// returns true if lineheight has changed</summary>
- /// <param name="start">1-based character position on line</param>
- internal static bool FormatText(Line line, int start, int length, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
- {
- LineTag tag;
- LineTag start_tag;
- LineTag end_tag;
- int end;
- bool retval = false; // Assume line-height doesn't change
-
- // Too simple?
- if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) {
- retval = true;
- }
- line.recalc = true; // This forces recalculation of the line in RecalculateDocument
-
- // A little sanity, not sure if it's needed, might be able to remove for speed
- if (length > line.text.Length) {
- length = line.text.Length;
- }
-
- tag = line.tags;
- end = start + length;
-
- // Common special case
- if ((start == 1) && (length == tag.length)) {
- tag.ascent = 0;
- SetFormat (tag, font, color, back_color, specified);
- return retval;
- }
-
- start_tag = FindTag (line, start);
- tag = start_tag.Break (start);
-
- while (tag != null && tag.end <= end) {
- SetFormat (tag, font, color, back_color, specified);
- tag = tag.next;
- }
-
- if (tag != null && tag.end == end)
- return retval;
-
- /// Now do the last tag
- end_tag = FindTag (line, end);
-
- if (end_tag != null) {
- end_tag.Break (end);
- SetFormat (end_tag, font, color, back_color, specified);
- }
-
- return retval;
- }
-
- private static void SetFormat (LineTag tag, Font font, SolidBrush color, SolidBrush back_color, FormatSpecified specified)
- {
- if ((FormatSpecified.Font & specified) == FormatSpecified.Font)
- tag.font = font;
- if ((FormatSpecified.Color & specified) == FormatSpecified.Color)
- tag.color = color;
- if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {
- tag.back_color = back_color;
- }
- // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color);
- }
-
- /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>
- internal static LineTag FindTag(Line line, int pos) {
- LineTag tag = line.tags;
-
- // Beginning of line is a bit special
- if (pos == 0) {
- // Not sure if we should get the final tag here
- return tag;
- }
-
- while (tag != null) {
- if ((tag.start <= pos) && (pos <= tag.end)) {
- return GetFinalTag (tag);
- }
-
- tag = tag.next;
- }
-
- return null;
- }
-
- // There can be multiple tags at the same position, we want to make
- // sure we are using the very last tag at the given position
- internal static LineTag GetFinalTag (LineTag tag)
- {
- LineTag res = tag;
-
- while (res.length == 0 && res.next != null && res.next.length == 0)
- res = res.next;
-
- return res;
- }
-
- /// <summary>Combines 'this' tag with 'other' tag</summary>
- internal bool Combine(LineTag other) {
- if (!this.Equals(other)) {
- return false;
- }
-
- this.next = other.next;
- if (this.next != null) {
- this.next.previous = this;
- }
-
- return true;
- }
-
-
- /// <summary>Remove 'this' tag ; to be called when formatting is to be removed</summary>
- internal bool Remove() {
- if ((this.start == 1) && (this.next == null)) {
- // We cannot remove the only tag
- return false;
- }
- if (this.start != 1) {
- this.previous.next = this.next;
- this.next.previous = this.previous;
- } else {
- this.next.start = 1;
- this.line.tags = this.next;
- this.next.previous = null;
- }
- return true;
- }
-
-
- /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>
- public override bool Equals(object obj) {
- LineTag other;
-
- if (obj == null) {
- return false;
- }
-
- if (!(obj is LineTag)) {
- return false;
- }
-
- if (obj == this) {
- return true;
- }
-
- other = (LineTag)obj;
-
- if (other.IsTextTag != IsTextTag)
- return false;
-
- if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
- return true;
- }
-
- return false;
- }
-
- public override int GetHashCode() {
- return base.GetHashCode ();
- }
-
- public override string ToString() {
- if (length > 0)
- return GetType () + " Tag starts at index " + this.start + " length " + this.length + " text: " + Text () + "Font " + this.font.ToString();
- return "Zero Lengthed tag at index " + this.start;
- }
-
- #endregion // Internal Methods
- }
-
internal class UndoManager {
internal enum ActionType {
redo_actions.Clear();
}
- internal void Undo ()
+ internal bool Undo ()
{
Action action;
bool user_action_finished = false;
if (undo_actions.Count == 0)
- return;
-
- // Nuke the redo queue
- redo_actions.Clear ();
+ return false;
locked = true;
do {
} while (!user_action_finished && undo_actions.Count > 0);
locked = false;
+
+ return true;
}
- internal void Redo ()
+ internal bool Redo ()
{
Action action;
bool user_action_finished = false;
if (redo_actions.Count == 0)
- return;
-
- // You can't undo anything after redoing
- undo_actions.Clear ();
+ return false;
locked = true;
do {
int start_index;
action = (Action) redo_actions.Pop ();
+ undo_actions.Push (action);
switch (action.type) {
} while (!user_action_finished && redo_actions.Count > 0);
locked = false;
+
+ return true;
}
#endregion // Internal Methods
if (locked)
return;
+ // Nuke the redo queue
+ redo_actions.Clear ();
+
Action ua = new Action ();
ua.type = ActionType.UserActionBegin;
ua.data = name;
if (locked)
return;
+ // Nuke the redo queue
+ redo_actions.Clear ();
+
Action a = new Action ();
// We cant simply store the string, because then formatting would be lost
if (locked || str.Length == 0)
return;
+ // Nuke the redo queue
+ redo_actions.Clear ();
+
Action a = new Action ();
a.type = ActionType.InsertString;
if (locked)
return;
+ // Nuke the redo queue
+ redo_actions.Clear ();
+
Action a = null;
if (undo_actions.Count > 0)
line.text = new StringBuilder (current.text.ToString (start, end - start));
// Copy tags from start to start+length onto new line
- current_tag = current.FindTag (start);
- while ((current_tag != null) && (current_tag.start <= end)) {
- if ((current_tag.start <= start) && (start < (current_tag.start + current_tag.length))) {
+ current_tag = current.FindTag (start + 1);
+ while ((current_tag != null) && (current_tag.Start <= end)) {
+ if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) {
// start tag is within this tag
tag_start = start;
} else {
- tag_start = current_tag.start;
+ tag_start = current_tag.Start;
}
tag = new LineTag(line, tag_start - start + 1);
tag.CopyFormattingFrom (current_tag);
- current_tag = current_tag.next;
+ current_tag = current_tag.Next;
// Add the new tag to the line
if (line.tags == null) {
LineTag tail;
tail = line.tags;
- while (tail.next != null) {
- tail = tail.next;
+ while (tail.Next != null) {
+ tail = tail.Next;
}
- tail.next = tag;
- tag.previous = tail;
+ tail.Next = tag;
+ tag.Previous = tail;
}
}
//Insert our tags at the end
tag = line.tags;
- while (tag.next != null) {
- tag = tag.next;
+ while (tag.Next != null) {
+ tag = tag.Next;
}
- offset = tag.start + tag.length - 1;
+ offset = tag.Start + tag.Length - 1;
- tag.next = insert.tags;
+ tag.Next = insert.tags;
line.text.Insert(offset, insert.text.ToString());
// Adjust start locations
- tag = tag.next;
+ tag = tag.Next;
while (tag != null) {
- tag.start += offset;
- tag.line = line;
- tag = tag.next;
+ tag.Start += offset;
+ tag.Line = line;
+ tag = tag.Next;
}
// Put it back together
document.Combine(line.line_no, line.line_no + 1);
tag = line.tags;
- if (tag != null && tag.length != 0) {
- while (tag.next != null) {
- tag = tag.next;
+ if (tag != null && tag.Length != 0) {
+ while (tag.Next != null) {
+ tag = tag.Next;
}
- offset = tag.start + tag.length - 1;
- tag.next = current.tags;
- tag.next.previous = tag;
+ offset = tag.Start + tag.Length - 1;
+ tag.Next = current.tags;
+ tag.Next.Previous = tag;
- tag = tag.next;
+ tag = tag.Next;
} else {
offset = 0;
line.tags = current.tags;
- line.tags.previous = null;
+ line.tags.Previous = null;
tag = line.tags;
}
document.Split(line.line_no, 0);
offset = 0;
line.tags = current.tags;
- line.tags.previous = null;
+ line.tags.Previous = null;
line.ending = current.ending;
tag = line.tags;
}
// Adjust start locations and line pointers
while (tag != null) {
- tag.start += offset - 1;
- tag.line = line;
- tag = tag.next;
+ tag.Start += offset - 1;
+ tag.Line = line;
+ tag = tag.Next;
}
line.text.Insert(offset, current.text.ToString());
line = document.GetLine(line.line_no + 1);
// FIXME? Test undo of line-boundaries
- if ((current.right == null) && (current.tags.length != 0)) {
+ if ((current.right == null) && (current.tags.Length != 0)) {
document.Combine(line.line_no - 1, line.line_no);
}
current = current.right;