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) {