Browse Source

Attempt to port AvalonEditViewContent to new OpenedFile model.

Problem: document services now may be shared across multiple views; which is problematic as some code expects ITextMarkerService to be per-view.
Also, editor extensions might not support switching to a different IDocument instance - we should probably Detach + re-Attach extensions to avoid problems.
filemodels
Daniel Grunwald 12 years ago
parent
commit
8eedbfed9b
  1. 1
      src/AddIns/BackendBindings/WixBinding/Project/Src/WixDocumentReader.cs
  2. 44
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditDisplayBinding.cs
  3. 40
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs
  4. 33
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs
  5. 5
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Commands/SaveFileWithEncoding.cs
  6. 5
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/NewLineConsistencyCheck.cs
  7. 4
      src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs
  8. 1
      src/AddIns/DisplayBindings/WpfDesign/WpfDesign.AddIn/Src/WpfViewContent.cs
  9. 1
      src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlParser.cs
  10. 9
      src/AddIns/DisplayBindings/XmlEditor/Project/Src/XmlView.cs
  11. 12
      src/Main/Base/Project/Util/SharpDevelopExtensions.cs
  12. 4
      src/Main/Base/Project/Workbench/AbstractViewContent.cs
  13. 4
      src/Main/Base/Project/Workbench/File/BinaryFileModelProvider.cs
  14. 51
      src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs
  15. 2
      src/Main/Base/Project/Workbench/File/FileModels.cs
  16. 9
      src/Main/Base/Project/Workbench/File/IFileModelProvider.cs
  17. 39
      src/Main/Base/Project/Workbench/File/OpenedFile.cs
  18. 76
      src/Main/Base/Project/Workbench/File/TextDocumentFileModelProvider.cs
  19. 10
      src/Main/Base/Project/Workbench/File/XDocumentFileModelProvider.cs
  20. 7
      src/Main/Base/Project/Workbench/IViewContent.cs
  21. 25
      src/Main/SharpDevelop/Workbench/WpfWorkbench.cs

1
src/AddIns/BackendBindings/WixBinding/Project/Src/WixDocumentReader.cs

@ -63,6 +63,7 @@ namespace ICSharpCode.WixBinding
public WixDocumentReader(TextReader textReader) public WixDocumentReader(TextReader textReader)
{ {
reader = new XmlTextReader(textReader); reader = new XmlTextReader(textReader);
reader.XmlResolver = null;
} }
public ReadOnlyCollection<string> GetDialogIds() public ReadOnlyCollection<string> GetDialogIds()

44
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditDisplayBinding.cs

@ -17,13 +17,16 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.ComponentModel.Design;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils; using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Project; using ICSharpCode.SharpDevelop.Project;
@ -77,23 +80,49 @@ namespace ICSharpCode.AvalonEdit.AddIn
return true; return true;
} }
public IViewContent CreateContentForFile(OpenedFile file) static Encoding DetectExistingEncoding(OpenedFile file)
{ {
ChooseEncodingDialog dlg = new ChooseEncodingDialog(); var existingTextModel = file.GetModel(FileModels.TextDocument, GetModelOptions.DoNotLoad);
dlg.Owner = SD.Workbench.MainWindow; if (existingTextModel != null)
using (Stream stream = file.OpenRead()) { return existingTextModel.GetFileModelInfo().Encoding;
using (Stream stream = file.GetModel(FileModels.Binary).OpenRead()) {
using (StreamReader reader = FileReader.OpenStream(stream, SD.FileService.DefaultFileEncoding)) { using (StreamReader reader = FileReader.OpenStream(stream, SD.FileService.DefaultFileEncoding)) {
reader.Peek(); // force reader to auto-detect encoding reader.Peek();
dlg.Encoding = reader.CurrentEncoding; // force reader to auto-detect encoding
return reader.CurrentEncoding;
} }
} }
}
public IViewContent CreateContentForFile(OpenedFile file)
{
ChooseEncodingDialog dlg = new ChooseEncodingDialog();
dlg.Owner = SD.Workbench.MainWindow;
dlg.Encoding = DetectExistingEncoding(file);
if (dlg.ShowDialog() == true) { if (dlg.ShowDialog() == true) {
return new AvalonEditViewContent(file, dlg.Encoding); LoadWithEncoding(file, dlg.Encoding);
return new AvalonEditViewContent(file);
} else { } else {
return null; return null;
} }
} }
static void LoadWithEncoding(OpenedFile file, Encoding encoding)
{
var doc = file.GetModel(FileModels.TextDocument, GetModelOptions.DoNotLoad | GetModelOptions.AllowStale);
if (doc == null)
doc = new TextDocument();
doc.GetFileModelInfo().Encoding = encoding;
string newText;
using (Stream stream = file.GetModel(FileModels.Binary).OpenRead()) {
using (StreamReader reader = new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: false)) {
newText = reader.ReadToEnd();
}
}
FileModels.TextDocument.ReloadDocument(doc, new StringTextSource(newText));
file.ReplaceModel(FileModels.TextDocument, doc, ReplaceModelMode.SetAsValid);
}
public bool IsPreferredBindingForFile(FileName fileName) public bool IsPreferredBindingForFile(FileName fileName)
{ {
return false; return false;
@ -105,3 +134,4 @@ namespace ICSharpCode.AvalonEdit.AddIn
} }
} }
} }

40
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs

@ -45,17 +45,13 @@ namespace ICSharpCode.AvalonEdit.AddIn
readonly CodeEditor codeEditor = new CodeEditor(); readonly CodeEditor codeEditor = new CodeEditor();
IAnalyticsMonitorTrackedFeature trackedFeature; IAnalyticsMonitorTrackedFeature trackedFeature;
public AvalonEditViewContent(OpenedFile file, Encoding fixedEncodingForLoading = null) public AvalonEditViewContent(OpenedFile file)
{ {
// Use common service container for view content and primary text editor. // Use common service container for view content and primary text editor.
// This makes all text editor services available as view content services and vice versa. // This makes all text editor services available as view content services and vice versa.
// (with the exception of the interfaces implemented directly by this class, // (with the exception of the interfaces implemented directly by this class,
// those are available as view-content services only) // those are available as view-content services only)
this.Services = codeEditor.PrimaryTextEditor.GetRequiredService<IServiceContainer>(); this.Services = codeEditor.PrimaryTextEditor.GetRequiredService<IServiceContainer>();
if (fixedEncodingForLoading != null) {
codeEditor.UseFixedEncoding = true;
codeEditor.PrimaryTextEditor.Encoding = fixedEncodingForLoading;
}
this.TabPageText = "${res:FormsDesigner.DesignTabPages.SourceTabPage}"; this.TabPageText = "${res:FormsDesigner.DesignTabPages.SourceTabPage}";
if (file.FileName != null) { if (file.FileName != null) {
@ -66,10 +62,7 @@ namespace ICSharpCode.AvalonEdit.AddIn
} }
this.Files.Add(file); this.Files.Add(file);
file.ForceInitializeView(this); this.LoadModel();
file.IsDirtyChanged += PrimaryFile_IsDirtyChanged;
codeEditor.Document.UndoStack.PropertyChanged += codeEditor_Document_UndoStack_PropertyChanged;
} }
bool IsKnownFileExtension(string filetype) bool IsKnownFileExtension(string filetype)
@ -86,30 +79,19 @@ namespace ICSharpCode.AvalonEdit.AddIn
get { return codeEditor.PrimaryTextEditor.TextArea; } get { return codeEditor.PrimaryTextEditor.TextArea; }
} }
bool isLoading;
public override void Load(OpenedFile file, Stream stream) public override void LoadModel()
{ {
if (file != PrimaryFile) // if (!file.IsUntitled) {
return; // codeEditor.PrimaryTextEditor.IsReadOnly = (File.GetAttributes(file.FileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
isLoading = true; // }
try {
if (!file.IsUntitled) {
codeEditor.PrimaryTextEditor.IsReadOnly = (File.GetAttributes(file.FileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
}
codeEditor.Load(stream); codeEditor.Document = PrimaryFile.GetModel(FileModels.TextDocument);
// Load() causes the undo stack to think stuff changed, so re-mark the file as original if necessary base.LoadModel();
if (!this.PrimaryFile.IsDirty) {
codeEditor.Document.UndoStack.MarkAsOriginalFile();
}
// we set the file name after loading because this will place the fold markers etc. // we set the file name after loading because this will place the fold markers etc.
codeEditor.FileName = file.FileName; codeEditor.FileName = PrimaryFile.FileName;
BookmarksAttach(); BookmarksAttach();
} finally {
isLoading = false;
}
} }
protected override void OnFileNameChanged(OpenedFile file) protected override void OnFileNameChanged(OpenedFile file)

33
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs

@ -281,38 +281,6 @@ namespace ICSharpCode.AvalonEdit.AddIn
} }
} }
// always use primary text editor for loading/saving
// (the file encoding is stored only there)
public void Load(Stream stream)
{
if (UseFixedEncoding) {
using (StreamReader reader = new StreamReader(stream, primaryTextEditor.Encoding, detectEncodingFromByteOrderMarks: false)) {
ReloadDocument(primaryTextEditor.Document, reader.ReadToEnd());
}
} else {
// do encoding auto-detection
using (StreamReader reader = FileReader.OpenStream(stream, this.Encoding ?? SD.FileService.DefaultFileEncoding)) {
ReloadDocument(primaryTextEditor.Document, reader.ReadToEnd());
this.Encoding = reader.CurrentEncoding;
}
}
// raise event which allows removing existing NewLineConsistencyCheck overlays
if (LoadedFileContent != null)
LoadedFileContent(this, EventArgs.Empty);
NewLineConsistencyCheck.StartConsistencyCheck(this);
}
public event EventHandler LoadedFileContent;
public void Save(Stream stream)
{
// don't use TextEditor.Save here because that would touch the Modified flag,
// but OpenedFile is already managing IsDirty
using (StreamWriter writer = new StreamWriter(stream, primaryTextEditor.Encoding ?? Encoding.UTF8)) {
primaryTextEditor.Document.WriteTextTo(writer);
}
}
public event EventHandler CaretPositionChanged; public event EventHandler CaretPositionChanged;
void TextAreaCaretPositionChanged(object sender, EventArgs e) void TextAreaCaretPositionChanged(object sender, EventArgs e)
@ -336,7 +304,6 @@ namespace ICSharpCode.AvalonEdit.AddIn
} }
NavigationService.Log(this.BuildNavPoint()); NavigationService.Log(this.BuildNavPoint());
var document = this.Document;
int lineOffset = document.GetLineByNumber(this.Line).Offset; int lineOffset = document.GetLineByNumber(this.Line).Offset;
int chOffset = this.Column; int chOffset = this.Column;
int col = 1; int col = 1;

5
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Commands/SaveFileWithEncoding.cs

@ -18,6 +18,7 @@
using System; using System;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Gui;
@ -37,9 +38,9 @@ namespace ICSharpCode.AvalonEdit.AddIn.Commands
if (codeEditor != null) { if (codeEditor != null) {
ChooseEncodingDialog dlg = new ChooseEncodingDialog(); ChooseEncodingDialog dlg = new ChooseEncodingDialog();
dlg.Owner = SD.Workbench.MainWindow; dlg.Owner = SD.Workbench.MainWindow;
dlg.Encoding = codeEditor.PrimaryTextEditor.Encoding; dlg.Encoding = codeEditor.Document.GetFileModelInfo().Encoding;
if (dlg.ShowDialog() == true) { if (dlg.ShowDialog() == true) {
codeEditor.PrimaryTextEditor.Encoding = dlg.Encoding; codeEditor.Document.GetFileModelInfo().Encoding = dlg.Encoding;
SharpDevelop.Commands.SaveFile.Save(vc.PrimaryFile); SharpDevelop.Commands.SaveFile.Save(vc.PrimaryFile);
} }
} }

5
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/NewLineConsistencyCheck.cs

@ -125,16 +125,13 @@ namespace ICSharpCode.AvalonEdit.AddIn
var featureUse = SD.AnalyticsMonitor.TrackFeature(typeof(NewLineConsistencyCheck)); var featureUse = SD.AnalyticsMonitor.TrackFeature(typeof(NewLineConsistencyCheck));
EventHandler removeWarning = null; EventHandler removeWarning = delegate {
removeWarning = delegate {
groupBox.Remove(); groupBox.Remove();
editor.PrimaryTextEditor.TextArea.Focus(); editor.PrimaryTextEditor.TextArea.Focus();
editor.LoadedFileContent -= removeWarning;
featureUse.EndTracking(); featureUse.EndTracking();
}; };
editor.LoadedFileContent += removeWarning;
cancelButton.Click += delegate { cancelButton.Click += delegate {
SD.AnalyticsMonitor.TrackFeature(typeof(NewLineConsistencyCheck), "cancelButton"); SD.AnalyticsMonitor.TrackFeature(typeof(NewLineConsistencyCheck), "cancelButton");
removeWarning(null, null); removeWarning(null, null);

4
src/AddIns/DisplayBindings/HexEditor/Project/Src/View/HexEditView.cs

@ -138,9 +138,5 @@ namespace HexEditor.View
{ {
if (PrimaryFile != null) PrimaryFile.MakeDirty(); if (PrimaryFile != null) PrimaryFile.MakeDirty();
} }
public override bool IsDirty {
get { return base.IsDirty; }
}
} }
} }

1
src/AddIns/DisplayBindings/WpfDesign/WpfDesign.AddIn/Src/WpfViewContent.cs

@ -101,6 +101,7 @@ namespace ICSharpCode.WpfDesign.AddIn
outline.Root = null; outline.Root = null;
} }
using (XmlTextReader r = new XmlTextReader(stream)) { using (XmlTextReader r = new XmlTextReader(stream)) {
r.XmlResolver = null;
XamlLoadSettings settings = new XamlLoadSettings(); XamlLoadSettings settings = new XamlLoadSettings();
settings.DesignerAssemblies.Add(typeof(WpfViewContent).Assembly); settings.DesignerAssemblies.Add(typeof(WpfViewContent).Assembly);
settings.CustomServiceRegisterFunctions.Add( settings.CustomServiceRegisterFunctions.Add(

1
src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlParser.cs

@ -706,6 +706,7 @@ namespace ICSharpCode.WpfDesign.XamlDom
public static XamlObject ParseSnippet(XamlObject root, string xaml, XamlParserSettings settings) public static XamlObject ParseSnippet(XamlObject root, string xaml, XamlParserSettings settings)
{ {
XmlTextReader reader = new XmlTextReader(new StringReader(xaml)); XmlTextReader reader = new XmlTextReader(new StringReader(xaml));
reader.XmlResolver = null;
var element = root.OwnerDocument.XmlDocument.ReadNode(reader); var element = root.OwnerDocument.XmlDocument.ReadNode(reader);
if (element != null) { if (element != null) {

9
src/AddIns/DisplayBindings/XmlEditor/Project/Src/XmlView.cs

@ -230,7 +230,8 @@ namespace ICSharpCode.XmlEditor
TaskService.ClearExceptCommentTasks(); TaskService.ClearExceptCommentTasks();
if (IsWellFormed) { if (IsWellFormed) {
try { try {
using (XmlTextReader reader = new XmlTextReader(new StringReader(editor.Document.Text))) { using (XmlTextReader reader = new XmlTextReader(editor.Document.CreateReader())) {
reader.XmlResolver = null;
XmlSchemaInference schemaInference = new XmlSchemaInference(); XmlSchemaInference schemaInference = new XmlSchemaInference();
XmlSchemaSet schemaSet = schemaInference.InferSchema(reader); XmlSchemaSet schemaSet = schemaInference.InferSchema(reader);
return GetSchemas(schemaSet, editor); return GetSchemas(schemaSet, editor);
@ -351,8 +352,7 @@ namespace ICSharpCode.XmlEditor
if (editor == null) return false; if (editor == null) return false;
try { try {
StringReader stringReader = new StringReader(editor.Document.Text); XmlTextReader xmlReader = new XmlTextReader(editor.Document.CreateReader());
XmlTextReader xmlReader = new XmlTextReader(stringReader);
xmlReader.XmlResolver = null; xmlReader.XmlResolver = null;
XmlReaderSettings settings = new XmlReaderSettings(); XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema; settings.ValidationType = ValidationType.Schema;
@ -398,8 +398,7 @@ namespace ICSharpCode.XmlEditor
ITextEditor editor = TextEditor; ITextEditor editor = TextEditor;
if (editor == null) return false; if (editor == null) return false;
StringReader stringReader = new StringReader(editor.Document.Text); XmlTextReader xmlReader = new XmlTextReader(editor.Document.CreateReader());
XmlTextReader xmlReader = new XmlTextReader(stringReader);
xmlReader.XmlResolver = null; xmlReader.XmlResolver = null;
try { try {

12
src/Main/Base/Project/Util/SharpDevelopExtensions.cs

@ -19,6 +19,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel.Design;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -920,7 +921,7 @@ namespace ICSharpCode.SharpDevelop
/// Gets an <see cref="IImage"/> from a resource. /// Gets an <see cref="IImage"/> from a resource.
/// </summary> /// </summary>
/// <exception cref="ResourceNotFoundException">The resource with the specified name does not exist</exception> /// <exception cref="ResourceNotFoundException">The resource with the specified name does not exist</exception>
public static IImage GetImage(this IResourceService resourceService, string resourceName) public static IImage GetImage(this ICSharpCode.Core.IResourceService resourceService, string resourceName)
{ {
if (resourceService == null) if (resourceService == null)
throw new ArgumentNullException("resourceService"); throw new ArgumentNullException("resourceService");
@ -933,7 +934,7 @@ namespace ICSharpCode.SharpDevelop
/// Gets an image source from a resource. /// Gets an image source from a resource.
/// </summary> /// </summary>
/// <exception cref="ResourceNotFoundException">The resource with the specified name does not exist</exception> /// <exception cref="ResourceNotFoundException">The resource with the specified name does not exist</exception>
public static ImageSource GetImageSource(this IResourceService resourceService, string resourceName) public static ImageSource GetImageSource(this ICSharpCode.Core.IResourceService resourceService, string resourceName)
{ {
if (resourceService == null) if (resourceService == null)
throw new ArgumentNullException("resourceService"); throw new ArgumentNullException("resourceService");
@ -1035,7 +1036,12 @@ namespace ICSharpCode.SharpDevelop
/// </summary> /// </summary>
public static DocumentFileModelInfo GetFileModelInfo(this TextDocument document) public static DocumentFileModelInfo GetFileModelInfo(this TextDocument document)
{ {
return document.GetService<DocumentFileModelInfo>(); var info = document.GetService<DocumentFileModelInfo>();
if (info == null) {
info = new DocumentFileModelInfo();
document.GetRequiredService<IServiceContainer>().AddService(typeof(DocumentFileModelInfo), info);
}
return info;
} }
/// <summary> /// <summary>

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

@ -89,6 +89,10 @@ namespace ICSharpCode.SharpDevelop.Workbench
{ {
} }
public virtual void LoadModel()
{
}
string tabPageText = "TabPageText"; string tabPageText = "TabPageText";
public event EventHandler TabPageTextChanged; public event EventHandler TabPageTextChanged;

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

@ -65,6 +65,10 @@ namespace ICSharpCode.SharpDevelop.Workbench
{ {
} }
public void NotifyLoaded(OpenedFile file, IBinaryFileModel model)
{
}
public void NotifyUnloaded(OpenedFile file, IBinaryFileModel model) public void NotifyUnloaded(OpenedFile file, IBinaryFileModel model)
{ {
} }

51
src/Main/Base/Project/Workbench/File/DocumentFileModelInfo.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Text; using System.Text;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.SharpDevelop.Workbench namespace ICSharpCode.SharpDevelop.Workbench
{ {
@ -11,6 +12,9 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// </summary> /// </summary>
public class DocumentFileModelInfo public class DocumentFileModelInfo
{ {
OpenedFile file;
TextDocument document;
public DocumentFileModelInfo() public DocumentFileModelInfo()
{ {
this.Encoding = SD.FileService.DefaultFileEncoding; this.Encoding = SD.FileService.DefaultFileEncoding;
@ -30,5 +34,52 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// Gets the encoding of the model. /// Gets the encoding of the model.
/// </summary> /// </summary>
public Encoding Encoding { get; set; } public Encoding Encoding { get; set; }
internal void NotifyLoaded(OpenedFile file, TextDocument document)
{
if (this.file != null)
throw new InvalidOperationException("Duplicate NotifyLoaded() call");
this.file = file;
this.document = document;
document.TextChanged += OnDocumentTextChanged;
file.IsDirtyChanged += OnFileIsDirtyChanged;
UpdateUndoStackIsOriginalFile();
}
internal void NotifyUnloaded()
{
document.TextChanged -= OnDocumentTextChanged;
file.IsDirtyChanged -= OnFileIsDirtyChanged;
this.IsStale = true;
this.file = null;
this.document = null;
}
void OnDocumentTextChanged(object sender, EventArgs e)
{
if (!isLoading) {
// Set dirty flag on OpenedFile according to UndoStack.IsOriginalFile
if (document.UndoStack.IsOriginalFile && file.IsDirty) {
// reset dirty marker
file.ReplaceModel(FileModels.TextDocument, document, ReplaceModelMode.SetAsValid);
} else if (!document.UndoStack.IsOriginalFile) {
file.MakeDirty(FileModels.TextDocument);
}
}
}
void OnFileIsDirtyChanged(object sender, EventArgs e)
{
UpdateUndoStackIsOriginalFile();
}
void UpdateUndoStackIsOriginalFile()
{
if (file.IsDirty) {
document.UndoStack.DiscardOriginalFileMarker();
} else {
document.UndoStack.MarkAsOriginalFile();
}
}
} }
} }

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

@ -19,7 +19,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <summary> /// <summary>
/// The text document file model provider. /// The text document file model provider.
/// </summary> /// </summary>
public static readonly IFileModelProvider<ICSharpCode.AvalonEdit.Document.TextDocument> TextDocument = new TextDocumentFileModelProvider(); public static readonly TextDocumentFileModelProvider TextDocument = new TextDocumentFileModelProvider();
/// <summary> /// <summary>
/// The XDocument file model provider. /// The XDocument file model provider.

9
src/Main/Base/Project/Workbench/File/IFileModelProvider.cs

@ -77,6 +77,15 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// </summary> /// </summary>
void NotifyStale(OpenedFile file, T model); void NotifyStale(OpenedFile file, T model);
/// <summary>
/// Notifies the provider that a new model was loaded.
/// This method is called by OpenedFile.GetModel() after a Load() operation (unless Load() returned the existing stale model),
/// or by OpenedFile.ReplaceModel().
///
/// This method is intended to be used to set up the handling of the IsDirty flag.
/// </summary>
void NotifyLoaded(OpenedFile file, T model);
/// <summary> /// <summary>
/// Notifies a model that it became unloaded. /// Notifies a model that it became unloaded.
/// This can happen due to: /// This can happen due to:

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

@ -70,6 +70,13 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <summary> /// <summary>
/// Represents an opened file. /// Represents an opened file.
/// </summary> /// </summary>
/// <remarks>
/// This class uses reference counting: the <c>AddReference()</c> and <c>ReleaseReference()</c>
/// methods are used to track an explicit reference count.
/// When the last reference is released, the opened file will be disposed.
/// View contents must maintain a reference to their opened files
/// (this usually happens via the AbstractViewContent.Files collection).
/// </remarks>
public abstract class OpenedFile : ICanBeDirty public abstract class OpenedFile : ICanBeDirty
{ {
abstract class ModelEntry abstract class ModelEntry
@ -295,7 +302,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
{ {
CheckDisposed(); CheckDisposed();
foreach (var entry in entries) { foreach (var entry in entries) {
if (entry.Provider == modelProvider) if (object.Equals(entry.Provider, modelProvider))
return (ModelEntry<T>)entry; return (ModelEntry<T>)entry;
} }
return null; return null;
@ -694,29 +701,29 @@ namespace ICSharpCode.SharpDevelop.Workbench
IsDirty = false; IsDirty = false;
} }
// /// <summary> // /// <summary>
// /// Called before saving the current view. This event is raised both when saving to disk and to memory (for switching between views). // /// Called before saving the current view. This event is raised both when saving to disk and to memory (for switching between views).
// /// </summary> // /// </summary>
// public event EventHandler SavingCurrentView; // public event EventHandler SavingCurrentView;
// //
// /// <summary> // /// <summary>
// /// Called after saving the current view. This event is raised both when saving to disk and to memory (for switching between views). // /// Called after saving the current view. This event is raised both when saving to disk and to memory (for switching between views).
// /// </summary> // /// </summary>
// public event EventHandler SavedCurrentView; // public event EventHandler SavedCurrentView;
void SaveCurrentViewToStream(Stream stream) void SaveCurrentViewToStream(Stream stream)
{ {
// if (SavingCurrentView != null) // if (SavingCurrentView != null)
// SavingCurrentView(this, EventArgs.Empty); // SavingCurrentView(this, EventArgs.Empty);
inSaveOperation = true; inSaveOperation = true;
try { try {
currentView.Save(this, stream); currentView.Save(this, stream);
} finally { } finally {
inSaveOperation = false; inSaveOperation = false;
} }
// if (SavedCurrentView != null) // if (SavedCurrentView != null)
// SavedCurrentView(this, EventArgs.Empty); // SavedCurrentView(this, EventArgs.Empty);
} }
protected void SaveCurrentView() protected void SaveCurrentView()
@ -736,7 +743,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
return; return;
if (currentView != null) { if (currentView != null) {
if (newView.SupportsSwitchToThisWithoutSaveLoad(this, currentView) if (newView.SupportsSwitchToThisWithoutSaveLoad(this, currentView)
|| currentView.SupportsSwitchFromThisWithoutSaveLoad(this, newView)) || currentView.SupportsSwitchFromThisWithoutSaveLoad(this, newView))
{ {
// switch without Save/Load // switch without Save/Load
currentView.SwitchFromThisWithoutSaveLoad(this, newView); currentView.SwitchFromThisWithoutSaveLoad(this, newView);
@ -820,5 +827,5 @@ namespace ICSharpCode.SharpDevelop.Workbench
} }
} }
} }
*/ */
} }

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

@ -15,9 +15,18 @@ using ICSharpCode.SharpDevelop.Widgets.MyersDiff;
namespace ICSharpCode.SharpDevelop.Workbench namespace ICSharpCode.SharpDevelop.Workbench
{ {
sealed class TextDocumentFileModelProvider : IFileModelProvider<TextDocument> public sealed class TextDocumentFileModelProvider : IFileModelProvider<TextDocument>
{ {
public TextDocument Load(OpenedFile file) /// <summary>
/// Internal ctor: FileModels.TextDocument should be the only instance.
/// </summary>
internal TextDocumentFileModelProvider()
{
}
// use explicit interface implementations because these methods are supposed to only
// be called from the OpenedFile infrastructure
TextDocument IFileModelProvider<TextDocument>.Load(OpenedFile file)
{ {
TextDocument document = file.GetModel(this, GetModelOptions.AllowStale | GetModelOptions.DoNotLoad); TextDocument document = file.GetModel(this, GetModelOptions.AllowStale | GetModelOptions.DoNotLoad);
var info = document != null ? document.GetFileModelInfo() : new DocumentFileModelInfo(); var info = document != null ? document.GetFileModelInfo() : new DocumentFileModelInfo();
@ -30,44 +39,28 @@ namespace ICSharpCode.SharpDevelop.Workbench
} }
if (document != null) { if (document != null) {
// Reload document // Reload document
var diff = new MyersDiffAlgorithm(new StringSequence(document.Text), new StringSequence(textContent)); ReloadDocument(document, new StringTextSource(textContent));
info.isLoading = true; info.IsStale = false;
try {
document.Replace(0, document.TextLength, textContent, ToOffsetChangeMap(diff.GetEdits()));
document.UndoStack.ClearAll();
info.IsStale = false;
} finally {
info.isLoading = false;
}
} else { } else {
document = new TextDocument(textContent); document = new TextDocument(textContent);
document.TextChanged += delegate { UpdateFileIsDirty(document, file, info); }; // Store info for the new document (necessary so that we don't forget the encoding we just used)
document.GetRequiredService<IServiceContainer>().AddService(typeof(DocumentFileModelInfo), info); document.GetRequiredService<IServiceContainer>().AddService(typeof(DocumentFileModelInfo), info);
} }
file.IsDirtyChanged += delegate { UpdateUndoStackIsOriginalFile(file, document); };
UpdateUndoStackIsOriginalFile(file, document);
return document; return document;
} }
static void UpdateUndoStackIsOriginalFile(ICanBeDirty file, TextDocument document) public void ReloadDocument(TextDocument document, ITextSource newContent)
{
if (file.IsDirty) {
document.UndoStack.DiscardOriginalFileMarker();
} else {
document.UndoStack.MarkAsOriginalFile();
}
}
void UpdateFileIsDirty(TextDocument document, OpenedFile file, DocumentFileModelInfo info)
{ {
if (!info.isLoading) { var info = document.GetFileModelInfo();
// Set dirty flag on OpenedFile according to UndoStack.IsOriginalFile var diff = new MyersDiffAlgorithm(new StringSequence(document.Text), new StringSequence(newContent.Text));
if (document.UndoStack.IsOriginalFile && file.IsDirty) { if (info != null)
// reset dirty marker info.isLoading = true;
file.ReplaceModel(this, document, ReplaceModelMode.SetAsValid); try {
} else if (!document.UndoStack.IsOriginalFile) { document.Replace(0, document.TextLength, newContent, ToOffsetChangeMap(diff.GetEdits()));
file.MakeDirty(this); document.UndoStack.ClearAll();
} } finally {
if (info != null)
info.isLoading = false;
} }
} }
@ -87,14 +80,14 @@ namespace ICSharpCode.SharpDevelop.Workbench
return map; return map;
} }
public void Save(OpenedFile file, TextDocument model, FileSaveOptions options) void IFileModelProvider<TextDocument>.Save(OpenedFile file, TextDocument model, FileSaveOptions options)
{ {
MemoryStream ms = new MemoryStream(); MemoryStream ms = new MemoryStream();
SaveTo(ms, model, options); SaveTo(ms, model, options);
file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray()), ReplaceModelMode.TransferDirty); file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray()), ReplaceModelMode.TransferDirty);
} }
public void SaveCopyAs(OpenedFile file, TextDocument model, FileName outputFileName, FileSaveOptions options) void IFileModelProvider<TextDocument>.SaveCopyAs(OpenedFile file, TextDocument model, FileName outputFileName, FileSaveOptions options)
{ {
using (Stream s = SD.FileSystem.OpenWrite(outputFileName)) { using (Stream s = SD.FileSystem.OpenWrite(outputFileName)) {
SaveTo(s, model, options); SaveTo(s, model, options);
@ -147,7 +140,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
int r = MessageService.ShowCustomDialog( int r = MessageService.ShowCustomDialog(
"${res:Dialog.Options.IDEOptions.TextEditor.General.FontGroupBox.FileEncodingGroupBox}", "${res:Dialog.Options.IDEOptions.TextEditor.General.FontGroupBox.FileEncodingGroupBox}",
StringParser.Parse("${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss}", StringParser.Parse("${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss}",
new StringTagPair("encoding", encoding.EncodingName)), new StringTagPair("encoding", encoding.EncodingName)),
0, -1, 0, -1,
"${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.UseUTF8}", "${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.UseUTF8}",
"${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.Continue}"); "${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.Continue}");
@ -159,19 +152,24 @@ namespace ICSharpCode.SharpDevelop.Workbench
return otherProvider == FileModels.Binary || FileModels.Binary.CanLoadFrom(otherProvider); return otherProvider == FileModels.Binary || FileModels.Binary.CanLoadFrom(otherProvider);
} }
public void NotifyRename(OpenedFile file, TextDocument model, FileName oldName, FileName newName) void IFileModelProvider<TextDocument>.NotifyRename(OpenedFile file, TextDocument model, FileName oldName, FileName newName)
{ {
model.FileName = newName; model.FileName = newName;
} }
public void NotifyStale(OpenedFile file, TextDocument model) void IFileModelProvider<TextDocument>.NotifyStale(OpenedFile file, TextDocument model)
{ {
model.GetFileModelInfo().IsStale = true; model.GetFileModelInfo().IsStale = true;
} }
public void NotifyUnloaded(OpenedFile file, TextDocument model) void IFileModelProvider<TextDocument>.NotifyLoaded(OpenedFile file, TextDocument model)
{ {
model.GetFileModelInfo().IsStale = true; model.GetFileModelInfo().NotifyLoaded(file, model);
}
void IFileModelProvider<TextDocument>.NotifyUnloaded(OpenedFile file, TextDocument model)
{
model.GetFileModelInfo().NotifyUnloaded();
} }
} }
} }

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

@ -16,9 +16,6 @@ namespace ICSharpCode.SharpDevelop.Workbench
using (Stream stream = file.GetModel(FileModels.Binary).OpenRead()) { using (Stream stream = file.GetModel(FileModels.Binary).OpenRead()) {
document = XDocument.Load(stream, LoadOptions.PreserveWhitespace); document = XDocument.Load(stream, LoadOptions.PreserveWhitespace);
} }
document.Changed += delegate {
file.MakeDirty(this);
};
return document; return document;
} }
@ -47,6 +44,13 @@ namespace ICSharpCode.SharpDevelop.Workbench
{ {
} }
public void NotifyLoaded(OpenedFile file, XDocument model)
{
model.Changed += delegate {
file.MakeDirty(this);
};
}
public void NotifyUnloaded(OpenedFile file, XDocument model) public void NotifyUnloaded(OpenedFile file, XDocument model)
{ {
} }

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

@ -36,6 +36,13 @@ namespace ICSharpCode.SharpDevelop
/// </summary> /// </summary>
public interface IViewContent : IDisposable, ICanBeDirty, IServiceProvider public interface IViewContent : IDisposable, ICanBeDirty, IServiceProvider
{ {
/// <summary>
/// Notifies the view content that it should (re-)load the file models.
/// This method is called by the SharpDevelop infrastructure whenever a view content
/// receives focus.
/// </summary>
void LoadModel();
/// <summary> /// <summary>
/// This is the UI element for the view. /// This is the UI element for the view.
/// You can use both Windows.Forms and WPF controls. /// You can use both Windows.Forms and WPF controls.

25
src/Main/SharpDevelop/Workbench/WpfWorkbench.cs

@ -31,15 +31,13 @@ using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Navigation; using System.Windows.Navigation;
using System.Windows.Threading;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.Core.Presentation; using ICSharpCode.Core.Presentation;
using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Parser;
using ICSharpCode.SharpDevelop.Project;
using ICSharpCode.SharpDevelop.Startup;
using ICSharpCode.SharpDevelop.WinForms; using ICSharpCode.SharpDevelop.WinForms;
using ICSharpCode.SharpDevelop.Startup;
namespace ICSharpCode.SharpDevelop.Workbench namespace ICSharpCode.SharpDevelop.Workbench
{ {
@ -263,10 +261,11 @@ namespace ICSharpCode.SharpDevelop.Workbench
public ICollection<IViewContent> PrimaryViewContents { public ICollection<IViewContent> PrimaryViewContents {
get { get {
SD.MainThread.VerifyAccess(); SD.MainThread.VerifyAccess();
return (from window in WorkbenchWindowCollection return (
where window.ViewContents.Count > 0 from window in WorkbenchWindowCollection
select window.ViewContents[0] where window.ViewContents.Count > 0
).ToList().AsReadOnly(); select window.ViewContents[0]
).ToList().AsReadOnly();
} }
} }
@ -306,10 +305,10 @@ namespace ICSharpCode.SharpDevelop.Workbench
value.ActiveViewContentChanged += WorkbenchWindowActiveViewContentChanged; value.ActiveViewContentChanged += WorkbenchWindowActiveViewContentChanged;
} }
WorkbenchWindowActiveViewContentChanged(null, null);
if (ActiveWorkbenchWindowChanged != null) { if (ActiveWorkbenchWindowChanged != null) {
ActiveWorkbenchWindowChanged(this, EventArgs.Empty); ActiveWorkbenchWindowChanged(this, EventArgs.Empty);
} }
WorkbenchWindowActiveViewContentChanged(null, null);
} }
} }
} }
@ -319,10 +318,11 @@ namespace ICSharpCode.SharpDevelop.Workbench
if (workbenchLayout != null) { if (workbenchLayout != null) {
// update ActiveViewContent // update ActiveViewContent
IWorkbenchWindow window = this.ActiveWorkbenchWindow; IWorkbenchWindow window = this.ActiveWorkbenchWindow;
if (window != null) if (window != null) {
this.ActiveViewContent = window.ActiveViewContent; this.ActiveViewContent = window.ActiveViewContent;
else } else {
this.ActiveViewContent = null; this.ActiveViewContent = null;
}
// update ActiveContent // update ActiveContent
this.ActiveContent = workbenchLayout.ActiveContent; this.ActiveContent = workbenchLayout.ActiveContent;
@ -360,6 +360,11 @@ namespace ICSharpCode.SharpDevelop.Workbench
if (activeViewContent != value) { if (activeViewContent != value) {
activeViewContent = value; activeViewContent = value;
// Call LoadModel() before raising the event; this
// ensures that listeners to ActiveViewContentChanged
// see an up-to-date model.
if (value != null)
value.LoadModel();
if (ActiveViewContentChanged != null) { if (ActiveViewContentChanged != null) {
ActiveViewContentChanged(this, EventArgs.Empty); ActiveViewContentChanged(this, EventArgs.Empty);
} }

Loading…
Cancel
Save