From 1eabdccf126125a9b9ada40c39ceeed7bb757387 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 23 Sep 2007 17:28:40 +0000 Subject: [PATCH] Add TextAnchor to text editor. Usage: TextAnchor anchor = document.GetLineSegment(lineNumber).CreateAnchor(columnNumber); anchor.LineNumber and anchor.ColumnNumber are updated when text is inserted/removed in the document. anchor.IsDeleted will be true when the text location containing the anchor was removed. LineSegment gets an IsDeleted property, so a LineSegment reference can be used as anchor for a line. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@2683 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Project/ICSharpCode.TextEditor.csproj | 2 + .../Document/DefaultTextEditorProperties.cs | 10 +- .../DefaultHighlightingStrategy.cs | 142 +++++++++- .../LineManager/DefaultLineManager.cs | 23 +- .../Src/Document/LineManager/LineSegment.cs | 242 +++++++++--------- .../Document/LineManager/LineSegmentTree.cs | 6 + .../Project/Src/Document/TextAnchor.cs | 88 +++++++ .../Project/Src/Gui/Caret.cs | 4 +- .../Project/Src/Gui/TextArea.cs | 6 +- .../Project/Src/Gui/TextEditorControl.cs | 6 +- .../Project/Src/Gui/TextEditorControlBase.cs | 35 +-- .../Project/Src/Util/WeakCollection.cs | 136 ++++++++++ 12 files changed, 541 insertions(+), 159 deletions(-) create mode 100644 src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs create mode 100644 src/Libraries/ICSharpCode.TextEditor/Project/Src/Util/WeakCollection.cs diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditor.csproj b/src/Libraries/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditor.csproj index 2c081df7a8..b067093c9c 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditor.csproj +++ b/src/Libraries/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditor.csproj @@ -60,6 +60,7 @@ + @@ -207,6 +208,7 @@ Configuration\GlobalAssemblyInfo.cs + diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/DefaultTextEditorProperties.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/DefaultTextEditorProperties.cs index 04ff6d8179..4d7a7d59a1 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/DefaultTextEditorProperties.cs +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/DefaultTextEditorProperties.cs @@ -40,13 +40,13 @@ namespace ICSharpCode.TextEditor.Document bool showMatchingBracket = true; bool showLineNumbers = true; - bool showSpaces = true; - bool showTabs = true; - bool showEOLMarker = true; + bool showSpaces = false; + bool showTabs = false; + bool showEOLMarker = false; - bool showInvalidLines = true; + bool showInvalidLines = false; - bool isIconBarVisible = true; + bool isIconBarVisible = false; bool enableFolding = true; bool showHorizontalRuler = false; bool showVerticalRuler = true; diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/DefaultHighlightingStrategy.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/DefaultHighlightingStrategy.cs index c47b7eddbc..3e8f6e4be9 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/DefaultHighlightingStrategy.cs +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/DefaultHighlightingStrategy.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Text; using System.Drawing; using System.Windows.Forms; @@ -620,9 +621,9 @@ namespace ICSharpCode.TextEditor.Document // Check for SPAN ENDs if (inSpan) { if (activeSpan.End != null && activeSpan.End.Length > 0) { - if (currentLine.MatchExpr(activeSpan.End, i, document, activeSpan.IgnoreCase)) { + if (MatchExpr(currentLine, activeSpan.End, i, document, activeSpan.IgnoreCase)) { PushCurWord(document, ref markNext, words); - string regex = currentLine.GetRegString(activeSpan.End, i, document); + string regex = GetRegString(currentLine, activeSpan.End, i, document); currentLength += regex.Length; words.Add(new TextWord(document, currentLine, currentOffset, currentLength, activeSpan.EndColor, false)); currentOffset += currentLength; @@ -640,9 +641,9 @@ namespace ICSharpCode.TextEditor.Document foreach (Span span in activeRuleSet.Spans) { if ((!span.IsBeginSingleWord || currentLength == 0) && (!span.IsBeginStartOfLine.HasValue || span.IsBeginStartOfLine.Value == (currentLength == 0 && words.TrueForAll(delegate(TextWord textWord) { return textWord.Type != TextWordType.Word; }))) - && currentLine.MatchExpr(span.Begin, i, document, activeRuleSet.IgnoreCase)) { + && MatchExpr(currentLine, span.Begin, i, document, activeRuleSet.IgnoreCase)) { PushCurWord(document, ref markNext, words); - string regex = currentLine.GetRegString(span.Begin, i, document); + string regex = GetRegString(currentLine, span.Begin, i, document); if (!OverrideSpan(regex, document, words, span, ref i)) { currentLength += regex.Length; @@ -768,5 +769,138 @@ namespace ICSharpCode.TextEditor.Document currentLength = 0; } } + + #region Matching + /// + /// get the string, which matches the regular expression expr, + /// in string s2 at index + /// + static string GetRegString(LineSegment lineSegment, char[] expr, int index, IDocument document) + { + int j = 0; + StringBuilder regexpr = new StringBuilder(); + + for (int i = 0; i < expr.Length; ++i, ++j) { + if (index + j >= lineSegment.Length) + break; + + switch (expr[i]) { + case '@': // "special" meaning + ++i; + switch (expr[i]) { + case '!': // don't match the following expression + StringBuilder whatmatch = new StringBuilder(); + ++i; + while (i < expr.Length && expr[i] != '@') { + whatmatch.Append(expr[i++]); + } + break; + case '@': // matches @ + regexpr.Append(document.GetCharAt(lineSegment.Offset + index + j)); + break; + } + break; + default: + if (expr[i] != document.GetCharAt(lineSegment.Offset + index + j)) { + return regexpr.ToString(); + } + regexpr.Append(document.GetCharAt(lineSegment.Offset + index + j)); + break; + } + } + return regexpr.ToString(); + } + + /// + /// returns true, if the get the string s2 at index matches the expression expr + /// + static bool MatchExpr(LineSegment lineSegment, char[] expr, int index, IDocument document, bool ignoreCase) + { + for (int i = 0, j = 0; i < expr.Length; ++i, ++j) { + switch (expr[i]) { + case '@': // "special" meaning + ++i; + if (i < expr.Length) { + switch (expr[i]) { + case 'C': // match whitespace or punctuation + if (index + j == lineSegment.Offset || index + j >= lineSegment.Offset + lineSegment.Length) { + // nothing (EOL or SOL) + } else { + char ch = document.GetCharAt(lineSegment.Offset + index + j); + if (!Char.IsWhiteSpace(ch) && !Char.IsPunctuation(ch)) { + return false; + } + } + break; + case '!': // don't match the following expression + { + StringBuilder whatmatch = new StringBuilder(); + ++i; + while (i < expr.Length && expr[i] != '@') { + whatmatch.Append(expr[i++]); + } + if (lineSegment.Offset + index + j + whatmatch.Length < document.TextLength) { + int k = 0; + for (; k < whatmatch.Length; ++k) { + char docChar = ignoreCase ? Char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index + j + k)) : document.GetCharAt(lineSegment.Offset + index + j + k); + char spanChar = ignoreCase ? Char.ToUpperInvariant(whatmatch[k]) : whatmatch[k]; + if (docChar != spanChar) { + break; + } + } + if (k >= whatmatch.Length) { + return false; + } + } +// --j; + break; + } + case '-': // don't match the expression before + { + StringBuilder whatmatch = new StringBuilder(); + ++i; + while (i < expr.Length && expr[i] != '@') { + whatmatch.Append(expr[i++]); + } + if (index - whatmatch.Length >= 0) { + int k = 0; + for (; k < whatmatch.Length; ++k) { + char docChar = ignoreCase ? Char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index - whatmatch.Length + k)) : document.GetCharAt(lineSegment.Offset + index - whatmatch.Length + k); + char spanChar = ignoreCase ? Char.ToUpperInvariant(whatmatch[k]) : whatmatch[k]; + if (docChar != spanChar) + break; + } + if (k >= whatmatch.Length) { + return false; + } + } +// --j; + break; + } + case '@': // matches @ + if (index + j >= lineSegment.Length || '@' != document.GetCharAt(lineSegment.Offset + index + j)) { + return false; + } + break; + } + } + break; + default: + { + if (index + j >= lineSegment.Length) { + return false; + } + char docChar = ignoreCase ? Char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index + j)) : document.GetCharAt(lineSegment.Offset + index + j); + char spanChar = ignoreCase ? Char.ToUpperInvariant(expr[i]) : expr[i]; + if (docChar != spanChar) { + return false; + } + break; + } + } + } + return true; + } + #endregion } } diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/DefaultLineManager.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/DefaultLineManager.cs index d658b7bba9..c061c4047c 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/DefaultLineManager.cs +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/DefaultLineManager.cs @@ -77,7 +77,7 @@ namespace ICSharpCode.TextEditor.Document public void Replace(int offset, int length, string text) { -// Console.WriteLine("Replace offset="+offset+" length="+length+" text.Length="+text.Length); + Console.WriteLine("Replace offset="+offset+" length="+length+" text.Length="+text.Length); int lineStart = GetLineNumberForOffset(offset); int oldNumberOfLines = this.TotalNumberOfLines; RemoveInternal(offset, length); @@ -106,6 +106,7 @@ namespace ICSharpCode.TextEditor.Document int startSegmentOffset = startSegment.Offset; if (offset + length < startSegmentOffset + startSegment.TotalLength) { // just removing a part of this line segment + startSegment.RemovedLinePart(offset - startSegmentOffset, length); SetSegmentLength(startSegment, startSegment.TotalLength - length); return; } @@ -113,6 +114,7 @@ namespace ICSharpCode.TextEditor.Document // possibly remove lines in between if multiple delimiters were deleted int charactersRemovedInStartLine = startSegmentOffset + startSegment.TotalLength - offset; Debug.Assert(charactersRemovedInStartLine > 0); + startSegment.RemovedLinePart(offset - startSegmentOffset, charactersRemovedInStartLine); LineSegment endSegment = lineCollection.GetByOffset(offset + length); @@ -124,6 +126,8 @@ namespace ICSharpCode.TextEditor.Document } int endSegmentOffset = endSegment.Offset; int charactersLeftInEndLine = endSegmentOffset + endSegment.TotalLength - (offset + length); + endSegment.RemovedLinePart(0, endSegment.TotalLength - charactersLeftInEndLine); + startSegment.MergedWith(endSegment, offset - startSegmentOffset); SetSegmentLength(startSegment, startSegment.TotalLength - charactersRemovedInStartLine + charactersLeftInEndLine); startSegment.DelimiterLength = endSegment.DelimiterLength; // remove all segments between startSegment (excl.) and endSegment (incl.) @@ -132,6 +136,7 @@ namespace ICSharpCode.TextEditor.Document do { segmentToRemove = it.Current; it.MoveNext(); + segmentToRemove.Deleted(); lineCollection.RemoveSegment(segmentToRemove); } while (segmentToRemove != endSegment); } @@ -139,9 +144,17 @@ namespace ICSharpCode.TextEditor.Document void InsertInternal(int offset, string text) { LineSegment segment = lineCollection.GetByOffset(offset); + DelimiterSegment ds = NextDelimiter(text, 0); + if (ds == null) { + // no newline is being inserted, all text is inserted in a single line + segment.InsertedLinePart(offset - segment.Offset, text.Length); + SetSegmentLength(segment, segment.TotalLength + text.Length); + return; + } + LineSegment firstLine = segment; + firstLine.InsertedLinePart(offset - firstLine.Offset, ds.Offset); int lastDelimiterEnd = 0; - DelimiterSegment ds; - while ((ds = NextDelimiter(text, lastDelimiterEnd)) != null) { + while (ds != null) { // split line segment at line delimiter int lineBreakOffset = offset + ds.Offset + ds.Length; int segmentOffset = segment.Offset; @@ -152,9 +165,13 @@ namespace ICSharpCode.TextEditor.Document segment = newSegment; lastDelimiterEnd = ds.Offset + ds.Length; + + ds = NextDelimiter(text, lastDelimiterEnd); } + firstLine.SplitTo(segment); // insert rest after last delimiter if (lastDelimiterEnd != text.Length) { + segment.InsertedLinePart(0, text.Length - lastDelimiterEnd); SetSegmentLength(segment, segment.TotalLength + text.Length - lastDelimiterEnd); } } diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegment.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegment.cs index 4199407dc1..caa55067b6 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegment.cs +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegment.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Diagnostics; using System.Text; namespace ICSharpCode.TextEditor.Document @@ -32,6 +33,10 @@ namespace ICSharpCode.TextEditor.Document return null; } + public bool IsDeleted { + get { return !treeEntry.IsValid; } + } + public int LineNumber { get { return treeEntry.CurrentIndex; } } @@ -60,7 +65,7 @@ namespace ICSharpCode.TextEditor.Document public int DelimiterLength { get { return delimiterLength; } - set { delimiterLength = value; } + internal set { delimiterLength = value; } } // highlighting information @@ -104,137 +109,140 @@ namespace ICSharpCode.TextEditor.Document return "[LineSegment: Offset = "+ Offset +", Length = " + Length + ", TotalLength = " + TotalLength + ", DelimiterLength = " + delimiterLength + "]"; } - // Svante Lidman: reconsider whether it was the right descision to move these methids here. + #region Anchor management + Util.WeakCollection anchors; + + public TextAnchor CreateAnchor(int column) + { + TextAnchor anchor = new TextAnchor(this, column); + AddAnchor(anchor); + return anchor; + } + + void AddAnchor(TextAnchor anchor) + { + Debug.Assert(anchor.Line == this); + + if (anchors == null) + anchors = new Util.WeakCollection(); + + anchors.Add(anchor); + } + + /// + /// Is called when the LineSegment is deleted. + /// + internal void Deleted() + { + //Console.WriteLine("Deleted"); + treeEntry = LineSegmentTree.Enumerator.Invalid; + if (anchors != null) { + foreach (TextAnchor a in anchors) { + a.Deleted(); + } + anchors = null; + } + } /// - /// get the string, which matches the regular expression expr, - /// in string s2 at index + /// Is called when a part of the line is removed. /// - internal string GetRegString(char[] expr, int index, IDocument document) + internal void RemovedLinePart(int startColumn, int length) { - int j = 0; - StringBuilder regexpr = new StringBuilder();; + if (length == 0) + return; + Debug.Assert(length > 0); - for (int i = 0; i < expr.Length; ++i, ++j) { - if (index + j >= this.Length) - break; - - switch (expr[i]) { - case '@': // "special" meaning - ++i; - switch (expr[i]) { - case '!': // don't match the following expression - StringBuilder whatmatch = new StringBuilder(); - ++i; - while (i < expr.Length && expr[i] != '@') { - whatmatch.Append(expr[i++]); - } - break; - case '@': // matches @ - regexpr.Append(document.GetCharAt(this.Offset + index + j)); - break; + //Console.WriteLine("RemovedLinePart " + startColumn + ", " + length); + if (anchors != null) { + List deletedAnchors = null; + foreach (TextAnchor a in anchors) { + if (a.ColumnNumber > startColumn) { + if (a.ColumnNumber >= startColumn + length) { + a.ColumnNumber -= length; + } else { + if (deletedAnchors == null) + deletedAnchors = new List(); + a.Deleted(); + deletedAnchors.Add(a); } - break; - default: - if (expr[i] != document.GetCharAt(this.Offset + index + j)) { - return regexpr.ToString(); - } - regexpr.Append(document.GetCharAt(this.Offset + index + j)); - break; + } + } + if (deletedAnchors != null) { + foreach (TextAnchor a in deletedAnchors) { + anchors.Remove(a); + } } } - return regexpr.ToString(); } /// - /// returns true, if the get the string s2 at index matches the expression expr + /// Is called when a part of the line is inserted. /// - internal bool MatchExpr(char[] expr, int index, IDocument document, bool ignoreCase) + internal void InsertedLinePart(int startColumn, int length) { - for (int i = 0, j = 0; i < expr.Length; ++i, ++j) { - switch (expr[i]) { - case '@': // "special" meaning - ++i; - if (i < expr.Length) { - switch (expr[i]) { - case 'C': // match whitespace or punctuation - if (index + j == this.Offset || index + j >= this.Offset + this.Length) { - // nothing (EOL or SOL) - } else { - char ch = document.GetCharAt(this.Offset + index + j); - if (!Char.IsWhiteSpace(ch) && !Char.IsPunctuation(ch)) { - return false; - } - } - break; - case '!': // don't match the following expression - { - StringBuilder whatmatch = new StringBuilder(); - ++i; - while (i < expr.Length && expr[i] != '@') { - whatmatch.Append(expr[i++]); - } - if (this.Offset + index + j + whatmatch.Length < document.TextLength) { - int k = 0; - for (; k < whatmatch.Length; ++k) { - char docChar = ignoreCase ? Char.ToUpperInvariant(document.GetCharAt(this.Offset + index + j + k)) : document.GetCharAt(this.Offset + index + j + k); - char spanChar = ignoreCase ? Char.ToUpperInvariant(whatmatch[k]) : whatmatch[k]; - if (docChar != spanChar) { - break; - } - } - if (k >= whatmatch.Length) { - return false; - } - } -// --j; - break; - } - case '-': // don't match the expression before - { - StringBuilder whatmatch = new StringBuilder(); - ++i; - while (i < expr.Length && expr[i] != '@') { - whatmatch.Append(expr[i++]); - } - if (index - whatmatch.Length >= 0) { - int k = 0; - for (; k < whatmatch.Length; ++k) { - char docChar = ignoreCase ? Char.ToUpperInvariant(document.GetCharAt(this.Offset + index - whatmatch.Length + k)) : document.GetCharAt(this.Offset + index - whatmatch.Length + k); - char spanChar = ignoreCase ? Char.ToUpperInvariant(whatmatch[k]) : whatmatch[k]; - if (docChar != spanChar) - break; - } - if (k >= whatmatch.Length) { - return false; - } - } -// --j; - break; - } - case '@': // matches @ - if (index + j >= this.Length || '@' != document.GetCharAt(this.Offset + index + j)) { - return false; - } - break; - } - } - break; - default: - { - if (index + j >= this.Length) { - return false; - } - char docChar = ignoreCase ? Char.ToUpperInvariant(document.GetCharAt(this.Offset + index + j)) : document.GetCharAt(this.Offset + index + j); - char spanChar = ignoreCase ? Char.ToUpperInvariant(expr[i]) : expr[i]; - if (docChar != spanChar) { - return false; - } - break; - } + if (length == 0) + return; + Debug.Assert(length > 0); + + //Console.WriteLine("InsertedLinePart " + startColumn + ", " + length); + if (anchors != null) { + foreach (TextAnchor a in anchors) { + if (a.ColumnNumber >= startColumn) { + a.ColumnNumber += length; + } + } + } + } + + /// + /// Is called after another line's content is appended to this line because the newline in between + /// was deleted. + /// The DefaultLineManager will call Deleted() on the deletedLine after the MergedWith call. + /// + /// firstLineLength: the length of the line before the merge. + /// + internal void MergedWith(LineSegment deletedLine, int firstLineLength) + { + //Console.WriteLine("MergedWith"); + + if (deletedLine.anchors != null) { + foreach (TextAnchor a in deletedLine.anchors) { + a.Line = this; + AddAnchor(a); + a.ColumnNumber += firstLineLength; + } + deletedLine.anchors = null; + } + } + + /// + /// Is called after a newline was inserted into this line, splitting it into this and followingLine. + /// + internal void SplitTo(LineSegment followingLine) + { + //Console.WriteLine("SplitTo"); + + if (anchors != null) { + List movedAnchors = null; + foreach (TextAnchor a in anchors) { + if (a.ColumnNumber > this.Length) { + a.Line = followingLine; + followingLine.AddAnchor(a); + a.ColumnNumber -= this.Length; + + if (movedAnchors == null) + movedAnchors = new List(); + movedAnchors.Add(a); + } + } + if (movedAnchors != null) { + foreach (TextAnchor a in movedAnchors) { + anchors.Remove(a); + } } } - return true; } + #endregion } } diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegmentTree.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegmentTree.cs index c404612de0..74d4c12ed9 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegmentTree.cs +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegmentTree.cs @@ -377,6 +377,12 @@ namespace ICSharpCode.TextEditor.Document public struct Enumerator : IEnumerator { + /// + /// An invalid enumerator value. Calling MoveNext on the invalid enumerator + /// will always return false, accessing Current will throw an exception. + /// + public static readonly Enumerator Invalid = default(Enumerator); + internal RedBlackTreeIterator it; internal Enumerator(RedBlackTreeIterator it) diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs new file mode 100644 index 0000000000..2721fb6f5e --- /dev/null +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs @@ -0,0 +1,88 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Description of TextAnchor. + /// + public sealed class TextAnchor + { + static Exception AnchorDeletedError() + { + return new InvalidOperationException("The text containing the anchor was deleted"); + } + + LineSegment lineSegment; + int columnNumber; + + public LineSegment Line { + get { + if (lineSegment == null) throw AnchorDeletedError(); + return lineSegment; + } + internal set { + lineSegment = value; + } + } + + public bool IsDeleted { + get { + return lineSegment == null; + } + } + + public int LineNumber { + get { + return this.Line.LineNumber; + } + } + + public int ColumnNumber { + get { + if (lineSegment == null) throw AnchorDeletedError(); + return columnNumber; + } + internal set { + columnNumber = value; + } + } + + public TextLocation Location { + get { + return new TextLocation(this.ColumnNumber, this.LineNumber); + } + } + + public int Offset { + get { + return this.Line.Offset + columnNumber; + } + } + + internal void Deleted() + { + lineSegment = null; + } + + internal TextAnchor(LineSegment lineSegment, int columnNumber) + { + this.lineSegment = lineSegment; + this.columnNumber = columnNumber; + } + + public override string ToString() + { + if (this.IsDeleted) + return "[TextAnchor (deleted)]"; + else + return "[TextAnchor " + this.Location.ToString() + "]"; + } + } +} diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/Caret.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/Caret.cs index db82ab75f8..6335a50751 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/Caret.cs +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/Caret.cs @@ -195,7 +195,7 @@ namespace ICSharpCode.TextEditor void GotFocus(object sender, EventArgs e) { hidden = false; - if (!textArea.MotherTextEditorControl.IsUpdating) { + if (!textArea.MotherTextEditorControl.IsInUpdate) { CreateCaret(); UpdateCaretPosition(); } @@ -226,7 +226,7 @@ namespace ICSharpCode.TextEditor oldLine = line; - if (hidden || textArea.MotherTextEditorControl.IsUpdating) { + if (hidden || textArea.MotherTextEditorControl.IsInUpdate) { return; } if (!caretCreated) { diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextArea.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextArea.cs index 91013eea6a..1d0ffddd0e 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextArea.cs +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextArea.cs @@ -741,7 +741,7 @@ namespace ICSharpCode.TextEditor /// public void InsertChar(char ch) { - bool updating = motherTextEditorControl.IsUpdating; + bool updating = motherTextEditorControl.IsInUpdate; if (!updating) { BeginUpdate(); } @@ -783,7 +783,7 @@ namespace ICSharpCode.TextEditor /// public void InsertString(string str) { - bool updating = motherTextEditorControl.IsUpdating; + bool updating = motherTextEditorControl.IsInUpdate; if (!updating) { BeginUpdate(); } @@ -824,7 +824,7 @@ namespace ICSharpCode.TextEditor /// public void ReplaceChar(char ch) { - bool updating = motherTextEditorControl.IsUpdating; + bool updating = motherTextEditorControl.IsInUpdate; if (!updating) { BeginUpdate(); } diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControl.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControl.cs index fd558f5bbd..79124d8898 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControl.cs +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControl.cs @@ -29,6 +29,7 @@ namespace ICSharpCode.TextEditor PrintDocument printDocument = null; + [Browsable(false)] public PrintDocument PrintDocument { get { if (printDocument == null) { @@ -128,11 +129,14 @@ namespace ICSharpCode.TextEditor } } + [Browsable(false)] public bool EnableUndo { get { return Document.UndoStack.CanUndo; } } + + [Browsable(false)] public bool EnableRedo { get { return Document.UndoStack.CanRedo; @@ -216,7 +220,7 @@ namespace ICSharpCode.TextEditor void CommitUpdateRequested(object sender, EventArgs e) { - if (IsUpdating) { + if (IsInUpdate) { return; } foreach (TextAreaUpdate update in Document.UpdateQueue) { diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlBase.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlBase.cs index 263e358ce3..d410773740 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlBase.cs +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlBase.cs @@ -37,6 +37,7 @@ namespace ICSharpCode.TextEditor /// protected Dictionary editactions = new Dictionary(); + [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ITextEditorProperties TextEditorProperties { get { @@ -83,17 +84,6 @@ namespace ICSharpCode.TextEditor } } - /// - /// true, if the textarea is updating it's status, while - /// it updates it status no redraw operation occurs. - /// - [Browsable(false)] - public bool IsUpdating { - get { - return updateLevel > 0; - } - } - /// /// The current document /// @@ -122,6 +112,7 @@ namespace ICSharpCode.TextEditor [EditorBrowsable(EditorBrowsableState.Always), Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + [Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor))] public override string Text { get { return Document.TextContent; @@ -158,10 +149,14 @@ namespace ICSharpCode.TextEditor } } + /// + /// true, if the textarea is updating it's status, while + /// it updates it status no redraw operation occurs. + /// [Browsable(false)] public bool IsInUpdate { get { - return this.updateLevel > 0; + return updateLevel > 0; } } @@ -260,7 +255,7 @@ namespace ICSharpCode.TextEditor /// If true the vertical ruler is shown in the textarea /// [Category("Appearance")] - [DefaultValue(false)] + [DefaultValue(true)] [Description("If true the vertical ruler is shown in the textarea")] public bool ShowVRuler { get { @@ -308,7 +303,7 @@ namespace ICSharpCode.TextEditor /// If true invalid lines are marked in the textarea /// [Category("Appearance")] - [DefaultValue(true)] + [DefaultValue(false)] [Description("If true invalid lines are marked in the textarea")] public bool ShowInvalidLines { get { @@ -350,7 +345,7 @@ namespace ICSharpCode.TextEditor } [Category("Appearance")] - [DefaultValue(true)] + [DefaultValue(false)] [Description("If true the icon bar is displayed")] public bool IsIconBarVisible { get { @@ -732,7 +727,7 @@ namespace ICSharpCode.TextEditor /// public override void Refresh() { - if (IsUpdating) { + if (IsInUpdate) { return; } base.Refresh(); @@ -755,14 +750,6 @@ namespace ICSharpCode.TextEditor } } - protected virtual void OnChanged(EventArgs e) - { - if (Changed != null) { - Changed(this, e); - } - } - public event EventHandler FileNameChanged; - public event EventHandler Changed; } } diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Util/WeakCollection.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Util/WeakCollection.cs new file mode 100644 index 0000000000..43bdfb7907 --- /dev/null +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Util/WeakCollection.cs @@ -0,0 +1,136 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.TextEditor.Util +{ + /// + /// A collection that does not allows its elements to be garbage-collected (unless there are other + /// references to the elements). Elements will disappear from the collection when they are + /// garbage-collected. + /// + /// The WeakCollection is not thread-safe, not even for read-only access! + /// No methods may be called on the WeakCollection while it is enumerated, not even a Contains or + /// creating a second enumerator. + /// The WeakCollection does not preserve any order among its contents; the ordering may be different each + /// time the collection is enumerated. + /// + /// Since items may disappear at any time when they are garbage collected, this class + /// cannot provide a useful implementation for Count and thus cannot implement the ICollection interface. + /// + public class WeakCollection : IEnumerable where T : class + { + readonly List innerList = new List(); + + /// + /// Adds an element to the collection. Runtime: O(n). + /// + public void Add(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + CheckNoEnumerator(); + if (innerList.Count == innerList.Capacity || (innerList.Count % 32) == 31) + innerList.RemoveAll(delegate(WeakReference r) { return !r.IsAlive; }); + innerList.Add(new WeakReference(item)); + } + + /// + /// Removes all elements from the collection. Runtime: O(n). + /// + public void Clear() + { + innerList.Clear(); + CheckNoEnumerator(); + } + + /// + /// Checks if the collection contains an item. Runtime: O(n). + /// + public bool Contains(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + CheckNoEnumerator(); + foreach (T element in this) { + if (item.Equals(element)) + return true; + } + return false; + } + + /// + /// Removes an element from the collection. Returns true if the item is found and removed, + /// false when the item is not found. + /// Runtime: O(n). + /// + public bool Remove(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + CheckNoEnumerator(); + for (int i = 0; i < innerList.Count;) { + T element = (T)innerList[i].Target; + if (element == null) { + RemoveAt(i); + } else if (element == item) { + RemoveAt(i); + return true; + } else { + i++; + } + } + return false; + } + + void RemoveAt(int i) + { + int lastIndex = innerList.Count - 1; + innerList[i] = innerList[lastIndex]; + innerList.RemoveAt(lastIndex); + } + + bool hasEnumerator; + + void CheckNoEnumerator() + { + if (hasEnumerator) + throw new InvalidOperationException("The WeakCollection is already being enumerated, it cannot be modified at the same time. Ensure you dispose the first enumerator before modifying the WeakCollection."); + } + + /// + /// Enumerates the collection. + /// Each MoveNext() call on the enumerator is O(1), thus the enumeration is O(n). + /// + public IEnumerator GetEnumerator() + { + if (hasEnumerator) + throw new InvalidOperationException("The WeakCollection is already being enumerated, it cannot be enumerated twice at the same time. Ensure you dispose the first enumerator before using another enumerator."); + try { + hasEnumerator = true; + for (int i = 0; i < innerList.Count;) { + T element = (T)innerList[i].Target; + if (element == null) { + RemoveAt(i); + } else { + yield return element; + i++; + } + } + } finally { + hasEnumerator = false; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +}