From 51fcc171d6c715cdd40727600f43264b8cbe0c1f Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 10 Aug 2009 17:44:47 +0000 Subject: [PATCH] Add ChangeTrackingCheckpoint to AvalonEdit.Document. Removed calls to LINQ Cast() where possible (now using C# 4.0 covariance). git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4639 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../XamlBinding/CompletionDataHelper.cs | 26 ++--- .../XamlBinding/XamlCodeCompletionBinding.cs | 2 - .../AvalonEdit.AddIn/Src/TextMarkerService.cs | 27 ++++-- .../Graph/TreeModel/AbstractNode.cs | 2 +- .../Project/Gui/SearchRootNode.cs | 2 +- .../Document/ChangeTrackingTest.cs | 68 ++++++++++++++ .../ICSharpCode.AvalonEdit.Tests.csproj | 1 + .../Document/ChangeTrackingCheckpoint.cs | 94 +++++++++++++++++++ .../Document/DocumentChangeEventArgs.cs | 34 +++++-- .../Document/DocumentChangeOperation.cs | 17 ++-- .../Document/TextDocument.cs | 42 +++++++-- .../Document/TextSegmentCollection.cs | 6 ++ .../Document/UndoStack.cs | 7 +- .../ICSharpCode.AvalonEdit.csproj | 1 + .../Utils/CallbackOnDispose.cs | 2 +- .../ICSharpCode.AvalonEdit/Utils/CharRope.cs | 2 + .../Base/Project/Src/Editor/ITextMarker.cs | 5 + .../Base/Project/Src/Util/ExtensionMethods.cs | 10 -- .../Base/Test/Utils/MockTextMarkerService.cs | 5 + .../Project/Src/MemberLookupHelper.cs | 4 +- 20 files changed, 281 insertions(+), 76 deletions(-) create mode 100644 src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/ChangeTrackingTest.cs create mode 100644 src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ChangeTrackingCheckpoint.cs diff --git a/src/AddIns/BackendBindings/XamlBinding/XamlBinding/CompletionDataHelper.cs b/src/AddIns/BackendBindings/XamlBinding/XamlBinding/CompletionDataHelper.cs index de58438026..428b57d7db 100644 --- a/src/AddIns/BackendBindings/XamlBinding/XamlBinding/CompletionDataHelper.cs +++ b/src/AddIns/BackendBindings/XamlBinding/XamlBinding/CompletionDataHelper.cs @@ -272,8 +272,7 @@ namespace ICSharpCode.XamlBinding return list .Distinct(new XmlnsEqualityComparer()) - .OrderBy(item => item, new XmlnsComparer()) - .Cast(); + .OrderBy(item => item, new XmlnsComparer()); } static string GetContentPropertyName(IReturnType type) @@ -403,16 +402,12 @@ namespace ICSharpCode.XamlBinding var neededItems = list.OfType() .Where(i => (i.Entity as IClass).DerivesFrom("System.Windows.Markup.MarkupExtension")); - neededItems - .ForEach( - selItem => { - var it = selItem as XamlCodeCompletionItem; - string text = it.Text; - if (it.Text.EndsWith("Extension", StringComparison.Ordinal)) - text = text.Remove(it.Text.Length - "Extension".Length); - it.Text = text; - } - ); + foreach (XamlCodeCompletionItem it in neededItems) { + string text = it.Text; + if (it.Text.EndsWith("Extension", StringComparison.Ordinal)) + text = text.Remove(it.Text.Length - "Extension".Length); + it.Text = text; + } return neededItems.Cast().Add(new XamlCompletionItem(Utils.GetXamlNamespacePrefix(context), XamlNamespace, "Reference")); } @@ -556,7 +551,7 @@ namespace ICSharpCode.XamlBinding } } - list.Items.AddRange(type.GetProperties().Where(p => p.CanSet && p.IsPublic).Select(p => new XamlCodeCompletionItem(p.Name + "=", p)).Cast()); + list.Items.AddRange(type.GetProperties().Where(p => p.CanSet && p.IsPublic).Select(p => new XamlCodeCompletionItem(p.Name + "=", p))); } /// returns true if elements from named args completion should be added afterwards. @@ -1032,8 +1027,7 @@ namespace ICSharpCode.XamlBinding var items = GetClassesFromContext(context); foreach (var ns in items) { list.Items.AddRange(ns.Value.Where(c => c.Fields.Any(f => f.IsStatic) || c.Properties.Any(p => p.IsStatic)) - .Select(c => new XamlCodeCompletionItem(c, ns.Key)) - .Cast()); + .Select(c => new XamlCodeCompletionItem(c, ns.Key))); } if (selItem != null && selItem.IsString) { list.PreselectionLength = selItem.StringValue.Length; @@ -1240,7 +1234,6 @@ namespace ICSharpCode.XamlBinding return new XamlCodeCompletionItem(name.Remove(0, prefixLength), new DefaultProperty(c, property) { ReturnType = GetAttachedPropertyType(item, c) }); } ) - .Cast() ); } @@ -1267,7 +1260,6 @@ namespace ICSharpCode.XamlBinding return new XamlCodeCompletionItem(name.Remove(0, prefixLength), new DefaultEvent(c, @event) { ReturnType = GetAttachedEventDelegateType(item, c) }); } ) - .Cast() ); } diff --git a/src/AddIns/BackendBindings/XamlBinding/XamlBinding/XamlCodeCompletionBinding.cs b/src/AddIns/BackendBindings/XamlBinding/XamlBinding/XamlCodeCompletionBinding.cs index f994fe0b2f..79aedaf7e0 100644 --- a/src/AddIns/BackendBindings/XamlBinding/XamlBinding/XamlCodeCompletionBinding.cs +++ b/src/AddIns/BackendBindings/XamlBinding/XamlBinding/XamlCodeCompletionBinding.cs @@ -338,7 +338,6 @@ namespace ICSharpCode.XamlBinding typeName.GetProperties() .Where(p => p.IsPublic && p.CanSet) .Select(prop => new XamlCodeCompletionItem(prop)) - .Cast() ); break; case "Event": @@ -346,7 +345,6 @@ namespace ICSharpCode.XamlBinding typeName.GetEvents() .Where(e => e.IsPublic) .Select(evt => new XamlCodeCompletionItem(evt)) - .Cast() ); break; case "Handler": diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs index 26f8d48c46..8cea26356b 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs @@ -60,7 +60,7 @@ namespace ICSharpCode.AvalonEdit.AddIn } public IEnumerable TextMarkers { - get { return markers.UpCast(); } + get { return markers; } } public void RemoveAll(Predicate predicate) @@ -69,14 +69,19 @@ namespace ICSharpCode.AvalonEdit.AddIn throw new ArgumentNullException("predicate"); foreach (TextMarker m in markers.ToArray()) { if (predicate(m)) - m.Delete(); + Remove(m); } } - internal void Remove(TextMarker marker) + public void Remove(ITextMarker marker) { - markers.Remove(marker); - Redraw(marker); + if (marker == null) + throw new ArgumentNullException("marker"); + TextMarker m = marker as TextMarker; + if (markers.Remove(m)) { + Redraw(m); + m.OnDeleted(); + } } /// @@ -173,11 +178,13 @@ namespace ICSharpCode.AvalonEdit.AddIn public void Delete() { - if (this.IsConnectedToCollection) { - service.Remove(this); - if (Deleted != null) - Deleted(this, EventArgs.Empty); - } + service.Remove(this); + } + + internal void OnDeleted() + { + if (Deleted != null) + Deleted(this, EventArgs.Empty); } void Redraw() diff --git a/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/TreeModel/AbstractNode.cs b/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/TreeModel/AbstractNode.cs index e704d421ee..58329d464a 100644 --- a/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/TreeModel/AbstractNode.cs +++ b/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/TreeModel/AbstractNode.cs @@ -42,7 +42,7 @@ namespace Debugger.AddIn.Visualizers.Graph /// public IEnumerable FlattenPropertyNodes() { - return Utils.TreeFlattener.Flatten(this).Where((node) => { return node is PropertyNode; }).Cast(); + return Utils.TreeFlattener.Flatten(this).OfType(); } } } diff --git a/src/AddIns/Misc/SearchAndReplace/Project/Gui/SearchRootNode.cs b/src/AddIns/Misc/SearchAndReplace/Project/Gui/SearchRootNode.cs index 4ac40cab9e..0962939529 100644 --- a/src/AddIns/Misc/SearchAndReplace/Project/Gui/SearchRootNode.cs +++ b/src/AddIns/Misc/SearchAndReplace/Project/Gui/SearchRootNode.cs @@ -46,7 +46,7 @@ namespace SearchAndReplace this.results = results.Select(r => new SearchResultNode(r)).ToArray(); fileCount = results.GroupBy(r => r.FileName, new FileNameComparer()).Count(); - this.Children = this.results.UpCast(); + this.Children = this.results; this.IsExpanded = true; } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/ChangeTrackingTest.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/ChangeTrackingTest.cs new file mode 100644 index 0000000000..e1de2ce408 --- /dev/null +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Document/ChangeTrackingTest.cs @@ -0,0 +1,68 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Linq; +using NUnit.Framework; + +namespace ICSharpCode.AvalonEdit.Document.Tests +{ + [TestFixture] + public class ChangeTrackingTest + { + [Test] + public void NoChanges() + { + TextDocument document = new TextDocument("initial text"); + ChangeTrackingCheckpoint checkpoint1, checkpoint2; + ITextSource snapshot1 = document.CreateSnapshot(out checkpoint1); + ITextSource snapshot2 = document.CreateSnapshot(out checkpoint2); + Assert.AreEqual(0, checkpoint1.CompareAge(checkpoint2)); + Assert.AreEqual(0, checkpoint1.GetChangesTo(checkpoint2).Count()); + Assert.AreEqual(document.Text, snapshot1.Text); + Assert.AreEqual(document.Text, snapshot2.Text); + } + + [Test] + public void ForwardChanges() + { + TextDocument document = new TextDocument("initial text"); + ChangeTrackingCheckpoint checkpoint1, checkpoint2; + ITextSource snapshot1 = document.CreateSnapshot(out checkpoint1); + document.Replace(0, 7, "nw"); + document.Insert(1, "e"); + ITextSource snapshot2 = document.CreateSnapshot(out checkpoint2); + Assert.AreEqual(-1, checkpoint1.CompareAge(checkpoint2)); + DocumentChangeEventArgs[] arr = checkpoint1.GetChangesTo(checkpoint2).ToArray(); + Assert.AreEqual(2, arr.Length); + Assert.AreEqual("nw", arr[0].InsertedText); + Assert.AreEqual("e", arr[1].InsertedText); + + Assert.AreEqual("initial text", snapshot1.Text); + Assert.AreEqual("new text", snapshot2.Text); + } + + [Test] + public void BackwardChanges() + { + TextDocument document = new TextDocument("initial text"); + ChangeTrackingCheckpoint checkpoint1, checkpoint2; + ITextSource snapshot1 = document.CreateSnapshot(out checkpoint1); + document.Replace(0, 7, "nw"); + document.Insert(1, "e"); + ITextSource snapshot2 = document.CreateSnapshot(out checkpoint2); + Assert.AreEqual(1, checkpoint2.CompareAge(checkpoint1)); + DocumentChangeEventArgs[] arr = checkpoint2.GetChangesTo(checkpoint1).ToArray(); + Assert.AreEqual(2, arr.Length); + Assert.AreEqual("", arr[0].InsertedText); + Assert.AreEqual("initial", arr[1].InsertedText); + + Assert.AreEqual("initial text", snapshot1.Text); + Assert.AreEqual("new text", snapshot2.Text); + } + } +} diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj index b8989b2c59..804512b5ec 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj @@ -65,6 +65,7 @@ + diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ChangeTrackingCheckpoint.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ChangeTrackingCheckpoint.cs new file mode 100644 index 0000000000..d835b8fd86 --- /dev/null +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ChangeTrackingCheckpoint.cs @@ -0,0 +1,94 @@ +// +// +// +// +// $Revision$ +// + +using ICSharpCode.AvalonEdit.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace ICSharpCode.AvalonEdit.Document +{ + /// + /// A checkpoint that allows tracking changes to a TextDocument. + /// + /// Use to create a checkpoint. + /// + public sealed class ChangeTrackingCheckpoint + { + readonly TextDocument document; + // 'value' is the change from the previous checkpoint to this checkpoint + readonly DocumentChangeEventArgs value; + readonly int id; + ChangeTrackingCheckpoint next; + + internal ChangeTrackingCheckpoint(TextDocument document) + { + this.document = document; + } + + internal ChangeTrackingCheckpoint(TextDocument document, DocumentChangeEventArgs value, int id) + { + this.document = document; + this.value = value; + this.id = id; + } + + internal ChangeTrackingCheckpoint Append(DocumentChangeEventArgs change) + { + Debug.Assert(this.next == null); + this.next = new ChangeTrackingCheckpoint(this.document, change, unchecked( this.id + 1 )); + return this.next; + } + + /// + /// Compares the age of this checkpoint to the other checkpoint. + /// + /// This method is thread-safe. + /// Raised if 'other' belongs to a different document than this checkpoint. + /// -1 if this checkpoint is older than . + /// 0 if this==. + /// 1 if this checkpoint is newer than . + public int CompareAge(ChangeTrackingCheckpoint other) + { + if (other == null) + throw new ArgumentNullException("other"); + if (other.document != this.document) + throw new ArgumentException("Checkpoints do not belong to the same document."); + // We will allow overflows, but assume that the maximum distance between checkpoints is 2^31-1. + // This is guaranteed on x86 because so many checkpoints don't fit into memory. + return Math.Sign(unchecked( this.id - other.id )); + } + + /// + /// Gets the changes from this checkpoint to the other checkpoint. + /// If 'other' is older than this checkpoint, reverse changes are calculated. + /// + /// This method is thread-safe. + /// Raised if 'other' belongs to a different document than this checkpoint. + public IEnumerable GetChangesTo(ChangeTrackingCheckpoint other) + { + int result = CompareAge(other); + if (result < 0) + return GetForwardChanges(other); + else if (result > 0) + return other.GetForwardChanges(this).Reverse().Select(change => change.Invert()); + else + return Empty.Array; + } + + IEnumerable GetForwardChanges(ChangeTrackingCheckpoint other) + { + // Return changes from this(exclusive) to other(inclusive). + ChangeTrackingCheckpoint node = this; + do { + node = node.next; + yield return node.value; + } while (node != other); + } + } +} diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeEventArgs.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeEventArgs.cs index 9ca68c6f85..1f7206164f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeEventArgs.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeEventArgs.cs @@ -22,10 +22,17 @@ namespace ICSharpCode.AvalonEdit.Document /// public int Offset { get; private set; } + /// + /// The text that was inserted. + /// + public string RemovedText { get; private set; } + /// /// The number of characters removed. /// - public int RemovalLength { get; private set; } + public int RemovalLength { + get { return RemovedText.Length; } + } /// /// The text that was inserted. @@ -85,31 +92,44 @@ namespace ICSharpCode.AvalonEdit.Document /// /// Creates a new DocumentChangeEventArgs object. /// - public DocumentChangeEventArgs(int offset, int removalLength, string insertedText) - : this(offset, removalLength, insertedText, null) + public DocumentChangeEventArgs(int offset, string removedText, string insertedText) + : this(offset, removedText, insertedText, null) { } /// /// Creates a new DocumentChangeEventArgs object. /// - public DocumentChangeEventArgs(int offset, int removalLength, string insertedText, OffsetChangeMap offsetChangeMap) + public DocumentChangeEventArgs(int offset, string removedText, string insertedText, OffsetChangeMap offsetChangeMap) { ThrowUtil.CheckNotNegative(offset, "offset"); - ThrowUtil.CheckNotNegative(removalLength, "removalLength"); + ThrowUtil.CheckNotNull(removedText, "removedText"); ThrowUtil.CheckNotNull(insertedText, "insertedText"); this.Offset = offset; - this.RemovalLength = removalLength; + this.RemovedText = removedText; this.InsertedText = insertedText; if (offsetChangeMap != null) { if (!offsetChangeMap.IsFrozen) throw new ArgumentException("The OffsetChangeMap must be frozen before it can be used in DocumentChangeEventArgs"); - if (!offsetChangeMap.IsValidForDocumentChange(offset, removalLength, insertedText.Length)) + if (!offsetChangeMap.IsValidForDocumentChange(offset, removedText.Length, insertedText.Length)) throw new ArgumentException("OffsetChangeMap is not valid for this document change", "offsetChangeMap"); this.offsetChangeMap = offsetChangeMap; } } + + /// + /// Creates DocumentChangeEventArgs for the reverse change. + /// + public DocumentChangeEventArgs Invert() + { + OffsetChangeMap map = this.OffsetChangeMapOrNull; + if (map != null) { + map = map.Invert(); + map.Freeze(); + } + return new DocumentChangeEventArgs(this.Offset, this.InsertedText, this.RemovedText, map); + } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeOperation.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeOperation.cs index 65544c1b13..3ac9dceacc 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeOperation.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeOperation.cs @@ -15,28 +15,23 @@ namespace ICSharpCode.AvalonEdit.Document sealed class DocumentChangeOperation : IUndoableOperation { TextDocument document; - int offset; - string removedText; - string insertedText; - OffsetChangeMap offsetChangeMap; + DocumentChangeEventArgs change; - public DocumentChangeOperation(TextDocument document, int offset, string removedText, string insertedText, OffsetChangeMap offsetChangeMap) + public DocumentChangeOperation(TextDocument document, DocumentChangeEventArgs change) { this.document = document; - this.offset = offset; - this.removedText = removedText; - this.insertedText = insertedText; - this.offsetChangeMap = offsetChangeMap; + this.change = change; } public void Undo() { - document.Replace(offset, insertedText.Length, removedText, offsetChangeMap != null ? offsetChangeMap.Invert() : null); + OffsetChangeMap map = change.OffsetChangeMapOrNull; + document.Replace(change.Offset, change.InsertionLength, change.RemovedText, map != null ? map.Invert() : null); } public void Redo() { - document.Replace(offset, removedText.Length, insertedText, offsetChangeMap); + document.Replace(change.Offset, change.RemovalLength, change.InsertedText, change.OffsetChangeMapOrNull); } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs index fb0d649adf..71b4ca1594 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs @@ -64,6 +64,7 @@ namespace ICSharpCode.AvalonEdit.Document readonly DocumentLineTree lineTree; readonly LineManager lineManager; readonly TextAnchorTree anchorTree; + ChangeTrackingCheckpoint currentCheckpoint; /// /// Create an empty text document. @@ -191,7 +192,6 @@ namespace ICSharpCode.AvalonEdit.Document /// /// Creates a snapshot of the current text. /// - /// Returns a copy of the document's text as a rope. /// /// Unlike all other TextDocument methods, this method may be called from any thread; even when the owning thread /// is concurrently writing to the document. @@ -205,6 +205,26 @@ namespace ICSharpCode.AvalonEdit.Document } } + /// + /// Creates a snapshot of the current text. + /// Additionally, creates a checkpoint that allows tracking document changes. + /// + /// + /// Unlike all other TextDocument methods, this method may be called from any thread; even when the owning thread + /// is concurrently writing to the document. + /// This special thread-safety guarantee is valid only for TextDocument.CreateSnapshot(), not necessarily for other + /// classes implementing ITextSource.CreateSnapshot(). + /// + public ITextSource CreateSnapshot(out ChangeTrackingCheckpoint checkpoint) + { + lock (rope) { + if (currentCheckpoint == null) + currentCheckpoint = new ChangeTrackingCheckpoint(this); + checkpoint = currentCheckpoint; + return new RopeTextSource(rope.Clone()); + } + } + /// /// Creates a snapshot of a part of the current text. /// @@ -473,18 +493,19 @@ namespace ICSharpCode.AvalonEdit.Document } } - void DoReplace(int offset, int length, string text, OffsetChangeMap offsetChangeMap) + void DoReplace(int offset, int length, string newText, OffsetChangeMap offsetChangeMap) { - if (length == 0 && text.Length == 0) + if (length == 0 && newText.Length == 0) return; // trying to replace a single character in 'Normal' mode? // for single characters, 'CharacterReplace' mode is equivalent, but more performant // (we don't have to touch the anchorTree at all in 'CharacterReplace' mode) - if (length == 1 && text.Length == 1 && offsetChangeMap == null) + if (length == 1 && newText.Length == 1 && offsetChangeMap == null) offsetChangeMap = OffsetChangeMap.Empty; - DocumentChangeEventArgs args = new DocumentChangeEventArgs(offset, length, text, offsetChangeMap); + string removedText = rope.ToString(offset, length); + DocumentChangeEventArgs args = new DocumentChangeEventArgs(offset, removedText, newText, offsetChangeMap); // fire DocumentChanging event if (Changing != null) @@ -495,11 +516,16 @@ namespace ICSharpCode.AvalonEdit.Document DelayedEvents delayedEvents = new DelayedEvents(); lock (rope) { + // create linked list of checkpoints, if required + if (currentCheckpoint != null) { + currentCheckpoint = currentCheckpoint.Append(args); + } + // now update the textBuffer and lineTree if (offset == 0 && length == rope.Length) { // optimize replacing the whole document rope.Clear(); - rope.InsertText(0, text); + rope.InsertText(0, newText); lineManager.Rebuild(); } else { rope.RemoveRange(offset, length); @@ -507,8 +533,8 @@ namespace ICSharpCode.AvalonEdit.Document #if DEBUG lineTree.CheckProperties(); #endif - rope.InsertText(offset, text); - lineManager.Insert(offset, text); + rope.InsertText(offset, newText); + lineManager.Insert(offset, newText); #if DEBUG lineTree.CheckProperties(); #endif diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs index 7f7ab6c724..5e0e5b9f49 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs @@ -35,6 +35,12 @@ namespace ICSharpCode.AvalonEdit.Document /// If a document change causes a segment to be deleted completely, it will be reduced to length 0, but segments are /// never automatically removed. /// + /// + /// Thread-safety: a TextSegmentCollection that is connected to a TextDocument may only be used on that document's owner thread. + /// A disconnected TextSegmentCollection is safe for concurrent reads, but concurrent access is not safe when there are writes. + /// Keep in mind that reading the Offset properties of a inside the collection is a read access on the + /// collection; and setting an Offset property of a is a write access on the collection. + /// public sealed class TextSegmentCollection : ICollection, ISegmentTree, IWeakEventListener where T : TextSegment { // Implementation: this is basically a mixture of an augmented interval tree diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs index ebeab0d8df..8752b366e1 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs @@ -271,12 +271,7 @@ namespace ICSharpCode.AvalonEdit.Document void document_Changing(object sender, DocumentChangeEventArgs e) { TextDocument document = (TextDocument)sender; - Push(new DocumentChangeOperation( - document, - e.Offset, - document.GetText(e.Offset, e.RemovalLength), - e.InsertedText, - e.OffsetChangeMapOrNull)); + Push(new DocumentChangeOperation(document, e)); } /// diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj index 013fb9b5e8..366c696e17 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj @@ -89,6 +89,7 @@ + UndoStack.cs diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CallbackOnDispose.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CallbackOnDispose.cs index 7af619826d..a8e39bce14 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CallbackOnDispose.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CallbackOnDispose.cs @@ -16,7 +16,7 @@ namespace ICSharpCode.AvalonEdit.Utils /// /// /// This class ensures the callback is invoked at most once, - /// even when Dispose is called on multiple theadeds. + /// even when Dispose is called on multiple threads. /// sealed class CallbackOnDispose : IDisposable { diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CharRope.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CharRope.cs index b224adb388..e0eda542c6 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CharRope.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CharRope.cs @@ -38,6 +38,8 @@ namespace ICSharpCode.AvalonEdit.Utils { if (rope == null) throw new ArgumentNullException("rope"); + if (length == 0) + return string.Empty; char[] buffer = new char[length]; rope.CopyTo(startIndex, buffer, 0, length); return new string(buffer); diff --git a/src/Main/Base/Project/Src/Editor/ITextMarker.cs b/src/Main/Base/Project/Src/Editor/ITextMarker.cs index 751fc45554..7671cc4b47 100644 --- a/src/Main/Base/Project/Src/Editor/ITextMarker.cs +++ b/src/Main/Base/Project/Src/Editor/ITextMarker.cs @@ -75,6 +75,11 @@ namespace ICSharpCode.SharpDevelop.Editor /// IEnumerable TextMarkers { get; } + /// + /// Removes the specified text marker. + /// + void Remove(ITextMarker marker); + /// /// Removes all text markers that match the condition. /// diff --git a/src/Main/Base/Project/Src/Util/ExtensionMethods.cs b/src/Main/Base/Project/Src/Util/ExtensionMethods.cs index e0dc058a64..c916eb4243 100644 --- a/src/Main/Base/Project/Src/Util/ExtensionMethods.cs +++ b/src/Main/Base/Project/Src/Util/ExtensionMethods.cs @@ -76,16 +76,6 @@ namespace ICSharpCode.SharpDevelop } } - /// - /// Casts the elements in input to B. - /// This method can be removed in .NET 4.0 - it's no longer necessary with covariance. - /// - public static IEnumerable UpCast(this IEnumerable input) where A : B - { - foreach (A o in input) - yield return o; - } - /// /// Adds all to . /// diff --git a/src/Main/Base/Test/Utils/MockTextMarkerService.cs b/src/Main/Base/Test/Utils/MockTextMarkerService.cs index 651e948ece..19b7cd964b 100644 --- a/src/Main/Base/Test/Utils/MockTextMarkerService.cs +++ b/src/Main/Base/Test/Utils/MockTextMarkerService.cs @@ -47,6 +47,11 @@ namespace ICSharpCode.SharpDevelop.Tests.Utils return m; } + public void Remove(ITextMarker marker) + { + marker.Delete(); + } + public void RemoveAll(Predicate predicate) { foreach (ITextMarker m in markers.ToArray()) { diff --git a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/MemberLookupHelper.cs b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/MemberLookupHelper.cs index ac08badd5d..ea43411dba 100644 --- a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/MemberLookupHelper.cs +++ b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/MemberLookupHelper.cs @@ -250,7 +250,7 @@ namespace ICSharpCode.SharpDevelop.Dom if (methods.Count == 0) return null; return (IMethod)CSharp.OverloadResolution.FindOverload( - methods.Cast().ToList(), + methods, arguments, false, true, @@ -263,7 +263,7 @@ namespace ICSharpCode.SharpDevelop.Dom return null; bool acceptableMatch; return (IProperty)CSharp.OverloadResolution.FindOverload( - properties.Cast().ToList(), + properties, arguments, false, false,