From 3839873b7ed5d89527dd4282e9bee609b3532657 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Thu, 7 Jan 2010 19:56:34 +0000 Subject: [PATCH] Implemented SizeLimit for UndoStack. This allows disabling the undo stack as requested in forum-10585. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5383 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Document/UndoOperationGroup.cs | 6 +- .../Document/UndoStack.cs | 55 ++++-- .../Editing/TextAreaInputHandler.cs | 2 +- .../Highlighting/HighlightedInlineBuilder.cs | 2 + .../ICSharpCode.AvalonEdit.csproj | 1 + .../Properties/CodeAnalysisDictionary.xml | 1 + .../ICSharpCode.AvalonEdit/Utils/Deque.cs | 177 ++++++++++++++++++ .../Utils/TextFormatterFactory.cs | 3 +- 8 files changed, 231 insertions(+), 16 deletions(-) create mode 100644 src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/Deque.cs diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoOperationGroup.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoOperationGroup.cs index 1a56ce6b56..07be7e8bc4 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoOperationGroup.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoOperationGroup.cs @@ -6,8 +6,8 @@ // using System; -using System.Collections.Generic; using System.Diagnostics; +using ICSharpCode.AvalonEdit.Utils; namespace ICSharpCode.AvalonEdit.Document { @@ -19,7 +19,7 @@ namespace ICSharpCode.AvalonEdit.Document { IUndoableOperation[] undolist; - public UndoOperationGroup(Stack stack, int numops) + public UndoOperationGroup(Deque stack, int numops) { if (stack == null) { throw new ArgumentNullException("stack"); @@ -31,7 +31,7 @@ namespace ICSharpCode.AvalonEdit.Document } undolist = new IUndoableOperation[numops]; for (int i = 0; i < numops; ++i) { - undolist[i] = stack.Pop(); + undolist[i] = stack.PopBack(); } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs index 8752b366e1..a7a153d4ff 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs @@ -6,8 +6,10 @@ // using System; -using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; + +using ICSharpCode.AvalonEdit.Utils; namespace ICSharpCode.AvalonEdit.Document { @@ -17,9 +19,10 @@ namespace ICSharpCode.AvalonEdit.Document [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] public sealed class UndoStack : INotifyPropertyChanged { - Stack undostack = new Stack(); - Stack redostack = new Stack(); + Deque undostack = new Deque(); + Deque redostack = new Deque(); + int sizeLimit = 1000; bool acceptChanges = true; /// @@ -46,6 +49,35 @@ namespace ICSharpCode.AvalonEdit.Document get { return redostack.Count > 0; } } + /// + /// Gets/Sets the limit on the number of items on the undo stack. + /// The default value is 1000. + /// + /// The size limit is enforced only on the number of stored top-level undo groups. + /// Elements within undo groups do not count towards the size limit. + public int SizeLimit { + get { return sizeLimit; } + set { + if (value < 0) + ThrowUtil.CheckNotNegative(value, "value"); + if (sizeLimit != value) { + sizeLimit = value; + NotifyPropertyChanged("SizeLimit"); + if (undoGroupDepth == 0) + EnforceSizeLimit(); + } + } + } + + void EnforceSizeLimit() + { + Debug.Assert(undoGroupDepth == 0); + while (undostack.Count > sizeLimit) + undostack.PopFront(); + while (redostack.Count > sizeLimit) + redostack.PopFront(); + } + int undoGroupDepth; int actionCountInUndoGroup; int optionalActionCount; @@ -121,11 +153,12 @@ namespace ICSharpCode.AvalonEdit.Document if (actionCountInUndoGroup == optionalActionCount) { // only optional actions: don't store them for (int i = 0; i < optionalActionCount; i++) { - undostack.Pop(); + undostack.PopBack(); } } else if (actionCountInUndoGroup > 1) { - undostack.Push(new UndoOperationGroup(undostack, actionCountInUndoGroup)); + undostack.PushBack(new UndoOperationGroup(undostack, actionCountInUndoGroup)); } + EnforceSizeLimit(); } } @@ -149,8 +182,8 @@ namespace ICSharpCode.AvalonEdit.Document if (undostack.Count > 0) { lastGroupDescriptor = null; acceptChanges = false; - IUndoableOperation uedit = undostack.Pop(); - redostack.Push(uedit); + IUndoableOperation uedit = undostack.PopBack(); + redostack.PushBack(uedit); uedit.Undo(); acceptChanges = true; if (undostack.Count == 0) @@ -169,8 +202,8 @@ namespace ICSharpCode.AvalonEdit.Document if (redostack.Count > 0) { lastGroupDescriptor = null; acceptChanges = false; - IUndoableOperation uedit = redostack.Pop(); - undostack.Push(uedit); + IUndoableOperation uedit = redostack.PopBack(); + undostack.PushBack(uedit); uedit.Redo(); acceptChanges = true; if (redostack.Count == 0) @@ -209,11 +242,11 @@ namespace ICSharpCode.AvalonEdit.Document throw new ArgumentNullException("operation"); } - if (acceptChanges) { + if (acceptChanges && sizeLimit > 0) { bool wasEmpty = undostack.Count == 0; StartUndoGroup(); - undostack.Push(operation); + undostack.PushBack(operation); actionCountInUndoGroup++; if (isOptional) optionalActionCount++; diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextAreaInputHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextAreaInputHandler.cs index f93d53906b..bb8c548186 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextAreaInputHandler.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextAreaInputHandler.cs @@ -65,7 +65,7 @@ namespace ICSharpCode.AvalonEdit.Editing /// /// Creates a new TextAreaInputHandler. /// - public TextAreaStackedInputHandler(TextArea textArea) + protected TextAreaStackedInputHandler(TextArea textArea) { if (textArea == null) throw new ArgumentNullException("textArea"); diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedInlineBuilder.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedInlineBuilder.cs index 13e312b883..d73d8dfbad 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedInlineBuilder.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedInlineBuilder.cs @@ -88,6 +88,8 @@ namespace ICSharpCode.AvalonEdit.Highlighting /// public void SetHighlighting(int offset, int length, HighlightingColor color) { + if (color == null) + throw new ArgumentNullException("color"); int startIndex = GetIndexForOffset(offset); int endIndex = GetIndexForOffset(offset + length); for (int i = startIndex; i < endIndex; i++) { diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj index d9fed716b9..9b4af13f7a 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj @@ -319,6 +319,7 @@ + diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Properties/CodeAnalysisDictionary.xml b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Properties/CodeAnalysisDictionary.xml index bda996ea2e..71301b91d6 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Properties/CodeAnalysisDictionary.xml +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Properties/CodeAnalysisDictionary.xml @@ -7,6 +7,7 @@ Foldings Xshd Utils + Deque Colorizer Renderer Renderers diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/Deque.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/Deque.cs new file mode 100644 index 0000000000..e249378d2e --- /dev/null +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/Deque.cs @@ -0,0 +1,177 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ICSharpCode.AvalonEdit.Utils +{ + /// + /// Double-ended queue. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + public class Deque : ICollection + { + T[] arr = Empty.Array; + int size, head, tail; + + /// + public int Count { + get { return size; } + } + + /// + public void Clear() + { + arr = Empty.Array; + size = 0; + head = 0; + tail = 0; + } + + /// + /// Gets/Sets an element inside the deque. + /// + public T this[int index] { + get { + ThrowUtil.CheckInRangeInclusive(index, "index", 0, size - 1); + return arr[(head + index) % arr.Length]; + } + set { + ThrowUtil.CheckInRangeInclusive(index, "index", 0, size - 1); + arr[(head + index) % arr.Length] = value; + } + } + + /// + /// Adds an element to the end of the deque. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "PushBack")] + public void PushBack(T item) + { + if (size == arr.Length) + SetCapacity(Math.Max(4, arr.Length * 2)); + arr[tail++] = item; + if (tail == arr.Length) tail = 0; + size++; + } + + /// + /// Pops an element from the end of the deque. + /// + public T PopBack() + { + if (size == 0) + throw new InvalidOperationException(); + if (tail == 0) + tail = arr.Length - 1; + else + tail--; + T val = arr[tail]; + arr[tail] = default(T); // allow GC to collect the element + size--; + return val; + } + + /// + /// Adds an element to the front of the deque. + /// + public void PushFront(T item) + { + if (size == arr.Length) + SetCapacity(Math.Max(4, arr.Length * 2)); + if (head == 0) + head = arr.Length - 1; + else + head--; + arr[head] = item; + size++; + } + + /// + /// Pops an element from the end of the deque. + /// + public T PopFront() + { + if (size == 0) + throw new InvalidOperationException(); + T val = arr[head]; + arr[head] = default(T); // allow GC to collect the element + head++; + if (head == arr.Length) head = 0; + size--; + return val; + } + + void SetCapacity(int capacity) + { + T[] newArr = new T[capacity]; + CopyTo(newArr, 0); + head = 0; + tail = (size == capacity) ? 0 : size; + arr = newArr; + } + + /// + public IEnumerator GetEnumerator() + { + if (head < tail) { + for (int i = head; i < tail; i++) + yield return arr[i]; + } else { + for (int i = head; i < arr.Length; i++) + yield return arr[i]; + for (int i = 0; i < tail; i++) + yield return arr[i]; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + bool ICollection.IsReadOnly { + get { return false; } + } + + void ICollection.Add(T item) + { + PushBack(item); + } + + /// + public bool Contains(T item) + { + EqualityComparer comparer = EqualityComparer.Default; + foreach (T element in this) + if (comparer.Equals(item, element)) + return true; + return false; + } + + /// + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array"); + if (head < tail) { + Array.Copy(arr, head, array, arrayIndex, tail - head); + } else { + int num1 = arr.Length - head; + Array.Copy(arr, head, array, arrayIndex, num1); + Array.Copy(arr, 0, array, arrayIndex + num1, tail); + } + } + + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextFormatterFactory.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextFormatterFactory.cs index 3b02205b44..0ca329c737 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextFormatterFactory.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextFormatterFactory.cs @@ -45,7 +45,8 @@ namespace ICSharpCode.AvalonEdit.Utils "Create", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, - new object[] { formattingMode }); + new object[] { formattingMode }, + CultureInfo.InvariantCulture); } else { return TextFormatter.Create(); }