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. 48
      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. 20
      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. 43
      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. 29
      src/Main/SharpDevelop/Workbench/WpfWorkbench.cs

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

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

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

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

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

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

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

@ -281,38 +281,6 @@ namespace ICSharpCode.AvalonEdit.AddIn @@ -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;
void TextAreaCaretPositionChanged(object sender, EventArgs e)
@ -336,7 +304,6 @@ namespace ICSharpCode.AvalonEdit.AddIn @@ -336,7 +304,6 @@ namespace ICSharpCode.AvalonEdit.AddIn
}
NavigationService.Log(this.BuildNavPoint());
var document = this.Document;
int lineOffset = document.GetLineByNumber(this.Line).Offset;
int chOffset = this.Column;
int col = 1;

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

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

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

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

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

@ -138,9 +138,5 @@ namespace HexEditor.View @@ -138,9 +138,5 @@ namespace HexEditor.View
{
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 @@ -101,6 +101,7 @@ namespace ICSharpCode.WpfDesign.AddIn
outline.Root = null;
}
using (XmlTextReader r = new XmlTextReader(stream)) {
r.XmlResolver = null;
XamlLoadSettings settings = new XamlLoadSettings();
settings.DesignerAssemblies.Add(typeof(WpfViewContent).Assembly);
settings.CustomServiceRegisterFunctions.Add(

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

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

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

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

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

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using System.Text;
@ -296,7 +297,7 @@ namespace ICSharpCode.SharpDevelop @@ -296,7 +297,7 @@ namespace ICSharpCode.SharpDevelop
{
return source.MinBy(keySelector, Comparer<K>.Default);
}
/// <summary>
/// Returns the minimum element.
/// </summary>
@ -334,7 +335,7 @@ namespace ICSharpCode.SharpDevelop @@ -334,7 +335,7 @@ namespace ICSharpCode.SharpDevelop
{
return source.MaxBy(keySelector, Comparer<K>.Default);
}
/// <summary>
/// Returns the maximum element.
/// </summary>
@ -740,7 +741,7 @@ namespace ICSharpCode.SharpDevelop @@ -740,7 +741,7 @@ namespace ICSharpCode.SharpDevelop
return s;
return s.Substring(0, length);
}
/// <summary>
/// Takes at most <param name="length" /> first characters from string, and appends '...' if string is longer.
/// String can be null.
@ -751,7 +752,7 @@ namespace ICSharpCode.SharpDevelop @@ -751,7 +752,7 @@ namespace ICSharpCode.SharpDevelop
return s;
return s.Substring(0, length) + "...";
}
/// <summary>
/// Removes any character given in the array from the given string.
/// </summary>
@ -920,7 +921,7 @@ namespace ICSharpCode.SharpDevelop @@ -920,7 +921,7 @@ namespace ICSharpCode.SharpDevelop
/// Gets an <see cref="IImage"/> from a resource.
/// </summary>
/// <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)
throw new ArgumentNullException("resourceService");
@ -933,7 +934,7 @@ namespace ICSharpCode.SharpDevelop @@ -933,7 +934,7 @@ namespace ICSharpCode.SharpDevelop
/// Gets an image source from a resource.
/// </summary>
/// <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)
throw new ArgumentNullException("resourceService");
@ -1035,7 +1036,12 @@ namespace ICSharpCode.SharpDevelop @@ -1035,7 +1036,12 @@ namespace ICSharpCode.SharpDevelop
/// </summary>
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>

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

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

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

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

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

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
using System;
using System.Text;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.SharpDevelop.Workbench
{
@ -11,6 +12,9 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -11,6 +12,9 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// </summary>
public class DocumentFileModelInfo
{
OpenedFile file;
TextDocument document;
public DocumentFileModelInfo()
{
this.Encoding = SD.FileService.DefaultFileEncoding;
@ -30,5 +34,52 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -30,5 +34,52 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// Gets the encoding of the model.
/// </summary>
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 @@ -19,7 +19,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <summary>
/// The text document file model provider.
/// </summary>
public static readonly IFileModelProvider<ICSharpCode.AvalonEdit.Document.TextDocument> TextDocument = new TextDocumentFileModelProvider();
public static readonly TextDocumentFileModelProvider TextDocument = new TextDocumentFileModelProvider();
/// <summary>
/// The XDocument file model provider.

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

@ -77,6 +77,15 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -77,6 +77,15 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// </summary>
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>
/// Notifies a model that it became unloaded.
/// This can happen due to:

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

@ -70,6 +70,13 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -70,6 +70,13 @@ namespace ICSharpCode.SharpDevelop.Workbench
/// <summary>
/// Represents an opened file.
/// </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
{
abstract class ModelEntry
@ -287,7 +294,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -287,7 +294,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
preventLoading = false;
}
}
#endregion
#region GetModel
@ -295,7 +302,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -295,7 +302,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
{
CheckDisposed();
foreach (var entry in entries) {
if (entry.Provider == modelProvider)
if (object.Equals(entry.Provider, modelProvider))
return (ModelEntry<T>)entry;
}
return null;
@ -495,7 +502,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -495,7 +502,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
#region Reference Counting
int referenceCount = 1;
void CheckDisposed()
{
if (referenceCount <= 0)
@ -694,29 +701,29 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -694,29 +701,29 @@ namespace ICSharpCode.SharpDevelop.Workbench
IsDirty = false;
}
// /// <summary>
// /// Called before saving the current view. This event is raised both when saving to disk and to memory (for switching between views).
// /// </summary>
// public event EventHandler SavingCurrentView;
//
// /// <summary>
// /// Called after saving the current view. This event is raised both when saving to disk and to memory (for switching between views).
// /// </summary>
// public event EventHandler SavedCurrentView;
// /// <summary>
// /// Called before saving the current view. This event is raised both when saving to disk and to memory (for switching between views).
// /// </summary>
// public event EventHandler SavingCurrentView;
//
// /// <summary>
// /// Called after saving the current view. This event is raised both when saving to disk and to memory (for switching between views).
// /// </summary>
// public event EventHandler SavedCurrentView;
void SaveCurrentViewToStream(Stream stream)
{
// if (SavingCurrentView != null)
// SavingCurrentView(this, EventArgs.Empty);
// if (SavingCurrentView != null)
// SavingCurrentView(this, EventArgs.Empty);
inSaveOperation = true;
try {
currentView.Save(this, stream);
} finally {
inSaveOperation = false;
}
// if (SavedCurrentView != null)
// SavedCurrentView(this, EventArgs.Empty);
// if (SavedCurrentView != null)
// SavedCurrentView(this, EventArgs.Empty);
}
protected void SaveCurrentView()
@ -736,7 +743,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -736,7 +743,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
return;
if (currentView != null) {
if (newView.SupportsSwitchToThisWithoutSaveLoad(this, currentView)
|| currentView.SupportsSwitchFromThisWithoutSaveLoad(this, newView))
|| currentView.SupportsSwitchFromThisWithoutSaveLoad(this, newView))
{
// switch without Save/Load
currentView.SwitchFromThisWithoutSaveLoad(this, newView);
@ -820,5 +827,5 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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; @@ -15,9 +15,18 @@ using ICSharpCode.SharpDevelop.Widgets.MyersDiff;
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);
var info = document != null ? document.GetFileModelInfo() : new DocumentFileModelInfo();
@ -30,44 +39,28 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -30,44 +39,28 @@ namespace ICSharpCode.SharpDevelop.Workbench
}
if (document != null) {
// Reload document
var diff = new MyersDiffAlgorithm(new StringSequence(document.Text), new StringSequence(textContent));
info.isLoading = true;
try {
document.Replace(0, document.TextLength, textContent, ToOffsetChangeMap(diff.GetEdits()));
document.UndoStack.ClearAll();
info.IsStale = false;
} finally {
info.isLoading = false;
}
ReloadDocument(document, new StringTextSource(textContent));
info.IsStale = false;
} else {
document = new TextDocument(textContent);
document.TextChanged += delegate { UpdateFileIsDirty(document, file, info); };
// Store info for the new document (necessary so that we don't forget the encoding we just used)
document.GetRequiredService<IServiceContainer>().AddService(typeof(DocumentFileModelInfo), info);
}
file.IsDirtyChanged += delegate { UpdateUndoStackIsOriginalFile(file, document); };
UpdateUndoStackIsOriginalFile(file, document);
return document;
}
static void UpdateUndoStackIsOriginalFile(ICanBeDirty file, TextDocument document)
{
if (file.IsDirty) {
document.UndoStack.DiscardOriginalFileMarker();
} else {
document.UndoStack.MarkAsOriginalFile();
}
}
void UpdateFileIsDirty(TextDocument document, OpenedFile file, DocumentFileModelInfo info)
public void ReloadDocument(TextDocument document, ITextSource newContent)
{
if (!info.isLoading) {
// Set dirty flag on OpenedFile according to UndoStack.IsOriginalFile
if (document.UndoStack.IsOriginalFile && file.IsDirty) {
// reset dirty marker
file.ReplaceModel(this, document, ReplaceModelMode.SetAsValid);
} else if (!document.UndoStack.IsOriginalFile) {
file.MakeDirty(this);
}
var info = document.GetFileModelInfo();
var diff = new MyersDiffAlgorithm(new StringSequence(document.Text), new StringSequence(newContent.Text));
if (info != null)
info.isLoading = true;
try {
document.Replace(0, document.TextLength, newContent, ToOffsetChangeMap(diff.GetEdits()));
document.UndoStack.ClearAll();
} finally {
if (info != null)
info.isLoading = false;
}
}
@ -87,14 +80,14 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -87,14 +80,14 @@ namespace ICSharpCode.SharpDevelop.Workbench
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();
SaveTo(ms, model, options);
file.ReplaceModel(FileModels.Binary, new BinaryFileModel(ms.ToArray()), ReplaceModelMode.TransferDirty);
}
public void SaveCopyAs(OpenedFile file, TextDocument model, FileName outputFileName, FileSaveOptions options)
void IFileModelProvider<TextDocument>.SaveCopyAs(OpenedFile file, TextDocument model, FileName outputFileName, FileSaveOptions options)
{
using (Stream s = SD.FileSystem.OpenWrite(outputFileName)) {
SaveTo(s, model, options);
@ -147,7 +140,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -147,7 +140,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
int r = MessageService.ShowCustomDialog(
"${res:Dialog.Options.IDEOptions.TextEditor.General.FontGroupBox.FileEncodingGroupBox}",
StringParser.Parse("${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss}",
new StringTagPair("encoding", encoding.EncodingName)),
new StringTagPair("encoding", encoding.EncodingName)),
0, -1,
"${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.UseUTF8}",
"${res:AvalonEdit.FileEncoding.EncodingCausesDataLoss.Continue}");
@ -159,19 +152,24 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -159,19 +152,24 @@ namespace ICSharpCode.SharpDevelop.Workbench
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;
}
public void NotifyStale(OpenedFile file, TextDocument model)
void IFileModelProvider<TextDocument>.NotifyStale(OpenedFile file, TextDocument model)
{
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 @@ -16,9 +16,6 @@ namespace ICSharpCode.SharpDevelop.Workbench
using (Stream stream = file.GetModel(FileModels.Binary).OpenRead()) {
document = XDocument.Load(stream, LoadOptions.PreserveWhitespace);
}
document.Changed += delegate {
file.MakeDirty(this);
};
return document;
}
@ -47,6 +44,13 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -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)
{
}

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

@ -36,6 +36,13 @@ namespace ICSharpCode.SharpDevelop @@ -36,6 +36,13 @@ namespace ICSharpCode.SharpDevelop
/// </summary>
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>
/// This is the UI element for the view.
/// You can use both Windows.Forms and WPF controls.

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

@ -31,15 +31,13 @@ using System.Windows.Input; @@ -31,15 +31,13 @@ using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Threading;
using ICSharpCode.Core;
using ICSharpCode.Core.Presentation;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Parser;
using ICSharpCode.SharpDevelop.Project;
using ICSharpCode.SharpDevelop.Startup;
using ICSharpCode.SharpDevelop.WinForms;
using ICSharpCode.SharpDevelop.Startup;
namespace ICSharpCode.SharpDevelop.Workbench
{
@ -107,7 +105,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -107,7 +105,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
// Set WindowState after PresentationSource is initialized, because now bounds and location are properly set.
this.WindowState = lastNonMinimizedWindowState;
}
void SetBounds(Rect bounds)
{
this.Left = bounds.Left;
@ -174,7 +172,7 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -174,7 +172,7 @@ namespace ICSharpCode.SharpDevelop.Workbench
// keep a reference to the event handler to prevent it from being garbage collected
// (CommandManager.RequerySuggested only keeps weak references to the event handlers)
EventHandler requerySuggestedEventHandler;
void CommandManager_RequerySuggested(object sender, EventArgs e)
{
UpdateMenu();
@ -263,10 +261,11 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -263,10 +261,11 @@ namespace ICSharpCode.SharpDevelop.Workbench
public ICollection<IViewContent> PrimaryViewContents {
get {
SD.MainThread.VerifyAccess();
return (from window in WorkbenchWindowCollection
where window.ViewContents.Count > 0
select window.ViewContents[0]
).ToList().AsReadOnly();
return (
from window in WorkbenchWindowCollection
where window.ViewContents.Count > 0
select window.ViewContents[0]
).ToList().AsReadOnly();
}
}
@ -306,10 +305,10 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -306,10 +305,10 @@ namespace ICSharpCode.SharpDevelop.Workbench
value.ActiveViewContentChanged += WorkbenchWindowActiveViewContentChanged;
}
WorkbenchWindowActiveViewContentChanged(null, null);
if (ActiveWorkbenchWindowChanged != null) {
ActiveWorkbenchWindowChanged(this, EventArgs.Empty);
}
WorkbenchWindowActiveViewContentChanged(null, null);
}
}
}
@ -319,10 +318,11 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -319,10 +318,11 @@ namespace ICSharpCode.SharpDevelop.Workbench
if (workbenchLayout != null) {
// update ActiveViewContent
IWorkbenchWindow window = this.ActiveWorkbenchWindow;
if (window != null)
if (window != null) {
this.ActiveViewContent = window.ActiveViewContent;
else
} else {
this.ActiveViewContent = null;
}
// update ActiveContent
this.ActiveContent = workbenchLayout.ActiveContent;
@ -360,6 +360,11 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -360,6 +360,11 @@ namespace ICSharpCode.SharpDevelop.Workbench
if (activeViewContent != value) {
activeViewContent = value;
// Call LoadModel() before raising the event; this
// ensures that listeners to ActiveViewContentChanged
// see an up-to-date model.
if (value != null)
value.LoadModel();
if (ActiveViewContentChanged != null) {
ActiveViewContentChanged(this, EventArgs.Empty);
}

Loading…
Cancel
Save