diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestClass.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestClass.cs
index f9f604633d..e78de022b7 100644
--- a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestClass.cs
+++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestClass.cs
@@ -4,9 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
+
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.TypeSystem.Implementation;
using ICSharpCode.SharpDevelop;
+using ICSharpCode.SharpDevelop.Dom;
+using ICSharpCode.SharpDevelop.Parser;
using ICSharpCode.SharpDevelop.Widgets;
namespace ICSharpCode.UnitTesting
@@ -67,7 +70,12 @@ namespace ICSharpCode.UnitTesting
public ITypeDefinition Resolve()
{
- ICompilation compilation = SD.ParserService.GetCompilation(parentProject.Project);
+ return Resolve(SD.ParserService.GetCurrentSolutionSnapshot());
+ }
+
+ public ITypeDefinition Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot)
+ {
+ ICompilation compilation = solutionSnapshot.GetCompilation(parentProject.Project);
IType type = compilation.MainAssembly.GetTypeDefinition(fullTypeName);
return type.GetDefinition();
}
diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestMethod.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestMethod.cs
index 82daccee8a..01a69492ea 100644
--- a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestMethod.cs
+++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestMethod.cs
@@ -5,6 +5,7 @@ using System;
using System.Windows.Input;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.SharpDevelop;
+using ICSharpCode.SharpDevelop.Parser;
using ICSharpCode.SharpDevelop.Widgets;
namespace ICSharpCode.UnitTesting
@@ -80,7 +81,12 @@ namespace ICSharpCode.UnitTesting
public IMethod Resolve()
{
- ICompilation compilation = SD.ParserService.GetCompilation(parentProject.Project);
+ return Resolve(SD.ParserService.GetCurrentSolutionSnapshot());
+ }
+
+ public IMethod Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot)
+ {
+ ICompilation compilation = solutionSnapshot.GetCompilation(parentProject.Project);
IMethod resolvedMethod = method.Resolve(new SimpleTypeResolveContext(compilation.MainAssembly));
return resolvedMethod;
}
diff --git a/src/Main/Base/Project/Dom/ConcatModelCollection.cs b/src/Main/Base/Project/Dom/ConcatModelCollection.cs
new file mode 100644
index 0000000000..9e4c4218d1
--- /dev/null
+++ b/src/Main/Base/Project/Dom/ConcatModelCollection.cs
@@ -0,0 +1,164 @@
+// 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.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// A that works by concatening multiple
+ /// other model collections.
+ ///
+ public sealed class ConcatModelCollection : IModelCollection
+ {
+ sealed class InputCollection : Collection>
+ {
+ readonly ConcatModelCollection owner;
+
+ public InputCollection(ConcatModelCollection owner)
+ {
+ this.owner = owner;
+ }
+
+ protected override void ClearItems()
+ {
+ if (owner.collectionChanged != null) {
+ foreach (var input in Items) {
+ input.CollectionChanged -= owner.OnInputCollectionChanged;
+ }
+ }
+ base.ClearItems();
+ owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ protected override void InsertItem(int index, IModelCollection item)
+ {
+ if (owner.collectionChanged != null)
+ item.CollectionChanged += owner.OnInputCollectionChanged;
+ base.InsertItem(index, item);
+ owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(
+ NotifyCollectionChangedAction.Add, item.ToArray(), owner.GetCount(index)));
+ }
+
+ protected override void RemoveItem(int index)
+ {
+ if (owner.collectionChanged != null)
+ Items[index].CollectionChanged -= owner.OnInputCollectionChanged;
+ var oldItems = Items[index].ToArray();
+ base.RemoveItem(index);
+ owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(
+ NotifyCollectionChangedAction.Remove, oldItems, owner.GetCount(index)));
+ }
+
+ protected override void SetItem(int index, IModelCollection item)
+ {
+ RemoveItem(index);
+ InsertItem(index, item);
+ }
+ }
+
+ InputCollection inputs;
+
+ public ConcatModelCollection()
+ {
+ this.inputs = new InputCollection(this);
+ }
+
+ public IList> Inputs {
+ get { return inputs; }
+ }
+
+ NotifyCollectionChangedEventHandler collectionChanged;
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged {
+ add {
+ var oldEventHandlers = collectionChanged;
+ collectionChanged += value;
+ if (oldEventHandlers == null && collectionChanged != null) {
+ foreach (var input in inputs)
+ input.CollectionChanged += OnInputCollectionChanged;
+ }
+ }
+ remove {
+ var oldEventHandlers = collectionChanged;
+ collectionChanged -= value;
+ if (oldEventHandlers != null && collectionChanged == null) {
+ foreach (var input in inputs)
+ input.CollectionChanged -= OnInputCollectionChanged;
+ }
+ }
+ }
+
+ void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
+ {
+ if (collectionChanged != null)
+ collectionChanged(this, e);
+ }
+
+ void OnInputCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ int inputIndex = inputs.IndexOf((IModelCollection)sender);
+ int startIndex = GetCount(inputIndex);
+ NotifyCollectionChangedEventArgs newEventArgs;
+ switch (e.Action) {
+ case NotifyCollectionChangedAction.Add:
+ newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.NewItems, startIndex + e.NewStartingIndex);
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, startIndex + e.OldStartingIndex);
+ break;
+ case NotifyCollectionChangedAction.Replace:
+ newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, e.NewItems, startIndex + e.OldStartingIndex);
+ break;
+ case NotifyCollectionChangedAction.Move:
+ newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, startIndex + e.OldStartingIndex, startIndex + e.NewStartingIndex);
+ break;
+ case NotifyCollectionChangedAction.Reset:
+ newEventArgs = new NotifyCollectionChangedEventArgs(e.Action);
+ break;
+ default:
+ throw new NotSupportedException("Invalid value for NotifyCollectionChangedAction");
+ }
+ collectionChanged(this, newEventArgs);
+ }
+
+ public int Count {
+ get { return GetCount(inputs.Count); }
+ }
+
+ int GetCount(int inputIndex)
+ {
+ int count = 0;
+ for (int i = 0; i < inputIndex; i++) {
+ count += inputs[i].Count;
+ }
+ return count;
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return inputs.SelectMany(i => i).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public T this[int index] {
+ get {
+ int inputIndex = 0;
+ while (index >= inputs[inputIndex].Count) {
+ index -= inputs[inputIndex].Count;
+ inputIndex++;
+ }
+ return inputs[inputIndex][index];
+ }
+ }
+ }
+}
diff --git a/src/Main/Base/Project/Dom/IEntityModel.cs b/src/Main/Base/Project/Dom/IEntityModel.cs
new file mode 100644
index 0000000000..7f1f05be19
--- /dev/null
+++ b/src/Main/Base/Project/Dom/IEntityModel.cs
@@ -0,0 +1,40 @@
+// 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.ComponentModel;
+using ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.SharpDevelop.Parser;
+using ICSharpCode.SharpDevelop.Project;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// An NRefactory entity as a model.
+ ///
+ public interface IEntityModel : INotifyPropertyChanged
+ {
+ ///
+ /// Gets the parent project that contains this entity.
+ /// May return null if the entity is not part of a project.
+ ///
+ IProject ParentProject { get; }
+
+ ///
+ /// Gets the region where this entity is defined.
+ ///
+ DomRegion Region { get; }
+
+ ///
+ /// Resolves the entity in the current solution snapshot.
+ /// Returns null if the entity could not be resolved.
+ ///
+ IEntity Resolve();
+
+ ///
+ /// Resolves the entity in the specified solution snapshot.
+ /// Returns null if the entity could not be resolved.
+ ///
+ IEntity Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot);
+ }
+}
diff --git a/src/Main/Base/Project/Dom/IEntityModelContext.cs b/src/Main/Base/Project/Dom/IEntityModelContext.cs
new file mode 100644
index 0000000000..cb3f299693
--- /dev/null
+++ b/src/Main/Base/Project/Dom/IEntityModelContext.cs
@@ -0,0 +1,79 @@
+// 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 ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.SharpDevelop.Parser;
+using ICSharpCode.SharpDevelop.Project;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// The context for an entity model.
+ /// This may be a reference to a project, or a compilation provider for a single stand-alone code file.
+ ///
+ public interface IEntityModelContext
+ {
+ ///
+ /// Used for .
+ ///
+ IProject Project { get; }
+
+ ///
+ /// Used for .
+ ///
+ ///
+ /// The solution snapshot provided to ,
+ /// or null if the overload was used.
+ ///
+ ICompilation GetCompilation(ISolutionSnapshotWithProjectMapping solutionSnapshot);
+
+ ///
+ /// Returns true if part1 is considered a better candidate for the primary part than part2.
+ ///
+ bool IsBetterPart(IUnresolvedTypeDefinition part1, IUnresolvedTypeDefinition part2);
+ }
+
+ public class ProjectEntityModelContext : IEntityModelContext
+ {
+ readonly IProject project;
+ readonly string primaryCodeFileExtension;
+
+ public ProjectEntityModelContext(IProject project, string primaryCodeFileExtension)
+ {
+ if (project == null)
+ throw new ArgumentNullException("project");
+ this.project = project;
+ this.primaryCodeFileExtension = primaryCodeFileExtension;
+ }
+
+ public IProject Project {
+ get { return project; }
+ }
+
+ public ICompilation GetCompilation(ISolutionSnapshotWithProjectMapping solutionSnapshot)
+ {
+ if (solutionSnapshot != null)
+ return solutionSnapshot.GetCompilation(project);
+ else
+ return SD.ParserService.GetCompilation(project);
+ }
+
+ public bool IsBetterPart(IUnresolvedTypeDefinition part1, IUnresolvedTypeDefinition part2)
+ {
+ IUnresolvedFile file1 = part1.UnresolvedFile;
+ IUnresolvedFile file2 = part2.UnresolvedFile;
+ if (file1 != null && file2 == null)
+ return true;
+ if (file1 == null)
+ return false;
+ bool file1HasExtension = file1.FileName.EndsWith(primaryCodeFileExtension, StringComparison.OrdinalIgnoreCase);
+ bool file2HasExtension = file2.FileName.EndsWith(primaryCodeFileExtension, StringComparison.OrdinalIgnoreCase);
+ if (file1HasExtension && !file2HasExtension)
+ return true;
+ if (!file1HasExtension && file2HasExtension)
+ return false;
+ return file1.FileName.Length < file2.FileName.Length;
+ }
+ }
+}
diff --git a/src/Main/Base/Project/Dom/IMemberModel.cs b/src/Main/Base/Project/Dom/IMemberModel.cs
new file mode 100644
index 0000000000..9a719fa599
--- /dev/null
+++ b/src/Main/Base/Project/Dom/IMemberModel.cs
@@ -0,0 +1,27 @@
+// 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 ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.SharpDevelop.Parser;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// Observable model for a member.
+ ///
+ public interface IMemberModel : IEntityModel
+ {
+ ///
+ /// Resolves the member in the current solution snapshot.
+ /// Returns null if the member could not be resolved.
+ ///
+ IMember Resolve();
+
+ ///
+ /// Resolves the member in the specified solution snapshot.
+ /// Returns null if the member could not be resolved.
+ ///
+ IMember Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot);
+ }
+}
diff --git a/src/Main/Base/Project/Dom/IModelCollection.cs b/src/Main/Base/Project/Dom/IModelCollection.cs
index 3f251e7bab..b5ce0e7f66 100644
--- a/src/Main/Base/Project/Dom/IModelCollection.cs
+++ b/src/Main/Base/Project/Dom/IModelCollection.cs
@@ -10,7 +10,7 @@ namespace ICSharpCode.SharpDevelop.Dom
///
/// A read-only collection that provides change notifications.
///
- public interface IModelCollection : IReadOnlyCollection, INotifyCollectionChanged
+ public interface IModelCollection : IReadOnlyList, INotifyCollectionChanged
{
}
}
diff --git a/src/Main/Base/Project/Dom/IModelService.cs b/src/Main/Base/Project/Dom/IModelService.cs
new file mode 100644
index 0000000000..5a9e125bb9
--- /dev/null
+++ b/src/Main/Base/Project/Dom/IModelService.cs
@@ -0,0 +1,15 @@
+// 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 ICSharpCode.NRefactory.TypeSystem;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// Service that enables lookup of model objects from NRefactory objects.
+ ///
+ public interface IModelService
+ {
+ }
+}
diff --git a/src/Main/Base/Project/Dom/ITypeDefinitionModel.cs b/src/Main/Base/Project/Dom/ITypeDefinitionModel.cs
new file mode 100644
index 0000000000..c0e10383ba
--- /dev/null
+++ b/src/Main/Base/Project/Dom/ITypeDefinitionModel.cs
@@ -0,0 +1,31 @@
+// 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 ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.SharpDevelop.Parser;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// Observable model for a type definition.
+ ///
+ public interface ITypeDefinitionModel : IEntityModel
+ {
+ FullTypeName FullTypeName { get; }
+ IModelCollection NestedTypes { get; }
+ IModelCollection Members { get; }
+
+ ///
+ /// Resolves the type definition in the current solution snapshot.
+ /// Returns null if the type definition could not be resolved.
+ ///
+ new ITypeDefinition Resolve();
+
+ ///
+ /// Resolves the type definition in the specified solution snapshot.
+ /// Returns null if the type definition could not be resolved.
+ ///
+ new ITypeDefinition Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot);
+ }
+}
diff --git a/src/Main/Base/Project/Dom/ITypeDefinitionModelCollection.cs b/src/Main/Base/Project/Dom/ITypeDefinitionModelCollection.cs
new file mode 100644
index 0000000000..4b4dba61d2
--- /dev/null
+++ b/src/Main/Base/Project/Dom/ITypeDefinitionModelCollection.cs
@@ -0,0 +1,69 @@
+// 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.Collections;
+using System.Collections.ObjectModel;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using ICSharpCode.NRefactory.TypeSystem;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// A collection of type definition models with the ability to look them up by name.
+ ///
+ public interface ITypeDefinitionModelCollection : IModelCollection
+ {
+ ///
+ /// Gets the type definition model with the specified type name.
+ /// Returns null if no such model object exists.
+ ///
+ ITypeDefinitionModel this[FullTypeName fullTypeName] { get; }
+
+ ///
+ /// Gets the type definition model with the specified type name.
+ /// Returns null if no such model object exists.
+ ///
+ ITypeDefinitionModel this[TopLevelTypeName topLevelTypeName] { get; }
+ }
+
+ public sealed class EmptyTypeDefinitionModelCollection : ITypeDefinitionModelCollection
+ {
+ public static readonly EmptyTypeDefinitionModelCollection Instance = new EmptyTypeDefinitionModelCollection();
+
+ event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged {
+ add { }
+ remove { }
+ }
+
+ ITypeDefinitionModel ITypeDefinitionModelCollection.this[FullTypeName name] {
+ get { return null; }
+ }
+
+ ITypeDefinitionModel ITypeDefinitionModelCollection.this[TopLevelTypeName name] {
+ get { return null; }
+ }
+
+ ITypeDefinitionModel IReadOnlyList.this[int index] {
+ get {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ int IReadOnlyCollection.Count {
+ get { return 0; }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return Enumerable.Empty().GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return Enumerable.Empty().GetEnumerator();
+ }
+ }
+}
diff --git a/src/Main/Base/Project/Dom/KeyedModelCollection.cs b/src/Main/Base/Project/Dom/KeyedModelCollection.cs
new file mode 100644
index 0000000000..6aa1a0e42c
--- /dev/null
+++ b/src/Main/Base/Project/Dom/KeyedModelCollection.cs
@@ -0,0 +1,47 @@
+// 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.Collections.ObjectModel;
+using System.Collections.Specialized;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// Observable KeyedCollection.
+ ///
+ public abstract class KeyedModelCollection : KeyedCollection, IModelCollection
+ {
+ protected override void ClearItems()
+ {
+ base.ClearItems();
+ if (CollectionChanged != null)
+ CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ protected override void InsertItem(int index, TItem item)
+ {
+ base.InsertItem(index, item);
+ if (CollectionChanged != null)
+ CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
+ }
+
+ protected override void RemoveItem(int index)
+ {
+ var oldItem = Items[index];
+ base.RemoveItem(index);
+ if (CollectionChanged != null)
+ CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index));
+ }
+
+ protected override void SetItem(int index, TItem item)
+ {
+ var oldItem = Items[index];
+ base.SetItem(index, item);
+ if (CollectionChanged != null)
+ CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index));
+ }
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+ }
+}
diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
index e7a69097ee..afb123a8ae 100644
--- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
+++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
@@ -95,6 +95,14 @@
Src\Project\MSBuildEngine\ExtendedBinaryReader.cs
+
+
+
+
+
+
+
+
diff --git a/src/Main/Base/Project/Src/Project/AbstractProject.cs b/src/Main/Base/Project/Src/Project/AbstractProject.cs
index 0ead8b0cd9..516169fce7 100644
--- a/src/Main/Base/Project/Src/Project/AbstractProject.cs
+++ b/src/Main/Base/Project/Src/Project/AbstractProject.cs
@@ -11,10 +11,10 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Xml.Linq;
-
using ICSharpCode.Core;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.SharpDevelop.Debugging;
+using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Gui.OptionPanels;
using ICSharpCode.SharpDevelop.Parser;
@@ -671,5 +671,11 @@ namespace ICSharpCode.SharpDevelop.Project
return false;
}
}
+
+ public virtual ICSharpCode.SharpDevelop.Dom.ITypeDefinitionModelCollection TypeDefinitionModels {
+ get {
+ return EmptyTypeDefinitionModelCollection.Instance;
+ }
+ }
}
}
diff --git a/src/Main/Base/Project/Src/Project/IProject.cs b/src/Main/Base/Project/Src/Project/IProject.cs
index 894191c17a..b19dfa3d3c 100644
--- a/src/Main/Base/Project/Src/Project/IProject.cs
+++ b/src/Main/Base/Project/Src/Project/IProject.cs
@@ -9,8 +9,10 @@ using System.ComponentModel;
using System.IO;
using System.Xml;
using System.Xml.Linq;
+
using ICSharpCode.Core;
using ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Parser;
@@ -319,6 +321,12 @@ namespace ICSharpCode.SharpDevelop.Project
/// This method is called by the parser service within a per-file lock.
///
void OnParseInformationUpdated(ParseInformationEventArgs args);
+
+ ///
+ /// Gets the models for the top-level type definitions in this project.
+ /// Never returns null, but may return a permanently empty collection if this project does not support such models.
+ ///
+ ITypeDefinitionModelCollection TypeDefinitionModels { get; }
}
///
diff --git a/src/Main/Base/Test/Dom/CSharpModelTests.cs b/src/Main/Base/Test/Dom/CSharpModelTests.cs
new file mode 100644
index 0000000000..a40290e965
--- /dev/null
+++ b/src/Main/Base/Test/Dom/CSharpModelTests.cs
@@ -0,0 +1,82 @@
+// 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 ICSharpCode.NRefactory.CSharp;
+using ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.SharpDevelop.Parser;
+using ICSharpCode.SharpDevelop.Project;
+using NUnit.Framework;
+using Rhino.Mocks;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ [TestFixture]
+ public class CSharpModelTests
+ {
+ IProject project;
+ IProjectContent projectContent;
+ IEntityModelContext context;
+ TopLevelTypeDefinitionModelCollection topLevelTypeModels;
+
+ #region SetUp and other helper methods
+ [SetUp]
+ public virtual void SetUp()
+ {
+ SD.InitializeForUnitTests();
+ SD.Services.AddStrictMockService();
+ project = MockRepository.GenerateStrictMock();
+ projectContent = new CSharpProjectContent().AddAssemblyReferences(AssemblyLoader.Corlib);
+ context = new ProjectEntityModelContext(project, ".cs");
+ topLevelTypeModels = new TopLevelTypeDefinitionModelCollection(context);
+
+ SD.ParserService.Stub(p => p.GetCompilation(project)).WhenCalled(c => c.ReturnValue = projectContent.CreateCompilation());
+ }
+
+ [TearDown]
+ public virtual void TearDown()
+ {
+ SD.TearDownForUnitTests();
+ }
+
+ protected void AddCodeFile(string fileName, string code)
+ {
+ var oldFile = projectContent.GetFile(fileName);
+ Assert.IsNull(oldFile);
+ var newFile = Parse(fileName, code);
+ projectContent = projectContent.AddOrUpdateFiles(newFile);
+ topLevelTypeModels.NotifyParseInformationChanged(oldFile, newFile);
+ }
+
+ IUnresolvedFile Parse(string fileName, string code)
+ {
+ var parser = new CSharpParser();
+ var syntaxTree = parser.Parse(code, fileName);
+ Assert.IsFalse(parser.HasErrors);
+ return syntaxTree.ToTypeSystem();
+ }
+
+ protected void UpdateCodeFile(string fileName, string code)
+ {
+ var oldFile = projectContent.GetFile(fileName);
+ Assert.IsNotNull(oldFile);
+ var newFile = Parse(fileName, code);
+ projectContent = projectContent.AddOrUpdateFiles(newFile);
+ topLevelTypeModels.NotifyParseInformationChanged(oldFile, newFile);
+ }
+
+ protected void RemoveCodeFile(string fileName)
+ {
+ var oldFile = projectContent.GetFile(fileName);
+ projectContent = projectContent.RemoveFiles(fileName);
+ topLevelTypeModels.NotifyParseInformationChanged(oldFile, null);
+ }
+ #endregion
+
+ [Test]
+ public void EmptyProject()
+ {
+ Assert.AreEqual(0, topLevelTypeModels.Count);
+ }
+ }
+}
diff --git a/src/Main/Base/Test/Dom/ProjectEntityModelContextTests.cs b/src/Main/Base/Test/Dom/ProjectEntityModelContextTests.cs
new file mode 100644
index 0000000000..435e8b48e7
--- /dev/null
+++ b/src/Main/Base/Test/Dom/ProjectEntityModelContextTests.cs
@@ -0,0 +1,42 @@
+// 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 ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.SharpDevelop.Project;
+using NUnit.Framework;
+using Rhino.Mocks;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ [TestFixture]
+ public class ProjectEntityModelContextTests
+ {
+ IUnresolvedTypeDefinition CreateMockTypeDefinition(string fileName)
+ {
+ var typeDefinition = MockRepository.GenerateStrictMock();
+ var file = MockRepository.GenerateStrictMock();
+ file.Stub(f => f.FileName).Return(fileName);
+ typeDefinition.Stub(td => td.UnresolvedFile).Return(file);
+ return typeDefinition;
+ }
+
+ [Test]
+ public void XamlCodeBehindIsBetterThanXaml()
+ {
+ var context = new ProjectEntityModelContext(MockRepository.GenerateStrictMock(), ".cs");
+ Assert.IsTrue(context.IsBetterPart(CreateMockTypeDefinition("Window.xaml.cs"), CreateMockTypeDefinition("Window.xaml")));
+ Assert.IsTrue(context.IsBetterPart(CreateMockTypeDefinition("Window.cs"), CreateMockTypeDefinition("Window.xaml")));
+ Assert.IsFalse(context.IsBetterPart(CreateMockTypeDefinition("Window.xaml"), CreateMockTypeDefinition("Window.xaml.cs")));
+ Assert.IsFalse(context.IsBetterPart(CreateMockTypeDefinition("Window.xaml"), CreateMockTypeDefinition("Window.cs")));
+ }
+
+ [Test]
+ public void MainPartIsBetterThanDesigner()
+ {
+ var context = new ProjectEntityModelContext(MockRepository.GenerateStrictMock(), ".cs");
+ Assert.IsTrue(context.IsBetterPart(CreateMockTypeDefinition("Form.cs"), CreateMockTypeDefinition("Form.Designer.cs")));
+ Assert.IsFalse(context.IsBetterPart(CreateMockTypeDefinition("Form.Designer.cs"), CreateMockTypeDefinition("Form.cs")));
+ }
+ }
+}
diff --git a/src/Main/Base/Test/ICSharpCode.SharpDevelop.Tests.csproj b/src/Main/Base/Test/ICSharpCode.SharpDevelop.Tests.csproj
index 6f926f9d76..c516858e55 100644
--- a/src/Main/Base/Test/ICSharpCode.SharpDevelop.Tests.csproj
+++ b/src/Main/Base/Test/ICSharpCode.SharpDevelop.Tests.csproj
@@ -78,6 +78,8 @@
+
+
@@ -114,6 +116,7 @@
+
@@ -160,6 +163,10 @@
{6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}
ICSharpCode.AvalonEdit
+
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}
+ ICSharpCode.NRefactory.CSharp
+
{3B2A5653-EC97-4001-BB9B-D90F1AF2C371}
ICSharpCode.NRefactory
diff --git a/src/Main/Base/Test/Utils/AssemblyLoader.cs b/src/Main/Base/Test/Utils/AssemblyLoader.cs
new file mode 100644
index 0000000000..e2f528c7cd
--- /dev/null
+++ b/src/Main/Base/Test/Utils/AssemblyLoader.cs
@@ -0,0 +1,13 @@
+// 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 ICSharpCode.NRefactory.TypeSystem;
+
+namespace ICSharpCode.SharpDevelop
+{
+ public static class AssemblyLoader
+ {
+ public static readonly IUnresolvedAssembly Corlib = new CecilLoader { LazyLoad = true }.LoadAssemblyFile(typeof(object).Assembly.Location);
+ }
+}
diff --git a/src/Main/ICSharpCode.SharpDevelop.Widgets/Project/MyersDiff/MyersDiffAlgorithm.cs b/src/Main/ICSharpCode.SharpDevelop.Widgets/Project/MyersDiff/MyersDiffAlgorithm.cs
index f9d50a5b6b..2a47d896d4 100644
--- a/src/Main/ICSharpCode.SharpDevelop.Widgets/Project/MyersDiff/MyersDiffAlgorithm.cs
+++ b/src/Main/ICSharpCode.SharpDevelop.Widgets/Project/MyersDiff/MyersDiffAlgorithm.cs
@@ -59,11 +59,11 @@ namespace ICSharpCode.SharpDevelop.Widgets.MyersDiff
///
/// Example:
///
- /// H E L L O W O R L D
- /// ____
- /// L \___
- /// O \___
- /// W \________
+ /// H E L L O W O R L D
+ /// ____
+ /// L \___
+ /// O \___
+ /// W \________
///
/// Since every D-path has exactly D horizontal or vertical elements, it can
/// only end on the diagonals -D, -D+2, ..., D-2, D.
diff --git a/src/Main/SharpDevelop/Dom/MemberModel.cs b/src/Main/SharpDevelop/Dom/MemberModel.cs
new file mode 100644
index 0000000000..70e3c51e14
--- /dev/null
+++ b/src/Main/SharpDevelop/Dom/MemberModel.cs
@@ -0,0 +1,105 @@
+// 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.ComponentModel;
+using ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.SharpDevelop.Parser;
+using ICSharpCode.SharpDevelop.Project;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// A mutable class that can track a member as the solution is being changed.
+ ///
+ sealed class MemberModel : IMemberModel
+ {
+ ///
+ /// A strong reference to the parent TypeDefinitionModel.Members collection.
+ /// This is necessary to prevent the garbage collector from
+ /// freeing the weak reference to the collection while one of the member models
+ /// is still in use.
+ /// If we don't prevent this, the type definition model might create multiple
+ /// MemberModels for the same member.
+ ///
+ internal IModelCollection strongParentCollectionReference;
+
+ readonly IEntityModelContext context;
+ IUnresolvedMember member;
+
+ public MemberModel(IEntityModelContext context, IUnresolvedMember member)
+ {
+ if (context == null)
+ throw new ArgumentNullException("context");
+ if (member == null)
+ throw new ArgumentNullException("member");
+ this.context = context;
+ this.member = member;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged { add {} remove {} }
+
+ public void Update(IUnresolvedMember newMember)
+ {
+ if (newMember == null)
+ throw new ArgumentNullException("newMember");
+ this.member = newMember;
+ }
+
+ public IProject ParentProject {
+ get { return context.Project; }
+ }
+
+ public IUnresolvedMember UnresolvedMember {
+ get { return member; }
+ }
+
+ public EntityType EntityType {
+ get { return member.EntityType; }
+ }
+
+ public DomRegion Region {
+ get { return member.Region; }
+ }
+
+ public string Name {
+ get { return member.Name; }
+ }
+
+ ///
+ /// Gets the full type name of the type that declares this member.
+ ///
+ public FullTypeName DeclaringTypeName {
+ get {
+ if (member.DeclaringTypeDefinition != null)
+ return member.DeclaringTypeDefinition.FullTypeName;
+ else
+ return new TopLevelTypeName(string.Empty, string.Empty);
+ }
+ }
+
+ #region Resolve
+ public IMember Resolve()
+ {
+ var compilation = context.GetCompilation(null);
+ return member.Resolve(new SimpleTypeResolveContext(compilation.MainAssembly));
+ }
+
+ public IMember Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot)
+ {
+ var compilation = context.GetCompilation(solutionSnapshot);
+ return member.Resolve(new SimpleTypeResolveContext(compilation.MainAssembly));
+ }
+
+ IEntity IEntityModel.Resolve()
+ {
+ return Resolve();
+ }
+
+ IEntity IEntityModel.Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot)
+ {
+ return Resolve(solutionSnapshot);
+ }
+ #endregion
+ }
+}
diff --git a/src/Main/SharpDevelop/Dom/TopLevelTypeDefinitionModelCollection.cs b/src/Main/SharpDevelop/Dom/TopLevelTypeDefinitionModelCollection.cs
new file mode 100644
index 0000000000..06598219cf
--- /dev/null
+++ b/src/Main/SharpDevelop/Dom/TopLevelTypeDefinitionModelCollection.cs
@@ -0,0 +1,52 @@
+// 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.Collections;
+using System.Collections.ObjectModel;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using ICSharpCode.NRefactory.TypeSystem;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// A TypeDefinitionModel-collection that holds models for all top-level types in a project content.
+ ///
+ sealed class TopLevelTypeDefinitionModelCollection : KeyedModelCollection
+ {
+ readonly IEntityModelContext context;
+
+ public TopLevelTypeDefinitionModelCollection(IEntityModelContext context)
+ {
+ if (context == null)
+ throw new ArgumentNullException("context");
+ this.context = context;
+ }
+
+ public TypeDefinitionModel this[FullTypeName fullTypeName] {
+ get {
+ TypeDefinitionModel model = base[fullTypeName.TopLevelTypeName];
+ for (int i = 0; i < fullTypeName.NestingLevel; i++) {
+ throw new NotImplementedException();
+ }
+ return model;
+ }
+ }
+
+ ///
+ /// Updates the parse information.
+ ///
+ public void NotifyParseInformationChanged(IUnresolvedFile oldFile, IUnresolvedFile newFile)
+ {
+ if (oldFile != null) {
+
+ }
+ }
+
+ protected override TopLevelTypeName GetKeyForItem(TypeDefinitionModel item)
+ {
+ return item.FullTypeName.TopLevelTypeName;
+ }
+ }
+}
diff --git a/src/Main/SharpDevelop/Dom/TypeDefinitionModel.cs b/src/Main/SharpDevelop/Dom/TypeDefinitionModel.cs
new file mode 100644
index 0000000000..b4ac2e66a5
--- /dev/null
+++ b/src/Main/SharpDevelop/Dom/TypeDefinitionModel.cs
@@ -0,0 +1,264 @@
+// 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.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+
+using ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.SharpDevelop.Parser;
+using ICSharpCode.SharpDevelop.Project;
+
+namespace ICSharpCode.SharpDevelop.Dom
+{
+ ///
+ /// A mutable class that can track a type definition as the solution is being changed.
+ ///
+ sealed class TypeDefinitionModel : ITypeDefinitionModel
+ {
+ readonly IEntityModelContext context;
+ readonly FullTypeName fullTypeName;
+ List parts;
+
+ public TypeDefinitionModel(IEntityModelContext context, params IUnresolvedTypeDefinition[] parts)
+ {
+ if (context == null)
+ throw new ArgumentNullException("context");
+ if (parts.Length == 0)
+ throw new ArgumentException("Number of parts must not be zero");
+ this.context = context;
+ this.parts = new List(parts);
+ MovePrimaryPartToFront();
+ this.fullTypeName = parts[0].FullTypeName;
+ }
+
+ void MovePrimaryPartToFront()
+ {
+ int bestPartIndex = 0;
+ for (int i = 1; i < parts.Count; i++) {
+ if (context.IsBetterPart(parts[i], parts[bestPartIndex]))
+ bestPartIndex = i;
+ }
+ IUnresolvedTypeDefinition primaryPart = parts[bestPartIndex];
+ for (int i = bestPartIndex; i > 0; i--) {
+ parts[i] = parts[i - 1];
+ }
+ parts[0] = primaryPart;
+ }
+
+ /*
+ ///
+ /// Updates the type definition model by removing the old parts and adding the new ones.
+ ///
+ public void Update(IReadOnlyList removedParts, IReadOnlyList newParts)
+ {
+ SD.MainThread.VerifyAccess();
+ if (removedParts != null)
+ foreach (var p in removedParts)
+ parts.Remove(p);
+ if (newParts != null)
+ parts.AddRange(newParts);
+
+ MemberModelCollection members;
+ if (membersWeakReference.TryGetTarget(out members)) {
+ members.Update(parts);
+ }
+ }*/
+
+ public IProject ParentProject {
+ get { return context.Project; }
+ }
+
+ public FullTypeName FullTypeName {
+ get { return fullTypeName; }
+ }
+
+ public DomRegion Region {
+ get { return parts[0].Region; }
+ }
+
+ public string Name {
+ get { return fullTypeName.Name; }
+ }
+
+ #region Resolve
+ public ITypeDefinition Resolve()
+ {
+ var compilation = context.GetCompilation(null);
+ return compilation.MainAssembly.GetTypeDefinition(fullTypeName);
+ }
+
+ public ITypeDefinition Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot)
+ {
+ var compilation = context.GetCompilation(solutionSnapshot);
+ return compilation.MainAssembly.GetTypeDefinition(fullTypeName);
+ }
+
+ IEntity IEntityModel.Resolve()
+ {
+ return Resolve();
+ }
+
+ IEntity IEntityModel.Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot)
+ {
+ return Resolve(solutionSnapshot);
+ }
+ #endregion
+
+ public event PropertyChangedEventHandler PropertyChanged { add {} remove {} }
+
+ #region Members collection
+ sealed class MemberCollection : IModelCollection
+ {
+ readonly TypeDefinitionModel parent;
+ List> lists = new List>();
+
+ public MemberCollection(TypeDefinitionModel parent)
+ {
+ this.parent = parent;
+ }
+
+ public void Insert(int partIndex, IUnresolvedTypeDefinition newPart)
+ {
+ List newItems = new List(newPart.Members.Count);
+ foreach (var newMember in newPart.Members) {
+ newItems.Add(new MemberModel(parent.context, newMember) { strongParentCollectionReference = this });
+ }
+ lists.Insert(partIndex, newItems);
+ if (collectionChanged != null)
+ collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, GetCount(partIndex)));
+ }
+
+ public void Remove(int partIndex)
+ {
+ var oldItems = lists[partIndex];
+ lists.RemoveAt(partIndex);
+ if (collectionChanged != null)
+ collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, GetCount(partIndex)));
+ }
+
+ public void Update(int partIndex, IUnresolvedTypeDefinition newPart)
+ {
+ List list = lists[partIndex];
+ var newMembers = newPart.Members;
+ int startPos = 0;
+ // Look at the initial members and update them if they're matching
+ while (startPos < list.Count && startPos < newMembers.Count && IsMatch(list[startPos], newMembers[startPos])) {
+ list[startPos].Update(newMembers[startPos]);
+ startPos++;
+ }
+ // Look at the final members
+ int endPosOld = list.Count - 1;
+ int endPosNew = newMembers.Count - 1;
+ while (endPosOld >= startPos && endPosNew >= startPos && IsMatch(list[endPosOld], newMembers[endPosNew])) {
+ list[endPosOld--].Update(newMembers[endPosNew--]);
+ }
+ // [startPos, endPos] is the middle portion that contains all the changes
+ // Add one to endPos so that it's the exclusive end of the middle portion:
+ endPosOld++;
+ endPosNew++;
+ // [startPos, endPos)
+
+ // Now we still need to update the members in between.
+ // We might try to be clever here and find a LCS so that we only update the members that were actually changed,
+ // or we might consider moving members around (INotifyCollectionChanged supports moves)
+ // However, the easiest solution by far is to just remove + readd the whole middle portion.
+ var oldItems = collectionChanged != null ? list.GetRange(startPos, endPosOld - startPos) : null;
+ list.RemoveRange(startPos, endPosOld - startPos);
+ var newItems = new MemberModel[endPosNew - startPos];
+ for (int i = 0; i < newItems.Length; i++) {
+ newItems[i] = new MemberModel(parent.context, newMembers[startPos + i]);
+ newItems[i].strongParentCollectionReference = this;
+ }
+ list.InsertRange(startPos, newItems);
+ if (collectionChanged != null)
+ collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, GetCount(partIndex) + startPos));
+ }
+
+ static bool IsMatch(MemberModel memberModel, IUnresolvedMember newMember)
+ {
+ return memberModel.EntityType == newMember.EntityType && memberModel.Name == newMember.Name;
+ }
+
+ NotifyCollectionChangedEventHandler collectionChanged;
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged {
+ add {
+ collectionChanged += value;
+ // Set strong reference to collection while there are event listeners
+ if (collectionChanged != null)
+ parent.membersStrongReference = this;
+ }
+ remove {
+ collectionChanged -= value;
+ if (collectionChanged == null)
+ parent.membersStrongReference = null;
+ }
+ }
+
+ int GetCount(int partIndex)
+ {
+ int count = 0;
+ for (int i = 0; i < partIndex; i++) {
+ count += lists[i].Count;
+ }
+ return count;
+ }
+
+ public int Count {
+ get { return GetCount(lists.Count); }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return lists.SelectMany(i => i).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public MemberModel this[int index] {
+ get {
+ int inputIndex = 0;
+ while (index >= lists[inputIndex].Count) {
+ index -= lists[inputIndex].Count;
+ inputIndex++;
+ }
+ return lists[inputIndex][index];
+ }
+ }
+ }
+
+ WeakReference membersWeakReference = new WeakReference(null);
+
+ ///
+ /// Used to keep the member model collection alive while there are event listeners.
+ /// This is necessary to prevent us from creating multiple member models for the same member.
+ ///
+ MemberCollection membersStrongReference;
+
+ public IModelCollection Members {
+ get {
+ SD.MainThread.VerifyAccess();
+ MemberCollection members;
+ if (!membersWeakReference.TryGetTarget(out members)) {
+ members = new MemberCollection(this);
+ membersWeakReference.SetTarget(members);
+ }
+ return members;
+ }
+ }
+ #endregion
+
+ public IModelCollection NestedTypes {
+ get {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/src/Main/SharpDevelop/SharpDevelop.csproj b/src/Main/SharpDevelop/SharpDevelop.csproj
index 683b7ca4a0..8bcc91b28c 100644
--- a/src/Main/SharpDevelop/SharpDevelop.csproj
+++ b/src/Main/SharpDevelop/SharpDevelop.csproj
@@ -81,7 +81,10 @@
+
+
+
LoadSaveOptions.xaml
Code