From 8bb111bc11b5e26b3bc3b582067287e5df8c241b Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 18 Jan 2014 22:01:57 +0100 Subject: [PATCH] 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 7fa331294e..e9d2d3e515 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 9fa03747e6..3dd360490f 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 baa7da48a4..b3bd61f07a 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 @@ namespace ICSharpCode.SharpDevelop.Project // 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 @@ namespace ICSharpCode.SharpDevelop.Project 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 d3868b88f4..4d676e716c 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.Editor.Search; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Project; +using ICSharpCode.SharpDevelop.Workbench; namespace ICSharpCode.SharpDevelop.Refactoring { @@ -255,16 +256,11 @@ namespace ICSharpCode.SharpDevelop.Refactoring 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 b6d3839ed2..bc126f0bb1 100644 --- a/src/Main/Base/Project/Workbench/AbstractViewContent.cs +++ b/src/Main/Base/Project/Workbench/AbstractViewContent.cs @@ -23,6 +23,7 @@ using System.Collections.ObjectModel; using System.IO; using System.Windows.Forms; +using ICSharpCode.AvalonEdit.Utils; using ICSharpCode.Core; using ICSharpCode.Core.Presentation; @@ -230,20 +231,28 @@ namespace ICSharpCode.SharpDevelop.Workbench #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 @@ namespace ICSharpCode.SharpDevelop.Workbench } } - 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 @@ namespace ICSharpCode.SharpDevelop.Workbench { 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 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 @@ namespace ICSharpCode.SharpDevelop.Workbench { 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 9fc63e1ad1..0000000000 --- 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 137123fcb5..78d391b4fb 100644 --- a/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs @@ -7,9 +7,9 @@ using ICSharpCode.Core; 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 @@ namespace ICSharpCode.SharpDevelop.Workbench } } - 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 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 ce9dea8a31..51888b785c 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 @@ namespace ICSharpCode.SharpDevelop.Workbench /// /// 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 @@ namespace ICSharpCode.SharpDevelop.Workbench /// 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 6cc550d713..ad5618296f 100644 --- a/src/Main/Base/Project/Workbench/File/IFileService.cs +++ b/src/Main/Base/Project/Workbench/File/IFileService.cs @@ -27,6 +27,26 @@ using ICSharpCode.SharpDevelop.Gui; 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 @@ namespace ICSharpCode.SharpDevelop.Workbench /// 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 @@ namespace ICSharpCode.SharpDevelop.Workbench /// 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 56fc043544..a924e563c6 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; 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 @@ namespace ICSharpCode.SharpDevelop.Workbench /// 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 @@ namespace ICSharpCode.SharpDevelop.Workbench /// 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 @@ namespace ICSharpCode.SharpDevelop.Workbench { 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 @@ namespace ICSharpCode.SharpDevelop.Workbench /// 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 @@ namespace ICSharpCode.SharpDevelop.Workbench /// 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 @@ namespace ICSharpCode.SharpDevelop.Workbench /// 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 @@ namespace ICSharpCode.SharpDevelop.Workbench /// 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 34a1fcfab9..6f78d2704b 100644 --- a/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs +++ b/src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs @@ -33,11 +33,12 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 0000000000..af35d07a84 --- /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 c0c85e1859..868f522d2a 100644 --- a/src/Main/Base/Project/Workbench/FileChangeWatcher.cs +++ b/src/Main/Base/Project/Workbench/FileChangeWatcher.cs @@ -82,7 +82,7 @@ namespace ICSharpCode.SharpDevelop.Workbench bool wasChangedExternally = false; OpenedFile file; - public FileChangeWatcher(OpenedFile file) + internal FileChangeWatcher(OpenedFile file) { if (file == null) throw new ArgumentNullException("file"); @@ -203,18 +203,18 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 6f59eac257..37d158bef5 100644 --- a/src/Main/Base/Project/Workbench/IViewContent.cs +++ b/src/Main/Base/Project/Workbench/IViewContent.cs @@ -120,7 +120,7 @@ namespace ICSharpCode.SharpDevelop /// 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 04e12d081b..5029626386 100644 --- a/src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs +++ b/src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs @@ -528,8 +528,8 @@ namespace ICSharpCode.SharpDevelop.Templates 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 @@ namespace ICSharpCode.SharpDevelop.Templates 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 0aa6d95f06..a432fbba1c 100644 --- a/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs +++ b/src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs @@ -51,7 +51,7 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 906ea2cc8d..97d9073f6b 100644 --- a/src/Main/SharpDevelop/Workbench/FileService.cs +++ b/src/Main/SharpDevelop/Workbench/FileService.cs @@ -127,20 +127,7 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 @@ namespace ICSharpCode.SharpDevelop.Workbench #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 @@ namespace ICSharpCode.SharpDevelop.Workbench #endregion #region OpenedFile - Dictionary openedFileDict = new Dictionary(); + readonly Dictionary openedFileDict = new Dictionary(); /// public IReadOnlyList OpenedFiles { @@ -204,33 +191,33 @@ namespace ICSharpCode.SharpDevelop.Workbench } /// - 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 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 @@ namespace ICSharpCode.SharpDevelop.Workbench SD.Workbench.ShowView(newContent, switchToOpenedView); } } finally { - file.CloseIfAllViewsClosed(); + file.ReleaseReference(); } } } @@ -383,18 +392,20 @@ namespace ICSharpCode.SharpDevelop.Workbench 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 7f4e8113b2..9b0e355584 100644 --- a/src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs +++ b/src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs @@ -42,135 +42,44 @@ namespace ICSharpCode.SharpDevelop.Workbench { 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 {}; } }