diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs
index ac4ff7ab59..8e33449270 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs
@@ -39,6 +39,60 @@ namespace ICSharpCode.AvalonEdit.Document
object lastGroupDescriptor;
bool allowContinue;
+ #region IsOriginalFile implementation
+ // implements feature request SD2-784 - File still considered dirty after undoing all changes
+
+ ///
+ /// Number of times undo must be executed until the original state is reached.
+ /// Negative: number of times redo must be executed until the original state is reached.
+ /// Special case: int.MinValue == original state is unreachable
+ ///
+ int elementsOnUndoUntilOriginalFile;
+
+ ///
+ /// Gets whether the document is currently in its original state (no modifications).
+ ///
+ public bool IsOriginalFile {
+ get { return elementsOnUndoUntilOriginalFile == 0; }
+ }
+
+ ///
+ /// Marks the current state as original. Discards any previous "original" markers.
+ ///
+ public void MarkAsOriginalFile()
+ {
+ bool oldIsOriginalFile = IsOriginalFile;
+ elementsOnUndoUntilOriginalFile = 0;
+ if (!oldIsOriginalFile)
+ NotifyPropertyChanged("IsOriginalFile");
+ }
+
+ ///
+ /// Discards the current "original" marker.
+ ///
+ public void DiscardOriginalFileMarker()
+ {
+ bool oldIsOriginalFile = IsOriginalFile;
+ elementsOnUndoUntilOriginalFile = int.MinValue;
+ if (oldIsOriginalFile)
+ NotifyPropertyChanged("IsOriginalFile");
+ }
+
+ void FileModified(int newElementsOnUndoStack)
+ {
+ if (newElementsOnUndoStack == 0 || elementsOnUndoUntilOriginalFile == int.MinValue)
+ return;
+
+ bool oldIsOriginalFile = IsOriginalFile;
+ elementsOnUndoUntilOriginalFile += newElementsOnUndoStack;
+ if (elementsOnUndoUntilOriginalFile > undostack.Count)
+ elementsOnUndoUntilOriginalFile = int.MinValue;
+
+ if (oldIsOriginalFile != IsOriginalFile)
+ NotifyPropertyChanged("IsOriginalFile");
+ }
+ #endregion
+
///
/// Gets if the undo stack currently accepts changes.
/// Is false while an undo action is running.
@@ -166,9 +220,11 @@ namespace ICSharpCode.AvalonEdit.Document
} else if (actionCountInUndoGroup > 1) {
// combine all actions within the group into a single grouped action
undostack.PushBack(new UndoOperationGroup(undostack, actionCountInUndoGroup));
+ actionCountInUndoGroup = 1; // actions were combined
}
EnforceSizeLimit();
allowContinue = true;
+ FileModified(actionCountInUndoGroup);
}
}
@@ -204,6 +260,7 @@ namespace ICSharpCode.AvalonEdit.Document
} finally {
state = StateListen;
}
+ FileModified(-1);
if (undostack.Count == 0)
NotifyPropertyChanged("CanUndo");
if (redostack.Count == 1)
@@ -236,6 +293,7 @@ namespace ICSharpCode.AvalonEdit.Document
} finally {
state = StateListen;
}
+ FileModified(1);
if (redostack.Count == 0)
NotifyPropertyChanged("CanRedo");
if (undostack.Count == 1)
@@ -304,6 +362,9 @@ namespace ICSharpCode.AvalonEdit.Document
if (redostack.Count != 0) {
redostack.Clear();
NotifyPropertyChanged("CanRedo");
+ // if the "orginal file" marker is on the redo stack: remove it
+ if (elementsOnUndoUntilOriginalFile < 0)
+ elementsOnUndoUntilOriginalFile = int.MinValue;
}
}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditor.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditor.cs
index be969d56f4..4cf4930419 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditor.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditor.cs
@@ -121,10 +121,12 @@ namespace ICSharpCode.AvalonEdit
{
if (oldValue != null) {
TextDocumentWeakEventManager.TextChanged.RemoveListener(oldValue, this);
+ PropertyChangedEventManager.RemoveListener(oldValue.UndoStack, this, "IsOriginalFile");
}
textArea.Document = newValue;
if (newValue != null) {
TextDocumentWeakEventManager.TextChanged.AddListener(newValue, this);
+ PropertyChangedEventManager.AddListener(newValue.UndoStack, this, "IsOriginalFile");
}
OnDocumentChanged(EventArgs.Empty);
OnTextChanged(EventArgs.Empty);
@@ -187,6 +189,8 @@ namespace ICSharpCode.AvalonEdit
} else if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) {
OnTextChanged(e);
return true;
+ } else if (managerType == typeof(PropertyChangedEventManager)) {
+ return HandleIsOriginalChanged((PropertyChangedEventArgs)e);
}
return false;
}
@@ -208,10 +212,8 @@ namespace ICSharpCode.AvalonEdit
return document != null ? document.Text : string.Empty;
}
set {
- if (value == null)
- value = string.Empty;
TextDocument document = GetDocument();
- document.Text = value;
+ document.Text = value ?? string.Empty;
// after replacing the full text, the caret is positioned at the end of the document
// - reset it to the beginning.
this.CaretOffset = 0;
@@ -391,6 +393,53 @@ namespace ICSharpCode.AvalonEdit
}
#endregion
+ #region IsModified
+ ///
+ /// Dependency property for
+ ///
+ public static readonly DependencyProperty IsModifiedProperty =
+ DependencyProperty.Register("IsModified", typeof(bool), typeof(TextEditor),
+ new FrameworkPropertyMetadata(Boxes.False, OnIsModifiedChanged));
+
+ ///
+ /// Gets/Sets the 'modified' flag.
+ ///
+ public bool IsModified {
+ get { return (bool)GetValue(IsModifiedProperty); }
+ set { SetValue(IsModifiedProperty, value); }
+ }
+
+ static void OnIsModifiedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ TextEditor editor = d as TextEditor;
+ if (editor != null) {
+ TextDocument document = editor.Document;
+ if (document != null) {
+ UndoStack undoStack = document.UndoStack;
+ if ((bool)e.NewValue) {
+ if (undoStack.IsOriginalFile)
+ undoStack.DiscardOriginalFileMarker();
+ } else {
+ undoStack.MarkAsOriginalFile();
+ }
+ }
+ }
+ }
+
+ bool HandleIsOriginalChanged(PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "IsOriginalFile") {
+ TextDocument document = this.Document;
+ if (document != null) {
+ this.IsModified = !document.UndoStack.IsOriginalFile;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+ #endregion
+
#region ShowLineNumbers
///
/// IsReadOnly dependency property.
@@ -825,12 +874,16 @@ namespace ICSharpCode.AvalonEdit
///
/// Loads the text from the stream, auto-detecting the encoding.
///
+ ///
+ /// This method sets to false.
+ ///
public void Load(Stream stream)
{
- using (StreamReader reader = FileReader.OpenStream(stream, Encoding ?? Encoding.UTF8)) {
- Text = reader.ReadToEnd();
- Encoding = reader.CurrentEncoding; // assign encoding after ReadToEnd() so that the StreamReader can autodetect the encoding
+ using (StreamReader reader = FileReader.OpenStream(stream, this.Encoding ?? Encoding.UTF8)) {
+ this.Text = reader.ReadToEnd();
+ this.Encoding = reader.CurrentEncoding; // assign encoding after ReadToEnd() so that the StreamReader can autodetect the encoding
}
+ this.IsModified = false;
}
///
@@ -854,18 +907,22 @@ namespace ICSharpCode.AvalonEdit
///
/// Saves the text to the stream.
///
+ ///
+ /// This method sets to false.
+ ///
public void Save(Stream stream)
{
if (stream == null)
throw new ArgumentNullException("stream");
- StreamWriter writer = new StreamWriter(stream, Encoding ?? Encoding.UTF8);
- writer.Write(Text);
+ StreamWriter writer = new StreamWriter(stream, this.Encoding ?? Encoding.UTF8);
+ writer.Write(this.Text);
writer.Flush();
// do not close the stream
+ this.IsModified = false;
}
///
- /// Loads the text from the stream, auto-detecting the encoding.
+ /// Saves the text to the file.
///
public void Save(string fileName)
{