From 866f0d57aa55caea5fca4ee36d1d167eab2562d9 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 18 Jan 2014 18:34:41 +0100 Subject: [PATCH 01/13] New OpenedFile API based on file models. --- .../AvalonEdit.AddIn/Src/Utils.cs | 16 -- .../Project/ICSharpCode.SharpDevelop.csproj | 5 + src/Main/Base/Project/Services/IFileSystem.cs | 9 +- .../Project/Util/SharpDevelopExtensions.cs | 10 + .../Workbench/File/BinaryFileModelProvider.cs | 72 +++++++ .../Workbench/File/DocumentFileModelInfo.cs | 29 +++ .../Base/Project/Workbench/File/FileModels.cs | 38 ++++ .../Workbench/File/IFileModelProvider.cs | 76 ++++++++ .../Base/Project/Workbench/File/OpenedFile.cs | 181 +++++++++++++++--- .../File/TextDocumentFileModelProvider.cs | 89 +++++++++ .../Src/Services/FileUtility/PathName.cs | 9 + 11 files changed, 486 insertions(+), 48 deletions(-) create mode 100644 src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs create mode 100644 src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs create mode 100644 src/Main/Base/Project/Workbench/File/FileModels.cs create mode 100644 src/Main/Base/Project/Workbench/File/IFileModelProvider.cs create mode 100644 src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Utils.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Utils.cs index c0b81093c77..32e597f899e 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Utils.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Utils.cs @@ -28,22 +28,6 @@ namespace ICSharpCode.AvalonEdit.AddIn { public static class Utils { - public static OffsetChangeMap ToOffsetChangeMap(this IEnumerable edits) - { - var map = new OffsetChangeMap(); - int diff = 0; - foreach (var edit in edits) { - Debug.Assert(edit.EditType != ChangeType.None && edit.EditType != ChangeType.Unsaved); - int offset = edit.BeginA + diff; - int removalLength = edit.EndA - edit.BeginA; - int insertionLength = edit.EndB - edit.BeginB; - - diff += (insertionLength - removalLength); - map.Add(new OffsetChangeMapEntry(offset, removalLength, insertionLength)); - } - return map; - } - /// /// Copies editor options and default element customizations. /// Does not copy the syntax highlighting. diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index b787a60c6d3..9fa03747e65 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -361,8 +361,13 @@ + + + + + diff --git a/src/Main/Base/Project/Services/IFileSystem.cs b/src/Main/Base/Project/Services/IFileSystem.cs index 49cf5e794ba..3fbf71e7158 100644 --- a/src/Main/Base/Project/Services/IFileSystem.cs +++ b/src/Main/Base/Project/Services/IFileSystem.cs @@ -34,10 +34,10 @@ public interface IFileSystem : IReadOnlyFileSystem /// void Delete(FileName path); - /// + /// void CopyFile(FileName source, FileName destination, bool overwrite = false); - /// + /// void CreateDirectory(DirectoryName path); /// @@ -69,10 +69,13 @@ public interface IReadOnlyFileSystem /// Options that influence the search. /// /// An enumerable that iterates through the directory contents - /// The directory does not exist / access is denied. + /// The directory does not exist / access is denied. IEnumerable GetFiles(DirectoryName directory, string searchPattern = "*", DirectorySearchOptions searchOptions = DirectorySearchOptions.None); } + /// + /// Options for SD.FileSystem.GetFiles(). + /// [Flags] public enum DirectorySearchOptions { diff --git a/src/Main/Base/Project/Util/SharpDevelopExtensions.cs b/src/Main/Base/Project/Util/SharpDevelopExtensions.cs index 316b857e4b1..2350c0a8baa 100644 --- a/src/Main/Base/Project/Util/SharpDevelopExtensions.cs +++ b/src/Main/Base/Project/Util/SharpDevelopExtensions.cs @@ -28,6 +28,7 @@ using System.Windows.Media; using System.Xml; using System.Xml.Linq; +using ICSharpCode.AvalonEdit.Document; using ICSharpCode.Core; using ICSharpCode.Core.Presentation; using ICSharpCode.NRefactory; @@ -36,6 +37,7 @@ using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Project; +using ICSharpCode.SharpDevelop.Workbench; namespace ICSharpCode.SharpDevelop { @@ -1028,6 +1030,14 @@ public static string GetText(this IDocument document, TextLocation startPos, Tex return document.GetText(startOffset, document.GetOffset(endPos) - startOffset); } + /// + /// Retrieves the DocumentFileModelInfo associated with this TextDocument. + /// + public static DocumentFileModelInfo GetFileModelInfo(this TextDocument document) + { + return document.GetService(); + } + /// /// Obsolete. Use GetOffset() instead. /// diff --git a/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs new file mode 100644 index 00000000000..137123fcb54 --- /dev/null +++ b/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs @@ -0,0 +1,72 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.IO; +using ICSharpCode.Core; + +namespace ICSharpCode.SharpDevelop.Workbench +{ + sealed class BinaryFileModelProvider : IFileModelProvider + { + sealed class OnDiskBinaryModel : IBinaryModel + { + internal readonly OpenedFile file; + + public OnDiskBinaryModel(OpenedFile file) + { + this.file = file; + } + + public Stream OpenRead() + { + return SD.FileSystem.OpenRead(file.FileName); + } + } + + public IBinaryModel Load(OpenedFile file) + { + return new OnDiskBinaryModel(file); + } + + public void Save(OpenedFile file, IBinaryModel model) + { + SaveCopyAs(file, model, file.FileName); + file.ReplaceModel(this, model, ReplaceModelMode.SetAsValid); // remove dirty flag + } + + public void SaveCopyAs(OpenedFile file, IBinaryModel model, FileName outputFileName) + { + var onDisk = model as OnDiskBinaryModel; + if (onDisk != null) { + // We can just copy the file (but avoid copying to itself) + if (onDisk.file.FileName != outputFileName) { + SD.FileSystem.CopyFile(onDisk.file.FileName, outputFileName); + } + } else { + using (var inputStream = model.OpenRead()) { + using (var outputStream = SD.FileSystem.OpenWrite(outputFileName)) { + inputStream.CopyTo(outputStream); + } + } + } + } + + public bool CanLoadFrom(IFileModelProvider otherProvider) where U : class + { + return false; + } + + public void NotifyRename(OpenedFile file, IBinaryModel model, FileName oldName, FileName newName) + { + } + + public void NotifyStale(OpenedFile file, IBinaryModel model) + { + } + + public void NotifyUnloaded(OpenedFile file, IBinaryModel model) + { + } + } +} diff --git a/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs b/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs new file mode 100644 index 00000000000..7d5964cb10c --- /dev/null +++ b/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs @@ -0,0 +1,29 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Text; + +namespace ICSharpCode.SharpDevelop.Workbench +{ + /// + /// Extra information about a TextDocument that was loaded from an OpenedFile. + /// + public class DocumentFileModelInfo + { + public DocumentFileModelInfo() + { + this.Encoding = SD.FileService.DefaultFileEncoding; + } + + /// + /// Gets whether the model is stale. + /// + public bool IsStale { get; internal set; } + + /// + /// Gets the encoding of the model. + /// + public Encoding Encoding { get; set; } + } +} diff --git a/src/Main/Base/Project/Workbench/File/FileModels.cs b/src/Main/Base/Project/Workbench/File/FileModels.cs new file mode 100644 index 00000000000..ce9dea8a31b --- /dev/null +++ b/src/Main/Base/Project/Workbench/File/FileModels.cs @@ -0,0 +1,38 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.IO; +using ICSharpCode.AvalonEdit.Document; + +namespace ICSharpCode.SharpDevelop.Workbench +{ + /// + /// Pre-defined file models. + /// + public static class FileModels + { + /// + /// The binary file model provider. + /// + public static readonly IFileModelProvider Binary = new BinaryFileModelProvider(); + + /// + /// The text document file model provider. + /// + public static readonly IFileModelProvider TextDocument = new TextDocumentFileModelProvider(); + } + + /// + /// Represents the binary contents of a file. + /// + public interface IBinaryModel + { + /// + /// Creates a stream that reads from the binary model. + /// + /// A readable and seekable stream. + /// Error opening the file. + Stream OpenRead(); + } +} diff --git a/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs new file mode 100644 index 00000000000..194875ec616 --- /dev/null +++ b/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs @@ -0,0 +1,76 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using ICSharpCode.Core; + +namespace ICSharpCode.SharpDevelop.Workbench +{ + /// + /// Interface that deals with loading and saving OpenedFile model instances. + /// Pre-defined model providers are available in the class. + /// Custom IFileModelProvider implementations may be used to add support for additional types of models. + /// + public interface IFileModelProvider where T : class + { + /// + /// Loads a file model from the OpenedFile. + /// Most implementations of this method will request a lower-level model (e.g. FileModels.Binary) from the opened file + /// and construct a higher-level representation. + /// + /// Error loading the file. + /// Cannot construct the model because the underyling data is in an invalid format. + T Load(OpenedFile file); + + /// + /// Saves a file model. + /// The file should be saved to a location where a subsequent Load() operation can load from. + /// Usually, this means saving to a lower-level model (e.g. a TextDocument model is saved by creating a binary model). + /// + /// After the Save() method completes successfully, the specified model may no longer be marked as dirty. For models saving to + /// lower-level models, this is done by marking that lower-level model as dirty. + /// In other cases, the provider may have to explicitly remove the dirty flag from the model. + /// + void Save(OpenedFile file, T model); + + /// + /// Saves a file model to disk. This method may by-pass lower-level models (such as FileModels.Binary) and directly write to disk. + /// Note that this method is supposed to save a copy of the file, without resetting the dirty flag (think "Save Copy As..."). + /// However, transferring the dirty flag to a lower-level model is allowed. + /// + /// Error saving the file. + void SaveCopyAs(OpenedFile file, T model, FileName outputFileName); + + /// + /// Gets whether this provider can load from the specified other provider. + /// It is important that CanLoadFrom() is transitive! + /// For a file model provider that loads from a text document model, the implementation should look like this: + /// + /// return otherProvider == FileModels.TextDocument || FileModels.TextDocument.CanLoadFrom(otherProvider); + /// + /// + bool CanLoadFrom(IFileModelProvider otherProvider) where U : class; + + /// + /// Notifies the provider that a file was renamed. + /// If the file name is already available as part of the model, the provider is responsible for updating the model. + /// + void NotifyRename(OpenedFile file, T model, FileName oldName, FileName newName); + + /// + /// Notifies the provider that a model was marked as stale. + /// + void NotifyStale(OpenedFile file, T model); + + /// + /// Notifies a model that it became unloaded. + /// This can happen due to: + /// + /// an explicit call + /// a call replacing this model with another + /// a call replacing a stale model with another + /// the OpenedFile getting disposed + /// + void NotifyUnloaded(OpenedFile file, T model); + } +} diff --git a/src/Main/Base/Project/Workbench/File/OpenedFile.cs b/src/Main/Base/Project/Workbench/File/OpenedFile.cs index 83e74192b67..56fc0435448 100644 --- a/src/Main/Base/Project/Workbench/File/OpenedFile.cs +++ b/src/Main/Base/Project/Workbench/File/OpenedFile.cs @@ -27,21 +27,49 @@ namespace ICSharpCode.SharpDevelop.Workbench { /// - /// Represents an opened file. + /// Options for use with /// - public abstract class OpenedFile : ICanBeDirty + [Flags] + public enum GetModelOptions { - protected IViewContent currentView; - bool inLoadOperation; - bool inSaveOperation; - + None = 0, /// - /// holds unsaved file content in memory when view containing the file was closed but no other view - /// activated + /// Return stale models without reloading. /// - byte[] fileData; - - #region IsDirty + AllowStale = 1, + /// + /// Do not load any models: + /// Returns null if the model is not already loaded, or if it is stale and the AllowStale option isn't in use. + /// + DoNotLoad = 2, + } + + /// + /// Option that control how handles the dirty flag. + /// + public enum ReplaceModelMode + { + /// + /// The new model is marked as dirty; and any other models are marked as stale. + /// + SetAsDirty, + /// + /// The new model is marked as valid; the status of any other models is unchanged. + /// + SetAsValid, + /// + /// The new model is marked as dirty, the previously dirty model is marked as stale, and any other models are unchanged. + /// This mode is intended for use in implementations. + /// + TransferDirty + } + + /// + /// Represents an opened file. + /// + public abstract class OpenedFile : ICanBeDirty + { + #region IsDirty implementation bool isDirty; public event EventHandler IsDirtyChanged; @@ -50,7 +78,7 @@ public abstract class OpenedFile : ICanBeDirty /// public bool IsDirty { get { return isDirty;} - set { + private set { if (isDirty != value) { isDirty = value; @@ -60,26 +88,16 @@ public bool IsDirty { } } } - - /// - /// Marks the file as dirty if it currently is not in a load operation. - /// - public virtual void MakeDirty() - { - if (!inLoadOperation) { - this.IsDirty = true; - } - } #endregion - bool isUntitled; - + #region FileName /// /// Gets if the file is untitled. Untitled files show a "Save as" dialog when they are saved. /// public bool IsUntitled { - get { return isUntitled; } - protected set { isUntitled = value; } + get { + return fileName.ToString().StartsWith("untitled:", StringComparison.Ordinal); + } } FileName fileName; @@ -96,6 +114,11 @@ public FileName FileName { } } + /// + /// Occurs when the file name has changed. + /// + public event EventHandler FileNameChanged; + protected virtual void ChangeFileName(FileName newValue) { SD.MainThread.VerifyAccess(); @@ -106,13 +129,112 @@ protected virtual void ChangeFileName(FileName newValue) FileNameChanged(this, EventArgs.Empty); } } + #endregion /// - /// Occurs when the file name has changed. + /// This method sets all models to 'stale', causing the file to be re-loaded from disk + /// on the next GetModel() call. /// - public event EventHandler FileNameChanged; + /// The file is untitled. + public void ReloadFromDisk() + { + if (IsUntitled) + throw new InvalidOperationException("Cannot reload an untitled file from disk."); + throw new NotImplementedException(); + } + + /// + /// Saves the file to disk. + /// + /// If the file is saved successfully, the dirty flag will be cleared (the dirty model becomes valid instead). + /// The file is untitled. + public void SaveToDisk() + { + if (IsUntitled) + throw new InvalidOperationException("Cannot reload an untitled file from disk."); + throw new NotImplementedException(); + } + + /// + /// Changes the file name, and saves the file to disk. + /// + /// If the file is saved successfully, the dirty flag will be cleared (the dirty model becomes valid instead). + public void SaveToDisk(FileName fileName) + { + this.FileName = fileName; + SaveToDisk(); + } + + /// + /// Saves a copy of the file to disk. Does not change the name of the OpenedFile to the specified file name, and does not reset the dirty flag. + /// + public void SaveCopyAs(FileName fileName) + { + throw new NotImplementedException(); + } + + /// + /// Retrieves a file model, loading it if necessary. + /// + /// The model provider for the desired model type. Built-in model providers can be found in the class. + /// Options that control how + /// The model instance, or possibly null if GetModelOptions.DoNotLoad is in use. + /// Error loading the file. + /// Cannot construct the model because the underyling data is in an invalid format. + public T GetModel(IFileModelProvider modelProvider, GetModelOptions options = GetModelOptions.None) where T : class + { + throw new NotImplementedException(); + } + + /// + /// Sets the model associated with the specified model provider to be dirty. + /// All other models are marked as stale. If another model was previously dirty, those earlier changes will be lost. + /// + public void MakeDirty(IFileModelProvider modelProvider) where T : class + { + throw new NotImplementedException(); + } + + /// + /// Unloads the model associated with the specified model provider. + /// Unloading the dirty model will cause changes to be lost. + /// + public void UnloadModel(IFileModelProvider modelProvider) where T : class + { + throw new NotImplementedException(); + } + + /// + /// Replaces the model associated with the specified model provider with a different instance. + /// + /// The model provider for the model type. + /// The new model instance. + /// Specifies how the dirty flag is handled during the replacement. + /// By default, the new model is marked as dirty and all other models are marked as stale. + /// In implementations, you should use instead. + public void ReplaceModel(IFileModelProvider modelProvider, T model, ReplaceModelMode mode = ReplaceModelMode.SetAsDirty) where T : class + { + throw new NotImplementedException(); + } + + } + + /* + /// + /// Represents an opened file. + /// + public abstract class OpenedFile : ICanBeDirty + { + protected IViewContent currentView; + bool inLoadOperation; + bool inSaveOperation; + + /// + /// holds unsaved file content in memory when view containing the file was closed but no other view + /// activated + /// + byte[] fileData; - public abstract event EventHandler FileClosed; /// /// Use this method to save the file to disk using a new name. @@ -378,4 +500,5 @@ static void RestoreMemento(IViewContent viewContent, Properties memento) } } } + */ } diff --git a/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs new file mode 100644 index 00000000000..34a1fcfab98 --- /dev/null +++ b/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs @@ -0,0 +1,89 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.IO; +using System.Text; +using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit.Utils; +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop.Widgets.MyersDiff; + +namespace ICSharpCode.SharpDevelop.Workbench +{ + sealed class TextDocumentFileModelProvider : IFileModelProvider + { + public TextDocument Load(OpenedFile file) + { + TextDocument document = file.GetModel(this, GetModelOptions.AllowStale | GetModelOptions.DoNotLoad); + var info = document != null ? document.GetFileModelInfo() : new DocumentFileModelInfo(); + string textContent; + using (Stream stream = file.GetModel(FileModels.Binary).OpenRead()) { + using (StreamReader reader = FileReader.OpenStream(stream, SD.FileService.DefaultFileEncoding)) { + textContent = reader.ReadToEnd(); + info.Encoding = reader.CurrentEncoding; + } + } + if (document != null) { + // Reload document + var diff = new MyersDiffAlgorithm(new StringSequence(document.Text), new StringSequence(textContent)); + document.Replace(0, document.TextLength, textContent, ToOffsetChangeMap(diff.GetEdits())); + document.UndoStack.ClearAll(); + info.IsStale = false; + return document; + } else { + document = new TextDocument(textContent); + document.GetRequiredService().AddService(typeof(DocumentFileModelInfo), info); + } + } + + OffsetChangeMap ToOffsetChangeMap(IEnumerable edits) + { + var map = new OffsetChangeMap(); + int diff = 0; + foreach (var edit in edits) { + Debug.Assert(edit.EditType != ChangeType.None && edit.EditType != ChangeType.Unsaved); + int offset = edit.BeginA + diff; + int removalLength = edit.EndA - edit.BeginA; + int insertionLength = edit.EndB - edit.BeginB; + + diff += (insertionLength - removalLength); + map.Add(new OffsetChangeMapEntry(offset, removalLength, insertionLength)); + } + return map; + } + + public void Save(OpenedFile file, TextDocument model) + { + throw new NotImplementedException(); + } + + public void SaveCopyAs(OpenedFile file, TextDocument model, FileName outputFileName) + { + throw new NotImplementedException(); + } + + bool IFileModelProvider.CanLoadFrom(IFileModelProvider otherProvider) + { + return otherProvider == FileModels.Binary || FileModels.Binary.CanLoadFrom(otherProvider); + } + + public void NotifyRename(OpenedFile file, TextDocument model, FileName oldName, FileName newName) + { + model.FileName = newName; + } + + public void NotifyStale(OpenedFile file, TextDocument model) + { + model.GetFileModelInfo().IsStale = true; + } + + public void NotifyUnloaded(OpenedFile file, TextDocument model) + { + model.GetFileModelInfo().IsStale = true; + } + } +} diff --git a/src/Main/Core/Project/Src/Services/FileUtility/PathName.cs b/src/Main/Core/Project/Src/Services/FileUtility/PathName.cs index d0ff2e5b3b5..5aa61ad36e6 100644 --- a/src/Main/Core/Project/Src/Services/FileUtility/PathName.cs +++ b/src/Main/Core/Project/Src/Services/FileUtility/PathName.cs @@ -57,6 +57,15 @@ public override string ToString() return normalizedPath; } + /// + /// Converts the path into an URI. + /// + /// The path contains invalid characters for an URI. + public Uri ToUri() + { + return new Uri(normalizedPath, IsRelative ? UriKind.Relative : UriKind.Absolute); + } + /// /// Gets whether this path is relative. /// From 8bb111bc11b5e26b3bc3b582067287e5df8c241b Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 18 Jan 2014 22:01:57 +0100 Subject: [PATCH 02/13] Implement new OpenedFile API. --- .../Utils/ObserveAddRemoveCollection.cs | 2 +- .../Project/ICSharpCode.SharpDevelop.csproj | 2 +- .../Project/Behaviors/DotNetStartBehavior.cs | 11 +- .../FindReferenceService.cs | 12 +- .../Project/Workbench/AbstractViewContent.cs | 66 +--- .../Project/Workbench/FakeXmlViewContent.cs | 233 ------------ .../Workbench/File/BinaryFileModelProvider.cs | 18 +- .../Base/Project/Workbench/File/FileModels.cs | 30 +- .../Project/Workbench/File/IFileService.cs | 63 +++- .../Base/Project/Workbench/File/OpenedFile.cs | 344 +++++++++++++++++- .../File/TextDocumentFileModelProvider.cs | 18 +- .../File/XDocumentFileModelProvider.cs | 54 +++ .../Project/Workbench/FileChangeWatcher.cs | 10 +- .../Base/Project/Workbench/IViewContent.cs | 2 +- .../Templates/File/FileTemplateImpl.cs | 6 +- .../AutoDetectDisplayBinding.cs | 2 +- .../SharpDevelop/Workbench/FileService.cs | 107 +++--- .../Workbench/FileServiceOpenedFile.cs | 127 +------ 18 files changed, 594 insertions(+), 513 deletions(-) delete mode 100644 src/Main/Base/Project/Workbench/FakeXmlViewContent.cs create mode 100644 src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ObserveAddRemoveCollection.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ObserveAddRemoveCollection.cs index 7fa331294e1..e9d2d3e5159 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ObserveAddRemoveCollection.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ObserveAddRemoveCollection.cs @@ -26,7 +26,7 @@ namespace ICSharpCode.AvalonEdit.Utils /// It is valid for the onAdd callback to throw an exception - this will prevent the new item from /// being added to the collection. /// - sealed class ObserveAddRemoveCollection : Collection + public sealed class ObserveAddRemoveCollection : Collection { readonly Action onAdd, onRemove; diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 9fa03747e65..3dd360490ff 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -359,7 +359,6 @@ - @@ -368,6 +367,7 @@ + diff --git a/src/Main/Base/Project/Src/Project/Behaviors/DotNetStartBehavior.cs b/src/Main/Base/Project/Src/Project/Behaviors/DotNetStartBehavior.cs index baa7da48a41..b3bd61f07a3 100644 --- a/src/Main/Base/Project/Src/Project/Behaviors/DotNetStartBehavior.cs +++ b/src/Main/Base/Project/Src/Project/Behaviors/DotNetStartBehavior.cs @@ -280,13 +280,14 @@ void UpdateAppConfig(TargetFramework newFramework) // Also, for applications (not libraries), create an app.config is it is required for the target framework bool createAppConfig = newFramework.RequiresAppConfigEntry && (Project.OutputType != OutputType.Library && Project.OutputType != OutputType.Module); - string appConfigFileName = CompilableProject.GetAppConfigFile(Project, createAppConfig); + FileName appConfigFileName = CompilableProject.GetAppConfigFile(Project, createAppConfig); if (appConfigFileName == null) return; - using (FakeXmlViewContent xml = new FakeXmlViewContent(appConfigFileName)) { - if (xml.Document != null) { - XElement configuration = xml.Document.Root; + SD.FileService.UpdateFileModel( + appConfigFileName, FileModels.XDocument, + delegate (XDocument document) { + XElement configuration = document.Root; XElement startup = configuration.Element("startup"); if (startup == null) { startup = new XElement("startup"); @@ -304,7 +305,7 @@ void UpdateAppConfig(TargetFramework newFramework) supportedRuntime.SetAttributeValue("version", newFramework.SupportedRuntimeVersion); supportedRuntime.SetAttributeValue("sku", newFramework.SupportedSku); } - } + ); } protected virtual void AddOrRemoveExtensions() diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs b/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs index d3868b88f46..4d676e716c3 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs @@ -32,6 +32,7 @@ using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Project; +using ICSharpCode.SharpDevelop.Workbench; namespace ICSharpCode.SharpDevelop.Refactoring { @@ -255,16 +256,11 @@ static void ApplyChanges(PatchedFile file) var openedFile = SD.FileService.GetOpenedFile(file.FileName); if (openedFile == null) { SD.FileService.OpenFile(file.FileName, false); - openedFile = SD.FileService.GetOpenedFile(file.FileName); //? + openedFile = SD.FileService.GetOpenedFile(file.FileName); } - - var provider = openedFile.CurrentView.GetService(); - if (provider != null) { - var document = provider.GetDocumentForFile(openedFile); - if (document == null) - throw new InvalidOperationException("Editor/document not found!"); + if (openedFile != null) { + var document = openedFile.GetModel(FileModels.TextDocument); file.Apply(document); - openedFile.MakeDirty(); } } } diff --git a/src/Main/Base/Project/Workbench/AbstractViewContent.cs b/src/Main/Base/Project/Workbench/AbstractViewContent.cs index b6d3839ed2c..bc126f0bb14 100644 --- a/src/Main/Base/Project/Workbench/AbstractViewContent.cs +++ b/src/Main/Base/Project/Workbench/AbstractViewContent.cs @@ -23,6 +23,7 @@ using System.IO; using System.Windows.Forms; +using ICSharpCode.AvalonEdit.Utils; using ICSharpCode.Core; using ICSharpCode.Core.Presentation; @@ -230,20 +231,28 @@ public virtual void SwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent ol #endregion #region Files - FilesCollection files; + ObserveAddRemoveCollection files; ReadOnlyCollection filesReadonly; void InitFiles() { - files = new FilesCollection(this); + files = new ObserveAddRemoveCollection(RegisterFileEventHandlers, UnregisterFileEventHandlers); filesReadonly = new ReadOnlyCollection(files); } + /// + /// The list of files that are being edited by this view content. + /// The first item in this list is used for the property. + /// + /// The collection automatically calls and as files + /// are added/removed from the collection. + /// The collection is cleared (thus freeing all references) when the view content is disposed. + /// protected Collection Files { get { return files; } } - IList IViewContent.Files { + IReadOnlyList IViewContent.Files { get { return filesReadonly; } } @@ -273,15 +282,11 @@ public virtual FileName PrimaryFileName { } } - protected bool AutomaticallyRegisterViewOnFiles = true; - void RegisterFileEventHandlers(OpenedFile newItem) { newItem.FileNameChanged += OnFileNameChanged; newItem.IsDirtyChanged += OnIsDirtyChanged; - if (AutomaticallyRegisterViewOnFiles) { - newItem.RegisterView(this); - } + newItem.AddReference(); OnIsDirtyChanged(null, EventArgs.Empty); // re-evaluate this.IsDirty after changing the file collection } @@ -289,9 +294,7 @@ void UnregisterFileEventHandlers(OpenedFile oldItem) { oldItem.FileNameChanged -= OnFileNameChanged; oldItem.IsDirtyChanged -= OnIsDirtyChanged; - if (AutomaticallyRegisterViewOnFiles) { - oldItem.UnregisterView(this); - } + oldItem.ReleaseReference(); OnIsDirtyChanged(null, EventArgs.Empty); // re-evaluate this.IsDirty after changing the file collection } @@ -309,43 +312,6 @@ void OnFileNameChanged(object sender, EventArgs e) protected virtual void OnFileNameChanged(OpenedFile file) { } - - private sealed class FilesCollection : Collection - { - AbstractViewContent parent; - - public FilesCollection(AbstractViewContent parent) - { - this.parent = parent; - } - - protected override void InsertItem(int index, OpenedFile item) - { - base.InsertItem(index, item); - parent.RegisterFileEventHandlers(item); - } - - protected override void SetItem(int index, OpenedFile item) - { - parent.UnregisterFileEventHandlers(this[index]); - base.SetItem(index, item); - parent.RegisterFileEventHandlers(item); - } - - protected override void RemoveItem(int index) - { - parent.UnregisterFileEventHandlers(this[index]); - base.RemoveItem(index); - } - - protected override void ClearItems() - { - foreach (OpenedFile item in this) { - parent.UnregisterFileEventHandlers(item); - } - base.ClearItems(); - } - } #endregion #region TitleName @@ -487,9 +453,7 @@ public virtual void Dispose() { workbenchWindow = null; UnregisterOnActiveViewContentChanged(); - if (AutomaticallyRegisterViewOnFiles) { - this.Files.Clear(); - } + this.Files.Clear(); isDisposed = true; if (Disposed != null) { Disposed(this, EventArgs.Empty); diff --git a/src/Main/Base/Project/Workbench/FakeXmlViewContent.cs b/src/Main/Base/Project/Workbench/FakeXmlViewContent.cs deleted file mode 100644 index 9fc63e1ad15..00000000000 --- a/src/Main/Base/Project/Workbench/FakeXmlViewContent.cs +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.IO; -using System.Xml; -using System.Xml.Linq; - -using ICSharpCode.SharpDevelop.Gui; -using ICSharpCode.SharpDevelop.Workbench; - -namespace ICSharpCode.SharpDevelop.Workbench -{ - /// - /// IViewContent implementation that opens a file as XDocument and allows editing it, while synchronizing changes with any open editor. - /// - public sealed class FakeXmlViewContent : IViewContent - { - public FakeXmlViewContent(string fileName) - { - this.PrimaryFile = SD.FileService.GetOrCreateOpenedFile(fileName); - this.oldView = this.PrimaryFile.CurrentView; - this.PrimaryFile.RegisterView(this); - this.PrimaryFile.SwitchedToView(this); - } - - IViewContent oldView; - XDocument document; - byte[] fileData; - - /// - /// Gets the document. - /// Can return null if there were load errors. - /// - public XDocument Document { - get { return document; } - } - - public void Dispose() - { - if (this.IsDisposed) - return; - this.IsDisposed = true; - if (document != null) { - this.PrimaryFile.MakeDirty(); - if (this.PrimaryFile.RegisteredViewContents.Count == 1) - this.PrimaryFile.SaveToDisk(); - } - this.PrimaryFile.UnregisterView(this); - if (oldView != null) - this.PrimaryFile.SwitchedToView(oldView); - if (Disposed != null) - Disposed(this, EventArgs.Empty); - } - - void IViewContent.Save(OpenedFile file, Stream stream) - { - if (document != null) - document.Save(stream, SaveOptions.DisableFormatting); - else if (fileData != null) - stream.Write(fileData, 0, fileData.Length); - } - - void IViewContent.Load(OpenedFile file, Stream stream) - { - document = null; - fileData = null; - - try { - document = XDocument.Load(stream, LoadOptions.PreserveWhitespace); - } catch (XmlException) { - stream.Position = 0; - fileData = new byte[(int)stream.Length]; - int pos = 0; - while (pos < fileData.Length) { - int c = stream.Read(fileData, pos, fileData.Length - pos); - if (c == 0) break; - pos += c; - } - } - } - - #region IViewContent stub implementation - event EventHandler IViewContent.TabPageTextChanged { - add { } - remove { } - } - - event EventHandler IViewContent.TitleNameChanged { - add { } - remove { } - } - - event EventHandler IViewContent.InfoTipChanged { - add { } - remove { } - } - - public event EventHandler Disposed; - - event EventHandler ICanBeDirty.IsDirtyChanged { - add { } - remove { } - } - - object IViewContent.Control { - get { - throw new NotImplementedException(); - } - } - - object IViewContent.InitiallyFocusedControl { - get { - throw new NotImplementedException(); - } - } - - IWorkbenchWindow IViewContent.WorkbenchWindow { - get { - throw new NotImplementedException(); - } - set { - throw new NotImplementedException(); - } - } - - string IViewContent.TabPageText { - get { - throw new NotImplementedException(); - } - } - - string IViewContent.TitleName { - get { - throw new NotImplementedException(); - } - } - - System.Collections.Generic.IList IViewContent.Files { - get { return new [] { PrimaryFile }; } - } - - public OpenedFile PrimaryFile { get; set; } - - ICSharpCode.Core.FileName IViewContent.PrimaryFileName { - get { return PrimaryFile.FileName; } - } - - public bool IsDisposed { get; private set; } - - bool IViewContent.IsReadOnly { - get { - throw new NotImplementedException(); - } - } - - bool IViewContent.IsViewOnly { - get { - throw new NotImplementedException(); - } - } - - string IViewContent.InfoTip { - get { - throw new NotImplementedException(); - } - } - - bool IViewContent.CloseWithSolution { - get { - throw new NotImplementedException(); - } - } - - System.Collections.Generic.ICollection IViewContent.SecondaryViewContents { - get { - throw new NotImplementedException(); - } - } - - bool ICanBeDirty.IsDirty { - get { - throw new NotImplementedException(); - } - } - - INavigationPoint IViewContent.BuildNavPoint() - { - throw new NotImplementedException(); - } - - bool IViewContent.SupportsSwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView) - { - return false; - } - - bool IViewContent.SupportsSwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView) - { - return false; - } - - void IViewContent.SwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView) - { - throw new NotImplementedException(); - } - - void IViewContent.SwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView) - { - throw new NotImplementedException(); - } - - object IServiceProvider.GetService(Type serviceType) - { - return null; - } - #endregion - } -} diff --git a/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs index 137123fcb54..78d391b4fb6 100644 --- a/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs @@ -7,9 +7,9 @@ namespace ICSharpCode.SharpDevelop.Workbench { - sealed class BinaryFileModelProvider : IFileModelProvider + sealed class BinaryFileModelProvider : IFileModelProvider { - sealed class OnDiskBinaryModel : IBinaryModel + sealed class OnDiskBinaryModel : IBinaryFileModel { internal readonly OpenedFile file; @@ -24,24 +24,24 @@ public Stream OpenRead() } } - public IBinaryModel Load(OpenedFile file) + public IBinaryFileModel Load(OpenedFile file) { return new OnDiskBinaryModel(file); } - public void Save(OpenedFile file, IBinaryModel model) + public void Save(OpenedFile file, IBinaryFileModel model) { SaveCopyAs(file, model, file.FileName); file.ReplaceModel(this, model, ReplaceModelMode.SetAsValid); // remove dirty flag } - public void SaveCopyAs(OpenedFile file, IBinaryModel model, FileName outputFileName) + public void SaveCopyAs(OpenedFile file, IBinaryFileModel model, FileName outputFileName) { var onDisk = model as OnDiskBinaryModel; if (onDisk != null) { // We can just copy the file (but avoid copying to itself) if (onDisk.file.FileName != outputFileName) { - SD.FileSystem.CopyFile(onDisk.file.FileName, outputFileName); + SD.FileSystem.CopyFile(onDisk.file.FileName, outputFileName, true); } } else { using (var inputStream = model.OpenRead()) { @@ -57,15 +57,15 @@ public bool CanLoadFrom(IFileModelProvider otherProvider) where U : class return false; } - public void NotifyRename(OpenedFile file, IBinaryModel model, FileName oldName, FileName newName) + public void NotifyRename(OpenedFile file, IBinaryFileModel model, FileName oldName, FileName newName) { } - public void NotifyStale(OpenedFile file, IBinaryModel model) + public void NotifyStale(OpenedFile file, IBinaryFileModel model) { } - public void NotifyUnloaded(OpenedFile file, IBinaryModel model) + public void NotifyUnloaded(OpenedFile file, IBinaryFileModel model) { } } diff --git a/src/Main/Base/Project/Workbench/File/FileModels.cs b/src/Main/Base/Project/Workbench/File/FileModels.cs index ce9dea8a31b..51888b785c8 100644 --- a/src/Main/Base/Project/Workbench/File/FileModels.cs +++ b/src/Main/Base/Project/Workbench/File/FileModels.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using ICSharpCode.AvalonEdit.Document; namespace ICSharpCode.SharpDevelop.Workbench { @@ -15,18 +14,23 @@ public static class FileModels /// /// The binary file model provider. /// - public static readonly IFileModelProvider Binary = new BinaryFileModelProvider(); + public static readonly IFileModelProvider Binary = new BinaryFileModelProvider(); /// /// The text document file model provider. /// - public static readonly IFileModelProvider TextDocument = new TextDocumentFileModelProvider(); + public static readonly IFileModelProvider TextDocument = new TextDocumentFileModelProvider(); + + /// + /// The XDocument file model provider. + /// + public static readonly IFileModelProvider XDocument = new XDocumentFileModelProvider(); } /// /// Represents the binary contents of a file. /// - public interface IBinaryModel + public interface IBinaryFileModel { /// /// Creates a stream that reads from the binary model. @@ -35,4 +39,22 @@ public interface IBinaryModel /// Error opening the file. Stream OpenRead(); } + + /// + /// Simple IBinaryFileModel implementation that uses a byte array. + /// + public class BinaryFileModel : IBinaryFileModel + { + readonly byte[] data; + + public BinaryFileModel(byte[] data) + { + this.data = data; + } + + public Stream OpenRead() + { + return new MemoryStream(data); + } + } } diff --git a/src/Main/Base/Project/Workbench/File/IFileService.cs b/src/Main/Base/Project/Workbench/File/IFileService.cs index 6cc550d713b..ad5618296fd 100644 --- a/src/Main/Base/Project/Workbench/File/IFileService.cs +++ b/src/Main/Base/Project/Workbench/File/IFileService.cs @@ -27,6 +27,26 @@ namespace ICSharpCode.SharpDevelop.Workbench { + /// + /// Options for the method. + /// + [Flags] + public enum FileUpdateOptions + { + None = 0, + /// + /// If no view content exists for the file, it will be opened in a new view. + /// The view content can then be used to save the changes performed by the update. + /// If this option is not used, changes are automatically saved to disk if the file is not open in any view. + /// + OpenViewIfNoneExists = 1, + /// + /// The changes are saved to disk after the update, even if the file is currently open in a view + /// (in that case, other changes from the view may be saved as well). + /// + SaveToDisk = 2, + } + /// /// Manages the list files opened by view contents so that multiple view contents opening the same file can synchronize. /// Also provides events that can be used to listen to file operations performed in the IDE. @@ -94,7 +114,7 @@ public interface IFileService /// Description shown in the dialog. /// Optional: Initially selected folder. /// The selected folder; or null if the user cancelled the dialog. - string BrowseForFolder(string description, string selectedPath = null); + DirectoryName BrowseForFolder(string description, string selectedPath = null); #endregion #region OpenedFiles @@ -103,38 +123,49 @@ public interface IFileService /// The returned collection is a read-only copy of the currently opened files - /// it will not reflect future changes of the list of opened files. /// + /// + /// Accessing this property does not increase the reference count on the opened files in the collection. + /// If you want to maintain a reference over a longer period of time (so that the existing reference might be released), + /// you need to call . + /// IReadOnlyList OpenedFiles { get; } /// /// Gets an opened file, or returns null if the file is not opened. /// + /// + /// This method does not increase the reference count on the opened files in the collection. + /// If you want to maintain a reference over a longer period of time (so that the existing reference might be released), + /// you need to call . + /// OpenedFile GetOpenedFile(FileName fileName); - /// - /// Gets an opened file, or returns null if the file is not opened. - /// + /// OpenedFile GetOpenedFile(string fileName); /// - /// Gets or creates an opened file. - /// Warning: the opened file will be a file without any views attached. - /// Make sure to attach a view to it, or call CloseIfAllViewsClosed on the OpenedFile to - /// unload the OpenedFile instance if no views were attached to it. + /// Creates a new OpenedFile for the specified file name. + /// If the file is already open, an existing OpenedFile is returned instead (and its reference count is increased). + /// + /// Every CreateOpenedFile() call must be paired with a call! /// - OpenedFile GetOrCreateOpenedFile(FileName fileName); + OpenedFile CreateOpenedFile(FileName fileName); /// - /// Gets or creates an opened file. - /// Warning: the opened file will be a file without any views attached. - /// Make sure to attach a view to it, or call CloseIfAllViewsClosed on the OpenedFile to - /// unload the OpenedFile instance if no views were attached to it. + /// Creates a new untitled OpenedFile. + /// + /// Every CreateUntitledOpenedFile() call must be paired with a call! /// - OpenedFile GetOrCreateOpenedFile(string fileName); + OpenedFile CreateUntitledOpenedFile(string defaultName, byte[] content); /// - /// Creates a new untitled OpenedFile. + /// Updates a file by performing actions on a model. /// - OpenedFile CreateUntitledOpenedFile(string defaultName, byte[] content); + /// The file to be updated. + /// The type of model to use for the update. + /// A delegate that performs the update. + /// Provides options regarding the file model update. + void UpdateFileModel(FileName fileName, IFileModelProvider modelProvider, Action action, FileUpdateOptions options = FileUpdateOptions.None) where T : class; #endregion #region CheckFileName diff --git a/src/Main/Base/Project/Workbench/File/OpenedFile.cs b/src/Main/Base/Project/Workbench/File/OpenedFile.cs index 56fc0435448..a924e563c6f 100644 --- a/src/Main/Base/Project/Workbench/File/OpenedFile.cs +++ b/src/Main/Base/Project/Workbench/File/OpenedFile.cs @@ -20,9 +20,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.Core; -using ICSharpCode.SharpDevelop.Parser; namespace ICSharpCode.SharpDevelop.Workbench { @@ -69,6 +67,69 @@ public enum ReplaceModelMode /// public abstract class OpenedFile : ICanBeDirty { + abstract class ModelEntry + { + public bool IsStale; + + public abstract object Provider { get; } + + public abstract void Save(OpenedFile file); + public abstract void SaveCopyAs(OpenedFile file, FileName outputFileName); + public abstract void NotifyRename(OpenedFile file, FileName oldName, FileName newName); + public abstract void NotifyStale(OpenedFile file); + public abstract void NotifyUnloaded(OpenedFile file); + public abstract bool NeedsSaveForLoadInto(IFileModelProvider modelProvider) where T : class; + } + class ModelEntry : ModelEntry where T : class + { + readonly IFileModelProvider provider; + public T Model; + + public ModelEntry(IFileModelProvider provider, T model) + { + Debug.Assert(provider != null); + Debug.Assert(model != null); + this.provider = provider; + this.Model = model; + } + + public override object Provider { get { return provider; } } + + public override void Save(OpenedFile file) + { + provider.Save(file, Model); + } + + public override void SaveCopyAs(OpenedFile file, FileName outputFileName) + { + provider.SaveCopyAs(file, Model, outputFileName); + } + + public override void NotifyRename(OpenedFile file, FileName oldName, FileName newName) + { + provider.NotifyRename(file, Model, oldName, newName); + } + + public override void NotifyStale(OpenedFile file) + { + provider.NotifyStale(file, Model); + } + + public override void NotifyUnloaded(OpenedFile file) + { + provider.NotifyUnloaded(file, Model); + } + + public override bool NeedsSaveForLoadInto(IFileModelProvider modelProvider) + { + return modelProvider.CanLoadFrom(provider); + } + } + + readonly List entries = new List(); + ModelEntry dirtyEntry; + bool preventLoading; + #region IsDirty implementation bool isDirty; public event EventHandler IsDirtyChanged; @@ -77,7 +138,7 @@ public abstract class OpenedFile : ICanBeDirty /// Gets/sets if the file is has unsaved changes. /// public bool IsDirty { - get { return isDirty;} + get { return isDirty; } private set { if (isDirty != value) { isDirty = value; @@ -123,14 +184,18 @@ protected virtual void ChangeFileName(FileName newValue) { SD.MainThread.VerifyAccess(); + FileName oldName = fileName; fileName = newValue; + foreach (var entry in entries) + entry.NotifyRename(this, oldName, newValue); if (FileNameChanged != null) { FileNameChanged(this, EventArgs.Empty); } } #endregion + #region ReloadFromDisk /// /// This method sets all models to 'stale', causing the file to be re-loaded from disk /// on the next GetModel() call. @@ -138,11 +203,19 @@ protected virtual void ChangeFileName(FileName newValue) /// The file is untitled. public void ReloadFromDisk() { + CheckDisposed(); if (IsUntitled) throw new InvalidOperationException("Cannot reload an untitled file from disk."); - throw new NotImplementedException(); + // First set all entries to stale, then call NotifyStale(). + foreach (var entry in entries) + entry.IsStale = true; + foreach (var entry in entries) + entry.NotifyStale(this); + this.IsDirty = false; } + #endregion + #region SaveToDisk /// /// Saves the file to disk. /// @@ -150,27 +223,78 @@ public void ReloadFromDisk() /// The file is untitled. public void SaveToDisk() { + CheckDisposed(); if (IsUntitled) - throw new InvalidOperationException("Cannot reload an untitled file from disk."); - throw new NotImplementedException(); + throw new InvalidOperationException("Cannot save an untitled file to disk."); + SaveToDisk(this.FileName); } /// /// Changes the file name, and saves the file to disk. /// /// If the file is saved successfully, the dirty flag will be cleared (the dirty model becomes valid instead). - public void SaveToDisk(FileName fileName) + public virtual void SaveToDisk(FileName fileName) { + CheckDisposed(); + + bool safeSaving = SD.FileService.SaveUsingTemporaryFile && SD.FileSystem.FileExists(fileName); + FileName saveAs = safeSaving ? FileName.Create(fileName + ".bak") : fileName; + SaveCopyTo(saveAs); + if (safeSaving) { + DateTime creationTime = File.GetCreationTimeUtc(fileName); + File.Delete(fileName); + try { + File.Move(saveAs, fileName); + } catch (UnauthorizedAccessException) { + // sometime File.Move raise exception (TortoiseSVN, Anti-vir ?) + // try again after short delay + System.Threading.Thread.Sleep(250); + File.Move(saveAs, fileName); + } + File.SetCreationTimeUtc(fileName, creationTime); + } + + dirtyEntry = null; this.FileName = fileName; - SaveToDisk(); + this.IsDirty = false; } /// /// Saves a copy of the file to disk. Does not change the name of the OpenedFile to the specified file name, and does not reset the dirty flag. /// - public void SaveCopyAs(FileName fileName) + public virtual void SaveCopyAs(FileName fileName) { - throw new NotImplementedException(); + CheckDisposed(); + SaveCopyTo(fileName); + } + + void SaveCopyTo(FileName outputFileName) + { + preventLoading = true; + try { + var entry = PickValidEntry(); + if (entry != null) { + entry.SaveCopyAs(this, outputFileName); + } else if (outputFileName != this.FileName) { + SD.FileSystem.CopyFile(this.FileName, outputFileName, true); + } + } + finally { + preventLoading = false; + } + } + + #endregion + + #region GetModel + ModelEntry GetEntry(IFileModelProvider modelProvider) where T : class + { + CheckDisposed(); + foreach (var entry in entries) { + if (entry.Provider == modelProvider) + return (ModelEntry)entry; + } + return null; } /// @@ -179,31 +303,145 @@ public void SaveCopyAs(FileName fileName) /// The model provider for the desired model type. Built-in model providers can be found in the class. /// Options that control how /// The model instance, or possibly null if GetModelOptions.DoNotLoad is in use. - /// Error loading the file. + /// Error loading the file. /// Cannot construct the model because the underyling data is in an invalid format. public T GetModel(IFileModelProvider modelProvider, GetModelOptions options = GetModelOptions.None) where T : class { - throw new NotImplementedException(); + if (modelProvider == null) + throw new ArgumentNullException("modelProvider"); + if (preventLoading && (options & GetModelOptions.DoNotLoad) == 0) + throw new InvalidOperationException("GetModel() operations that potentially load models are not permitted at this point. (to retrieve existing models, use the DoNotLoad option)"); + ModelEntry entry = GetEntry(modelProvider); + // Return existing model if possible: + if (entry != null && ((options & GetModelOptions.AllowStale) != 0 || !entry.IsStale)) + return entry.Model; + // If we aren't allowed to load, just return null: + if ((options & GetModelOptions.DoNotLoad) != 0) + return null; + Debug.Assert(!preventLoading); + preventLoading = true; + try { + // Before we can load the requested model, save the dirty model (if necessary): + while (dirtyEntry != null && dirtyEntry.NeedsSaveForLoadInto(modelProvider)) { + dirtyEntry.Save(this); + // re-fetch entry because it's possible that it was created/replaced/unloaded + entry = GetEntry(modelProvider); + // if the entry was made valid by the save operation, return it directly + if (entry != null && !entry.IsStale) + return entry.Model; + } + } finally { + preventLoading = false; + } + // Load the model. Note that we do allow (and expect) recursive loads at this point. + T model = modelProvider.Load(this); + // re-fetch entry because it's possible that it was created/replaced/unloaded (normally this shouldn't happen, but let's be on the safe side) + entry = GetEntry(modelProvider); + if (entry == null) { + // No entry for the model provider exists; we need to create a new one. + entry = new ModelEntry(modelProvider, model); + entries.Add(entry); + } else if (entry.Model == model) { + // The existing stale model was reused + entry.IsStale = false; + } else { + // The model is being replaced + entry.NotifyUnloaded(this); + entry.Model = model; + entry.IsStale = false; + } + return model; + } + #endregion + + #region MakeDirty + ModelEntry PickValidEntry() + { + if (dirtyEntry != null) + return dirtyEntry; // prefer dirty entry + foreach (var entry in entries) { + if (!entry.IsStale) { + return entry; + } + } + return null; + } + + /// + /// Takes a valid model and marks it as dirty. + /// If no valid model exists, this method has no effect. + /// If multiple valid models exist, one is picked at random. + /// + /// + /// This method is used when SharpDevelop detects + /// + public void MakeDirty() + { + var entry = PickValidEntry(); + if (entry != null) { + dirtyEntry = entry; + this.IsDirty = true; + } } /// /// Sets the model associated with the specified model provider to be dirty. /// All other models are marked as stale. If another model was previously dirty, those earlier changes will be lost. /// + /// + /// This method is usually called by the model provider. In a well-designed model, any change to the model + /// should result in the model provider calling MakeDirty() automatically. + /// public void MakeDirty(IFileModelProvider modelProvider) where T : class { - throw new NotImplementedException(); + if (modelProvider == null) + throw new ArgumentNullException("modelProvider"); + var entry = GetEntry(modelProvider); + if (entry == null) + throw new ArgumentException("There is no model loaded for the specified model provider."); + entry.IsStale = false; + dirtyEntry = entry; + MarkAllAsStaleExcept(entry); + this.IsDirty = true; + } + + void MarkAllAsStaleExcept(ModelEntry entry) + { + foreach (var otherEntry in entries) { + if (otherEntry != entry) { + otherEntry.IsStale = true; + } + } + // Raise events after all state is updated: + foreach (var otherEntry in entries) { + if (otherEntry != entry) { + otherEntry.NotifyStale(this); + } + } } + #endregion + #region UnloadModel /// /// Unloads the model associated with the specified model provider. /// Unloading the dirty model will cause changes to be lost. /// public void UnloadModel(IFileModelProvider modelProvider) where T : class { - throw new NotImplementedException(); + if (modelProvider == null) + throw new ArgumentNullException("modelProvider"); + var entry = GetEntry(modelProvider); + if (entry != null) { + if (dirtyEntry == entry) { + dirtyEntry = null; + } + entries.Remove(entry); + entry.NotifyUnloaded(this); + } } + #endregion + #region ReplaceModel /// /// Replaces the model associated with the specified model provider with a different instance. /// @@ -214,9 +452,85 @@ public void UnloadModel(IFileModelProvider modelProvider) where T : class /// In implementations, you should use instead. public void ReplaceModel(IFileModelProvider modelProvider, T model, ReplaceModelMode mode = ReplaceModelMode.SetAsDirty) where T : class { - throw new NotImplementedException(); + if (modelProvider == null) + throw new ArgumentNullException("modelProvider"); + var entry = GetEntry(modelProvider); + if (entry == null) { + entry = new ModelEntry(modelProvider, model); + } else { + if (entry.Model != model) { + entry.NotifyUnloaded(this); + entry.Model = model; + } + entry.IsStale = false; + } + switch (mode) { + case ReplaceModelMode.SetAsDirty: + dirtyEntry = entry; + MarkAllAsStaleExcept(entry); + this.IsDirty = true; + break; + case ReplaceModelMode.SetAsValid: + if (dirtyEntry == entry) { + dirtyEntry = null; + this.IsDirty = false; + } + break; + case ReplaceModelMode.TransferDirty: + dirtyEntry = entry; + this.IsDirty = true; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + #endregion + + #region Reference Counting + int referenceCount = 1; + + void CheckDisposed() + { + if (referenceCount <= 0) + throw new ObjectDisposedException("OpenedFile"); + } + + /// + /// Gets the reference count of the OpenedFile. + /// This is used for the IFileService.UpdateFileModel() implementation. + /// + public int ReferenceCount { + get { return referenceCount; } } + public void AddReference() + { + CheckDisposed(); + referenceCount++; + } + + public void ReleaseReference() + { + CheckDisposed(); + if (--referenceCount == 0) { + UnloadFile(); + } + } + + /// + /// Unloads the file, this method is called once after the reference count has reached zero. + /// + protected virtual void UnloadFile() + { + Debug.Assert(referenceCount == 0); + foreach (var entry in entries) { + entry.NotifyUnloaded(this); + } + // Free memory consumed by models even if the OpenedFile is leaked somewhere + entries.Clear(); + dirtyEntry = null; + } + #endregion } /* diff --git a/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs index 34a1fcfab98..6f78d2704b9 100644 --- a/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs @@ -33,11 +33,12 @@ public TextDocument Load(OpenedFile file) document.Replace(0, document.TextLength, textContent, ToOffsetChangeMap(diff.GetEdits())); document.UndoStack.ClearAll(); info.IsStale = false; - return document; } else { document = new TextDocument(textContent); + document.TextChanged += delegate { file.MakeDirty(this); }; document.GetRequiredService().AddService(typeof(DocumentFileModelInfo), info); } + return document; } OffsetChangeMap ToOffsetChangeMap(IEnumerable edits) @@ -58,12 +59,23 @@ OffsetChangeMap ToOffsetChangeMap(IEnumerable edits) public void Save(OpenedFile file, TextDocument model) { - throw new NotImplementedException(); + MemoryStream ms = new MemoryStream(); + SaveTo(ms, model); + file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray())); } public void SaveCopyAs(OpenedFile file, TextDocument model, FileName outputFileName) { - throw new NotImplementedException(); + using (Stream s = SD.FileSystem.OpenWrite(outputFileName)) { + SaveTo(s, model); + } + } + + static void SaveTo(Stream s, TextDocument model) + { + using (StreamWriter w = new StreamWriter(s, model.GetFileModelInfo().Encoding)) { + model.WriteTextTo(w); + } } bool IFileModelProvider.CanLoadFrom(IFileModelProvider otherProvider) diff --git a/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs new file mode 100644 index 00000000000..af35d07a849 --- /dev/null +++ b/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs @@ -0,0 +1,54 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.IO; +using System.Xml.Linq; +using ICSharpCode.Core; + +namespace ICSharpCode.SharpDevelop.Workbench +{ + sealed class XDocumentFileModelProvider : IFileModelProvider + { + public XDocument Load(OpenedFile file) + { + XDocument document; + using (Stream stream = file.GetModel(FileModels.Binary).OpenRead()) { + document = XDocument.Load(stream, LoadOptions.PreserveWhitespace); + } + document.Changed += delegate { + file.MakeDirty(this); + }; + return document; + } + + public void Save(OpenedFile file, XDocument model) + { + MemoryStream ms = new MemoryStream(); + model.Save(ms, SaveOptions.DisableFormatting); + file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray())); + } + + public void SaveCopyAs(OpenedFile file, XDocument model, FileName outputFileName) + { + model.Save(outputFileName, SaveOptions.DisableFormatting); + } + + public bool CanLoadFrom(IFileModelProvider otherProvider) where U : class + { + return otherProvider == FileModels.Binary || FileModels.Binary.CanLoadFrom(otherProvider); + } + + public void NotifyRename(OpenedFile file, XDocument model, FileName oldName, FileName newName) + { + } + + public void NotifyStale(OpenedFile file, XDocument model) + { + } + + public void NotifyUnloaded(OpenedFile file, XDocument model) + { + } + } +} diff --git a/src/Main/Base/Project/Workbench/FileChangeWatcher.cs b/src/Main/Base/Project/Workbench/FileChangeWatcher.cs index c0c85e1859b..868f522d2a6 100644 --- a/src/Main/Base/Project/Workbench/FileChangeWatcher.cs +++ b/src/Main/Base/Project/Workbench/FileChangeWatcher.cs @@ -82,7 +82,7 @@ public static void EnableAllChangeWatchers() bool wasChangedExternally = false; OpenedFile file; - public FileChangeWatcher(OpenedFile file) + internal FileChangeWatcher(OpenedFile file) { if (file == null) throw new ArgumentNullException("file"); @@ -203,18 +203,18 @@ void MainForm_Activated(object sender, EventArgs e) if (file == null) return; - string fileName = file.FileName; - if (!File.Exists(fileName)) + var fileName = file.FileName; + if (!SD.FileSystem.FileExists(fileName)) return; string message = StringParser.Parse( "${res:ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.TextEditorDisplayBinding.FileAlteredMessage}", new StringTagPair("File", Path.GetFullPath(fileName)) ); - if ((AutoLoadExternalChangesOption && file.IsDirty == false) + if ((AutoLoadExternalChangesOption && !file.IsDirty) || MessageService.AskQuestion(message, StringParser.Parse("${res:MainWindow.DialogName}"))) { - if (File.Exists(fileName)) { + if (SD.FileSystem.FileExists(fileName)) { file.ReloadFromDisk(); } } else { diff --git a/src/Main/Base/Project/Workbench/IViewContent.cs b/src/Main/Base/Project/Workbench/IViewContent.cs index 6f59eac2572..37d158bef5e 100644 --- a/src/Main/Base/Project/Workbench/IViewContent.cs +++ b/src/Main/Base/Project/Workbench/IViewContent.cs @@ -120,7 +120,7 @@ string InfoTip { /// Gets the list of files that are being edited using this view content. /// The returned collection usually is read-only. /// - IList Files { get; } + IReadOnlyList Files { get; } /// /// Gets the primary file being edited. Might return null if no file is edited. diff --git a/src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs b/src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs index 04e12d081be..5029626386f 100644 --- a/src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs +++ b/src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs @@ -528,8 +528,8 @@ string SaveFile(FileDescriptionTemplate newFile, string content, string binaryFi OpenedFile file = null; try { if (Path.IsPathRooted(fileName)) { - file = SD.FileService.GetOrCreateOpenedFile(fileName); - file.SetData(data); + file = SD.FileService.CreateOpenedFile(fileName); + file.ReplaceModel(FileModels.Binary, new BinaryFileModel(data)); Directory.CreateDirectory(Path.GetDirectoryName(fileName)); file.SaveToDisk(); @@ -543,7 +543,7 @@ string SaveFile(FileDescriptionTemplate newFile, string content, string binaryFi SD.FileService.OpenFile(file.FileName); } finally { if (file != null) - file.CloseIfAllViewsClosed(); + file.ReleaseReference(); } } diff --git a/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs b/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs index 0aa6d95f061..a432fbba1c2 100644 --- a/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs +++ b/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs @@ -51,7 +51,7 @@ public IViewContent CreateContentForFile(OpenedFile file) double max = double.NegativeInfinity; const int BUFFER_LENGTH = 4 * 1024; - using (var stream = file.OpenRead()) { + using (var stream = file.GetModel(FileModels.Binary).OpenRead()) { string mime = "text/plain"; if (stream.Length > 0) { stream.Position = 0; diff --git a/src/Main/SharpDevelop/Workbench/FileService.cs b/src/Main/SharpDevelop/Workbench/FileService.cs index 906ea2cc8d8..97d9073f6be 100644 --- a/src/Main/SharpDevelop/Workbench/FileService.cs +++ b/src/Main/SharpDevelop/Workbench/FileService.cs @@ -127,20 +127,7 @@ public ITextSource GetFileContentForOpenFile(FileName fileName) delegate { OpenedFile file = this.GetOpenedFile(fileName); if (file != null) { - if (file.CurrentView != null) { - IFileDocumentProvider provider = file.CurrentView.GetService(); - if (provider != null) { - IDocument document = provider.GetDocumentForFile(file); - if (document != null) { - return document.CreateSnapshot(); - } - } - } - - using (Stream s = file.OpenRead()) { - // load file - return new StringTextSource(FileReader.ReadFileContent(s, DefaultFileEncoding)); - } + return file.GetModel(FileModels.TextDocument).CreateSnapshot(); } return null; }); @@ -156,16 +143,16 @@ public ITextSource GetFileContentFromDisk(FileName fileName, CancellationToken c #endregion #region BrowseForFolder - public string BrowseForFolder(string description, string selectedPath) + public DirectoryName BrowseForFolder(string description, string selectedPath) { using (FolderBrowserDialog dialog = new FolderBrowserDialog()) { dialog.Description = StringParser.Parse(description); - if (selectedPath != null && selectedPath.Length > 0 && Directory.Exists(selectedPath)) { + if (!string.IsNullOrEmpty(selectedPath) && Directory.Exists(selectedPath)) { dialog.RootFolder = Environment.SpecialFolder.MyComputer; dialog.SelectedPath = selectedPath; } if (dialog.ShowDialog() == DialogResult.OK) { - return dialog.SelectedPath; + return DirectoryName.Create(dialog.SelectedPath); } else { return null; } @@ -174,7 +161,7 @@ public string BrowseForFolder(string description, string selectedPath) #endregion #region OpenedFile - Dictionary openedFileDict = new Dictionary(); + readonly Dictionary openedFileDict = new Dictionary(); /// public IReadOnlyList OpenedFiles { @@ -204,33 +191,33 @@ public OpenedFile GetOpenedFile(FileName fileName) } /// - public OpenedFile GetOrCreateOpenedFile(string fileName) - { - return GetOrCreateOpenedFile(FileName.Create(fileName)); - } - - /// - public OpenedFile GetOrCreateOpenedFile(FileName fileName) + public OpenedFile CreateOpenedFile(FileName fileName) { if (fileName == null) throw new ArgumentNullException("fileName"); OpenedFile file; - if (!openedFileDict.TryGetValue(fileName, out file)) { + if (openedFileDict.TryGetValue(fileName, out file)) { + file.AddReference(); + } else { openedFileDict[fileName] = file = new FileServiceOpenedFile(this, fileName); } return file; } + int untitledFileID; + /// public OpenedFile CreateUntitledOpenedFile(string defaultName, byte[] content) { if (defaultName == null) throw new ArgumentNullException("defaultName"); - OpenedFile file = new FileServiceOpenedFile(this, content); - file.FileName = new FileName(file.GetHashCode() + "/" + defaultName); + untitledFileID++; + var fileName = new FileName("untitled://" + untitledFileID + "/" + defaultName); + OpenedFile file = new FileServiceOpenedFile(this, fileName); openedFileDict[file.FileName] = file; + file.ReplaceModel(FileModels.Binary, new BinaryFileModel(content)); return file; } @@ -243,14 +230,14 @@ internal void OpenedFileFileNameChange(OpenedFile file, FileName oldName, FileNa if (openedFileDict[oldName] != file) throw new ArgumentException("file must be registered as oldName"); - if (openedFileDict.ContainsKey(newName)) { - OpenedFile oldFile = openedFileDict[newName]; - if (oldFile.CurrentView != null) { - if (oldFile.CurrentView.WorkbenchWindow != null) - oldFile.CurrentView.WorkbenchWindow.CloseWindow(true); - } else { - throw new ArgumentException("there already is a file with the newName"); + OpenedFile oldFile; + if (openedFileDict.TryGetValue(newName, out oldFile)) { + foreach (var view in SD.Workbench.ViewContentCollection.ToArray()) { + if (view.WorkbenchWindow != null && view.Files.Contains(oldFile)) + view.WorkbenchWindow.CloseWindow(true); } + if (oldFile.ReferenceCount > 0) + throw new ArgumentException("another file is already registered as newName; and could not be freed by closing its view contents"); } openedFileDict.Remove(oldName); openedFileDict[newName] = file; @@ -266,6 +253,28 @@ internal void OpenedFileClosed(OpenedFile file) openedFileDict.Remove(file.FileName); LoggingService.Debug("OpenedFileClosed: " + file.FileName); } + + public void UpdateFileModel(FileName fileName, IFileModelProvider modelProvider, Action action, FileUpdateOptions options = FileUpdateOptions.None) where T : class + { + OpenedFile file = CreateOpenedFile(fileName); + try { + T model = file.GetModel(modelProvider); + action(model); + if ((options & FileUpdateOptions.SaveToDisk) == FileUpdateOptions.SaveToDisk) { + file.SaveToDisk(); + } + if (!IsOpen(fileName) && file.IsDirty) { + // The file is not open in a view, but we need to store our changes somewhere: + if ((options & FileUpdateOptions.OpenViewIfNoneExists) != 0) { + OpenFile(fileName, false); + } else { + file.SaveToDisk(); + } + } + } finally { + file.ReleaseReference(); + } + } #endregion #region CheckFileName @@ -349,7 +358,7 @@ public LoadFileWrapper(IDisplayBinding binding, bool switchToOpenedView) public void Invoke(FileName fileName) { - OpenedFile file = SD.FileService.GetOrCreateOpenedFile(fileName); + OpenedFile file = SD.FileService.CreateOpenedFile(fileName); try { IViewContent newContent = binding.CreateContentForFile(file); if (newContent != null) { @@ -357,7 +366,7 @@ public void Invoke(FileName fileName) SD.Workbench.ShowView(newContent, switchToOpenedView); } } finally { - file.CloseIfAllViewsClosed(); + file.ReleaseReference(); } } } @@ -383,18 +392,20 @@ public IViewContent NewFile(string defaultName, byte[] content) binding = new ErrorFallbackBinding("Can't create display binding for file " + defaultName); } OpenedFile file = CreateUntitledOpenedFile(defaultName, content); - - IViewContent newContent = binding.CreateContentForFile(file); - if (newContent == null) { - LoggingService.Warn("Created view content was null - DefaultName:" + defaultName); - file.CloseIfAllViewsClosed(); - return null; + try { + IViewContent newContent = binding.CreateContentForFile(file); + if (newContent == null) { + LoggingService.Warn("Created view content was null - DefaultName:" + defaultName); + return null; + } + + displayBindingService.AttachSubWindows(newContent, false); + + SD.Workbench.ShowView(newContent); + return newContent; + } finally { + file.ReleaseReference(); } - - displayBindingService.AttachSubWindows(newContent, false); - - SD.Workbench.ShowView(newContent); - return newContent; } /// diff --git a/src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs b/src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs index 7f4e8113b2a..9b0e3555849 100644 --- a/src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs +++ b/src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs @@ -42,135 +42,44 @@ internal FileServiceOpenedFile(FileService fileService, FileName fileName) { this.fileService = fileService; this.FileName = fileName; - IsUntitled = false; fileChangeWatcher = new FileChangeWatcher(this); } - internal FileServiceOpenedFile(FileService fileService, byte[] fileData) + protected override void UnloadFile() { - this.fileService = fileService; - this.FileName = null; - SetData(fileData); - IsUntitled = true; - MakeDirty(); - fileChangeWatcher = new FileChangeWatcher(this); - } - - /// - /// Gets the list of view contents registered with this opened file. - /// - public override IList RegisteredViewContents { - get { return registeredViews.AsReadOnly(); } - } - - public override void ForceInitializeView(IViewContent view) - { - if (view == null) - throw new ArgumentNullException("view"); - if (!registeredViews.Contains(view)) - throw new ArgumentException("registeredViews must contain view"); + bool wasDirty = this.IsDirty; + fileService.OpenedFileClosed(this); + base.UnloadFile(); - base.ForceInitializeView(view); - } - - public override void RegisterView(IViewContent view) - { - if (view == null) - throw new ArgumentNullException("view"); - if (registeredViews.Contains(view)) - throw new ArgumentException("registeredViews already contains view"); - - registeredViews.Add(view); - - if (SD.Workbench != null) { - SD.Workbench.ActiveViewContentChanged += WorkbenchActiveViewContentChanged; - if (SD.Workbench.ActiveViewContent == view) { - SwitchedToView(view); - } - } - #if DEBUG - view.Disposed += ViewDisposed; - #endif - } - - public override void UnregisterView(IViewContent view) - { - if (view == null) - throw new ArgumentNullException("view"); - Debug.Assert(registeredViews.Contains(view)); + //FileClosed(this, EventArgs.Empty); - if (SD.Workbench != null) { - SD.Workbench.ActiveViewContentChanged -= WorkbenchActiveViewContentChanged; + if (fileChangeWatcher != null) { + fileChangeWatcher.Dispose(); + fileChangeWatcher = null; } - #if DEBUG - view.Disposed -= ViewDisposed; - #endif - registeredViews.Remove(view); - if (registeredViews.Count > 0) { - if (currentView == view) { - SaveCurrentView(); - currentView = null; - } - } else { - // all views to the file were closed - CloseIfAllViewsClosed(); + if (wasDirty) { + // We discarded some information when closing the file, + // so we need to re-parse it. + if (SD.FileSystem.FileExists(this.FileName)) + SD.ParserService.ParseAsync(this.FileName).FireAndForget(); + else + SD.ParserService.ClearParseInformation(this.FileName); } } - public override void CloseIfAllViewsClosed() - { - if (registeredViews.Count == 0) { - bool wasDirty = this.IsDirty; - fileService.OpenedFileClosed(this); - - FileClosed(this, EventArgs.Empty); - - if (fileChangeWatcher != null) { - fileChangeWatcher.Dispose(); - fileChangeWatcher = null; - } - - if (wasDirty) { - // We discarded some information when closing the file, - // so we need to re-parse it. - if (File.Exists(this.FileName)) - SD.ParserService.ParseAsync(this.FileName).FireAndForget(); - else - SD.ParserService.ClearParseInformation(this.FileName); - } - } - } - - #if DEBUG - void ViewDisposed(object sender, EventArgs e) - { - Debug.Fail("View was disposed while still registered with OpenedFile!"); - } - #endif - - void WorkbenchActiveViewContentChanged(object sender, EventArgs e) - { - IViewContent newView = SD.Workbench.ActiveViewContent; - - if (!registeredViews.Contains(newView)) - return; - - SwitchedToView(newView); - } - - public override void SaveToDisk() + public override void SaveToDisk(FileName fileName) { try { if (fileChangeWatcher != null) fileChangeWatcher.Enabled = false; - base.SaveToDisk(); + base.SaveToDisk(fileName); } finally { if (fileChangeWatcher != null) fileChangeWatcher.Enabled = true; } } - public override event EventHandler FileClosed = delegate {}; + //public override event EventHandler FileClosed = delegate {}; } } From 6a6c8a645d92f99c871bfd2f045197c9734cc85d Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 2 Feb 2014 18:34:56 +0100 Subject: [PATCH 03/13] Adjust IViewContent to OpenedFile models. --- .../Project/Src/Gui/PackageFilesView.cs | 16 ++- .../ILSpyAddIn/DecompiledViewContent.cs | 16 +-- .../Project/ICSharpCode.SharpDevelop.csproj | 2 - .../Src/Gui/Dialogs/ProjectOptionsView.cs | 24 ++-- .../Workbench/AbstractSecondaryViewContent.cs | 117 ------------------ .../Project/Workbench/AbstractViewContent.cs | 40 ------ .../AbstractViewContentHandlingLoadErrors.cs | 33 +---- .../AbstractViewContentWithoutFile.cs | 75 ----------- .../Base/Project/Workbench/IViewContent.cs | 43 ------- 9 files changed, 32 insertions(+), 334 deletions(-) delete mode 100644 src/Main/Base/Project/Workbench/AbstractSecondaryViewContent.cs delete mode 100644 src/Main/Base/Project/Workbench/AbstractViewContentWithoutFile.cs diff --git a/src/AddIns/BackendBindings/WixBinding/Project/Src/Gui/PackageFilesView.cs b/src/AddIns/BackendBindings/WixBinding/Project/Src/Gui/PackageFilesView.cs index cd1a51439b8..0c4101613b0 100644 --- a/src/AddIns/BackendBindings/WixBinding/Project/Src/Gui/PackageFilesView.cs +++ b/src/AddIns/BackendBindings/WixBinding/Project/Src/Gui/PackageFilesView.cs @@ -29,7 +29,7 @@ namespace ICSharpCode.WixBinding /// /// Displays the setup package files. /// - public class PackageFilesView : AbstractViewContentWithoutFile, IWixDocumentWriter + public class PackageFilesView : AbstractViewContent, IWixDocumentWriter, ICustomizedCommands { IWixPackageFilesControl packageFilesControl; WixProject project; @@ -88,9 +88,17 @@ public bool IsForProject(WixProject project) { return this.project == project; } + + bool ICustomizedCommands.SaveCommand() + { + Save(); + return true; + } - public override void Load() + bool ICustomizedCommands.SaveAsCommand() { + Save(); + return true; } public override void Save() @@ -98,6 +106,10 @@ public override void Save() packageFilesControl.Save(); } + public override bool IsViewOnly { + get { return false; } + } + public override bool IsDirty { get { return packageFilesControl.IsDirty; } } diff --git a/src/AddIns/DisplayBindings/ILSpyAddIn/DecompiledViewContent.cs b/src/AddIns/DisplayBindings/ILSpyAddIn/DecompiledViewContent.cs index ad9818dcf39..85fac3f2526 100644 --- a/src/AddIns/DisplayBindings/ILSpyAddIn/DecompiledViewContent.cs +++ b/src/AddIns/DisplayBindings/ILSpyAddIn/DecompiledViewContent.cs @@ -39,7 +39,7 @@ namespace ICSharpCode.ILSpyAddIn /// /// Hosts a decompiled type. /// - class DecompiledViewContent : AbstractViewContentWithoutFile + class DecompiledViewContent : AbstractViewContent { /// /// Entity to jump to once decompilation has finished. @@ -102,20 +102,6 @@ public override void Dispose() } #endregion - #region Load/Save - public override void Load() - { - // nothing to do... - } - - public override void Save() - { - if (!decompilationFinished) - return; - // TODO: show Save As dialog to allow the user to save the decompiled file - } - #endregion - public override INavigationPoint BuildNavPoint() { return codeEditor.BuildNavPoint(); diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 3dd360490ff..67808456e17 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -350,10 +350,8 @@ - - diff --git a/src/Main/Base/Project/Src/Gui/Dialogs/ProjectOptionsView.cs b/src/Main/Base/Project/Src/Gui/Dialogs/ProjectOptionsView.cs index c5f8ff0ef97..2e304d313ae 100644 --- a/src/Main/Base/Project/Src/Gui/Dialogs/ProjectOptionsView.cs +++ b/src/Main/Base/Project/Src/Gui/Dialogs/ProjectOptionsView.cs @@ -30,7 +30,7 @@ namespace ICSharpCode.SharpDevelop.Project.Dialogs /// /// Option view for project options. /// - public class ProjectOptionsView : AbstractViewContentWithoutFile + public class ProjectOptionsView : AbstractViewContent, ICustomizedCommands { List descriptors = new List(); TabbedOptions tabControl = new TabbedOptions(); @@ -69,14 +69,11 @@ public override bool IsDirty { get { return tabControl.IsDirty; } } - public override void Load() - { - foreach (IOptionPanel op in tabControl.OptionPanels) { - op.LoadOptions(); - } + public override bool IsViewOnly { + get { return false; } } - public override void Save() + public bool Save() { try { foreach (IOptionPanel op in tabControl.OptionPanels) { @@ -84,9 +81,20 @@ public override void Save() } } catch (Exception ex) { MessageService.ShowException(ex, "Error saving project options panel"); - return; + return false; } project.Save(); + return true; + } + + bool ICustomizedCommands.SaveCommand() + { + return Save(); + } + + bool ICustomizedCommands.SaveAsCommand() + { + return Save(); } public override void Dispose() diff --git a/src/Main/Base/Project/Workbench/AbstractSecondaryViewContent.cs b/src/Main/Base/Project/Workbench/AbstractSecondaryViewContent.cs deleted file mode 100644 index 6956cacebf7..00000000000 --- a/src/Main/Base/Project/Workbench/AbstractSecondaryViewContent.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.IO; - -namespace ICSharpCode.SharpDevelop.Workbench -{ - /// - /// A view content that is based on another (primary) view content. - /// Loading works by getting the content from the primary view content, - /// saving by merging it back to the primary view content and then saving the - /// primary view content. - /// AbstractSecondaryViewContent implements the [Support]Switch* methods to make switching - /// between the primary view content and the secondary possible without having to save/load the - /// primary view content. - /// - public abstract class AbstractSecondaryViewContent : AbstractViewContent - { - readonly IViewContent primaryViewContent; - readonly OpenedFile primaryFile; - - public IViewContent PrimaryViewContent { - get { return primaryViewContent; } - } - - public sealed override OpenedFile PrimaryFile { - get { return primaryFile; } - } - - protected AbstractSecondaryViewContent(IViewContent primaryViewContent) - { - if (primaryViewContent == null) - throw new ArgumentNullException("primaryViewContent"); - if (primaryViewContent.PrimaryFile == null) - throw new ArgumentException("primaryViewContent.PrimaryFile must not be null"); - this.primaryViewContent = primaryViewContent; - - primaryFile = primaryViewContent.PrimaryFile; - this.Files.Add(primaryFile); - } - - public override void Load(OpenedFile file, Stream stream) - { - if (file != this.PrimaryFile) - throw new ArgumentException("file must be the primary file of the primary view content, override Load() to handle other files"); - primaryViewContent.Load(file, stream); - LoadFromPrimary(); - } - - public override void Save(OpenedFile file, Stream stream) - { - if (file != this.PrimaryFile) - throw new ArgumentException("file must be the primary file of the primary view content, override Save() to handle other files"); - SaveToPrimary(); - primaryViewContent.Save(file, stream); - } - - public override bool SupportsSwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView) - { - if (file == this.PrimaryFile) - return newView.SupportsSwitchToThisWithoutSaveLoad(file, primaryViewContent); - else - return base.SupportsSwitchFromThisWithoutSaveLoad(file, newView); - } - - public override bool SupportsSwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView) - { - if (file == this.PrimaryFile) - return oldView.SupportsSwitchToThisWithoutSaveLoad(file, primaryViewContent); - else - return base.SupportsSwitchFromThisWithoutSaveLoad(file, oldView); - } - - public override void SwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView) - { - if (file == this.PrimaryFile && this != newView) { - SaveToPrimary(); - primaryViewContent.SwitchFromThisWithoutSaveLoad(file, newView); - } - } - - public override void SwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView) - { - if (file == this.PrimaryFile && oldView != this) { - primaryViewContent.SwitchToThisWithoutSaveLoad(file, oldView); - LoadFromPrimary(); - } - } - - protected abstract void LoadFromPrimary(); - protected abstract void SaveToPrimary(); - - /// - /// Gets the list of sibling secondary view contents. - /// - public override ICollection SecondaryViewContents { - get { return primaryViewContent.SecondaryViewContents; } - } - } -} diff --git a/src/Main/Base/Project/Workbench/AbstractViewContent.cs b/src/Main/Base/Project/Workbench/AbstractViewContent.cs index bc126f0bb14..a2b4a55490d 100644 --- a/src/Main/Base/Project/Workbench/AbstractViewContent.cs +++ b/src/Main/Base/Project/Workbench/AbstractViewContent.cs @@ -196,38 +196,6 @@ public virtual ICollection SecondaryViewContents { return secondaryViewContentCollection; } } - - /// - /// Gets switching without a Save/Load cycle for is supported - /// when switching from this view content to . - /// - public virtual bool SupportsSwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView) - { - return newView == this; - } - - /// - /// Gets switching without a Save/Load cycle for is supported - /// when switching from to this view content. - /// - public virtual bool SupportsSwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView) - { - return oldView == this; - } - - /// - /// Executes an action before switching from this view content to the new view content. - /// - public virtual void SwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView) - { - } - - /// - /// Executes an action before switching from the old view content to this view content. - /// - public virtual void SwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView) - { - } #endregion #region Files @@ -573,14 +541,6 @@ protected set { #endregion - public virtual void Save(OpenedFile file, Stream stream) - { - } - - public virtual void Load(OpenedFile file, Stream stream) - { - } - public virtual INavigationPoint BuildNavPoint() { return null; diff --git a/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs b/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs index 37e20e3b2e2..43966bc8f3d 100644 --- a/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs +++ b/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs @@ -94,7 +94,7 @@ public LoadError(Exception exception, Stream stream) TextBox errorTextBox; - void ShowError(Exception ex) + protected void ShowError(Exception ex) { if (errorTextBox == null) { errorTextBox = new TextBox(); @@ -114,36 +114,5 @@ void ShowError(Exception ex) protected virtual string LoadErrorHeaderText { get { return String.Empty; } } - - public override sealed void Load(OpenedFile file, Stream stream) - { - try { - LoadInternal(file, new UnclosableStream(stream)); - if (errorList.Count > 0) { - errorList.Remove(file); - if (errorList.Count == 0) { - SD.WinForms.SetContent(contentControl, userContent, this); - } else { - ShowError(errorList.Values.First().exception); - } - } - } catch (Exception ex) { - errorList[file] = new LoadError(ex, stream); - ShowError(ex); - } - } - - public override sealed void Save(OpenedFile file, Stream stream) - { - if (errorList.ContainsKey(file)) { - byte[] data = errorList[file].fileData; - stream.Write(data, 0, data.Length); - } else { - SaveInternal(file, stream); - } - } - - protected abstract void LoadInternal(OpenedFile file, Stream stream); - protected abstract void SaveInternal(OpenedFile file, Stream stream); } } diff --git a/src/Main/Base/Project/Workbench/AbstractViewContentWithoutFile.cs b/src/Main/Base/Project/Workbench/AbstractViewContentWithoutFile.cs deleted file mode 100644 index f668ab63c58..00000000000 --- a/src/Main/Base/Project/Workbench/AbstractViewContentWithoutFile.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; - -namespace ICSharpCode.SharpDevelop.Workbench -{ - /// - /// Base class for view contents that are not based on a file, but can be loaded/saved. - /// If you need a view content that cannot save (IsViewOnly==true), you should instead derive - /// directly from AbstractViewContent and leave the Files collection empty. - /// - /// AbstractViewContentWithoutFile implements ICustomizedCommands to make "File > Save" work - /// without requiring an OpenedFile. "File > Save as" will also cause Save() to be called, without - /// showing a "Save as" dialog. - /// - public abstract class AbstractViewContentWithoutFile : AbstractViewContent, ICustomizedCommands - { - public override bool IsViewOnly { - get { return false; } - } - - [Obsolete("AbstractViewContentWithoutFile.PrimaryFile is always null")] - public sealed override OpenedFile PrimaryFile { get { return null; } } - - [Obsolete("This method is not supported on an AbstractViewContentWithoutFile")] - public sealed override void Load(OpenedFile file, System.IO.Stream stream) - { - throw new NotSupportedException(); - } - - [Obsolete("This method is not supported on an AbstractViewContentWithoutFile")] - public sealed override void Save(OpenedFile file, System.IO.Stream stream) - { - throw new NotSupportedException(); - } - - /// - /// Load the view content. - /// - public abstract void Load(); - - /// - /// Save the view content. - /// - public abstract void Save(); - - bool ICustomizedCommands.SaveCommand() - { - Save(); - return true; - } - - bool ICustomizedCommands.SaveAsCommand() - { - Save(); - return true; - } - } -} diff --git a/src/Main/Base/Project/Workbench/IViewContent.cs b/src/Main/Base/Project/Workbench/IViewContent.cs index 37d158bef5e..3e7f394a1f9 100644 --- a/src/Main/Base/Project/Workbench/IViewContent.cs +++ b/src/Main/Base/Project/Workbench/IViewContent.cs @@ -96,26 +96,6 @@ string InfoTip { /// event EventHandler InfoTipChanged; - /// - /// Saves the content to the location fileName - /// - /// - /// When the user switches between multiple views editing the same file, a view - /// change will trigger one view content to save that file into a memory stream - /// and the other view content will load the file from that memory stream. - /// - void Save(OpenedFile file, Stream stream); - - /// - /// Load or reload the content of the specified file from the stream. - /// - /// - /// When the user switches between multiple views editing the same file, a view - /// change will trigger one view content to save that file into a memory stream - /// and the other view content will load the file from that memory stream. - /// - void Load(OpenedFile file, Stream stream); - /// /// Gets the list of files that are being edited using this view content. /// The returned collection usually is read-only. @@ -161,29 +141,6 @@ string InfoTip { /// Gets the collection that stores the secondary view contents. /// ICollection SecondaryViewContents { get; } - - - /// - /// Gets switching without a Save/Load cycle for is supported - /// when switching from this view content to . - /// - bool SupportsSwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView); - - /// - /// Gets switching without a Save/Load cycle for is supported - /// when switching from to this view content. - /// - bool SupportsSwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView); - - /// - /// Executes an action before switching from this view content to the new view content. - /// - void SwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView); - - /// - /// Executes an action before switching from the old view content to this view content. - /// - void SwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView); #endregion } } From f59576e5452d9dd0dc3180f56e2181b94d22cbbe Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 10 Feb 2014 20:24:41 +0100 Subject: [PATCH 04/13] Port WpfWorkbench.CheckRemovedOrReplacedFile to new OpenedFile API. --- SharpDevelop.Tests.sln | 6 ++++-- src/Main/SharpDevelop/SharpDevelop.csproj | 1 - src/Main/SharpDevelop/Workbench/WpfWorkbench.cs | 14 ++++---------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/SharpDevelop.Tests.sln b/SharpDevelop.Tests.sln index 95241db5bd7..172c75e9ffe 100644 --- a/SharpDevelop.Tests.sln +++ b/SharpDevelop.Tests.sln @@ -1,7 +1,9 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 # SharpDevelop 5.0 +VisualStudioVersion = 12.0.20827.3 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Main", "Main", "{256F5C28-532C-44C0-8AB8-D8EC5E492E01}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.SharpDevelop.BuildWorker35", "src\Main\ICSharpCode.SharpDevelop.BuildWorker35\ICSharpCode.SharpDevelop.BuildWorker35.csproj", "{B5F54272-49F0-40DB-845A-8D837875D3BA}" diff --git a/src/Main/SharpDevelop/SharpDevelop.csproj b/src/Main/SharpDevelop/SharpDevelop.csproj index 06643ba19c6..591fcf9bf07 100644 --- a/src/Main/SharpDevelop/SharpDevelop.csproj +++ b/src/Main/SharpDevelop/SharpDevelop.csproj @@ -329,7 +329,6 @@ - diff --git a/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs b/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs index 0d1ccdde53f..6683d06a7bf 100644 --- a/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs +++ b/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs @@ -205,16 +205,10 @@ void SetProjectTitle(object sender, Project.ProjectEventArgs e) void CheckRemovedOrReplacedFile(object sender, FileEventArgs e) { - foreach (OpenedFile file in SD.FileService.OpenedFiles) { - if (FileUtility.IsBaseDirectory(e.FileName, file.FileName)) { - foreach (IViewContent content in file.RegisteredViewContents.ToArray()) { - // content.WorkbenchWindow can be null if multiple view contents - // were in the same WorkbenchWindow and both should be closed - // (e.g. Windows Forms Designer, Subversion History View) - if (content.WorkbenchWindow != null) { - content.WorkbenchWindow.CloseWindow(true); - } - } + foreach (var workbenchWindow in WorkbenchWindowCollection.ToArray()) { + var files = workbenchWindow.ViewContents.SelectMany(vc => vc.Files); + if (files.Any(file => FileUtility.IsBaseDirectory(e.FileName, file.FileName))) { + workbenchWindow.CloseWindow(true); } } Editor.PermanentAnchorService.FileDeleted(e); From 9b9089d9b4025c857223e9c5d455682b689d4fcc Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 10 Feb 2014 20:24:52 +0100 Subject: [PATCH 05/13] Fix crash when no display bindings are available. --- .../Base/Project/Workbench/DisplayBinding/IDisplayBinding.cs | 3 +++ src/Main/SharpDevelop/Project/ProjectService.cs | 2 +- .../Workbench/DisplayBinding/AutoDetectDisplayBinding.cs | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Main/Base/Project/Workbench/DisplayBinding/IDisplayBinding.cs b/src/Main/Base/Project/Workbench/DisplayBinding/IDisplayBinding.cs index 02ff22c433b..e46f3f1a79c 100644 --- a/src/Main/Base/Project/Workbench/DisplayBinding/IDisplayBinding.cs +++ b/src/Main/Base/Project/Workbench/DisplayBinding/IDisplayBinding.cs @@ -50,7 +50,10 @@ public interface IDisplayBinding /// /// /// A newly created IViewContent object. + /// May return null if the view content could not be created. /// + /// Error loading the file + /// Error loading the file (invalid file format) IViewContent CreateContentForFile(OpenedFile file); } } diff --git a/src/Main/SharpDevelop/Project/ProjectService.cs b/src/Main/SharpDevelop/Project/ProjectService.cs index 795b04995bd..7af54912fd4 100644 --- a/src/Main/SharpDevelop/Project/ProjectService.cs +++ b/src/Main/SharpDevelop/Project/ProjectService.cs @@ -36,7 +36,7 @@ public SDProjectService() { allSolutions = new NullSafeSimpleModelCollection(); allProjects = allSolutions.SelectMany(s => s.Projects); - projectBindings = SD.AddInTree.BuildItems("/SharpDevelop/Workbench/ProjectBindings", null); + projectBindings = SD.AddInTree.BuildItems("/SharpDevelop/Workbench/ProjectBindings", null, false); targetFrameworks = SD.AddInTree.BuildItems("/SharpDevelop/TargetFrameworks", null); SD.GetFutureService().ContinueWith(t => t.Result.ActiveViewContentChanged += ActiveViewContentChanged).FireAndForget(); diff --git a/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs b/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs index a432fbba1c2..bb2ddfb4039 100644 --- a/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs +++ b/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs @@ -68,7 +68,7 @@ public IViewContent CreateContentForFile(OpenedFile file) } if (bestMatch == null) - throw new InvalidOperationException(); + return null; return bestMatch.Binding.CreateContentForFile(file); } From 157a745ba04a9cbdfce7144726fd3fae15f57943 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 10 Feb 2014 21:30:03 +0100 Subject: [PATCH 06/13] Add IsDirty logic and CanSaveWithEncoding() to TextDocumentFileModelProvider. --- .../Src/AvalonEditViewContent.cs | 57 ----------- .../AvalonEdit.AddIn/Src/CodeEditor.cs | 48 --------- .../Base/Project/Src/Commands/FileCommands.cs | 4 +- .../Workbench/File/BinaryFileModelProvider.cs | 6 +- .../Workbench/File/DocumentFileModelInfo.cs | 5 + .../Workbench/File/IFileModelProvider.cs | 19 +++- .../Project/Workbench/File/IFileService.cs | 4 + .../Base/Project/Workbench/File/OpenedFile.cs | 42 ++++---- .../File/TextDocumentFileModelProvider.cs | 98 ++++++++++++++++--- .../File/XDocumentFileModelProvider.cs | 4 +- .../Templates/File/FileTemplateImpl.cs | 2 +- .../SharpDevelop/Workbench/FileService.cs | 8 +- .../Workbench/FileServiceOpenedFile.cs | 6 +- 13 files changed, 153 insertions(+), 150 deletions(-) diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs index e243dd30099..0e2eaed2eed 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs @@ -77,26 +77,6 @@ bool IsKnownFileExtension(string filetype) return ProjectService.GetFileFilters().Any(f => f.ContainsExtension(filetype)) || IconService.HasImageForFile(filetype); } - - void codeEditor_Document_UndoStack_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (!isLoading) - PrimaryFile.IsDirty = !codeEditor.Document.UndoStack.IsOriginalFile; - } - - void PrimaryFile_IsDirtyChanged(object sender, EventArgs e) - { - var document = codeEditor.Document; - if (document != null) { - var undoStack = document.UndoStack; - if (this.PrimaryFile.IsDirty) { - if (undoStack.IsOriginalFile) - undoStack.DiscardOriginalFileMarker(); - } else { - undoStack.MarkAsOriginalFile(); - } - } - } public override object Control { get { return codeEditor; } @@ -106,41 +86,6 @@ public override object InitiallyFocusedControl { get { return codeEditor.PrimaryTextEditor.TextArea; } } - public override void Save(OpenedFile file, Stream stream) - { - if (file != PrimaryFile) - return; - - if (codeEditor.CanSaveWithCurrentEncoding()) { - codeEditor.Save(stream); - } else { - int r = MessageService.ShowCustomDialog( - "${res:Dialog.Options.IDEOptions.TextEditor.General.FontGroupBox.FileEncodingGroupBox}", - StringParser.Parse("${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss}", - new StringTagPair("encoding", codeEditor.Encoding.EncodingName)), - 0, -1, - "${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.UseUTF8}", - "${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.Continue}"); - if (r == 1) { - // continue saving with data loss - MemoryStream ms = new MemoryStream(); - codeEditor.Save(ms); - ms.Position = 0; - ms.WriteTo(stream); - ms.Position = 0; - // Read back the version we just saved to show the data loss to the user (he'll be able to press Undo). - using (StreamReader reader = new StreamReader(ms, codeEditor.Encoding, false)) { - codeEditor.Document.Text = reader.ReadToEnd(); - } - return; - } else { - // unfortunately we don't support cancel within IViewContent.Save, so we'll use the safe choice of UTF-8 instead - codeEditor.Encoding = System.Text.Encoding.UTF8; - codeEditor.Save(stream); - } - } - } - bool isLoading; public override void Load(OpenedFile file, Stream stream) @@ -261,8 +206,6 @@ public override void Dispose() { if (trackedFeature != null) trackedFeature.EndTracking(); - if (PrimaryFile != null) - this.PrimaryFile.IsDirtyChanged -= PrimaryFile_IsDirtyChanged; base.Dispose(); BookmarksDetach(); codeEditor.Dispose(); diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs index 0c14792c7cf..e96ceca130e 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs @@ -281,29 +281,6 @@ void TextAreaMouseRightButtonDown(object sender, MouseButtonEventArgs e) } } - /// - /// Use fixed encoding for loading. - /// - public bool UseFixedEncoding { get; set; } - - public Encoding Encoding { - get { return primaryTextEditor.Encoding; } - set { primaryTextEditor.Encoding = value; } - } - - /// - /// Gets if the document can be saved with the current encoding without losing data. - /// - public bool CanSaveWithCurrentEncoding() - { - Encoding encoding = this.Encoding; - if (encoding == null || FileReader.IsUnicode(encoding)) - return true; - // not a unicode codepage - string text = document.Text; - return encoding.GetString(encoding.GetBytes(text)) == text; - } - // always use primary text editor for loading/saving // (the file encoding is stored only there) public void Load(Stream stream) @@ -325,31 +302,6 @@ public void Load(Stream stream) NewLineConsistencyCheck.StartConsistencyCheck(this); } - bool documentFirstLoad = true; - bool clearUndoStackOnSwitch = true; - - /// - /// Gets/Sets whether to clear the undo stack when reloading the document. - /// The default is true. - /// http://community.sharpdevelop.net/forums/t/15816.aspx - /// - public bool ClearUndoStackOnSwitch { - get { return clearUndoStackOnSwitch; } - set { clearUndoStackOnSwitch = value; } - } - - void ReloadDocument(TextDocument document, string newContent) - { - var diff = new MyersDiffAlgorithm(new StringSequence(document.Text), new StringSequence(newContent)); - document.Replace(0, document.TextLength, newContent, diff.GetEdits().ToOffsetChangeMap()); - - if (this.ClearUndoStackOnSwitch || documentFirstLoad) - document.UndoStack.ClearAll(); - - if (documentFirstLoad) - documentFirstLoad = false; - } - public event EventHandler LoadedFileContent; public void Save(Stream stream) diff --git a/src/Main/Base/Project/Src/Commands/FileCommands.cs b/src/Main/Base/Project/Src/Commands/FileCommands.cs index ed60c7c5f7c..c04dbf66373 100644 --- a/src/Main/Base/Project/Src/Commands/FileCommands.cs +++ b/src/Main/Base/Project/Src/Commands/FileCommands.cs @@ -110,7 +110,7 @@ public static void Save(OpenedFile file) if (File.Exists(file.FileName) && (File.GetAttributes(file.FileName) & attr) != 0) { SaveFileAs.Save(file); } else { - FileUtility.ObservedSave(new NamedFileOperationDelegate(file.SaveToDisk), file.FileName, FileErrorPolicy.ProvideAlternative); + FileUtility.ObservedSave(fn => file.SaveToDisk(fn, FileSaveOptions.AllowUserInteraction), file.FileName, FileErrorPolicy.ProvideAlternative); } } } @@ -194,7 +194,7 @@ internal static void Save(OpenedFile file) if (!FileService.CheckFileName(fileName)) { return; } - if (FileUtility.ObservedSave(new NamedFileOperationDelegate(file.SaveToDisk), fileName) == FileOperationResult.OK) { + if (FileUtility.ObservedSave(fn => file.SaveToDisk(fn, FileSaveOptions.AllowUserInteraction), fileName) == FileOperationResult.OK) { SD.FileService.RecentOpen.AddRecentFile(fileName); MessageService.ShowMessage(fileName, "${res:ICSharpCode.SharpDevelop.Commands.SaveFile.FileSaved}"); } diff --git a/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs index 78d391b4fb6..989826febdb 100644 --- a/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs @@ -29,13 +29,13 @@ public IBinaryFileModel Load(OpenedFile file) return new OnDiskBinaryModel(file); } - public void Save(OpenedFile file, IBinaryFileModel model) + public void Save(OpenedFile file, IBinaryFileModel model, FileSaveOptions options) { - SaveCopyAs(file, model, file.FileName); + SaveCopyAs(file, model, file.FileName, options); file.ReplaceModel(this, model, ReplaceModelMode.SetAsValid); // remove dirty flag } - public void SaveCopyAs(OpenedFile file, IBinaryFileModel model, FileName outputFileName) + public void SaveCopyAs(OpenedFile file, IBinaryFileModel model, FileName outputFileName, FileSaveOptions options) { var onDisk = model as OnDiskBinaryModel; if (onDisk != null) { diff --git a/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs b/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs index 7d5964cb10c..9fcd51bf15d 100644 --- a/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs +++ b/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs @@ -16,6 +16,11 @@ public DocumentFileModelInfo() this.Encoding = SD.FileService.DefaultFileEncoding; } + /// + /// Gets whether the model is (re)loading. + /// + internal bool isLoading; + /// /// Gets whether the model is stale. /// diff --git a/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs index 194875ec616..78e84362374 100644 --- a/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs @@ -6,6 +6,21 @@ namespace ICSharpCode.SharpDevelop.Workbench { + [Flags] + public enum FileSaveOptions + { + None = 0, + /// + /// Allows displaying modal dialogs during the save operation. + /// For example, the text document file model may prompt the user on data loss due to the choice of encoding. + /// + AllowUserInteraction = 1, + /// + /// The save operation was triggered by a GetModel() call. + /// + SaveForGetModel = 2, + } + /// /// Interface that deals with loading and saving OpenedFile model instances. /// Pre-defined model providers are available in the class. @@ -31,7 +46,7 @@ public interface IFileModelProvider where T : class /// lower-level models, this is done by marking that lower-level model as dirty. /// In other cases, the provider may have to explicitly remove the dirty flag from the model. /// - void Save(OpenedFile file, T model); + void Save(OpenedFile file, T model, FileSaveOptions options); /// /// Saves a file model to disk. This method may by-pass lower-level models (such as FileModels.Binary) and directly write to disk. @@ -39,7 +54,7 @@ public interface IFileModelProvider where T : class /// However, transferring the dirty flag to a lower-level model is allowed. /// /// Error saving the file. - void SaveCopyAs(OpenedFile file, T model, FileName outputFileName); + void SaveCopyAs(OpenedFile file, T model, FileName outputFileName, FileSaveOptions options); /// /// Gets whether this provider can load from the specified other provider. diff --git a/src/Main/Base/Project/Workbench/File/IFileService.cs b/src/Main/Base/Project/Workbench/File/IFileService.cs index ad5618296fd..97dcf960057 100644 --- a/src/Main/Base/Project/Workbench/File/IFileService.cs +++ b/src/Main/Base/Project/Workbench/File/IFileService.cs @@ -45,6 +45,10 @@ public enum FileUpdateOptions /// (in that case, other changes from the view may be saved as well). /// SaveToDisk = 2, + /// + /// Whether to allow user interaction in the model load+save operations. + /// + AllowUserInteraction = 4, } /// diff --git a/src/Main/Base/Project/Workbench/File/OpenedFile.cs b/src/Main/Base/Project/Workbench/File/OpenedFile.cs index a924e563c6f..5484f5f811e 100644 --- a/src/Main/Base/Project/Workbench/File/OpenedFile.cs +++ b/src/Main/Base/Project/Workbench/File/OpenedFile.cs @@ -40,6 +40,11 @@ public enum GetModelOptions /// Returns null if the model is not already loaded, or if it is stale and the AllowStale option isn't in use. /// DoNotLoad = 2, + /// + /// Allows showing modal dialogs during the GetModel() call. + /// For example, the previous model might ask the user details on how to save. + /// + AllowUserInteraction = 4, } /// @@ -73,8 +78,8 @@ abstract class ModelEntry public abstract object Provider { get; } - public abstract void Save(OpenedFile file); - public abstract void SaveCopyAs(OpenedFile file, FileName outputFileName); + public abstract void Save(OpenedFile file, FileSaveOptions options); + public abstract void SaveCopyAs(OpenedFile file, FileName outputFileName, FileSaveOptions options); public abstract void NotifyRename(OpenedFile file, FileName oldName, FileName newName); public abstract void NotifyStale(OpenedFile file); public abstract void NotifyUnloaded(OpenedFile file); @@ -95,14 +100,14 @@ public ModelEntry(IFileModelProvider provider, T model) public override object Provider { get { return provider; } } - public override void Save(OpenedFile file) + public override void Save(OpenedFile file, FileSaveOptions options) { - provider.Save(file, Model); + provider.Save(file, Model, options); } - public override void SaveCopyAs(OpenedFile file, FileName outputFileName) + public override void SaveCopyAs(OpenedFile file, FileName outputFileName, FileSaveOptions options) { - provider.SaveCopyAs(file, Model, outputFileName); + provider.SaveCopyAs(file, Model, outputFileName, options); } public override void NotifyRename(OpenedFile file, FileName oldName, FileName newName) @@ -221,25 +226,25 @@ public void ReloadFromDisk() /// /// If the file is saved successfully, the dirty flag will be cleared (the dirty model becomes valid instead). /// The file is untitled. - public void SaveToDisk() + public void SaveToDisk(FileSaveOptions options) { CheckDisposed(); if (IsUntitled) throw new InvalidOperationException("Cannot save an untitled file to disk."); - SaveToDisk(this.FileName); + SaveToDisk(this.FileName, options); } /// /// Changes the file name, and saves the file to disk. /// /// If the file is saved successfully, the dirty flag will be cleared (the dirty model becomes valid instead). - public virtual void SaveToDisk(FileName fileName) + public virtual void SaveToDisk(FileName fileName, FileSaveOptions options) { CheckDisposed(); bool safeSaving = SD.FileService.SaveUsingTemporaryFile && SD.FileSystem.FileExists(fileName); FileName saveAs = safeSaving ? FileName.Create(fileName + ".bak") : fileName; - SaveCopyTo(saveAs); + SaveCopyTo(saveAs, options); if (safeSaving) { DateTime creationTime = File.GetCreationTimeUtc(fileName); File.Delete(fileName); @@ -262,24 +267,23 @@ public virtual void SaveToDisk(FileName fileName) /// /// Saves a copy of the file to disk. Does not change the name of the OpenedFile to the specified file name, and does not reset the dirty flag. /// - public virtual void SaveCopyAs(FileName fileName) + public virtual void SaveCopyAs(FileName fileName, FileSaveOptions options) { CheckDisposed(); - SaveCopyTo(fileName); + SaveCopyTo(fileName, options); } - void SaveCopyTo(FileName outputFileName) + void SaveCopyTo(FileName outputFileName, FileSaveOptions options) { preventLoading = true; try { var entry = PickValidEntry(); if (entry != null) { - entry.SaveCopyAs(this, outputFileName); + entry.SaveCopyAs(this, outputFileName, options); } else if (outputFileName != this.FileName) { SD.FileSystem.CopyFile(this.FileName, outputFileName, true); } - } - finally { + } finally { preventLoading = false; } } @@ -323,7 +327,10 @@ public T GetModel(IFileModelProvider modelProvider, GetModelOptions option try { // Before we can load the requested model, save the dirty model (if necessary): while (dirtyEntry != null && dirtyEntry.NeedsSaveForLoadInto(modelProvider)) { - dirtyEntry.Save(this); + var saveOptions = FileSaveOptions.SaveForGetModel; + if ((options & GetModelOptions.AllowUserInteraction) != 0) + saveOptions |= FileSaveOptions.AllowUserInteraction; + dirtyEntry.Save(this, saveOptions); // re-fetch entry because it's possible that it was created/replaced/unloaded entry = GetEntry(modelProvider); // if the entry was made valid by the save operation, return it directly @@ -497,7 +504,6 @@ void CheckDisposed() /// /// Gets the reference count of the OpenedFile. - /// This is used for the IFileService.UpdateFileModel() implementation. /// public int ReferenceCount { get { return referenceCount; } diff --git a/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs index 6f78d2704b9..aa026e54fb4 100644 --- a/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs @@ -10,6 +10,7 @@ using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Utils; using ICSharpCode.Core; +using ICSharpCode.NRefactory.Editor; using ICSharpCode.SharpDevelop.Widgets.MyersDiff; namespace ICSharpCode.SharpDevelop.Workbench @@ -30,17 +31,46 @@ public TextDocument Load(OpenedFile file) if (document != null) { // Reload document var diff = new MyersDiffAlgorithm(new StringSequence(document.Text), new StringSequence(textContent)); - document.Replace(0, document.TextLength, textContent, ToOffsetChangeMap(diff.GetEdits())); - document.UndoStack.ClearAll(); - info.IsStale = false; + info.isLoading = true; + try { + document.Replace(0, document.TextLength, textContent, ToOffsetChangeMap(diff.GetEdits())); + document.UndoStack.ClearAll(); + info.IsStale = false; + } finally { + info.isLoading = false; + } } else { document = new TextDocument(textContent); - document.TextChanged += delegate { file.MakeDirty(this); }; + document.TextChanged += delegate { UpdateFileIsDirty(document, file, info); }; document.GetRequiredService().AddService(typeof(DocumentFileModelInfo), info); } + file.IsDirtyChanged += delegate { UpdateUndoStackIsOriginalFile(file, document); }; + UpdateUndoStackIsOriginalFile(file, document); return document; } + static void UpdateUndoStackIsOriginalFile(ICanBeDirty file, TextDocument document) + { + if (file.IsDirty) { + document.UndoStack.DiscardOriginalFileMarker(); + } else { + document.UndoStack.MarkAsOriginalFile(); + } + } + + void UpdateFileIsDirty(TextDocument document, OpenedFile file, DocumentFileModelInfo info) + { + if (!info.isLoading) { + // Set dirty flag on OpenedFile according to UndoStack.IsOriginalFile + if (document.UndoStack.IsOriginalFile && file.IsDirty) { + // reset dirty marker + file.ReplaceModel(this, document, ReplaceModelMode.SetAsValid); + } else if (!document.UndoStack.IsOriginalFile) { + file.MakeDirty(this); + } + } + } + OffsetChangeMap ToOffsetChangeMap(IEnumerable edits) { var map = new OffsetChangeMap(); @@ -57,27 +87,73 @@ OffsetChangeMap ToOffsetChangeMap(IEnumerable edits) return map; } - public void Save(OpenedFile file, TextDocument model) + public void Save(OpenedFile file, TextDocument model, FileSaveOptions options) { MemoryStream ms = new MemoryStream(); - SaveTo(ms, model); - file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray())); + SaveTo(ms, model, options); + file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray()), ReplaceModelMode.TransferDirty); } - public void SaveCopyAs(OpenedFile file, TextDocument model, FileName outputFileName) + public void SaveCopyAs(OpenedFile file, TextDocument model, FileName outputFileName, FileSaveOptions options) { using (Stream s = SD.FileSystem.OpenWrite(outputFileName)) { - SaveTo(s, model); + SaveTo(s, model, options); } } - static void SaveTo(Stream s, TextDocument model) + static void SaveTo(Stream stream, TextDocument model, FileSaveOptions options) { - using (StreamWriter w = new StreamWriter(s, model.GetFileModelInfo().Encoding)) { + var info = model.GetFileModelInfo(); + if (!CanSaveWithEncoding(model, info.Encoding)) { + if (ConfirmSaveWithDataLoss(info.Encoding, options)) { + // continue saving with data loss + MemoryStream ms = new MemoryStream(); + using (StreamWriter w = new StreamWriter(ms, info.Encoding)) { + model.WriteTextTo(w); + } + ms.Position = 0; + ms.WriteTo(stream); + ms.Position = 0; + // Read back the version we just saved to show the data loss to the user (he'll be able to press Undo). + using (StreamReader reader = new StreamReader(ms, info.Encoding, false)) { + model.Text = reader.ReadToEnd(); + } + return; + } else { + info.Encoding = new UTF8Encoding(false); + } + } + using (StreamWriter w = new StreamWriter(stream, info.Encoding)) { model.WriteTextTo(w); } } + /// + /// Gets if the document can be saved with the current encoding without losing data. + /// + static bool CanSaveWithEncoding(IDocument document, Encoding encoding) + { + if (encoding == null || FileReader.IsUnicode(encoding)) + return true; + // not a unicode codepage + string text = document.Text; + return encoding.GetString(encoding.GetBytes(text)) == text; + } + + static bool ConfirmSaveWithDataLoss(Encoding encoding, FileSaveOptions options) + { + if ((options & FileSaveOptions.AllowUserInteraction) == 0) + return false; // default to UTF-8 if we can't ask the user + int r = MessageService.ShowCustomDialog( + "${res:Dialog.Options.IDEOptions.TextEditor.General.FontGroupBox.FileEncodingGroupBox}", + StringParser.Parse("${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss}", + new StringTagPair("encoding", encoding.EncodingName)), + 0, -1, + "${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.UseUTF8}", + "${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.Continue}"); + return r == 1; + } + bool IFileModelProvider.CanLoadFrom(IFileModelProvider otherProvider) { return otherProvider == FileModels.Binary || FileModels.Binary.CanLoadFrom(otherProvider); diff --git a/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs index af35d07a849..081b6892a07 100644 --- a/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs @@ -22,14 +22,14 @@ public XDocument Load(OpenedFile file) return document; } - public void Save(OpenedFile file, XDocument model) + public void Save(OpenedFile file, XDocument model, FileSaveOptions options) { MemoryStream ms = new MemoryStream(); model.Save(ms, SaveOptions.DisableFormatting); file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray())); } - public void SaveCopyAs(OpenedFile file, XDocument model, FileName outputFileName) + public void SaveCopyAs(OpenedFile file, XDocument model, FileName outputFileName, FileSaveOptions options) { model.Save(outputFileName, SaveOptions.DisableFormatting); } diff --git a/src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs b/src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs index 5029626386f..e477d28ed5e 100644 --- a/src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs +++ b/src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs @@ -532,7 +532,7 @@ string SaveFile(FileDescriptionTemplate newFile, string content, string binaryFi file.ReplaceModel(FileModels.Binary, new BinaryFileModel(data)); Directory.CreateDirectory(Path.GetDirectoryName(fileName)); - file.SaveToDisk(); + file.SaveToDisk(FileSaveOptions.None); if (project != null) AddTemplateFileToProject(project, newFile, fileName); diff --git a/src/Main/SharpDevelop/Workbench/FileService.cs b/src/Main/SharpDevelop/Workbench/FileService.cs index 97d9073f6be..0f41d982140 100644 --- a/src/Main/SharpDevelop/Workbench/FileService.cs +++ b/src/Main/SharpDevelop/Workbench/FileService.cs @@ -256,19 +256,21 @@ internal void OpenedFileClosed(OpenedFile file) public void UpdateFileModel(FileName fileName, IFileModelProvider modelProvider, Action action, FileUpdateOptions options = FileUpdateOptions.None) where T : class { + bool interactive = (options & FileUpdateOptions.AllowUserInteraction) == FileUpdateOptions.AllowUserInteraction; OpenedFile file = CreateOpenedFile(fileName); try { - T model = file.GetModel(modelProvider); + T model = file.GetModel(modelProvider, interactive ? GetModelOptions.AllowUserInteraction : GetModelOptions.None); action(model); + FileSaveOptions saveOptions = interactive ? FileSaveOptions.AllowUserInteraction : FileSaveOptions.None; if ((options & FileUpdateOptions.SaveToDisk) == FileUpdateOptions.SaveToDisk) { - file.SaveToDisk(); + file.SaveToDisk(saveOptions); } if (!IsOpen(fileName) && file.IsDirty) { // The file is not open in a view, but we need to store our changes somewhere: if ((options & FileUpdateOptions.OpenViewIfNoneExists) != 0) { OpenFile(fileName, false); } else { - file.SaveToDisk(); + file.SaveToDisk(saveOptions); } } } finally { diff --git a/src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs b/src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs index 9b0e3555849..20f165f5644 100644 --- a/src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs +++ b/src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs @@ -58,7 +58,7 @@ protected override void UnloadFile() fileChangeWatcher = null; } - if (wasDirty) { + if (wasDirty && SD.ParserService.GetExistingUnresolvedFile(this.FileName) != null) { // We discarded some information when closing the file, // so we need to re-parse it. if (SD.FileSystem.FileExists(this.FileName)) @@ -68,12 +68,12 @@ protected override void UnloadFile() } } - public override void SaveToDisk(FileName fileName) + public override void SaveToDisk(FileName fileName, FileSaveOptions options) { try { if (fileChangeWatcher != null) fileChangeWatcher.Enabled = false; - base.SaveToDisk(fileName); + base.SaveToDisk(fileName, options); } finally { if (fileChangeWatcher != null) fileChangeWatcher.Enabled = true; From 8eedbfed9b31e4942b7db3f48c0393fe28743eea Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 17 Feb 2014 20:06:22 +0100 Subject: [PATCH 07/13] Attempt to port AvalonEditViewContent to new OpenedFile model. Problem: document services now may be shared across multiple views; which is problematic as some code expects ITextMarkerService to be per-view. Also, editor extensions might not support switching to a different IDocument instance - we should probably Detach + re-Attach extensions to avoid problems. --- .../Project/Src/WixDocumentReader.cs | 1 + .../Src/AvalonEditDisplayBinding.cs | 44 +++++++++-- .../Src/AvalonEditViewContent.cs | 48 ++++-------- .../AvalonEdit.AddIn/Src/CodeEditor.cs | 33 -------- .../Src/Commands/SaveFileWithEncoding.cs | 5 +- .../Src/NewLineConsistencyCheck.cs | 5 +- .../HexEditor/Project/Src/View/HexEditView.cs | 4 - .../WpfDesign.AddIn/Src/WpfViewContent.cs | 1 + .../WpfDesign.XamlDom/Project/XamlParser.cs | 1 + .../XmlEditor/Project/Src/XmlView.cs | 9 +-- .../Project/Util/SharpDevelopExtensions.cs | 20 +++-- .../Project/Workbench/AbstractViewContent.cs | 4 + .../Workbench/File/BinaryFileModelProvider.cs | 4 + .../Workbench/File/DocumentFileModelInfo.cs | 51 +++++++++++++ .../Base/Project/Workbench/File/FileModels.cs | 2 +- .../Workbench/File/IFileModelProvider.cs | 9 +++ .../Base/Project/Workbench/File/OpenedFile.cs | 43 ++++++----- .../File/TextDocumentFileModelProvider.cs | 76 +++++++++---------- .../File/XDocumentFileModelProvider.cs | 10 ++- .../Base/Project/Workbench/IViewContent.cs | 7 ++ .../SharpDevelop/Workbench/WpfWorkbench.cs | 29 ++++--- 21 files changed, 238 insertions(+), 168 deletions(-) diff --git a/src/AddIns/BackendBindings/WixBinding/Project/Src/WixDocumentReader.cs b/src/AddIns/BackendBindings/WixBinding/Project/Src/WixDocumentReader.cs index bd615a417a9..c35a901e444 100644 --- a/src/AddIns/BackendBindings/WixBinding/Project/Src/WixDocumentReader.cs +++ b/src/AddIns/BackendBindings/WixBinding/Project/Src/WixDocumentReader.cs @@ -63,6 +63,7 @@ public WixDocumentReader(string document) public WixDocumentReader(TextReader textReader) { reader = new XmlTextReader(textReader); + reader.XmlResolver = null; } public ReadOnlyCollection GetDialogIds() diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditDisplayBinding.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditDisplayBinding.cs index afdcf6c9d70..edff99a4a0d 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditDisplayBinding.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditDisplayBinding.cs @@ -17,13 +17,16 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.ComponentModel.Design; using System.IO; using System.Linq; using System.Text; +using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Utils; using ICSharpCode.Core; +using ICSharpCode.NRefactory.Editor; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Project; @@ -77,23 +80,49 @@ public bool CanCreateContentForFile(FileName fileName) return true; } - public IViewContent CreateContentForFile(OpenedFile file) + static Encoding DetectExistingEncoding(OpenedFile file) { - ChooseEncodingDialog dlg = new ChooseEncodingDialog(); - dlg.Owner = SD.Workbench.MainWindow; - using (Stream stream = file.OpenRead()) { + var existingTextModel = file.GetModel(FileModels.TextDocument, GetModelOptions.DoNotLoad); + if (existingTextModel != null) + return existingTextModel.GetFileModelInfo().Encoding; + using (Stream stream = file.GetModel(FileModels.Binary).OpenRead()) { using (StreamReader reader = FileReader.OpenStream(stream, SD.FileService.DefaultFileEncoding)) { - reader.Peek(); // force reader to auto-detect encoding - dlg.Encoding = reader.CurrentEncoding; + reader.Peek(); + // force reader to auto-detect encoding + return reader.CurrentEncoding; } } + } + + public IViewContent CreateContentForFile(OpenedFile file) + { + ChooseEncodingDialog dlg = new ChooseEncodingDialog(); + dlg.Owner = SD.Workbench.MainWindow; + dlg.Encoding = DetectExistingEncoding(file); if (dlg.ShowDialog() == true) { - return new AvalonEditViewContent(file, dlg.Encoding); + LoadWithEncoding(file, dlg.Encoding); + return new AvalonEditViewContent(file); } else { return null; } } + static void LoadWithEncoding(OpenedFile file, Encoding encoding) + { + var doc = file.GetModel(FileModels.TextDocument, GetModelOptions.DoNotLoad | GetModelOptions.AllowStale); + if (doc == null) + doc = new TextDocument(); + doc.GetFileModelInfo().Encoding = encoding; + string newText; + using (Stream stream = file.GetModel(FileModels.Binary).OpenRead()) { + using (StreamReader reader = new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: false)) { + newText = reader.ReadToEnd(); + } + } + FileModels.TextDocument.ReloadDocument(doc, new StringTextSource(newText)); + file.ReplaceModel(FileModels.TextDocument, doc, ReplaceModelMode.SetAsValid); + } + public bool IsPreferredBindingForFile(FileName fileName) { return false; @@ -105,3 +134,4 @@ public double AutoDetectFileContent(FileName fileName, Stream fileContent, strin } } } + \ No newline at end of file diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs index 0e2eaed2eed..5cbcc0e35dd 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs @@ -45,17 +45,13 @@ public class AvalonEditViewContent readonly CodeEditor codeEditor = new CodeEditor(); IAnalyticsMonitorTrackedFeature trackedFeature; - public AvalonEditViewContent(OpenedFile file, Encoding fixedEncodingForLoading = null) + public AvalonEditViewContent(OpenedFile file) { // Use common service container for view content and primary text editor. // This makes all text editor services available as view content services and vice versa. // (with the exception of the interfaces implemented directly by this class, // those are available as view-content services only) this.Services = codeEditor.PrimaryTextEditor.GetRequiredService(); - if (fixedEncodingForLoading != null) { - codeEditor.UseFixedEncoding = true; - codeEditor.PrimaryTextEditor.Encoding = fixedEncodingForLoading; - } this.TabPageText = "${res:FormsDesigner.DesignTabPages.SourceTabPage}"; if (file.FileName != null) { @@ -66,10 +62,7 @@ public AvalonEditViewContent(OpenedFile file, Encoding fixedEncodingForLoading = } this.Files.Add(file); - file.ForceInitializeView(this); - - file.IsDirtyChanged += PrimaryFile_IsDirtyChanged; - codeEditor.Document.UndoStack.PropertyChanged += codeEditor_Document_UndoStack_PropertyChanged; + this.LoadModel(); } bool IsKnownFileExtension(string filetype) @@ -77,7 +70,7 @@ bool IsKnownFileExtension(string filetype) return ProjectService.GetFileFilters().Any(f => f.ContainsExtension(filetype)) || IconService.HasImageForFile(filetype); } - + public override object Control { get { return codeEditor; } } @@ -86,30 +79,19 @@ public override object InitiallyFocusedControl { get { return codeEditor.PrimaryTextEditor.TextArea; } } - bool isLoading; - public override void Load(OpenedFile file, Stream stream) + public override void LoadModel() { - if (file != PrimaryFile) - return; - isLoading = true; - try { - if (!file.IsUntitled) { - codeEditor.PrimaryTextEditor.IsReadOnly = (File.GetAttributes(file.FileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly; - } - - codeEditor.Load(stream); - // Load() causes the undo stack to think stuff changed, so re-mark the file as original if necessary - if (!this.PrimaryFile.IsDirty) { - codeEditor.Document.UndoStack.MarkAsOriginalFile(); - } - - // we set the file name after loading because this will place the fold markers etc. - codeEditor.FileName = file.FileName; - BookmarksAttach(); - } finally { - isLoading = false; - } + // if (!file.IsUntitled) { + // codeEditor.PrimaryTextEditor.IsReadOnly = (File.GetAttributes(file.FileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly; + // } + + codeEditor.Document = PrimaryFile.GetModel(FileModels.TextDocument); + base.LoadModel(); + + // we set the file name after loading because this will place the fold markers etc. + codeEditor.FileName = PrimaryFile.FileName; + BookmarksAttach(); } protected override void OnFileNameChanged(OpenedFile file) @@ -159,7 +141,7 @@ void BookmarksAttach() PermanentAnchorService.AttachDocument(codeEditor.FileName, codeEditor.Document); } - + void BookmarksDetach() { if (codeEditor.FileName != null) { diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs index e96ceca130e..a9bb18f66fb 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs @@ -281,38 +281,6 @@ void TextAreaMouseRightButtonDown(object sender, MouseButtonEventArgs e) } } - // always use primary text editor for loading/saving - // (the file encoding is stored only there) - public void Load(Stream stream) - { - if (UseFixedEncoding) { - using (StreamReader reader = new StreamReader(stream, primaryTextEditor.Encoding, detectEncodingFromByteOrderMarks: false)) { - ReloadDocument(primaryTextEditor.Document, reader.ReadToEnd()); - } - } else { - // do encoding auto-detection - using (StreamReader reader = FileReader.OpenStream(stream, this.Encoding ?? SD.FileService.DefaultFileEncoding)) { - ReloadDocument(primaryTextEditor.Document, reader.ReadToEnd()); - this.Encoding = reader.CurrentEncoding; - } - } - // raise event which allows removing existing NewLineConsistencyCheck overlays - if (LoadedFileContent != null) - LoadedFileContent(this, EventArgs.Empty); - NewLineConsistencyCheck.StartConsistencyCheck(this); - } - - public event EventHandler LoadedFileContent; - - public void Save(Stream stream) - { - // don't use TextEditor.Save here because that would touch the Modified flag, - // but OpenedFile is already managing IsDirty - using (StreamWriter writer = new StreamWriter(stream, primaryTextEditor.Encoding ?? Encoding.UTF8)) { - primaryTextEditor.Document.WriteTextTo(writer); - } - } - public event EventHandler CaretPositionChanged; void TextAreaCaretPositionChanged(object sender, EventArgs e) @@ -336,7 +304,6 @@ void HandleCaretPositionChange() } NavigationService.Log(this.BuildNavPoint()); - var document = this.Document; int lineOffset = document.GetLineByNumber(this.Line).Offset; int chOffset = this.Column; int col = 1; diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Commands/SaveFileWithEncoding.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Commands/SaveFileWithEncoding.cs index 2859a2ab280..1a81ace5eec 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Commands/SaveFileWithEncoding.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Commands/SaveFileWithEncoding.cs @@ -18,6 +18,7 @@ using System; using ICSharpCode.Core; +using ICSharpCode.NRefactory.Editor; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Gui; @@ -37,9 +38,9 @@ public override void Run() if (codeEditor != null) { ChooseEncodingDialog dlg = new ChooseEncodingDialog(); dlg.Owner = SD.Workbench.MainWindow; - dlg.Encoding = codeEditor.PrimaryTextEditor.Encoding; + dlg.Encoding = codeEditor.Document.GetFileModelInfo().Encoding; if (dlg.ShowDialog() == true) { - codeEditor.PrimaryTextEditor.Encoding = dlg.Encoding; + codeEditor.Document.GetFileModelInfo().Encoding = dlg.Encoding; SharpDevelop.Commands.SaveFile.Save(vc.PrimaryFile); } } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/NewLineConsistencyCheck.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/NewLineConsistencyCheck.cs index f40ca7decd4..55e4db71156 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/NewLineConsistencyCheck.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/NewLineConsistencyCheck.cs @@ -125,16 +125,13 @@ void ShowInconsistentWarning(bool preferUnixNewLines) var featureUse = SD.AnalyticsMonitor.TrackFeature(typeof(NewLineConsistencyCheck)); - EventHandler removeWarning = null; - removeWarning = delegate { + EventHandler removeWarning = delegate { groupBox.Remove(); editor.PrimaryTextEditor.TextArea.Focus(); - editor.LoadedFileContent -= removeWarning; featureUse.EndTracking(); }; - editor.LoadedFileContent += removeWarning; cancelButton.Click += delegate { SD.AnalyticsMonitor.TrackFeature(typeof(NewLineConsistencyCheck), "cancelButton"); removeWarning(null, null); diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs index 63e4e00b04b..d42a3e785d1 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs @@ -138,9 +138,5 @@ void DocumentChanged(object sender, EventArgs e) { if (PrimaryFile != null) PrimaryFile.MakeDirty(); } - - public override bool IsDirty { - get { return base.IsDirty; } - } } } diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.AddIn/Src/WpfViewContent.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.AddIn/Src/WpfViewContent.cs index d0bb3082731..8a367994636 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.AddIn/Src/WpfViewContent.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.AddIn/Src/WpfViewContent.cs @@ -101,6 +101,7 @@ protected override void LoadInternal(OpenedFile file, System.IO.Stream stream) outline.Root = null; } using (XmlTextReader r = new XmlTextReader(stream)) { + r.XmlResolver = null; XamlLoadSettings settings = new XamlLoadSettings(); settings.DesignerAssemblies.Add(typeof(WpfViewContent).Assembly); settings.CustomServiceRegisterFunctions.Add( diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlParser.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlParser.cs index 04f3a330d89..df021e0aaf8 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlParser.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlParser.cs @@ -706,6 +706,7 @@ static void RemoveRootNamespacesFromNodeAndChildNodes(XamlObject root, XmlNode n public static XamlObject ParseSnippet(XamlObject root, string xaml, XamlParserSettings settings) { XmlTextReader reader = new XmlTextReader(new StringReader(xaml)); + reader.XmlResolver = null; var element = root.OwnerDocument.XmlDocument.ReadNode(reader); if (element != null) { diff --git a/src/AddIns/DisplayBindings/XmlEditor/Project/Src/XmlView.cs b/src/AddIns/DisplayBindings/XmlEditor/Project/Src/XmlView.cs index f87cb904b1d..e42f7b9c3e4 100644 --- a/src/AddIns/DisplayBindings/XmlEditor/Project/Src/XmlView.cs +++ b/src/AddIns/DisplayBindings/XmlEditor/Project/Src/XmlView.cs @@ -230,7 +230,8 @@ public string[] InferSchema() TaskService.ClearExceptCommentTasks(); if (IsWellFormed) { try { - using (XmlTextReader reader = new XmlTextReader(new StringReader(editor.Document.Text))) { + using (XmlTextReader reader = new XmlTextReader(editor.Document.CreateReader())) { + reader.XmlResolver = null; XmlSchemaInference schemaInference = new XmlSchemaInference(); XmlSchemaSet schemaSet = schemaInference.InferSchema(reader); return GetSchemas(schemaSet, editor); @@ -351,8 +352,7 @@ bool ValidateAgainstSchema() if (editor == null) return false; try { - StringReader stringReader = new StringReader(editor.Document.Text); - XmlTextReader xmlReader = new XmlTextReader(stringReader); + XmlTextReader xmlReader = new XmlTextReader(editor.Document.CreateReader()); xmlReader.XmlResolver = null; XmlReaderSettings settings = new XmlReaderSettings(); settings.ValidationType = ValidationType.Schema; @@ -398,8 +398,7 @@ bool ValidateSchema() ITextEditor editor = TextEditor; if (editor == null) return false; - StringReader stringReader = new StringReader(editor.Document.Text); - XmlTextReader xmlReader = new XmlTextReader(stringReader); + XmlTextReader xmlReader = new XmlTextReader(editor.Document.CreateReader()); xmlReader.XmlResolver = null; try { diff --git a/src/Main/Base/Project/Util/SharpDevelopExtensions.cs b/src/Main/Base/Project/Util/SharpDevelopExtensions.cs index 2350c0a8baa..12faf5fad2a 100644 --- a/src/Main/Base/Project/Util/SharpDevelopExtensions.cs +++ b/src/Main/Base/Project/Util/SharpDevelopExtensions.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel.Design; using System.IO; using System.Linq; using System.Text; @@ -296,7 +297,7 @@ public static T MinBy(this IEnumerable source, Func keySelector) { return source.MinBy(keySelector, Comparer.Default); } - + /// /// Returns the minimum element. /// @@ -334,7 +335,7 @@ public static T MaxBy(this IEnumerable source, Func keySelector) { return source.MaxBy(keySelector, Comparer.Default); } - + /// /// Returns the maximum element. /// @@ -740,7 +741,7 @@ public static string TakeStart(this string s, int length) return s; return s.Substring(0, length); } - + /// /// Takes at most first characters from string, and appends '...' if string is longer. /// String can be null. @@ -751,7 +752,7 @@ public static string TakeStartEllipsis(this string s, int length) return s; return s.Substring(0, length) + "..."; } - + /// /// Removes any character given in the array from the given string. /// @@ -920,7 +921,7 @@ public static ICompilation GetCompilationForCurrentProject(this IParserService s /// Gets an from a resource. /// /// The resource with the specified name does not exist - public static IImage GetImage(this IResourceService resourceService, string resourceName) + public static IImage GetImage(this ICSharpCode.Core.IResourceService resourceService, string resourceName) { if (resourceService == null) throw new ArgumentNullException("resourceService"); @@ -933,7 +934,7 @@ public static IImage GetImage(this IResourceService resourceService, string reso /// Gets an image source from a resource. /// /// The resource with the specified name does not exist - public static ImageSource GetImageSource(this IResourceService resourceService, string resourceName) + public static ImageSource GetImageSource(this ICSharpCode.Core.IResourceService resourceService, string resourceName) { if (resourceService == null) throw new ArgumentNullException("resourceService"); @@ -1035,7 +1036,12 @@ public static string GetText(this IDocument document, TextLocation startPos, Tex /// public static DocumentFileModelInfo GetFileModelInfo(this TextDocument document) { - return document.GetService(); + var info = document.GetService(); + if (info == null) { + info = new DocumentFileModelInfo(); + document.GetRequiredService().AddService(typeof(DocumentFileModelInfo), info); + } + return info; } /// diff --git a/src/Main/Base/Project/Workbench/AbstractViewContent.cs b/src/Main/Base/Project/Workbench/AbstractViewContent.cs index a2b4a55490d..c56cb12489a 100644 --- a/src/Main/Base/Project/Workbench/AbstractViewContent.cs +++ b/src/Main/Base/Project/Workbench/AbstractViewContent.cs @@ -89,6 +89,10 @@ protected virtual void OnWorkbenchWindowChanged() { } + public virtual void LoadModel() + { + } + string tabPageText = "TabPageText"; public event EventHandler TabPageTextChanged; diff --git a/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs index 989826febdb..314a00b25ce 100644 --- a/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs @@ -65,6 +65,10 @@ public void NotifyStale(OpenedFile file, IBinaryFileModel model) { } + public void NotifyLoaded(OpenedFile file, IBinaryFileModel model) + { + } + public void NotifyUnloaded(OpenedFile file, IBinaryFileModel model) { } diff --git a/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs b/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs index 9fcd51bf15d..f5d604b97e4 100644 --- a/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs +++ b/src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs @@ -3,6 +3,7 @@ using System; using System.Text; +using ICSharpCode.AvalonEdit.Document; namespace ICSharpCode.SharpDevelop.Workbench { @@ -11,6 +12,9 @@ namespace ICSharpCode.SharpDevelop.Workbench /// public class DocumentFileModelInfo { + OpenedFile file; + TextDocument document; + public DocumentFileModelInfo() { this.Encoding = SD.FileService.DefaultFileEncoding; @@ -30,5 +34,52 @@ public DocumentFileModelInfo() /// Gets the encoding of the model. /// public Encoding Encoding { get; set; } + + internal void NotifyLoaded(OpenedFile file, TextDocument document) + { + if (this.file != null) + throw new InvalidOperationException("Duplicate NotifyLoaded() call"); + this.file = file; + this.document = document; + document.TextChanged += OnDocumentTextChanged; + file.IsDirtyChanged += OnFileIsDirtyChanged; + UpdateUndoStackIsOriginalFile(); + } + + internal void NotifyUnloaded() + { + document.TextChanged -= OnDocumentTextChanged; + file.IsDirtyChanged -= OnFileIsDirtyChanged; + this.IsStale = true; + this.file = null; + this.document = null; + } + + void OnDocumentTextChanged(object sender, EventArgs e) + { + if (!isLoading) { + // Set dirty flag on OpenedFile according to UndoStack.IsOriginalFile + if (document.UndoStack.IsOriginalFile && file.IsDirty) { + // reset dirty marker + file.ReplaceModel(FileModels.TextDocument, document, ReplaceModelMode.SetAsValid); + } else if (!document.UndoStack.IsOriginalFile) { + file.MakeDirty(FileModels.TextDocument); + } + } + } + + void OnFileIsDirtyChanged(object sender, EventArgs e) + { + UpdateUndoStackIsOriginalFile(); + } + + void UpdateUndoStackIsOriginalFile() + { + if (file.IsDirty) { + document.UndoStack.DiscardOriginalFileMarker(); + } else { + document.UndoStack.MarkAsOriginalFile(); + } + } } } diff --git a/src/Main/Base/Project/Workbench/File/FileModels.cs b/src/Main/Base/Project/Workbench/File/FileModels.cs index 51888b785c8..2f8263b9c7e 100644 --- a/src/Main/Base/Project/Workbench/File/FileModels.cs +++ b/src/Main/Base/Project/Workbench/File/FileModels.cs @@ -19,7 +19,7 @@ public static class FileModels /// /// The text document file model provider. /// - public static readonly IFileModelProvider TextDocument = new TextDocumentFileModelProvider(); + public static readonly TextDocumentFileModelProvider TextDocument = new TextDocumentFileModelProvider(); /// /// The XDocument file model provider. diff --git a/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs index 78e84362374..73f1989d516 100644 --- a/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/IFileModelProvider.cs @@ -77,6 +77,15 @@ public interface IFileModelProvider where T : class /// void NotifyStale(OpenedFile file, T model); + /// + /// Notifies the provider that a new model was loaded. + /// This method is called by OpenedFile.GetModel() after a Load() operation (unless Load() returned the existing stale model), + /// or by OpenedFile.ReplaceModel(). + /// + /// This method is intended to be used to set up the handling of the IsDirty flag. + /// + void NotifyLoaded(OpenedFile file, T model); + /// /// Notifies a model that it became unloaded. /// This can happen due to: diff --git a/src/Main/Base/Project/Workbench/File/OpenedFile.cs b/src/Main/Base/Project/Workbench/File/OpenedFile.cs index 5484f5f811e..96af9b18791 100644 --- a/src/Main/Base/Project/Workbench/File/OpenedFile.cs +++ b/src/Main/Base/Project/Workbench/File/OpenedFile.cs @@ -70,6 +70,13 @@ public enum ReplaceModelMode /// /// Represents an opened file. /// + /// + /// This class uses reference counting: the AddReference() and ReleaseReference() + /// methods are used to track an explicit reference count. + /// When the last reference is released, the opened file will be disposed. + /// View contents must maintain a reference to their opened files + /// (this usually happens via the AbstractViewContent.Files collection). + /// public abstract class OpenedFile : ICanBeDirty { abstract class ModelEntry @@ -287,7 +294,7 @@ void SaveCopyTo(FileName outputFileName, FileSaveOptions options) preventLoading = false; } } - + #endregion #region GetModel @@ -295,7 +302,7 @@ ModelEntry GetEntry(IFileModelProvider modelProvider) where T : class { CheckDisposed(); foreach (var entry in entries) { - if (entry.Provider == modelProvider) + if (object.Equals(entry.Provider, modelProvider)) return (ModelEntry)entry; } return null; @@ -495,7 +502,7 @@ public void ReplaceModel(IFileModelProvider modelProvider, T model, Replac #region Reference Counting int referenceCount = 1; - + void CheckDisposed() { if (referenceCount <= 0) @@ -694,29 +701,29 @@ public virtual void SaveToDisk() IsDirty = false; } -// /// -// /// Called before saving the current view. This event is raised both when saving to disk and to memory (for switching between views). -// /// -// public event EventHandler SavingCurrentView; -// -// /// -// /// Called after saving the current view. This event is raised both when saving to disk and to memory (for switching between views). -// /// -// public event EventHandler SavedCurrentView; + // /// + // /// Called before saving the current view. This event is raised both when saving to disk and to memory (for switching between views). + // /// + // public event EventHandler SavingCurrentView; + // + // /// + // /// Called after saving the current view. This event is raised both when saving to disk and to memory (for switching between views). + // /// + // public event EventHandler SavedCurrentView; void SaveCurrentViewToStream(Stream stream) { -// if (SavingCurrentView != null) -// SavingCurrentView(this, EventArgs.Empty); + // if (SavingCurrentView != null) + // SavingCurrentView(this, EventArgs.Empty); inSaveOperation = true; try { currentView.Save(this, stream); } finally { inSaveOperation = false; } -// if (SavedCurrentView != null) -// SavedCurrentView(this, EventArgs.Empty); + // if (SavedCurrentView != null) + // SavedCurrentView(this, EventArgs.Empty); } protected void SaveCurrentView() @@ -736,7 +743,7 @@ public void SwitchedToView(IViewContent newView) return; if (currentView != null) { if (newView.SupportsSwitchToThisWithoutSaveLoad(this, currentView) - || currentView.SupportsSwitchFromThisWithoutSaveLoad(this, newView)) + || currentView.SupportsSwitchFromThisWithoutSaveLoad(this, newView)) { // switch without Save/Load currentView.SwitchFromThisWithoutSaveLoad(this, newView); @@ -820,5 +827,5 @@ static void RestoreMemento(IViewContent viewContent, Properties memento) } } } - */ + */ } diff --git a/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs index aa026e54fb4..6323ede8920 100644 --- a/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs @@ -15,9 +15,18 @@ namespace ICSharpCode.SharpDevelop.Workbench { - sealed class TextDocumentFileModelProvider : IFileModelProvider + public sealed class TextDocumentFileModelProvider : IFileModelProvider { - public TextDocument Load(OpenedFile file) + /// + /// Internal ctor: FileModels.TextDocument should be the only instance. + /// + internal TextDocumentFileModelProvider() + { + } + + // use explicit interface implementations because these methods are supposed to only + // be called from the OpenedFile infrastructure + TextDocument IFileModelProvider.Load(OpenedFile file) { TextDocument document = file.GetModel(this, GetModelOptions.AllowStale | GetModelOptions.DoNotLoad); var info = document != null ? document.GetFileModelInfo() : new DocumentFileModelInfo(); @@ -30,44 +39,28 @@ public TextDocument Load(OpenedFile file) } if (document != null) { // Reload document - var diff = new MyersDiffAlgorithm(new StringSequence(document.Text), new StringSequence(textContent)); - info.isLoading = true; - try { - document.Replace(0, document.TextLength, textContent, ToOffsetChangeMap(diff.GetEdits())); - document.UndoStack.ClearAll(); - info.IsStale = false; - } finally { - info.isLoading = false; - } + ReloadDocument(document, new StringTextSource(textContent)); + info.IsStale = false; } else { document = new TextDocument(textContent); - document.TextChanged += delegate { UpdateFileIsDirty(document, file, info); }; + // Store info for the new document (necessary so that we don't forget the encoding we just used) document.GetRequiredService().AddService(typeof(DocumentFileModelInfo), info); } - file.IsDirtyChanged += delegate { UpdateUndoStackIsOriginalFile(file, document); }; - UpdateUndoStackIsOriginalFile(file, document); return document; } - static void UpdateUndoStackIsOriginalFile(ICanBeDirty file, TextDocument document) - { - if (file.IsDirty) { - document.UndoStack.DiscardOriginalFileMarker(); - } else { - document.UndoStack.MarkAsOriginalFile(); - } - } - - void UpdateFileIsDirty(TextDocument document, OpenedFile file, DocumentFileModelInfo info) + public void ReloadDocument(TextDocument document, ITextSource newContent) { - if (!info.isLoading) { - // Set dirty flag on OpenedFile according to UndoStack.IsOriginalFile - if (document.UndoStack.IsOriginalFile && file.IsDirty) { - // reset dirty marker - file.ReplaceModel(this, document, ReplaceModelMode.SetAsValid); - } else if (!document.UndoStack.IsOriginalFile) { - file.MakeDirty(this); - } + var info = document.GetFileModelInfo(); + var diff = new MyersDiffAlgorithm(new StringSequence(document.Text), new StringSequence(newContent.Text)); + if (info != null) + info.isLoading = true; + try { + document.Replace(0, document.TextLength, newContent, ToOffsetChangeMap(diff.GetEdits())); + document.UndoStack.ClearAll(); + } finally { + if (info != null) + info.isLoading = false; } } @@ -87,14 +80,14 @@ OffsetChangeMap ToOffsetChangeMap(IEnumerable edits) return map; } - public void Save(OpenedFile file, TextDocument model, FileSaveOptions options) + void IFileModelProvider.Save(OpenedFile file, TextDocument model, FileSaveOptions options) { MemoryStream ms = new MemoryStream(); SaveTo(ms, model, options); file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray()), ReplaceModelMode.TransferDirty); } - public void SaveCopyAs(OpenedFile file, TextDocument model, FileName outputFileName, FileSaveOptions options) + void IFileModelProvider.SaveCopyAs(OpenedFile file, TextDocument model, FileName outputFileName, FileSaveOptions options) { using (Stream s = SD.FileSystem.OpenWrite(outputFileName)) { SaveTo(s, model, options); @@ -147,7 +140,7 @@ static bool ConfirmSaveWithDataLoss(Encoding encoding, FileSaveOptions options) int r = MessageService.ShowCustomDialog( "${res:Dialog.Options.IDEOptions.TextEditor.General.FontGroupBox.FileEncodingGroupBox}", StringParser.Parse("${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss}", - new StringTagPair("encoding", encoding.EncodingName)), + new StringTagPair("encoding", encoding.EncodingName)), 0, -1, "${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.UseUTF8}", "${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.Continue}"); @@ -159,19 +152,24 @@ bool IFileModelProvider.CanLoadFrom(IFileModelProvider other return otherProvider == FileModels.Binary || FileModels.Binary.CanLoadFrom(otherProvider); } - public void NotifyRename(OpenedFile file, TextDocument model, FileName oldName, FileName newName) + void IFileModelProvider.NotifyRename(OpenedFile file, TextDocument model, FileName oldName, FileName newName) { model.FileName = newName; } - public void NotifyStale(OpenedFile file, TextDocument model) + void IFileModelProvider.NotifyStale(OpenedFile file, TextDocument model) { model.GetFileModelInfo().IsStale = true; } - public void NotifyUnloaded(OpenedFile file, TextDocument model) + void IFileModelProvider.NotifyLoaded(OpenedFile file, TextDocument model) { - model.GetFileModelInfo().IsStale = true; + model.GetFileModelInfo().NotifyLoaded(file, model); + } + + void IFileModelProvider.NotifyUnloaded(OpenedFile file, TextDocument model) + { + model.GetFileModelInfo().NotifyUnloaded(); } } } diff --git a/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs b/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs index 081b6892a07..b1d0b8fc88e 100644 --- a/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs @@ -16,9 +16,6 @@ public XDocument Load(OpenedFile file) using (Stream stream = file.GetModel(FileModels.Binary).OpenRead()) { document = XDocument.Load(stream, LoadOptions.PreserveWhitespace); } - document.Changed += delegate { - file.MakeDirty(this); - }; return document; } @@ -47,6 +44,13 @@ public void NotifyStale(OpenedFile file, XDocument model) { } + public void NotifyLoaded(OpenedFile file, XDocument model) + { + model.Changed += delegate { + file.MakeDirty(this); + }; + } + public void NotifyUnloaded(OpenedFile file, XDocument model) { } diff --git a/src/Main/Base/Project/Workbench/IViewContent.cs b/src/Main/Base/Project/Workbench/IViewContent.cs index 3e7f394a1f9..10530c3af71 100644 --- a/src/Main/Base/Project/Workbench/IViewContent.cs +++ b/src/Main/Base/Project/Workbench/IViewContent.cs @@ -36,6 +36,13 @@ namespace ICSharpCode.SharpDevelop /// public interface IViewContent : IDisposable, ICanBeDirty, IServiceProvider { + /// + /// Notifies the view content that it should (re-)load the file models. + /// This method is called by the SharpDevelop infrastructure whenever a view content + /// receives focus. + /// + void LoadModel(); + /// /// This is the UI element for the view. /// You can use both Windows.Forms and WPF controls. diff --git a/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs b/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs index 6683d06a7bf..902cee44d21 100644 --- a/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs +++ b/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs @@ -31,15 +31,13 @@ using System.Windows.Interop; using System.Windows.Media; using System.Windows.Navigation; -using System.Windows.Threading; using ICSharpCode.Core; using ICSharpCode.Core.Presentation; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Parser; -using ICSharpCode.SharpDevelop.Project; -using ICSharpCode.SharpDevelop.Startup; using ICSharpCode.SharpDevelop.WinForms; +using ICSharpCode.SharpDevelop.Startup; namespace ICSharpCode.SharpDevelop.Workbench { @@ -107,7 +105,7 @@ protected override void OnSourceInitialized(EventArgs e) // Set WindowState after PresentationSource is initialized, because now bounds and location are properly set. this.WindowState = lastNonMinimizedWindowState; } - + void SetBounds(Rect bounds) { this.Left = bounds.Left; @@ -174,7 +172,7 @@ void ExecuteCommand(ICommand command, object caller) // keep a reference to the event handler to prevent it from being garbage collected // (CommandManager.RequerySuggested only keeps weak references to the event handlers) EventHandler requerySuggestedEventHandler; - + void CommandManager_RequerySuggested(object sender, EventArgs e) { UpdateMenu(); @@ -263,10 +261,11 @@ public ICollection ViewContentCollection { public ICollection PrimaryViewContents { get { SD.MainThread.VerifyAccess(); - return (from window in WorkbenchWindowCollection - where window.ViewContents.Count > 0 - select window.ViewContents[0] - ).ToList().AsReadOnly(); + return ( + from window in WorkbenchWindowCollection + where window.ViewContents.Count > 0 + select window.ViewContents[0] + ).ToList().AsReadOnly(); } } @@ -306,10 +305,10 @@ private set { value.ActiveViewContentChanged += WorkbenchWindowActiveViewContentChanged; } + WorkbenchWindowActiveViewContentChanged(null, null); if (ActiveWorkbenchWindowChanged != null) { ActiveWorkbenchWindowChanged(this, EventArgs.Empty); } - WorkbenchWindowActiveViewContentChanged(null, null); } } } @@ -319,10 +318,11 @@ void WorkbenchWindowActiveViewContentChanged(object sender, EventArgs e) if (workbenchLayout != null) { // update ActiveViewContent IWorkbenchWindow window = this.ActiveWorkbenchWindow; - if (window != null) + if (window != null) { this.ActiveViewContent = window.ActiveViewContent; - else + } else { this.ActiveViewContent = null; + } // update ActiveContent this.ActiveContent = workbenchLayout.ActiveContent; @@ -360,6 +360,11 @@ private set { if (activeViewContent != value) { activeViewContent = value; + // Call LoadModel() before raising the event; this + // ensures that listeners to ActiveViewContentChanged + // see an up-to-date model. + if (value != null) + value.LoadModel(); if (ActiveViewContentChanged != null) { ActiveViewContentChanged(this, EventArgs.Empty); } From 525f2a243dd15215de653a1a0535612dd7dfd334 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Wed, 19 Feb 2014 17:05:09 +0100 Subject: [PATCH 08/13] Add AbstractViewContentSD1234 for backward compatiblity with existing viewcontents. --- .../HexEditor/Project/Src/View/HexEditView.cs | 4 +- .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../Project/Workbench/AbstractViewContent.cs | 6 +- .../AbstractViewContentHandlingLoadErrors.cs | 29 +---- .../Workbench/AbstractViewContentSD1234.cs | 104 ++++++++++++++++++ .../Base/Project/Workbench/File/OpenedFile.cs | 14 ++- 6 files changed, 123 insertions(+), 35 deletions(-) create mode 100644 src/Main/Base/Project/Workbench/AbstractViewContentSD1234.cs diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs index d42a3e785d1..a948f8e83d3 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs @@ -27,7 +27,7 @@ namespace HexEditor.View { - public class HexEditView : AbstractViewContent, IClipboardHandler, IUndoHandler + public class HexEditView : AbstractViewContentSD1234, IClipboardHandler, IUndoHandler { HexEditContainer hexEditContainer; @@ -38,7 +38,7 @@ public HexEditView(OpenedFile file) this.Files.Add(file); - file.ForceInitializeView(this); + LoadModel(); SD.AnalyticsMonitor.TrackFeature(typeof(HexEditView)); } diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 67808456e17..6958e39d1e6 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -352,6 +352,7 @@ + diff --git a/src/Main/Base/Project/Workbench/AbstractViewContent.cs b/src/Main/Base/Project/Workbench/AbstractViewContent.cs index c56cb12489a..a680556cac6 100644 --- a/src/Main/Base/Project/Workbench/AbstractViewContent.cs +++ b/src/Main/Base/Project/Workbench/AbstractViewContent.cs @@ -208,7 +208,7 @@ public virtual ICollection SecondaryViewContents { void InitFiles() { - files = new ObserveAddRemoveCollection(RegisterFileEventHandlers, UnregisterFileEventHandlers); + files = new ObserveAddRemoveCollection(OnFileAdded, OnFileRemoved); filesReadonly = new ReadOnlyCollection(files); } @@ -254,7 +254,7 @@ public virtual FileName PrimaryFileName { } } - void RegisterFileEventHandlers(OpenedFile newItem) + internal virtual void OnFileAdded(OpenedFile newItem) { newItem.FileNameChanged += OnFileNameChanged; newItem.IsDirtyChanged += OnIsDirtyChanged; @@ -262,7 +262,7 @@ void RegisterFileEventHandlers(OpenedFile newItem) OnIsDirtyChanged(null, EventArgs.Empty); // re-evaluate this.IsDirty after changing the file collection } - void UnregisterFileEventHandlers(OpenedFile oldItem) + internal virtual void OnFileRemoved(OpenedFile oldItem) { oldItem.FileNameChanged -= OnFileNameChanged; oldItem.IsDirtyChanged -= OnIsDirtyChanged; diff --git a/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs b/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs index 43966bc8f3d..5f898586c7e 100644 --- a/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs +++ b/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs @@ -26,6 +26,7 @@ namespace ICSharpCode.SharpDevelop.Workbench { + /* /// /// This class handles errors in the Load method and prevents destroying invalid files. /// Scenario: @@ -67,31 +68,6 @@ protected object UserContent { } } - public bool HasLoadError { - get { - return errorList.Count > 0; - } - } - - class LoadError - { - internal Exception exception; - internal byte[] fileData; - - public LoadError(Exception exception, Stream stream) - { - this.exception = exception; - stream.Position = 0; - this.fileData = new byte[(int)stream.Length]; - int pos = 0; - while (pos < fileData.Length) { - int c = stream.Read(fileData, pos, fileData.Length - pos); - if (c == 0) break; - pos += c; - } - } - } - TextBox errorTextBox; protected void ShowError(Exception ex) @@ -105,8 +81,6 @@ protected void ShowError(Exception ex) SD.WinForms.SetContent(contentControl, errorTextBox, this); } - Dictionary errorList = new Dictionary(); - /// /// Gets a text to be shown above the exception when a load error occurs. /// The default is an empty string. @@ -115,4 +89,5 @@ protected virtual string LoadErrorHeaderText { get { return String.Empty; } } } + */ } diff --git a/src/Main/Base/Project/Workbench/AbstractViewContentSD1234.cs b/src/Main/Base/Project/Workbench/AbstractViewContentSD1234.cs new file mode 100644 index 00000000000..3e889eb774f --- /dev/null +++ b/src/Main/Base/Project/Workbench/AbstractViewContentSD1234.cs @@ -0,0 +1,104 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using ICSharpCode.Core; + +namespace ICSharpCode.SharpDevelop.Workbench +{ + /// + /// View content base class that is compatible with the old SD-1234 OpenedFile model. + /// + public abstract class AbstractViewContentSD1234 : AbstractViewContent + { + // The old behavior where view contents are loaded/saved without the use of a model instance is achieved by + // making the IViewContent itself the model. + class Provider : IFileModelProvider + { + AbstractViewContentSD1234 IFileModelProvider.Load(OpenedFile file) + { + throw new NotSupportedException(); + } + + void IFileModelProvider.Save(OpenedFile file, AbstractViewContentSD1234 model, FileSaveOptions options) + { + MemoryStream ms = new MemoryStream(); + model.Save(file, ms); + file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray()), ReplaceModelMode.TransferDirty); + } + + void IFileModelProvider.SaveCopyAs(OpenedFile file, AbstractViewContentSD1234 model, FileName outputFileName, FileSaveOptions options) + { + using (Stream s = SD.FileSystem.OpenWrite(outputFileName)) { + model.Save(file, s); + } + } + + bool IFileModelProvider.CanLoadFrom(IFileModelProvider otherProvider) + { + return false; + } + + void IFileModelProvider.NotifyRename(OpenedFile file, AbstractViewContentSD1234 model, FileName oldName, FileName newName) + { + } + + void IFileModelProvider.NotifyStale(OpenedFile file, AbstractViewContentSD1234 model) + { + model.isStale = true; + } + + void IFileModelProvider.NotifyLoaded(OpenedFile file, AbstractViewContentSD1234 model) + { + } + + void IFileModelProvider.NotifyUnloaded(OpenedFile file, AbstractViewContentSD1234 model) + { + } + } + + Provider provider = new Provider(); // each AbstractViewContent gets its own provider instance + bool isStale = true; + + public abstract void Save(OpenedFile file, Stream stream); + public abstract void Load(OpenedFile file, Stream stream); + + public override void LoadModel() + { + base.LoadModel(); + if (isStale) { + foreach (var file in this.Files) { + using (Stream s = file.GetModel(FileModels.Binary).OpenRead()) { + Load(file, s); + } + file.ReplaceModel(provider, this, ReplaceModelMode.TransferDirty); + } + isStale = false; + } + } + + internal override void OnFileRemoved(OpenedFile oldItem) + { + // When a file is removed from the Files collection, unload our 'model' to remove the reference to the view content. + oldItem.UnloadModel(provider); + // This method also gets called for each OpenedFile when the view content is disposed; so we don't need to override Dispose for cleanup. + base.OnFileRemoved(oldItem); + } + } +} diff --git a/src/Main/Base/Project/Workbench/File/OpenedFile.cs b/src/Main/Base/Project/Workbench/File/OpenedFile.cs index 96af9b18791..82c99c1be03 100644 --- a/src/Main/Base/Project/Workbench/File/OpenedFile.cs +++ b/src/Main/Base/Project/Workbench/File/OpenedFile.cs @@ -61,7 +61,8 @@ public enum ReplaceModelMode /// SetAsValid, /// - /// The new model is marked as dirty, the previously dirty model is marked as stale, and any other models are unchanged. + /// The new model is marked as dirty or valid (depending on whether the OpenedFile was previously dirty), + /// the previously dirty model (if any) is marked as stale, and any other models are unchanged. /// This mode is intended for use in implementations. /// TransferDirty @@ -89,6 +90,7 @@ abstract class ModelEntry public abstract void SaveCopyAs(OpenedFile file, FileName outputFileName, FileSaveOptions options); public abstract void NotifyRename(OpenedFile file, FileName oldName, FileName newName); public abstract void NotifyStale(OpenedFile file); + public abstract void NotifyLoaded(OpenedFile file); public abstract void NotifyUnloaded(OpenedFile file); public abstract bool NeedsSaveForLoadInto(IFileModelProvider modelProvider) where T : class; } @@ -127,6 +129,11 @@ public override void NotifyStale(OpenedFile file) provider.NotifyStale(file, Model); } + public override void NotifyLoaded(OpenedFile file) + { + provider.NotifyLoaded(file, Model); + } + public override void NotifyUnloaded(OpenedFile file) { provider.NotifyUnloaded(file, Model); @@ -475,6 +482,7 @@ public void ReplaceModel(IFileModelProvider modelProvider, T model, Replac if (entry.Model != model) { entry.NotifyUnloaded(this); entry.Model = model; + entry.NotifyLoaded(this); } entry.IsStale = false; } @@ -491,8 +499,8 @@ public void ReplaceModel(IFileModelProvider modelProvider, T model, Replac } break; case ReplaceModelMode.TransferDirty: - dirtyEntry = entry; - this.IsDirty = true; + if (dirtyEntry != null) + dirtyEntry = entry; break; default: throw new ArgumentOutOfRangeException(); From 2c8229e832dfdad273e066357900f7454678237a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Wed, 19 Feb 2014 19:26:11 +0100 Subject: [PATCH 09/13] Add back the old AbstractViewContentHandlingLoadErrors --- .../Scripting/Test/Utils/MockOpenedFile.cs | 16 ----- .../Scripting/Test/Utils/MockViewContent.cs | 71 +------------------ .../DisplayBinding/EDMDesignerViewContent.cs | 6 +- .../Project/Src/DesignerViewContent.cs | 2 +- .../Src/Gui/ImageResourceEditorDialog.cs | 2 +- .../Src/Services/ProjectResourceInfo.cs | 2 +- .../HexEditor/Project/Src/View/HexEditView.cs | 3 +- .../IconEditor/IconViewContent.cs | 4 +- .../Project/Src/DisplayDefinition.cs | 2 +- .../Project/SettingsViewContent.cs | 10 +-- .../WpfDesign.AddIn/Src/WpfViewContent.cs | 2 +- .../Project/ReportDesignerView.cs | 2 +- .../Project/Engine/SearchManager.cs | 8 +-- .../AbstractViewContentHandlingLoadErrors.cs | 64 +++++++++++++++-- .../Workbench/AbstractViewContentSD1234.cs | 25 +++++++ .../Base/Project/Workbench/File/OpenedFile.cs | 5 +- src/Main/Base/Test/Utils/MockOpenedFile.cs | 39 +--------- 17 files changed, 114 insertions(+), 149 deletions(-) diff --git a/src/AddIns/BackendBindings/Scripting/Test/Utils/MockOpenedFile.cs b/src/AddIns/BackendBindings/Scripting/Test/Utils/MockOpenedFile.cs index b3a8d72a073..49171160aa6 100644 --- a/src/AddIns/BackendBindings/Scripting/Test/Utils/MockOpenedFile.cs +++ b/src/AddIns/BackendBindings/Scripting/Test/Utils/MockOpenedFile.cs @@ -33,21 +33,5 @@ public MockOpenedFile(string fileName) { this.FileName = new ICSharpCode.Core.FileName(fileName); } - - public override IList RegisteredViewContents { - get { - throw new NotImplementedException(); - } - } - - public override void RegisterView(IViewContent view) - { - } - - public override void UnregisterView(IViewContent view) - { - } - - public override event EventHandler FileClosed { add {} remove {} } } } diff --git a/src/AddIns/BackendBindings/Scripting/Test/Utils/MockViewContent.cs b/src/AddIns/BackendBindings/Scripting/Test/Utils/MockViewContent.cs index 66531ce5b31..eecdd15733b 100644 --- a/src/AddIns/BackendBindings/Scripting/Test/Utils/MockViewContent.cs +++ b/src/AddIns/BackendBindings/Scripting/Test/Utils/MockViewContent.cs @@ -110,18 +110,8 @@ public bool IsDirty { throw new NotImplementedException(); } } - - public void Save() - { - throw new NotImplementedException(); - } - - public void Save(string fileName) - { - throw new NotImplementedException(); - } - - public void Load(string fileName) + + public void LoadModel() { throw new NotImplementedException(); } @@ -131,37 +121,12 @@ public INavigationPoint BuildNavPoint() throw new NotImplementedException(); } - public void SwitchedTo() - { - throw new NotImplementedException(); - } - - public void Selected() - { - throw new NotImplementedException(); - } - - public void Deselecting() - { - throw new NotImplementedException(); - } - - public void Deselected() - { - throw new NotImplementedException(); - } - - public void RedrawContent() - { - throw new NotImplementedException(); - } - public void Dispose() { throw new NotImplementedException(); } - public IList Files { + public IReadOnlyList Files { get { throw new NotImplementedException(); } @@ -183,36 +148,6 @@ public bool IsDisposed { } } - public void Save(OpenedFile file, System.IO.Stream stream) - { - throw new NotImplementedException(); - } - - public void Load(OpenedFile file, System.IO.Stream stream) - { - throw new NotImplementedException(); - } - - public bool SupportsSwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView) - { - throw new NotImplementedException(); - } - - public bool SupportsSwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView) - { - throw new NotImplementedException(); - } - - public void SwitchFromThisWithoutSaveLoad(OpenedFile file, IViewContent newView) - { - throw new NotImplementedException(); - } - - public void SwitchToThisWithoutSaveLoad(OpenedFile file, IViewContent oldView) - { - throw new NotImplementedException(); - } - protected virtual void OnTitleNameChanged(EventArgs e) { if (TitleNameChanged != null) { diff --git a/src/AddIns/DisplayBindings/Data/ICSharpCode.Data.EDMDesigner.Core.UI/DisplayBinding/EDMDesignerViewContent.cs b/src/AddIns/DisplayBindings/Data/ICSharpCode.Data.EDMDesigner.Core.UI/DisplayBinding/EDMDesignerViewContent.cs index d11ad4a6333..cf1f0df32ad 100644 --- a/src/AddIns/DisplayBindings/Data/ICSharpCode.Data.EDMDesigner.Core.UI/DisplayBinding/EDMDesignerViewContent.cs +++ b/src/AddIns/DisplayBindings/Data/ICSharpCode.Data.EDMDesigner.Core.UI/DisplayBinding/EDMDesignerViewContent.cs @@ -41,7 +41,7 @@ namespace ICSharpCode.Data.EDMDesigner.Core.UI.DisplayBinding { - public class EDMDesignerViewContent : AbstractViewContent, IHasPropertyContainer, IToolsHost, IEDMDesignerChangeWatcherObserver + public class EDMDesignerViewContent : AbstractViewContentSD1234, IHasPropertyContainer, IToolsHost, IEDMDesignerChangeWatcherObserver { private ScrollViewer _scrollViewer = new ScrollViewer() { HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, @@ -86,7 +86,7 @@ public EDMDesignerViewContent(OpenedFile primaryFile) if (primaryFile == null) throw new ArgumentNullException("primaryFile"); - primaryFile.ForceInitializeView(this); // call Load() + LoadModel(); // call Load() EDMDesignerChangeWatcher.AddEDMDesignerViewContent(this); } @@ -200,7 +200,7 @@ public bool ObjectChanged(object changedObject) foreach (DesignerView designerView in _edmView.DesignerViews) { foreach (ITypeDesigner uiType in designerView) { if (uiType == changedObject || uiType.UIType.BusinessInstance == changedObject) { - PrimaryFile.IsDirty = true; + MakeDirty(PrimaryFile); return true; } } diff --git a/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/DesignerViewContent.cs b/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/DesignerViewContent.cs index 838ff884efe..a6b5e5786d7 100644 --- a/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/DesignerViewContent.cs +++ b/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/DesignerViewContent.cs @@ -364,7 +364,7 @@ IProject GetProjectForFile() void MakeDirty() { hasUnmergedChanges = true; - this.DesignerCodeFile.MakeDirty(); + MakeDirty(this.DesignerCodeFile); this.resourceStore.MarkResourceFilesAsDirty(); System.Windows.Input.CommandManager.InvalidateRequerySuggested(); } diff --git a/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/Gui/ImageResourceEditorDialog.cs b/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/Gui/ImageResourceEditorDialog.cs index fa44cd97e97..db92917287a 100644 --- a/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/Gui/ImageResourceEditorDialog.cs +++ b/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/Gui/ImageResourceEditorDialog.cs @@ -397,7 +397,7 @@ Dictionary GetResources(FileName fileName) SD.MainThread.InvokeIfRequired(delegate { OpenedFile file = SD.FileService.GetOpenedFile(fileName); if (file != null) { - s = file.OpenRead(); + s = file.GetModel(FileModels.Binary).OpenRead(); } }); if (s == null) { diff --git a/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/Services/ProjectResourceInfo.cs b/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/Services/ProjectResourceInfo.cs index f799a01d07d..f3244543045 100644 --- a/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/Services/ProjectResourceInfo.cs +++ b/src/AddIns/DisplayBindings/FormsDesigner/Project/Src/Services/ProjectResourceInfo.cs @@ -79,7 +79,7 @@ public ProjectResourceInfo(FileName resourceFile, string resourceKey) OpenedFile openedFile = SD.FileService.GetOpenedFile(resourceFile); Stream s; if (openedFile != null) { - s = openedFile.OpenRead(); + s = openedFile.GetModel(FileModels.Binary).OpenRead(); } else { s = new FileStream(resourceFile, FileMode.Open, FileAccess.Read, FileShare.Read); } diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs index a948f8e83d3..18a239e8388 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs @@ -136,7 +136,8 @@ public void Redo() void DocumentChanged(object sender, EventArgs e) { - if (PrimaryFile != null) PrimaryFile.MakeDirty(); + if (PrimaryFile != null) + MakeDirty(PrimaryFile); } } } diff --git a/src/AddIns/DisplayBindings/IconEditor/IconViewContent.cs b/src/AddIns/DisplayBindings/IconEditor/IconViewContent.cs index f868ed57773..dddd1bea60e 100644 --- a/src/AddIns/DisplayBindings/IconEditor/IconViewContent.cs +++ b/src/AddIns/DisplayBindings/IconEditor/IconViewContent.cs @@ -27,7 +27,7 @@ namespace ICSharpCode.IconEditor { - public class IconViewContent : AbstractViewContent + public class IconViewContent : AbstractViewContentSD1234 { EditorPanel editor = new EditorPanel(); @@ -44,7 +44,7 @@ public IconViewContent(OpenedFile file) : base(file) void editor_IconWasEdited(object sender, EventArgs e) { - PrimaryFile.MakeDirty(); + MakeDirty(PrimaryFile); } public override void Load(OpenedFile file, Stream stream) diff --git a/src/AddIns/DisplayBindings/ResourceEditor/Project/Src/DisplayDefinition.cs b/src/AddIns/DisplayBindings/ResourceEditor/Project/Src/DisplayDefinition.cs index 28ad9149daf..15689d1c0ff 100644 --- a/src/AddIns/DisplayBindings/ResourceEditor/Project/Src/DisplayDefinition.cs +++ b/src/AddIns/DisplayBindings/ResourceEditor/Project/Src/DisplayDefinition.cs @@ -73,7 +73,7 @@ public override bool IsReadOnly { void SetDirty(object sender, EventArgs e) { - this.PrimaryFile.MakeDirty(); + MakeDirty(this.PrimaryFile); } public ResourceEditWrapper(OpenedFile file) diff --git a/src/AddIns/DisplayBindings/SettingsEditor/Project/SettingsViewContent.cs b/src/AddIns/DisplayBindings/SettingsEditor/Project/SettingsViewContent.cs index 409faf38df0..ea8b416f4ef 100644 --- a/src/AddIns/DisplayBindings/SettingsEditor/Project/SettingsViewContent.cs +++ b/src/AddIns/DisplayBindings/SettingsEditor/Project/SettingsViewContent.cs @@ -55,9 +55,9 @@ public SettingsViewContent(OpenedFile file) : base(file) view.SettingsChanged += ((s,e) => { if (this.PrimaryFile != null) - this.PrimaryFile.MakeDirty(); + MakeDirty(this.PrimaryFile); if (appConfigFile != null) - appConfigFile.MakeDirty(); + MakeDirty(appConfigFile); }); } @@ -74,11 +74,11 @@ void TryOpenAppConfig(bool createIfNotExists) return; FileName appConfigFileName = CompilableProject.GetAppConfigFile(p, createIfNotExists); if (appConfigFileName != null) { - appConfigFile = SD.FileService.GetOrCreateOpenedFile(appConfigFileName); + appConfigFile = SD.FileService.CreateOpenedFile(appConfigFileName); this.Files.Add(appConfigFile); + ForceInitializeView(appConfigFile); if (createIfNotExists) - appConfigFile.MakeDirty(); - appConfigFile.ForceInitializeView(this); + MakeDirty(appConfigFile); } } diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.AddIn/Src/WpfViewContent.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.AddIn/Src/WpfViewContent.cs index 8a367994636..be7eddac763 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.AddIn/Src/WpfViewContent.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.AddIn/Src/WpfViewContent.cs @@ -181,7 +181,7 @@ void UpdateTasks(XamlErrorService xamlErrorService) void OnUndoStackChanged(object sender, EventArgs e) { wasChangedInDesigner = true; - this.PrimaryFile.MakeDirty(); + MakeDirty(PrimaryFile); } #region Property editor / SelectionChanged diff --git a/src/AddIns/Misc/Reports/ICSharpCode.Reports.Addin/Project/ReportDesignerView.cs b/src/AddIns/Misc/Reports/ICSharpCode.Reports.Addin/Project/ReportDesignerView.cs index 0bc75ae00d7..c975b9a6d60 100644 --- a/src/AddIns/Misc/Reports/ICSharpCode.Reports.Addin/Project/ReportDesignerView.cs +++ b/src/AddIns/Misc/Reports/ICSharpCode.Reports.Addin/Project/ReportDesignerView.cs @@ -388,7 +388,7 @@ void OnComponentListChanged(object sender, EventArgs e) private void MakeDirty() { hasUnmergedChanges = true; - this.PrimaryFile.MakeDirty(); + MakeDirty(this.PrimaryFile); } #endregion diff --git a/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs b/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs index 5583413bdd9..63cdf6f6c8c 100644 --- a/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs +++ b/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs @@ -72,12 +72,10 @@ class SearchableFileContentFinder static ITextSource ReadFile(FileName fileName) { OpenedFile openedFile = SD.FileService.GetOpenedFile(fileName); - if (openedFile == null || openedFile.CurrentView == null) + if (openedFile == null) return null; - var provider = openedFile.CurrentView.GetService(); - if (provider == null) - return null; - IDocument doc = provider.GetDocumentForFile(openedFile); + // Get document, but only if it's already loaded: + IDocument doc = openedFile.GetModel(FileModels.TextDocument, GetModelOptions.DoNotLoad); if (doc == null) return null; return doc.CreateSnapshot(); diff --git a/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs b/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs index 5f898586c7e..d023e7765be 100644 --- a/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs +++ b/src/Main/Base/Project/Workbench/AbstractViewContentHandlingLoadErrors.cs @@ -26,7 +26,6 @@ namespace ICSharpCode.SharpDevelop.Workbench { - /* /// /// This class handles errors in the Load method and prevents destroying invalid files. /// Scenario: @@ -39,7 +38,7 @@ namespace ICSharpCode.SharpDevelop.Workbench /// holding the invalid data that got copied from the text editor to the resource editor in memory. /// So saving during a load error works as expected. /// - public abstract class AbstractViewContentHandlingLoadErrors : AbstractViewContent + public abstract class AbstractViewContentHandlingLoadErrors : AbstractViewContentSD1234 { ContentPresenter contentControl = new ContentPresenter(); object userContent; @@ -68,9 +67,34 @@ protected object UserContent { } } + public bool HasLoadError { + get { + return errorList.Count > 0; + } + } + + class LoadError + { + internal Exception exception; + internal byte[] fileData; + + public LoadError(Exception exception, Stream stream) + { + this.exception = exception; + stream.Position = 0; + this.fileData = new byte[(int)stream.Length]; + int pos = 0; + while (pos < fileData.Length) { + int c = stream.Read(fileData, pos, fileData.Length - pos); + if (c == 0) break; + pos += c; + } + } + } + TextBox errorTextBox; - protected void ShowError(Exception ex) + void ShowError(Exception ex) { if (errorTextBox == null) { errorTextBox = new TextBox(); @@ -81,6 +105,8 @@ protected void ShowError(Exception ex) SD.WinForms.SetContent(contentControl, errorTextBox, this); } + Dictionary errorList = new Dictionary(); + /// /// Gets a text to be shown above the exception when a load error occurs. /// The default is an empty string. @@ -88,6 +114,36 @@ protected void ShowError(Exception ex) protected virtual string LoadErrorHeaderText { get { return String.Empty; } } + + public override sealed void Load(OpenedFile file, Stream stream) + { + try { + LoadInternal(file, new UnclosableStream(stream)); + if (errorList.Count > 0) { + errorList.Remove(file); + if (errorList.Count == 0) { + SD.WinForms.SetContent(contentControl, userContent, this); + } else { + ShowError(errorList.Values.First().exception); + } + } + } catch (Exception ex) { + errorList[file] = new LoadError(ex, stream); + ShowError(ex); + } + } + + public override sealed void Save(OpenedFile file, Stream stream) + { + if (errorList.ContainsKey(file)) { + byte[] data = errorList[file].fileData; + stream.Write(data, 0, data.Length); + } else { + SaveInternal(file, stream); + } + } + + protected abstract void LoadInternal(OpenedFile file, Stream stream); + protected abstract void SaveInternal(OpenedFile file, Stream stream); } - */ } diff --git a/src/Main/Base/Project/Workbench/AbstractViewContentSD1234.cs b/src/Main/Base/Project/Workbench/AbstractViewContentSD1234.cs index 3e889eb774f..44040087ac6 100644 --- a/src/Main/Base/Project/Workbench/AbstractViewContentSD1234.cs +++ b/src/Main/Base/Project/Workbench/AbstractViewContentSD1234.cs @@ -76,6 +76,31 @@ void IFileModelProvider.NotifyUnloaded(OpenedFile fil Provider provider = new Provider(); // each AbstractViewContent gets its own provider instance bool isStale = true; + protected AbstractViewContentSD1234() + { + } + + protected AbstractViewContentSD1234(OpenedFile file) : base(file) + { + } + + protected void MakeDirty(OpenedFile file) + { + file.MakeDirty(provider); + } + + /// + /// Calls Load() for the specified file only. + /// This is useful after adding a new file to the Files collection. + /// + protected void ForceInitializeView(OpenedFile file) + { + using (Stream s = file.GetModel(FileModels.Binary).OpenRead()) { + Load(file, s); + } + file.ReplaceModel(provider, this, ReplaceModelMode.TransferDirty); + } + public abstract void Save(OpenedFile file, Stream stream); public abstract void Load(OpenedFile file, Stream stream); diff --git a/src/Main/Base/Project/Workbench/File/OpenedFile.cs b/src/Main/Base/Project/Workbench/File/OpenedFile.cs index 82c99c1be03..ee79553a21d 100644 --- a/src/Main/Base/Project/Workbench/File/OpenedFile.cs +++ b/src/Main/Base/Project/Workbench/File/OpenedFile.cs @@ -78,7 +78,7 @@ public enum ReplaceModelMode /// View contents must maintain a reference to their opened files /// (this usually happens via the AbstractViewContent.Files collection). /// - public abstract class OpenedFile : ICanBeDirty + public class OpenedFile : ICanBeDirty { abstract class ModelEntry { @@ -394,7 +394,8 @@ ModelEntry PickValidEntry() /// If multiple valid models exist, one is picked at random. /// /// - /// This method is used when SharpDevelop detects + /// This method is used when SharpDevelop detects that the file was changed externally; + /// but the user does not want to reload the file. /// public void MakeDirty() { diff --git a/src/Main/Base/Test/Utils/MockOpenedFile.cs b/src/Main/Base/Test/Utils/MockOpenedFile.cs index c2ede8c0b7a..42731e6384a 100644 --- a/src/Main/Base/Test/Utils/MockOpenedFile.cs +++ b/src/Main/Base/Test/Utils/MockOpenedFile.cs @@ -31,43 +31,8 @@ public class MockOpenedFile : OpenedFile { public MockOpenedFile(string fileName) { - base.FileName = new ICSharpCode.Core.FileName(fileName); - base.IsUntitled = true; - SetData(new byte[0]); + base.FileName = new ICSharpCode.Core.FileName("untitled:" + fileName); + ReplaceModel(FileModels.Binary, new BinaryFileModel(new byte[0])); } - - List registeredViews = new List(); - - public override IList RegisteredViewContents { - get { return registeredViews.AsReadOnly(); } - } - - public override void RegisterView(IViewContent view) - { - if (view == null) - throw new ArgumentNullException("view"); - if (registeredViews.Contains(view)) - throw new ArgumentException("registeredViews already contains view"); - - registeredViews.Add(view); - - if (registeredViews.Count == 1) - SwitchedToView(registeredViews[0]); - } - - public override void UnregisterView(IViewContent view) - { - if (!registeredViews.Remove(view)) - throw new ArgumentException("registeredViews does not contain view"); - } - - public void SwitchToView(IViewContent view) - { - if (!registeredViews.Contains(view)) - throw new ArgumentException("registeredViews does not contain view"); - base.SwitchedToView(view); - } - - public override event EventHandler FileClosed { add {} remove {} } } } From 326c4602522e811e7daf5d1c80d5bdfbfa2991ac Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 21 Feb 2014 13:12:03 +0100 Subject: [PATCH 10/13] OpenedFile: add missing NotifyLoaded() calls. --- src/Main/Base/Project/Workbench/File/OpenedFile.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Main/Base/Project/Workbench/File/OpenedFile.cs b/src/Main/Base/Project/Workbench/File/OpenedFile.cs index ee79553a21d..5939a40c818 100644 --- a/src/Main/Base/Project/Workbench/File/OpenedFile.cs +++ b/src/Main/Base/Project/Workbench/File/OpenedFile.cs @@ -362,6 +362,7 @@ public T GetModel(IFileModelProvider modelProvider, GetModelOptions option // No entry for the model provider exists; we need to create a new one. entry = new ModelEntry(modelProvider, model); entries.Add(entry); + entry.NotifyLoaded(this); } else if (entry.Model == model) { // The existing stale model was reused entry.IsStale = false; @@ -370,6 +371,7 @@ public T GetModel(IFileModelProvider modelProvider, GetModelOptions option entry.NotifyUnloaded(this); entry.Model = model; entry.IsStale = false; + entry.NotifyLoaded(this); } return model; } From fe5fce98ca08421819924b5bce720eb4df97b1df Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 21 Feb 2014 20:23:09 +0100 Subject: [PATCH 11/13] implement HexEditFileModelProvider --- .../HexEditor/Project/HexEditor.csproj | 2 +- .../HexEditor/Project/Src/Editor.Designer.cs | 21 +- .../HexEditor/Project/Src/Editor.cs | 967 ++++++++---------- .../Project/Src/Util/BufferManager.cs | 194 ++-- .../HexEditor/Project/Src/Util/Caret.cs | 2 +- .../Project/Src/Util/ClipboardManager.cs | 64 -- .../Src/Util/HexEditFileModelProvider.cs | 84 ++ .../Project/Src/Util/SelectionManager.cs | 30 +- .../HexEditor/Project/Src/Util/UndoManager.cs | 4 +- .../Src/View/HexEditContainer.Designer.cs | 13 +- .../Project/Src/View/HexEditContainer.cs | 27 +- .../HexEditor/Project/Src/View/HexEditView.cs | 43 +- 12 files changed, 662 insertions(+), 789 deletions(-) delete mode 100644 src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/ClipboardManager.cs create mode 100644 src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/HexEditFileModelProvider.cs diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/HexEditor.csproj b/src/AddIns/DisplayBindings/HexEditor/Project/HexEditor.csproj index 725f4393f34..7653278814c 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/HexEditor.csproj +++ b/src/AddIns/DisplayBindings/HexEditor/Project/HexEditor.csproj @@ -82,7 +82,7 @@ - + diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.Designer.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.Designer.cs index d3a4e335fbb..e3c1e6893d3 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.Designer.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.Designer.cs @@ -55,11 +55,10 @@ private void InitializeComponent() // // VScrollBar // - this.VScrollBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Right))); - this.VScrollBar.Location = new System.Drawing.Point(670, 0); + this.VScrollBar.Dock = System.Windows.Forms.DockStyle.Right; + this.VScrollBar.Location = new System.Drawing.Point(672, 0); this.VScrollBar.Name = "VScrollBar"; - this.VScrollBar.Size = new System.Drawing.Size(18, 365); + this.VScrollBar.Size = new System.Drawing.Size(16, 365); this.VScrollBar.TabIndex = 9; this.VScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.VScrollBarScroll); // @@ -72,30 +71,30 @@ private void InitializeComponent() this.textView.Name = "textView"; this.textView.Size = new System.Drawing.Size(108, 347); this.textView.TabIndex = 12; - this.textView.MouseMove += new System.Windows.Forms.MouseEventHandler(this.TextViewMouseMove); this.textView.MouseClick += new System.Windows.Forms.MouseEventHandler(this.TextViewMouseClick); this.textView.MouseDown += new System.Windows.Forms.MouseEventHandler(this.TextViewMouseDown); + this.textView.MouseMove += new System.Windows.Forms.MouseEventHandler(this.TextViewMouseMove); this.textView.MouseUp += new System.Windows.Forms.MouseEventHandler(this.TextViewMouseUp); // // hexView // this.hexView.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left))); + | System.Windows.Forms.AnchorStyles.Left))); this.hexView.Cursor = System.Windows.Forms.Cursors.IBeam; this.hexView.Location = new System.Drawing.Point(92, 18); this.hexView.MinimumSize = new System.Drawing.Size(1, 1); this.hexView.Name = "hexView"; this.hexView.Size = new System.Drawing.Size(463, 347); this.hexView.TabIndex = 11; - this.hexView.MouseMove += new System.Windows.Forms.MouseEventHandler(this.HexViewMouseMove); this.hexView.MouseClick += new System.Windows.Forms.MouseEventHandler(this.HexViewMouseClick); this.hexView.MouseDown += new System.Windows.Forms.MouseEventHandler(this.HexViewMouseDown); + this.hexView.MouseMove += new System.Windows.Forms.MouseEventHandler(this.HexViewMouseMove); this.hexView.MouseUp += new System.Windows.Forms.MouseEventHandler(this.HexViewMouseUp); // // side // this.side.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left))); + | System.Windows.Forms.AnchorStyles.Left))); this.side.Location = new System.Drawing.Point(0, 0); this.side.MinimumSize = new System.Drawing.Size(1, 1); this.side.Name = "side"; @@ -122,12 +121,12 @@ private void InitializeComponent() this.MinimumSize = new System.Drawing.Size(1, 1); this.Name = "Editor"; this.Size = new System.Drawing.Size(688, 365); - this.Paint += new System.Windows.Forms.PaintEventHandler(this.HexEditPaint); this.ContextMenuStripChanged += new System.EventHandler(this.HexEditControlContextMenuStripChanged); - this.GotFocus += new System.EventHandler(this.HexEditGotFocus); - this.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.HexEditKeyPress); this.SizeChanged += new System.EventHandler(this.HexEditSizeChanged); + this.Paint += new System.Windows.Forms.PaintEventHandler(this.HexEditPaint); + this.GotFocus += new System.EventHandler(this.HexEditGotFocus); this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HexEditKeyDown); + this.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.HexEditKeyPress); this.ResumeLayout(false); } private System.Windows.Forms.Panel header; diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.cs index f558f6d59b8..c5f7bfe230a 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.cs @@ -17,19 +17,17 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections; +using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; +using System.Linq; using System.Text; using System.Windows.Forms; -using System.Xml; - +using ICSharpCode.SharpDevelop; using HexEditor.Util; using ICSharpCode.Core; -using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.WinForms; using ICSharpCode.SharpDevelop.Workbench; @@ -43,19 +41,14 @@ namespace HexEditor public partial class Editor : UserControl { // TODO : Make big files compatible (data structures are bad) - + /// /// number of the first visible line (first line = 0) /// int topline; - - public int TopLine { - get { return topline; } - set { topline = value; } - } int charwidth, hexinputmodepos; int underscorewidth, underscorewidth3, fontheight; - bool insertmode, hexinputmode, selectionmode, handled, moved; + bool insertmode, hexinputmode, selectionmode, moved; public bool Initializing { get; set; } @@ -72,7 +65,7 @@ public Caret Caret { SelectionManager selection; UndoManager undoStack; - + Panel activeView; public Panel ActiveView { @@ -91,7 +84,7 @@ public Panel ActiveView { public event EventHandler DocumentChanged; /// - /// On-method for DocumentChanged-event. + /// Fires the event. /// /// The eventargs for the event protected virtual void OnDocumentChanged(EventArgs e) @@ -111,29 +104,42 @@ public Editor() // InitializeComponent(); - buffer = new BufferManager(this); - selection = new SelectionManager(ref buffer); - undoStack = new UndoManager(); insertmode = true; - underscorewidth = MeasureStringWidth(this.CreateGraphics(), "_", Settings.DataFont); + underscorewidth = MeasureStringWidth(CreateGraphics(), "_", Settings.DataFont); underscorewidth3 = underscorewidth * 3; fontheight = GetFontHeight(Settings.DataFont); - selregion = new Rectangle[] {}; - selpoints = new Point[] {}; - headertext = GetHeaderText(); + selregion = new Rectangle[0]; + selpoints = new Point[0]; this.ActiveView = this.hexView; - UpdatePainters(); - caret = new Caret(this.gbHex, 1, fontheight, 0); + } + + void SetBuffer(BufferManager buffer) + { + if (this.buffer != null) + this.buffer.BufferChanged -= BufferChanged; + this.buffer = buffer; + buffer.BufferChanged += BufferChanged; + + selection = new SelectionManager(buffer); + undoStack = new UndoManager(); + + headertext = GetHeaderText(); HexEditSizeChanged(null, EventArgs.Empty); AdjustScrollBar(); - this.Invalidate(); + Invalidate(); } - + + void BufferChanged(object sender, EventArgs e) + { + Invalidate(); + OnDocumentChanged(EventArgs.Empty); + } + #region Measure functions static int GetFontHeight(Font font) { @@ -141,15 +147,15 @@ static int GetFontHeight(Font font) int height2 = (int)Math.Ceiling(font.GetHeight()); return Math.Max(height1, height2) + 1; } - + static int MeasureStringWidth(Graphics g, string word, Font font) { return TextRenderer.MeasureText(g, word, font, new Size(short.MaxValue, short.MaxValue), - TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix | - TextFormatFlags.PreserveGraphicsClipping).Width; + TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix | + TextFormatFlags.PreserveGraphicsClipping).Width; } #endregion - + /// /// used to store headertext for calculation. /// @@ -172,34 +178,20 @@ protected override bool IsInputKey(Keys keyData) } return false; } - + #region Properties ViewMode viewMode = ViewMode.Hexadecimal; int bytesPerLine = 16; bool fitToWindowWidth; string fileName; Encoding encoding = Encoding.Default; - - /// - /// ProgressBar used to display the progress of loading saving, outside of the control. - /// - /// Currently not in use - private ToolStripProgressBar progressBar; - - /// - /// ProgressBar used to display the progress of loading saving, outside of the control. - /// - /// Currently not in use - public ToolStripProgressBar ProgressBar { - get { return progressBar; } - set { progressBar = value; } - } /// /// Represents the current buffer of the editor. /// public BufferManager Buffer { get { return buffer; } + set { SetBuffer(value); } } /// @@ -219,10 +211,10 @@ public UndoManager UndoStack { new public bool Enabled { get { return base.Enabled; } set { - if (this.InvokeRequired) { - base.Enabled = this.VScrollBar.Enabled = this.hexView.Enabled = this.textView.Enabled = this.side.Enabled = this.header.Enabled = value; + if (InvokeRequired) { + base.Enabled = VScrollBar.Enabled = hexView.Enabled = textView.Enabled = side.Enabled = header.Enabled = value; } else { - base.Enabled = this.VScrollBar.Enabled = this.hexView.Enabled = this.textView.Enabled = this.side.Enabled = this.header.Enabled = value; + base.Enabled = VScrollBar.Enabled = hexView.Enabled = textView.Enabled = side.Enabled = header.Enabled = value; } } } @@ -252,10 +244,10 @@ public Font DataFont { get { return Settings.DataFont; } set { Settings.DataFont = value; - underscorewidth = MeasureStringWidth(this.CreateGraphics(), "_", value); + underscorewidth = MeasureStringWidth(CreateGraphics(), "_", value); underscorewidth3 = underscorewidth * 3; fontheight = GetFontHeight(value); - this.Invalidate(); + Invalidate(); } } @@ -266,18 +258,16 @@ public Font OffsetFont { get { return Settings.OffsetFont; } set { Settings.OffsetFont = value; - underscorewidth = MeasureStringWidth(this.CreateGraphics(), "_", value); + underscorewidth = MeasureStringWidth(CreateGraphics(), "_", value); underscorewidth3 = underscorewidth * 3; fontheight = GetFontHeight(value); - this.Invalidate(); + Invalidate(); } } new public Font Font { get { return null; } - set { - - } + set { } } /// @@ -289,10 +279,8 @@ public ViewMode ViewMode set { viewMode = value; UpdateViews(); - - this.headertext = GetHeaderText(); - - this.Invalidate(); + headertext = GetHeaderText(); + Invalidate(); } } @@ -304,7 +292,7 @@ public bool FitToWindowWidth get { return fitToWindowWidth; } set { fitToWindowWidth = value; - if (value) this.BytesPerLine = CalculateMaxBytesPerLine(); + if (value) BytesPerLine = CalculateMaxBytesPerLine(); } } @@ -320,9 +308,9 @@ public int BytesPerLine bytesPerLine = value; UpdateViews(); - this.headertext = GetHeaderText(); + headertext = GetHeaderText(); - this.Invalidate(); + Invalidate(); } } @@ -333,14 +321,14 @@ public int BytesPerLine string GetHeaderText() { StringBuilder text = new StringBuilder(); - for (int i = 0; i < this.BytesPerLine; i++) { - switch (this.ViewMode) { + for (int i = 0; i < BytesPerLine; i++) { + switch (ViewMode) { case ViewMode.Decimal: text.Append(' ', 3 - GetLength(i)); text.Append(i.ToString()); break; case ViewMode.Hexadecimal: - text.Append(' ', 3 - string.Format("{0:X}", i).Length); + text.Append(' ', 3 - GetLength(i, 16)); text.AppendFormat("{0:X}", i); break; case ViewMode.Octal: @@ -368,7 +356,7 @@ string GetHeaderText() /// void AdjustScrollBar() { - int linecount = this.GetMaxLines(); + int linecount = GetMaxLines(); if (linecount > GetMaxVisibleLines()) { // Set Vertical scrollbar @@ -387,11 +375,11 @@ void AdjustScrollBar() void VScrollBarScroll(object sender, ScrollEventArgs e) { UpdateViews(); - this.topline = VScrollBar.Value; + topline = VScrollBar.Value; Point pos = GetPositionForOffset(caret.Offset, charwidth); caret.SetToPosition(pos); - this.Invalidate(); + Invalidate(); } /// @@ -400,25 +388,25 @@ void VScrollBarScroll(object sender, ScrollEventArgs e) protected override void OnMouseWheel(MouseEventArgs e) { base.OnMouseWheel(e); - - if (!this.VScrollBar.Enabled) return; - + + if (!VScrollBar.Enabled) return; + int delta = -(e.Delta / 3 / 10); int oldvalue = 0; - + if ((VScrollBar.Value + delta) > VScrollBar.Maximum) { oldvalue = VScrollBar.Value; VScrollBar.Value = VScrollBar.Maximum; - this.VScrollBarScroll(null, new ScrollEventArgs(ScrollEventType.Last, oldvalue, VScrollBar.Value, ScrollOrientation.VerticalScroll)); + VScrollBarScroll(null, new ScrollEventArgs(ScrollEventType.Last, oldvalue, VScrollBar.Value, ScrollOrientation.VerticalScroll)); } else if ((VScrollBar.Value + delta) < VScrollBar.Minimum) { oldvalue = VScrollBar.Value; VScrollBar.Value = 0; - this.VScrollBarScroll(null, new ScrollEventArgs(ScrollEventType.First, oldvalue, VScrollBar.Value, ScrollOrientation.VerticalScroll)); + VScrollBarScroll(null, new ScrollEventArgs(ScrollEventType.First, oldvalue, VScrollBar.Value, ScrollOrientation.VerticalScroll)); } else { oldvalue = VScrollBar.Value; VScrollBar.Value += delta; - if (delta > 0) this.VScrollBarScroll(null, new ScrollEventArgs(ScrollEventType.SmallIncrement, oldvalue, VScrollBar.Value, ScrollOrientation.VerticalScroll)); - if (delta < 0) this.VScrollBarScroll(null, new ScrollEventArgs(ScrollEventType.SmallDecrement, oldvalue, VScrollBar.Value, ScrollOrientation.VerticalScroll)); + if (delta > 0) VScrollBarScroll(null, new ScrollEventArgs(ScrollEventType.SmallIncrement, oldvalue, VScrollBar.Value, ScrollOrientation.VerticalScroll)); + if (delta < 0) VScrollBarScroll(null, new ScrollEventArgs(ScrollEventType.SmallDecrement, oldvalue, VScrollBar.Value, ScrollOrientation.VerticalScroll)); } } @@ -427,14 +415,14 @@ protected override void OnMouseWheel(MouseEventArgs e) /// void HexViewMouseClick(object sender, MouseEventArgs e) { - this.Focus(); - this.ActiveView = this.hexView; - this.charwidth = 3; - this.caret.Width = 1; + Focus(); + ActiveView = hexView; + charwidth = 3; + caret.Width = 1; if (!insertmode) - this.caret.Width = underscorewidth * 2; - - caret.Graphics = this.gbHex; + caret.Width = underscorewidth * 2; + + caret.Graphics = gbHex; if (e.Button != MouseButtons.Right) { if (!moved) { @@ -452,13 +440,13 @@ void HexViewMouseClick(object sender, MouseEventArgs e) /// void TextViewMouseClick(object sender, MouseEventArgs e) { - this.Focus(); + Focus(); hexinputmode = false; - this.ActiveView = this.textView; - this.charwidth = 1; - this.caret.Width = 1; - if (!insertmode) this.caret.Width = underscorewidth; - caret.Graphics = this.gbText; + ActiveView = textView; + charwidth = 1; + caret.Width = 1; + if (!insertmode) caret.Width = underscorewidth; + caret.Graphics = gbText; if (e.Button != MouseButtons.Right) { if (!moved) { @@ -470,21 +458,23 @@ void TextViewMouseClick(object sender, MouseEventArgs e) } } - this.Focus(); + Focus(); hexinputmode = false; - this.ActiveView = this.textView; - this.charwidth = 1; - this.caret.Width = 1; - if (!insertmode) this.caret.Width = underscorewidth; + ActiveView = textView; + charwidth = 1; + caret.Width = 1; + if (!insertmode) caret.Width = underscorewidth; } #endregion - + #region Painters /// /// General painting, using double buffering. /// void HexEditPaint(object sender, PaintEventArgs e) { + if (buffer == null) return; + // Refresh selection. CalculateSelectionRegions(); @@ -503,57 +493,57 @@ void HexEditPaint(object sender, PaintEventArgs e) PaintSelection(gbHex, gbText, true); if (activeView == hexView) - this.caret.Graphics = gbHex; + caret.Graphics = gbHex; else - this.caret.Graphics = gbText; + caret.Graphics = gbText; - this.caret.DrawCaret(); + caret.DrawCaret(); // Paint on device ... - this.gHeader.DrawImageUnscaled(bHeader, 0, 0); - this.gSide.DrawImageUnscaled(bSide, 0, 0); - this.gHex.DrawImageUnscaled(bHex, 0, 0); - this.gText.DrawImageUnscaled(bText, 0, 0); + gHeader.DrawImageUnscaled(bHeader, 0, 0); + gSide.DrawImageUnscaled(bSide, 0, 0); + gHex.DrawImageUnscaled(bHex, 0, 0); + gText.DrawImageUnscaled(bText, 0, 0); } /// /// Draws the header text ("Offset 0 1 2 3 ...") /// /// The graphics device to draw on. - void PaintHeader(System.Drawing.Graphics g) + void PaintHeader(Graphics g) { g.Clear(Color.White); - TextRenderer.DrawText(g, headertext, Settings.OffsetFont, new Rectangle(1, 1, this.hexView.Width + 5, fontheight), - Settings.OffsetForeColor.ToSystemDrawing(), this.BackColor, TextFormatFlags.Left & TextFormatFlags.Top); + TextRenderer.DrawText(g, headertext, Settings.OffsetFont, new Rectangle(1, 1, hexView.Width + 5, fontheight), + Settings.OffsetForeColor.ToSystemDrawing(), BackColor, TextFormatFlags.Left & TextFormatFlags.Top); } - + /// /// Draws the offset numbers for each visible line. /// /// The graphics device to draw on. /// The top line to start. - void PaintOffsetNumbers(System.Drawing.Graphics g, int top) + void PaintOffsetNumbers(Graphics g, int top) { g.Clear(Color.White); string text = String.Empty; - int count = top + this.GetMaxVisibleLines(); - + int count = top + GetMaxVisibleLines(); + StringBuilder builder = new StringBuilder(StringParser.Parse("${res:AddIns.HexEditor.Display.Elements.Offset}\n")); if (count == 0) builder.Append("0\n"); - + for (int i = top; i < count; i++) { - if ((i * this.BytesPerLine) <= this.buffer.BufferSize) { - switch (this.ViewMode) { + if ((i * BytesPerLine) <= buffer.BufferSize) { + switch (ViewMode) { case ViewMode.Decimal: - builder.AppendLine((i * this.BytesPerLine).ToString()); + builder.AppendLine((i * BytesPerLine).ToString()); break; case ViewMode.Hexadecimal: - builder.AppendFormat("{0:X}", i * this.BytesPerLine); + builder.AppendFormat("{0:X}", i * BytesPerLine); builder.AppendLine(); break; case ViewMode.Octal: - int tmp = i * this.BytesPerLine; + int tmp = i * BytesPerLine; if (tmp == 0) { builder.AppendLine("0"); } else { @@ -564,17 +554,17 @@ void PaintOffsetNumbers(System.Drawing.Graphics g, int top) } builder.AppendLine(num.ToString()); } - + break; } } } - + text = builder.ToString(); builder = null; - TextRenderer.DrawText(g, text, Settings.OffsetFont,this.side.ClientRectangle, - Settings.OffsetForeColor.ToSystemDrawing(), Color.White, TextFormatFlags.Right); + TextRenderer.DrawText(g, text, Settings.OffsetFont,side.ClientRectangle, + Settings.OffsetForeColor.ToSystemDrawing(), Color.White, TextFormatFlags.Right); } /// @@ -582,19 +572,19 @@ void PaintOffsetNumbers(System.Drawing.Graphics g, int top) /// /// The graphics device to draw on. /// The top line to start. - void PaintHex(System.Drawing.Graphics g, int top) + void PaintHex(Graphics g, int top) { g.Clear(Color.White); StringBuilder builder = new StringBuilder(); - + int offset = GetOffsetForLine(top); - + for (int i = 0; i < GetMaxVisibleLines(); i++) { - builder.AppendLine(GetHex(buffer.GetBytes(offset, this.BytesPerLine))); + builder.AppendLine(GetHex(buffer.GetBytes(offset, BytesPerLine))); offset = GetOffsetForLine(top + i + 1); } - - TextRenderer.DrawText(g, builder.ToString(), Settings.DataFont, new Rectangle(0, 0, this.hexView.Width, this.hexView.Height), Settings.DataForeColor.ToSystemDrawing(), Color.White, TextFormatFlags.Left & TextFormatFlags.Top); + + TextRenderer.DrawText(g, builder.ToString(), Settings.DataFont, new Rectangle(0, 0, hexView.Width, hexView.Height), Settings.DataForeColor.ToSystemDrawing(), Color.White, TextFormatFlags.Left & TextFormatFlags.Top); } /// @@ -602,16 +592,16 @@ void PaintHex(System.Drawing.Graphics g, int top) /// /// The graphics device to draw on. /// The top line to start. - void PaintText(System.Drawing.Graphics g, int top) + void PaintText(Graphics g, int top) { g.Clear(Color.White); - + int offset = GetOffsetForLine(top); StringBuilder builder = new StringBuilder(); - + for (int i = 0; i < GetMaxVisibleLines(); i++) { - builder.AppendLine(GetText(buffer.GetBytes(offset, this.BytesPerLine))); + builder.AppendLine(GetText(buffer.GetBytes(offset, BytesPerLine))); offset = GetOffsetForLine(top + i + 1); } TextRenderer.DrawText(g, builder.ToString(), Settings.DataFont, new Point(0, 0), Settings.DataForeColor.ToSystemDrawing(), Color.White); @@ -622,23 +612,23 @@ void PaintText(System.Drawing.Graphics g, int top) /// /// the graphics device for the hex view panel /// the graphics device for the text view panel - void PaintPointer(System.Drawing.Graphics hexView, System.Drawing.Graphics textView) + void PaintPointer(Graphics hexView, Graphics textView) { // Paint a rectangle as a pointer in the view without the focus ... if (selection.HasSomethingSelected) return; - if (this.ActiveView == this.hexView) { - Point pos = this.GetPositionForOffset(caret.Offset, 1); - if (hexinputmode) pos = this.GetPositionForOffset(caret.Offset - 1, 1); + if (ActiveView == this.hexView) { + Point pos = GetPositionForOffset(caret.Offset, 1); + if (hexinputmode) pos = GetPositionForOffset(caret.Offset - 1, 1); Size size = new Size(underscorewidth, fontheight); Pen p = new Pen(Color.Black, 1f); - p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot; + p.DashStyle = DashStyle.Dot; textView.DrawRectangle(p, new Rectangle(pos, size)); } else { - Point pos = this.GetPositionForOffset(caret.Offset, 3); + Point pos = GetPositionForOffset(caret.Offset, 3); pos.Offset(0, 1); Size size = new Size(underscorewidth * 2, fontheight); Pen p = new Pen(Color.Black, 1f); - p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot; + p.DashStyle = DashStyle.Dot; hexView.DrawRectangle(p, new Rectangle(pos, size)); } } @@ -648,7 +638,11 @@ void PaintPointer(System.Drawing.Graphics hexView, System.Drawing.Graphics textV /// void CalculateSelectionRegions() { - ArrayList al = new ArrayList(); + if (selection == null) { + selpoints = new Point[0]; + selregion = new Rectangle[0]; + return; + } int lines = Math.Abs(GetLineForOffset(selection.End) - GetLineForOffset(selection.Start)); int start, end; @@ -671,164 +665,167 @@ void CalculateSelectionRegions() if (end > GetOffsetForLine(topline + GetMaxVisibleLines())) end = GetOffsetForLine(topline + GetMaxVisibleLines() + 1); int tmp_start = start; - if (((selection.End % this.bytesPerLine) == 0) && (selection.End < selection.Start)) + if (((selection.End % bytesPerLine) == 0) && (selection.End < selection.Start)) tmp_start++; - if (this.activeView == this.hexView) + if (activeView == hexView) { + var rectangles = new List(); + if (GetLineForOffset(end) == GetLineForOffset(tmp_start)) { Point pt = GetPositionForOffset(start, 3); - al.Add(new Rectangle(new Point(pt.X - 4, pt.Y), new Size((end - start) * underscorewidth3 + 2, fontheight))); + rectangles.Add(new Rectangle(new Point(pt.X - 4, pt.Y), new Size((end - start) * underscorewidth3 + 2, fontheight))); } else { // First Line Point pt = GetPositionForOffset(start, 3); - al.Add(new Rectangle(new Point(pt.X - 4, pt.Y), new Size((this.BytesPerLine - (start - this.BytesPerLine * GetLineForOffset(start))) * underscorewidth3 + 2, fontheight))); + rectangles.Add(new Rectangle(new Point(pt.X - 4, pt.Y), new Size((BytesPerLine - (start - BytesPerLine * GetLineForOffset(start))) * underscorewidth3 + 2, fontheight))); // Lines between - Point pt2 = GetPositionForOffset((1 + GetLineForOffset(start)) * this.BytesPerLine, 3); - al.Add(new Rectangle(new Point(pt2.X - 4, pt2.Y), new Size(this.BytesPerLine * underscorewidth3 + 2, fontheight * (lines - 1) - lines + 1))); + Point pt2 = GetPositionForOffset((1 + GetLineForOffset(start)) * BytesPerLine, 3); + rectangles.Add(new Rectangle(new Point(pt2.X - 4, pt2.Y), new Size(BytesPerLine * underscorewidth3 + 2, fontheight * (lines - 1) - lines + 1))); // Last Line - Point pt3 = GetPositionForOffset(GetLineForOffset(end) * this.BytesPerLine, 3); - al.Add(new Rectangle(new Point(pt3.X - 4, pt3.Y), new Size((end - GetLineForOffset(end) * this.BytesPerLine) * underscorewidth3 + 2, fontheight))); + Point pt3 = GetPositionForOffset(GetLineForOffset(end) * BytesPerLine, 3); + rectangles.Add(new Rectangle(new Point(pt3.X - 4, pt3.Y), new Size((end - GetLineForOffset(end) * BytesPerLine) * underscorewidth3 + 2, fontheight))); } - this.selregion = (Rectangle[])al.ToArray(typeof (Rectangle)); + selregion = rectangles.ToArray(); - al.Clear(); + var points = new List(); start = start_dummy; if (GetLineForOffset(end) == GetLineForOffset(tmp_start)) { Point pt = GetPositionForOffset(start, 1); - al.Add(new Point(pt.X - 1, pt.Y)); - al.Add(new Point(pt.X - 1, pt.Y + fontheight)); - al.Add(new Point(pt.X - 1 + (end - start + 1) * underscorewidth - 8, pt.Y + fontheight)); - al.Add(new Point(pt.X - 1 + (end - start + 1) * underscorewidth - 8, pt.Y)); + points.Add(new Point(pt.X - 1, pt.Y)); + points.Add(new Point(pt.X - 1, pt.Y + fontheight)); + points.Add(new Point(pt.X - 1 + (end - start + 1) * underscorewidth - 8, pt.Y + fontheight)); + points.Add(new Point(pt.X - 1 + (end - start + 1) * underscorewidth - 8, pt.Y)); } else { // First Line Point pt = GetPositionForOffset(start, 1); pt = new Point(pt.X - 1, pt.Y); - al.Add(pt); + points.Add(pt); pt = new Point(pt.X, pt.Y + fontheight - 1); - al.Add(pt); - + points.Add(pt); + // Second Line pt = GetPositionForOffset(GetOffsetForLine(GetLineForOffset(start) + 1), 1); pt = new Point(pt.X - 1, pt.Y); - al.Add(pt); - + points.Add(pt); + //last pt = GetPositionForOffset(GetOffsetForLine(GetLineForOffset(end)), 1); - if ((end % this.BytesPerLine) != 0) { + if ((end % BytesPerLine) != 0) { pt = new Point(pt.X - 1, pt.Y + fontheight); } else { pt = new Point(pt.X - 1, pt.Y + fontheight - 1); } - al.Add(pt); + points.Add(pt); - if ((end % this.BytesPerLine) != 0) { + if ((end % BytesPerLine) != 0) { //last pt = GetPositionForOffset(end, 1); pt = new Point(pt.X, pt.Y + fontheight); - al.Add(pt); - + points.Add(pt); + //last pt = GetPositionForOffset(end, 1); pt = new Point(pt.X, pt.Y); - al.Add(pt); - + points.Add(pt); } - + //last - pt = GetPositionForOffset(end + (this.BytesPerLine - (end % this.BytesPerLine)) - 1, 1); + pt = GetPositionForOffset(end + (BytesPerLine - (end % BytesPerLine)) - 1, 1); pt = new Point(pt.X + underscorewidth, pt.Y); - al.Add(pt); - + points.Add(pt); + //last - pt = GetPositionForOffset(end + (this.BytesPerLine - (end % this.BytesPerLine)) - 1, 1); + pt = GetPositionForOffset(end + (BytesPerLine - (end % BytesPerLine)) - 1, 1); pt = new Point(pt.X + underscorewidth, GetPositionForOffset(start, 1).Y); - al.Add(pt); + points.Add(pt); } - selpoints = (Point[])al.ToArray(typeof(Point)); + selpoints = points.ToArray(); } else { + var rectangles = new List(); + if (GetLineForOffset(end) == GetLineForOffset(tmp_start)) { Point pt = GetPositionForOffset(start, 1); - al.Add(new Rectangle(new Point(pt.X - 4, pt.Y), new Size((end - start) * underscorewidth + 3, fontheight))); + rectangles.Add(new Rectangle(new Point(pt.X - 4, pt.Y), new Size((end - start) * underscorewidth + 3, fontheight))); } else { // First Line Point pt = GetPositionForOffset(start, 1); - al.Add(new Rectangle(new Point(pt.X - 4, pt.Y), new Size((this.BytesPerLine - (start - this.BytesPerLine * GetLineForOffset(start))) * underscorewidth + 3, fontheight))); + rectangles.Add(new Rectangle(new Point(pt.X - 4, pt.Y), new Size((BytesPerLine - (start - BytesPerLine * GetLineForOffset(start))) * underscorewidth + 3, fontheight))); // Lines between - Point pt2 = GetPositionForOffset((1 + GetLineForOffset(start)) * this.BytesPerLine, 3); - al.Add(new Rectangle(new Point(pt2.X - 4, pt2.Y), new Size(this.BytesPerLine * underscorewidth + 3, fontheight * (lines - 1) - lines + 1))); + Point pt2 = GetPositionForOffset((1 + GetLineForOffset(start)) * BytesPerLine, 3); + rectangles.Add(new Rectangle(new Point(pt2.X - 4, pt2.Y), new Size(BytesPerLine * underscorewidth + 3, fontheight * (lines - 1) - lines + 1))); // Last Line - Point pt3 = GetPositionForOffset(GetLineForOffset(end) * this.BytesPerLine, 1); - al.Add(new Rectangle(new Point(pt3.X - 4, pt3.Y), new Size((end - GetLineForOffset(end) * this.BytesPerLine) * underscorewidth + 3, fontheight))); + Point pt3 = GetPositionForOffset(GetLineForOffset(end) * BytesPerLine, 1); + rectangles.Add(new Rectangle(new Point(pt3.X - 4, pt3.Y), new Size((end - GetLineForOffset(end) * BytesPerLine) * underscorewidth + 3, fontheight))); } - selregion = (Rectangle[])al.ToArray(typeof(Rectangle)); + selregion = rectangles.ToArray(); - al.Clear(); + var points = new List(); start = start_dummy; if (GetLineForOffset(end) == GetLineForOffset(tmp_start)) { Point pt = GetPositionForOffset(start, 3); - al.Add(new Point(pt.X - 1, pt.Y)); - al.Add(new Point(pt.X - 1, pt.Y + fontheight)); - al.Add(new Point(pt.X - 1 + (end - start) * underscorewidth3 - 5, pt.Y + fontheight)); - al.Add(new Point(pt.X - 1 + (end - start) * underscorewidth3 - 5, pt.Y)); + points.Add(new Point(pt.X - 1, pt.Y)); + points.Add(new Point(pt.X - 1, pt.Y + fontheight)); + points.Add(new Point(pt.X - 1 + (end - start) * underscorewidth3 - 5, pt.Y + fontheight)); + points.Add(new Point(pt.X - 1 + (end - start) * underscorewidth3 - 5, pt.Y)); } else { // First Line Point pt = GetPositionForOffset(start, 3); pt = new Point(pt.X - 1, pt.Y); - al.Add(pt); + points.Add(pt); pt = new Point(pt.X, pt.Y + fontheight - 1); - al.Add(pt); - + points.Add(pt); + // Second Line pt = GetPositionForOffset(GetOffsetForLine(GetLineForOffset(start) + 1), 3); pt = new Point(pt.X - 1, pt.Y); - al.Add(pt); - + points.Add(pt); + //last pt = GetPositionForOffset(GetOffsetForLine(GetLineForOffset(end)), 3); - if ((end % this.BytesPerLine) != 0) { + if ((end % BytesPerLine) != 0) { pt = new Point(pt.X - 1, pt.Y + fontheight); } else { pt = new Point(pt.X - 1, pt.Y + fontheight - 1); } - al.Add(pt); + points.Add(pt); - if ((end % this.BytesPerLine) != 0) { - + if ((end % BytesPerLine) != 0) { + //last pt = GetPositionForOffset(end, 3); pt = new Point(pt.X - 5, pt.Y + fontheight); - al.Add(pt); - + points.Add(pt); + //last pt = GetPositionForOffset(end, 3); pt = new Point(pt.X - 5, pt.Y); - al.Add(pt); + points.Add(pt); } - + //last - pt = GetPositionForOffset(end + (this.BytesPerLine - (end % this.BytesPerLine)) - 1, 3); + pt = GetPositionForOffset(end + (BytesPerLine - (end % BytesPerLine)) - 1, 3); pt = new Point(pt.X - 5 + underscorewidth3, pt.Y); - al.Add(pt); - + points.Add(pt); + //last - pt = GetPositionForOffset(end + (this.BytesPerLine - (end % this.BytesPerLine)) - 1, 3); + pt = GetPositionForOffset(end + (BytesPerLine - (end % BytesPerLine)) - 1, 3); pt = new Point(pt.X - 5 + underscorewidth3, GetPositionForOffset(start, 3).Y); - al.Add(pt); + points.Add(pt); } - selpoints = (Point[])al.ToArray(typeof(Point)); + selpoints = points.ToArray(); } } @@ -858,7 +855,7 @@ void PaintSelection(Graphics hexView, Graphics textView, bool paintMarker) if (start < GetOffsetForLine(topline)) start = GetOffsetForLine(topline) - 2; if (end > GetOffsetForLine(topline + GetMaxVisibleLines())) end = GetOffsetForLine(topline + GetMaxVisibleLines() + 1); - if (this.activeView == this.hexView) { + if (activeView == this.hexView) { StringBuilder builder = new StringBuilder(); for (int i = GetLineForOffset(start) + 1; i < GetLineForOffset(end); i++) { @@ -866,14 +863,14 @@ void PaintSelection(Graphics hexView, Graphics textView, bool paintMarker) } if (selregion.Length == 3) { - TextRenderer.DrawText(hexView, GetHex(buffer.GetBytes(start, this.BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); + TextRenderer.DrawText(hexView, GetHex(buffer.GetBytes(start, BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); TextRenderer.DrawText(hexView, builder.ToString(), Settings.DataFont, (Rectangle)selregion[1], Color.White, SystemColors.Highlight, TextFormatFlags.Left); TextRenderer.DrawText(hexView, GetLineHex(GetLineForOffset(end)), Settings.DataFont, (Rectangle)selregion[2], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); } else if (selregion.Length == 2) { - TextRenderer.DrawText(hexView, GetHex(buffer.GetBytes(start, this.BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); + TextRenderer.DrawText(hexView, GetHex(buffer.GetBytes(start, BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); TextRenderer.DrawText(hexView, GetLineHex(GetLineForOffset(end)), Settings.DataFont, (Rectangle)selregion[1], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); } else { - TextRenderer.DrawText(hexView, GetHex(buffer.GetBytes(start, this.BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); + TextRenderer.DrawText(hexView, GetHex(buffer.GetBytes(start, BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); } } else { StringBuilder builder = new StringBuilder(); @@ -883,14 +880,14 @@ void PaintSelection(Graphics hexView, Graphics textView, bool paintMarker) } if (selregion.Length == 3) { - TextRenderer.DrawText(textView, GetText(buffer.GetBytes(start, this.BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); + TextRenderer.DrawText(textView, GetText(buffer.GetBytes(start, BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); TextRenderer.DrawText(textView, builder.ToString(), Settings.DataFont, (Rectangle)selregion[1], Color.White, SystemColors.Highlight, TextFormatFlags.Left); TextRenderer.DrawText(textView, GetLineText(GetLineForOffset(end)), Settings.DataFont, (Rectangle)selregion[2], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); } else if (selregion.Length == 2) { - TextRenderer.DrawText(textView, GetText(buffer.GetBytes(start, this.BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); + TextRenderer.DrawText(textView, GetText(buffer.GetBytes(start, BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); TextRenderer.DrawText(textView, GetLineText(GetLineForOffset(end)), Settings.DataFont, (Rectangle)selregion[1], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); } else { - TextRenderer.DrawText(textView, GetText(buffer.GetBytes(start, this.BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); + TextRenderer.DrawText(textView, GetText(buffer.GetBytes(start, BytesPerLine)), Settings.DataFont, (Rectangle)selregion[0], Color.White, SystemColors.Highlight, TextFormatFlags.Left & TextFormatFlags.SingleLine); } } @@ -898,32 +895,32 @@ void PaintSelection(Graphics hexView, Graphics textView, bool paintMarker) GraphicsPath path = new GraphicsPath(FillMode.Winding); - if (GetLineForOffset(start) == GetLineForOffset(end) || ((start % this.bytesPerLine) == 0 && GetLineForOffset(start) + 1 == GetLineForOffset(end))) { - if (this.selpoints.Length == 8) { - path.AddLine(this.selpoints[0], this.selpoints[1]); - path.AddLine(this.selpoints[3], this.selpoints[4]); - path.AddLine(this.selpoints[4], this.selpoints[5]); - path.AddLine(this.selpoints[5], this.selpoints[0]); + if (GetLineForOffset(start) == GetLineForOffset(end) || ((start % bytesPerLine) == 0 && GetLineForOffset(start) + 1 == GetLineForOffset(end))) { + if (selpoints.Length == 8) { + path.AddLine(selpoints[0], selpoints[1]); + path.AddLine(selpoints[3], selpoints[4]); + path.AddLine(selpoints[4], selpoints[5]); + path.AddLine(selpoints[5], selpoints[0]); } else - path.AddPolygon(this.selpoints); + path.AddPolygon(selpoints); } else { - if ((GetLineForOffset(start) == GetLineForOffset(end) - 1) && (start % this.bytesPerLine >= end % this.bytesPerLine)) { - if (this.selpoints.Length < 8) { - path.AddPolygon(this.selpoints); + if ((GetLineForOffset(start) == GetLineForOffset(end) - 1) && (start % bytesPerLine >= end % bytesPerLine)) { + if (selpoints.Length < 8) { + path.AddPolygon(selpoints); } else { - path.AddLine(this.selpoints[0], this.selpoints[1]); - path.AddLine(this.selpoints[6], this.selpoints[7]); + path.AddLine(selpoints[0], selpoints[1]); + path.AddLine(selpoints[6], selpoints[7]); path.CloseFigure(); - path.AddLine(this.selpoints[2], this.selpoints[3]); - path.AddLine(this.selpoints[4], this.selpoints[5]); + path.AddLine(selpoints[2], selpoints[3]); + path.AddLine(selpoints[4], selpoints[5]); path.CloseFigure(); } } else - path.AddPolygon(this.selpoints); + path.AddPolygon(selpoints); } - if (this.activeView == this.hexView) + if (activeView == this.hexView) textView.DrawPath(Pens.Black, path); else hexView.DrawPath(Pens.Black, path); @@ -932,36 +929,34 @@ void PaintSelection(Graphics hexView, Graphics textView, bool paintMarker) #region Undo/Redo /* - * Undo/Redo handling for the buffer. - * */ + * Undo/Redo handling for the buffer. + * */ public void Redo() { - EventArgs e2 = new EventArgs(); - OnDocumentChanged(e2); - - UndoStep step = undoStack.Redo(ref buffer); + OnDocumentChanged(EventArgs.Empty); + + UndoStep step = undoStack.Redo(buffer); hexinputmode = false; hexinputmodepos = 0; selection.Clear(); - if (step != null) caret.SetToPosition(GetPositionForOffset(step.Start, this.charwidth)); - this.Invalidate(); + if (step != null) caret.SetToPosition(GetPositionForOffset(step.Start, charwidth)); + Invalidate(); } public void Undo() { - EventArgs e2 = new EventArgs(); - OnDocumentChanged(e2); - - UndoStep step = undoStack.Undo(ref buffer); + OnDocumentChanged(EventArgs.Empty); + + UndoStep step = undoStack.Undo(buffer); hexinputmode = false; hexinputmodepos = 0; selection.Clear(); if (step != null) { int offset = step.Start; if (offset > buffer.BufferSize) offset = buffer.BufferSize; - caret.SetToPosition(GetPositionForOffset(offset, this.charwidth)); + caret.SetToPosition(GetPositionForOffset(offset, charwidth)); } - this.Invalidate(); + Invalidate(); } public bool CanUndo { @@ -975,8 +970,8 @@ public bool CanRedo { #region Selection /* - * Selection handling - * */ + * Selection handling + * */ public void SetSelection(int start, int end) { if (start > buffer.BufferSize) start = buffer.BufferSize; @@ -990,7 +985,7 @@ public void SetSelection(int start, int end) CalculateSelectionRegions(); - this.Invalidate(); + Invalidate(); } public bool HasSomethingSelected { @@ -999,14 +994,14 @@ public bool HasSomethingSelected { public void SelectAll() { - SetSelection(0, this.buffer.BufferSize); + SetSelection(0, buffer.BufferSize); } #endregion #region Clipboard Actions /* - * Clipboard handling - * */ + * Clipboard handling + * */ public string Copy() { string text = selection.SelectionText; @@ -1032,35 +1027,33 @@ public void Paste(string text) if (caret.Offset > buffer.BufferSize) caret.Offset = buffer.BufferSize; if (selection.HasSomethingSelected) { byte[] old = selection.GetSelectionBytes(); - int start = selection.Start; - - if (selection.Start > selection.End) start = selection.End; + int start = Math.Min(selection.Start, selection.End); buffer.RemoveBytes(start, Math.Abs(selection.End - selection.Start)); - buffer.SetBytes(start, this.Encoding.GetBytes(text.ToCharArray()), false); - undoStack.AddOverwriteStep(start, this.Encoding.GetBytes(text.ToCharArray()), old); - - caret.Offset = start + ClipboardManager.Paste().Length; + buffer.SetBytes(start, Encoding.GetBytes(text), false); + undoStack.AddOverwriteStep(start, Encoding.GetBytes(text), old); + + caret.Offset = start + SD.Clipboard.GetText().Length; selection.Clear(); } else { - buffer.SetBytes(caret.Offset, this.Encoding.GetBytes(text.ToCharArray()), false); + buffer.SetBytes(caret.Offset, Encoding.GetBytes(text), false); - undoStack.AddRemoveStep(caret.Offset, this.Encoding.GetBytes(text.ToCharArray())); - - caret.Offset += ClipboardManager.Paste().Length; + undoStack.AddRemoveStep(caret.Offset, Encoding.GetBytes(text)); + + caret.Offset += SD.Clipboard.GetText().Length; } - if (GetLineForOffset(caret.Offset) < this.topline) this.topline = GetLineForOffset(caret.Offset); - if (GetLineForOffset(caret.Offset) > this.topline + this.GetMaxVisibleLines() - 2) this.topline = GetLineForOffset(caret.Offset) - this.GetMaxVisibleLines() + 2; - if (this.topline < 0) this.topline = 0; - if (this.topline > VScrollBar.Maximum) { + if (GetLineForOffset(caret.Offset) < topline) topline = GetLineForOffset(caret.Offset); + if (GetLineForOffset(caret.Offset) > topline + GetMaxVisibleLines() - 2) + topline = GetLineForOffset(caret.Offset) - GetMaxVisibleLines() + 2; + if (topline < 0) topline = 0; + if (topline > VScrollBar.Maximum) { AdjustScrollBar(); - if (this.topline > VScrollBar.Maximum) this.topline = VScrollBar.Maximum; + if (topline > VScrollBar.Maximum) topline = VScrollBar.Maximum; } - VScrollBar.Value = this.topline; - this.Invalidate(); + VScrollBar.Value = topline; + Invalidate(); - EventArgs e2 = new EventArgs(); - OnDocumentChanged(e2); + OnDocumentChanged(EventArgs.Empty); } public void Delete() @@ -1075,10 +1068,9 @@ public void Delete() selection.Clear(); } - this.Invalidate(); + Invalidate(); - EventArgs e2 = new EventArgs(); - OnDocumentChanged(e2); + OnDocumentChanged(EventArgs.Empty); } #endregion @@ -1093,11 +1085,11 @@ string GetText(byte[] bytes) for (int i = 0; i < bytes.Length; i++) { if (bytes[i] < 32 || (bytes[i] >= 0x80 && bytes[i] < 0xA0)) bytes[i] = 46; } - - string text = this.Encoding.GetString(bytes); + + string text = Encoding.GetString(bytes); return text.Replace("&", "&&"); } - + /// /// Gets the text from a line. /// @@ -1105,7 +1097,7 @@ string GetText(byte[] bytes) /// A string, which contains the text on the given line. string GetLineText(int line) { - return GetText(buffer.GetBytes(GetOffsetForLine(line), this.BytesPerLine)); + return GetText(buffer.GetBytes(GetOffsetForLine(line), BytesPerLine)); } /// @@ -1115,7 +1107,7 @@ string GetLineText(int line) /// A string, which contains the text on the given line in hex representation. string GetLineHex(int line) { - return GetHex(buffer.GetBytes(GetOffsetForLine(line), this.BytesPerLine)); + return GetHex(buffer.GetBytes(GetOffsetForLine(line), BytesPerLine)); } /// @@ -1137,25 +1129,24 @@ static string GetHex(byte[] bytes) /// void HexEditSizeChanged(object sender, EventArgs e) { - if (this.FitToWindowWidth) this.BytesPerLine = CalculateMaxBytesPerLine(); + if (FitToWindowWidth) BytesPerLine = CalculateMaxBytesPerLine(); - this.Invalidate(); + Invalidate(); UpdateViews(); - } void UpdatePainters() { - gHeader = this.header.CreateGraphics(); - gSide = this.side.CreateGraphics(); - gHex = this.hexView.CreateGraphics(); - gText = this.textView.CreateGraphics(); + gHeader = header.CreateGraphics(); + gSide = side.CreateGraphics(); + gHex = hexView.CreateGraphics(); + gText = textView.CreateGraphics(); // Bitmaps for painting - bHeader = new Bitmap(this.header.Width, this.header.Height, this.gHeader); - bSide = new Bitmap(this.side.Width, this.side.Height, this.gSide); - bHex = new Bitmap(this.hexView.Width, this.hexView.Height, this.gHex); - bText = new Bitmap(this.textView.Width, this.textView.Height, this.gText); + bHeader = new Bitmap(header.Width, header.Height, gHeader); + bSide = new Bitmap(side.Width, side.Height, gSide); + bHex = new Bitmap(hexView.Width, hexView.Height, gHex); + bText = new Bitmap(textView.Width, textView.Height, gText); gbHeader = Graphics.FromImage(bHeader); gbSide = Graphics.FromImage(bSide); @@ -1168,22 +1159,22 @@ void UpdatePainters() /// void UpdateViews() { - this.UpdatePainters(); + UpdatePainters(); - int sidetext = this.GetMaxLines() * this.BytesPerLine; - int textwidth = MeasureStringWidth(this.textView.CreateGraphics(), new string('_', this.BytesPerLine + 1), Settings.DataFont); - int hexwidth = underscorewidth3 * this.BytesPerLine; + int sidetext = GetMaxLines() * BytesPerLine; + int textwidth = MeasureStringWidth(textView.CreateGraphics(), new string('_', BytesPerLine + 1), Settings.DataFont); + int hexwidth = underscorewidth3 * BytesPerLine; int top = hexView.Top; - this.hexView.Top = fontheight - 1; - this.textView.Top = fontheight - 1; - this.header.Top = 0; - this.header.Left = hexView.Left - 10; - this.hexView.Height = this.Height - fontheight + top - 18; - this.textView.Height = this.Height - fontheight + top - 18; - + hexView.Top = fontheight - 1; + textView.Top = fontheight - 1; + header.Top = 0; + header.Left = hexView.Left - 10; + hexView.Height = Height - fontheight + top - 18; + textView.Height = Height - fontheight + top - 18; + string st = String.Empty; - - switch (this.ViewMode) { + + switch (ViewMode) { case ViewMode.Hexadecimal: if (sidetext.ToString().Length < 8) { st = " Offset"; @@ -1201,7 +1192,7 @@ void UpdateViews() tmp = (int)(tmp / 8); } } - + st = " " + st; break; case ViewMode.Decimal: @@ -1212,24 +1203,24 @@ void UpdateViews() } break; } - - this.side.Width = MeasureStringWidth(this.side.CreateGraphics(), st, Settings.OffsetFont); - this.side.Left = 0; - this.hexView.Left = this.side.Width + 10; - - if ((textwidth + hexwidth + 25) > this.Width - this.side.Width) { - this.hexView.Width = this.Width - this.side.Width - textwidth - 30; - this.textView.Width = textwidth; - this.textView.Left = this.Width - textwidth - 16; + + side.Width = MeasureStringWidth(side.CreateGraphics(), st, Settings.OffsetFont); + side.Left = 0; + hexView.Left = side.Width + 10; + + if ((textwidth + hexwidth + 25) > Width - side.Width) { + hexView.Width = Width - side.Width - textwidth - 30; + textView.Width = textwidth; + textView.Left = Width - textwidth - 16; } else { - this.hexView.Width = hexwidth; - this.textView.Width = textwidth; - this.textView.Left = hexwidth + this.hexView.Left + 20; + hexView.Width = hexwidth; + textView.Width = textwidth; + textView.Left = hexwidth + hexView.Left + 20; } - this.caret.SetToPosition(GetPositionForOffset(this.caret.Offset, this.charwidth)); - this.header.Width = this.hexView.Width + 10; - this.header.Height = this.fontheight; + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); + header.Width = hexView.Width + 10; + header.Height = fontheight; AdjustScrollBar(); } @@ -1246,7 +1237,7 @@ void HexEditKeyDown(object sender, KeyEventArgs e) end = selection.Start; } - if (this.activeView != this.hexView) { + if (activeView != hexView) { hexinputmode = false; hexinputmodepos = 0; } @@ -1256,18 +1247,18 @@ void HexEditKeyDown(object sender, KeyEventArgs e) switch (e.KeyCode) { case Keys.Up: - if (this.topline > 0) - this.topline--; + if (topline > 0) + topline--; break; case Keys.Down: - if (this.topline < this.GetMaxLines()) - this.topline++; + if (topline < GetMaxLines()) + topline++; break; } - - this.VScrollBar.Value = this.topline; - - this.handled = true; + + VScrollBar.Value = topline; + + e.Handled = true; } switch (e.KeyCode) { @@ -1278,38 +1269,38 @@ void HexEditKeyDown(object sender, KeyEventArgs e) if (!e.Control) { int oldoffset = caret.Offset; MoveCaret(e); - if (GetLineForOffset(caret.Offset) < this.topline) this.topline = GetLineForOffset(caret.Offset); - if (GetLineForOffset(caret.Offset) > this.topline + this.GetMaxVisibleLines() - 2) this.topline = GetLineForOffset(caret.Offset) - this.GetMaxVisibleLines() + 2; - VScrollBar.Value = this.topline; + if (GetLineForOffset(caret.Offset) < topline) topline = GetLineForOffset(caret.Offset); + if (GetLineForOffset(caret.Offset) > topline + GetMaxVisibleLines() - 2) topline = GetLineForOffset(caret.Offset) - GetMaxVisibleLines() + 2; + VScrollBar.Value = topline; if (e.Shift) { if (selection.HasSomethingSelected) { - this.SetSelection(selection.Start, caret.Offset); + SetSelection(selection.Start, caret.Offset); } else { - this.SetSelection(oldoffset, caret.Offset); + SetSelection(oldoffset, caret.Offset); } } else { - this.selection.Clear(); + selection.Clear(); } - handled = true; + e.Handled = true; } break; case Keys.Insert: insertmode = !insertmode; if (!insertmode) { - if (this.activeView == this.hexView) - this.caret.Width = underscorewidth * 2; + if (activeView == hexView) + caret.Width = underscorewidth * 2; else - this.caret.Width = underscorewidth; + caret.Width = underscorewidth; } else { - this.caret.Width = 1; + caret.Width = 1; } - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); - handled = true; + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); + e.Handled = true; break; case Keys.Back: - handled = true; + e.Handled = true; if (hexinputmode) return; if (selection.HasSomethingSelected) { byte[] bytes = selection.GetSelectionBytes(); @@ -1326,24 +1317,23 @@ void HexEditKeyDown(object sender, KeyEventArgs e) if (buffer.RemoveByte(caret.Offset - 1)) { if (caret.Offset > -1) caret.Offset--; - if (GetLineForOffset(caret.Offset) < this.topline) this.topline = GetLineForOffset(caret.Offset); - if (GetLineForOffset(caret.Offset) > this.topline + this.GetMaxVisibleLines() - 2) this.topline = GetLineForOffset(caret.Offset) - this.GetMaxVisibleLines() + 2; + if (GetLineForOffset(caret.Offset) < topline) topline = GetLineForOffset(caret.Offset); + if (GetLineForOffset(caret.Offset) > topline + GetMaxVisibleLines() - 2) topline = GetLineForOffset(caret.Offset) - GetMaxVisibleLines() + 2; undoStack.AddInsertStep(caret.Offset, new byte[] {b}); } } - EventArgs e2 = new EventArgs(); - OnDocumentChanged(e2); + OnDocumentChanged(EventArgs.Empty); break; case Keys.Delete: - handled = true; + e.Handled = true; if (hexinputmode) return; if (selection.HasSomethingSelected) { byte[] old = selection.GetSelectionBytes(); buffer.RemoveBytes(start, Math.Abs(selection.End - selection.Start)); caret.Offset = selection.Start; - + undoStack.AddInsertStep(selection.Start, old); selection.Clear(); @@ -1354,76 +1344,76 @@ void HexEditKeyDown(object sender, KeyEventArgs e) undoStack.AddInsertStep(caret.Offset, new byte[] {b}); - if (GetLineForOffset(caret.Offset) < this.topline) this.topline = GetLineForOffset(caret.Offset); - if (GetLineForOffset(caret.Offset) > this.topline + this.GetMaxVisibleLines() - 2) this.topline = GetLineForOffset(caret.Offset) - this.GetMaxVisibleLines() + 2; + if (GetLineForOffset(caret.Offset) < topline) topline = GetLineForOffset(caret.Offset); + if (GetLineForOffset(caret.Offset) > topline + GetMaxVisibleLines() - 2) topline = GetLineForOffset(caret.Offset) - GetMaxVisibleLines() + 2; } - e2 = new EventArgs(); - OnDocumentChanged(e2); + OnDocumentChanged(EventArgs.Empty); break; case Keys.CapsLock: case Keys.ShiftKey: case Keys.ControlKey: break; case Keys.Tab: - if (this.activeView == this.hexView) { - this.activeView = this.textView; - this.charwidth = 1; + if (activeView == hexView) { + activeView = textView; + charwidth = 1; } else { - this.activeView = this.hexView; - this.charwidth = 3; + activeView = hexView; + charwidth = 3; } - this.handled = true; + e.Handled = true; break; default: byte asc = (byte)e.KeyValue; if (e.Control) { - handled = true; + e.Handled = true; switch (asc) { - // Ctrl-A is pressed -> select all + // Ctrl-A is pressed -> select all case 65 : - this.SetSelection(0, buffer.BufferSize); + SetSelection(0, buffer.BufferSize); break; // Ctrl-C is pressed -> copy text to ClipboardManager case 67 : - ClipboardManager.Copy(this.Copy()); + SD.Clipboard.SetText(Copy()); break; // Ctrl-V is pressed -> paste from ClipboardManager case 86 : - if (ClipboardManager.ContainsText) { - this.Paste(ClipboardManager.Paste()); - if (GetLineForOffset(caret.Offset) < this.topline) this.topline = GetLineForOffset(caret.Offset); - if (GetLineForOffset(caret.Offset) > this.topline + this.GetMaxVisibleLines() - 2) this.topline = GetLineForOffset(caret.Offset) - this.GetMaxVisibleLines() + 2; - if (this.topline < 0) this.topline = 0; - if (this.topline > VScrollBar.Maximum) { + if (SD.Clipboard.ContainsText()) { + Paste(SD.Clipboard.GetText()); + if (GetLineForOffset(caret.Offset) < topline) topline = GetLineForOffset(caret.Offset); + if (GetLineForOffset(caret.Offset) > topline + GetMaxVisibleLines() - 2) topline = GetLineForOffset(caret.Offset) - GetMaxVisibleLines() + 2; + if (topline < 0) topline = 0; + if (topline > VScrollBar.Maximum) { AdjustScrollBar(); - if (this.topline > VScrollBar.Maximum) this.topline = VScrollBar.Maximum; + if (topline > VScrollBar.Maximum) topline = VScrollBar.Maximum; } - VScrollBar.Value = this.topline; + VScrollBar.Value = topline; } break; // Ctrl-X is pressed -> cut from document case 88 : if (selection.HasSomethingSelected) { - ClipboardManager.Copy(this.Copy()); - this.Delete(); + SD.Clipboard.SetText(Copy()); + Delete(); } break; } break; } - if (this.activeView == this.hexView) { + if (activeView == hexView) { ProcessHexInput(e); - handled = true; + e.Handled = true; return; } - + break; } - if (handled) { - this.Invalidate(); - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); + if (e.Handled) { + e.SuppressKeyPress = true; + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); + SD.MainThread.CallLater(TimeSpan.FromMilliseconds(50), Invalidate); } } @@ -1432,35 +1422,28 @@ void HexEditKeyDown(object sender, KeyEventArgs e) /// void HexEditKeyPress(object sender, KeyPressEventArgs e) { - if (handled) { - handled = false; - } else { - byte[] old = buffer.GetBytes(caret.Offset, 1); - try { - if (selection.HasSomethingSelected) { - Delete(); - buffer.SetByte(caret.Offset, (byte)e.KeyChar, !insertmode); - } else { - buffer.SetByte(caret.Offset, (byte)e.KeyChar, !insertmode); - } - } catch (System.ArgumentOutOfRangeException) {} - caret.Offset++; - if (GetLineForOffset(caret.Offset) < this.topline) this.topline = GetLineForOffset(caret.Offset); - if (GetLineForOffset(caret.Offset) > this.topline + this.GetMaxVisibleLines() - 2) this.topline = GetLineForOffset(caret.Offset) - this.GetMaxVisibleLines() + 2; - VScrollBar.Value = this.topline; + byte[] old = buffer.GetBytes(caret.Offset, 1); + try { + if (selection.HasSomethingSelected) { + Delete(); + buffer.SetByte(caret.Offset, (byte)e.KeyChar, !insertmode); + } else { + buffer.SetByte(caret.Offset, (byte)e.KeyChar, !insertmode); + } + } catch (ArgumentOutOfRangeException) {} + caret.Offset++; + if (GetLineForOffset(caret.Offset) < topline) topline = GetLineForOffset(caret.Offset); + if (GetLineForOffset(caret.Offset) > topline + GetMaxVisibleLines() - 2) topline = GetLineForOffset(caret.Offset) - GetMaxVisibleLines() + 2; + VScrollBar.Value = topline; - if (insertmode) - undoStack.AddRemoveStep(caret.Offset - 1, new byte[] {(byte)e.KeyChar}); - else - undoStack.AddOverwriteStep(caret.Offset - 1, new byte[] {(byte)e.KeyChar}, old); + if (insertmode) + undoStack.AddRemoveStep(caret.Offset - 1, new byte[] {(byte)e.KeyChar}); + else + undoStack.AddOverwriteStep(caret.Offset - 1, new byte[] {(byte)e.KeyChar}, old); - OnDocumentChanged(EventArgs.Empty); - } + OnDocumentChanged(EventArgs.Empty); caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); - - - - this.Invalidate(); + Invalidate(); } /// @@ -1475,23 +1458,23 @@ void MoveCaret(KeyEventArgs input) } switch (input.KeyCode) { case Keys.Up: - if (caret.Offset >= this.BytesPerLine) { - caret.Offset -= this.BytesPerLine; - caret.SetToPosition(this.GetPositionForOffset(caret.Offset, this.charwidth)); + if (caret.Offset >= BytesPerLine) { + caret.Offset -= BytesPerLine; + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); } break; case Keys.Down: - if (caret.Offset <= this.Buffer.BufferSize - this.BytesPerLine) { - caret.Offset += this.BytesPerLine; - caret.SetToPosition(this.GetPositionForOffset(caret.Offset, this.charwidth)); + if (caret.Offset <= Buffer.BufferSize - BytesPerLine) { + caret.Offset += BytesPerLine; + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); } else { - caret.Offset = this.Buffer.BufferSize; - caret.SetToPosition(this.GetPositionForOffset(caret.Offset, this.charwidth)); + caret.Offset = Buffer.BufferSize; + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); } break; case Keys.Left: if (caret.Offset >= 1) { - if (this.activeView == this.hexView) { + if (activeView == hexView) { if (input.Control) { hexinputmode = false; if (hexinputmodepos == 0) { @@ -1507,12 +1490,12 @@ void MoveCaret(KeyEventArgs input) } else { caret.Offset--; } - caret.SetToPosition(this.GetPositionForOffset(caret.Offset, this.charwidth)); + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); } break; case Keys.Right: - if (caret.Offset <= this.Buffer.BufferSize - 1) { - if (this.activeView == this.hexView) { + if (caret.Offset <= Buffer.BufferSize - 1) { + if (activeView == hexView) { if (input.Control) { hexinputmode = true; if (hexinputmodepos == 1) { @@ -1528,12 +1511,12 @@ void MoveCaret(KeyEventArgs input) } else { caret.Offset++; } - - caret.SetToPosition(this.GetPositionForOffset(caret.Offset, this.charwidth)); + + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); } break; } - + } /// @@ -1577,7 +1560,7 @@ void ProcessHexInput(KeyEventArgs input) buffer.SetByte(caret.Offset, (byte)(Convert.ToInt32(@in, 16)), true); caret.Offset++; - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); } else if (hexinputmodepos == 0) { UndoAction action; @@ -1594,11 +1577,11 @@ void ProcessHexInput(KeyEventArgs input) hexinputmodepos = 1; undoStack.AddUndoStep(new UndoStep(new byte[] {(byte)(Convert.ToInt32(@in, 16))}, old, caret.Offset, action)); - - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); + + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); } - - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); + + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); } else { UndoAction action; @@ -1621,9 +1604,9 @@ void ProcessHexInput(KeyEventArgs input) } undoStack.AddUndoStep(new UndoStep(new byte[] {(byte)(Convert.ToInt32(@in, 16))}, _old, caret.Offset - 1, action)); - - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); - + + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); + } else if (hexinputmodepos == 0) { byte[] _old = buffer.GetBytes(caret.Offset, 1); @in = (char)(input.KeyValue) + "0"; @@ -1640,83 +1623,31 @@ void ProcessHexInput(KeyEventArgs input) undoStack.AddUndoStep(new UndoStep(new byte[] {(byte)(Convert.ToInt32(@in, 16))}, _old, caret.Offset, action)); } - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); } - EventArgs e = new EventArgs(); - OnDocumentChanged(e); - - this.Invalidate(); - } - } - - #region file functions - /// - /// Loads a file into the editor. - /// - /// The file-info to open. - /// The stream to read from/write to. - public void LoadFile(OpenedFile file, Stream stream) - { - buffer.Load(file, stream); - if (this.progressBar != null) { - this.progressBar.Visible = false; - this.progressBar.Available = false; - this.progressBar.Value = 0; - } - //this.Cursor = Cursors.WaitCursor; - } - - /// - /// Called from the BufferManager when Loading is finished. - /// - /// Currently not directly needed, because there's no thread in use to load the data. - internal void LoadingFinished() - { - if (this.InvokeRequired) { - this.Invoke (new MethodInvoker (LoadingFinished)); - return; - } - this.FileName = fileName; - selection.Clear(); - if (this.progressBar != null) { - this.progressBar.Visible = false; - this.progressBar.Available = false; - this.progressBar.Value = 0; + OnDocumentChanged(EventArgs.Empty); + Invalidate(); } - - this.side.Cursor = this.Cursor = this.header.Cursor = Cursors.Default; - - GC.Collect(); - this.Invalidate(); - } - - /// - /// Saves the current buffer to a stream. - /// - public void SaveFile(OpenedFile file, Stream stream) - { - buffer.Save(file, stream); } - #endregion /// /// Invalidates the control when the focus returns to it. /// private void HexEditGotFocus(object sender, EventArgs e) { - this.Invalidate(); + Invalidate(); } #region selection events /** - * Methods to control the current selection - * with mouse for both hex and text view. - * */ + * Methods to control the current selection + * with mouse for both hex and text view. + * */ void TextViewMouseDown(object sender, MouseEventArgs e) { - this.activeView = this.textView; + activeView = textView; if (e.Button == MouseButtons.Left) { if (selection.HasSomethingSelected) { selection.Start = 0; @@ -1732,13 +1663,13 @@ void TextViewMouseMove(object sender, MouseEventArgs e) if ((e.Button == MouseButtons.Left) && selectionmode && (e.Location != oldMousePos)) { int end = selection.End; selection.End = GetOffsetForPosition(e.Location, 1); - this.activeView = this.textView; + activeView = textView; moved = true; selection.HasSomethingSelected = true; caret.Offset = GetOffsetForPosition(e.Location, 1); - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); - this.Invalidate(); + Invalidate(); } oldMousePos = e.Location; @@ -1762,16 +1693,16 @@ void TextViewMouseUp(object sender, MouseEventArgs e) moved = false; } caret.Offset = GetOffsetForPosition(e.Location, 1); - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); selectionmode = false; - this.Invalidate(); + Invalidate(); } void HexViewMouseDown(object sender, MouseEventArgs e) { - this.activeView = this.hexView; + activeView = hexView; if (e.Button == MouseButtons.Left) { selectionmode = true; selection.Start = GetOffsetForPosition(e.Location, 3); @@ -1785,13 +1716,13 @@ void HexViewMouseMove(object sender, MouseEventArgs e) int end = selection.End; selection.End = GetOffsetForPosition(e.Location, 3); selection.HasSomethingSelected = true; - this.activeView = this.hexView; + activeView = hexView; moved = true; caret.Offset = GetOffsetForPosition(e.Location, 3); - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); - this.Invalidate(); + Invalidate(); } oldMousePos = e.Location; @@ -1800,7 +1731,7 @@ void HexViewMouseMove(object sender, MouseEventArgs e) void HexViewMouseUp(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) return; - + if (selectionmode) { selection.HasSomethingSelected = true; if ((selection.End == selection.Start) || ((selection.Start == 0) && (selection.End == 0))) { @@ -1816,10 +1747,10 @@ void HexViewMouseUp(object sender, MouseEventArgs e) moved = false; } caret.Offset = GetOffsetForPosition(e.Location, 3); - caret.SetToPosition(GetPositionForOffset(caret.Offset, this.charwidth)); + caret.SetToPosition(GetPositionForOffset(caret.Offset, charwidth)); selectionmode = false; - this.Invalidate(); + Invalidate(); } #endregion @@ -1845,7 +1776,7 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) /// Int32, containing the result internal int CalculateMaxBytesPerLine() { - int width = this.Width - this.side.Width - 90; + int width = Width - side.Width - 90; int textwidth = 0, hexwidth = 0; int count = 0; // while the width of the textview + the width of the hexview is @@ -1859,7 +1790,7 @@ internal int CalculateMaxBytesPerLine() if (count < 1) count = 1; - + return count; } @@ -1877,7 +1808,7 @@ internal int GetOffsetForPosition(Point position, int charwidth) // one line (height of font = fontheight) = physical line + topline = virtual line. int line = (int)Math.Round((float)position.Y / (float)fontheight) + topline; - if (position.Y > (this.textView.Height / 2.0f)) + if (position.Y > (textView.Height / 2.0f)) line++; // calculate the char: horizontal position (X) divided by the width of one char @@ -1886,26 +1817,26 @@ internal int GetOffsetForPosition(Point position, int charwidth) int ch = diff >= 0.75f ? (int)col + 1 : (int)col; - if (ch > this.BytesPerLine) ch = this.BytesPerLine; + if (ch > BytesPerLine) ch = BytesPerLine; if (ch < 0) ch = 0; // calculate offset - int offset = line * this.BytesPerLine + ch; + int offset = line * BytesPerLine + ch; if ((diff > 0.35f) && (diff < 0.75f)) { - this.hexinputmodepos = 1; - this.hexinputmode = true; + hexinputmodepos = 1; + hexinputmode = true; } else { - this.hexinputmodepos = 0; - this.hexinputmode = false; + hexinputmodepos = 0; + hexinputmode = false; } // check if (offset < 0) return 0; - if (offset < this.buffer.BufferSize) { + if (offset < buffer.BufferSize) { return offset; } else { - return this.buffer.BufferSize; + return buffer.BufferSize; } } @@ -1918,10 +1849,10 @@ internal int GetOffsetForPosition(Point position, int charwidth) /// The Drawing.Point at which the offset is currently. internal Point GetPositionForOffset(int offset, int charwidth) { - int line = (int)(offset / this.BytesPerLine) - this.topline; + int line = (int)(offset / BytesPerLine) - topline; int pline = line * fontheight - 1 * (line - 1) - 1; - int col = (offset % this.BytesPerLine) * underscorewidth * charwidth + 4; - if (hexinputmode && !selectionmode && !selection.HasSomethingSelected && this.insertmode) col += (hexinputmodepos * underscorewidth); + int col = (offset % BytesPerLine) * underscorewidth * charwidth + 4; + if (hexinputmode && !selectionmode && !selection.HasSomethingSelected && insertmode) col += (hexinputmodepos * underscorewidth); return new Point(col, pline); } @@ -1933,7 +1864,7 @@ internal Point GetPositionForOffset(int offset, int charwidth) /// The starting offset for a line. internal int GetOffsetForLine(int line) { - return line * this.BytesPerLine; + return line * BytesPerLine; } /// @@ -1946,7 +1877,7 @@ internal int GetLineForOffset(int offset) { if (offset == 0) return 0; - return (int)Math.Ceiling((double)offset / (double)this.BytesPerLine) - 1; + return (int)Math.Ceiling((double)offset / (double)BytesPerLine) - 1; } /// @@ -1955,31 +1886,29 @@ internal int GetLineForOffset(int offset) /// The count of currently visible virtual lines. internal int GetMaxVisibleLines() { - return (int)(this.hexView.Height / fontheight) + 3; + return (int)(hexView.Height / fontheight) + 3; } /// /// Calculates the count of all virtual lines in the buffer. /// /// Retrns 1 if the buffer is empty, otherwise the count of all virtual lines in the buffer. - internal int GetMaxLines() + int GetMaxLines() { if (buffer == null) return 1; - int lines = (int)(buffer.BufferSize / this.BytesPerLine); - if ((buffer.BufferSize % this.BytesPerLine) != 0) lines++; + int lines = (int)(buffer.BufferSize / BytesPerLine); + if ((buffer.BufferSize % BytesPerLine) != 0) lines++; return lines; } /// /// Calculates the count of digits of a given number. /// - /// The number to calculate - /// the count of digits in the number - static int GetLength(int number) + static int GetLength(int number, int @base = 10) { int count = 1; - while (number > 9) { - number = number / 10; + while (number >= @base) { + number = number / @base; count++; } return count; @@ -1990,7 +1919,7 @@ static int GetLength(int number) /// void HexEditControlContextMenuStripChanged(object sender, EventArgs e) { - this.ContextMenuStrip.Closed += new ToolStripDropDownClosedEventHandler(ContextMenuStripClosed); + ContextMenuStrip.Closed += new ToolStripDropDownClosedEventHandler(ContextMenuStripClosed); } /// @@ -1998,7 +1927,7 @@ void HexEditControlContextMenuStripChanged(object sender, EventArgs e) /// void ContextMenuStripClosed(object sender, EventArgs e) { - this.Invalidate(); + Invalidate(); } } } diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/BufferManager.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/BufferManager.cs index 1a77f7bdfab..721f017e5bd 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/BufferManager.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/BufferManager.cs @@ -17,8 +17,8 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using System.Windows.Forms; using ICSharpCode.AvalonEdit.Utils; @@ -33,139 +33,102 @@ namespace HexEditor.Util /// public class BufferManager { - internal Control parent; - OpenedFile currentFile; - Stream stream; - /// /// Currently used, but not good for really big files (like 590 MB) /// - Rope buffer; + Rope buffer = new Rope(); + + readonly OpenedFile file; + + public event EventHandler BufferChanged; + + protected virtual void OnBufferChanged(EventArgs e) + { + var handler = BufferChanged; + if (handler != null) + handler(this, e); + } /// /// Creates a new BufferManager and attaches it to a control. /// - /// The parent control to attach to. - public BufferManager(Control parent) + public BufferManager(OpenedFile file) { - this.parent = parent; - - this.buffer = new Rope(); + this.file = file; + var stream = file.GetModel(FileModels.Binary).OpenRead(); + this.LoadTask = Task.Run(() => DoLoad(stream)); } - /// - /// Cleares the whole buffer. - /// - public void Clear() + public Task LoadTask { get; private set; } + public IProgressMonitor Progress { get; set; } + + bool OnProgressUpdated(double d) { - this.buffer.Clear(); - parent.Invalidate(); - GC.Collect(); + var progress = Progress; + if (progress != null) { + progress.Report(d); + return !progress.CancellationToken.IsCancellationRequested; + } + return true; } - /// - /// Loads the data from a stream. - /// - public void Load(OpenedFile file, Stream stream) + const int CHUNK_SIZE = 1024 * 512; + + void DoLoad(Stream stream) { - this.currentFile = file; - this.stream = stream; - this.buffer.Clear(); - - ((Editor)this.parent).Enabled = false; - - if (File.Exists(currentFile.FileName)) { - try { - BinaryReader reader = new BinaryReader(this.stream, System.Text.Encoding.Default); - - while (reader.PeekChar() != -1) { - this.buffer.AddRange(reader.ReadBytes(524288)); - UpdateProgress((int)(this.buffer.Count / (double)reader.BaseStream.Length) * 100); + var buffer = new Rope(); + try { + double progressInverse = (double)CHUNK_SIZE / stream.Length; + int count = 0; + + byte[] byteBuffer = new byte[CHUNK_SIZE]; + + using (stream) { + int read = stream.Read(byteBuffer, 0, CHUNK_SIZE); + while (read > 0) { + buffer.AddRange(byteBuffer, 0, read); + count++; + if (!OnProgressUpdated(count * progressInverse)) return; + if (count % 5 == 0) { + var copy = buffer.Clone(); + SD.MainThread.InvokeAsyncAndForget( + () => { + this.buffer = copy; + OnBufferChanged(EventArgs.Empty); + } + ); + } + read = stream.Read(byteBuffer, 0, CHUNK_SIZE); } - - reader.Close(); - } catch (OutOfMemoryException) { - MessageService.ShowErrorFormatted("${res:FileUtilityService.FileSizeTooBig}"); } - } else { - MessageService.ShowErrorFormatted("${res:Fileutility.CantFindFileError}", currentFile.FileName); + } catch (OutOfMemoryException) { + SD.MessageService.ShowErrorFormatted("${res:FileUtilityService.FileSizeTooBig}"); + } finally { + SD.MainThread.InvokeAsyncAndForget( + () => { + this.buffer = buffer; + OnBufferChanged(EventArgs.Empty); + } + ); } - - this.parent.Invalidate(); - - UpdateProgress(100); - - if (this.parent.InvokeRequired) - this.parent.Invoke(new MethodInvoker( - delegate() {this.parent.Cursor = Cursors.Default;} - )); - else {this.parent.Cursor = Cursors.Default;} - - - ((Editor)this.parent).LoadingFinished(); - - ((Editor)this.parent).Enabled = true; - - this.parent.Invalidate(); } /// /// Writes all data to a stream. /// - public void Save(OpenedFile file, Stream stream) + public void Save(Stream stream) { BinaryWriter writer = new BinaryWriter(stream); - writer.Write(this.buffer.ToArray()); + writer.Write(buffer.ToArray()); writer.Flush(); } - /// - /// Used for threading to update the processbars and stuff. - /// - /// The current percentage of the process - private void UpdateProgress(int percentage) - { - Editor c = (Editor)this.parent; - - Application.DoEvents(); - - if (c.ProgressBar != null) { - if (percentage >= 100) { - if (c.InvokeRequired) - c.Invoke(new MethodInvoker( - delegate() {c.ProgressBar.Value = 100; c.ProgressBar.Visible = false;} - )); - else { - c.ProgressBar.Value = 100; - c.ProgressBar.Visible = false; } - } else { - if (c.InvokeRequired) - c.Invoke(new MethodInvoker( - delegate() {c.ProgressBar.Value = percentage; c.ProgressBar.Visible = true;} - )); - else { c.ProgressBar.Value = percentage; c.ProgressBar.Visible = true; } - } - } - } - - /// - /// Returns the current buffer as a byte[]. - /// - public byte[] Buffer { - get { - if (buffer == null) return new byte[0]; - return buffer.ToArray(); - } - } - /// /// The size of the current buffer. /// public int BufferSize { get { return buffer.Count; } } - - #region Methods public byte[] GetBytes(int start, int count) { @@ -176,7 +139,7 @@ public byte[] GetBytes(int start, int count) if (count >= (buffer.Count - start)) count = (buffer.Count - start); return buffer.GetRange(start, count).ToArray(); } - + public byte GetByte(int offset) { if (buffer.Count == 0) return 0; @@ -185,19 +148,11 @@ public byte GetByte(int offset) return (byte)buffer[offset]; } - public bool DeleteByte(int offset) - { - if ((offset < buffer.Count) & (offset > -1)) { - buffer.RemoveAt(offset); - return true; - } - return false; - } - public bool RemoveByte(int offset) { if ((offset < buffer.Count) & (offset > -1)) { buffer.RemoveAt(offset); + file.MakeDirty(HexEditFileModelProvider.Instance); return true; } return false; @@ -207,6 +162,8 @@ public bool RemoveBytes(int offset, int length) { if (((offset < buffer.Count) && (offset > -1)) && ((offset + length) <= buffer.Count)) { buffer.RemoveRange(offset, length); + file.MakeDirty(HexEditFileModelProvider.Instance); + OnBufferChanged(EventArgs.Empty); return true; } return false; @@ -221,20 +178,25 @@ public void SetBytes(int start, byte[] bytes, bool overwrite) } else { buffer.InsertRange(start, bytes); } + file.MakeDirty(HexEditFileModelProvider.Instance); + OnBufferChanged(EventArgs.Empty); } - public void SetByte(int position, byte @byte, bool overwrite) + public void SetByte(int offset, byte @byte, bool overwrite) { if (overwrite) { - if (position > buffer.Count - 1) { + if (offset > buffer.Count - 1) { buffer.Add(@byte); } else { - buffer[position] = @byte; + buffer[offset] = @byte; } } else { - buffer.Insert(position, @byte); + buffer.Insert(offset, @byte); } + file.MakeDirty(HexEditFileModelProvider.Instance); + OnBufferChanged(EventArgs.Empty); } - #endregion } + + } diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/Caret.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/Caret.cs index d970fa6ecf2..cbb00f53d73 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/Caret.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/Caret.cs @@ -73,7 +73,7 @@ public void SetToPosition(Point position) DrawCaret(position, this.width, this.height); } - private void DrawCaret(Point start, int width, int height) + void DrawCaret(Point start, int width, int height) { if (width > 1) g.DrawRectangle(Pens.Black, start.X - 1, start.Y, width, height - 1); diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/ClipboardManager.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/ClipboardManager.cs deleted file mode 100644 index b363ffdc549..00000000000 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/ClipboardManager.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Windows.Forms; - -namespace HexEditor.Util -{ - /// - /// Manages the clipboard actions. - /// - public static class ClipboardManager - { - /// - /// Used to determine if text is in the clipboard or not. - /// - public static bool ContainsText { - get { - return Clipboard.ContainsText(); - } - } - - /// - /// Cleares the Clipboard. - /// - public static void Clear() - { - Clipboard.Clear(); - } - - /// - /// Copies text into the clipboard. - /// - /// The text to be copied to the clipboard. - public static void Copy(string text) - { - Clipboard.SetText(text); - } - - /// - /// Pastes the text. - /// - /// the text in the clipboard. - public static string Paste() - { - return Clipboard.GetText(); - } - } -} diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/HexEditFileModelProvider.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/HexEditFileModelProvider.cs new file mode 100644 index 00000000000..63f9f39445d --- /dev/null +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/HexEditFileModelProvider.cs @@ -0,0 +1,84 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Workbench; + +namespace HexEditor.Util +{ + public class HexEditFileModelProvider : IFileModelProvider + { + static readonly HexEditFileModelProvider instance = new HexEditFileModelProvider(); + + HexEditFileModelProvider() + { + } + + public static HexEditFileModelProvider Instance { + get { + return instance; + } + } + + public BufferManager Load(OpenedFile file) + { + SD.AnalyticsMonitor.TrackFeature(typeof(HexEditor.View.HexEditView), "Load"); + return new BufferManager(file); + } + + public void Save(OpenedFile file, BufferManager model, FileSaveOptions options) + { + SD.AnalyticsMonitor.TrackFeature(typeof(HexEditor.View.HexEditView), "Save"); + MemoryStream ms = new MemoryStream(); + model.Save(ms); + file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray())); + } + + public void SaveCopyAs(OpenedFile file, BufferManager model, FileName outputFileName, FileSaveOptions options) + { + using (var fileStream = new FileStream(outputFileName, FileMode.Create, FileAccess.Write)) { + model.Save(fileStream); + } + } + + public bool CanLoadFrom(IFileModelProvider otherProvider) where U : class + { + return otherProvider == FileModels.Binary || FileModels.Binary.CanLoadFrom(otherProvider); + } + + public void NotifyRename(OpenedFile file, BufferManager model, FileName oldName, FileName newName) + { + } + + public void NotifyStale(OpenedFile file, BufferManager model) + { + file.UnloadModel(this); + } + + public void NotifyLoaded(OpenedFile file, BufferManager model) + { + } + + public void NotifyUnloaded(OpenedFile file, BufferManager model) + { + } + } +} \ No newline at end of file diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/SelectionManager.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/SelectionManager.cs index 623c35cab33..98184243e29 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/SelectionManager.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/SelectionManager.cs @@ -37,21 +37,19 @@ protected virtual void OnSelectionChanged(EventArgs e) } } - public SelectionManager(ref BufferManager buffer) + public SelectionManager(BufferManager buffer) { this.buffer = buffer; } #region Properties int start, end; - bool hasSelection; public int Start { get { return start; } set { start = value; - EventArgs e = new EventArgs(); - OnSelectionChanged(e); + OnSelectionChanged(EventArgs.Empty); } } @@ -59,15 +57,11 @@ public int End { get { return end; } set { end = value; - EventArgs e = new EventArgs(); - OnSelectionChanged(e); + OnSelectionChanged(EventArgs.Empty); } } - public bool HasSomethingSelected { - get { return hasSelection; } - set { hasSelection = value; } - } + public bool HasSomethingSelected { get; set; } #endregion @@ -78,25 +72,13 @@ public bool HasSomethingSelected { /// A string with the selected text public string SelectionText { get { - int start = this.Start; - int end = this.End; - if (this.End < this.Start) { - start = this.End; - end = this.Start; - } - return Encoding.Default.GetString(buffer.GetBytes(this.Start, Math.Abs(end - start))); + return Encoding.Default.GetString(GetSelectionBytes()); } } public byte[] GetSelectionBytes() { - int start = this.Start; - int end = this.End; - if (this.End < this.Start) { - start = this.End; - end = this.Start; - } - return buffer.GetBytes(this.Start, Math.Abs(end - start)); + return buffer.GetBytes(Math.Min(Start, End), Math.Abs(End - Start)); } /// diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/UndoManager.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/UndoManager.cs index cdb1317b160..f6af5214e4f 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/UndoManager.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/UndoManager.cs @@ -86,7 +86,7 @@ internal void AddRemoveStep(int start, byte[] bytes) /// /// Buffer to use /// Used internally, don't use! - internal UndoStep Undo(ref BufferManager buffer) + internal UndoStep Undo(BufferManager buffer) { if (UndoStack.Count > 0) { UndoStep step = (UndoStep)UndoStack.Peek(); @@ -117,7 +117,7 @@ internal UndoStep Undo(ref BufferManager buffer) /// /// Buffer to use /// Used internally, don't use! - internal UndoStep Redo(ref BufferManager buffer) + internal UndoStep Redo(BufferManager buffer) { if (RedoStack.Count > 0) { UndoStep step = (UndoStep)RedoStack.Peek(); diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditContainer.Designer.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditContainer.Designer.cs index a4e8ef1ffc0..5b1adb1b453 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditContainer.Designer.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditContainer.Designer.cs @@ -54,7 +54,6 @@ private void InitializeComponent() this.tCBViewMode = new System.Windows.Forms.ToolStripComboBox(); this.toolStrip1 = new System.Windows.Forms.ToolStrip(); this.tSTBCharsPerLine = new System.Windows.Forms.NumericUpDown(); - this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar(); this.hexEditControl = new HexEditor.Editor(); this.toolStrip1.SuspendLayout(); this.SuspendLayout(); @@ -91,8 +90,7 @@ private void InitializeComponent() // this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.tbSizeToFit, - this.toolStripSeparator1, - this.toolStripProgressBar1}); + this.toolStripSeparator1}); this.toolStrip1.Location = new System.Drawing.Point(0, 0); this.toolStrip1.Name = "toolStrip1"; this.toolStrip1.Size = new System.Drawing.Size(689, 25); @@ -105,13 +103,6 @@ private void InitializeComponent() this.tSTBCharsPerLine.Size = new System.Drawing.Size(40, 25); this.tSTBCharsPerLine.TextChanged += new System.EventHandler(tSTBCharsPerLine_TextChanged); // - // toolStripProgressBar1 - // - this.toolStripProgressBar1.Name = "toolStripProgressBar1"; - this.toolStripProgressBar1.Overflow = System.Windows.Forms.ToolStripItemOverflow.Never; - this.toolStripProgressBar1.Size = new System.Drawing.Size(150, 22); - this.toolStripProgressBar1.Visible = false; - // // hexEditControl // this.hexEditControl.BackColor = System.Drawing.Color.White; @@ -123,7 +114,6 @@ private void InitializeComponent() this.hexEditControl.FitToWindowWidth = false; this.hexEditControl.Location = new System.Drawing.Point(0, 25); this.hexEditControl.Name = "hexEditControl"; - this.hexEditControl.ProgressBar = this.toolStripProgressBar1; this.hexEditControl.Size = new System.Drawing.Size(689, 308); this.hexEditControl.TabIndex = 3; this.hexEditControl.ViewMode = HexEditor.Util.ViewMode.Hexadecimal; @@ -143,7 +133,6 @@ private void InitializeComponent() this.PerformLayout(); } - private System.Windows.Forms.ToolStripProgressBar toolStripProgressBar1; internal HexEditor.Editor hexEditControl; private System.Windows.Forms.ToolStripComboBox tCBViewMode; private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditContainer.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditContainer.cs index 3d06983640f..0ec46fe734e 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditContainer.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditContainer.cs @@ -45,12 +45,27 @@ public bool CanUndo { public bool CanRedo { get { return hexEditControl.CanRedo; } } + + public BufferManager Buffer { + get { + return hexEditControl.Buffer; + } + set { + hexEditControl.Buffer = value; + } + } public HexEditContainer() { InitializeComponent(); } + public void InvalidateAll() + { + Invalidate(); + hexEditControl.Invalidate(); + } + bool loaded = false; void Init(object sender, EventArgs e) @@ -112,18 +127,6 @@ void HexEditContainerGotFocus(object sender, EventArgs e) hexEditControl.Focus(); } - public void LoadFile(OpenedFile file, Stream stream) - { - hexEditControl.LoadFile(file, stream); - hexEditControl.Invalidate(); - } - - public void SaveFile(OpenedFile file, Stream stream) - { - hexEditControl.SaveFile(file, stream); - hexEditControl.Invalidate(); - } - public string Cut() { string text = hexEditControl.Copy(); diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs index 18a239e8388..1a713afb1c7 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs @@ -17,48 +17,43 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.IO; -using ICSharpCode.Core; -using ICSharpCode.Core.WinForms; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.WinForms; using ICSharpCode.SharpDevelop.Workbench; +using HexEditor.Util; namespace HexEditor.View { - public class HexEditView : AbstractViewContentSD1234, IClipboardHandler, IUndoHandler + public class HexEditView : AbstractViewContent, IClipboardHandler, IUndoHandler { HexEditContainer hexEditContainer; + OpenedFile file; public HexEditView(OpenedFile file) { hexEditContainer = new HexEditContainer(); - hexEditContainer.hexEditControl.DocumentChanged += new EventHandler(DocumentChanged); - + this.file = file; this.Files.Add(file); - LoadModel(); - SD.AnalyticsMonitor.TrackFeature(typeof(HexEditView)); } - - public override object Control { - get { return hexEditContainer; } - } - public override void Save(OpenedFile file, Stream stream) + public override async void LoadModel() { - SD.AnalyticsMonitor.TrackFeature(typeof(HexEditView), "Save"); - this.hexEditContainer.SaveFile(file, stream); - this.TitleName = Path.GetFileName(file.FileName); - this.TabPageText = this.TitleName; + using (var progress = AsynchronousWaitDialog.ShowWaitDialog("Loading ...", true)) { + var model = file.GetModel(HexEditFileModelProvider.Instance); + hexEditContainer.Enabled = false; + hexEditContainer.Buffer = model; + model.Progress = progress; + await model.LoadTask; + hexEditContainer.Enabled = true; + SD.MainThread.CallLater(TimeSpan.FromMilliseconds(200), hexEditContainer.InvalidateAll); + } } - public override void Load(OpenedFile file, Stream stream) - { - SD.AnalyticsMonitor.TrackFeature(typeof(HexEditView), "Load"); - this.hexEditContainer.LoadFile(file, stream); + public override object Control { + get { return hexEditContainer; } } public override bool IsReadOnly { @@ -133,11 +128,5 @@ public void Redo() } #endregion - - void DocumentChanged(object sender, EventArgs e) - { - if (PrimaryFile != null) - MakeDirty(PrimaryFile); - } } } From 5a2177dbc5ae18bcee4ef0f28fb2fd59c1f9271c Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 22 Feb 2014 10:16:00 +0100 Subject: [PATCH 12/13] fixed bugs in hex editor related to "split view" --- .../HexEditor/Project/Src/Editor.Designer.cs | 2 +- .../HexEditor/Project/Src/Editor.cs | 19 +++++++++++++++---- .../Project/Src/Util/BufferManager.cs | 1 + .../Project/Src/Util/SelectionManager.cs | 10 ++++++++++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.Designer.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.Designer.cs index e3c1e6893d3..a375b0f44a5 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.Designer.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.Designer.cs @@ -112,11 +112,11 @@ private void InitializeComponent() // Editor // this.BackColor = System.Drawing.Color.White; + this.Controls.Add(this.VScrollBar); this.Controls.Add(this.header); this.Controls.Add(this.textView); this.Controls.Add(this.hexView); this.Controls.Add(this.side); - this.Controls.Add(this.VScrollBar); this.DoubleBuffered = true; this.MinimumSize = new System.Drawing.Size(1, 1); this.Name = "Editor"; diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.cs index c5f7bfe230a..58c8c56282f 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Editor.cs @@ -136,6 +136,12 @@ void SetBuffer(BufferManager buffer) void BufferChanged(object sender, EventArgs e) { + if (!Focused) { + selection.ValidateSelection(); + caret.Offset = Math.Max(0, Math.Min(caret.Offset, buffer.BufferSize)); + Point pos = GetPositionForOffset(caret.Offset, charwidth); + caret.SetToPosition(pos); + } Invalidate(); OnDocumentChanged(EventArgs.Empty); } @@ -1301,6 +1307,7 @@ void HexEditKeyDown(object sender, KeyEventArgs e) break; case Keys.Back: e.Handled = true; + e.SuppressKeyPress = true; if (hexinputmode) return; if (selection.HasSomethingSelected) { byte[] bytes = selection.GetSelectionBytes(); @@ -1314,8 +1321,7 @@ void HexEditKeyDown(object sender, KeyEventArgs e) } else { byte b = buffer.GetByte(caret.Offset - 1); - if (buffer.RemoveByte(caret.Offset - 1)) - { + if (buffer.RemoveByte(caret.Offset - 1)) { if (caret.Offset > -1) caret.Offset--; if (GetLineForOffset(caret.Offset) < topline) topline = GetLineForOffset(caret.Offset); if (GetLineForOffset(caret.Offset) > topline + GetMaxVisibleLines() - 2) topline = GetLineForOffset(caret.Offset) - GetMaxVisibleLines() + 2; @@ -1328,6 +1334,7 @@ void HexEditKeyDown(object sender, KeyEventArgs e) break; case Keys.Delete: e.Handled = true; + e.SuppressKeyPress = true; if (hexinputmode) return; if (selection.HasSomethingSelected) { byte[] old = selection.GetSelectionBytes(); @@ -1353,6 +1360,7 @@ void HexEditKeyDown(object sender, KeyEventArgs e) case Keys.CapsLock: case Keys.ShiftKey: case Keys.ControlKey: + case Keys.Enter: break; case Keys.Tab: if (activeView == hexView) { @@ -1405,6 +1413,7 @@ void HexEditKeyDown(object sender, KeyEventArgs e) if (activeView == hexView) { ProcessHexInput(e); e.Handled = true; + e.SuppressKeyPress = true; return; } @@ -1773,7 +1782,6 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) /// /// Calculates the max possible bytes per line. /// - /// Int32, containing the result internal int CalculateMaxBytesPerLine() { int width = Width - side.Width - 90; @@ -1836,6 +1844,8 @@ internal int GetOffsetForPosition(Point position, int charwidth) if (offset < buffer.BufferSize) { return offset; } else { + hexinputmodepos = 0; + hexinputmode = false; return buffer.BufferSize; } } @@ -1852,7 +1862,8 @@ internal Point GetPositionForOffset(int offset, int charwidth) int line = (int)(offset / BytesPerLine) - topline; int pline = line * fontheight - 1 * (line - 1) - 1; int col = (offset % BytesPerLine) * underscorewidth * charwidth + 4; - if (hexinputmode && !selectionmode && !selection.HasSomethingSelected && insertmode) col += (hexinputmodepos * underscorewidth); + if (hexinputmode && !selectionmode && !selection.HasSomethingSelected && insertmode) + col += (hexinputmodepos * underscorewidth); return new Point(col, pline); } diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/BufferManager.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/BufferManager.cs index 721f017e5bd..0f8a421c38f 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/BufferManager.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/BufferManager.cs @@ -153,6 +153,7 @@ public bool RemoveByte(int offset) if ((offset < buffer.Count) & (offset > -1)) { buffer.RemoveAt(offset); file.MakeDirty(HexEditFileModelProvider.Instance); + OnBufferChanged(EventArgs.Empty); return true; } return false; diff --git a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/SelectionManager.cs b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/SelectionManager.cs index 98184243e29..2811cadda82 100644 --- a/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/SelectionManager.cs +++ b/src/AddIns/DisplayBindings/HexEditor/Project/Src/Util/SelectionManager.cs @@ -80,6 +80,16 @@ public byte[] GetSelectionBytes() { return buffer.GetBytes(Math.Min(Start, End), Math.Abs(End - Start)); } + + public void ValidateSelection() + { + int start = Math.Min(this.start, this.end); + int end = Math.Max(this.start, this.end); + start = Math.Min(Math.Max(0, start), buffer.BufferSize); + end = Math.Min(Math.Max(0, end), buffer.BufferSize); + this.start = start; + this.end = end; + } /// /// Cleares the selection (no text is altered) From c38a76cd7f01384658e49d9451803f98410e4f0c Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 24 Feb 2014 09:45:10 +0100 Subject: [PATCH 13/13] Fix usage of ErrorFallbackBinding. --- src/Main/SharpDevelop/SharpDevelop.csproj | 1 + .../AutoDetectDisplayBinding.cs | 2 +- .../DisplayBinding/ErrorFallbackBinding.cs | 59 +++++++++++++++++++ .../SharpDevelop/Workbench/FileService.cs | 34 +---------- 4 files changed, 63 insertions(+), 33 deletions(-) create mode 100644 src/Main/SharpDevelop/Workbench/DisplayBinding/ErrorFallbackBinding.cs diff --git a/src/Main/SharpDevelop/SharpDevelop.csproj b/src/Main/SharpDevelop/SharpDevelop.csproj index 591fcf9bf07..4deaab7796e 100644 --- a/src/Main/SharpDevelop/SharpDevelop.csproj +++ b/src/Main/SharpDevelop/SharpDevelop.csproj @@ -202,6 +202,7 @@ + OpenWithDialog.cs diff --git a/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs b/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs index bb2ddfb4039..cafc9bb7d8c 100644 --- a/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs +++ b/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs @@ -68,7 +68,7 @@ public IViewContent CreateContentForFile(OpenedFile file) } if (bestMatch == null) - return null; + return ErrorFallbackBinding.CouldNotFindDisplayBindingFor(file.FileName).CreateContentForFile(file); return bestMatch.Binding.CreateContentForFile(file); } diff --git a/src/Main/SharpDevelop/Workbench/DisplayBinding/ErrorFallbackBinding.cs b/src/Main/SharpDevelop/Workbench/DisplayBinding/ErrorFallbackBinding.cs new file mode 100644 index 00000000000..7296ce8bc4b --- /dev/null +++ b/src/Main/SharpDevelop/Workbench/DisplayBinding/ErrorFallbackBinding.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using ICSharpCode.Core; + +namespace ICSharpCode.SharpDevelop.Workbench +{ + sealed class ErrorFallbackBinding : IDisplayBinding + { + public static ErrorFallbackBinding CouldNotFindDisplayBindingFor(FileName fileName) + { + return new ErrorFallbackBinding("Could not find any display binding for " + fileName.GetFileName()); + } + + string errorMessage; + + public ErrorFallbackBinding(string errorMessage) + { + this.errorMessage = errorMessage; + } + + public bool CanCreateContentForFile(FileName fileName) + { + return true; + } + + public IViewContent CreateContentForFile(OpenedFile file) + { + return new SimpleViewContent(errorMessage) { TitleName = file.FileName.GetFileName() }; + } + + public bool IsPreferredBindingForFile(FileName fileName) + { + return false; + } + + public double AutoDetectFileContent(FileName fileName, Stream fileContent, string detectedMimeType) + { + return double.NegativeInfinity; + } + } +} diff --git a/src/Main/SharpDevelop/Workbench/FileService.cs b/src/Main/SharpDevelop/Workbench/FileService.cs index 0f41d982140..700f6416db0 100644 --- a/src/Main/SharpDevelop/Workbench/FileService.cs +++ b/src/Main/SharpDevelop/Workbench/FileService.cs @@ -311,7 +311,7 @@ public IViewContent OpenFile(FileName fileName) { return OpenFile(fileName, true); } - + /// public IViewContent OpenFile(FileName fileName, bool switchToOpenedView) { @@ -328,7 +328,7 @@ public IViewContent OpenFile(FileName fileName, bool switchToOpenedView) IDisplayBinding binding = SD.DisplayBindingService.GetBindingPerFileName(fileName); if (binding == null) { - binding = new ErrorFallbackBinding("Could not find any display binding for " + Path.GetFileName(fileName)); + binding = ErrorFallbackBinding.CouldNotFindDisplayBindingFor(fileName); } if (FileUtility.ObservedLoad(new NamedFileOperationDelegate(new LoadFileWrapper(binding, switchToOpenedView).Invoke), fileName) == FileOperationResult.OK) { RecentOpen.AddRecentFile(fileName); @@ -438,36 +438,6 @@ public IViewContent GetOpenFile(FileName fileName) return null; } - sealed class ErrorFallbackBinding : IDisplayBinding - { - string errorMessage; - - public ErrorFallbackBinding(string errorMessage) - { - this.errorMessage = errorMessage; - } - - public bool CanCreateContentForFile(FileName fileName) - { - return true; - } - - public IViewContent CreateContentForFile(OpenedFile file) - { - return new SimpleViewContent(errorMessage) { TitleName = Path.GetFileName(file.FileName) }; - } - - public bool IsPreferredBindingForFile(FileName fileName) - { - return false; - } - - public double AutoDetectFileContent(FileName fileName, Stream fileContent, string detectedMimeType) - { - return double.NegativeInfinity; - } - } - /// public IViewContent JumpToFilePosition(FileName fileName, int line, int column) {