From 918b47ca704ac031658224797c1e5f6c5d47425d Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Tue, 12 Mar 2013 18:10:15 +0100 Subject: [PATCH] Fix various issues with the new solution model. --- .../Project/Src/Project/CSharpProject.cs | 3 +- .../Project/PackageManagement.csproj | 1 - .../InstallProjectTemplatePackagesCommand.cs | 6 +- .../Project/Src/NewProjectsCreated.cs | 50 ------- .../Test/PackageManagement.Tests.csproj | 1 - ...tallProjectTemplatePackagesCommandTests.cs | 38 +---- .../Test/Src/NewProjectsCreatedTests.cs | 102 ------------- src/Main/Base/Project/Dom/IModelCollection.cs | 8 + .../Project/Dom/ReadOnlyModelCollection.cs | 2 +- .../Base/Project/Dom/SimpleModelCollection.cs | 13 ++ .../Dom/SynchronizedModelCollection.cs | 140 ++++++++++++++++++ .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../Configuration/ConfigurationMapping.cs | 9 +- .../Base/Project/Project/IProjectService.cs | 18 ++- src/Main/Base/Project/Project/ISolution.cs | 5 + .../Project/Project/ProjectInformation.cs | 28 +++- .../Base/Project/Project/SolutionSection.cs | 75 ++++++---- .../Src/Gui/Dialogs/NewProjectDialog.cs | 25 ++-- .../Base/Project/Src/Gui/Pads/FileScout.cs | 2 +- .../ProjectBrowser/ProjectBrowserControl.cs | 2 +- .../Templates/Project/ProjectDescriptor.cs | 1 + .../Templates/Project/ProjectTemplate.cs | 7 + .../Project/Src/Project/AbstractProject.cs | 2 +- .../Src/Project/MSBuildBasedProject.cs | 10 +- .../Project/Src/Project/MSBuildInternals.cs | 4 +- .../Services/ProjectService/ProjectService.cs | 6 +- .../Base/Project/Util/IProgressMonitor.cs | 4 + .../Base/Project/Util/ReactiveExtensions.cs | 140 +++++++++++++++++- ...onConfigurationOrPlatformNameCollection.cs | 76 ++++++---- .../SharpDevelop/Project/ProjectService.cs | 43 ++++-- src/Main/SharpDevelop/Project/Solution.cs | 23 ++- .../SharpDevelop/Project/SolutionFolder.cs | 18 ++- .../Workbench/WorkbenchStartup.cs | 2 +- .../SharpDevelop/Workbench/WpfWorkbench.cs | 2 +- 34 files changed, 570 insertions(+), 297 deletions(-) delete mode 100644 src/AddIns/Misc/PackageManagement/Project/Src/NewProjectsCreated.cs delete mode 100644 src/AddIns/Misc/PackageManagement/Test/Src/NewProjectsCreatedTests.cs create mode 100644 src/Main/Base/Project/Dom/SynchronizedModelCollection.cs diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Project/CSharpProject.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Project/CSharpProject.cs index aeae35a99e..6897e02629 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Project/CSharpProject.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Project/CSharpProject.cs @@ -57,7 +57,8 @@ namespace CSharpBinding : base(loadInformation) { Init(); - InitializeProjectContent(new CSharpProjectContent()); + if (loadInformation.InitializeTypeSystem) + InitializeProjectContent(new CSharpProjectContent()); } public const string DefaultTargetsFile = @"$(MSBuildToolsPath)\Microsoft.CSharp.targets"; diff --git a/src/AddIns/Misc/PackageManagement/Project/PackageManagement.csproj b/src/AddIns/Misc/PackageManagement/Project/PackageManagement.csproj index 22c53e32b2..e05cb5d3bd 100644 --- a/src/AddIns/Misc/PackageManagement/Project/PackageManagement.csproj +++ b/src/AddIns/Misc/PackageManagement/Project/PackageManagement.csproj @@ -226,7 +226,6 @@ - diff --git a/src/AddIns/Misc/PackageManagement/Project/Src/InstallProjectTemplatePackagesCommand.cs b/src/AddIns/Misc/PackageManagement/Project/Src/InstallProjectTemplatePackagesCommand.cs index ff8c7ce3ff..849e57519a 100644 --- a/src/AddIns/Misc/PackageManagement/Project/Src/InstallProjectTemplatePackagesCommand.cs +++ b/src/AddIns/Misc/PackageManagement/Project/Src/InstallProjectTemplatePackagesCommand.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using ICSharpCode.Core; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Internal.Templates; @@ -64,9 +65,8 @@ namespace ICSharpCode.PackageManagement IEnumerable GetCreatedProjects() { - var createInfo = Owner as ProjectCreateInformation; - var newCreatedProjects = new NewProjectsCreated(createInfo, projectService); - return newCreatedProjects.GetProjects(); + var createInfo = Owner as ProjectCreateOptions; + return createInfo != null ? createInfo.CreatedProjects.OfType() : Enumerable.Empty(); } IPackageReferencesForProject CreatePackageReferencesForProject(MSBuildBasedProject project) diff --git a/src/AddIns/Misc/PackageManagement/Project/Src/NewProjectsCreated.cs b/src/AddIns/Misc/PackageManagement/Project/Src/NewProjectsCreated.cs deleted file mode 100644 index 9c52d984b6..0000000000 --- a/src/AddIns/Misc/PackageManagement/Project/Src/NewProjectsCreated.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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.Generic; -using ICSharpCode.SharpDevelop.Internal.Templates; -using ICSharpCode.SharpDevelop.Project; - -namespace ICSharpCode.PackageManagement -{ - public class NewProjectsCreated - { - ProjectCreateInformation createInfo; - OpenMSBuildProjects openProjects; - - public NewProjectsCreated( - ProjectCreateInformation createInfo, - IPackageManagementProjectService projectService) - : this(createInfo, new OpenMSBuildProjects(projectService)) - { - } - - public NewProjectsCreated( - ProjectCreateInformation createInfo, - OpenMSBuildProjects openProjects) - { - this.createInfo = createInfo; - this.openProjects = openProjects; - } - - public IEnumerable GetProjects() - { - // ProjectCreateInformation is no longer used to collect the results of the whole solution creation, - // there now is a separate instance per project. - #warning Reimplement PackageManagement.NewProjectsCreated - throw new NotImplementedException(); - /*foreach (IProject project in createInfo.CreatedProjects) { - MSBuildBasedProject openProject = FindProject(project.Name); - if (openProject != null) { - yield return openProject; - } - }*/ - } - - MSBuildBasedProject FindProject(string name) - { - return openProjects.FindProject(name); - } - } -} diff --git a/src/AddIns/Misc/PackageManagement/Test/PackageManagement.Tests.csproj b/src/AddIns/Misc/PackageManagement/Test/PackageManagement.Tests.csproj index 6c7af232bd..32855883ff 100644 --- a/src/AddIns/Misc/PackageManagement/Test/PackageManagement.Tests.csproj +++ b/src/AddIns/Misc/PackageManagement/Test/PackageManagement.Tests.csproj @@ -195,7 +195,6 @@ - diff --git a/src/AddIns/Misc/PackageManagement/Test/Src/InstallProjectTemplatePackagesCommandTests.cs b/src/AddIns/Misc/PackageManagement/Test/Src/InstallProjectTemplatePackagesCommandTests.cs index 542789e700..dc6224ff19 100644 --- a/src/AddIns/Misc/PackageManagement/Test/Src/InstallProjectTemplatePackagesCommandTests.cs +++ b/src/AddIns/Misc/PackageManagement/Test/Src/InstallProjectTemplatePackagesCommandTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Windows.Input; using ICSharpCode.PackageManagement; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Dom; @@ -37,20 +38,18 @@ namespace PackageManagement.Tests void RunCommandWithProjectCreateInfoAsOwner(List projects) { - throw new NotImplementedException(); - /*var createInfo = new ProjectCreateInformation(projects); - createInfo.Solution = projects[0].ParentSolution; + var createInfo = new ProjectCreateOptions(); + createInfo.CreatedProjects.AddRange(projects); - command.FakeProjectService.AllProjects.Inputs.Add(new ReadOnlyModelCollection(projects)); + command.FakeProjectService.ProjectCollections.Add(new ReadOnlyModelCollection(projects)); - RunCommandWithProjectCreateInfoAsOwner(createInfo);*/ + RunCommandWithProjectCreateInfoAsOwner(createInfo); } - void RunCommandWithProjectCreateInfoAsOwner(ProjectCreateInformation createInfo) + void RunCommandWithProjectCreateInfoAsOwner(ProjectCreateOptions createInfo) { - command.Owner = createInfo; - command.Run(); - } + ((ICommand)command).Execute(createInfo); + } void RunCommandWithExceptionThrowingPackageReferences(Exception exception) { @@ -73,27 +72,6 @@ namespace PackageManagement.Tests Assert.AreEqual(expectedProject, project); } - [Test] - public void Run_OneProjectCreatedByNewProjectDialog_ProjectUsedToCreatePackageReferencesIsTakenFromOpenProjectsNotCreateInfo() - { - CreateCommand(); - TestableProject createInfoProject = CreateFakeProject("Test"); - var projects = new List(); - projects.Add(createInfoProject); - throw new NotImplementedException(); - - /*var createInfo = new ProjectCreateInformation(projects); - - TestableProject expectedProject = ProjectHelper.CreateTestProject("TEST"); - command.FakeProjectService.AddProject(expectedProject); - - RunCommandWithProjectCreateInfoAsOwner(createInfo); - - MSBuildBasedProject project = command.ProjectPassedToCreatePackageReferencesForProject; - - Assert.AreEqual(expectedProject, project);*/ - } - [Test] public void Run_TwoProjectsCreatedByNewProjectDialog_TwoProjectsUsedToCreatePackageReferences() { diff --git a/src/AddIns/Misc/PackageManagement/Test/Src/NewProjectsCreatedTests.cs b/src/AddIns/Misc/PackageManagement/Test/Src/NewProjectsCreatedTests.cs deleted file mode 100644 index b1086f7382..0000000000 --- a/src/AddIns/Misc/PackageManagement/Test/Src/NewProjectsCreatedTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -// 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.Generic; -using ICSharpCode.PackageManagement; -using ICSharpCode.PackageManagement.Design; -using ICSharpCode.SharpDevelop.Internal.Templates; -using ICSharpCode.SharpDevelop.Project; -using NUnit.Framework; -using PackageManagement.Tests.Helpers; - -namespace PackageManagement.Tests -{ - [TestFixture, IgnoreAttribute("NewProjectsCreated currently not implemented")] - public class NewProjectsCreatedTests - { - FakePackageManagementProjectService fakeProjectService; - NewProjectsCreated newProjectsCreated; - - TestableProject CreateProject(string name) - { - return ProjectHelper.CreateTestProject(name); - } - - ProjectCreateInformation CreateProjectCreateInfo(MSBuildBasedProject project) - { - var projects = new List(); - projects.Add(project); - return CreateProjectCreateInfo(projects); - } - - ProjectCreateInformation CreateProjectCreateInfo(IEnumerable projects) - { - throw new NotImplementedException(); - //return new ProjectCreateInformation(projects); - } - - void CreateNewProjectsCreated(ProjectCreateInformation createInfo) - { - fakeProjectService = new FakePackageManagementProjectService(); - newProjectsCreated = new NewProjectsCreated(createInfo, fakeProjectService); - } - - TestableProject AddProjectToProjectServiceOpenProjects(string projectName) - { - TestableProject project = CreateProject(projectName); - fakeProjectService.AddProject(project); - return project; - } - - [Test] - public void GetProjects_OneProjectCreatedAndOneOldProjectInSolution_ReturnsProjectFromProjectService() - { - TestableProject project = CreateProject("TestProject"); - ProjectCreateInformation createInfo = CreateProjectCreateInfo(project); - CreateNewProjectsCreated(createInfo); - AddProjectToProjectServiceOpenProjects("OriginalProject"); - TestableProject expectedProject = AddProjectToProjectServiceOpenProjects("TestProject"); - - var projects = new List(); - projects.AddRange(newProjectsCreated.GetProjects()); - - var expectedProjects = new MSBuildBasedProject[] { - expectedProject - }; - - Assert.AreEqual(expectedProjects, projects); - } - - [Test] - public void GetProjects_OneProjectCreatedWithDifferentNameCase_ReturnsProjectFromProjectService() - { - TestableProject project = CreateProject("TESTPROJECT"); - ProjectCreateInformation createInfo = CreateProjectCreateInfo(project); - CreateNewProjectsCreated(createInfo); - TestableProject expectedProject = AddProjectToProjectServiceOpenProjects("TestProject"); - - var projects = new List(); - projects.AddRange(newProjectsCreated.GetProjects()); - - var expectedProjects = new MSBuildBasedProject[] { - expectedProject - }; - - Assert.AreEqual(expectedProjects, projects); - } - - [Test] - public void GetProjects_OneProjectCreatedButProjectIsNotOpen_ReturnsNoProjects() - { - TestableProject project = CreateProject("TestProject"); - ProjectCreateInformation createInfo = CreateProjectCreateInfo(project); - CreateNewProjectsCreated(createInfo); - - var projects = new List(); - projects.AddRange(newProjectsCreated.GetProjects()); - - Assert.AreEqual(0, projects.Count); - } - } -} diff --git a/src/Main/Base/Project/Dom/IModelCollection.cs b/src/Main/Base/Project/Dom/IModelCollection.cs index 836ee4cd37..cdc553b5c1 100644 --- a/src/Main/Base/Project/Dom/IModelCollection.cs +++ b/src/Main/Base/Project/Dom/IModelCollection.cs @@ -36,6 +36,14 @@ namespace ICSharpCode.SharpDevelop.Dom /// public interface IMutableModelCollection : IModelCollection, ICollection { + /// + /// Gets the number of items in the collection. + /// + /// + /// Disambiguates between IReadOnlyCollection.Count and ICollection.Count + /// + new int Count { get; } + /// /// Adds the specified items to the collection. /// diff --git a/src/Main/Base/Project/Dom/ReadOnlyModelCollection.cs b/src/Main/Base/Project/Dom/ReadOnlyModelCollection.cs index 0e38e2d998..fb6f9301ab 100644 --- a/src/Main/Base/Project/Dom/ReadOnlyModelCollection.cs +++ b/src/Main/Base/Project/Dom/ReadOnlyModelCollection.cs @@ -1,5 +1,5 @@ // 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; +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/src/Main/Base/Project/Dom/SimpleModelCollection.cs b/src/Main/Base/Project/Dom/SimpleModelCollection.cs index 154dec956c..1366c94614 100644 --- a/src/Main/Base/Project/Dom/SimpleModelCollection.cs +++ b/src/Main/Base/Project/Dom/SimpleModelCollection.cs @@ -202,5 +202,18 @@ namespace ICSharpCode.SharpDevelop.Dom #endregion } + + /// + /// A model collection implementation that disallows null values. + /// + public class NullSafeSimpleModelCollection : SimpleModelCollection where T : class + { + protected override void ValidateItem(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + base.ValidateItem(item); + } + } } diff --git a/src/Main/Base/Project/Dom/SynchronizedModelCollection.cs b/src/Main/Base/Project/Dom/SynchronizedModelCollection.cs new file mode 100644 index 0000000000..7436ce5a75 --- /dev/null +++ b/src/Main/Base/Project/Dom/SynchronizedModelCollection.cs @@ -0,0 +1,140 @@ +// 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.Generic; +using System.Threading; +using ICSharpCode.NRefactory.Utils; + +namespace ICSharpCode.SharpDevelop.Dom +{ + /// + /// Synchronizing wrapper around IMutableModelCollection. + /// + public class SynchronizedModelCollection : IMutableModelCollection + { + readonly IMutableModelCollection underlyingCollection; + readonly object syncRoot; + + public SynchronizedModelCollection(IMutableModelCollection underlyingCollection) + : this(underlyingCollection, new object()) + { + } + + public SynchronizedModelCollection(IMutableModelCollection underlyingCollection, object syncRoot) + { + if (underlyingCollection == null) + throw new ArgumentNullException("underlyingCollection"); + if (syncRoot == null) + throw new ArgumentNullException("syncRoot"); + this.underlyingCollection = underlyingCollection; + this.syncRoot = syncRoot; + } + + // Event registration is thread-safe on the underlying collection + public event ModelCollectionChangedEventHandler CollectionChanged { + add { underlyingCollection.CollectionChanged += value; } + remove { underlyingCollection.CollectionChanged -= value; } + } + + #region IMutableModelCollection implementation + + public void Clear() + { + lock (syncRoot) { + underlyingCollection.Clear(); + } + } + + public void Add(T item) + { + lock (syncRoot) { + underlyingCollection.Add(item); + } + } + + public void AddRange(IEnumerable items) + { + lock (syncRoot) { + underlyingCollection.AddRange(items); + } + } + + public bool Remove(T item) + { + lock (syncRoot) { + return underlyingCollection.Remove(item); + } + } + + public int RemoveAll(Predicate predicate) + { + lock (syncRoot) { + return underlyingCollection.RemoveAll(predicate); + } + } + + public IDisposable BatchUpdate() + { + Monitor.Enter(syncRoot); + IDisposable disposable = underlyingCollection.BatchUpdate(); + return new CallbackOnDispose( + delegate { + if (disposable != null) + disposable.Dispose(); + Monitor.Exit(syncRoot); + }); + } + + #endregion + + #region ICollection implementation + + public bool Contains(T item) + { + lock (syncRoot) { + return underlyingCollection.Contains(item); + } + } + + public void CopyTo(T[] array, int arrayIndex) + { + lock (syncRoot) { + underlyingCollection.CopyTo(array, arrayIndex); + } + } + + public int Count { + get { + lock (syncRoot) { + return underlyingCollection.Count; + } + } + } + + public bool IsReadOnly { + get { + lock (syncRoot) { + return underlyingCollection.IsReadOnly; + } + } + } + + public IEnumerator GetEnumerator() + { + IEnumerable snapshot; + lock (syncRoot) { + T[] array = new T[underlyingCollection.Count]; + underlyingCollection.CopyTo(array, 0); + snapshot = array; + } + return snapshot.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + } +} diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 78e6ad2054..59aee7e054 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -95,6 +95,7 @@ + diff --git a/src/Main/Base/Project/Project/Configuration/ConfigurationMapping.cs b/src/Main/Base/Project/Project/Configuration/ConfigurationMapping.cs index c1cfd3122b..9c71c66a37 100644 --- a/src/Main/Base/Project/Project/Configuration/ConfigurationMapping.cs +++ b/src/Main/Base/Project/Project/Configuration/ConfigurationMapping.cs @@ -29,7 +29,7 @@ namespace ICSharpCode.SharpDevelop.Project /// /// The configuration mapping was changed. /// - public event EventHandler Changed; + public event EventHandler Changed = delegate { }; Dictionary dict = new Dictionary(); @@ -66,10 +66,13 @@ namespace ICSharpCode.SharpDevelop.Project { if (string.IsNullOrEmpty(projectConfiguration.Configuration)) throw new ArgumentException("Invalid project configuration"); - if (string.IsNullOrEmpty(projectConfiguration.Platform) || projectConfiguration.Platform == "Any CPU") + if (string.IsNullOrEmpty(projectConfiguration.Platform)) throw new ArgumentException("Invalid project platform"); lock (dict) { - GetOrCreateEntry(solutionConfiguration).Config = projectConfiguration; + GetOrCreateEntry(solutionConfiguration).Config = new ConfigurationAndPlatform( + projectConfiguration.Configuration, + MSBuildInternals.FixPlatformNameForProject(projectConfiguration.Platform) + ); } Changed(this, EventArgs.Empty); } diff --git a/src/Main/Base/Project/Project/IProjectService.cs b/src/Main/Base/Project/Project/IProjectService.cs index a8dd2f9355..e487022391 100644 --- a/src/Main/Base/Project/Project/IProjectService.cs +++ b/src/Main/Base/Project/Project/IProjectService.cs @@ -28,7 +28,7 @@ namespace ICSharpCode.SharpDevelop.Project /// /// This event is raised after a solution is opened. /// - event EventHandler SolutionLoaded; + event EventHandler SolutionOpened; /// /// This event is raised before a solution is closed. @@ -76,7 +76,17 @@ namespace ICSharpCode.SharpDevelop.Project /// This method may only be called on the main thread. /// If any errors occur, this method may display an error dialog. /// - void OpenSolutionOrProject(FileName fileName); + bool OpenSolutionOrProject(FileName fileName); + + /// + /// Opens a solution in the IDE. + /// + bool OpenSolution(FileName fileName); + + /// + /// Opens a solution in the IDE that was created/loaded earlier. + /// + bool OpenSolution(ISolution solution); /// /// Closes the solution: cancels build, clears solution data, fires the SolutionClosing and SolutionClosed events. @@ -91,13 +101,13 @@ namespace ICSharpCode.SharpDevelop.Project bool CloseSolution(bool allowCancel = true); /// - /// Returns if the given file is considered a project or solution file. + /// Returns if the given file is considered a solution or project file. /// This method looks at the list of registered file extensions in /SharpDevelop/Workbench/ProjectBinding. /// /// /// This method is thread-safe. /// - bool IsProjectOrSolutionFile(FileName fileName); + bool IsSolutionOrProjectFile(FileName fileName); /// /// Loads a solution file without opening it in the IDE. diff --git a/src/Main/Base/Project/Project/ISolution.cs b/src/Main/Base/Project/Project/ISolution.cs index 1c772ca6ad..9167c6a7d4 100644 --- a/src/Main/Base/Project/Project/ISolution.cs +++ b/src/Main/Base/Project/Project/ISolution.cs @@ -19,6 +19,11 @@ namespace ICSharpCode.SharpDevelop.Project /// /// Represents a solution. /// + /// + /// This interface is thread-safe for read-access. Background threads may read from ISolution + /// even while the main thread is modifying the solution. + /// However, only the main thread is allowed to modify the solution. + /// public interface ISolution : ISolutionFolder, ICanBeDirty, IConfigurable, IDisposable { Microsoft.Build.Evaluation.ProjectCollection MSBuildProjectCollection { get; } diff --git a/src/Main/Base/Project/Project/ProjectInformation.cs b/src/Main/Base/Project/Project/ProjectInformation.cs index cfaaeb9329..9dd4cc6dc0 100644 --- a/src/Main/Base/Project/Project/ProjectInformation.cs +++ b/src/Main/Base/Project/Project/ProjectInformation.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using ICSharpCode.Core; namespace ICSharpCode.SharpDevelop.Project @@ -24,23 +25,47 @@ namespace ICSharpCode.SharpDevelop.Project this.ProjectSections = new List(); this.ConfigurationMapping = new ConfigurationMapping(); var solutionConfig = solution.ActiveConfiguration; - // In unit tests, ActiveConfiguration mayb return null + // In unit tests, ActiveConfiguration maybe return null if (solutionConfig.Configuration != null && solutionConfig.Platform != null) this.ActiveProjectConfiguration = this.ConfigurationMapping.GetProjectConfiguration(solution.ActiveConfiguration); else this.ActiveProjectConfiguration = new ConfigurationAndPlatform("Debug", "AnyCPU"); + this.InitializeTypeSystem = true; } public ISolution Solution { get; private set; } public FileName FileName { get; private set; } + + /// + /// Specifies the mapping between solution configurations and project configurations. + /// public ConfigurationMapping ConfigurationMapping { get; set; } + + /// + /// Specified the project configuration that should be initially active when the project is loaded. + /// public ConfigurationAndPlatform ActiveProjectConfiguration { get; set; } + public IList ProjectSections { get; private set; } public string ProjectName { get; set; } + /// + /// Specified the ID GUID for the project. + /// If this property isn't set (stays at Guid.Empty), the project should generate a new GUID. (see AbstractProject ctor) + /// public Guid IdGuid { get; set; } + + /// + /// Specifies the type GUID for the project. + /// Necessary for both project creation and loading. + /// public Guid TypeGuid { get; set; } + /// + /// Specifies whether to initialize the type system for the project. + /// The default is true. + /// + public bool InitializeTypeSystem { get; set; } } /// @@ -91,6 +116,5 @@ namespace ICSharpCode.SharpDevelop.Project public string RootNamespace { get; set; } public TargetFramework TargetFramework { get; set; } - public bool InitializeTypeSystem { get; set; } } } diff --git a/src/Main/Base/Project/Project/SolutionSection.cs b/src/Main/Base/Project/Project/SolutionSection.cs index 4dcb4a305d..56d020737f 100644 --- a/src/Main/Base/Project/Project/SolutionSection.cs +++ b/src/Main/Base/Project/Project/SolutionSection.cs @@ -74,47 +74,56 @@ namespace ICSharpCode.SharpDevelop.Project } public int Count { - get { return entries.Count; } + get { + lock (entries) + return entries.Count; + } } public void Add(string key, string value) { Validate(key, value); - entries.Add(new KeyValuePair(key, value)); + lock (entries) + entries.Add(new KeyValuePair(key, value)); Changed(this, EventArgs.Empty); } public bool Remove(string key) { - if (entries.RemoveAll(e => e.Key == key) > 0) { + bool result; + lock (entries) + result = entries.RemoveAll(e => e.Key == key) > 0; + if (result) Changed(this, EventArgs.Empty); - return true; - } else { - return false; - } + return result; } public void Clear() { - entries.Clear(); + lock (entries) + entries.Clear(); Changed(this, EventArgs.Empty); } public bool ContainsKey(string key) { - for (int i = 0; i < entries.Count; i++) { - if (entries[i].Key == key) - return true; + lock (entries) { + for (int i = 0; i < entries.Count; i++) { + if (entries[i].Key == key) + return true; + } } return false; } public bool TryGetValue(string key, out string value) { - for (int i = 0; i < entries.Count; i++) { - if (entries[i].Key == key) { - value = entries[i].Value; - return true; + lock (entries) { + for (int i = 0; i < entries.Count; i++) { + if (entries[i].Key == key) { + value = entries[i].Value; + return true; + } } } value = null; @@ -123,46 +132,56 @@ namespace ICSharpCode.SharpDevelop.Project public string this[string key] { get { - for (int i = 0; i < entries.Count; i++) { - if (entries[i].Key == key) { - return entries[i].Value; + lock (entries) { + for (int i = 0; i < entries.Count; i++) { + if (entries[i].Key == key) { + return entries[i].Value; + } } } return null; } set { Validate(key, value); - for (int i = 0; i < entries.Count; i++) { - if (entries[i].Key == key) { - entries[i] = new KeyValuePair(key, value); - Changed(this, EventArgs.Empty); - return; + lock (entries) { + bool found = false; + for (int i = 0; i < entries.Count; i++) { + if (entries[i].Key == key) { + entries[i] = new KeyValuePair(key, value); + found = true; + break; + } } + if (!found) + entries.Add(new KeyValuePair(key, value)); } - Add(key, value); + Changed(this, EventArgs.Empty); } } public IEnumerable Keys { get { - return entries.Select(e => e.Key); + lock (entries) + return entries.Select(e => e.Key).ToArray(); } } public IEnumerable Values { get { - return entries.Select(e => e.Value); + lock (entries) + return entries.Select(e => e.Value).ToArray(); } } public IEnumerator> GetEnumerator() { - return entries.GetEnumerator(); + lock (entries) + return entries.ToList().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { - return entries.GetEnumerator(); + return GetEnumerator(); } } } diff --git a/src/Main/Base/Project/Src/Gui/Dialogs/NewProjectDialog.cs b/src/Main/Base/Project/Src/Gui/Dialogs/NewProjectDialog.cs index 8d0274ee85..dc92980afd 100644 --- a/src/Main/Base/Project/Src/Gui/Dialogs/NewProjectDialog.cs +++ b/src/Main/Base/Project/Src/Gui/Dialogs/NewProjectDialog.cs @@ -353,20 +353,21 @@ namespace ICSharpCode.SharpDevelop.Project.Dialogs NewSolutionLocation = null; NewProjectLocation = null; if (createNewSolution) { - using (ISolution createdSolution = item.Template.CreateSolution(cinfo)) { - if (createdSolution != null) - NewSolutionLocation = createdSolution.FileName; + if (!SD.ProjectService.CloseSolution()) + return; + ISolution createdSolution = item.Template.CreateSolution(cinfo); + if (createdSolution != null) { + NewSolutionLocation = createdSolution.FileName; + if (!SD.ProjectService.OpenSolution(createdSolution)) { + createdSolution.Dispose(); + return; + } } - if (NewSolutionLocation != null) - SD.ProjectService.OpenSolutionOrProject(NewSolutionLocation); } else { - using (IProject project = item.Template.CreateProject(SD.ProjectService.CurrentSolution, cinfo)) { - NewProjectLocation = project.FileName; - } - if (NewProjectLocation != null) { - SolutionFolderNode.Folder.AddExistingProject(NewProjectLocation); - ProjectService.SaveSolution(); - } + IProject project = item.Template.CreateProject(SD.ProjectService.CurrentSolution, cinfo); + NewProjectLocation = project.FileName; + SolutionFolderNode.Folder.Items.Add(project); + ProjectService.SaveSolution(); } item.Template.RunOpenActions(cinfo); diff --git a/src/Main/Base/Project/Src/Gui/Pads/FileScout.cs b/src/Main/Base/Project/Src/Gui/Pads/FileScout.cs index 3215f231d5..e212d56a2a 100644 --- a/src/Main/Base/Project/Src/Gui/Pads/FileScout.cs +++ b/src/Main/Base/Project/Src/Gui/Pads/FileScout.cs @@ -399,7 +399,7 @@ namespace ICSharpCode.SharpDevelop.Gui { foreach (FileList.FileListItem item in filelister.SelectedItems) { var fileName = FileName.Create(item.FullName); - if (SD.ProjectService.IsProjectOrSolutionFile(fileName)) + if (SD.ProjectService.IsSolutionOrProjectFile(fileName)) SD.ProjectService.OpenSolutionOrProject(fileName); else FileService.OpenFile(fileName); diff --git a/src/Main/Base/Project/Src/Gui/Pads/ProjectBrowser/ProjectBrowserControl.cs b/src/Main/Base/Project/Src/Gui/Pads/ProjectBrowser/ProjectBrowserControl.cs index 819a514108..581a743b67 100644 --- a/src/Main/Base/Project/Src/Gui/Pads/ProjectBrowser/ProjectBrowserControl.cs +++ b/src/Main/Base/Project/Src/Gui/Pads/ProjectBrowser/ProjectBrowserControl.cs @@ -100,7 +100,7 @@ namespace ICSharpCode.SharpDevelop.Project foreach (string file in files) { try { var fileName = FileName.Create(file); - if (SD.ProjectService.IsProjectOrSolutionFile(fileName)) + if (SD.ProjectService.IsSolutionOrProjectFile(fileName)) SD.ProjectService.OpenSolutionOrProject(fileName); else FileService.OpenFile(fileName); diff --git a/src/Main/Base/Project/Src/Internal/Templates/Project/ProjectDescriptor.cs b/src/Main/Base/Project/Src/Internal/Templates/Project/ProjectDescriptor.cs index 9d85f010c8..32d814f4ee 100644 --- a/src/Main/Base/Project/Src/Internal/Templates/Project/ProjectDescriptor.cs +++ b/src/Main/Base/Project/Src/Internal/Templates/Project/ProjectDescriptor.cs @@ -507,6 +507,7 @@ namespace ICSharpCode.SharpDevelop.Internal.Templates ProjectService.OnProjectCreated(new ProjectEventArgs(project)); + projectCreateOptions.CreatedProjects.Add(project); success = true; return project; } finally { diff --git a/src/Main/Base/Project/Src/Internal/Templates/Project/ProjectTemplate.cs b/src/Main/Base/Project/Src/Internal/Templates/Project/ProjectTemplate.cs index 626baee58a..fbe7ca5633 100644 --- a/src/Main/Base/Project/Src/Internal/Templates/Project/ProjectTemplate.cs +++ b/src/Main/Base/Project/Src/Internal/Templates/Project/ProjectTemplate.cs @@ -26,6 +26,13 @@ namespace ICSharpCode.SharpDevelop.Internal.Templates public string SolutionName { get; set; } public string ProjectName { get; set; } public TargetFramework TargetFramework { get; set; } + + public IList CreatedProjects { get; private set; } + + public ProjectCreateOptions() + { + this.CreatedProjects = new List(); + } } /// diff --git a/src/Main/Base/Project/Src/Project/AbstractProject.cs b/src/Main/Base/Project/Src/Project/AbstractProject.cs index e764cd3795..a0e16c8a6c 100644 --- a/src/Main/Base/Project/Src/Project/AbstractProject.cs +++ b/src/Main/Base/Project/Src/Project/AbstractProject.cs @@ -44,7 +44,7 @@ namespace ICSharpCode.SharpDevelop.Project this.configurationMapping = information.ConfigurationMapping ?? new ConfigurationMapping(); this.Name = information.ProjectName; this.FileName = information.FileName; - this.idGuid = information.IdGuid; + this.idGuid = (information.IdGuid != Guid.Empty ? information.IdGuid : Guid.NewGuid()); this.TypeGuid = information.TypeGuid; if (information.ProjectSections != null) this.projectSections.AddRange(information.ProjectSections); diff --git a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs index 66bd768e5f..6129526421 100644 --- a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs @@ -62,6 +62,11 @@ namespace ICSharpCode.SharpDevelop.Project }; public override void Dispose() + { + DisposeThisClass(); + } + + void DisposeThisClass() { base.Dispose(); UnloadCurrentlyOpenProject(); @@ -1113,6 +1118,7 @@ namespace ICSharpCode.SharpDevelop.Project : base(loadInformation) { isLoading = true; + bool success = false; try { try { LoadProjectInternal(loadInformation); @@ -1121,12 +1127,14 @@ namespace ICSharpCode.SharpDevelop.Project throw; } } + success = true; } catch (InvalidProjectFileException ex) { LoggingService.Warn(ex); LoggingService.Warn("ErrorCode = " + ex.ErrorCode); - Dispose(); throw new ProjectLoadException(ex.Message, ex); } finally { + if (!success) + DisposeThisClass(); isLoading = false; } } diff --git a/src/Main/Base/Project/Src/Project/MSBuildInternals.cs b/src/Main/Base/Project/Src/Project/MSBuildInternals.cs index 53caa60031..7475b74477 100644 --- a/src/Main/Base/Project/Src/Project/MSBuildInternals.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildInternals.cs @@ -87,7 +87,7 @@ namespace ICSharpCode.SharpDevelop.Project /// public static string FixPlatformNameForProject(string platformName) { - if (platformName == "Any CPU") { + if (ConfigurationAndPlatform.ConfigurationNameComparer.Equals(platformName, "Any CPU")) { return "AnyCPU"; } else { return platformName; @@ -100,7 +100,7 @@ namespace ICSharpCode.SharpDevelop.Project /// public static string FixPlatformNameForSolution(string platformName) { - if (platformName == "AnyCPU") { + if (ConfigurationAndPlatform.ConfigurationNameComparer.Equals(platformName, "AnyCPU")) { return "Any CPU"; } else { return platformName; diff --git a/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs b/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs index bb9261c28d..7d18e9c352 100644 --- a/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs +++ b/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs @@ -43,7 +43,7 @@ namespace ICSharpCode.SharpDevelop.Project [Obsolete("Use SD.ProjectService.IsProjectOrSolutionFile instead")] public static bool HasProjectLoader(string fileName) { - return SD.ProjectService.IsProjectOrSolutionFile(FileName.Create(fileName)); + return SD.ProjectService.IsSolutionOrProjectFile(FileName.Create(fileName)); } [Obsolete("Use SD.ProjectService.OpenSolutionOrProject instead")] @@ -198,8 +198,8 @@ namespace ICSharpCode.SharpDevelop.Project } public static event EventHandler SolutionLoaded { - add { SD.ProjectService.SolutionLoaded += value; } - remove { SD.ProjectService.SolutionLoaded -= value; } + add { SD.ProjectService.SolutionOpened += value; } + remove { SD.ProjectService.SolutionOpened -= value; } } public static event EventHandler SolutionClosed { diff --git a/src/Main/Base/Project/Util/IProgressMonitor.cs b/src/Main/Base/Project/Util/IProgressMonitor.cs index 21c9a7664a..c9428b5a4d 100644 --- a/src/Main/Base/Project/Util/IProgressMonitor.cs +++ b/src/Main/Base/Project/Util/IProgressMonitor.cs @@ -39,6 +39,10 @@ namespace ICSharpCode.SharpDevelop /// /// The amount of work this sub-task performs in relation to the work of this task. /// That means, this parameter is used as a scaling factor for work performed within the subtask. + /// + /// A cancellation token that can be used to cancel the sub-task. + /// Note: cancelling the main task will not cancel the sub-task. + /// /// A new progress monitor representing the sub-task. /// Multiple child progress monitors can be used at once; even concurrently on multiple threads. IProgressMonitor CreateSubTask(double workAmount, CancellationToken cancellationToken); diff --git a/src/Main/Base/Project/Util/ReactiveExtensions.cs b/src/Main/Base/Project/Util/ReactiveExtensions.cs index ea4d7d253f..bd5f2f7ee9 100644 --- a/src/Main/Base/Project/Util/ReactiveExtensions.cs +++ b/src/Main/Base/Project/Util/ReactiveExtensions.cs @@ -43,11 +43,38 @@ namespace ICSharpCode.SharpDevelop #endregion #region Create Observable from Task + Callback Delegate + /// + /// Converts an async function that produces a result collection using callbacks, + /// into an IObservable. + /// + /// + /// The async function will run once for each subscription on the observable. + /// + /// + /// The async function gets passed a cancellation token. This token will get signalled when the observable subscription is cancelled. + /// public static IObservable CreateObservable(Func, Task> func) { return new AnonymousObservable(observer => new TaskToObserverSubscription(func, observer)); } + /// + /// Converts an async function that produces a result collection using callbacks, + /// into an IObservable. + /// + /// + /// The async function will run once for each subscription on the observable. + /// + /// + /// The progress monitor used to report progress. The monitor will be disposed after the async function + /// completes. + /// The async function is given a child monitor of this progress monitor: it forwards all + /// calls to the parent progress monitor, but uses a cancellation token that gets signalled + /// when the observable subscription is cancelled. + /// + /// + /// Multiple subscriptions on the observable do not work well together with progress reporting. + /// public static IObservable CreateObservable(Func, Task> func, IProgressMonitor progressMonitor) { return new AnonymousObservable(observer => new TaskToObserverSubscription(func, progressMonitor, observer)); @@ -101,11 +128,122 @@ namespace ICSharpCode.SharpDevelop } #endregion + #region First/Single/Last + public static Task FirstAsync(this IObservable source, CancellationToken cancellationToken = default(CancellationToken)) + { + return FirstInternalAsync(source, cancellationToken, true); + } + + public static Task FirstOrDefaultAsync(this IObservable source, CancellationToken cancellationToken = default(CancellationToken)) + { + return FirstInternalAsync(source, cancellationToken, false); + } + + static async Task FirstInternalAsync(IObservable source, CancellationToken cancellationToken, bool throwIfEmpty) + { + var tcs = new TaskCompletionSource(); + Action onCompleted; + if (throwIfEmpty) + onCompleted = () => tcs.TrySetException(new InvalidOperationException()); + else + onCompleted = () => tcs.TrySetResult(default(T)); + using (source.Subscribe(item => tcs.TrySetResult(item), + exception => tcs.TrySetException(exception), + onCompleted)) + { + using (cancellationToken.Register(() => tcs.TrySetCanceled())) { + return await tcs.Task.ConfigureAwait(false); + } + } + } + + public static Task SingleAsync(this IObservable source, CancellationToken cancellationToken = default(CancellationToken)) + { + return SingleInternalAsync(source, cancellationToken, true); + } + + public static Task SingleOrDefaultAsync(this IObservable source, CancellationToken cancellationToken = default(CancellationToken)) + { + return SingleInternalAsync(source, cancellationToken, false); + } + + static async Task SingleInternalAsync(IObservable source, CancellationToken cancellationToken, bool throwIfEmpty) + { + SemaphoreSlim gate = new SemaphoreSlim(0); + T value = default(T); + bool isEmpty = true; + Exception ex = null; + using (source.Subscribe( + item => { + if (isEmpty) { + value = item; + isEmpty = false; + } else { + ex = new InvalidOperationException("Sequence contains more than one element."); + gate.Release(); + } + }, + exception => { + ex = exception; + gate.Release(); + }, + () => { + gate.Release(); + })) + { + await gate.WaitAsync(cancellationToken).ConfigureAwait(false); + } + if (ex != null) + throw ex; + if (isEmpty && throwIfEmpty) + throw new InvalidOperationException("Sequence contains no elements."); + return value; + } + + public static Task LastAsync(this IObservable source, CancellationToken cancellationToken = default(CancellationToken)) + { + return LastInternalAsync(source, cancellationToken, true); + } + + public static Task LastOrDefaultAsync(this IObservable source, CancellationToken cancellationToken = default(CancellationToken)) + { + return LastInternalAsync(source, cancellationToken, false); + } + + static async Task LastInternalAsync(IObservable source, CancellationToken cancellationToken, bool throwIfEmpty) + { + SemaphoreSlim gate = new SemaphoreSlim(0); + T value = default(T); + bool isEmpty = true; + Exception ex = null; + using (source.Subscribe( + item => { + value = item; + isEmpty = false; + }, + exception => { + ex = exception; + gate.Release(); + }, + () => { + gate.Release(); + })) + { + await gate.WaitAsync(cancellationToken).ConfigureAwait(false); + } + if (ex != null) + throw ex; + if (isEmpty && throwIfEmpty) + throw new InvalidOperationException("Sequence contains no elements."); + return value; + } + #endregion + #region ToListAsync /// /// Converts the observable into a List. /// - public static async Task> ToListAsync(this IObservable source, CancellationToken cancellationToken) + public static async Task> ToListAsync(this IObservable source, CancellationToken cancellationToken = default(CancellationToken)) { var tcs = new TaskCompletionSource>(); List results = new List(); diff --git a/src/Main/SharpDevelop/Project/Configuration/SolutionConfigurationOrPlatformNameCollection.cs b/src/Main/SharpDevelop/Project/Configuration/SolutionConfigurationOrPlatformNameCollection.cs index 271c045908..26e6780aca 100644 --- a/src/Main/SharpDevelop/Project/Configuration/SolutionConfigurationOrPlatformNameCollection.cs +++ b/src/Main/SharpDevelop/Project/Configuration/SolutionConfigurationOrPlatformNameCollection.cs @@ -12,36 +12,55 @@ using Microsoft.Build.Logging; namespace ICSharpCode.SharpDevelop.Project { + /// + /// Implementation for the and lists. + /// + /// + /// This class provides thread-safe read access even if there is a concurrent write operation. + /// However, multiple concurrent writes are not supported. + /// The intended use is that only the main thread will write to this collection; but background threads may read at any time. + /// class SolutionConfigurationOrPlatformNameCollection : IConfigurationOrPlatformNameCollection { public event ModelCollectionChangedEventHandler CollectionChanged; - + readonly List list = new List(); - readonly Solution solution; + volatile IReadOnlyList listSnapshot = EmptyList.Instance; + readonly ISolution solution; readonly bool isPlatform; - public SolutionConfigurationOrPlatformNameCollection(Solution solution, bool isPlatform) + public SolutionConfigurationOrPlatformNameCollection(ISolution solution, bool isPlatform) { this.solution = solution; this.isPlatform = isPlatform; } + void OnCollectionChanged(IReadOnlyCollection oldItems, IReadOnlyCollection newItems) + { + this.listSnapshot = list.ToArray(); + var eh = CollectionChanged; + if (eh != null) + eh(oldItems, newItems); + } + #region IReadOnlyCollection implementation - + public int Count { - get { return list.Count; } + get { + return listSnapshot.Count; + } } - + public IEnumerator GetEnumerator() { - return list.GetEnumerator(); + return listSnapshot.GetEnumerator(); } - + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return list.GetEnumerator(); + return listSnapshot.GetEnumerator(); } - + #endregion public string ValidateName(string name) @@ -59,6 +78,7 @@ namespace ICSharpCode.SharpDevelop.Project void IConfigurationOrPlatformNameCollection.Add(string newName, string copyFrom) { + SD.MainThread.VerifyAccess(); newName = ValidateName(newName); if (newName == null) throw new ArgumentException(); @@ -79,12 +99,13 @@ namespace ICSharpCode.SharpDevelop.Project void IConfigurationOrPlatformNameCollection.Remove(string name) { + SD.MainThread.VerifyAccess(); int pos = GetIndex(name); - if (pos >= 0) { - name = list[pos]; // get the name in original case - list.RemoveAt(pos); - OnCollectionChanged(new[] { name }, EmptyList.Instance); - } + if (pos < 0) + return; + name = list[pos]; // get the name in original case + list.RemoveAt(pos); + OnCollectionChanged(new[] { name }, EmptyList.Instance); } void IConfigurationOrPlatformNameCollection.Rename(string oldName, string newName) @@ -96,19 +117,22 @@ namespace ICSharpCode.SharpDevelop.Project if (pos < 0) throw new ArgumentException(); oldName = list[pos]; // get oldName in original case - foreach (var project in solution.Projects) { - throw new NotImplementedException(); - //project.ConfigurationMapping.RenameSolutionConfig(oldName, newName, isPlatform); - } list[pos] = newName; + listSnapshot = null; OnCollectionChanged(new[] { oldName }, new[] { newName }); - } - - void OnCollectionChanged(IReadOnlyCollection oldItems, IReadOnlyCollection newItems) - { - if (CollectionChanged != null) - CollectionChanged(oldItems, newItems); - solution.IsDirty = true; + + foreach (var project in solution.Projects) { + var mapping = project.ConfigurationMapping; + foreach (string otherName in isPlatform ? solution.ConfigurationNames : solution.PlatformNames) { + var oldSolutionConfig = isPlatform ? new ConfigurationAndPlatform(otherName, oldName) : new ConfigurationAndPlatform(oldName, otherName); + var newSolutionConfig = isPlatform ? new ConfigurationAndPlatform(otherName, newName) : new ConfigurationAndPlatform(newName, otherName); + var projectConfig = mapping.GetProjectConfiguration(oldSolutionConfig); + var buildEnabled = mapping.IsBuildEnabled(oldSolutionConfig); + mapping.Remove(oldSolutionConfig); + mapping.SetProjectConfiguration(newSolutionConfig, projectConfig); + mapping.SetBuildEnabled(newSolutionConfig, buildEnabled); + } + } } } } diff --git a/src/Main/SharpDevelop/Project/ProjectService.cs b/src/Main/SharpDevelop/Project/ProjectService.cs index 6a3407795b..cbd6d51739 100644 --- a/src/Main/SharpDevelop/Project/ProjectService.cs +++ b/src/Main/SharpDevelop/Project/ProjectService.cs @@ -124,20 +124,21 @@ namespace ICSharpCode.SharpDevelop.Project #endregion #region OpenSolutionOrProject - public event EventHandler SolutionLoaded = delegate {}; + public event EventHandler SolutionOpened = delegate {}; - public void OpenSolutionOrProject(FileName fileName) + public bool OpenSolutionOrProject(FileName fileName) { - if (!IsProjectOrSolutionFile(fileName)) { + if (!IsSolutionOrProjectFile(fileName)) { MessageService.ShowError(StringParser.Parse("${res:ICSharpCode.SharpDevelop.Commands.OpenCombine.InvalidProjectOrCombine}", new StringTagPair("FileName", fileName))); - return; + return false; } if (!CloseSolution(allowCancel: true)) - return; - FileUtility.ObservedLoad(OpenSolutionOrProjectInternal, fileName); + return false; + FileUtility.ObservedLoad(OpenSolutionInternal, fileName); + return currentSolution != null; } - void OpenSolutionOrProjectInternal(FileName fileName) + void OpenSolutionInternal(FileName fileName) { ISolution solution; using (var progress = AsynchronousWaitDialog.ShowWaitDialog("Loading Solution...")) { @@ -153,7 +154,29 @@ namespace ICSharpCode.SharpDevelop.Project this.CurrentSolution = solution; } - SolutionLoaded(this, new SolutionEventArgs(solution)); + OnSolutionOpened(solution); + } + + public bool OpenSolution(FileName fileName) + { + if (!CloseSolution(allowCancel: true)) + return false; + FileUtility.ObservedLoad(OpenSolutionInternal, fileName); + return currentSolution != null; + } + + public bool OpenSolution(ISolution solution) + { + if (!CloseSolution(allowCancel: true)) + return false; + this.CurrentSolution = solution; + OnSolutionOpened(solution); + return true; + } + + void OnSolutionOpened(ISolution solution) + { + SolutionOpened(this, new SolutionEventArgs(solution)); SD.FileService.RecentOpen.AddRecentProject(solution.FileName); Project.Converter.UpgradeViewContent.ShowIfRequired(solution); } @@ -266,8 +289,8 @@ namespace ICSharpCode.SharpDevelop.Project } #endregion - #region IsProjectOrSolutionFile - public bool IsProjectOrSolutionFile(FileName fileName) + #region IsSolutionOrProjectFile + public bool IsSolutionOrProjectFile(FileName fileName) { AddInTreeNode addinTreeNode = SD.AddInTree.GetTreeNode("/SharpDevelop/Workbench/Combine/FileFilter"); foreach (Codon codon in addinTreeNode.Codons) { diff --git a/src/Main/SharpDevelop/Project/Solution.cs b/src/Main/SharpDevelop/Project/Solution.cs index 3af02b44e5..95c3133dce 100644 --- a/src/Main/SharpDevelop/Project/Solution.cs +++ b/src/Main/SharpDevelop/Project/Solution.cs @@ -27,6 +27,10 @@ namespace ICSharpCode.SharpDevelop.Project this.ConfigurationNames = new SolutionConfigurationOrPlatformNameCollection(this, false); this.PlatformNames = new SolutionConfigurationOrPlatformNameCollection(this, true); this.FileName = fileName; + base.Name = fileName.GetFileNameWithoutExtension(); + + this.globalSections = new SynchronizedModelCollection(new NullSafeSimpleModelCollection()); + globalSections.CollectionChanged += OnSolutionSectionCollectionChanged; fileService.FileRenamed += FileServiceFileRenamed; fileService.FileRemoved += FileServiceFileRemoved; @@ -61,6 +65,15 @@ namespace ICSharpCode.SharpDevelop.Project public DirectoryName Directory { get { return directory; } } + + public override string Name { + get { + return base.Name; + } + set { + throw new NotImplementedException(); + } + } #endregion #region StartupProject @@ -100,8 +113,7 @@ namespace ICSharpCode.SharpDevelop.Project #endregion #region Project list - SimpleModelCollection projects = new SimpleModelCollection(); - // TODO: thread-safety? + IMutableModelCollection projects = new SynchronizedModelCollection(new SimpleModelCollection()); public IModelCollection Projects { get { return projects; } @@ -134,6 +146,11 @@ namespace ICSharpCode.SharpDevelop.Project }, DispatcherPriority.Background); } + internal IDisposable ReportBatch() + { + return projects.BatchUpdate(); + } + internal void ReportRemovedItem(ISolutionItem oldItem) { if (oldItem is ISolutionFolder) { @@ -165,7 +182,7 @@ namespace ICSharpCode.SharpDevelop.Project } } - SimpleModelCollection globalSections = new SimpleModelCollection(); + readonly IMutableModelCollection globalSections; public IMutableModelCollection GlobalSections { get { return globalSections; } diff --git a/src/Main/SharpDevelop/Project/SolutionFolder.cs b/src/Main/SharpDevelop/Project/SolutionFolder.cs index ea3a0dee19..7af6889fbf 100644 --- a/src/Main/SharpDevelop/Project/SolutionFolder.cs +++ b/src/Main/SharpDevelop/Project/SolutionFolder.cs @@ -53,13 +53,15 @@ namespace ICSharpCode.SharpDevelop.Project protected override void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { - foreach (ISolutionItem item in removedItems) { - folder.parentSolution.ReportRemovedItem(item); - item.ParentFolder = null; - } - foreach (ISolutionItem item in addedItems) { - item.ParentFolder = folder; - folder.parentSolution.ReportAddedItem(item); + using (folder.parentSolution.ReportBatch()) { + foreach (ISolutionItem item in removedItems) { + folder.parentSolution.ReportRemovedItem(item); + item.ParentFolder = null; + } + foreach (ISolutionItem item in addedItems) { + item.ParentFolder = folder; + folder.parentSolution.ReportAddedItem(item); + } } base.OnCollectionChanged(removedItems, addedItems); folder.parentSolution.IsDirty = true; @@ -73,7 +75,7 @@ namespace ICSharpCode.SharpDevelop.Project } #endregion - public string Name { get; set; } + public virtual string Name { get; set; } public ISolutionFolder ParentFolder { get; set; } diff --git a/src/Main/SharpDevelop/Workbench/WorkbenchStartup.cs b/src/Main/SharpDevelop/Workbench/WorkbenchStartup.cs index bad2517d52..a58aec0164 100644 --- a/src/Main/SharpDevelop/Workbench/WorkbenchStartup.cs +++ b/src/Main/SharpDevelop/Workbench/WorkbenchStartup.cs @@ -93,7 +93,7 @@ namespace ICSharpCode.SharpDevelop.Workbench try { var fullFileName = FileName.Create(Path.GetFullPath(file)); - if (SD.ProjectService.IsProjectOrSolutionFile(fullFileName)) { + if (SD.ProjectService.IsSolutionOrProjectFile(fullFileName)) { SD.ProjectService.OpenSolutionOrProject(fullFileName); } else { SharpDevelop.FileService.OpenFile(fullFileName); diff --git a/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs b/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs index 079b46e1ce..0110efe707 100644 --- a/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs +++ b/src/Main/SharpDevelop/Workbench/WpfWorkbench.cs @@ -643,7 +643,7 @@ namespace ICSharpCode.SharpDevelop.Workbench foreach (string file in files) { if (File.Exists(file)) { var fileName = FileName.Create(file); - if (SD.ProjectService.IsProjectOrSolutionFile(fileName)) { + if (SD.ProjectService.IsSolutionOrProjectFile(fileName)) { SD.ProjectService.OpenSolutionOrProject(fileName); } else { SD.FileService.OpenFile(fileName);