Browse Source

Implement new OpenedFile API.

filemodels
Daniel Grunwald 12 years ago
parent
commit
8bb111bc11
  1. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ObserveAddRemoveCollection.cs
  2. 2
      src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
  3. 11
      src/Main/Base/Project/Src/Project/Behaviors/DotNetStartBehavior.cs
  4. 12
      src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs
  5. 66
      src/Main/Base/Project/Workbench/AbstractViewContent.cs
  6. 233
      src/Main/Base/Project/Workbench/FakeXmlViewContent.cs
  7. 18
      src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs
  8. 30
      src/Main/Base/Project/Workbench/File/FileModels.cs
  9. 63
      src/Main/Base/Project/Workbench/File/IFileService.cs
  10. 344
      src/Main/Base/Project/Workbench/File/OpenedFile.cs
  11. 18
      src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs
  12. 54
      src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs
  13. 10
      src/Main/Base/Project/Workbench/FileChangeWatcher.cs
  14. 2
      src/Main/Base/Project/Workbench/IViewContent.cs
  15. 6
      src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs
  16. 2
      src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs
  17. 107
      src/Main/SharpDevelop/Workbench/FileService.cs
  18. 127
      src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ObserveAddRemoveCollection.cs

@ -26,7 +26,7 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -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.
/// </summary>
sealed class ObserveAddRemoveCollection<T> : Collection<T>
public sealed class ObserveAddRemoveCollection<T> : Collection<T>
{
readonly Action<T> onAdd, onRemove;

2
src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj

@ -359,7 +359,6 @@ @@ -359,7 +359,6 @@
<Compile Include="Workbench\DisplayBinding\IDisplayBinding.cs" />
<Compile Include="Workbench\DisplayBinding\IDisplayBindingService.cs" />
<Compile Include="Workbench\DisplayBinding\ISecondaryDisplayBinding.cs" />
<Compile Include="Workbench\FakeXmlViewContent.cs" />
<Compile Include="Workbench\FileChangeWatcher.cs" />
<Compile Include="Workbench\File\BinaryFileModelProvider.cs" />
<Compile Include="Workbench\File\DocumentFileModelInfo.cs" />
@ -368,6 +367,7 @@ @@ -368,6 +367,7 @@
<Compile Include="Workbench\File\IFileModelProvider.cs" />
<Compile Include="Workbench\File\IRecentOpen.cs" />
<Compile Include="Workbench\File\TextDocumentFileModelProvider.cs" />
<Compile Include="Workbench\File\XDocumentFileModelProvider.cs" />
<Compile Include="Workbench\ICustomizedCommands.cs" />
<Compile Include="Workbench\IOutputPad.cs" />
<Compile Include="Workbench\IPadContent.cs" />

11
src/Main/Base/Project/Src/Project/Behaviors/DotNetStartBehavior.cs

@ -280,13 +280,14 @@ namespace ICSharpCode.SharpDevelop.Project @@ -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 @@ -304,7 +305,7 @@ namespace ICSharpCode.SharpDevelop.Project
supportedRuntime.SetAttributeValue("version", newFramework.SupportedRuntimeVersion);
supportedRuntime.SetAttributeValue("sku", newFramework.SupportedSku);
}
}
);
}
protected virtual void AddOrRemoveExtensions()

12
src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs

@ -32,6 +32,7 @@ using ICSharpCode.SharpDevelop.Editor.Search; @@ -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 @@ -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<IFileDocumentProvider>();
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();
}
}
}

66
src/Main/Base/Project/Workbench/AbstractViewContent.cs

@ -23,6 +23,7 @@ using System.Collections.ObjectModel; @@ -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 @@ -230,20 +231,28 @@ namespace ICSharpCode.SharpDevelop.Workbench
#endregion
#region Files
FilesCollection files;
ObserveAddRemoveCollection<OpenedFile> files;
ReadOnlyCollection<OpenedFile> filesReadonly;
void InitFiles()
{
files = new FilesCollection(this);
files = new ObserveAddRemoveCollection<OpenedFile>(RegisterFileEventHandlers, UnregisterFileEventHandlers);
filesReadonly = new ReadOnlyCollection<OpenedFile>(files);
}
/// <summary>
/// The list of files that are being edited by this view content.
/// The first item in this list is used for the <see cref="PrimaryFile"/> property.
///
/// The collection automatically calls <see cref="OpenedFile.AddReference"/> and <see cref="OpenedFile.ReleaseReference"/> as files
/// are added/removed from the collection.
/// The collection is cleared (thus freeing all references) when the view content is disposed.
/// </summary>
protected Collection<OpenedFile> Files {
get { return files; }
}
IList<OpenedFile> IViewContent.Files {
IReadOnlyList<OpenedFile> IViewContent.Files {
get { return filesReadonly; }
}
@ -273,15 +282,11 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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 @@ -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 @@ -309,43 +312,6 @@ namespace ICSharpCode.SharpDevelop.Workbench
protected virtual void OnFileNameChanged(OpenedFile file)
{
}
private sealed class FilesCollection : Collection<OpenedFile>
{
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 @@ -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);

233
src/Main/Base/Project/Workbench/FakeXmlViewContent.cs

@ -1,233 +0,0 @@ @@ -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
{
/// <summary>
/// IViewContent implementation that opens a file as XDocument and allows editing it, while synchronizing changes with any open editor.
/// </summary>
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;
/// <summary>
/// Gets the document.
/// Can return null if there were load errors.
/// </summary>
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<OpenedFile> 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> 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
}
}

18
src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs

@ -7,9 +7,9 @@ using ICSharpCode.Core; @@ -7,9 +7,9 @@ using ICSharpCode.Core;
namespace ICSharpCode.SharpDevelop.Workbench
{
sealed class BinaryFileModelProvider : IFileModelProvider<IBinaryModel>
sealed class BinaryFileModelProvider : IFileModelProvider<IBinaryFileModel>
{
sealed class OnDiskBinaryModel : IBinaryModel
sealed class OnDiskBinaryModel : IBinaryFileModel
{
internal readonly OpenedFile file;
@ -24,24 +24,24 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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 @@ -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)
{
}
}

30
src/Main/Base/Project/Workbench/File/FileModels.cs

@ -3,7 +3,6 @@ @@ -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 @@ -15,18 +14,23 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <summary>
/// The binary file model provider.
/// </summary>
public static readonly IFileModelProvider<IBinaryModel> Binary = new BinaryFileModelProvider();
public static readonly IFileModelProvider<IBinaryFileModel> Binary = new BinaryFileModelProvider();
/// <summary>
/// The text document file model provider.
/// </summary>
public static readonly IFileModelProvider<TextDocument> TextDocument = new TextDocumentFileModelProvider();
public static readonly IFileModelProvider<ICSharpCode.AvalonEdit.Document.TextDocument> TextDocument = new TextDocumentFileModelProvider();
/// <summary>
/// The XDocument file model provider.
/// </summary>
public static readonly IFileModelProvider<System.Xml.Linq.XDocument> XDocument = new XDocumentFileModelProvider();
}
/// <summary>
/// Represents the binary contents of a file.
/// </summary>
public interface IBinaryModel
public interface IBinaryFileModel
{
/// <summary>
/// Creates a stream that reads from the binary model.
@ -35,4 +39,22 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -35,4 +39,22 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <exception cref="IOException">Error opening the file.</exception>
Stream OpenRead();
}
/// <summary>
/// Simple IBinaryFileModel implementation that uses a byte array.
/// </summary>
public class BinaryFileModel : IBinaryFileModel
{
readonly byte[] data;
public BinaryFileModel(byte[] data)
{
this.data = data;
}
public Stream OpenRead()
{
return new MemoryStream(data);
}
}
}

63
src/Main/Base/Project/Workbench/File/IFileService.cs

@ -27,6 +27,26 @@ using ICSharpCode.SharpDevelop.Gui; @@ -27,6 +27,26 @@ using ICSharpCode.SharpDevelop.Gui;
namespace ICSharpCode.SharpDevelop.Workbench
{
/// <summary>
/// Options for the <see cref="IFileService.UpdateFileModel()"/> method.
/// </summary>
[Flags]
public enum FileUpdateOptions
{
None = 0,
/// <summary>
/// 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.
/// </summary>
OpenViewIfNoneExists = 1,
/// <summary>
/// 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).
/// </summary>
SaveToDisk = 2,
}
/// <summary>
/// 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 @@ -94,7 +114,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <param name="description">Description shown in the dialog.</param>
/// <param name="selectedPath">Optional: Initially selected folder.</param>
/// <returns>The selected folder; or <c>null</c> if the user cancelled the dialog.</returns>
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 @@ -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.
/// </summary>
/// <remarks>
/// 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 <see cref="OpenedFile.AddReference()"/>.
/// </remarks>
IReadOnlyList<OpenedFile> OpenedFiles { get; }
/// <summary>
/// Gets an opened file, or returns null if the file is not opened.
/// </summary>
/// <remarks>
/// 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 <see cref="OpenedFile.AddReference()"/>.
/// </remarks>
OpenedFile GetOpenedFile(FileName fileName);
/// <summary>
/// Gets an opened file, or returns null if the file is not opened.
/// </summary>
/// <inheritdoc cref="GetOpenedFile(FileName)"/>
OpenedFile GetOpenedFile(string fileName);
/// <summary>
/// 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 <b>must</b> be paired with a <see cref="OpenedFile.ReleaseReference()"/> call!
/// </summary>
OpenedFile GetOrCreateOpenedFile(FileName fileName);
OpenedFile CreateOpenedFile(FileName fileName);
/// <summary>
/// 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 <b>must</b> be paired with a <see cref="OpenedFile.ReleaseReference()"/> call!
/// </summary>
OpenedFile GetOrCreateOpenedFile(string fileName);
OpenedFile CreateUntitledOpenedFile(string defaultName, byte[] content);
/// <summary>
/// Creates a new untitled OpenedFile.
/// Updates a file by performing actions on a model.
/// </summary>
OpenedFile CreateUntitledOpenedFile(string defaultName, byte[] content);
/// <param name="fileName">The file to be updated.</param>
/// <param name="modelProvider">The type of model to use for the update.</param>
/// <param name="action">A delegate that performs the update.</param>
/// <param name="options">Provides options regarding the file model update.</param>
void UpdateFileModel<T>(FileName fileName, IFileModelProvider<T> modelProvider, Action<T> action, FileUpdateOptions options = FileUpdateOptions.None) where T : class;
#endregion
#region CheckFileName

344
src/Main/Base/Project/Workbench/File/OpenedFile.cs

@ -20,9 +20,7 @@ using System; @@ -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 @@ -69,6 +67,69 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// </summary>
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<T>(IFileModelProvider<T> modelProvider) where T : class;
}
class ModelEntry<T> : ModelEntry where T : class
{
readonly IFileModelProvider<T> provider;
public T Model;
public ModelEntry(IFileModelProvider<T> 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<U>(IFileModelProvider<U> modelProvider)
{
return modelProvider.CanLoadFrom(provider);
}
}
readonly List<ModelEntry> entries = new List<ModelEntry>();
ModelEntry dirtyEntry;
bool preventLoading;
#region IsDirty implementation
bool isDirty;
public event EventHandler IsDirtyChanged;
@ -77,7 +138,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -77,7 +138,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// Gets/sets if the file is has unsaved changes.
/// </summary>
public bool IsDirty {
get { return isDirty;}
get { return isDirty; }
private set {
if (isDirty != value) {
isDirty = value;
@ -123,14 +184,18 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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
/// <summary>
/// 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 @@ -138,11 +203,19 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <exception cref="InvalidOperationException">The file is untitled.</exception>
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
/// <summary>
/// Saves the file to disk.
/// </summary>
@ -150,27 +223,78 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -150,27 +223,78 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <exception cref="InvalidOperationException">The file is untitled.</exception>
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);
}
/// <summary>
/// Changes the file name, and saves the file to disk.
/// </summary>
/// <remarks>If the file is saved successfully, the dirty flag will be cleared (the dirty model becomes valid instead).</remarks>
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;
}
/// <summary>
/// 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.
/// </summary>
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<T> GetEntry<T>(IFileModelProvider<T> modelProvider) where T : class
{
CheckDisposed();
foreach (var entry in entries) {
if (entry.Provider == modelProvider)
return (ModelEntry<T>)entry;
}
return null;
}
/// <summary>
@ -179,31 +303,145 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -179,31 +303,145 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <param name="modelProvider">The model provider for the desired model type. Built-in model providers can be found in the <see cref="FileModels"/> class.</param>
/// <param name="options">Options that control how</param>
/// <returns>The model instance, or possibly <c>null</c> if <c>GetModelOptions.DoNotLoad</c> is in use.</returns>
/// <exception cref="IOException">Error loading the file.</exception>
/// <exception cref="System.IO.IOException">Error loading the file.</exception>
/// <exception cref="FormatException">Cannot construct the model because the underyling data is in an invalid format.</exception>
public T GetModel<T>(IFileModelProvider<T> 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<T> 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<T>(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;
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// This method is used when SharpDevelop detects
/// </remarks>
public void MakeDirty()
{
var entry = PickValidEntry();
if (entry != null) {
dirtyEntry = entry;
this.IsDirty = true;
}
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public void MakeDirty<T>(IFileModelProvider<T> 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
/// <summary>
/// Unloads the model associated with the specified model provider.
/// Unloading the dirty model will cause changes to be lost.
/// </summary>
public void UnloadModel<T>(IFileModelProvider<T> 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
/// <summary>
/// Replaces the model associated with the specified model provider with a different instance.
/// </summary>
@ -214,9 +452,85 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -214,9 +452,85 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// In <see cref="IFileModelProvider{T}.Save"/> implementations, you should use <see cref="ReplaceModelMode.TransferDirty"/> instead.</param>
public void ReplaceModel<T>(IFileModelProvider<T> 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<T>(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");
}
/// <summary>
/// Gets the reference count of the OpenedFile.
/// This is used for the IFileService.UpdateFileModel() implementation.
/// </summary>
public int ReferenceCount {
get { return referenceCount; }
}
public void AddReference()
{
CheckDisposed();
referenceCount++;
}
public void ReleaseReference()
{
CheckDisposed();
if (--referenceCount == 0) {
UnloadFile();
}
}
/// <summary>
/// Unloads the file, this method is called once after the reference count has reached zero.
/// </summary>
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
}
/*

18
src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs

@ -33,11 +33,12 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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<IServiceContainer>().AddService(typeof(DocumentFileModelInfo), info);
}
return document;
}
OffsetChangeMap ToOffsetChangeMap(IEnumerable<Edit> edits)
@ -58,12 +59,23 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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<TextDocument>.CanLoadFrom<U>(IFileModelProvider<U> otherProvider)

54
src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs

@ -0,0 +1,54 @@ @@ -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<XDocument>
{
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<U>(IFileModelProvider<U> 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)
{
}
}
}

10
src/Main/Base/Project/Workbench/FileChangeWatcher.cs

@ -82,7 +82,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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 @@ -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 {

2
src/Main/Base/Project/Workbench/IViewContent.cs

@ -120,7 +120,7 @@ namespace ICSharpCode.SharpDevelop @@ -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.
/// </summary>
IList<OpenedFile> Files { get; }
IReadOnlyList<OpenedFile> Files { get; }
/// <summary>
/// Gets the primary file being edited. Might return null if no file is edited.

6
src/Main/SharpDevelop/Templates/File/FileTemplateImpl.cs

@ -528,8 +528,8 @@ namespace ICSharpCode.SharpDevelop.Templates @@ -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 @@ -543,7 +543,7 @@ namespace ICSharpCode.SharpDevelop.Templates
SD.FileService.OpenFile(file.FileName);
} finally {
if (file != null)
file.CloseIfAllViewsClosed();
file.ReleaseReference();
}
}

2
src/Main/SharpDevelop/Workbench/DisplayBinding/AutoDetectDisplayBinding.cs

@ -51,7 +51,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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;

107
src/Main/SharpDevelop/Workbench/FileService.cs

@ -127,20 +127,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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<IFileDocumentProvider>();
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 @@ -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 @@ -174,7 +161,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
#endregion
#region OpenedFile
Dictionary<FileName, OpenedFile> openedFileDict = new Dictionary<FileName, OpenedFile>();
readonly Dictionary<FileName, OpenedFile> openedFileDict = new Dictionary<FileName, OpenedFile>();
/// <inheritdoc/>
public IReadOnlyList<OpenedFile> OpenedFiles {
@ -204,33 +191,33 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -204,33 +191,33 @@ namespace ICSharpCode.SharpDevelop.Workbench
}
/// <inheritdoc/>
public OpenedFile GetOrCreateOpenedFile(string fileName)
{
return GetOrCreateOpenedFile(FileName.Create(fileName));
}
/// <inheritdoc/>
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;
/// <inheritdoc/>
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 @@ -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 @@ -266,6 +253,28 @@ namespace ICSharpCode.SharpDevelop.Workbench
openedFileDict.Remove(file.FileName);
LoggingService.Debug("OpenedFileClosed: " + file.FileName);
}
public void UpdateFileModel<T>(FileName fileName, IFileModelProvider<T> modelProvider, Action<T> 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 @@ -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 @@ -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 @@ -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;
}
/// <inheritdoc/>

127
src/Main/SharpDevelop/Workbench/FileServiceOpenedFile.cs

@ -42,135 +42,44 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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);
}
/// <summary>
/// Gets the list of view contents registered with this opened file.
/// </summary>
public override IList<IViewContent> 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 {};
}
}

Loading…
Cancel
Save