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
/// It is valid for the onAdd callback to throw an exception - this will prevent the new item from /// It is valid for the onAdd callback to throw an exception - this will prevent the new item from
/// being added to the collection. /// being added to the collection.
/// </summary> /// </summary>
sealed class ObserveAddRemoveCollection<T> : Collection<T> public sealed class ObserveAddRemoveCollection<T> : Collection<T>
{ {
readonly Action<T> onAdd, onRemove; readonly Action<T> onAdd, onRemove;

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

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

11
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 // 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); 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) if (appConfigFileName == null)
return; return;
using (FakeXmlViewContent xml = new FakeXmlViewContent(appConfigFileName)) { SD.FileService.UpdateFileModel(
if (xml.Document != null) { appConfigFileName, FileModels.XDocument,
XElement configuration = xml.Document.Root; delegate (XDocument document) {
XElement configuration = document.Root;
XElement startup = configuration.Element("startup"); XElement startup = configuration.Element("startup");
if (startup == null) { if (startup == null) {
startup = new XElement("startup"); startup = new XElement("startup");
@ -304,7 +305,7 @@ namespace ICSharpCode.SharpDevelop.Project
supportedRuntime.SetAttributeValue("version", newFramework.SupportedRuntimeVersion); supportedRuntime.SetAttributeValue("version", newFramework.SupportedRuntimeVersion);
supportedRuntime.SetAttributeValue("sku", newFramework.SupportedSku); supportedRuntime.SetAttributeValue("sku", newFramework.SupportedSku);
} }
} );
} }
protected virtual void AddOrRemoveExtensions() protected virtual void AddOrRemoveExtensions()

12
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.Gui;
using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Parser;
using ICSharpCode.SharpDevelop.Project; using ICSharpCode.SharpDevelop.Project;
using ICSharpCode.SharpDevelop.Workbench;
namespace ICSharpCode.SharpDevelop.Refactoring namespace ICSharpCode.SharpDevelop.Refactoring
{ {
@ -255,16 +256,11 @@ namespace ICSharpCode.SharpDevelop.Refactoring
var openedFile = SD.FileService.GetOpenedFile(file.FileName); var openedFile = SD.FileService.GetOpenedFile(file.FileName);
if (openedFile == null) { if (openedFile == null) {
SD.FileService.OpenFile(file.FileName, false); SD.FileService.OpenFile(file.FileName, false);
openedFile = SD.FileService.GetOpenedFile(file.FileName); //? openedFile = SD.FileService.GetOpenedFile(file.FileName);
} }
if (openedFile != null) {
var provider = openedFile.CurrentView.GetService<IFileDocumentProvider>(); var document = openedFile.GetModel(FileModels.TextDocument);
if (provider != null) {
var document = provider.GetDocumentForFile(openedFile);
if (document == null)
throw new InvalidOperationException("Editor/document not found!");
file.Apply(document); file.Apply(document);
openedFile.MakeDirty();
} }
} }
} }

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

@ -23,6 +23,7 @@ using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.Core.Presentation; using ICSharpCode.Core.Presentation;
@ -230,20 +231,28 @@ namespace ICSharpCode.SharpDevelop.Workbench
#endregion #endregion
#region Files #region Files
FilesCollection files; ObserveAddRemoveCollection<OpenedFile> files;
ReadOnlyCollection<OpenedFile> filesReadonly; ReadOnlyCollection<OpenedFile> filesReadonly;
void InitFiles() void InitFiles()
{ {
files = new FilesCollection(this); files = new ObserveAddRemoveCollection<OpenedFile>(RegisterFileEventHandlers, UnregisterFileEventHandlers);
filesReadonly = new ReadOnlyCollection<OpenedFile>(files); 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 { protected Collection<OpenedFile> Files {
get { return files; } get { return files; }
} }
IList<OpenedFile> IViewContent.Files { IReadOnlyList<OpenedFile> IViewContent.Files {
get { return filesReadonly; } get { return filesReadonly; }
} }
@ -273,15 +282,11 @@ namespace ICSharpCode.SharpDevelop.Workbench
} }
} }
protected bool AutomaticallyRegisterViewOnFiles = true;
void RegisterFileEventHandlers(OpenedFile newItem) void RegisterFileEventHandlers(OpenedFile newItem)
{ {
newItem.FileNameChanged += OnFileNameChanged; newItem.FileNameChanged += OnFileNameChanged;
newItem.IsDirtyChanged += OnIsDirtyChanged; newItem.IsDirtyChanged += OnIsDirtyChanged;
if (AutomaticallyRegisterViewOnFiles) { newItem.AddReference();
newItem.RegisterView(this);
}
OnIsDirtyChanged(null, EventArgs.Empty); // re-evaluate this.IsDirty after changing the file collection 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.FileNameChanged -= OnFileNameChanged;
oldItem.IsDirtyChanged -= OnIsDirtyChanged; oldItem.IsDirtyChanged -= OnIsDirtyChanged;
if (AutomaticallyRegisterViewOnFiles) { oldItem.ReleaseReference();
oldItem.UnregisterView(this);
}
OnIsDirtyChanged(null, EventArgs.Empty); // re-evaluate this.IsDirty after changing the file collection 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) 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 #endregion
#region TitleName #region TitleName
@ -487,9 +453,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
{ {
workbenchWindow = null; workbenchWindow = null;
UnregisterOnActiveViewContentChanged(); UnregisterOnActiveViewContentChanged();
if (AutomaticallyRegisterViewOnFiles) { this.Files.Clear();
this.Files.Clear();
}
isDisposed = true; isDisposed = true;
if (Disposed != null) { if (Disposed != null) {
Disposed(this, EventArgs.Empty); Disposed(this, EventArgs.Empty);

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

@ -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;
namespace ICSharpCode.SharpDevelop.Workbench 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; 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); return new OnDiskBinaryModel(file);
} }
public void Save(OpenedFile file, IBinaryModel model) public void Save(OpenedFile file, IBinaryFileModel model)
{ {
SaveCopyAs(file, model, file.FileName); SaveCopyAs(file, model, file.FileName);
file.ReplaceModel(this, model, ReplaceModelMode.SetAsValid); // remove dirty flag 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; var onDisk = model as OnDiskBinaryModel;
if (onDisk != null) { if (onDisk != null) {
// We can just copy the file (but avoid copying to itself) // We can just copy the file (but avoid copying to itself)
if (onDisk.file.FileName != outputFileName) { if (onDisk.file.FileName != outputFileName) {
SD.FileSystem.CopyFile(onDisk.file.FileName, outputFileName); SD.FileSystem.CopyFile(onDisk.file.FileName, outputFileName, true);
} }
} else { } else {
using (var inputStream = model.OpenRead()) { using (var inputStream = model.OpenRead()) {
@ -57,15 +57,15 @@ namespace ICSharpCode.SharpDevelop.Workbench
return false; 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 @@
using System; using System;
using System.IO; using System.IO;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.SharpDevelop.Workbench namespace ICSharpCode.SharpDevelop.Workbench
{ {
@ -15,18 +14,23 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <summary> /// <summary>
/// The binary file model provider. /// The binary file model provider.
/// </summary> /// </summary>
public static readonly IFileModelProvider<IBinaryModel> Binary = new BinaryFileModelProvider(); public static readonly IFileModelProvider<IBinaryFileModel> Binary = new BinaryFileModelProvider();
/// <summary> /// <summary>
/// The text document file model provider. /// The text document file model provider.
/// </summary> /// </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> /// <summary>
/// Represents the binary contents of a file. /// Represents the binary contents of a file.
/// </summary> /// </summary>
public interface IBinaryModel public interface IBinaryFileModel
{ {
/// <summary> /// <summary>
/// Creates a stream that reads from the binary model. /// Creates a stream that reads from the binary model.
@ -35,4 +39,22 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <exception cref="IOException">Error opening the file.</exception> /// <exception cref="IOException">Error opening the file.</exception>
Stream OpenRead(); 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;
namespace ICSharpCode.SharpDevelop.Workbench 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> /// <summary>
/// Manages the list files opened by view contents so that multiple view contents opening the same file can synchronize. /// 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. /// Also provides events that can be used to listen to file operations performed in the IDE.
@ -94,7 +114,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <param name="description">Description shown in the dialog.</param> /// <param name="description">Description shown in the dialog.</param>
/// <param name="selectedPath">Optional: Initially selected folder.</param> /// <param name="selectedPath">Optional: Initially selected folder.</param>
/// <returns>The selected folder; or <c>null</c> if the user cancelled the dialog.</returns> /// <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 #endregion
#region OpenedFiles #region OpenedFiles
@ -103,38 +123,49 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// The returned collection is a read-only copy of the currently opened files - /// 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. /// it will not reflect future changes of the list of opened files.
/// </summary> /// </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; } IReadOnlyList<OpenedFile> OpenedFiles { get; }
/// <summary> /// <summary>
/// Gets an opened file, or returns null if the file is not opened. /// Gets an opened file, or returns null if the file is not opened.
/// </summary> /// </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); OpenedFile GetOpenedFile(FileName fileName);
/// <summary> /// <inheritdoc cref="GetOpenedFile(FileName)"/>
/// Gets an opened file, or returns null if the file is not opened.
/// </summary>
OpenedFile GetOpenedFile(string fileName); OpenedFile GetOpenedFile(string fileName);
/// <summary> /// <summary>
/// Gets or creates an opened file. /// Creates a new OpenedFile for the specified file name.
/// Warning: the opened file will be a file without any views attached. /// If the file is already open, an existing OpenedFile is returned instead (and its reference count is increased).
/// 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. /// Every CreateOpenedFile() call <b>must</b> be paired with a <see cref="OpenedFile.ReleaseReference()"/> call!
/// </summary> /// </summary>
OpenedFile GetOrCreateOpenedFile(FileName fileName); OpenedFile CreateOpenedFile(FileName fileName);
/// <summary> /// <summary>
/// Gets or creates an opened file. /// Creates a new untitled OpenedFile.
/// 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 /// Every CreateUntitledOpenedFile() call <b>must</b> be paired with a <see cref="OpenedFile.ReleaseReference()"/> call!
/// unload the OpenedFile instance if no views were attached to it.
/// </summary> /// </summary>
OpenedFile GetOrCreateOpenedFile(string fileName); OpenedFile CreateUntitledOpenedFile(string defaultName, byte[] content);
/// <summary> /// <summary>
/// Creates a new untitled OpenedFile. /// Updates a file by performing actions on a model.
/// </summary> /// </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 #endregion
#region CheckFileName #region CheckFileName

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

@ -20,9 +20,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Parser;
namespace ICSharpCode.SharpDevelop.Workbench namespace ICSharpCode.SharpDevelop.Workbench
{ {
@ -69,6 +67,69 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// </summary> /// </summary>
public abstract class OpenedFile : ICanBeDirty 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 #region IsDirty implementation
bool isDirty; bool isDirty;
public event EventHandler IsDirtyChanged; public event EventHandler IsDirtyChanged;
@ -77,7 +138,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// Gets/sets if the file is has unsaved changes. /// Gets/sets if the file is has unsaved changes.
/// </summary> /// </summary>
public bool IsDirty { public bool IsDirty {
get { return isDirty;} get { return isDirty; }
private set { private set {
if (isDirty != value) { if (isDirty != value) {
isDirty = value; isDirty = value;
@ -123,14 +184,18 @@ namespace ICSharpCode.SharpDevelop.Workbench
{ {
SD.MainThread.VerifyAccess(); SD.MainThread.VerifyAccess();
FileName oldName = fileName;
fileName = newValue; fileName = newValue;
foreach (var entry in entries)
entry.NotifyRename(this, oldName, newValue);
if (FileNameChanged != null) { if (FileNameChanged != null) {
FileNameChanged(this, EventArgs.Empty); FileNameChanged(this, EventArgs.Empty);
} }
} }
#endregion #endregion
#region ReloadFromDisk
/// <summary> /// <summary>
/// This method sets all models to 'stale', causing the file to be re-loaded from disk /// This method sets all models to 'stale', causing the file to be re-loaded from disk
/// on the next GetModel() call. /// on the next GetModel() call.
@ -138,11 +203,19 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <exception cref="InvalidOperationException">The file is untitled.</exception> /// <exception cref="InvalidOperationException">The file is untitled.</exception>
public void ReloadFromDisk() public void ReloadFromDisk()
{ {
CheckDisposed();
if (IsUntitled) if (IsUntitled)
throw new InvalidOperationException("Cannot reload an untitled file from disk."); 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> /// <summary>
/// Saves the file to disk. /// Saves the file to disk.
/// </summary> /// </summary>
@ -150,27 +223,78 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <exception cref="InvalidOperationException">The file is untitled.</exception> /// <exception cref="InvalidOperationException">The file is untitled.</exception>
public void SaveToDisk() public void SaveToDisk()
{ {
CheckDisposed();
if (IsUntitled) if (IsUntitled)
throw new InvalidOperationException("Cannot reload an untitled file from disk."); throw new InvalidOperationException("Cannot save an untitled file to disk.");
throw new NotImplementedException(); SaveToDisk(this.FileName);
} }
/// <summary> /// <summary>
/// Changes the file name, and saves the file to disk. /// Changes the file name, and saves the file to disk.
/// </summary> /// </summary>
/// <remarks>If the file is saved successfully, the dirty flag will be cleared (the dirty model becomes valid instead).</remarks> /// <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; this.FileName = fileName;
SaveToDisk(); this.IsDirty = false;
} }
/// <summary> /// <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. /// 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> /// </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> /// <summary>
@ -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="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> /// <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> /// <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> /// <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 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> /// <summary>
/// Sets the model associated with the specified model provider to be dirty. /// 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. /// All other models are marked as stale. If another model was previously dirty, those earlier changes will be lost.
/// </summary> /// </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 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> /// <summary>
/// Unloads the model associated with the specified model provider. /// Unloads the model associated with the specified model provider.
/// Unloading the dirty model will cause changes to be lost. /// Unloading the dirty model will cause changes to be lost.
/// </summary> /// </summary>
public void UnloadModel<T>(IFileModelProvider<T> modelProvider) where T : class 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> /// <summary>
/// Replaces the model associated with the specified model provider with a different instance. /// Replaces the model associated with the specified model provider with a different instance.
/// </summary> /// </summary>
@ -214,9 +452,85 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// In <see cref="IFileModelProvider{T}.Save"/> implementations, you should use <see cref="ReplaceModelMode.TransferDirty"/> instead.</param> /// 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 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
document.Replace(0, document.TextLength, textContent, ToOffsetChangeMap(diff.GetEdits())); document.Replace(0, document.TextLength, textContent, ToOffsetChangeMap(diff.GetEdits()));
document.UndoStack.ClearAll(); document.UndoStack.ClearAll();
info.IsStale = false; info.IsStale = false;
return document;
} else { } else {
document = new TextDocument(textContent); document = new TextDocument(textContent);
document.TextChanged += delegate { file.MakeDirty(this); };
document.GetRequiredService<IServiceContainer>().AddService(typeof(DocumentFileModelInfo), info); document.GetRequiredService<IServiceContainer>().AddService(typeof(DocumentFileModelInfo), info);
} }
return document;
} }
OffsetChangeMap ToOffsetChangeMap(IEnumerable<Edit> edits) OffsetChangeMap ToOffsetChangeMap(IEnumerable<Edit> edits)
@ -58,12 +59,23 @@ namespace ICSharpCode.SharpDevelop.Workbench
public void Save(OpenedFile file, TextDocument model) 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) 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) bool IFileModelProvider<TextDocument>.CanLoadFrom<U>(IFileModelProvider<U> otherProvider)

54
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<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
bool wasChangedExternally = false; bool wasChangedExternally = false;
OpenedFile file; OpenedFile file;
public FileChangeWatcher(OpenedFile file) internal FileChangeWatcher(OpenedFile file)
{ {
if (file == null) if (file == null)
throw new ArgumentNullException("file"); throw new ArgumentNullException("file");
@ -203,18 +203,18 @@ namespace ICSharpCode.SharpDevelop.Workbench
if (file == null) if (file == null)
return; return;
string fileName = file.FileName; var fileName = file.FileName;
if (!File.Exists(fileName)) if (!SD.FileSystem.FileExists(fileName))
return; return;
string message = StringParser.Parse( string message = StringParser.Parse(
"${res:ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.TextEditorDisplayBinding.FileAlteredMessage}", "${res:ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.TextEditorDisplayBinding.FileAlteredMessage}",
new StringTagPair("File", Path.GetFullPath(fileName)) new StringTagPair("File", Path.GetFullPath(fileName))
); );
if ((AutoLoadExternalChangesOption && file.IsDirty == false) if ((AutoLoadExternalChangesOption && !file.IsDirty)
|| MessageService.AskQuestion(message, StringParser.Parse("${res:MainWindow.DialogName}"))) || MessageService.AskQuestion(message, StringParser.Parse("${res:MainWindow.DialogName}")))
{ {
if (File.Exists(fileName)) { if (SD.FileSystem.FileExists(fileName)) {
file.ReloadFromDisk(); file.ReloadFromDisk();
} }
} else { } else {

2
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. /// Gets the list of files that are being edited using this view content.
/// The returned collection usually is read-only. /// The returned collection usually is read-only.
/// </summary> /// </summary>
IList<OpenedFile> Files { get; } IReadOnlyList<OpenedFile> Files { get; }
/// <summary> /// <summary>
/// Gets the primary file being edited. Might return null if no file is edited. /// 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
OpenedFile file = null; OpenedFile file = null;
try { try {
if (Path.IsPathRooted(fileName)) { if (Path.IsPathRooted(fileName)) {
file = SD.FileService.GetOrCreateOpenedFile(fileName); file = SD.FileService.CreateOpenedFile(fileName);
file.SetData(data); file.ReplaceModel(FileModels.Binary, new BinaryFileModel(data));
Directory.CreateDirectory(Path.GetDirectoryName(fileName)); Directory.CreateDirectory(Path.GetDirectoryName(fileName));
file.SaveToDisk(); file.SaveToDisk();
@ -543,7 +543,7 @@ namespace ICSharpCode.SharpDevelop.Templates
SD.FileService.OpenFile(file.FileName); SD.FileService.OpenFile(file.FileName);
} finally { } finally {
if (file != null) if (file != null)
file.CloseIfAllViewsClosed(); file.ReleaseReference();
} }
} }

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

@ -51,7 +51,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
double max = double.NegativeInfinity; double max = double.NegativeInfinity;
const int BUFFER_LENGTH = 4 * 1024; const int BUFFER_LENGTH = 4 * 1024;
using (var stream = file.OpenRead()) { using (var stream = file.GetModel(FileModels.Binary).OpenRead()) {
string mime = "text/plain"; string mime = "text/plain";
if (stream.Length > 0) { if (stream.Length > 0) {
stream.Position = 0; stream.Position = 0;

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

@ -127,20 +127,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
delegate { delegate {
OpenedFile file = this.GetOpenedFile(fileName); OpenedFile file = this.GetOpenedFile(fileName);
if (file != null) { if (file != null) {
if (file.CurrentView != null) { return file.GetModel(FileModels.TextDocument).CreateSnapshot();
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 null; return null;
}); });
@ -156,16 +143,16 @@ namespace ICSharpCode.SharpDevelop.Workbench
#endregion #endregion
#region BrowseForFolder #region BrowseForFolder
public string BrowseForFolder(string description, string selectedPath) public DirectoryName BrowseForFolder(string description, string selectedPath)
{ {
using (FolderBrowserDialog dialog = new FolderBrowserDialog()) { using (FolderBrowserDialog dialog = new FolderBrowserDialog()) {
dialog.Description = StringParser.Parse(description); 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.RootFolder = Environment.SpecialFolder.MyComputer;
dialog.SelectedPath = selectedPath; dialog.SelectedPath = selectedPath;
} }
if (dialog.ShowDialog() == DialogResult.OK) { if (dialog.ShowDialog() == DialogResult.OK) {
return dialog.SelectedPath; return DirectoryName.Create(dialog.SelectedPath);
} else { } else {
return null; return null;
} }
@ -174,7 +161,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
#endregion #endregion
#region OpenedFile #region OpenedFile
Dictionary<FileName, OpenedFile> openedFileDict = new Dictionary<FileName, OpenedFile>(); readonly Dictionary<FileName, OpenedFile> openedFileDict = new Dictionary<FileName, OpenedFile>();
/// <inheritdoc/> /// <inheritdoc/>
public IReadOnlyList<OpenedFile> OpenedFiles { public IReadOnlyList<OpenedFile> OpenedFiles {
@ -204,33 +191,33 @@ namespace ICSharpCode.SharpDevelop.Workbench
} }
/// <inheritdoc/> /// <inheritdoc/>
public OpenedFile GetOrCreateOpenedFile(string fileName) public OpenedFile CreateOpenedFile(FileName fileName)
{
return GetOrCreateOpenedFile(FileName.Create(fileName));
}
/// <inheritdoc/>
public OpenedFile GetOrCreateOpenedFile(FileName fileName)
{ {
if (fileName == null) if (fileName == null)
throw new ArgumentNullException("fileName"); throw new ArgumentNullException("fileName");
OpenedFile file; OpenedFile file;
if (!openedFileDict.TryGetValue(fileName, out file)) { if (openedFileDict.TryGetValue(fileName, out file)) {
file.AddReference();
} else {
openedFileDict[fileName] = file = new FileServiceOpenedFile(this, fileName); openedFileDict[fileName] = file = new FileServiceOpenedFile(this, fileName);
} }
return file; return file;
} }
int untitledFileID;
/// <inheritdoc/> /// <inheritdoc/>
public OpenedFile CreateUntitledOpenedFile(string defaultName, byte[] content) public OpenedFile CreateUntitledOpenedFile(string defaultName, byte[] content)
{ {
if (defaultName == null) if (defaultName == null)
throw new ArgumentNullException("defaultName"); throw new ArgumentNullException("defaultName");
OpenedFile file = new FileServiceOpenedFile(this, content); untitledFileID++;
file.FileName = new FileName(file.GetHashCode() + "/" + defaultName); var fileName = new FileName("untitled://" + untitledFileID + "/" + defaultName);
OpenedFile file = new FileServiceOpenedFile(this, fileName);
openedFileDict[file.FileName] = file; openedFileDict[file.FileName] = file;
file.ReplaceModel(FileModels.Binary, new BinaryFileModel(content));
return file; return file;
} }
@ -243,14 +230,14 @@ namespace ICSharpCode.SharpDevelop.Workbench
if (openedFileDict[oldName] != file) if (openedFileDict[oldName] != file)
throw new ArgumentException("file must be registered as oldName"); throw new ArgumentException("file must be registered as oldName");
if (openedFileDict.ContainsKey(newName)) { OpenedFile oldFile;
OpenedFile oldFile = openedFileDict[newName]; if (openedFileDict.TryGetValue(newName, out oldFile)) {
if (oldFile.CurrentView != null) { foreach (var view in SD.Workbench.ViewContentCollection.ToArray()) {
if (oldFile.CurrentView.WorkbenchWindow != null) if (view.WorkbenchWindow != null && view.Files.Contains(oldFile))
oldFile.CurrentView.WorkbenchWindow.CloseWindow(true); view.WorkbenchWindow.CloseWindow(true);
} else {
throw new ArgumentException("there already is a file with the newName");
} }
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.Remove(oldName);
openedFileDict[newName] = file; openedFileDict[newName] = file;
@ -266,6 +253,28 @@ namespace ICSharpCode.SharpDevelop.Workbench
openedFileDict.Remove(file.FileName); openedFileDict.Remove(file.FileName);
LoggingService.Debug("OpenedFileClosed: " + 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 #endregion
#region CheckFileName #region CheckFileName
@ -349,7 +358,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
public void Invoke(FileName fileName) public void Invoke(FileName fileName)
{ {
OpenedFile file = SD.FileService.GetOrCreateOpenedFile(fileName); OpenedFile file = SD.FileService.CreateOpenedFile(fileName);
try { try {
IViewContent newContent = binding.CreateContentForFile(file); IViewContent newContent = binding.CreateContentForFile(file);
if (newContent != null) { if (newContent != null) {
@ -357,7 +366,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
SD.Workbench.ShowView(newContent, switchToOpenedView); SD.Workbench.ShowView(newContent, switchToOpenedView);
} }
} finally { } finally {
file.CloseIfAllViewsClosed(); file.ReleaseReference();
} }
} }
} }
@ -383,18 +392,20 @@ namespace ICSharpCode.SharpDevelop.Workbench
binding = new ErrorFallbackBinding("Can't create display binding for file " + defaultName); binding = new ErrorFallbackBinding("Can't create display binding for file " + defaultName);
} }
OpenedFile file = CreateUntitledOpenedFile(defaultName, content); OpenedFile file = CreateUntitledOpenedFile(defaultName, content);
try {
IViewContent newContent = binding.CreateContentForFile(file); IViewContent newContent = binding.CreateContentForFile(file);
if (newContent == null) { if (newContent == null) {
LoggingService.Warn("Created view content was null - DefaultName:" + defaultName); LoggingService.Warn("Created view content was null - DefaultName:" + defaultName);
file.CloseIfAllViewsClosed(); return null;
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/> /// <inheritdoc/>

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

@ -42,135 +42,44 @@ namespace ICSharpCode.SharpDevelop.Workbench
{ {
this.fileService = fileService; this.fileService = fileService;
this.FileName = fileName; this.FileName = fileName;
IsUntitled = false;
fileChangeWatcher = new FileChangeWatcher(this); fileChangeWatcher = new FileChangeWatcher(this);
} }
internal FileServiceOpenedFile(FileService fileService, byte[] fileData) protected override void UnloadFile()
{ {
this.fileService = fileService; bool wasDirty = this.IsDirty;
this.FileName = null; fileService.OpenedFileClosed(this);
SetData(fileData); base.UnloadFile();
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");
base.ForceInitializeView(view); //FileClosed(this, EventArgs.Empty);
}
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));
if (SD.Workbench != null) { if (fileChangeWatcher != null) {
SD.Workbench.ActiveViewContentChanged -= WorkbenchActiveViewContentChanged; fileChangeWatcher.Dispose();
fileChangeWatcher = null;
} }
#if DEBUG
view.Disposed -= ViewDisposed;
#endif
registeredViews.Remove(view); if (wasDirty) {
if (registeredViews.Count > 0) { // We discarded some information when closing the file,
if (currentView == view) { // so we need to re-parse it.
SaveCurrentView(); if (SD.FileSystem.FileExists(this.FileName))
currentView = null; SD.ParserService.ParseAsync(this.FileName).FireAndForget();
} else
} else { SD.ParserService.ClearParseInformation(this.FileName);
// all views to the file were closed
CloseIfAllViewsClosed();
} }
} }
public override void CloseIfAllViewsClosed() public override void SaveToDisk(FileName fileName)
{
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()
{ {
try { try {
if (fileChangeWatcher != null) if (fileChangeWatcher != null)
fileChangeWatcher.Enabled = false; fileChangeWatcher.Enabled = false;
base.SaveToDisk(); base.SaveToDisk(fileName);
} finally { } finally {
if (fileChangeWatcher != null) if (fileChangeWatcher != null)
fileChangeWatcher.Enabled = true; fileChangeWatcher.Enabled = true;
} }
} }
public override event EventHandler FileClosed = delegate {}; //public override event EventHandler FileClosed = delegate {};
} }
} }

Loading…
Cancel
Save