Konstantin Koch 8287c54432 Included the Visual Studio extension and made the necessary changes to make it run.
Replaced the old VS templates with ones that offer more flexiblity.
Started replacing the Content Project for the samples with our custom project type.
Inlcuded a basic not yet working AssimpImporter.
2015-04-08 14:50:03 +02:00

7075 lines
288 KiB
C#
Raw Blame History

/* ****************************************************************************
*
* Copyright (c) Microsoft Corporation.
*
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
* copy of the license can be found in the License.html file at the root of this distribution. If
* you cannot locate the Apache License, Version 2.0, please send an email to
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
* by the terms of the Apache License, Version 2.0.
*
* You must not remove this notice, or any other, from this software.
*
* ***************************************************************************/
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Xml;
using EnvDTE;
using Microsoft.Build.Execution;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
using IServiceProvider = System.IServiceProvider;
using MSBuild = Microsoft.Build.Evaluation;
using MSBuildConstruction = Microsoft.Build.Construction;
using MSBuildExecution = Microsoft.Build.Execution;
using OleConstants = Microsoft.VisualStudio.OLE.Interop.Constants;
using VsCommands = Microsoft.VisualStudio.VSConstants.VSStd97CmdID;
using VsCommands2K = Microsoft.VisualStudio.VSConstants.VSStd2KCmdID;
using System.Runtime.Versioning;
using System.ComponentModel.Design;
using System.Reflection;
using Microsoft.Build.Utilities;
using Microsoft.VisualStudio.ReferenceManager.Providers;
using Microsoft.Build.Evaluation;
using Microsoft.Win32;
using System.Collections;
using System.Runtime;
namespace Microsoft.VisualStudio.Project
{
/// <summary>
/// Manages the persistent state of the project (References, options, files, etc.) and deals with user interaction via a GUI in the form a hierarchy.
/// </summary>
public abstract partial class ProjectNode : HierarchyNode,
IVsUIHierarchy,
IVsPersistHierarchyItem2,
IVsHierarchyDeleteHandler,
IVsHierarchyDropDataTarget,
IVsHierarchyDropDataSource,
IVsHierarchyDropDataSource2,
IVsGetCfgProvider,
IVsProject3,
IVsAggregatableProject,
IVsProjectFlavorCfgProvider,
IPersistFileFormat,
IVsBuildPropertyStorage,
IVsComponentUser,
IVsDependencyProvider,
IReferenceContainerProvider,
IVsSccProject2,
IBuildDependencyUpdate,
IVsProjectSpecialFiles,
IVsProjectBuildSystem,
IOleCommandTarget
//IVsSetTargetFrameworkWorkerCallback
{
#region nested types
public enum ImageName
{
OfflineWebApp = 0,
WebReferencesFolder = 1,
OpenReferenceFolder = 2,
ReferenceFolder = 3,
Reference = 4,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDL")]
SDLWebReference = 5,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "DISCO")]
DISCOWebReference = 6,
Folder = 7,
OpenFolder = 8,
ExcludedFolder = 9,
OpenExcludedFolder = 10,
ExcludedFile = 11,
DependentFile = 12,
MissingFile = 13,
WindowsForm = 14,
WindowsUserControl = 15,
WindowsComponent = 16,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "XML")]
XMLSchema = 17,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "XML")]
XMLFile = 18,
WebForm = 19,
WebService = 20,
WebUserControl = 21,
WebCustomUserControl = 22,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ASP")]
ASPPage = 23,
GlobalApplicationClass = 24,
WebConfig = 25,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "HTML")]
HTMLPage = 26,
StyleSheet = 27,
ScriptFile = 28,
TextFile = 29,
SettingsFile = 30,
Resources = 31,
Bitmap = 32,
Icon = 33,
Image = 34,
ImageMap = 35,
XWorld = 36,
Audio = 37,
Video = 38,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CAB")]
CAB = 39,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "JAR")]
JAR = 40,
DataEnvironment = 41,
PreviewFile = 42,
DanglingReference = 43,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "XSLT")]
XSLTFile = 44,
Cursor = 45,
AppDesignerFolder = 46,
Data = 47,
Application = 48,
DataSet = 49,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PFX")]
PFX = 50,
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SNK")]
SNK = 51,
ImageLast = 51
}
/// <summary>
/// Flags for specifying which events to stop triggering.
/// </summary>
[Flags]
public enum EventTriggering
{
TriggerAll = 0,
DoNotTriggerHierarchyEvents = 1,
DoNotTriggerTrackerEvents = 2,
DoNotTriggerTrackerQueryEvents = 4
}
private class HierarchyEventsSink : IVsHierarchyEvents {
private readonly ProjectNode projectNode;
public HierarchyEventsSink(ProjectNode projectNode) {
this.projectNode = projectNode;
}
public int OnInvalidateIcon(IntPtr hicon) {
return VSConstants.S_OK;
}
public int OnInvalidateItems(uint itemidParent) {
return VSConstants.S_OK;
}
public int OnItemAdded(uint itemidParent, uint itemidSiblingPrev, uint itemidAdded) {
return VSConstants.S_OK;
}
public int OnItemDeleted(uint itemid) {
return VSConstants.S_OK;
}
public int OnItemsAppended(uint itemidParent) {
return VSConstants.S_OK;
}
public int OnPropertyChanged(uint itemid, int propid, uint flags) {
return VSConstants.S_OK;
}
}
#endregion
#region constants
/// <summary>
/// The user file extension.
/// </summary>
public const string PerUserFileExtension = ".user";
//This version is different from the one of ANX because we can't load a different framework version in the AppDomain and still communicate with it without using reflection and custom marshalling for every call.
//The framework version just depends on the version that visual studio uses.
#if DEV11_OR_LATER
private static readonly FrameworkName DefaultTargetFrameworkMoniker = new FrameworkName(".NETFramework", new Version(4, 5));
#else
private static readonly FrameworkName DefaultTargetFrameworkMoniker = new FrameworkName(".NETFramework", new Version(4, 0), "Client");
#endif
#endregion
#region fields
/// <summary>
/// List of output groups names and their associated target
/// </summary>
private static KeyValuePair<string, string>[] outputGroupNames =
{ // Name Target (MSBuild)
new KeyValuePair<string, string>("Built", "BuiltProjectOutputGroup"),
new KeyValuePair<string, string>("ContentFiles", "ContentFilesProjectOutputGroup"),
new KeyValuePair<string, string>("LocalizedResourceDlls", "SatelliteDllsProjectOutputGroup"),
new KeyValuePair<string, string>("Documentation", "DocumentationProjectOutputGroup"),
new KeyValuePair<string, string>("Symbols", "DebugSymbolsProjectOutputGroup"),
new KeyValuePair<string, string>("SourceFiles", "SourceFilesProjectOutputGroup"),
new KeyValuePair<string, string>("XmlSerializer", "SGenFilesOutputGroup"),
};
private EventSinkCollection _hierarchyEventSinks = new EventSinkCollection();
/// <summary>A project will only try to build if it can obtain a lock on this object</summary>
private volatile static object BuildLock = new object();
/// <summary>Maps integer ids to project item instances</summary>
private EventSinkCollection itemIdMap = new EventSinkCollection();
/// <summary>A service provider call back object provided by the IDE hosting the project manager</summary>
private ServiceProvider site;
private TrackDocumentsHelper tracker;
/// <summary>
/// This property returns the time of the last change made to this project.
/// It is not the time of the last change on the project file, but actually of
/// the in memory project settings. In other words, it is the last time that
/// SetProjectDirty was called.
/// </summary>
private DateTime lastModifiedTime;
/// <summary>
/// MSBuild engine we are going to use
/// </summary>
private MSBuild.ProjectCollection buildEngine;
private Microsoft.Build.Framework.ILogger buildLogger;
private bool useProvidedLogger;
private MSBuild.Project buildProject;
private MSBuildExecution.ProjectInstance currentConfig;
private ConfigProvider configProvider;
private TaskProvider taskProvider;
private string filename;
private Microsoft.VisualStudio.Shell.Url baseUri;
private string projectHome;
private bool isDirty;
private bool projectOpened;
private bool buildIsPrepared;
private string errorString;
private string warningString;
private ImageHandler imageHandler;
private Guid projectIdGuid;
private bool isClosed;
protected EventTriggering eventTriggeringFlag = EventTriggering.TriggerAll;
private bool canFileNodesHaveChilds;
private bool isProjectEventsListener = true;
/// <summary>
/// The build dependency list passed to IVsDependencyProvider::EnumDependencies
/// </summary>
private List<IVsBuildDependency> buildDependencyList = new List<IVsBuildDependency>();
/// <summary>
/// Defines if Project System supports Project Designer
/// </summary>
private bool supportsProjectDesigner;
private bool showProjectInSolutionPage = true;
private bool buildInProcess;
private string sccProjectName;
private string sccLocalPath;
private string sccAuxPath;
private string sccProvider;
/// <summary>
/// Flag for controling how many times we register with the Scc manager.
/// </summary>
private bool isRegisteredWithScc;
/// <summary>
/// Flag for controling query edit should communicate with the scc manager.
/// </summary>
protected bool disableQueryEdit;
/// <summary>
/// Control if command with potential destructive behavior such as delete should
/// be enabled for nodes of this project.
/// </summary>
private bool canProjectDeleteItems;
/// <summary>
/// Member to store output base relative path. Used by OutputBaseRelativePath property
/// </summary>
private string outputBaseRelativePath = "bin";
/// <summary>
/// Used for flavoring to hold the XML fragments
/// </summary>
private XmlDocument xmlFragments;
/// <summary>
/// Used to map types to CATID. This provide a generic way for us to do this
/// and make it simpler for a project to provide it's CATIDs for the different type of objects
/// for which it wants to support extensibility. This also enables us to have multiple
/// type mapping to the same CATID if we choose to.
/// </summary>
private Dictionary<Type, Guid> catidMapping = new Dictionary<Type, Guid>();
/// <summary>
/// The public package implementation.
/// </summary>
private ProjectPackage package;
/// <summary>
/// Mapping from item names to their hierarchy nodes for all disk-based nodes.
/// </summary>
protected readonly Dictionary<string, HierarchyNode> _diskNodes = new Dictionary<string, HierarchyNode>(StringComparer.OrdinalIgnoreCase);
// Has the object been disposed.
private bool isDisposed;
private IVsHierarchy parentHierarchy;
private int parentHierarchyItemId;
private List<HierarchyNode> itemsDraggedOrCutOrCopied;
private CopyPasteDragSource sourceDraggedOrCutOrCopied;
/// <summary>
/// Folder node in the process of being created. First the hierarchy node
/// is added, then the label is edited, and when that completes/cancels
/// the folder gets created.
/// </summary>
private FolderNode _folderBeingCreated;
/// <summary>
/// Changing the framework causes an additional save and loosing of the service provider. We suspend the save because we can't handle that without a service provider.
/// </summary>
protected bool isChangingFramework = false;
#endregion
#region abstract properties
/// <summary>
/// This Guid must match the Guid you registered under
/// HKLM\Software\Microsoft\VisualStudio\%version%\Projects.
/// Among other things, the Project framework uses this
/// guid to find your project and item templates.
/// </summary>
public abstract Guid ProjectGuid
{
get;
}
/// <summary>
/// Returns a caption for VSHPROPID_TypeName.
/// </summary>
/// <returns></returns>
public abstract string ProjectType
{
get;
}
#endregion
#region virtual properties
public virtual FrameworkName TargetFrameworkMoniker
{
get
{
return DefaultTargetFrameworkMoniker;
}
set
{
}
}
/// <summary>
/// Indicates whether or not the project system supports Show All Files.
///
/// Subclasses will need to return true here, and will need to handle calls
/// </summary>
public virtual bool CanShowAllFiles
{
get
{
return false;
}
}
/// <summary>
/// Indicates whether or not the project is currently in the mode where its showing all files.
/// </summary>
public virtual bool IsShowingAllFiles
{
get
{
return false;
}
}
/// <summary>
/// Represents the command guid for the project system. This enables
/// using CommonConstants.cmdid* commands.
///
/// By default these commands are disabled if this isn't overridden
/// with the packages command guid.
/// </summary>
public virtual Guid SharedCommandGuid {
get {
return CommonConstants.NoSharedCommandsGuid;
}
}
/// <summary>
/// This is the project instance guid that is peristed in the project file
/// </summary>
[System.ComponentModel.BrowsableAttribute(false)]
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")]
public virtual Guid ProjectIDGuid
{
get
{
return this.projectIdGuid;
}
set
{
if (this.projectIdGuid != value)
{
this.projectIdGuid = value;
if (this.buildProject != null)
{
this.SetProjectProperty("ProjectGuid", this.projectIdGuid.ToString("B"));
}
}
}
}
public override bool CanAddFiles
{
get
{
return true;
}
}
#endregion
#region properties
public EventSinkCollection EventSinks
{
get { return _hierarchyEventSinks; }
}
protected bool IsIdeInCommandLineMode
{
get
{
bool cmdline = false;
var shell = this.site.GetService(typeof(SVsShell)) as IVsShell;
if (shell != null)
{
object obj;
Marshal.ThrowExceptionForHR(shell.GetProperty((int)__VSSPROPID.VSSPROPID_IsInCommandLineMode, out obj));
cmdline = (bool)obj;
}
return cmdline;
}
}
/// <summary>
/// Gets the folder node which is currently being added to the project via
/// Solution Explorer.
/// </summary>
public FolderNode FolderBeingCreated {
get {
return _folderBeingCreated;
}
set {
_folderBeingCreated = value;
}
}
enum CopyPasteDragSource {
None,
Dragged,
Cut,
Copied
}
private CopyPasteDragSource SourceDraggedOrCutOrCopied {
get {
return this.sourceDraggedOrCutOrCopied;
}
set {
this.sourceDraggedOrCutOrCopied = value;
}
}
public IList<HierarchyNode> ItemsDraggedOrCutOrCopied {
get {
return this.itemsDraggedOrCutOrCopied;
}
}
public MSBuildExecution.ProjectInstance CurrentConfig
{
get
{
return currentConfig;
}
set
{
currentConfig = value;
}
}
#region overridden properties
public override string FullPathToChildren {
get {
return ProjectHome;
}
}
public override int MenuCommandId
{
get
{
return VsMenus.IDM_VS_CTXT_PROJNODE;
}
}
public override string Url
{
get
{
return this.GetMkDocument();
}
}
public override string Caption
{
get
{
// Default to file name
string caption = this.buildProject.FullPath;
if (String.IsNullOrEmpty(caption))
{
if (this.buildProject.GetProperty(ProjectFileConstants.Name) != null)
{
caption = this.buildProject.GetProperty(ProjectFileConstants.Name).EvaluatedValue;
if (caption == null || caption.Length == 0)
{
caption = this.ItemNode.GetMetadata(ProjectFileConstants.Include);
}
}
}
else
{
caption = Path.GetFileNameWithoutExtension(caption);
}
return caption;
}
}
public override Guid ItemTypeGuid
{
get
{
return this.ProjectGuid;
}
}
public override int ImageIndex
{
get
{
return (int)ProjectNode.ImageName.Application;
}
}
#endregion
#region virtual properties
public virtual string ErrorString
{
get
{
if (this.errorString == null)
{
this.errorString = SR.GetString(SR.Error, CultureInfo.CurrentUICulture);
}
return this.errorString;
}
}
public virtual string WarningString
{
get
{
if (this.warningString == null)
{
this.warningString = SR.GetString(SR.Warning, CultureInfo.CurrentUICulture);
}
return this.warningString;
}
}
/// <summary>
/// Override this property to specify when the project file is dirty.
/// </summary>
protected virtual bool IsProjectFileDirty
{
get
{
string document = this.GetMkDocument();
if (String.IsNullOrEmpty(document))
{
return this.isDirty;
}
return (this.isDirty || !File.Exists(document));
}
}
/// <summary>
/// True if the project uses the Project Designer Editor instead of the property page frame to edit project properties.
/// </summary>
protected virtual bool SupportsProjectDesigner
{
get
{
return this.supportsProjectDesigner;
}
set
{
this.supportsProjectDesigner = value;
}
}
protected virtual Guid ProjectDesignerEditor
{
get
{
return VSConstants.GUID_ProjectDesignerEditor;
}
}
/// <summary>
/// Defines the flag that supports the VSHPROPID.ShowProjInSolutionPage
/// </summary>
protected virtual bool ShowProjectInSolutionPage
{
get
{
return this.showProjectInSolutionPage;
}
set
{
this.showProjectInSolutionPage = value;
}
}
#endregion
/// <summary>
/// Gets or sets the ability of a project filenode to have child nodes (sub items).
/// Example would be C#/VB forms having resx and designer files.
/// </summary>
public bool CanFileNodesHaveChilds
{
get
{
return canFileNodesHaveChilds;
}
set
{
canFileNodesHaveChilds = value;
}
}
/// <summary>
/// Gets a service provider object provided by the IDE hosting the project
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")]
public IServiceProvider Site
{
get
{
return this.site;
}
}
/// <summary>
/// Gets an ImageHandler for the project node.
/// </summary>
public ImageHandler ImageHandler
{
get
{
if (null == imageHandler)
{
imageHandler = new ImageHandler(ProjectIconsImageStripStream);
}
return imageHandler;
}
}
protected virtual Stream ProjectIconsImageStripStream
{
get
{
float visualStudioVersion;
if (!float.TryParse(((EnvDTE.DTE)this.Site.GetService(typeof(EnvDTE.DTE))).Version, NumberStyles.Float, CultureInfo.InvariantCulture, out visualStudioVersion))
return typeof(ProjectNode).Assembly.GetManifestResourceStream("Microsoft.VisualStudio.Project.Resources.imagelis_VS2013.png");
if (visualStudioVersion == 11.0)
return typeof(ProjectNode).Assembly.GetManifestResourceStream("Microsoft.VisualStudio.Project.Resources.imagelis_VS2012.png");
else if (visualStudioVersion >= 12.0)
return typeof(ProjectNode).Assembly.GetManifestResourceStream("Microsoft.VisualStudio.Project.Resources.imagelis_VS2013.png");
else
return typeof(ProjectNode).Assembly.GetManifestResourceStream("Microsoft.VisualStudio.Project.Resources.imagelis_VS2010.bmp");
}
}
/// <summary>
/// Gets the path to the root folder of the project.
/// </summary>
public string ProjectHome
{
get
{
if (projectHome == null)
{
projectHome = CommonUtils.GetAbsoluteDirectoryPath(
this.ProjectFolder,
this.GetProjectProperty(CommonConstants.ProjectHome, true));
}
Debug.Assert(projectHome != null, "ProjectHome should not be null");
return projectHome;
}
}
/// <summary>
/// Gets the path to the folder containing the project.
/// </summary>
public string ProjectFolder
{
get
{
return Path.GetDirectoryName(this.filename);
}
}
/// <summary>
/// Gets or sets the project filename.
/// </summary>
public string ProjectFile
{
get
{
return Path.GetFileName(this.filename);
}
set
{
this.SetEditLabel(value);
}
}
/// <summary>
/// Gets the Base Uniform Resource Identifier (URI).
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "URI")]
public Microsoft.VisualStudio.Shell.Url BaseURI
{
get
{
if (baseUri == null && this.buildProject != null)
{
string path = CommonUtils.NormalizeDirectoryPath(Path.GetDirectoryName(this.buildProject.FullPath));
baseUri = new Url(path);
}
Debug.Assert(baseUri != null, "Base URL should not be null. Did you call BaseURI before loading the project?");
return baseUri;
}
}
protected void BuildProjectLocationChanged()
{
baseUri = null;
projectHome = null;
}
/// <summary>
/// Gets whether or not the project is closed.
/// </summary>
public bool IsClosed
{
get
{
return this.isClosed;
}
}
/// <summary>
/// Gets whether or not the project is being built.
/// </summary>
public bool BuildInProgress {
get {
return buildInProcess;
}
}
/// <summary>
/// Gets or set the relative path to the folder containing the project ouput.
/// </summary>
public virtual string OutputBaseRelativePath
{
get
{
return this.outputBaseRelativePath;
}
set
{
if (Path.IsPathRooted(value))
{
// TODO: Maybe bring the exception back instead of automatically fixing this?
this.outputBaseRelativePath = CommonUtils.GetRelativeDirectoryPath(ProjectHome, value);
}
this.outputBaseRelativePath = value;
}
}
/// <summary>
/// Gets a collection of integer ids that maps to project item instances
/// </summary>
public EventSinkCollection ItemIdMap
{
get
{
return this.itemIdMap;
}
}
/// <summary>
/// Get the helper object that track document changes.
/// </summary>
public TrackDocumentsHelper Tracker
{
get
{
return this.tracker;
}
}
/// <summary>
/// Gets or sets the build logger.
/// </summary>
protected Microsoft.Build.Framework.ILogger BuildLogger
{
get
{
return this.buildLogger;
}
set
{
this.buildLogger = value;
this.useProvidedLogger = true;
}
}
/// <summary>
/// Gets the taskprovider.
/// </summary>
protected TaskProvider TaskProvider
{
get
{
return this.taskProvider;
}
}
/// <summary>
/// Gets or sets the project file name.
/// </summary>
protected string FileName
{
get
{
return this.filename;
}
set
{
this.filename = value;
}
}
/// <summary>
/// Gets the configuration provider.
/// </summary>
public ConfigProvider ConfigProvider
{
get
{
if (this.configProvider == null)
{
this.configProvider = CreateConfigProvider();
}
return this.configProvider;
}
}
/// <summary>
/// Gets or set whether items can be deleted for this project.
/// Enabling this feature can have the potential destructive behavior such as deleting files from disk.
/// </summary>
public bool CanProjectDeleteItems
{
get
{
return canProjectDeleteItems;
}
set
{
canProjectDeleteItems = value;
}
}
/// <summary>
/// Gets or sets event triggering flags.
/// </summary>
public EventTriggering EventTriggeringFlag
{
get
{
return this.eventTriggeringFlag;
}
set
{
this.eventTriggeringFlag = value;
}
}
/// <summary>
/// Defines the build project that has loaded the project file.
/// </summary>
public MSBuild.Project BuildProject
{
get
{
return this.buildProject;
}
set
{
SetBuildProject(value);
}
}
/// <summary>
/// Defines the build engine that is used to build the project file.
/// </summary>
public MSBuild.ProjectCollection BuildEngine
{
get
{
return this.buildEngine;
}
set
{
this.buildEngine = value;
}
}
/// <summary>
/// The public package implementation.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public ProjectPackage Package
{
get
{
return this.package;
}
set
{
this.package = value;
}
}
#endregion
#region ctor
protected ProjectNode(ProjectPackage package)
{
this.package = package;
this.Initialize();
}
#endregion
#region overridden methods
public override void DeleteFromStorage(string path) {
if (File.Exists(path)) {
File.Delete(path);
}
base.DeleteFromStorage(path);
}
/// <summary>
/// Sets the properties for the project node.
/// </summary>
/// <param name="propid">Identifier of the hierarchy property. For a list of propid values, <see cref="__VSHPROPID"/> </param>
/// <param name="value">The value to set. </param>
/// <returns>A success or failure value.</returns>
public override int SetProperty(int propid, object value)
{
__VSHPROPID id = (__VSHPROPID)propid;
switch (id)
{
case __VSHPROPID.VSHPROPID_ParentHierarchy:
parentHierarchy = (IVsHierarchy)value;
break;
case __VSHPROPID.VSHPROPID_ParentHierarchyItemid:
parentHierarchyItemId = (int)value;
break;
case __VSHPROPID.VSHPROPID_ShowProjInSolutionPage:
this.ShowProjectInSolutionPage = (bool)value;
return VSConstants.S_OK;
}
return base.SetProperty(propid, value);
}
/// <summary>
/// Renames the project node.
/// </summary>
/// <param name="label">The new name</param>
/// <returns>A success or failure value.</returns>
public override int SetEditLabel(string label)
{
// Validate the filename.
if (VsUtilities.IsFileNameInvalid(label))
{
throw new InvalidOperationException(String.Format(SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture), label));
}
else if (this.ProjectFolder.Length + label.Length + 1 > NativeMethods.MAX_PATH)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.PathTooLong, CultureInfo.CurrentUICulture), label));
}
// TODO: Take file extension into account?
string fileName = Path.GetFileNameWithoutExtension(label);
// Nothing to do if the name is the same
string oldFileName = Path.GetFileNameWithoutExtension(this.Url);
if (String.Equals(oldFileName, label, StringComparison.Ordinal))
{
return VSConstants.S_FALSE;
}
// Now check whether the original file is still there. It could have been renamed.
if (!File.Exists(this.Url))
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.FileOrFolderCannotBeFound, CultureInfo.CurrentUICulture), this.ProjectFile));
}
// Get the full file name and then rename the project file.
string newFile = Path.Combine(this.ProjectFolder, label);
string extension = Path.GetExtension(this.Url);
// Make sure it has the correct extension
if (!String.Equals(Path.GetExtension(newFile), extension, StringComparison.OrdinalIgnoreCase))
{
newFile += extension;
}
this.RenameProjectFile(newFile);
return VSConstants.S_OK;
}
/// <summary>
/// Gets the automation object for the project node.
/// </summary>
/// <returns>An instance of an EnvDTE.Project implementation object representing the automation object for the project.</returns>
public override object GetAutomationObject()
{
return new Automation.OAProject(this);
}
/// <summary>
/// Gets the properties of the project node.
/// </summary>
/// <param name="propId">The __VSHPROPID of the property.</param>
/// <returns>A property dependent value. See: <see cref="__VSHPROPID"/> for details.</returns>
public override object GetProperty(int propId)
{
switch ((__VSHPROPID)propId)
{
case __VSHPROPID.VSHPROPID_ConfigurationProvider:
return this.ConfigProvider;
case __VSHPROPID.VSHPROPID_ProjectName:
return this.Caption;
case __VSHPROPID.VSHPROPID_ProjectDir:
return this.ProjectFolder;
case __VSHPROPID.VSHPROPID_TypeName:
return this.ProjectType;
case __VSHPROPID.VSHPROPID_ShowProjInSolutionPage:
return this.ShowProjectInSolutionPage;
case __VSHPROPID.VSHPROPID_ExpandByDefault:
return true;
// Use the same icon as if the folder was closed
case __VSHPROPID.VSHPROPID_OpenFolderIconIndex:
return GetProperty((int)__VSHPROPID.VSHPROPID_IconIndex);
case __VSHPROPID.VSHPROPID_ParentHierarchyItemid:
if (parentHierarchy != null) {
return (IntPtr)parentHierarchyItemId; // VS requires VT_I4 | VT_INT_PTR
}
break;
case __VSHPROPID.VSHPROPID_ParentHierarchy:
return parentHierarchy;
}
switch ((__VSHPROPID2)propId)
{
case __VSHPROPID2.VSHPROPID_SupportsProjectDesigner:
return this.SupportsProjectDesigner;
case __VSHPROPID2.VSHPROPID_PropertyPagesCLSIDList:
return VsUtilities.CreateSemicolonDelimitedListOfStringFromGuids(this.GetConfigurationIndependentPropertyPages());
case __VSHPROPID2.VSHPROPID_CfgPropertyPagesCLSIDList:
return VsUtilities.CreateSemicolonDelimitedListOfStringFromGuids(this.GetConfigurationDependentPropertyPages());
case __VSHPROPID2.VSHPROPID_PriorityPropertyPagesCLSIDList:
return VsUtilities.CreateSemicolonDelimitedListOfStringFromGuids(this.GetPriorityProjectDesignerPages());
case __VSHPROPID2.VSHPROPID_Container:
return true;
default:
break;
}
switch ((__VSHPROPID4)propId)
{
case __VSHPROPID4.VSHPROPID_TargetFrameworkMoniker:
return this.TargetFrameworkMoniker.FullName;
}
#if DEV11_OR_LATER && FALSE
switch ((__VSHPROPID5)propId)
{
case __VSHPROPID5.VSHPROPID_ReferenceManagerUser:
return Marshal.GetIUnknownForObject(this.GetReferenceManagerUser());
}
#endif
return base.GetProperty(propId);
}
/// <summary>
/// Gets the GUID value of the node.
/// </summary>
/// <param name="propid">A __VSHPROPID or __VSHPROPID2 value of the guid property</param>
/// <param name="guid">The guid to return for the property.</param>
/// <returns>A success or failure value.</returns>
public override int GetGuidProperty(int propid, out Guid guid)
{
guid = Guid.Empty;
if ((__VSHPROPID)propid == __VSHPROPID.VSHPROPID_ProjectIDGuid)
{
guid = this.ProjectIDGuid;
}
else if (propid == (int)__VSHPROPID.VSHPROPID_CmdUIGuid)
{
guid = this.ProjectGuid;
}
else if ((__VSHPROPID2)propid == __VSHPROPID2.VSHPROPID_ProjectDesignerEditor && this.SupportsProjectDesigner)
{
guid = this.ProjectDesignerEditor;
}
else
{
base.GetGuidProperty(propid, out guid);
}
if (guid.CompareTo(Guid.Empty) == 0)
{
return VSConstants.DISP_E_MEMBERNOTFOUND;
}
return VSConstants.S_OK;
}
/// <summary>
/// Sets Guid properties for the project node.
/// </summary>
/// <param name="propid">A __VSHPROPID or __VSHPROPID2 value of the guid property</param>
/// <param name="guid">The guid value to set.</param>
/// <returns>A success or failure value.</returns>
public override int SetGuidProperty(int propid, ref Guid guid)
{
switch ((__VSHPROPID)propid)
{
case __VSHPROPID.VSHPROPID_ProjectIDGuid:
this.ProjectIDGuid = guid;
return VSConstants.S_OK;
}
return VSConstants.DISP_E_MEMBERNOTFOUND;
}
/// <summary>
/// Removes items from the hierarchy.
/// </summary>
/// <devdoc>Project overwrites this.</devdoc>
public override void Remove(bool removeFromStorage)
{
// the project will not be deleted from disk, just removed
if (removeFromStorage)
{
return;
}
// Remove the entire project from the solution
IVsSolution solution = this.Site.GetService(typeof(SVsSolution)) as IVsSolution;
uint iOption = 1; // SLNSAVEOPT_PromptSave
ErrorHandler.ThrowOnFailure(solution.CloseSolutionElement(iOption, this.GetOuterInterface<IVsHierarchy>(), 0));
}
/// <summary>
/// Gets the moniker for the project node. That is the full path of the project file.
/// </summary>
/// <returns>The moniker for the project file.</returns>
public override string GetMkDocument()
{
Debug.Assert(!String.IsNullOrEmpty(this.filename));
Debug.Assert(this.BaseURI != null && !String.IsNullOrEmpty(this.BaseURI.AbsoluteUrl));
return CommonUtils.GetAbsoluteFilePath(this.BaseURI.AbsoluteUrl, this.filename);
}
/// <summary>
/// Disposes the project node object.
/// </summary>
/// <param name="disposing">Flag determining ehether it was deterministic or non deterministic clean up.</param>
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
try
{
try
{
this.UnRegisterProject();
}
finally
{
try
{
this.RegisterClipboardNotifications(false);
}
finally
{
try
{
if (this.site != null)
{
this.site.Dispose();
}
}
finally
{
this.buildEngine = null;
}
}
}
if (this.buildProject != null)
{
this.buildProject.ProjectCollection.UnloadProject(this.buildProject);
this.buildProject.ProjectCollection.UnloadProject(this.buildProject.Xml);
this.SetBuildProject(null);
}
if (null != imageHandler)
{
imageHandler.Close();
imageHandler = null;
}
}
finally
{
base.Dispose(disposing);
this.isDisposed = true;
}
}
/// <summary>
/// Handles command status on the project node. If a command cannot be handled then the base should be called.
/// </summary>
/// <param name="cmdGroup">A unique identifier of the command group. The pguidCmdGroup parameter can be NULL to specify the standard group.</param>
/// <param name="cmd">The command to query status for.</param>
/// <param name="pCmdText">Pointer to an OLECMDTEXT structure in which to return the name and/or status information of a single command. Can be NULL to indicate that the caller does not require this information.</param>
/// <param name="result">An out parameter specifying the QueryStatusResult of the command.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns>
public override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result)
{
if (cmdGroup == VsMenus.guidStandardCommandSet97)
{
switch ((VsCommands)cmd)
{
case VsCommands.Copy:
case VsCommands.Paste:
case VsCommands.Cut:
case VsCommands.Rename:
case VsCommands.Exit:
case VsCommands.ProjectSettings:
case VsCommands.UnloadProject:
result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
return VSConstants.S_OK;
case VsCommands.CancelBuild:
result |= QueryStatusResult.SUPPORTED;
if (this.buildInProcess)
result |= QueryStatusResult.ENABLED;
else
result |= QueryStatusResult.INVISIBLE;
return VSConstants.S_OK;
case VsCommands.NewFolder:
result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
return VSConstants.S_OK;
case VsCommands.SetStartupProject:
result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
return VSConstants.S_OK;
}
}
else if (cmdGroup == VsMenus.guidStandardCommandSet2K)
{
switch ((VsCommands2K)cmd)
{
case VsCommands2K.ADDREFERENCE:
if (GetReferenceContainer() != null) {
result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
} else {
result |= QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE;
}
return VSConstants.S_OK;
case VsCommands2K.EXCLUDEFROMPROJECT:
result |= QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE;
return VSConstants.S_OK;
}
}
else
{
return (int)OleConstants.OLECMDERR_E_UNKNOWNGROUP;
}
return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result);
}
/// <summary>
/// Handles command execution.
/// </summary>
/// <param name="cmdGroup">Unique identifier of the command group</param>
/// <param name="cmd">The command to be executed.</param>
/// <param name="nCmdexecopt">Values describe how the object should execute the command.</param>
/// <param name="pvaIn">Pointer to a VARIANTARG structure containing input arguments. Can be NULL</param>
/// <param name="pvaOut">VARIANTARG structure to receive command output. Can be NULL.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns>
public override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
{
if (cmdGroup == VsMenus.guidStandardCommandSet97)
{
switch ((VsCommands)cmd)
{
case VsCommands.UnloadProject:
return this.UnloadProject();
case VsCommands.CleanSel:
case VsCommands.CleanCtx:
return this.CleanProject();
}
}
return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut);
}
protected override int StartDebug()
{
return VSConstants.E_NOTIMPL;
}
/// <summary>
/// Get the boolean value for the deletion of a project item
/// </summary>
/// <param name="deleteOperation">A flag that specifies the type of delete operation (delete from storage or remove from project)</param>
/// <returns>true if item can be deleted from project</returns>
public override bool CanDeleteItem(__VSDELETEITEMOPERATION deleteOperation)
{
if (deleteOperation == __VSDELETEITEMOPERATION.DELITEMOP_RemoveFromProject)
{
return true;
}
return false;
}
/// <summary>
/// Returns a specific Document manager to handle opening and closing of the Project(Application) Designer if projectdesigner is supported.
/// </summary>
/// <returns>Document manager object</returns>
public override DocumentManager GetDocumentManager()
{
if (this.SupportsProjectDesigner)
{
return new ProjectDesignerDocumentManager(this);
}
return null;
}
#endregion
#region virtual methods
public virtual string GetExistingFilesFilter()
{
return SR.GetString(SR.AllFilesFilter);
}
/// <summary>
/// Creates a reference node for the given file returning the node, or returns null
/// if the file doesn't represent a valid file which can be referenced.
/// </summary>
public virtual ReferenceNode CreateReferenceNodeForFile(string filename)
{
#if FALSE
return new ComReferenceNode(this.ProjectMgr, selectorData);
#endif
return null;
}
/// <summary>
/// Executes a wizard.
/// </summary>
/// <param name="parentNode">The node to which the wizard should add item(s).</param>
/// <param name="itemName">The name of the file that the user typed in.</param>
/// <param name="wizardToRun">The name of the wizard to run.</param>
/// <param name="dlgOwner">The owner of the dialog box.</param>
/// <returns>A VSADDRESULT enum value describing success or failure.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dlg")]
public virtual VSADDRESULT RunWizard(HierarchyNode parentNode, string itemName, string wizardToRun, IntPtr dlgOwner)
{
Debug.Assert(!String.IsNullOrEmpty(itemName), "The Add item dialog was passing in a null or empty item to be added to the hierrachy.");
Debug.Assert(!String.IsNullOrEmpty(this.ProjectHome), "ProjectHome is not specified for this project.");
VsUtilities.ArgumentNotNull("parentNode", parentNode);
VsUtilities.ArgumentNotNullOrEmpty("itemName", itemName);
// We just validate for length, since we assume other validation has been performed by the dlgOwner.
if (CommonUtils.GetAbsoluteFilePath(this.ProjectHome, itemName).Length >= NativeMethods.MAX_PATH)
{
string errorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.PathTooLong, CultureInfo.CurrentUICulture), itemName);
if (!VsUtilities.IsInAutomationFunction(this.Site))
{
string title = null;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
VsShellUtilities.ShowMessageBox(this.Site, title, errorMessage, icon, buttons, defaultButton);
return VSADDRESULT.ADDRESULT_Failure;
}
else
{
throw new InvalidOperationException(errorMessage);
}
}
// Build up the ContextParams safearray
// [0] = Wizard type guid (bstr)
// [1] = Project name (bstr)
// [2] = ProjectItems collection (bstr)
// [3] = Local Directory (bstr)
// [4] = Filename the user typed (bstr)
// [5] = Product install Directory (bstr)
// [6] = Run silent (bool)
object[] contextParams = new object[7];
contextParams[0] = EnvDTE.Constants.vsWizardAddItem;
contextParams[1] = this.Caption;
object automationObject = parentNode.GetAutomationObject();
if (automationObject is EnvDTE.Project)
{
EnvDTE.Project project = (EnvDTE.Project)automationObject;
contextParams[2] = project.ProjectItems;
}
else
{
// This would normally be a folder unless it is an item with subitems
EnvDTE.ProjectItem item = (EnvDTE.ProjectItem)automationObject;
contextParams[2] = item.ProjectItems;
}
contextParams[3] = this.ProjectHome;
contextParams[4] = itemName;
object objInstallationDir = null;
IVsShell shell = (IVsShell)this.GetService(typeof(IVsShell));
ErrorHandler.ThrowOnFailure(shell.GetProperty((int)__VSSPROPID.VSSPROPID_InstallDirectory, out objInstallationDir));
string installDir = CommonUtils.NormalizeDirectoryPath((string)objInstallationDir);
contextParams[5] = installDir;
contextParams[6] = true;
IVsExtensibility3 ivsExtensibility = this.GetService(typeof(IVsExtensibility)) as IVsExtensibility3;
Debug.Assert(ivsExtensibility != null, "Failed to get IVsExtensibility3 service");
if (ivsExtensibility == null)
{
return VSADDRESULT.ADDRESULT_Failure;
}
// Determine if we have the trust to run this wizard.
IVsDetermineWizardTrust wizardTrust = this.GetService(typeof(SVsDetermineWizardTrust)) as IVsDetermineWizardTrust;
if (wizardTrust != null)
{
Guid guidProjectAdding = Guid.Empty;
ErrorHandler.ThrowOnFailure(wizardTrust.OnWizardInitiated(wizardToRun, ref guidProjectAdding));
}
int wizResultAsInt;
try
{
Array contextParamsAsArray = contextParams;
int result = ivsExtensibility.RunWizardFile(wizardToRun, (int)dlgOwner, ref contextParamsAsArray, out wizResultAsInt);
if (!ErrorHandler.Succeeded(result) && result != VSConstants.OLE_E_PROMPTSAVECANCELLED)
{
ErrorHandler.ThrowOnFailure(result);
}
}
finally
{
if (wizardTrust != null)
{
ErrorHandler.ThrowOnFailure(wizardTrust.OnWizardCompleted());
}
}
EnvDTE.wizardResult wizardResult = (EnvDTE.wizardResult)wizResultAsInt;
switch (wizardResult)
{
default:
return VSADDRESULT.ADDRESULT_Cancel;
case wizardResult.wizardResultSuccess:
return VSADDRESULT.ADDRESULT_Success;
case wizardResult.wizardResultFailure:
return VSADDRESULT.ADDRESULT_Failure;
}
}
/// <summary>
/// This overrides the base class method to show the VS 2005 style Add reference dialog. The ProjectNode implementation
/// shows the VS 2003 style Add Reference dialog.
/// </summary>
/// <returns>S_OK if succeeded. Failure other wise</returns>
public virtual int AddProjectReference()
{
#if DEV11_OR_LATER
IVsReferenceManager manager = GetService(typeof(SVsReferenceManager)) as IVsReferenceManager;
try
{
// call the container to open the add reference dialog.
if (manager != null)
{
// Let the project know not to show itself in the Add Project Reference Dialog page
ShowProjectInSolutionPage = false;
IReferenceContainer referenceContainer = this.GetReferenceContainer();
//var context = manager.CreateProviderContext(((ReferenceContainerNode)GetReferenceContainer()).ProviderGuid);
Type type = manager.GetType();
//var context = manager.CreateProviderContext(((ReferenceContainerNode)this.GetReferenceContainer()).ProviderGuid);
IVsAssemblyReferenceProviderContext assemblyProvider = (IVsAssemblyReferenceProviderContext)manager.CreateProviderContext(new Guid("9A341D95-5A64-11D3-BFF9-00C04F990235"));
assemblyProvider.TargetFrameworkMoniker = this.TargetFrameworkMoniker.FullName;
assemblyProvider.Tabs = (uint)__VSASSEMBLYPROVIDERTAB.TAB_ASSEMBLY_ALL;
assemblyProvider.SupportsRetargeting = true;
var frameworkRegKey = ToolLocationHelper.GetDotNetFrameworkRootRegistryKey(TargetDotNetFrameworkVersion.Version45);
//They don't want the hive name.
if (frameworkRegKey.StartsWith("HKEY_LOCAL_MACHINE\\"))
frameworkRegKey = frameworkRegKey.Substring("HKEY_LOCAL_MACHINE\\".Length);
var searchDirectories = ToolLocationHelper.GetAssemblyFoldersExInfo(frameworkRegKey, ToolLocationHelper.GetDotNetFrameworkVersionFolderPrefix(TargetDotNetFrameworkVersion.Version45), "AssemblyFoldersEx", null, null, System.Reflection.ProcessorArchitecture.MSIL);
assemblyProvider.AssemblySearchPaths = string.Join(";", searchDirectories.Select((x) => x.DirectoryPath).Where((x) => !string.IsNullOrEmpty(x)));
IVsProjectReferenceProviderContext projectProvider = (IVsProjectReferenceProviderContext)manager.CreateProviderContext(new Guid("51ECA6BD-5AE4-43F0-AA76-DD0A7B08F40C"));
projectProvider.CurrentProject = this;
IVsFileReferenceProviderContext fileReferenceProvider = (IVsFileReferenceProviderContext)manager.CreateProviderContext(new Guid("7B069159-FF02-4752-93E8-96B3CADF441A"));
fileReferenceProvider.DefaultBrowseLocation = Path.GetDirectoryName(ProjectHome);
fileReferenceProvider.BrowseFilter = "Component Files (*.dll;*.exe)|*.dll;*.exe|All Files (*.*)|*.*";
/*IVsPlatformReferenceProviderContext platformReferenceProvider = (IVsPlatformReferenceProviderContext)manager.CreateProviderContext(new Guid("97324595-E3F9-4AA8-85B7-DC941E812152"));
platformReferenceProvider.TargetFrameworkMoniker = this.TargetFrameworkMoniker.FullName;
//platformReferenceProvider.ExpandSDKContents = true;
platformReferenceProvider.VisualStudioVersion = "11.0";*/
/*var frameworkService2 = this.GetService(typeof(SVsFrameworkMultiTargeting)) as IVsFrameworkMultiTargeting2;
if (frameworkService2 != null)
{
List<string> sdkPaths = new List<string>();
Array sdks = frameworkService2.GetSDKRootFolders();
foreach (string path in sdks)
{
sdkPaths.AddRange(frameworkService2.GetSDKReferences(path).Cast<string>());
}
platformReferenceProvider.SDKDirectoryRoot = sdkPaths.FirstOrDefault();
platformReferenceProvider.AssemblySearchPaths = string.Join(";", sdkPaths);
}*/
foreach (ReferenceNode reference in referenceContainer.EnumReferences())
{
if (reference is ProjectReferenceNode)
{
ProjectReferenceNode projectReference = (ProjectReferenceNode)reference;
//The IVsProjectReferenceProviderContext can't handle normal IVsProjectReferences, he needs his references as projectIdentities.
var identitiy = new ProjectIdentity(projectReference.ReferencedProjectGuid.ToString("B"), projectReference.Name, projectReference.FullPath, projectReference.UniqueName, this.ProjectMgr);
identitiy.AlreadyReferenced = true;
projectProvider.AddReference(identitiy);
}
else if (reference is AssemblyReferenceNode)
{
AssemblyReferenceNode assemblyReference = (AssemblyReferenceNode)reference;
var fileIdentity = new FileIdentity(assemblyReference.FullPath);
fileIdentity.AlreadyReferenced = true;
fileReferenceProvider.AddReference(fileIdentity);
//TODO: Feststellen ob die Assembly als Framework-Assembly vorhanden ist oder nicht.
var assemblyIdentity = new AssemblyIdentity(assemblyReference.AssemblyName, assemblyReference.FullPath, assemblyReference.IsFrameworkAssembly, this.TargetFrameworkMoniker.FullName);
assemblyProvider.AddReference(assemblyIdentity);
}
}
var contexts = new IVsReferenceProviderContext[] {
assemblyProvider,
projectProvider,
fileReferenceProvider,
//platformReferenceProvider,
};
var userReferenceManager = new ReferenceManagerUser(contexts, this.GetReferenceContainer());
string location = this.GetType().Assembly.Location;
// call the container to open the add reference dialog.
manager.ShowReferenceManager(
userReferenceManager,
SR.GetString(SR.AddReferenceDialogTitle),
"VS.AddReference",
assemblyProvider.ProviderGuid,
true);
}
}
catch (COMException e)
{
Trace.WriteLine("Exception : " + e.Message);
return e.ErrorCode;
}
finally
{
// Let the project know it can show itself in the Add Project Reference Dialog page
ShowProjectInSolutionPage = true;
}
#else
IVsComponentSelectorDlg4 componentDialog;
Guid guidEmpty = Guid.Empty;
VSCOMPONENTSELECTORTABINIT[] tabInit = new VSCOMPONENTSELECTORTABINIT[4];
string strBrowseLocations = Path.GetDirectoryName(ProjectHome);
//Add the Project page
tabInit[0].dwSize = (uint)Marshal.SizeOf(typeof(VSCOMPONENTSELECTORTABINIT));
// Tell the Add Reference dialog to call hierarchies GetProperty with the following
// propID to enable filtering out ourself from the Project to Project reference
tabInit[0].varTabInitInfo = (int)__VSHPROPID.VSHPROPID_ShowProjInSolutionPage;
tabInit[0].guidTab = VSConstants.GUID_SolutionPage;
// Add the Browse for file page
tabInit[1].dwSize = (uint)Marshal.SizeOf(typeof(VSCOMPONENTSELECTORTABINIT));
tabInit[1].guidTab = VSConstants.GUID_COMPlusPage;
tabInit[1].varTabInitInfo = 0;
// Add the Browse for file page
tabInit[2].dwSize = (uint)Marshal.SizeOf(typeof(VSCOMPONENTSELECTORTABINIT));
tabInit[2].guidTab = VSConstants.GUID_BrowseFilePage;
tabInit[2].varTabInitInfo = 0;
// Add the WebPI page
tabInit[3].dwSize = (uint)Marshal.SizeOf(typeof(VSCOMPONENTSELECTORTABINIT));
tabInit[3].guidTab = typeof(WebPiComponentPickerControl).GUID;
tabInit[3].varTabInitInfo = 0;
uint pX = 0, pY = 0;
componentDialog = GetService(typeof(SVsComponentSelectorDlg)) as IVsComponentSelectorDlg4;
try
{
// call the container to open the add reference dialog.
if (componentDialog != null)
{
// Let the project know not to show itself in the Add Project Reference Dialog page
ShowProjectInSolutionPage = false;
// call the container to open the add reference dialog.
ErrorHandler.ThrowOnFailure(componentDialog.ComponentSelectorDlg5(
(System.UInt32)(__VSCOMPSELFLAGS.VSCOMSEL_MultiSelectMode | __VSCOMPSELFLAGS.VSCOMSEL_IgnoreMachineName),
(IVsComponentUser)this,
0,
null,
DynamicProjectSR.GetString(Microsoft.VisualStudio.Project.SR.AddReferenceDialogTitle), // Title
"VS.AddReference", // Help topic
ref pX,
ref pY,
(uint)tabInit.Length,
tabInit,
ref guidEmpty,
AddReferenceExtensions,
ref strBrowseLocations,
this.TargetFrameworkMoniker.FullName));
}
}
catch (COMException e)
{
Trace.WriteLine("Exception : " + e.Message);
return e.ErrorCode;
}
finally
{
// Let the project know it can show itself in the Add Project Reference Dialog page
ShowProjectInSolutionPage = true;
}
#endif
return VSConstants.S_OK;
}
protected virtual string AddReferenceExtensions
{
get
{
return "Dynamic Link Libraries (*.dll)\0*.dll\0All Files (*.*)\0*.*\0";
}
}
/// <summary>
/// Returns the Compiler associated to the project
/// </summary>
/// <returns>Null</returns>
public virtual ICodeCompiler GetCompiler()
{
return null;
}
/// <summary>
/// Override this method if you have your own project specific
/// subclass of ProjectOptions
/// </summary>
/// <returns>This method returns a new instance of the ProjectOptions base class.</returns>
public virtual CompilerParameters CreateProjectOptions()
{
return new CompilerParameters();
}
/// <summary>
/// Loads a project file. Called from the factory CreateProject to load the project.
/// </summary>
/// <param name="fileName">File name of the project that will be created. </param>
/// <param name="location">Location where the project will be created.</param>
/// <param name="name">If applicable, the name of the template to use when cloning a new project.</param>
/// <param name="flags">Set of flag values taken from the VSCREATEPROJFLAGS enumeration.</param>
/// <param name="iidProject">Identifier of the interface that the caller wants returned. </param>
/// <param name="canceled">An out parameter specifying if the project creation was canceled</param>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "iid")]
public virtual void Load(string fileName, string location, string name, uint flags, ref Guid iidProject, out int canceled)
{
using (new DebugTimer("ProjectLoad"))
{
try
{
this.disableQueryEdit = true;
// set up public members and icons
canceled = 0;
this.ProjectMgr = this;
if ((flags & (uint)__VSCREATEPROJFLAGS.CPF_CLONEFILE) == (uint)__VSCREATEPROJFLAGS.CPF_CLONEFILE)
{
// we need to generate a new guid for the project
this.projectIdGuid = Guid.NewGuid();
}
else
{
this.SetProjectGuidFromProjectFile();
}
// This is almost a No op if the engine has already been instantiated in the factory.
this.buildEngine = VsUtilities.InitializeMsBuildEngine(this.buildEngine, this.Site);
// based on the passed in flags, this either reloads/loads a project, or tries to create a new one
// now we create a new project... we do that by loading the template and then saving under a new name
// we also need to copy all the associated files with it.
if ((flags & (uint)__VSCREATEPROJFLAGS.CPF_CLONEFILE) == (uint)__VSCREATEPROJFLAGS.CPF_CLONEFILE)
{
Debug.Assert(!String.IsNullOrEmpty(fileName) && File.Exists(fileName), "Invalid filename passed to load the project. A valid filename is expected");
// This should be a very fast operation if the build project is already initialized by the Factory.
SetBuildProject(VsUtilities.ReinitializeMsBuildProject(this.buildEngine, fileName, this.buildProject));
// Compute the file name
// We try to solve two problems here. When input comes from a wizzard in case of zipped based projects
// the parameters are different.
// In that case the filename has the new filename in a temporay path.
// First get the extension from the template.
// Then get the filename from the name.
// Then create the new full path of the project.
string extension = Path.GetExtension(fileName);
string tempName = String.Empty;
// We have to be sure that we are not going to lose data here. If the project name is a.b.c then for a project that was based on a zipped template(the wizard calls us) GetFileNameWithoutExtension will suppress "c".
// We are going to check if the parameter "name" is extension based and the extension is the same as the one from the "filename" parameter.
string tempExtension = Path.GetExtension(name);
if (!String.IsNullOrEmpty(tempExtension))
{
bool isSameExtension = (String.Equals(tempExtension, extension, StringComparison.OrdinalIgnoreCase));
if (isSameExtension)
{
tempName = Path.GetFileNameWithoutExtension(name);
}
// If the tempExtension is not the same as the extension that the project name comes from then assume that the project name is a dotted name.
else
{
tempName = Path.GetFileName(name);
}
}
else
{
tempName = Path.GetFileName(name);
}
Debug.Assert(!String.IsNullOrEmpty(tempName), "Could not compute project name");
string tempProjectFileName = tempName + extension;
this.filename = CommonUtils.GetAbsoluteFilePath(location, tempProjectFileName);
// Initialize the common project properties.
this.InitializeProjectProperties();
ErrorHandler.ThrowOnFailure(this.Save(this.filename, 1, 0));
string unresolvedProjectHome = this.GetProjectProperty(CommonConstants.ProjectHome);
string basePath = CommonUtils.GetAbsoluteDirectoryPath(Path.GetDirectoryName(fileName), unresolvedProjectHome);
string baseLocation = CommonUtils.GetAbsoluteDirectoryPath(location, unresolvedProjectHome);
if (!CommonUtils.IsSameDirectory(basePath, baseLocation))
{
// now we do have the project file saved. we need to create embedded files.
foreach (MSBuild.ProjectItem item in this.BuildProject.Items)
{
// Ignore the item if it is a reference or folder
if (this.FilterItemTypeToBeAddedToHierarchy(item.ItemType))
{
continue;
}
// MSBuilds tasks/targets can create items (such as object files),
// such items are not part of the project per say, and should not be displayed.
// so ignore those items.
if (!this.IsItemTypeFileType(item.ItemType))
{
continue;
}
string strRelFilePath = item.EvaluatedInclude;
string strPathToFile;
string newFileName;
// taking the base name from the project template + the relative pathname,
// and you get the filename
strPathToFile = CommonUtils.GetAbsoluteFilePath(basePath, strRelFilePath);
// the new path should be the base dir of the new project (location) + the rel path of the file
newFileName = CommonUtils.GetAbsoluteFilePath(baseLocation, strRelFilePath);
// now the copy file
AddFileFromTemplate(strPathToFile, newFileName);
}
}
}
else
{
this.filename = fileName;
}
// now reload to fix up references
this.Reload();
}
finally
{
this.disableQueryEdit = false;
}
}
}
/// <summary>
/// Called to add a file to the project from a template.
/// Override to do it yourself if you want to customize the file
/// </summary>
/// <param name="source">Full path of template file</param>
/// <param name="target">Full path of file once added to the project</param>
public virtual void AddFileFromTemplate(string source, string target)
{
VsUtilities.ArgumentNotNullOrEmpty("source", source);
VsUtilities.ArgumentNotNullOrEmpty("target", target);
try
{
string directory = Path.GetDirectoryName(target);
if (!String.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
FileInfo fiOrg = new FileInfo(source);
FileInfo fiNew = fiOrg.CopyTo(target, true);
fiNew.Attributes = FileAttributes.Normal; // remove any read only attributes.
}
catch (IOException e)
{
Trace.WriteLine("Exception : " + e.Message);
}
catch (UnauthorizedAccessException e)
{
Trace.WriteLine("Exception : " + e.Message);
}
catch (ArgumentException e)
{
Trace.WriteLine("Exception : " + e.Message);
}
catch (NotSupportedException e)
{
Trace.WriteLine("Exception : " + e.Message);
}
}
/// <summary>
/// Called when the project opens an editor window for the given file
/// </summary>
public virtual void OnOpenItem(string fullPathToSourceFile)
{
}
/// <summary>
/// This add methos adds the "key" item to the hierarchy, potentially adding other subitems in the process
/// This method may recurse if the parent is an other subitem
///
/// </summary>
/// <param name="subitems">List of subitems not yet added to the hierarchy</param>
/// <param name="key">Key to retrieve the target item from the subitems list</param>
/// <returns>Newly added node</returns>
/// <remarks>If the parent node was found we add the dependent item to it otherwise we add the item ignoring the "DependentUpon" metatdata</remarks>
protected virtual HierarchyNode AddDependentFileNode(IDictionary<String, MSBuild.ProjectItem> subitems, string key)
{
VsUtilities.ArgumentNotNull("subitems", subitems);
MSBuild.ProjectItem item = subitems[key];
subitems.Remove(key);
HierarchyNode newNode;
HierarchyNode parent = null;
string dependentOf = item.GetMetadataValue(ProjectFileConstants.DependentUpon);
Debug.Assert(String.Compare(dependentOf, key, StringComparison.OrdinalIgnoreCase) != 0, "File dependent upon itself is not valid. Ignoring the DependentUpon metadata");
if (subitems.ContainsKey(dependentOf))
{
// The parent item is an other subitem, so recurse into this method to add the parent first
parent = AddDependentFileNode(subitems, dependentOf);
}
else
{
// See if the parent node already exist in the hierarchy
uint parentItemID;
string path = CommonUtils.GetAbsoluteFilePath(this.ProjectHome, dependentOf);
if (ErrorHandler.Succeeded(this.ParseCanonicalName(path, out parentItemID)) &&
parentItemID != 0)
parent = this.NodeFromItemId(parentItemID);
Debug.Assert(parent != null, "File dependent upon a non existing item or circular dependency. Ignoring the DependentUpon metadata");
}
// If the parent node was found we add the dependent item to it otherwise we add the item ignoring the "DependentUpon" metatdata
if (parent != null)
newNode = this.AddDependentFileNodeToNode(item, parent);
else
newNode = this.AddIndependentFileNode(item, GetItemParentNode(item));
return newNode;
}
/// <summary>
/// Do the build by invoking msbuild
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "vsopts")]
public virtual void BuildAsync(uint vsopts, string config, string platform, IVsOutputWindowPane output, string target, IEnumerable<string> files, Action<MSBuildResult, string> uiThreadCallback)
{
}
public virtual int StopBuild(uint vsopts, bool sync)
{
if (sync)
{
EndBuild(vsopts, null, true, UIThread.Instance.IsUIThread);
}
else
{
System.Threading.Tasks.Task.Run(() => { EndBuild(vsopts, null, true, UIThread.Instance.IsUIThread); });
}
return VSConstants.S_OK;
}
/// <summary>
/// Return the value of a project property
/// </summary>
/// <param name="propertyName">Name of the property to get</param>
/// <param name="resetCache">True to avoid using the cache</param>
/// <returns>null if property does not exist, otherwise value of the property</returns>
public virtual string GetProjectProperty(string propertyName, bool resetCache)
{
MSBuildExecution.ProjectPropertyInstance property = GetMsBuildProperty(propertyName, resetCache);
if (property == null)
return null;
return property.EvaluatedValue;
}
/// <summary>
/// Return the value of a project property in it's unevalauted form.
///
/// New in 1.5.
/// </summary>
/// <param name="propertyName">Name of the property to get</param>
public virtual string GetUnevaluatedProperty(string propertyName)
{
var res = this.buildProject.GetProperty(propertyName);
if (res != null)
{
return res.UnevaluatedValue;
}
return null;
}
/// <summary>
/// Set value of project property
/// </summary>
/// <param name="propertyName">Name of property</param>
/// <param name="propertyValue">Value of property</param>
public virtual void SetProjectProperty(string propertyName, string propertyValue)
{
VsUtilities.ArgumentNotNull("propertyName", propertyName);
string oldValue = null;
ProjectPropertyInstance oldProp = GetMsBuildProperty(propertyName, true);
if (oldProp != null)
oldValue = oldProp.EvaluatedValue;
if (propertyValue == null)
{
// if property already null, do nothing
if (oldValue == null)
return;
// otherwise, set it to empty
propertyValue = String.Empty;
}
// Only do the work if this is different to what we had before
if (String.Compare(oldValue, propertyValue, StringComparison.Ordinal) != 0)
{
// Check out the project file.
if (!this.QueryEditProjectFile(false))
{
throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED);
}
var newProp = this.buildProject.SetProperty(propertyName, propertyValue);
RaiseProjectPropertyChanged(propertyName, oldValue, propertyValue);
// property cache will need to be updated
this.currentConfig = null;
this.SetProjectFileDirty(true);
}
return;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
public virtual CompilerParameters GetProjectOptions(string config, string platform)
{
// This needs to be commented out because if you build for Debug the properties from the Debug
// config are cached. When you change configurations the old props are still cached, and
// building for release the properties from the Debug config are used. This may not be the best
// fix as every time you get properties the objects are reloaded, so for perf it is bad, but
// for making it work it is necessary (reload props when a config is changed?).
////if(this.options != null)
//// return this.options;
CompilerParameters options = CreateProjectOptions();
if (config == null)
return options;
options.GenerateExecutable = true;
this.SetConfiguration(config, platform);
string outputPath = this.GetOutputPath(this.currentConfig);
if (!String.IsNullOrEmpty(outputPath))
{
// absolutize relative to project folder location
outputPath = CommonUtils.GetAbsoluteDirectoryPath(this.ProjectHome, outputPath);
}
// Set some default values
options.OutputAssembly = outputPath + this.Caption + ".exe";
options.OutputAssembly = outputPath + this.GetAssemblyName(config, platform);
string outputtype = GetProjectProperty(ProjectFileConstants.OutputType, false);
if (!string.IsNullOrEmpty(outputtype))
{
outputtype = outputtype.ToLower(CultureInfo.InvariantCulture);
}
options.MainClass = GetProjectProperty("StartupObject", false);
// other settings from CSharp we may want to adopt at some point...
// AssemblyKeyContainerName = "" //This is the key file used to sign the interop assembly generated when importing a com object via add reference
// AssemblyOriginatorKeyFile = ""
// DelaySign = "false"
// DefaultClientScript = "JScript"
// DefaultHTMLPageLayout = "Grid"
// DefaultTargetSchema = "IE50"
// PreBuildEvent = ""
// PostBuildEvent = ""
// RunPostBuildEvent = "OnBuildSuccess"
if (GetBoolAttr(this.currentConfig, "DebugSymbols"))
{
options.IncludeDebugInformation = true;
}
if (GetBoolAttr(this.currentConfig, "RegisterForComInterop"))
{
}
if (GetBoolAttr(this.currentConfig, "RemoveIntegerChecks"))
{
}
if (GetBoolAttr(this.currentConfig, "TreatWarningsAsErrors"))
{
options.TreatWarningsAsErrors = true;
}
if (GetProjectProperty("WarningLevel", false) != null)
{
try
{
options.WarningLevel = Int32.Parse(GetProjectProperty("WarningLevel", false), CultureInfo.InvariantCulture);
}
catch (ArgumentNullException e)
{
Trace.WriteLine("Exception : " + e.Message);
}
catch (ArgumentException e)
{
Trace.WriteLine("Exception : " + e.Message);
}
catch (FormatException e)
{
Trace.WriteLine("Exception : " + e.Message);
}
catch (OverflowException e)
{
Trace.WriteLine("Exception : " + e.Message);
}
}
return options;
}
private string GetOutputPath(MSBuildExecution.ProjectInstance properties)
{
this.currentConfig = properties;
string outputPath = GetProjectProperty("OutputPath");
return outputPath;
}
private bool GetBoolAttr(MSBuildExecution.ProjectInstance properties, string name)
{
this.currentConfig = properties;
string s = GetProjectProperty(name);
return (s != null && s.ToUpperInvariant().Trim() == "TRUE");
}
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Attr")]
[SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "bool")]
public virtual bool GetBoolAttr(string config, string platform, string name)
{
this.SetConfiguration(config, platform);
return this.GetBoolAttr(this.currentConfig, name);
}
/// <summary>
/// Get the assembly name for a give configuration
/// </summary>
/// <param name="config">the matching configuration in the msbuild file</param>
/// <returns>assembly name</returns>
public virtual string GetAssemblyName(string config, string platform)
{
this.SetConfiguration(config, platform);
return GetAssemblyName(this.currentConfig);
}
/// <summary>
/// Determines whether a file is a code file.
/// </summary>
/// <param name="fileName">Name of the file to be evaluated</param>
/// <returns>false by default for any fileName</returns>
public virtual bool IsCodeFile(string fileName)
{
return false;
}
public virtual string[] CodeFileExtensions {
get {
return new string[0];
}
}
/// <summary>
/// Determines whether the given file is a resource file (resx file).
/// </summary>
/// <param name="fileName">Name of the file to be evaluated.</param>
/// <returns>true if the file is a resx file, otherwise false.</returns>
public virtual bool IsEmbeddedResource(string fileName)
{
return String.Equals(Path.GetExtension(fileName), ".ResX", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Create a file node based on an msbuild item.
/// </summary>
/// <param name="item">msbuild item</param>
/// <returns>FileNode added</returns>
public abstract FileNode CreateFileNode(ProjectElement item);
/// <summary>
/// Create a file node based on a string.
/// </summary>
/// <param name="file">filename of the new filenode</param>
/// <returns>File node added</returns>
public abstract FileNode CreateFileNode(string file);
/// <summary>
/// Create dependent file node based on an msbuild item
/// </summary>
/// <param name="item">msbuild item</param>
/// <returns>dependent file node</returns>
public virtual DependentFileNode CreateDependentFileNode(MsBuildProjectElement item)
{
return new DependentFileNode(this, item);
}
/// <summary>
/// Create a dependent file node based on a string.
/// </summary>
/// <param name="file">filename of the new dependent file node</param>
/// <returns>Dependent node added</returns>
public virtual DependentFileNode CreateDependentFileNode(string file)
{
var item = AddFileToMsBuild(file);
return this.CreateDependentFileNode(item);
}
/// <summary>
/// Walks the subpaths of a project relative path and checks if the folder nodes hierarchy is already there, if not creates it.
/// </summary>
/// <param name="strPath">Path of the folder, can be relative to project or absolute</param>
public virtual HierarchyNode CreateFolderNodes(string path, bool createOnDisk = true)
{
VsUtilities.ArgumentNotNullOrEmpty("path", path);
if (Path.IsPathRooted(path))
{
// Ensure we are using a path deeper than ProjectHome
if (!CommonUtils.IsSubpathOf(ProjectHome, path))
throw new ArgumentException("The path is not within the project", "path");
path = CommonUtils.GetRelativeDirectoryPath(ProjectHome, path);
}
// If the folder already exists, return early
string strFullPath = CommonUtils.GetAbsoluteDirectoryPath(ProjectHome, path);
uint uiItemId;
if (ErrorHandler.Succeeded(ParseCanonicalName(strFullPath, out uiItemId)) &&
uiItemId != 0)
{
var folder = this.NodeFromItemId(uiItemId) as FolderNode;
if (folder != null)
{
// found the folder, return immediately
return folder;
}
}
string[] parts = path.Split(new [] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0)
{
throw new ArgumentException("The path is invalid", "path");
}
path = parts[0];
HierarchyNode curParent = VerifySubFolderExists(path, this, createOnDisk);
// now we have an array of subparts....
for (int i = 1; i < parts.Length; i++)
{
if (parts[i].Length > 0)
{
path = Path.Combine(path, parts[i]);
curParent = VerifySubFolderExists(path, curParent, createOnDisk);
}
}
return curParent;
}
/// <summary>
/// Defines if Node has Designer. By default we do not support designers for nodes
/// </summary>
/// <param name="itemPath">Path to item to query for designer support</param>
/// <returns>true if node has designer</returns>
public virtual bool NodeHasDesigner(string itemPath)
{
return false;
}
/// <summary>
/// List of Guids of the config independent property pages. It is called by the GetProperty for VSHPROPID_PropertyPagesCLSIDList property.
/// </summary>
/// <returns></returns>
protected virtual Guid[] GetConfigurationIndependentPropertyPages()
{
return new Guid[0];
}
/// <summary>
/// Returns a list of Guids of the configuration dependent property pages. It is called by the GetProperty for VSHPROPID_CfgPropertyPagesCLSIDList property.
/// </summary>
/// <returns></returns>
protected virtual Guid[] GetConfigurationDependentPropertyPages()
{
return new Guid[0];
}
/// <summary>
/// An ordered list of guids of the prefered property pages. See <see cref="__VSHPROPID.VSHPROPID_PriorityPropertyPagesCLSIDList"/>
/// </summary>
/// <returns>An array of guids.</returns>
protected virtual Guid[] GetPriorityProjectDesignerPages()
{
return new Guid[] { Guid.Empty };
}
/// <summary>
/// Takes a path and verifies that we have a node with that name.
/// It is meant to be a helper method for CreateFolderNodes().
/// For some scenario it may be useful to override.
/// </summary>
/// <param name="path">full path to the subfolder we want to verify.</param>
/// <param name="parent">the parent node where to add the subfolder if it does not exist.</param>
/// <returns>the foldernode correcsponding to the path.</returns>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "SubFolder")]
protected virtual FolderNode VerifySubFolderExists(string path, HierarchyNode parent, bool createOnDisk = true)
{
FolderNode folderNode = null;
uint uiItemId;
string strFullPath = CommonUtils.GetAbsoluteDirectoryPath(ProjectHome, path);
if (ErrorHandler.Succeeded(this.ParseCanonicalName(strFullPath, out uiItemId)) &&
uiItemId != 0)
{
Debug.Assert(this.NodeFromItemId(uiItemId) is FolderNode, "Not a FolderNode");
folderNode = (FolderNode)this.NodeFromItemId(uiItemId);
}
if (folderNode == null && strFullPath != null && parent != null)
{
// folder does not exist yet...
// We could be in the process of loading so see if msbuild knows about it
ProjectElement item = null;
foreach (MSBuild.ProjectItem folder in buildProject.GetItems(ProjectFileConstants.Folder))
{
var absPath = CommonUtils.GetAbsoluteDirectoryPath(ProjectHome, folder.EvaluatedInclude);
if (CommonUtils.IsSameDirectory(absPath, strFullPath))
{
item = new MsBuildProjectElement(this, folder);
break;
}
}
// If MSBuild did not know about it, create a new one
if (item == null) {
item = this.AddFolderToMsBuild(strFullPath);
}
if (createOnDisk) {
Directory.CreateDirectory(strFullPath);
}
folderNode = this.CreateFolderNode(item);
parent.AddChild(folderNode);
}
return folderNode;
}
/// <summary>
/// To support virtual folders, override this method to return your own folder nodes
/// </summary>
/// <param name="path">Path to store for this folder</param>
/// <param name="element">Element corresponding to the folder</param>
/// <returns>A FolderNode that can then be added to the hierarchy</returns>
public virtual FolderNode CreateFolderNode(ProjectElement element)
{
return new FolderNode(this, element);
}
/// <summary>
/// Gets the list of selected HierarchyNode objects
/// </summary>
/// <returns>A list of HierarchyNode objects</returns>
public virtual IList<HierarchyNode> GetSelectedNodes()
{
// Retrieve shell interface in order to get current selection
IVsMonitorSelection monitorSelection = this.GetService(typeof(IVsMonitorSelection)) as IVsMonitorSelection;
VsUtilities.CheckNotNull(monitorSelection);
List<HierarchyNode> selectedNodes = new List<HierarchyNode>();
IntPtr hierarchyPtr = IntPtr.Zero;
IntPtr selectionContainer = IntPtr.Zero;
try
{
// Get the current project hierarchy, project item, and selection container for the current selection
// If the selection spans multiple hierachies, hierarchyPtr is Zero
uint itemid;
IVsMultiItemSelect multiItemSelect = null;
ErrorHandler.ThrowOnFailure(monitorSelection.GetCurrentSelection(out hierarchyPtr, out itemid, out multiItemSelect, out selectionContainer));
// We only care if there are one ore more nodes selected in the tree
if (itemid != VSConstants.VSITEMID_NIL && hierarchyPtr != IntPtr.Zero)
{
IVsHierarchy hierarchy = Marshal.GetObjectForIUnknown(hierarchyPtr) as IVsHierarchy;
if (itemid != VSConstants.VSITEMID_SELECTION)
{
// This is a single selection. Compare hirarchy with our hierarchy and get node from itemid
if (VsUtilities.IsSameComObject(this, hierarchy))
{
HierarchyNode node = this.NodeFromItemId(itemid);
if (node != null)
{
selectedNodes.Add(node);
}
}
}
else if (multiItemSelect != null)
{
// This is a multiple item selection.
//Get number of items selected and also determine if the items are located in more than one hierarchy
uint numberOfSelectedItems;
int isSingleHierarchyInt;
ErrorHandler.ThrowOnFailure(multiItemSelect.GetSelectionInfo(out numberOfSelectedItems, out isSingleHierarchyInt));
bool isSingleHierarchy = (isSingleHierarchyInt != 0);
// Now loop all selected items and add to the list only those that are selected within this hierarchy
if (!isSingleHierarchy || (isSingleHierarchy && VsUtilities.IsSameComObject(this, hierarchy)))
{
Debug.Assert(numberOfSelectedItems > 0, "Bad number of selected itemd");
VSITEMSELECTION[] vsItemSelections = new VSITEMSELECTION[numberOfSelectedItems];
uint flags = (isSingleHierarchy) ? (uint)__VSGSIFLAGS.GSI_fOmitHierPtrs : 0;
ErrorHandler.ThrowOnFailure(multiItemSelect.GetSelectedItems(flags, numberOfSelectedItems, vsItemSelections));
foreach (VSITEMSELECTION vsItemSelection in vsItemSelections)
{
if (isSingleHierarchy || VsUtilities.IsSameComObject(this, vsItemSelection.pHier))
{
HierarchyNode node = this.NodeFromItemId(vsItemSelection.itemid);
if (node != null)
{
selectedNodes.Add(node);
}
}
}
}
}
}
}
finally
{
if (hierarchyPtr != IntPtr.Zero)
{
Marshal.Release(hierarchyPtr);
}
if (selectionContainer != IntPtr.Zero)
{
Marshal.Release(selectionContainer);
}
}
return selectedNodes;
}
/// <summary>
/// Recursevily walks the hierarchy nodes and redraws the state icons
/// </summary>
public override void UpdateSccStateIcons()
{
if (this.FirstChild == null)
{
return;
}
for (HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling)
{
n.UpdateSccStateIcons();
}
}
/// <summary>
/// Handles the shows all objects command.
/// </summary>
/// <returns></returns>
public virtual int ShowAllFiles()
{
return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED;
}
/// <summary>
/// Unloads the project.
/// </summary>
/// <returns></returns>
public virtual int UnloadProject()
{
return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED;
}
/// <summary>
/// Handles the clean project command.
/// </summary>
/// <returns></returns>
protected virtual int CleanProject()
{
return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED;
}
/// <summary>
/// Reload project from project file
/// </summary>
protected virtual void Reload()
{
try
{
this.disableQueryEdit = true;
this.isClosed = false;
this.eventTriggeringFlag = ProjectNode.EventTriggering.DoNotTriggerHierarchyEvents | ProjectNode.EventTriggering.DoNotTriggerTrackerEvents;
LoadProjectFile(this.FileName);
// Load the guid
this.SetProjectGuidFromProjectFile();
this.ProcessReferences();
this.ProcessFiles();
this.ProcessFolders();
this.ProcessConfigurations();
this.LoadNonBuildInformation();
this.InitSccInfo();
this.RegisterSccProject();
}
finally
{
this.SetProjectFileDirty(false);
this.eventTriggeringFlag = ProjectNode.EventTriggering.TriggerAll;
this.disableQueryEdit = false;
}
}
protected virtual void ProcessConfigurations()
{
}
protected virtual void LoadProjectFile(string filename)
{
Debug.Assert(this.buildEngine != null, "There is no build engine defined for this project");
SetBuildProject(VsUtilities.ReinitializeMsBuildProject(this.buildEngine, this.filename, this.buildProject));
}
/// <summary>
/// Renames the project file
/// </summary>
/// <param name="newFile">The full path of the new project file.</param>
protected virtual void RenameProjectFile(string newFile)
{
IVsUIShell shell = this.Site.GetService(typeof(SVsUIShell)) as IVsUIShell;
Debug.Assert(shell != null, "Could not get the ui shell from the project");
VsUtilities.CheckNotNull(shell);
// Figure out what the new full name is
string oldFile = this.Url;
int canContinue = 0;
IVsSolution vsSolution = (IVsSolution)this.GetService(typeof(SVsSolution));
if (ErrorHandler.Succeeded(vsSolution.QueryRenameProject(this.GetOuterInterface<IVsProject>(), oldFile, newFile, 0, out canContinue))
&& canContinue != 0)
{
bool isFileSame = CommonUtils.IsSamePath(oldFile, newFile);
// If file already exist and is not the same file with different casing
if (!isFileSame && File.Exists(newFile))
{
// Prompt the user for replace
string message = SR.GetString(SR.FileAlreadyExists, newFile);
if (!VsUtilities.IsInAutomationFunction(this.Site))
{
if (!VsShellUtilities.PromptYesNo(message, null, OLEMSGICON.OLEMSGICON_WARNING, shell))
{
throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED);
}
}
else
{
throw new InvalidOperationException(message);
}
// Delete the destination file after making sure it is not read only
File.SetAttributes(newFile, FileAttributes.Normal);
File.Delete(newFile);
}
SuspendFileChanges fileChanges = new SuspendFileChanges(this.Site, this.filename);
fileChanges.Suspend();
try
{
// Actual file rename
this.SaveMSBuildProjectFileAs(newFile);
this.SetProjectFileDirty(false);
if (!isFileSame)
{
// Now that the new file name has been created delete the old one.
// TODO: Handle source control issues.
File.SetAttributes(oldFile, FileAttributes.Normal);
File.Delete(oldFile);
}
this.OnPropertyChanged(this, (int)__VSHPROPID.VSHPROPID_Caption, 0);
// Update solution
ErrorHandler.ThrowOnFailure(vsSolution.OnAfterRenameProject((IVsProject)this, oldFile, newFile, 0));
ErrorHandler.ThrowOnFailure(shell.RefreshPropertyBrowser(0));
}
finally
{
fileChanges.Resume();
}
}
else
{
throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED);
}
}
/// <summary>
/// Called by the project to know if the item is a file (that is part of the project)
/// or an intermediate file used by the MSBuild tasks/targets
/// Override this method if your project has more types or different ones
/// </summary>
/// <param name="type">Type name</param>
/// <returns>True = items of this type should be included in the project</returns>
protected virtual bool IsItemTypeFileType(string type)
{
// recognize the typical types as a file....
if (String.Compare(type, "Compile", StringComparison.OrdinalIgnoreCase) == 0
|| String.Compare(type, "Content", StringComparison.OrdinalIgnoreCase) == 0
|| String.Compare(type, "EmbeddedResource", StringComparison.OrdinalIgnoreCase) == 0
|| String.Compare(type, "None", StringComparison.OrdinalIgnoreCase) == 0)
return true;
// we don't know about this type, so ignore it.
return false;
}
/// <summary>
/// Filter items that should not be processed as file items. Example: Folders and References.
/// </summary>
protected virtual bool FilterItemTypeToBeAddedToHierarchy(string itemType)
{
return (String.Compare(itemType, ProjectFileConstants.Reference, StringComparison.OrdinalIgnoreCase) == 0
|| String.Compare(itemType, ProjectFileConstants.ProjectReference, StringComparison.OrdinalIgnoreCase) == 0
|| String.Compare(itemType, ProjectFileConstants.COMReference, StringComparison.OrdinalIgnoreCase) == 0
|| String.Compare(itemType, ProjectFileConstants.Folder, StringComparison.OrdinalIgnoreCase) == 0
|| String.Compare(itemType, ProjectFileConstants.WebReference, StringComparison.OrdinalIgnoreCase) == 0
|| String.Compare(itemType, ProjectFileConstants.WebReferenceFolder, StringComparison.OrdinalIgnoreCase) == 0
|| String.Compare(itemType, ProjectFileConstants.WebPiReference, StringComparison.OrdinalIgnoreCase) == 0);
}
/// <summary>
/// Associate window output pane to the build logger
/// </summary>
/// <param name="output"></param>
public virtual void SetOutputLogger(IVsOutputWindowPane output)
{
// Create our logger, if it was not specified
if (!this.useProvidedLogger || this.buildLogger == null)
{
// Because we may be aggregated, we need to make sure to get the outer IVsHierarchy
IntPtr unknown = IntPtr.Zero;
IVsHierarchy hierarchy = null;
try
{
unknown = Marshal.GetIUnknownForObject(this);
hierarchy = Marshal.GetTypedObjectForIUnknown(unknown, typeof(IVsHierarchy)) as IVsHierarchy;
}
finally
{
if (unknown != IntPtr.Zero)
Marshal.Release(unknown);
}
// Create the logger
this.BuildLogger = new IDEBuildLogger(output, this.TaskProvider, hierarchy);
// To retrive the verbosity level, the build logger depends on the registry root
// (otherwise it will used an hardcoded default)
ILocalRegistry2 registry = this.GetService(typeof(SLocalRegistry)) as ILocalRegistry2;
if (null != registry)
{
string registryRoot;
ErrorHandler.ThrowOnFailure(registry.GetLocalRegistryRoot(out registryRoot));
IDEBuildLogger logger = this.BuildLogger as IDEBuildLogger;
if (!String.IsNullOrEmpty(registryRoot) && (null != logger))
{
logger.BuildVerbosityRegistryRoot = registryRoot;
logger.ErrorString = this.ErrorString;
logger.WarningString = this.WarningString;
}
}
}
else
{
((IDEBuildLogger)this.BuildLogger).OutputWindowPane = output;
}
}
/// <summary>
/// Set configuration properties for a specific configuration
/// </summary>
/// <param name="config">configuration name</param>
protected virtual void SetBuildConfigurationProperties(string config, string platform)
{
CompilerParameters options = null;
if (!String.IsNullOrEmpty(config) && !String.IsNullOrEmpty(platform))
{
options = this.GetProjectOptions(config, platform);
}
if (options != null && this.buildProject != null)
{
// Make sure the project configuration is set properly
this.SetConfiguration(config, platform);
}
}
/// <summary>
/// This execute an MSBuild target for a design-time build.
/// </summary>
/// <param name="target">Name of the MSBuild target to execute</param>
/// <returns>Result from executing the target (success/failure)</returns>
/// <remarks>
/// If you depend on the items/properties generated by the target
/// you should be aware that any call to BuildTarget on any project
/// will reset the list of generated items/properties
/// </remarks>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ms")]
public virtual MSBuildResult InvokeMsBuild(uint vsopts, string target, string config, string platform, IEnumerable<string> files)
{
MSBuildResult result = MSBuildResult.Failed;
const bool designTime = true;
bool requiresUIThread = UIThread.Instance.IsUIThread; // we don't run tasks that require calling the STA thread, so unless we're ON it, we don't need it.
IVsBuildManagerAccessor accessor = this.Site.GetService(typeof(SVsBuildManagerAccessor)) as IVsBuildManagerAccessor;
BuildSubmission submission = null;
try
{
// Do the actual Build
if (this.buildProject != null)
{
if (!TryBeginBuild(designTime, requiresUIThread))
{
throw new InvalidOperationException("A build is already in progress.");
}
string[] targetsToBuild = new string[target != null ? 1 : 0];
if (target != null)
{
targetsToBuild[0] = target;
}
currentConfig = BuildProject.CreateProjectInstance();
BuildRequestData requestData = new BuildRequestData(currentConfig, targetsToBuild, this.BuildProject.ProjectCollection.HostServices, BuildRequestDataFlags.ReplaceExistingProjectInstance);
submission = BuildManager.DefaultBuildManager.PendBuildRequest(requestData);
if (accessor != null)
{
ErrorHandler.ThrowOnFailure(accessor.RegisterLogger(submission.SubmissionId, this.buildLogger));
}
BuildResult buildResult = submission.Execute();
result = (buildResult.OverallResult == BuildResultCode.Success) ? MSBuildResult.Successful : MSBuildResult.Failed;
}
}
finally
{
EndBuild(vsopts, submission, designTime, requiresUIThread);
}
return result;
}
/// <summary>
/// Initialize common project properties with default value if they are empty
/// </summary>
/// <remarks>The following common project properties are defaulted to projectName (if empty):
/// AssemblyName, Name and RootNamespace.
/// If the project filename is not set then no properties are set</remarks>
protected virtual void InitializeProjectProperties()
{
// Get projectName from project filename. Return if not set
string projectName = Path.GetFileNameWithoutExtension(this.filename);
if (String.IsNullOrEmpty(projectName))
{
return;
}
if (String.IsNullOrEmpty(GetProjectProperty(ProjectFileConstants.AssemblyName)))
{
SetProjectProperty(ProjectFileConstants.AssemblyName, projectName);
}
if (String.IsNullOrEmpty(GetProjectProperty(ProjectFileConstants.Name)))
{
SetProjectProperty(ProjectFileConstants.Name, projectName);
}
if (String.IsNullOrEmpty(GetProjectProperty(ProjectFileConstants.RootNamespace)))
{
SetProjectProperty(ProjectFileConstants.RootNamespace, projectName);
}
}
/// <summary>
/// Factory method for configuration provider
/// </summary>
/// <returns>Configuration provider created</returns>
protected abstract ConfigProvider CreateConfigProvider();
/// <summary>
/// Factory method for reference container node
/// </summary>
/// <returns>ReferenceContainerNode created</returns>
protected virtual ReferenceContainerNode CreateReferenceContainerNode()
{
return new ReferenceContainerNode(this);
}
/// <summary>
/// Saves the project file on a new name.
/// </summary>
/// <param name="newFileName">The new name of the project file.</param>
/// <returns>Success value or an error code.</returns>
protected virtual int SaveAs(string newFileName)
{
Debug.Assert(!String.IsNullOrEmpty(newFileName), "Cannot save project file for an empty or null file name");
VsUtilities.ArgumentNotNullOrEmpty(newFileName, "newFileName");
newFileName = newFileName.Trim();
string errorMessage = String.Empty;
if (newFileName.Length > NativeMethods.MAX_PATH)
{
errorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.PathTooLong, CultureInfo.CurrentUICulture), newFileName);
}
else
{
string fileName = String.Empty;
try
{
fileName = Path.GetFileNameWithoutExtension(newFileName);
}
// We want to be consistent in the error message and exception we throw. fileName could be for example #<23>&%"<22>&"% and that would trigger an ArgumentException on Path.IsRooted.
catch (ArgumentException)
{
errorMessage = String.Format(SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture), newFileName);
}
if (errorMessage.Length == 0)
{
// If there is no filename or it starts with a leading dot issue an error message and quit.
// For some reason the save as dialog box allows to save files like "......ext"
if (String.IsNullOrEmpty(fileName) || fileName[0] == '.')
{
errorMessage = SR.GetString(SR.FileNameCannotContainALeadingPeriod, CultureInfo.CurrentUICulture);
}
else if (VsUtilities.ContainsInvalidFileNameChars(newFileName))
{
errorMessage = String.Format(SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture), newFileName);
}
}
}
if (errorMessage.Length > 0)
{
// If it is not called from an automation method show a dialog box.
if (!VsUtilities.IsInAutomationFunction(this.Site))
{
string title = null;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
VsShellUtilities.ShowMessageBox(this.Site, title, errorMessage, icon, buttons, defaultButton);
return VSConstants.OLE_E_PROMPTSAVECANCELLED;
}
throw new InvalidOperationException(errorMessage);
}
string oldName = this.filename;
IVsSolution solution = this.Site.GetService(typeof(IVsSolution)) as IVsSolution;
Debug.Assert(solution != null, "Could not retrieve the solution form the service provider");
VsUtilities.CheckNotNull(solution);
int canRenameContinue = 0;
ErrorHandler.ThrowOnFailure(solution.QueryRenameProject(this.GetOuterInterface<IVsProject>(), this.filename, newFileName, 0, out canRenameContinue));
if (canRenameContinue == 0)
{
return VSConstants.OLE_E_PROMPTSAVECANCELLED;
}
SuspendFileChanges fileChanges = new SuspendFileChanges(this.Site, oldName);
fileChanges.Suspend();
try
{
// Save the project file and project file related properties.
this.SaveMSBuildProjectFileAs(newFileName);
this.SetProjectFileDirty(false);
// TODO: If source control is enabled check out the project file.
//Redraw.
this.OnPropertyChanged(this, (int)__VSHPROPID.VSHPROPID_Caption, 0);
ErrorHandler.ThrowOnFailure(solution.OnAfterRenameProject(this, oldName, this.filename, 0));
IVsUIShell shell = this.Site.GetService(typeof(SVsUIShell)) as IVsUIShell;
Debug.Assert(shell != null, "Could not get the ui shell from the project");
VsUtilities.CheckNotNull(shell);
ErrorHandler.ThrowOnFailure(shell.RefreshPropertyBrowser(0));
}
finally
{
fileChanges.Resume();
}
return VSConstants.S_OK;
}
/// <summary>
/// Saves project file related information to the new file name. It also calls msbuild API to save the project file.
/// It is called by the SaveAs method and the SetEditLabel before the project file rename related events are triggered.
/// An implementer can override this method to provide specialized semantics on how the project file is renamed in the msbuild file.
/// </summary>
/// <param name="newFileName">The new full path of the project file</param>
protected virtual void SaveMSBuildProjectFileAs(string newFileName)
{
Debug.Assert(!String.IsNullOrEmpty(newFileName), "Cannot save project file for an empty or null file name");
string newProjectHome = CommonUtils.GetRelativeDirectoryPath(Path.GetDirectoryName(newFileName), ProjectHome);
this.buildProject.SetProperty(CommonConstants.ProjectHome, newProjectHome);
this.buildProject.FullPath = newFileName;
this.filename = newFileName;
string newFileNameWithoutExtension = Path.GetFileNameWithoutExtension(newFileName);
// Refresh solution explorer
this.SetProjectProperty(ProjectFileConstants.Name, newFileNameWithoutExtension);
// Saves the project file on disk.
this.buildProject.Save(newFileName);
}
/// <summary>
/// Adds a file to the msbuild project.
/// </summary>
/// <param name="file">The file to be added.</param>
/// <returns>A Projectelement describing the newly added file.</returns>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ToMs")]
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ms")]
public virtual MsBuildProjectElement AddFileToMsBuild(string file)
{
MsBuildProjectElement newItem;
string itemPath = CommonUtils.GetRelativeFilePath(ProjectHome, file);
Debug.Assert(!Path.IsPathRooted(itemPath), "Cannot add item with full path.");
if (this.IsCodeFile(itemPath))
{
newItem = this.CreateMsBuildFileItem(itemPath, ProjectFileConstants.Compile);
newItem.SetMetadata(ProjectFileConstants.SubType, ProjectFileAttributeValue.Code);
}
else if (this.IsEmbeddedResource(itemPath))
{
newItem = this.CreateMsBuildFileItem(itemPath, ProjectFileConstants.EmbeddedResource);
}
else
{
newItem = this.CreateMsBuildFileItem(itemPath, ProjectFileConstants.Content);
newItem.SetMetadata(ProjectFileConstants.SubType, ProjectFileConstants.Content);
}
return newItem;
}
/// <summary>
/// Adds a folder to the msbuild project.
/// </summary>
/// <param name="folder">The folder to be added.</param>
/// <returns>A ProjectElement describing the newly added folder.</returns>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ToMs")]
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ms")]
protected virtual ProjectElement AddFolderToMsBuild(string folder)
{
ProjectElement newItem;
if (Path.IsPathRooted(folder))
{
folder = CommonUtils.GetRelativeDirectoryPath(ProjectHome, folder);
Debug.Assert(!Path.IsPathRooted(folder), "Cannot add item with full path.");
}
newItem = this.CreateMsBuildFileItem(folder, ProjectFileConstants.Folder);
return newItem;
}
const int E_CANCEL_FILE_ADD = unchecked((int)0xA0010001); // Severity = Error, Customer Bit set, Facility = 1, Error = 1
/// <summary>
/// Checks to see if the user wants to overwrite the specified file name.
///
/// Returns:
/// E_ABORT if we disallow the user to overwrite the file
/// OLECMDERR_E_CANCELED if the user wants to cancel
/// S_OK if the user wants to overwrite
/// E_CANCEL_FILE_ADD (0xA0010001) if the user doesn't want to overwrite and wants to abort the larger transaction
/// </summary>
/// <param name="originalFileName"></param>
/// <param name="computedNewFileName"></param>
/// <param name="canCancel"></param>
/// <returns></returns>
protected int CanOverwriteExistingItem(string originalFileName, string computedNewFileName, bool inProject = true)
{
if (String.IsNullOrEmpty(originalFileName) || String.IsNullOrEmpty(computedNewFileName))
{
return VSConstants.E_INVALIDARG;
}
string message = String.Empty;
string title = String.Empty;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
// If the document is open then return error message.
IVsUIHierarchy hier;
IVsWindowFrame windowFrame;
uint itemid = VSConstants.VSITEMID_NIL;
bool isOpen = VsShellUtilities.IsDocumentOpen(this.Site, computedNewFileName, Guid.Empty, out hier, out itemid, out windowFrame);
if (isOpen)
{
message = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.CannotAddFileThatIsOpenInEditor, CultureInfo.CurrentUICulture), Path.GetFileName(computedNewFileName));
VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton);
return VSConstants.E_ABORT;
}
// File already exists in project... message box
message = String.Format(SR.GetString(inProject ? SR.FileAlreadyInProject : SR.FileAlreadyExists, CultureInfo.CurrentUICulture), Path.GetFileName(originalFileName));
icon = OLEMSGICON.OLEMSGICON_QUERY;
buttons = OLEMSGBUTTON.OLEMSGBUTTON_YESNO;
int msgboxResult = VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton);
if (msgboxResult == NativeMethods.IDCANCEL)
{
return (int)E_CANCEL_FILE_ADD;
}
else if (msgboxResult != NativeMethods.IDYES)
{
return (int)OleConstants.OLECMDERR_E_CANCELED;
}
return VSConstants.S_OK;
}
/// <summary>
/// Adds a new file node to the hierarchy.
/// </summary>
/// <param name="parentNode">The parent of the new fileNode</param>
/// <param name="fileName">The file name</param>
protected virtual void AddNewFileNodeToHierarchy(HierarchyNode parentNode, string fileName)
{
VsUtilities.ArgumentNotNull("parentNode", parentNode);
HierarchyNode child;
// In the case of subitem, we want to create dependent file node
// and set the DependentUpon property
if (this.canFileNodesHaveChilds && (parentNode is FileNode || parentNode is DependentFileNode))
{
child = this.CreateDependentFileNode(fileName);
child.ItemNode.SetMetadata(ProjectFileConstants.DependentUpon, parentNode.ItemNode.GetMetadata(ProjectFileConstants.Include));
// Make sure to set the HasNameRelation flag on the dependent node if it is related to the parent by name
if (!child.HasParentNodeNameRelation && string.Compare(child.GetRelationalName(), parentNode.GetRelationalName(), StringComparison.OrdinalIgnoreCase) == 0)
{
child.HasParentNodeNameRelation = true;
}
}
else
{
//Create and add new filenode to the project
child = this.CreateFileNode(fileName);
}
parentNode.AddChild(child);
// TODO : Revisit the VSADDFILEFLAGS here. Can it be a nested project?
this.tracker.OnItemAdded(fileName, VSADDFILEFLAGS.VSADDFILEFLAGS_NoFlags);
}
/// <summary>
/// Defines whther the current mode of the project is in a supress command mode.
/// </summary>
/// <returns></returns>
public virtual bool IsCurrentStateASuppressCommandsMode()
{
if (VsShellUtilities.IsSolutionBuilding(this.Site))
{
return true;
}
DBGMODE dbgMode = VsShellUtilities.GetDebugMode(this.Site) & ~DBGMODE.DBGMODE_EncMask;
if (dbgMode == DBGMODE.DBGMODE_Run || dbgMode == DBGMODE.DBGMODE_Break)
{
return true;
}
return false;
}
/// <summary>
/// This is the list of output groups that the configuration object should
/// provide.
/// The first string is the name of the group.
/// The second string is the target name (MSBuild) for that group.
///
/// To add/remove OutputGroups, simply override this method and edit the list.
///
/// To get nice display names and description for your groups, override:
/// - GetOutputGroupDisplayName
/// - GetOutputGroupDescription
/// </summary>
/// <returns>List of output group name and corresponding MSBuild target</returns>
public virtual IList<KeyValuePair<string, string>> GetOutputGroupNames()
{
return new List<KeyValuePair<string, string>>(outputGroupNames);
}
/// <summary>
/// Get the display name of the given output group.
/// </summary>
/// <param name="canonicalName">Canonical name of the output group</param>
/// <returns>Display name</returns>
public virtual string GetOutputGroupDisplayName(string canonicalName)
{
string result = SR.GetString(String.Format(CultureInfo.InvariantCulture, "Output{0}", canonicalName), CultureInfo.CurrentUICulture);
if (String.IsNullOrEmpty(result))
result = canonicalName;
return result;
}
/// <summary>
/// Get the description of the given output group.
/// </summary>
/// <param name="canonicalName">Canonical name of the output group</param>
/// <returns>Description</returns>
public virtual string GetOutputGroupDescription(string canonicalName)
{
string result = SR.GetString(String.Format(CultureInfo.InvariantCulture, "Output{0}Description", canonicalName), CultureInfo.CurrentUICulture);
if (String.IsNullOrEmpty(result))
result = canonicalName;
return result;
}
/// <summary>
/// Set the configuration in MSBuild.
/// This does not get persisted and is used to evaluate msbuild conditions
/// which are based on the $(Configuration) property.
/// </summary>
public virtual void SetCurrentConfiguration()
{
// Can't ask for the active config until the project is opened, so do nothing in that scenario
if (!this.projectOpened)
return;
EnvDTE.Project automationObject = this.GetAutomationObject() as EnvDTE.Project;
this.SetConfiguration(VsUtilities.GetActiveConfigurationName(automationObject), VsUtilities.GetActivePlatformName(automationObject));
}
/// <summary>
/// Set the configuration property in MSBuild.
/// This does not get persisted and is used to evaluate msbuild conditions
/// which are based on the $(Configuration) property.
/// </summary>
/// <param name="config">Configuration name</param>
public virtual void SetConfiguration(string config, string platform)
{
VsUtilities.ArgumentNotNull("config", config);
VsUtilities.ArgumentNotNull("platform", platform);
// Can't ask for the active config until the project is opened, so do nothing in that scenario
if (!projectOpened)
return;
bool propertiesChanged = this.buildProject.SetGlobalProperty(ProjectFileConstants.Configuration, config);
if (this.buildProject.SetGlobalProperty(ProjectFileConstants.Platform, config))
{
propertiesChanged = true;
}
if (this.currentConfig == null || propertiesChanged)
{
this.currentConfig = this.buildProject.CreateProjectInstance();
}
}
/// <summary>
/// Loads reference items from the project file into the hierarchy.
/// </summary>
public virtual void ProcessReferences()
{
IReferenceContainer container = GetReferenceContainer();
if (null == container)
{
// This project type does not support references or there is a problem
// creating the reference container node.
// In both cases there is no point to try to process references, so exit.
return;
}
// Load the referernces.
container.LoadReferencesFromBuildProject(buildProject);
}
/// <summary>
/// Loads folders from the project file into the hierarchy.
/// </summary>
public virtual void ProcessFolders()
{
// Process Folders (useful to persist empty folder)
foreach (MSBuild.ProjectItem folder in this.buildProject.GetItems(ProjectFileConstants.Folder))
{
string strPath = folder.EvaluatedInclude;
// We do not need any special logic for assuring that a folder is only added once to the ui hierarchy.
// The below method will only add once the folder to the ui hierarchy
this.CreateFolderNodes(strPath, false);
}
}
/// <summary>
/// Loads file items from the project file into the hierarchy.
/// </summary>
public virtual void ProcessFiles()
{
List<String> subitemsKeys = new List<String>();
Dictionary<String, MSBuild.ProjectItem> subitems = new Dictionary<String, MSBuild.ProjectItem>();
// Define a set for our build items. The value does not really matter here.
Dictionary<String, MSBuild.ProjectItem> items = new Dictionary<String, MSBuild.ProjectItem>();
// Process Files
foreach (MSBuild.ProjectItem item in this.buildProject.Items.ToArray()) // copy the array, we could add folders while enumerating
{
// Ignore items imported from .targets files. In particular, this will ignore the <Content>
// items that are generated from any <Compile> items in our .targets.
if (item.IsImported) {
continue;
}
// Ignore the item if it is a reference or folder
if (this.FilterItemTypeToBeAddedToHierarchy(item.ItemType))
continue;
// MSBuilds tasks/targets can create items (such as object files),
// such items are not part of the project per say, and should not be displayed.
// so ignore those items.
if (!this.IsItemTypeFileType(item.ItemType))
continue;
// If the item is already contained do nothing.
// TODO: possibly report in the error list that the the item is already contained in the project file similar to Language projects.
if (items.ContainsKey(item.EvaluatedInclude.ToUpperInvariant()))
continue;
// Make sure that we do not want to add the item, dependent, or independent twice to the ui hierarchy
items.Add(item.EvaluatedInclude.ToUpperInvariant(), item);
string dependentOf = item.GetMetadataValue(ProjectFileConstants.DependentUpon);
string link = item.GetMetadataValue(ProjectFileConstants.Link);
if (!String.IsNullOrWhiteSpace(link))
{
if (Path.IsPathRooted(link))
{
// ignore fully rooted link paths.
continue;
}
if (!Path.IsPathRooted(item.EvaluatedInclude))
{
var itemPath = CommonUtils.GetAbsoluteFilePath(ProjectHome, item.EvaluatedInclude);
if (CommonUtils.IsSubpathOf(ProjectHome, itemPath))
{
// linked file which lives in our directory, don't allow that.
continue;
}
}
var linkPath = CommonUtils.GetAbsoluteFilePath(ProjectHome, link);
if (!CommonUtils.IsSubpathOf(ProjectHome, linkPath))
{
// relative path outside of project, don't allow that.
continue;
}
}
if (!this.CanFileNodesHaveChilds || String.IsNullOrEmpty(dependentOf))
{
var parent = GetItemParentNode(item);
string filename = Path.GetFileName(item.EvaluatedInclude);
HierarchyNode existingChild = null;
for (HierarchyNode child = parent.FirstChild; child != null; child = child.NextSibling)
{
if (String.Equals(Path.GetFileName(child.Url), filename, StringComparison.OrdinalIgnoreCase))
{
existingChild = child;
break;
}
}
if (existingChild != null)
{
if (existingChild.IsLinkFile)
{
// remove link node.
existingChild.Parent.RemoveChild(existingChild);
}
else
{
// we have duplicate entries, or this is a link file.
continue;
}
}
AddIndependentFileNode(item, parent);
}
else
{
// We will process dependent items later.
// Note that we use 2 lists as we want to remove elements from
// the collection as we loop through it
subitemsKeys.Add(item.EvaluatedInclude);
subitems.Add(item.EvaluatedInclude, item);
}
}
// Now process the dependent items.
if (this.CanFileNodesHaveChilds)
{
ProcessDependentFileNodes(subitemsKeys, subitems);
}
}
/// <summary>
/// Processes dependent filenodes from list of subitems. Multi level supported, but not circular dependencies.
/// </summary>
/// <param name="subitemsKeys">List of sub item keys </param>
/// <param name="subitems"></param>
public virtual void ProcessDependentFileNodes(IList<String> subitemsKeys, Dictionary<String, MSBuild.ProjectItem> subitems)
{
if (subitemsKeys == null || subitems == null)
{
return;
}
foreach (string key in subitemsKeys)
{
// A previous pass could have removed the key so make sure it still needs to be added
if (!subitems.ContainsKey(key))
continue;
AddDependentFileNode(subitems, key);
}
}
/// <summary>
/// For flavored projects which implement IPersistXMLFragment, load the information now
/// </summary>
public virtual void LoadNonBuildInformation()
{
IPersistXMLFragment outerHierarchy = GetOuterInterface<IPersistXMLFragment>();
if (outerHierarchy != null)
{
this.LoadXmlFragment(outerHierarchy, null, null);
}
}
/// <summary>
/// Used to sort nodes in the hierarchy.
/// </summary>
public virtual int CompareNodes(HierarchyNode node1, HierarchyNode node2)
{
Debug.Assert(node1 != null && node2 != null);
VsUtilities.ArgumentNotNull("node1", node1);
VsUtilities.ArgumentNotNull("node2", node2);
if (node1.SortPriority == node2.SortPriority)
{
return String.Compare(node2.Caption, node1.Caption, true, CultureInfo.CurrentCulture);
}
else
{
return node2.SortPriority - node1.SortPriority;
}
}
#endregion
#region non-virtual methods
public void InstantiateItemsDraggedOrCutOrCopiedList() {
itemsDraggedOrCutOrCopied = new List<HierarchyNode>();
}
/// <summary>
/// Overloaded method. Invokes MSBuild using the default configuration and does without logging on the output window pane.
/// </summary>
public MSBuildResult Build(string target)
{
return this.Build(0, String.Empty, string.Empty, null, target);
}
/// <summary>
/// This is called from the main thread before the background build starts.
/// cleanBuild is not part of the vsopts, but passed down as the callpath is differently
/// PrepareBuild mainly creates directories and cleans house if cleanBuild is true
/// </summary>
public virtual void PrepareBuild(uint vsopts, string config, string platform, bool cleanBuild) {
if (this.buildIsPrepared && !cleanBuild) return;
string outputPath = GetProjectProperty("OutputPath");
if (string.IsNullOrWhiteSpace(outputPath)) return;
outputPath = Path.GetDirectoryName(outputPath);
if (cleanBuild && this.currentConfig.Targets.ContainsKey(MsBuildTarget.Clean)) {
this.InvokeMsBuild(0, MsBuildTarget.Clean, config, platform, null);
}
PackageUtilities.EnsureOutputPath(outputPath);
this.buildIsPrepared = true;
}
/// <summary>
/// Do the build by invoking msbuild
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "vsopts")]
public virtual MSBuildResult Build(uint vsopts, string config, string platform, IVsOutputWindowPane output, string target)
{
lock (ProjectNode.BuildLock)
{
bool engineLogOnlyCritical = this.BuildPrelude(output);
MSBuildResult result = MSBuildResult.Failed;
try
{
this.SetBuildConfigurationProperties(config, platform);
result = this.InvokeMsBuild(vsopts, target, config, platform, null);
}
finally
{
// Unless someone specifically request to use an output window pane, we should not output to it
if (null != output)
{
this.SetOutputLogger(null);
BuildEngine.OnlyLogCriticalEvents = engineLogOnlyCritical;
}
}
return result;
}
}
/// <summary>
/// Get value of Project property
/// </summary>
/// <param name="propertyName">Name of Property to retrieve</param>
/// <returns>Value of property</returns>
public string GetProjectProperty(string propertyName)
{
return this.GetProjectProperty(propertyName, true);
}
/// <summary>
/// Set dirty state of project
/// </summary>
/// <param name="value">boolean value indicating dirty state</param>
public void SetProjectFileDirty(bool value)
{
this.isDirty = value;
if (this.isDirty)
{
this.lastModifiedTime = DateTime.Now;
this.buildIsPrepared = false;
}
}
/// <summary>
/// Get Node from ItemID.
/// </summary>
/// <param name="itemId">ItemID for the requested node</param>
/// <returns>Node if found</returns>
public HierarchyNode NodeFromItemId(uint itemId)
{
if (VSConstants.VSITEMID_ROOT == itemId)
{
return this;
}
else if (VSConstants.VSITEMID_NIL == itemId)
{
return null;
}
else if (VSConstants.VSITEMID_SELECTION == itemId)
{
throw new NotImplementedException();
}
return (HierarchyNode)this.ItemIdMap[itemId];
}
/// <summary>
/// This method return new project element, and add new MSBuild item to the project/build hierarchy
/// </summary>
/// <param name="file">file name</param>
/// <param name="itemType">MSBuild item type</param>
/// <returns>new project element</returns>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ms")]
public MsBuildProjectElement CreateMsBuildFileItem(string file, string itemType)
{
return new MsBuildProjectElement(this, file, itemType);
}
/// <summary>
/// This method returns new project element based on existing MSBuild item. It does not modify/add project/build hierarchy at all.
/// </summary>
/// <param name="item">MSBuild item instance</param>
/// <returns>wrapping project element</returns>
public MsBuildProjectElement GetProjectElement(MSBuild.ProjectItem item)
{
return new MsBuildProjectElement(this, item);
}
/// <summary>
/// Create FolderNode from Path
/// </summary>
/// <param name="path">Path to folder</param>
/// <returns>FolderNode created that can be added to the hierarchy</returns>
public FolderNode CreateFolderNode(string path)
{
ProjectElement item = this.AddFolderToMsBuild(path);
FolderNode folderNode = CreateFolderNode(item);
return folderNode;
}
/// <summary>
/// Verify if the file can be written to.
/// Return false if the file is read only and/or not checked out
/// and the user did not give permission to change it.
/// Note that exact behavior can also be affected based on the SCC
/// settings under Tools->Options.
/// </summary>
public bool QueryEditProjectFile(bool suppressUI)
{
bool result = true;
if (this.site == null)
{
// We're already zombied. Better return FALSE.
result = false;
}
else if (this.disableQueryEdit)
{
return true;
}
else
{
IVsQueryEditQuerySave2 queryEditQuerySave = this.GetService(typeof(SVsQueryEditQuerySave)) as IVsQueryEditQuerySave2;
if (queryEditQuerySave != null)
{ // Project path dependends on server/client project
string path = this.filename;
tagVSQueryEditFlags qef = tagVSQueryEditFlags.QEF_AllowInMemoryEdits;
if (suppressUI)
qef |= tagVSQueryEditFlags.QEF_SilentMode;
// If we are debugging, we want to prevent our project from being reloaded. To
// do this, we pass the QEF_NoReload flag
if (!VsUtilities.IsVisualStudioInDesignMode(this.Site))
qef |= tagVSQueryEditFlags.QEF_NoReload;
uint verdict;
uint moreInfo;
string[] files = new string[1];
files[0] = path;
uint[] flags = new uint[1];
VSQEQS_FILE_ATTRIBUTE_DATA[] attributes = new VSQEQS_FILE_ATTRIBUTE_DATA[1];
int hr = queryEditQuerySave.QueryEditFiles(
(uint)qef,
1, // 1 file
files, // array of files
flags, // no per file flags
attributes, // no per file file attributes
out verdict,
out moreInfo /* ignore additional results */);
tagVSQueryEditResult qer = (tagVSQueryEditResult)verdict;
if (ErrorHandler.Failed(hr) || (qer != tagVSQueryEditResult.QER_EditOK))
{
if (!suppressUI && !VsUtilities.IsInAutomationFunction(this.Site))
{
string message = SR.GetString(SR.CancelQueryEdit, path);
string title = string.Empty;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton);
}
result = false;
}
}
}
return result;
}
public bool QueryFolderAdd(HierarchyNode targetFolder, string path)
{
if (!disableQueryEdit)
{
var queryTrack = this.GetService(typeof(SVsTrackProjectDocuments)) as IVsTrackProjectDocuments2;
if (queryTrack != null)
{
VSQUERYADDDIRECTORYRESULTS[] res = new VSQUERYADDDIRECTORYRESULTS[1];
ErrorHandler.ThrowOnFailure(
queryTrack.OnQueryAddDirectories(
GetOuterInterface<IVsProject>(),
1,
new[] { CommonUtils.GetAbsoluteFilePath(GetBaseDirectoryForAddingFiles(targetFolder), Path.GetFileName(path)) },
new[] { VSQUERYADDDIRECTORYFLAGS.VSQUERYADDDIRECTORYFLAGS_padding },
res,
res
)
);
if (res[0] == VSQUERYADDDIRECTORYRESULTS.VSQUERYADDDIRECTORYRESULTS_AddNotOK)
{
return false;
}
}
}
return true;
}
public bool QueryFolderRemove(HierarchyNode targetFolder, string path) {
if (!disableQueryEdit)
{
var queryTrack = this.GetService(typeof(SVsTrackProjectDocuments)) as IVsTrackProjectDocuments2;
if (queryTrack != null)
{
VSQUERYREMOVEDIRECTORYRESULTS[] res = new VSQUERYREMOVEDIRECTORYRESULTS[1];
ErrorHandler.ThrowOnFailure(
queryTrack.OnQueryRemoveDirectories(
GetOuterInterface<IVsProject>(),
1,
new[] { CommonUtils.GetAbsoluteFilePath(GetBaseDirectoryForAddingFiles(targetFolder), Path.GetFileName(path)) },
new[] { VSQUERYREMOVEDIRECTORYFLAGS.VSQUERYREMOVEDIRECTORYFLAGS_padding },
res,
res
)
);
if (res[0] == VSQUERYREMOVEDIRECTORYRESULTS.VSQUERYREMOVEDIRECTORYRESULTS_RemoveNotOK)
{
return false;
}
}
}
return true;
}
/// <summary>
/// Given a node determines what is the directory that can accept files.
/// If the node is a FoldeNode than it is the Url of the Folder.
/// If the node is a ProjectNode it is the project folder.
/// Otherwise (such as FileNode subitem) it delegate the resolution to the parent node.
/// </summary>
public string GetBaseDirectoryForAddingFiles(HierarchyNode nodeToAddFile)
{
string baseDir = String.Empty;
if (nodeToAddFile is FolderNode)
{
baseDir = nodeToAddFile.Url;
}
else if (nodeToAddFile is ProjectNode)
{
baseDir = this.ProjectHome;
}
else if (nodeToAddFile != null)
{
baseDir = GetBaseDirectoryForAddingFiles(nodeToAddFile.Parent);
}
return baseDir;
}
/// <summary>
/// For public use only.
/// This creates a copy of an existing configuration and add it to the project.
/// Caller should change the condition on the PropertyGroup.
/// If derived class want to accomplish this, they should call ConfigProvider.AddCfgsOfCfgName()
/// It is expected that in the future MSBuild will have support for this so we don't have to
/// do it manually.
/// </summary>
/// <param name="group">PropertyGroup to clone</param>
/// <returns></returns>
public MSBuildConstruction.ProjectPropertyGroupElement ClonePropertyGroup(MSBuildConstruction.ProjectPropertyGroupElement group)
{
// Create a new (empty) PropertyGroup
MSBuildConstruction.ProjectPropertyGroupElement newPropertyGroup = this.buildProject.Xml.AddPropertyGroup();
// Now copy everything from the group we are trying to clone to the group we are creating
if (!String.IsNullOrEmpty(group.Condition))
newPropertyGroup.Condition = group.Condition;
foreach (MSBuildConstruction.ProjectPropertyElement prop in group.Properties)
{
MSBuildConstruction.ProjectPropertyElement newProperty = newPropertyGroup.AddProperty(prop.Name, prop.Value);
if (!String.IsNullOrEmpty(prop.Condition))
newProperty.Condition = prop.Condition;
}
return newPropertyGroup;
}
/// <summary>
/// Get the project extensions
/// </summary>
/// <returns></returns>
public MSBuildConstruction.ProjectExtensionsElement GetProjectExtensions()
{
var extensionsElement = this.buildProject.Xml.ChildrenReversed.OfType<MSBuildConstruction.ProjectExtensionsElement>().FirstOrDefault();
if (extensionsElement == null)
{
extensionsElement = this.buildProject.Xml.CreateProjectExtensionsElement();
this.buildProject.Xml.AppendChild(extensionsElement);
}
return extensionsElement;
}
/// <summary>
/// Set the xmlText as a project extension element with the id passed.
/// </summary>
/// <param name="id">The id of the project extension element.</param>
/// <param name="xmlText">The value to set for a project extension.</param>
public void SetProjectExtensions(string id, string xmlText)
{
MSBuildConstruction.ProjectExtensionsElement element = this.GetProjectExtensions();
// If it doesn't already have a value and we're asked to set it to
// nothing, don't do anything. Same as old OM. Keeps project neat.
if (element == null)
{
if (xmlText.Length == 0)
{
return;
}
element = this.buildProject.Xml.CreateProjectExtensionsElement();
this.buildProject.Xml.AppendChild(element);
}
element[id] = xmlText;
}
/// <summary>
/// Register the project with the Scc manager.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Scc")]
protected void RegisterSccProject()
{
if (this.isRegisteredWithScc || String.IsNullOrEmpty(this.sccProjectName))
{
return;
}
IVsSccManager2 sccManager = this.Site.GetService(typeof(SVsSccManager)) as IVsSccManager2;
if (sccManager != null)
{
ErrorHandler.ThrowOnFailure(sccManager.RegisterSccProject(this, this.sccProjectName, this.sccAuxPath, this.sccLocalPath, this.sccProvider));
this.isRegisteredWithScc = true;
}
}
/// <summary>
/// Unregisters us from the SCC manager
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "UnRegister")]
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Un")]
protected void UnRegisterProject()
{
if (!this.isRegisteredWithScc)
{
return;
}
IVsSccManager2 sccManager = this.Site.GetService(typeof(SVsSccManager)) as IVsSccManager2;
if (sccManager != null)
{
ErrorHandler.ThrowOnFailure(sccManager.UnregisterSccProject(this));
this.isRegisteredWithScc = false;
}
}
/// <summary>
/// Get the CATID corresponding to the specified type.
/// </summary>
/// <param name="type">Type of the object for which you want the CATID</param>
/// <returns>CATID</returns>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CATID")]
public Guid GetCATIDForType(Type type)
{
VsUtilities.ArgumentNotNull("type", type);
if (catidMapping.ContainsKey(type))
return catidMapping[type];
// If you get here and you want your object to be extensible, then add a call to AddCATIDMapping() in your project constructor
return Guid.Empty;
}
/// <summary>
/// This is used to specify a CATID corresponding to a BrowseObject or an ExtObject.
/// The CATID can be any GUID you choose. For types which are your owns, you could use
/// their type GUID, while for other types (such as those provided in the MPF) you should
/// provide a different GUID.
/// </summary>
/// <param name="type">Type of the extensible object</param>
/// <param name="catid">GUID that extender can use to uniquely identify your object type</param>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "catid")]
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CATID")]
protected void AddCATIDMapping(Type type, Guid catid)
{
catidMapping.Add(type, catid);
}
/// <summary>
/// Initialize an object with an XML fragment.
/// </summary>
/// <param name="iPersistXMLFragment">Object that support being initialized with an XML fragment</param>
/// <param name="configName">Name of the configuration being initialized, null if it is the project</param>
public void LoadXmlFragment(IPersistXMLFragment persistXmlFragment, string configName, string platformName)
{
VsUtilities.ArgumentNotNull("persistXmlFragment", persistXmlFragment);
if (xmlFragments == null)
{
// Retrieve the xml fragments from MSBuild
xmlFragments = new XmlDocument();
string fragments = GetProjectExtensions()[ProjectFileConstants.VisualStudio];
fragments = String.Format(CultureInfo.InvariantCulture, "<root>{0}</root>", fragments);
xmlFragments.LoadXml(fragments);
}
// We need to loop through all the flavors
string flavorsGuid;
ErrorHandler.ThrowOnFailure(((IVsAggregatableProject)this).GetAggregateProjectTypeGuids(out flavorsGuid));
foreach (Guid flavor in VsUtilities.GuidsArrayFromSemicolonDelimitedStringOfGuids(flavorsGuid))
{
// Look for a matching fragment
string flavorGuidString = flavor.ToString("B");
string fragment = null;
XmlNode node = null;
foreach (XmlNode child in xmlFragments.FirstChild.ChildNodes)
{
if (child.Attributes.Count > 0)
{
//TODO: check for platformName
string guid = String.Empty;
string configuration = String.Empty;
if (child.Attributes[ProjectFileConstants.Guid] != null)
guid = child.Attributes[ProjectFileConstants.Guid].Value;
if (child.Attributes[ProjectFileConstants.Configuration] != null)
configuration = child.Attributes[ProjectFileConstants.Configuration].Value;
if (String.Compare(child.Name, ProjectFileConstants.FlavorProperties, StringComparison.OrdinalIgnoreCase) == 0
&& String.Compare(guid, flavorGuidString, StringComparison.OrdinalIgnoreCase) == 0
&& ((String.IsNullOrEmpty(configName) && String.IsNullOrEmpty(configuration))
|| (String.Compare(configuration, configName, StringComparison.OrdinalIgnoreCase) == 0)))
{
// we found the matching fragment
fragment = child.InnerXml;
node = child;
break;
}
}
}
Guid flavorGuid = flavor;
if (String.IsNullOrEmpty(fragment))
{
// the fragment was not found so init with default values
ErrorHandler.ThrowOnFailure(persistXmlFragment.InitNew(ref flavorGuid, (uint)_PersistStorageType.PST_PROJECT_FILE));
// While we don't yet support user files, our flavors might, so we will store that in the project file until then
// TODO: Refactor this code when we support user files
ErrorHandler.ThrowOnFailure(persistXmlFragment.InitNew(ref flavorGuid, (uint)_PersistStorageType.PST_USER_FILE));
}
else
{
ErrorHandler.ThrowOnFailure(persistXmlFragment.Load(ref flavorGuid, (uint)_PersistStorageType.PST_PROJECT_FILE, fragment));
// While we don't yet support user files, our flavors might, so we will store that in the project file until then
// TODO: Refactor this code when we support user files
if (node.NextSibling != null && node.NextSibling.Attributes[ProjectFileConstants.User] != null)
ErrorHandler.ThrowOnFailure(persistXmlFragment.Load(ref flavorGuid, (uint)_PersistStorageType.PST_USER_FILE, node.NextSibling.InnerXml));
}
}
}
/// <summary>
/// Retrieve all XML fragments that need to be saved from the flavors and store the information in msbuild.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "XML")]
protected void PersistXMLFragments()
{
if (this.IsFlavorDirty() != 0)
{
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement("ROOT");
// We will need the list of configuration inside the loop, so get it before entering the loop
uint[] count = new uint[1];
IVsCfg[] configs = null;
int hr = this.ConfigProvider.GetCfgs(0, null, count, null);
if (ErrorHandler.Succeeded(hr) && count[0] > 0)
{
configs = new IVsCfg[count[0]];
hr = this.ConfigProvider.GetCfgs((uint)configs.Length, configs, count, null);
if (ErrorHandler.Failed(hr))
count[0] = 0;
}
if (count[0] == 0)
configs = new IVsCfg[0];
// We need to loop through all the flavors
string flavorsGuid;
ErrorHandler.ThrowOnFailure(((IVsAggregatableProject)this).GetAggregateProjectTypeGuids(out flavorsGuid));
foreach (Guid flavor in VsUtilities.GuidsArrayFromSemicolonDelimitedStringOfGuids(flavorsGuid))
{
IPersistXMLFragment outerHierarchy = GetOuterInterface<IPersistXMLFragment>();
// First check the project
if (outerHierarchy != null)
{
// Retrieve the XML fragment
string fragment = string.Empty;
Guid flavorGuid = flavor;
ErrorHandler.ThrowOnFailure((outerHierarchy).Save(ref flavorGuid, (uint)_PersistStorageType.PST_PROJECT_FILE, out fragment, 1));
if (!String.IsNullOrEmpty(fragment))
{
// Add the fragment to our XML
WrapXmlFragment(doc, root, flavor, null, fragment);
}
// While we don't yet support user files, our flavors might, so we will store that in the project file until then
// TODO: Refactor this code when we support user files
fragment = String.Empty;
ErrorHandler.ThrowOnFailure((outerHierarchy).Save(ref flavorGuid, (uint)_PersistStorageType.PST_USER_FILE, out fragment, 1));
if (!String.IsNullOrEmpty(fragment))
{
// Add the fragment to our XML
XmlElement node = WrapXmlFragment(doc, root, flavor, null, fragment);
node.Attributes.Append(doc.CreateAttribute(ProjectFileConstants.User));
}
}
// Then look at the configurations
foreach (IVsCfg config in configs)
{
// Get the fragment for this flavor/config pair
string fragment;
ErrorHandler.ThrowOnFailure(((Config)config).GetXmlFragment(flavor, _PersistStorageType.PST_PROJECT_FILE, out fragment));
if (!String.IsNullOrEmpty(fragment))
{
WrapXmlFragment(doc, root, flavor, ((Config)config).ConfigName, fragment);
}
}
}
if (root.ChildNodes != null && root.ChildNodes.Count > 0)
{
// Save our XML (this is only the non-build information for each flavor) in msbuild
SetProjectExtensions(ProjectFileConstants.VisualStudio, root.InnerXml.ToString());
}
}
}
#endregion
#region IVsGetCfgProvider Members
//=================================================================================
public virtual int GetCfgProvider(out IVsCfgProvider p)
{
// Be sure to call the property here since that is doing a polymorhic ProjectConfig creation.
p = this.ConfigProvider;
return (p == null ? VSConstants.E_NOTIMPL : VSConstants.S_OK);
}
#endregion
#region IPersist Members
public int GetClassID(out Guid clsid)
{
clsid = this.ProjectGuid;
return VSConstants.S_OK;
}
#endregion
#region IPersistFileFormat Members
int IPersistFileFormat.GetClassID(out Guid clsid)
{
clsid = this.ProjectGuid;
return VSConstants.S_OK;
}
public virtual int GetCurFile(out string name, out uint formatIndex)
{
name = this.filename;
formatIndex = 0;
return VSConstants.S_OK;
}
public virtual int GetFormatList(out string formatlist)
{
formatlist = String.Empty;
return VSConstants.S_OK;
}
public virtual int InitNew(uint formatIndex)
{
return VSConstants.S_OK;
}
public virtual int IsDirty(out int isDirty)
{
isDirty = 0;
if (this.buildProject.Xml.HasUnsavedChanges || this.IsProjectFileDirty)
{
isDirty = 1;
return VSConstants.S_OK;
}
isDirty = IsFlavorDirty();
return VSConstants.S_OK;
}
/// <summary>
/// Get the outer IVsHierarchy implementation.
/// This is used for scenario where a flavor may be modifying the behavior
/// </summary>
public IVsHierarchy GetOuterHierarchy() {
IVsHierarchy hierarchy = null;
// The hierarchy of a node is its project node hierarchy
IntPtr projectUnknown = Marshal.GetIUnknownForObject(this);
try {
hierarchy = (IVsHierarchy)Marshal.GetTypedObjectForIUnknown(projectUnknown, typeof(IVsHierarchy));
} finally {
if (projectUnknown != IntPtr.Zero) {
Marshal.Release(projectUnknown);
}
}
return hierarchy;
}
public T GetOuterInterface<T>() where T : class {
return GetOuterHierarchy() as T;
}
protected int IsFlavorDirty()
{
int isDirty = 0;
// See if one of our flavor consider us dirty
IPersistXMLFragment outerHierarchy = GetOuterInterface<IPersistXMLFragment>();
if (outerHierarchy != null)
{
// First check the project
ErrorHandler.ThrowOnFailure(outerHierarchy.IsFragmentDirty((uint)_PersistStorageType.PST_PROJECT_FILE, out isDirty));
// While we don't yet support user files, our flavors might, so we will store that in the project file until then
// TODO: Refactor this code when we support user files
if (isDirty == 0)
ErrorHandler.ThrowOnFailure(outerHierarchy.IsFragmentDirty((uint)_PersistStorageType.PST_USER_FILE, out isDirty));
}
if (isDirty == 0)
{
// Then look at the configurations
uint[] count = new uint[1];
int hr = this.ConfigProvider.GetCfgs(0, null, count, null);
if (ErrorHandler.Succeeded(hr) && count[0] > 0)
{
// We need to loop through the configurations
IVsCfg[] configs = new IVsCfg[count[0]];
hr = this.ConfigProvider.GetCfgs((uint)configs.Length, configs, count, null);
Debug.Assert(ErrorHandler.Succeeded(hr), "failed to retrieve configurations");
foreach (IVsCfg config in configs)
{
isDirty = ((Config)config).IsFlavorDirty(_PersistStorageType.PST_PROJECT_FILE);
if (isDirty != 0)
break;
}
}
}
return isDirty;
}
public virtual int Load(string fileName, uint mode, int readOnly)
{
this.filename = fileName;
this.Reload();
return VSConstants.S_OK;
}
public virtual int Save(string fileToBeSaved, int remember, uint formatIndex)
{
// The file name can be null. Then try to use the Url.
string tempFileToBeSaved = fileToBeSaved;
if (String.IsNullOrEmpty(tempFileToBeSaved) && !String.IsNullOrEmpty(this.Url))
{
tempFileToBeSaved = this.Url;
}
if (String.IsNullOrEmpty(tempFileToBeSaved))
{
throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "fileToBeSaved");
}
bool setProjectFileDirtyAfterSave = false;
if (remember == 0)
{
setProjectFileDirtyAfterSave = this.IsProjectFileDirty;
}
// Update the project with the latest flavor data (if needed)
PersistXMLFragments();
int result = VSConstants.S_OK;
bool saveAs = true;
if (CommonUtils.IsSamePath(tempFileToBeSaved, this.filename))
{
saveAs = false;
}
if (!saveAs)
{
SuspendFileChanges fileChanges = new SuspendFileChanges(this.Site, this.filename);
fileChanges.Suspend();
try
{
// Ensure the directory exist
string saveFolder = Path.GetDirectoryName(tempFileToBeSaved);
if (!Directory.Exists(saveFolder))
Directory.CreateDirectory(saveFolder);
// Save the project
SaveMSBuildProjectFile(tempFileToBeSaved);
this.SetProjectFileDirty(false);
}
finally
{
fileChanges.Resume();
}
}
else
{
result = this.SaveAs(tempFileToBeSaved);
if (result != VSConstants.OLE_E_PROMPTSAVECANCELLED)
{
ErrorHandler.ThrowOnFailure(result);
}
}
if (setProjectFileDirtyAfterSave)
{
this.SetProjectFileDirty(true);
}
return result;
}
protected virtual void SaveMSBuildProjectFile(string filename)
{
buildProject.Save(filename);
}
public virtual int SaveCompleted(string filename)
{
// TODO: turn file watcher back on.
return VSConstants.S_OK;
}
#endregion
#region IVsProject3 Members
/// <summary>
/// Callback from the additem dialog. Deals with adding new and existing items
/// </summary>
public virtual int GetMkDocument(uint itemId, out string mkDoc)
{
mkDoc = null;
if (itemId == VSConstants.VSITEMID_SELECTION)
{
return VSConstants.E_UNEXPECTED;
}
HierarchyNode n = this.NodeFromItemId(itemId);
if (n == null)
{
return VSConstants.E_INVALIDARG;
}
mkDoc = n.GetMkDocument();
if (String.IsNullOrEmpty(mkDoc))
{
return VSConstants.E_FAIL;
}
return VSConstants.S_OK;
}
public virtual int AddItem(uint itemIdLoc, VSADDITEMOPERATION op, string itemName, uint filesToOpen, string[] files, IntPtr dlgOwner, VSADDRESULT[] result)
{
Guid empty = Guid.Empty;
return AddItemWithSpecific(itemIdLoc, op, itemName, filesToOpen, files, dlgOwner, 0, ref empty, null, ref empty, result);
}
/// <summary>
/// Creates new items in a project, adds existing files to a project, or causes Add Item wizards to be run
/// </summary>
/// <param name="itemIdLoc"></param>
/// <param name="op"></param>
/// <param name="itemName"></param>
/// <param name="filesToOpen"></param>
/// <param name="files">Array of file names.
/// If dwAddItemOperation is VSADDITEMOP_CLONEFILE the first item in the array is the name of the file to clone.
/// If dwAddItemOperation is VSADDITEMOP_OPENDIRECTORY, the first item in the array is the directory to open.
/// If dwAddItemOperation is VSADDITEMOP_RUNWIZARD, the first item is the name of the wizard to run,
/// and the second item is the file name the user supplied (same as itemName).</param>
/// <param name="dlgOwner"></param>
/// <param name="editorFlags"></param>
/// <param name="editorType"></param>
/// <param name="physicalView"></param>
/// <param name="logicalView"></param>
/// <param name="result"></param>
/// <returns>S_OK if it succeeds </returns>
/// <remarks>The result array is initalized to failure.</remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
public virtual int AddItemWithSpecific(uint itemIdLoc, VSADDITEMOPERATION op, string itemName, uint filesToOpen, string[] files, IntPtr dlgOwner, uint editorFlags, ref Guid editorType, string physicalView, ref Guid logicalView, VSADDRESULT[] result)
{
return AddItemWithSpecificInternal(itemIdLoc, op, itemName, filesToOpen, files, dlgOwner, editorFlags, ref editorType, physicalView, ref logicalView, result);
}
// TODO: Refactor me into something sane
public int AddItemWithSpecificInternal(uint itemIdLoc, VSADDITEMOPERATION op, string itemName, uint filesToOpen, string[] files, IntPtr dlgOwner, uint editorFlags, ref Guid editorType, string physicalView, ref Guid logicalView, VSADDRESULT[] result, bool? promptOverwrite = null)
{
if (files == null || result == null || files.Length == 0 || result.Length == 0)
{
return VSConstants.E_INVALIDARG;
}
// Locate the node to be the container node for the file(s) being added
// only projectnode or foldernode and file nodes are valid container nodes
// We need to locate the parent since the item wizard expects the parent to be passed.
HierarchyNode n = this.NodeFromItemId(itemIdLoc);
if (n == null)
{
return VSConstants.E_INVALIDARG;
}
while (!n.CanAddFiles && (!this.CanFileNodesHaveChilds || !(n is FileNode)))
{
n = n.Parent;
}
Debug.Assert(n != null, "We should at this point have either a ProjectNode or FolderNode or a FileNode as a container for the new filenodes");
// handle link and runwizard operations at this point
bool isLink = false;
switch (op)
{
case VSADDITEMOPERATION.VSADDITEMOP_LINKTOFILE:
// we do not support this right now
isLink = true;
break;
case VSADDITEMOPERATION.VSADDITEMOP_RUNWIZARD:
result[0] = this.RunWizard(n, itemName, files[0], dlgOwner);
return VSConstants.S_OK;
}
string[] actualFiles = new string[files.Length];
VSQUERYADDFILEFLAGS[] flags = this.GetQueryAddFileFlags(files);
string baseDir = this.GetBaseDirectoryForAddingFiles(n);
// If we did not get a directory for node that is the parent of the item then fail.
if (String.IsNullOrEmpty(baseDir))
{
return VSConstants.E_FAIL;
}
// Pre-calculates some paths that we can use when calling CanAddItems
List<string> filesToAdd = new List<string>();
for (int index = 0; index < files.Length; index++)
{
string newFileName = String.Empty;
string file = files[index];
switch (op)
{
case VSADDITEMOPERATION.VSADDITEMOP_CLONEFILE:
{
string fileName = Path.GetFileName(itemName ?? file);
newFileName = CommonUtils.GetAbsoluteFilePath(baseDir, fileName);
}
break;
case VSADDITEMOPERATION.VSADDITEMOP_LINKTOFILE:
case VSADDITEMOPERATION.VSADDITEMOP_OPENFILE:
{
string fileName = Path.GetFileName(file);
newFileName = CommonUtils.GetAbsoluteFilePath(baseDir, fileName);
var friendlyPath = CommonUtils.CreateFriendlyFilePath(ProjectHome, file);
if (isLink && CommonUtils.IsSubpathOf(ProjectHome, file))
{
// creating a link to a file that's actually in the project, it's not really a link.
isLink = false;
newFileName = file;
n = this.CreateFolderNodes(Path.GetDirectoryName(file));
}
}
break;
}
filesToAdd.Add(newFileName);
}
// Ask tracker objects if we can add files
if (!this.tracker.CanAddItems(filesToAdd.ToArray(), flags))
{
// We were not allowed to add the files
return VSConstants.E_FAIL;
}
if (!this.QueryEditProjectFile(false))
{
throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED);
}
// Add the files to the hierarchy
int actualFilesAddedIndex = 0;
for (int index = 0; index < filesToAdd.Count; index++)
{
HierarchyNode child;
bool overwrite = false;
MsBuildProjectElement linkedFile = null;
string newFileName = filesToAdd[index];
string file = files[index];
result[0] = VSADDRESULT.ADDRESULT_Failure;
child = this.FindNodeByFullPath(newFileName);
if (child != null)
{
// If the file to be added is an existing file part of the hierarchy then continue.
if (CommonUtils.IsSamePath(file, newFileName))
{
if (child.IsNonMemberItem)
{
// https://pytools.codeplex.com/workitem/1251
ErrorHandler.ThrowOnFailure(child.IncludeInProject(false));
}
result[0] = VSADDRESULT.ADDRESULT_Cancel;
continue;
}
else if (isLink)
{
string message = "There is already a file of the same name in this folder.";
string title = string.Empty;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_QUERY;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton);
result[0] = VSADDRESULT.ADDRESULT_Cancel;
return (int)OleConstants.OLECMDERR_E_CANCELED;
}
else
{
int canOverWriteExistingItem = CanOverwriteExistingItem(file, newFileName, !child.IsNonMemberItem);
if (canOverWriteExistingItem == E_CANCEL_FILE_ADD)
{
result[0] = VSADDRESULT.ADDRESULT_Cancel;
return (int)OleConstants.OLECMDERR_E_CANCELED;
}
else if (canOverWriteExistingItem == (int)OleConstants.OLECMDERR_E_CANCELED)
{
result[0] = VSADDRESULT.ADDRESULT_Cancel;
return canOverWriteExistingItem;
}
else if (canOverWriteExistingItem == VSConstants.S_OK)
{
overwrite = true;
}
else
{
return canOverWriteExistingItem;
}
}
}
else
{
if (isLink)
{
child = this.FindNodeByFullPath(file);
if (child != null)
{
string message = String.Format("There is already a link to '{0}'. A project cannot have more than one link to the same file.", file);
string title = string.Empty;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_QUERY;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton);
result[0] = VSADDRESULT.ADDRESULT_Cancel;
return (int)OleConstants.OLECMDERR_E_CANCELED;
}
}
if (newFileName.Length >= NativeMethods.MAX_PATH) {
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
VsShellUtilities.ShowMessageBox(Site, FolderNode.PathTooLongMessage, null, icon, buttons, defaultButton);
result[0] = VSADDRESULT.ADDRESULT_Cancel;
return (int)OleConstants.OLECMDERR_E_CANCELED;
}
// we need to figure out where this file would be added and make sure there's
// not an existing link node at the same location
string filename = Path.GetFileName(newFileName);
var folder = this.FindNodeByFullPath(Path.GetDirectoryName(newFileName));
if (folder != null)
{
if (folder.FindImmediateChildByName(filename) != null) {
string message = "There is already a file of the same name in this folder.";
string title = string.Empty;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_QUERY;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton);
result[0] = VSADDRESULT.ADDRESULT_Cancel;
return (int)OleConstants.OLECMDERR_E_CANCELED;
}
}
}
// If the file to be added is not in the same path copy it.
if (!CommonUtils.IsSamePath(file, newFileName) || Directory.Exists(newFileName))
{
if (!overwrite && File.Exists(newFileName))
{
var existingChild = this.FindNodeByFullPath(file);
if (existingChild == null || !existingChild.IsLinkFile)
{
string message = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.FileAlreadyExists, CultureInfo.CurrentUICulture), newFileName);
string title = string.Empty;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_QUERY;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_YESNO;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
if (isLink)
{
message = "There is already a file of the same name in this folder.";
buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
}
int messageboxResult = VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton);
if (messageboxResult != NativeMethods.IDYES)
{
result[0] = VSADDRESULT.ADDRESULT_Cancel;
return (int)OleConstants.OLECMDERR_E_CANCELED;
}
}
}
var updatingNode = this.FindNodeByFullPath(file);
if (updatingNode != null && updatingNode.IsLinkFile)
{
// we just need to update the link to the new path.
linkedFile = updatingNode.ItemNode as MsBuildProjectElement;
}
else if (Directory.Exists(file))
{
// http://pytools.codeplex.com/workitem/546
int hr = AddDirectory(result, n, file, promptOverwrite);
if (ErrorHandler.Failed(hr)) {
return hr;
}
result[0] = VSADDRESULT.ADDRESULT_Success;
continue;
}
else if (!isLink)
{
// Copy the file to the correct location.
// We will suppress the file change events to be triggered to this item, since we are going to copy over the existing file and thus we will trigger a file change event.
// We do not want the filechange event to ocur in this case, similar that we do not want a file change event to occur when saving a file.
IVsFileChangeEx fileChange = this.site.GetService(typeof(SVsFileChangeEx)) as IVsFileChangeEx;
VsUtilities.CheckNotNull(fileChange);
try
{
ErrorHandler.ThrowOnFailure(fileChange.IgnoreFile(VSConstants.VSCOOKIE_NIL, newFileName, 1));
if (op == VSADDITEMOPERATION.VSADDITEMOP_CLONEFILE)
{
this.AddFileFromTemplate(file, newFileName);
}
else
{
PackageUtilities.CopyUrlToLocal(new Uri(file), newFileName);
// Reset RO attribute on file if present - for example, if source file was under TFS control and not checked out.
try {
var fileInfo = new FileInfo(newFileName);
if (fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly)) {
fileInfo.Attributes &= ~FileAttributes.ReadOnly;
}
} catch (Exception) {
// Best-effort, but no big deal if this fails.
}
}
}
finally
{
ErrorHandler.ThrowOnFailure(fileChange.IgnoreFile(VSConstants.VSCOOKIE_NIL, newFileName, 0));
}
}
}
if (overwrite)
{
if (child.IsNonMemberItem)
{
ErrorHandler.ThrowOnFailure(child.IncludeInProject(false));
}
}
else if (linkedFile != null || isLink)
{
// files not moving, add the old name, and set the link.
var friendlyPath = CommonUtils.GetRelativeFilePath(ProjectHome, file);
FileNode newChild;
if (linkedFile == null)
{
Debug.Assert(!CommonUtils.IsSubpathOf(ProjectHome, file), "Should have cleared isLink above for file in project dir");
newChild = CreateFileNode(file);
}
else
{
newChild = CreateFileNode(linkedFile);
}
newChild.SetIsLinkFile(true);
newChild.ItemNode.SetMetadata(ProjectFileConstants.Link, CommonUtils.CreateFriendlyFilePath(ProjectFolder, newFileName));
n.AddChild(newChild);
DocumentManager.RenameDocument(site, file, file, n.ID);
LinkFileAdded(file);
SetProjectFileDirty(true);
}
else
{
//Add new filenode/dependentfilenode
this.AddNewFileNodeToHierarchy(n, newFileName);
}
result[0] = VSADDRESULT.ADDRESULT_Success;
actualFiles[actualFilesAddedIndex++] = newFileName;
}
// Notify listeners that items were appended.
if (actualFilesAddedIndex > 0)
OnItemsAppended(n);
//Open files if this was requested through the editorFlags
bool openFiles = (editorFlags & (uint)__VSSPECIFICEDITORFLAGS.VSSPECIFICEDITOR_DoOpen) != 0;
if (openFiles && actualFiles.Length <= filesToOpen)
{
for (int i = 0; i < filesToOpen; i++)
{
if (!String.IsNullOrEmpty(actualFiles[i]))
{
string name = actualFiles[i];
HierarchyNode child = this.FindNodeByFullPath(name);
Debug.Assert(child != null, "We should have been able to find the new element in the hierarchy");
if (child != null)
{
IVsWindowFrame frame;
if (editorType == Guid.Empty)
{
Guid view = Guid.Empty;
ErrorHandler.ThrowOnFailure(this.OpenItem(child.ID, ref view, IntPtr.Zero, out frame));
}
else
{
ErrorHandler.ThrowOnFailure(this.OpenItemWithSpecific(child.ID, editorFlags, ref editorType, physicalView, ref logicalView, IntPtr.Zero, out frame));
}
// Show the window frame in the UI and make it the active window
if (frame != null)
{
ErrorHandler.ThrowOnFailure(frame.Show());
}
}
}
}
}
return VSConstants.S_OK;
}
/// <summary>
/// Adds a folder into the project recursing and adding any sub-files and sub-directories.
///
/// The user can be prompted to overwrite the existing files if the folder already exists
/// in the project. They will be initially prompted to overwrite - if they answer no
/// we'll set promptOverwrite to false and when we recurse we won't prompt. If they say
/// yes then we'll set it to true and we will prompt for individual files.
/// </summary>
private int AddDirectory(VSADDRESULT[] result, HierarchyNode n, string file, bool? promptOverwrite)
{
// need to recursively add all of the directory contents
HierarchyNode targetFolder = n.FindImmediateChildByName(Path.GetFileName(file));
if (targetFolder == null)
{
var fullPath = Path.Combine(GetBaseDirectoryForAddingFiles(n), Path.GetFileName(file));
Directory.CreateDirectory(fullPath);
var newChild = CreateFolderNode(fullPath);
n.AddChild(newChild);
targetFolder = newChild;
}
else if (targetFolder.IsNonMemberItem)
{
int hr = targetFolder.IncludeInProject(true);
if (ErrorHandler.Failed(hr))
{
return hr;
}
OnItemAdded(targetFolder.Parent, targetFolder);
return hr;
}
else if (promptOverwrite == null)
{
var res = MessageBox.Show(
String.Format(
@"This folder already contains a folder called '{0}'.
If the files in the existing folder have the same names as files in the folder you are copying, do you want to replace the existing files?", Path.GetFileName(file)),
"Merge Folders",
MessageBoxButtons.YesNoCancel
);
// yes means prompt for each file
// no means don't prompt for any of the files
// cancel means forget what I'm doing
switch (res) {
case DialogResult.Cancel:
result[0] = VSADDRESULT.ADDRESULT_Cancel;
return (int)OleConstants.OLECMDERR_E_CANCELED;
case DialogResult.No:
promptOverwrite = false;
break;
case DialogResult.Yes:
promptOverwrite = true;
break;
}
}
Guid empty = Guid.Empty;
// add the files...
var dirFiles = Directory.GetFiles(file);
if (dirFiles.Length > 0) {
var subRes = AddItemWithSpecificInternal(
targetFolder.ID,
VSADDITEMOPERATION.VSADDITEMOP_CLONEFILE,
null,
(uint)dirFiles.Length,
dirFiles,
IntPtr.Zero,
0,
ref empty,
null,
ref empty,
result,
promptOverwrite: promptOverwrite
);
if (ErrorHandler.Failed(subRes)) {
return subRes;
}
}
// add any subdirectories...
var subDirs = Directory.GetDirectories(file);
if (subDirs.Length > 0) {
return AddItemWithSpecificInternal(
targetFolder.ID,
VSADDITEMOPERATION.VSADDITEMOP_CLONEFILE,
null,
(uint)subDirs.Length,
subDirs,
IntPtr.Zero,
0,
ref empty,
null,
ref empty,
result,
promptOverwrite: promptOverwrite
);
}
return VSConstants.S_OK;
}
protected virtual void LinkFileAdded(string filename)
{
}
private static string GetIncrementedFileName(string newFileName, int count)
{
return CommonUtils.GetAbsoluteFilePath(Path.GetDirectoryName(newFileName), Path.GetFileNameWithoutExtension(newFileName) + " - Copy (" + count + ")" + Path.GetExtension(newFileName));
}
/// <summary>
/// for now used by add folder. Called on the ROOT, as only the project should need
/// to implement this.
/// for folders, called with parent folder, blank extension and blank suggested root
/// </summary>
public virtual int GenerateUniqueItemName(uint itemIdLoc, string ext, string suggestedRoot, out string itemName)
{
string rootName = String.Empty;
string extToUse = String.Empty;
itemName = String.Empty;
//force new items to have a number
int cb = 1;
bool found = false;
bool fFolderCase = false;
HierarchyNode parent = this.NodeFromItemId(itemIdLoc);
if (!String.IsNullOrEmpty(ext))
{
extToUse = ext.Trim();
}
if (!String.IsNullOrEmpty(suggestedRoot))
{
suggestedRoot = suggestedRoot.Trim();
}
if (suggestedRoot == null || suggestedRoot.Length == 0)
{
// foldercase, we assume...
suggestedRoot = "NewFolder";
fFolderCase = true;
}
while (!found)
{
rootName = suggestedRoot;
if (cb > 0)
rootName += cb.ToString(CultureInfo.CurrentCulture);
if (extToUse.Length > 0)
{
rootName += extToUse;
}
cb++;
found = true;
for (HierarchyNode n = parent.FirstChild; n != null; n = n.NextSibling)
{
if (rootName == n.GetEditLabel())
{
found = false;
break;
}
//if parent is a folder, we need the whole url
string parentFolder = parent.Url;
ProjectNode parentProj = parent as ProjectNode;
if (parentProj != null)
parentFolder = parentProj.ProjectHome;
string checkFile = CommonUtils.GetAbsoluteFilePath(parentFolder, rootName);
if (fFolderCase)
{
if (Directory.Exists(checkFile))
{
found = false;
break;
}
}
else
{
if (File.Exists(checkFile))
{
found = false;
break;
}
}
}
}
itemName = rootName;
return VSConstants.S_OK;
}
public virtual int GetItemContext(uint itemId, out Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp)
{
psp = null;
HierarchyNode child = this.NodeFromItemId(itemId);
if (child != null)
{
psp = child.OleServiceProvider as IOleServiceProvider;
}
return VSConstants.S_OK;
}
public virtual int IsDocumentInProject(string mkDoc, out int found, VSDOCUMENTPRIORITY[] pri, out uint itemId)
{
if (pri != null && pri.Length >= 1)
{
pri[0] = VSDOCUMENTPRIORITY.DP_Unsupported;
}
found = 0;
itemId = 0;
// If it is the project file just return.
if (CommonUtils.IsSamePath(mkDoc, this.GetMkDocument()))
{
found = 1;
itemId = VSConstants.VSITEMID_ROOT;
}
else
{
HierarchyNode child = this.FindNodeByFullPath(EnsureRootedPath(mkDoc));
if (child != null)
{
found = 1;
itemId = child.ID;
}
}
if (found == 1)
{
if (pri != null && pri.Length >= 1)
{
pri[0] = VSDOCUMENTPRIORITY.DP_Standard;
}
}
return VSConstants.S_OK;
}
public virtual int OpenItem(uint itemId, ref Guid logicalView, IntPtr punkDocDataExisting, out IVsWindowFrame frame)
{
// Init output params
frame = null;
HierarchyNode n = this.NodeFromItemId(itemId);
if (n == null)
{
throw new ArgumentException(SR.GetString(SR.ParameterMustBeAValidItemId, CultureInfo.CurrentUICulture), "itemId");
}
// Delegate to the document manager object that knows how to open the item
DocumentManager documentManager = n.GetDocumentManager();
if (documentManager != null)
{
return documentManager.Open(ref logicalView, punkDocDataExisting, out frame, WindowFrameShowAction.DoNotShow);
}
// This node does not have an associated document manager and we must fail
return VSConstants.E_FAIL;
}
public virtual int OpenItemWithSpecific(uint itemId, uint editorFlags, ref Guid editorType, string physicalView, ref Guid logicalView, IntPtr docDataExisting, out IVsWindowFrame frame)
{
// Init output params
frame = null;
HierarchyNode n = this.NodeFromItemId(itemId);
if (n == null)
{
throw new ArgumentException(SR.GetString(SR.ParameterMustBeAValidItemId, CultureInfo.CurrentUICulture), "itemId");
}
// Delegate to the document manager object that knows how to open the item
DocumentManager documentManager = n.GetDocumentManager();
if (documentManager != null)
{
return documentManager.OpenWithSpecific(editorFlags, ref editorType, physicalView, ref logicalView, docDataExisting, out frame, WindowFrameShowAction.DoNotShow);
}
// This node does not have an associated document manager and we must fail
return VSConstants.E_FAIL;
}
public virtual int RemoveItem(uint reserved, uint itemId, out int result)
{
HierarchyNode n = this.NodeFromItemId(itemId);
if (n == null)
{
throw new ArgumentException(SR.GetString(SR.ParameterMustBeAValidItemId, CultureInfo.CurrentUICulture), "itemId");
}
n.Remove(true);
result = 1;
return VSConstants.S_OK;
}
public virtual int ReopenItem(uint itemId, ref Guid editorType, string physicalView, ref Guid logicalView, IntPtr docDataExisting, out IVsWindowFrame frame)
{
// Init output params
frame = null;
HierarchyNode n = this.NodeFromItemId(itemId);
if (n == null)
{
throw new ArgumentException(SR.GetString(SR.ParameterMustBeAValidItemId, CultureInfo.CurrentUICulture), "itemId");
}
// Delegate to the document manager object that knows how to open the item
DocumentManager documentManager = n.GetDocumentManager();
if (documentManager != null)
{
return documentManager.ReOpenWithSpecific(0, ref editorType, physicalView, ref logicalView, docDataExisting, out frame, WindowFrameShowAction.DoNotShow);
}
// This node does not have an associated document manager and we must fail
return VSConstants.E_FAIL;
}
/// <summary>
/// Implements IVsProject3::TransferItem
/// This function is called when an open miscellaneous file is being transferred
/// to our project. The sequence is for the shell to call AddItemWithSpecific and
/// then use TransferItem to transfer the open document to our project.
/// </summary>
/// <param name="oldMkDoc">Old document name</param>
/// <param name="newMkDoc">New document name</param>
/// <param name="frame">Optional frame if the document is open</param>
/// <returns></returns>
public virtual int TransferItem(string oldMkDoc, string newMkDoc, IVsWindowFrame frame)
{
// Fail if hierarchy already closed
if (this.ProjectMgr == null || this.IsClosed)
{
return VSConstants.E_FAIL;
}
//Fail if the document names passed are null.
if (oldMkDoc == null || newMkDoc == null)
return VSConstants.E_INVALIDARG;
int hr = VSConstants.S_OK;
VSDOCUMENTPRIORITY[] priority = new VSDOCUMENTPRIORITY[1];
uint itemid = VSConstants.VSITEMID_NIL;
uint cookie = 0;
uint grfFlags = 0;
IVsRunningDocumentTable pRdt = GetService(typeof(IVsRunningDocumentTable)) as IVsRunningDocumentTable;
if (pRdt == null)
return VSConstants.E_ABORT;
string doc;
int found;
IVsHierarchy pHier;
uint id, readLocks, editLocks;
IntPtr docdataForCookiePtr = IntPtr.Zero;
IntPtr docDataPtr = IntPtr.Zero;
IntPtr hierPtr = IntPtr.Zero;
// We get the document from the running doc table so that we can see if it is transient
try
{
ErrorHandler.ThrowOnFailure(pRdt.FindAndLockDocument((uint)_VSRDTFLAGS.RDT_NoLock, oldMkDoc, out pHier, out id, out docdataForCookiePtr, out cookie));
}
finally
{
if (docdataForCookiePtr != IntPtr.Zero)
Marshal.Release(docdataForCookiePtr);
}
//Get the document info
try
{
ErrorHandler.ThrowOnFailure(pRdt.GetDocumentInfo(cookie, out grfFlags, out readLocks, out editLocks, out doc, out pHier, out id, out docDataPtr));
}
finally
{
if (docDataPtr != IntPtr.Zero)
Marshal.Release(docDataPtr);
}
// Now see if the document is in the project. If not, we fail
try
{
ErrorHandler.ThrowOnFailure(IsDocumentInProject(newMkDoc, out found, priority, out itemid));
Debug.Assert(itemid != VSConstants.VSITEMID_NIL && itemid != VSConstants.VSITEMID_ROOT);
hierPtr = Marshal.GetComInterfaceForObject(this, typeof(IVsUIHierarchy));
// Now rename the document
ErrorHandler.ThrowOnFailure(pRdt.RenameDocument(oldMkDoc, newMkDoc, hierPtr, itemid));
}
finally
{
if (hierPtr != IntPtr.Zero)
Marshal.Release(hierPtr);
}
//Change the caption if we are passed a window frame
if (frame != null)
{
string caption = ((HierarchyNode)pHier).Caption;
hr = frame.SetProperty((int)(__VSFPROPID.VSFPROPID_OwnerCaption), caption);
}
return hr;
}
#endregion
#region IVsDependencyProvider Members
public int EnumDependencies(out IVsEnumDependencies enumDependencies)
{
enumDependencies = new EnumDependencies(this.buildDependencyList);
return VSConstants.S_OK;
}
public int OpenDependency(string szDependencyCanonicalName, out IVsDependency dependency)
{
dependency = null;
return VSConstants.S_OK;
}
#endregion
#region IVsComponentUser methods
/// <summary>
/// Add Components to the Project.
/// Used by the environment to add components specified by the user in the Component Selector dialog
/// to the specified project
/// </summary>
/// <param name="dwAddCompOperation">The component operation to be performed.</param>
/// <param name="cComponents">Number of components to be added</param>
/// <param name="rgpcsdComponents">array of component selector data</param>
/// <param name="hwndDialog">Handle to the component picker dialog</param>
/// <param name="pResult">Result to be returned to the caller</param>
public virtual int AddComponent(VSADDCOMPOPERATION dwAddCompOperation, uint cComponents, System.IntPtr[] rgpcsdComponents, System.IntPtr hwndDialog, VSADDCOMPRESULT[] pResult)
{
if (rgpcsdComponents == null || pResult == null)
{
return VSConstants.E_FAIL;
}
//initalize the out parameter
pResult[0] = VSADDCOMPRESULT.ADDCOMPRESULT_Success;
IReferenceContainer references = GetReferenceContainer();
if (null == references)
{
// This project does not support references or the reference container was not created.
// In both cases this operation is not supported.
return VSConstants.E_NOTIMPL;
}
for (int cCount = 0; cCount < cComponents; cCount++)
{
VSCOMPONENTSELECTORDATA selectorData = new VSCOMPONENTSELECTORDATA();
IntPtr ptr = rgpcsdComponents[cCount];
selectorData = (VSCOMPONENTSELECTORDATA)Marshal.PtrToStructure(ptr, typeof(VSCOMPONENTSELECTORDATA));
if (null == references.AddReferenceFromSelectorData(selectorData))
{
//Skip further proccessing since a reference has to be added
pResult[0] = VSADDCOMPRESULT.ADDCOMPRESULT_Failure;
return VSConstants.S_OK;
}
}
return VSConstants.S_OK;
}
#endregion
#region IVsSccProject2 Members
/// <summary>
/// This method is called to determine which files should be placed under source control for a given VSITEMID within this hierarchy.
/// </summary>
/// <param name="itemid">Identifier for the VSITEMID being queried.</param>
/// <param name="stringsOut">Pointer to an array of CALPOLESTR strings containing the file names for this item.</param>
/// <param name="flagsOut">Pointer to a CADWORD array of flags stored in DWORDs indicating that some of the files have special behaviors.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
public virtual int GetSccFiles(uint itemid, CALPOLESTR[] stringsOut, CADWORD[] flagsOut)
{
if (itemid == VSConstants.VSITEMID_SELECTION)
{
throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "itemid");
}
else if (itemid == VSConstants.VSITEMID_ROOT)
{
// Root node. Return our project file path.
if (stringsOut != null && stringsOut.Length > 0)
{
stringsOut[0] = VsUtilities.CreateCALPOLESTR(new[] { filename });
}
if (flagsOut != null && flagsOut.Length > 0)
{
flagsOut[0] = VsUtilities.CreateCADWORD(new[] { tagVsSccFilesFlags.SFF_NoFlags });
}
return VSConstants.S_OK;
}
// otherwise delegate to either a file or a folder to get the SCC files
HierarchyNode n = this.NodeFromItemId(itemid);
if (n == null)
{
throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "itemid");
}
List<string> files = new List<string>();
List<tagVsSccFilesFlags> flags = new List<tagVsSccFilesFlags>();
n.GetSccFiles(files, flags);
if (stringsOut != null && stringsOut.Length > 0)
{
stringsOut[0] = VsUtilities.CreateCALPOLESTR(files);
}
if (flagsOut != null && flagsOut.Length > 0)
{
flagsOut[0] = VsUtilities.CreateCADWORD(flags);
}
return VSConstants.S_OK;
}
public override void GetSccFiles(IList<string> files, IList<tagVsSccFilesFlags> flags)
{
for (HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling)
{
n.GetSccFiles(files, flags);
}
}
public override void GetSccSpecialFiles(string sccFile, IList<string> files, IList<tagVsSccFilesFlags> flags)
{
for (HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling)
{
n.GetSccSpecialFiles(sccFile, files, flags);
}
}
/// <summary>
/// This method is called to discover special (hidden files) associated with a given VSITEMID within this hierarchy.
/// </summary>
/// <param name="itemid">Identifier for the VSITEMID being queried.</param>
/// <param name="sccFile">One of the files associated with the node</param>
/// <param name="stringsOut">Pointer to an array of CALPOLESTR strings containing the file names for this item.</param>
/// <param name="flagsOut">Pointer to a CADWORD array of flags stored in DWORDs indicating that some of the files have special behaviors.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
/// <remarks>This method is called to discover any special or hidden files associated with an item in the project hierarchy. It is called when GetSccFiles returns with the SFF_HasSpecialFiles flag set for any of the files associated with the node.</remarks>
public virtual int GetSccSpecialFiles(uint itemid, string sccFile, CALPOLESTR[] stringsOut, CADWORD[] flagsOut)
{
if (itemid == VSConstants.VSITEMID_SELECTION)
{
throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "itemid");
}
HierarchyNode n = this.NodeFromItemId(itemid);
if (n == null)
{
throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "itemid");
}
List<string> files = new List<string>();
List<tagVsSccFilesFlags> flags = new List<tagVsSccFilesFlags>();
n.GetSccSpecialFiles(sccFile, files, flags);
if (stringsOut != null && stringsOut.Length > 0)
{
stringsOut[0] = VsUtilities.CreateCALPOLESTR(files);
}
if (flagsOut != null && flagsOut.Length > 0)
{
flagsOut[0] = VsUtilities.CreateCADWORD(flags);
}
// we have no special files.
return VSConstants.S_OK;
}
/// <summary>
/// This method is called by the source control portion of the environment to inform the project of changes to the source control glyph on various nodes.
/// </summary>
/// <param name="affectedNodes">Count of changed nodes.</param>
/// <param name="itemidAffectedNodes">An array of VSITEMID identifiers of the changed nodes.</param>
/// <param name="newGlyphs">An array of VsStateIcon glyphs representing the new state of the corresponding item in rgitemidAffectedNodes.</param>
/// <param name="newSccStatus">An array of status flags from SccStatus corresponding to rgitemidAffectedNodes. </param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
public virtual int SccGlyphChanged(int affectedNodes, uint[] itemidAffectedNodes, VsStateIcon[] newGlyphs, uint[] newSccStatus)
{
// if all the paramaters are null adn the count is 0, it means scc wants us to updated everything
if (affectedNodes == 0 && itemidAffectedNodes == null && newGlyphs == null && newSccStatus == null)
{
ReDrawNode(this, UIHierarchyElement.SccState);
this.UpdateSccStateIcons();
}
else if (affectedNodes > 0 && itemidAffectedNodes != null && newGlyphs != null && newSccStatus != null)
{
for (int i = 0; i < affectedNodes; i++)
{
HierarchyNode n = this.NodeFromItemId(itemidAffectedNodes[i]);
if (n == null)
{
throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "itemidAffectedNodes");
}
ReDrawNode(n, UIHierarchyElement.SccState);
}
}
return VSConstants.S_OK;
}
/// <summary>
/// This method is called by the source control portion of the environment when a project is initially added to source control, or to change some of the project's settings.
/// </summary>
/// <param name="sccProjectName">String, opaque to the project, that identifies the project location on the server. Persist this string in the project file. </param>
/// <param name="sccLocalPath">String, opaque to the project, that identifies the path to the server. Persist this string in the project file.</param>
/// <param name="sccAuxPath">String, opaque to the project, that identifies the local path to the project. Persist this string in the project file.</param>
/// <param name="sccProvider">String, opaque to the project, that identifies the source control package. Persist this string in the project file.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns>
public virtual int SetSccLocation(string sccProjectName, string sccAuxPath, string sccLocalPath, string sccProvider)
{
VsUtilities.ArgumentNotNull("sccProjectName", sccProjectName);
VsUtilities.ArgumentNotNull("sccAuxPath", sccAuxPath);
VsUtilities.ArgumentNotNull("sccLocalPath", sccLocalPath);
VsUtilities.ArgumentNotNull("sccProvider", sccProvider);
// Save our settings (returns true if something changed)
if (!this.SetSccSettings(sccProjectName, sccLocalPath, sccAuxPath, sccProvider))
{
return VSConstants.S_OK;
}
bool unbinding = (sccProjectName.Length == 0 && sccProvider.Length == 0);
if (unbinding || this.QueryEditProjectFile(false))
{
this.buildProject.SetProperty(ProjectFileConstants.SccProjectName, sccProjectName);
this.buildProject.SetProperty(ProjectFileConstants.SccProvider, sccProvider);
this.buildProject.SetProperty(ProjectFileConstants.SccAuxPath, sccAuxPath);
this.buildProject.SetProperty(ProjectFileConstants.SccLocalPath, sccLocalPath);
SetProjectFileDirty(true);
}
this.isRegisteredWithScc = true;
return VSConstants.S_OK;
}
#endregion
#region IVsProjectSpecialFiles Members
/// <summary>
/// Allows you to query the project for special files and optionally create them.
/// </summary>
/// <param name="fileId">__PSFFILEID of the file</param>
/// <param name="flags">__PSFFLAGS flags for the file</param>
/// <param name="itemid">The itemid of the node in the hierarchy</param>
/// <param name="fileName">The file name of the special file.</param>
/// <returns></returns>
public virtual int GetFile(int fileId, uint flags, out uint itemid, out string fileName)
{
itemid = VSConstants.VSITEMID_NIL;
fileName = String.Empty;
// We need to return S_OK, otherwise the property page tabs will not be shown.
return VSConstants.E_NOTIMPL;
}
#endregion
#region IAggregatedHierarchy Members
/// <summary>
/// Get the inner object of an aggregated hierarchy
/// </summary>
/// <returns>A HierarchyNode</returns>
public virtual HierarchyNode GetInner()
{
return this;
}
#endregion
#region IReferenceDataProvider Members
/// <summary>
/// Returns the reference container node.
/// </summary>
/// <returns></returns>
public IReferenceContainer GetReferenceContainer()
{
var container = FindImmediateChild(node => node is ReferenceContainerNode) as ReferenceContainerNode;
if (container == null)
{
container = this.CreateReferenceContainerNode();
if (container != null)
this.AddChild(container);
}
return container;
}
#if DEV11_OR_LATER
public IVsReferenceManagerUser GetReferenceManagerUser(IVsReferenceProviderContext[] contexts)
{
return new ReferenceManagerUser(contexts, this.GetReferenceContainer());
}
#endif
#endregion
#region IBuildDependencyUpdate Members
public virtual IVsBuildDependency[] BuildDependencies
{
get
{
return this.buildDependencyList.ToArray();
}
}
public virtual void AddBuildDependency(IVsBuildDependency dependency)
{
if (this.isClosed || dependency == null)
{
return;
}
if (!this.buildDependencyList.Contains(dependency))
{
this.buildDependencyList.Add(dependency);
}
}
public virtual void RemoveBuildDependency(IVsBuildDependency dependency)
{
if (this.isClosed || dependency == null)
{
return;
}
if (this.buildDependencyList.Contains(dependency))
{
this.buildDependencyList.Remove(dependency);
}
}
#endregion
#region IProjectEventsListener Members
public bool IsProjectEventsListener
{
get { return this.isProjectEventsListener; }
set { this.isProjectEventsListener = value; }
}
#endregion
#region IVsAggregatableProject Members
/// <summary>
/// Retrieve the list of project GUIDs that are aggregated together to make this project.
/// </summary>
/// <param name="projectTypeGuids">Semi colon separated list of Guids. Typically, the last GUID would be the GUID of the base project factory</param>
/// <returns>HResult</returns>
public int GetAggregateProjectTypeGuids(out string projectTypeGuids)
{
projectTypeGuids = this.GetProjectProperty(ProjectFileConstants.ProjectTypeGuids);
// In case someone manually removed this from our project file, default to our project without flavors
if (String.IsNullOrEmpty(projectTypeGuids))
projectTypeGuids = this.ProjectGuid.ToString("B");
return VSConstants.S_OK;
}
/// <summary>
/// This is where the initialization occurs.
/// </summary>
public virtual int InitializeForOuter(string filename, string location, string name, uint flags, ref Guid iid, out IntPtr projectPointer, out int canceled)
{
canceled = 0;
projectPointer = IntPtr.Zero;
// Initialize the project
//Make this async, so we don't block the ui for the total time and other projects can load too.
this.Load(filename, location, name, flags, ref iid, out canceled);
if (canceled != 1)
{
// Set ourself as the project
return Marshal.QueryInterface(Marshal.GetIUnknownForObject(this), ref iid, out projectPointer);
}
return VSConstants.OLE_E_PROMPTSAVECANCELLED;
}
/// <summary>
/// This is called after the project is done initializing the different layer of the aggregations
/// </summary>
/// <returns>HResult</returns>
public virtual int OnAggregationComplete()
{
return VSConstants.S_OK;
}
/// <summary>
/// Set the list of GUIDs that are aggregated together to create this project.
/// </summary>
/// <param name="projectTypeGuids">Semi-colon separated list of GUIDs, the last one is usually the project factory of the base project factory</param>
/// <returns>HResult</returns>
public int SetAggregateProjectTypeGuids(string projectTypeGuids)
{
this.SetProjectProperty(ProjectFileConstants.ProjectTypeGuids, projectTypeGuids);
return VSConstants.S_OK;
}
/// <summary>
/// We are always the inner most part of the aggregation
/// and as such we don't support setting an inner project
/// </summary>
public int SetInnerProject(object innerProject)
{
return VSConstants.E_NOTIMPL;
}
#endregion
#region IVsProjectFlavorCfgProvider Members
int IVsProjectFlavorCfgProvider.CreateProjectFlavorCfg(IVsCfg pBaseProjectCfg, out IVsProjectFlavorCfg ppFlavorCfg)
{
// Our config object is also our IVsProjectFlavorCfg object
ppFlavorCfg = pBaseProjectCfg as IVsProjectFlavorCfg;
return VSConstants.S_OK;
}
#endregion
#region IVsBuildPropertyStorage Members
/// <summary>
/// Get the property of an item
/// </summary>
/// <param name="item">ItemID</param>
/// <param name="attributeName">Name of the property</param>
/// <param name="attributeValue">Value of the property (out parameter)</param>
/// <returns>HRESULT</returns>
int IVsBuildPropertyStorage.GetItemAttribute(uint item, string attributeName, out string attributeValue)
{
attributeValue = null;
HierarchyNode node = NodeFromItemId(item);
if (node == null)
throw new ArgumentException("Invalid item id", "item");
if (node.ItemNode != null) {
attributeValue = node.ItemNode.GetMetadata(attributeName);
} else if(node == node.ProjectMgr) {
attributeName = node.ProjectMgr.GetProjectProperty(attributeName);
}
return VSConstants.S_OK;
}
/// <summary>
/// Get the value of the property in the project file
/// </summary>
/// <param name="propertyName">Name of the property to remove</param>
/// <param name="configName">Configuration for which to remove the property</param>
/// <param name="storage">Project or user file (_PersistStorageType)</param>
/// <param name="propertyValue">Value of the property (out parameter)</param>
/// <returns>HRESULT</returns>
int IVsBuildPropertyStorage.GetPropertyValue(string propertyName, string configName, uint storage, out string propertyValue)
{
// TODO: when adding support for User files, we need to update this method
propertyValue = null;
if (string.IsNullOrEmpty(configName))
{
propertyValue = this.GetProjectProperty(propertyName);
}
else
{
IVsCfg configurationInterface;
int platformStart;
if ((platformStart = configName.IndexOf('|')) != -1) {
// matches C# project system, GetPropertyValue handles display name, not just config name
configName = configName.Substring(0, platformStart);
}
ErrorHandler.ThrowOnFailure(this.ConfigProvider.GetCfgOfName(configName, string.Empty, out configurationInterface));
Config config = (Config)configurationInterface;
propertyValue = config.GetConfigurationProperty(propertyName, true);
}
return VSConstants.S_OK;
}
/// <summary>
/// Delete a property
/// In our case this simply mean defining it as null
/// </summary>
/// <param name="propertyName">Name of the property to remove</param>
/// <param name="configName">Configuration for which to remove the property</param>
/// <param name="storage">Project or user file (_PersistStorageType)</param>
/// <returns>HRESULT</returns>
int IVsBuildPropertyStorage.RemoveProperty(string propertyName, string configName, uint storage)
{
return ((IVsBuildPropertyStorage)this).SetPropertyValue(propertyName, configName, storage, null);
}
/// <summary>
/// Set a property on an item
/// </summary>
/// <param name="item">ItemID</param>
/// <param name="attributeName">Name of the property</param>
/// <param name="attributeValue">New value for the property</param>
/// <returns>HRESULT</returns>
int IVsBuildPropertyStorage.SetItemAttribute(uint item, string attributeName, string attributeValue)
{
HierarchyNode node = NodeFromItemId(item);
if (node == null)
throw new ArgumentException("Invalid item id", "item");
node.ItemNode.SetMetadata(attributeName, attributeValue);
return VSConstants.S_OK;
}
/// <summary>
/// Set a project property
/// </summary>
/// <param name="propertyName">Name of the property to set</param>
/// <param name="configName">Configuration for which to set the property</param>
/// <param name="storage">Project file or user file (_PersistStorageType)</param>
/// <param name="propertyValue">New value for that property</param>
/// <returns>HRESULT</returns>
int IVsBuildPropertyStorage.SetPropertyValue(string propertyName, string configName, uint storage, string propertyValue)
{
// TODO: when adding support for User files, we need to update this method
if (string.IsNullOrEmpty(configName))
{
this.SetProjectProperty(propertyName, propertyValue);
}
else
{
IVsCfg configurationInterface;
ErrorHandler.ThrowOnFailure(this.ConfigProvider.GetCfgOfName(configName, string.Empty, out configurationInterface));
Config config = (Config)configurationInterface;
config.SetConfigurationProperty(propertyName, propertyValue);
}
return VSConstants.S_OK;
}
#endregion
#region private helper methods
/// <summary>
/// Initialize projectNode
/// </summary>
private void Initialize()
{
this.ID = VSConstants.VSITEMID_ROOT;
this.tracker = new TrackDocumentsHelper(this);
uint cookie;
this.AdviseHierarchyEvents(new HierarchyEventsSink(this), out cookie);
var serviceProvider = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)(((IServiceProvider)this.Package).GetService(typeof(Microsoft.VisualStudio.OLE.Interop.IServiceProvider)));
this.SetSite(serviceProvider);
}
/// <summary>
/// Add an item to the hierarchy based on the item path
/// </summary>
/// <param name="item">Item to add</param>
/// <returns>Added node</returns>
private HierarchyNode AddIndependentFileNode(MSBuild.ProjectItem item, HierarchyNode parent)
{
return AddFileNodeToNode(item, parent);
}
/// <summary>
/// Add a dependent file node to the hierarchy
/// </summary>
/// <param name="item">msbuild item to add</param>
/// <param name="parentNode">Parent Node</param>
/// <returns>Added node</returns>
private HierarchyNode AddDependentFileNodeToNode(MSBuild.ProjectItem item, HierarchyNode parentNode)
{
FileNode node = this.CreateDependentFileNode(new MsBuildProjectElement(this, item));
parentNode.AddChild(node);
// Make sure to set the HasNameRelation flag on the dependent node if it is related to the parent by name
if (!node.HasParentNodeNameRelation && string.Compare(node.GetRelationalName(), parentNode.GetRelationalName(), StringComparison.OrdinalIgnoreCase) == 0)
{
node.HasParentNodeNameRelation = true;
}
return node;
}
/// <summary>
/// Add a file node to the hierarchy
/// </summary>
/// <param name="item">msbuild item to add</param>
/// <param name="parentNode">Parent Node</param>
/// <returns>Added node</returns>
private HierarchyNode AddFileNodeToNode(MSBuild.ProjectItem item, HierarchyNode parentNode)
{
FileNode node = this.CreateFileNode(new MsBuildProjectElement(this, item));
parentNode.AddChild(node);
return node;
}
/// <summary>
/// Get the parent node of an msbuild item
/// </summary>
/// <param name="item">msbuild item</param>
/// <returns>parent node</returns>
private HierarchyNode GetItemParentNode(MSBuild.ProjectItem item)
{
var link = item.GetMetadataValue(ProjectFileConstants.Link);
HierarchyNode currentParent = this;
string strPath = item.EvaluatedInclude;
if (!String.IsNullOrWhiteSpace(link))
{
strPath = Path.GetDirectoryName(link);
}
else
{
string absPath = CommonUtils.GetAbsoluteFilePath(ProjectHome, strPath);
if (CommonUtils.IsSubpathOf(ProjectHome, absPath))
{
strPath = CommonUtils.GetRelativeDirectoryPath(ProjectHome, Path.GetDirectoryName(absPath));
}
else
{
// file lives outside of the project, w/o a link it's just at the top level.
return this;
}
}
if (strPath.Length > 0)
{
// Use the relative to verify the folders...
currentParent = this.CreateFolderNodes(strPath);
}
return currentParent;
}
private MSBuildExecution.ProjectPropertyInstance GetMsBuildProperty(string propertyName, bool resetCache)
{
if (resetCache || this.currentConfig == null)
{
// Get properties from project file and cache it
this.SetCurrentConfiguration();
this.currentConfig = this.buildProject.CreateProjectInstance();
}
if (this.currentConfig == null)
throw new Exception(String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.FailedToRetrieveProperties, CultureInfo.CurrentUICulture), propertyName));
// return property asked for
return this.currentConfig.GetProperty(propertyName);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
private string GetAssemblyName(MSBuildExecution.ProjectInstance properties)
{
this.currentConfig = properties;
string name = null;
name = GetProjectProperty(ProjectFileConstants.AssemblyName);
if (name == null)
name = this.Caption;
string outputtype = GetProjectProperty(ProjectFileConstants.OutputType, false);
if (outputtype == "library")
{
outputtype = outputtype.ToLowerInvariant();
name += ".dll";
}
else
{
name += ".exe";
}
return name;
}
/// <summary>
/// Updates our scc project settings.
/// </summary>
/// <param name="sccProjectName">String, opaque to the project, that identifies the project location on the server. Persist this string in the project file. </param>
/// <param name="sccLocalPath">String, opaque to the project, that identifies the path to the server. Persist this string in the project file.</param>
/// <param name="sccAuxPath">String, opaque to the project, that identifies the local path to the project. Persist this string in the project file.</param>
/// <param name="sccProvider">String, opaque to the project, that identifies the source control package. Persist this string in the project file.</param>
/// <returns>Returns true if something changed.</returns>
private bool SetSccSettings(string sccProjectName, string sccLocalPath, string sccAuxPath, string sccProvider)
{
bool changed = false;
Debug.Assert(sccProjectName != null && sccLocalPath != null && sccAuxPath != null && sccProvider != null);
if (String.Compare(sccProjectName, this.sccProjectName, StringComparison.OrdinalIgnoreCase) != 0 ||
String.Compare(sccLocalPath, this.sccLocalPath, StringComparison.OrdinalIgnoreCase) != 0 ||
String.Compare(sccAuxPath, this.sccAuxPath, StringComparison.OrdinalIgnoreCase) != 0 ||
String.Compare(sccProvider, this.sccProvider, StringComparison.OrdinalIgnoreCase) != 0)
{
changed = true;
this.sccProjectName = sccProjectName;
this.sccLocalPath = sccLocalPath;
this.sccAuxPath = sccAuxPath;
this.sccProvider = sccProvider;
}
return changed;
}
/// <summary>
/// Sets the scc info from the project file.
/// </summary>
protected void InitSccInfo()
{
this.sccProjectName = this.GetProjectProperty(ProjectFileConstants.SccProjectName);
this.sccLocalPath = this.GetProjectProperty(ProjectFileConstants.SccLocalPath);
this.sccProvider = this.GetProjectProperty(ProjectFileConstants.SccProvider);
this.sccAuxPath = this.GetProjectProperty(ProjectFileConstants.SccAuxPath);
}
private void OnAfterProjectOpen(object sender, AfterProjectFileOpenedEventArgs e)
{
this.projectOpened = true;
}
private static XmlElement WrapXmlFragment(XmlDocument document, XmlElement root, Guid flavor, string configuration, string fragment)
{
XmlElement node = document.CreateElement(ProjectFileConstants.FlavorProperties);
XmlAttribute attribute = document.CreateAttribute(ProjectFileConstants.Guid);
attribute.Value = flavor.ToString("B");
node.Attributes.Append(attribute);
if (!String.IsNullOrEmpty(configuration))
{
attribute = document.CreateAttribute(ProjectFileConstants.Configuration);
attribute.Value = configuration;
node.Attributes.Append(attribute);
}
node.InnerXml = fragment;
root.AppendChild(node);
return node;
}
/// <summary>
/// Sets the project guid from the project file. If no guid is found a new one is created and assigne for the instance project guid.
/// </summary>
private void SetProjectGuidFromProjectFile()
{
string projectGuid = this.GetProjectProperty(ProjectFileConstants.ProjectGuid);
if (String.IsNullOrEmpty(projectGuid))
{
this.projectIdGuid = Guid.NewGuid();
}
else
{
Guid guid = new Guid(projectGuid);
if (guid != this.projectIdGuid)
{
this.projectIdGuid = guid;
}
}
}
/// <summary>
/// Helper for sharing common code between Build() and BuildAsync()
/// </summary>
/// <param name="output"></param>
/// <returns></returns>
private bool BuildPrelude(IVsOutputWindowPane output)
{
bool engineLogOnlyCritical = false;
// If there is some output, then we can ask the build engine to log more than
// just the critical events.
if (null != output)
{
engineLogOnlyCritical = BuildEngine.OnlyLogCriticalEvents;
BuildEngine.OnlyLogCriticalEvents = false;
}
this.SetOutputLogger(output);
return engineLogOnlyCritical;
}
/// <summary>
/// Recusively parses the tree and closes all nodes.
/// </summary>
/// <param name="node">The subtree to close.</param>
private static void CloseAllNodes(HierarchyNode node)
{
for (HierarchyNode n = node.FirstChild; n != null; n = n.NextSibling)
{
if (n.FirstChild != null)
{
CloseAllNodes(n);
}
n.Close();
}
}
/// <summary>
/// Set the build project with the new project instance value
/// </summary>
/// <param name="project">The new build project instance</param>
private void SetBuildProject(MSBuild.Project project)
{
bool isNewBuildProject = (this.buildProject != project);
this.buildProject = project;
if (this.buildProject != null)
{
SetupProjectGlobalPropertiesThatAllProjectSystemsMustSet();
}
if (isNewBuildProject)
{
NewBuildProject(project);
}
}
/// <summary>
/// Called when a new value for <see cref="BuildProject"/> is available.
/// </summary>
protected virtual void NewBuildProject(MSBuild.Project project) { }
/// <summary>
/// Setup the global properties for project instance.
/// </summary>
private void SetupProjectGlobalPropertiesThatAllProjectSystemsMustSet()
{
string solutionDirectory = null;
string solutionFile = null;
string userOptionsFile = null;
IVsSolution solution = this.Site.GetService(typeof(SVsSolution)) as IVsSolution;
if (solution != null)
{
// We do not want to throw. If we cannot set the solution related constants we set them to empty string.
solution.GetSolutionInfo(out solutionDirectory, out solutionFile, out userOptionsFile);
}
if (solutionDirectory == null)
{
solutionDirectory = String.Empty;
}
if (solutionFile == null)
{
solutionFile = String.Empty;
}
string solutionFileName = Path.GetFileName(solutionFile);
string solutionName = Path.GetFileNameWithoutExtension(solutionFile);
string solutionExtension = Path.GetExtension(solutionFile);
this.buildProject.SetGlobalProperty(GlobalProperty.SolutionDir.ToString(), solutionDirectory);
this.buildProject.SetGlobalProperty(GlobalProperty.SolutionPath.ToString(), solutionFile);
this.buildProject.SetGlobalProperty(GlobalProperty.SolutionFileName.ToString(), solutionFileName);
this.buildProject.SetGlobalProperty(GlobalProperty.SolutionName.ToString(), solutionName);
this.buildProject.SetGlobalProperty(GlobalProperty.SolutionExt.ToString(), solutionExtension);
// Other misc properties
this.buildProject.SetGlobalProperty(GlobalProperty.BuildingInsideVisualStudio.ToString(), "true");
this.buildProject.SetGlobalProperty(GlobalProperty.Configuration.ToString(), Config.Debug);
this.buildProject.SetGlobalProperty(GlobalProperty.Platform.ToString(), Config.AnyCPU);
// DevEnvDir property
object installDirAsObject = null;
IVsShell shell = this.Site.GetService(typeof(SVsShell)) as IVsShell;
if (shell != null)
{
// We do not want to throw. If we cannot set the solution related constants we set them to empty string.
shell.GetProperty((int)__VSSPROPID.VSSPROPID_InstallDirectory, out installDirAsObject);
}
// Ensure that we have traimnling backslash as this is done for the langproj macros too.
string installDir = CommonUtils.NormalizeDirectoryPath((string)installDirAsObject) ?? String.Empty;
this.buildProject.SetGlobalProperty(GlobalProperty.DevEnvDir.ToString(), installDir);
}
/// <summary>
/// Attempts to lock in the privilege of running a build in Visual Studio.
/// </summary>
/// <param name="designTime"><c>false</c> if this build was called for by the Solution Build Manager; <c>true</c> otherwise.</param>
/// <param name="requiresUIThread">
/// Need to claim the UI thread for build under the following conditions:
/// 1. The build must use a resource that uses the UI thread, such as
/// - you set HostServices and you have a host object which requires (even indirectly) the UI thread (VB and C# compilers do this for instance.)
/// or,
/// 2. The build requires the in-proc node AND waits on the UI thread for the build to complete, such as:
/// - you use a ProjectInstance to build, or
/// - you have specified a host object, whether or not it requires the UI thread, or
/// - you set HostServices and you have specified a node affinity.
/// - In addition to the above you also call submission.Execute(), or you call submission.ExecuteAsync() and then also submission.WaitHandle.Wait*().
/// </param>
/// <returns>A value indicating whether a build may proceed.</returns>
/// <remarks>
/// This method must be called on the UI thread.
/// </remarks>
protected bool TryBeginBuild(bool designTime, bool requiresUIThread = false)
{
IVsBuildManagerAccessor accessor = null;
if (this.Site != null)
{
accessor = this.Site.GetService(typeof(SVsBuildManagerAccessor)) as IVsBuildManagerAccessor;
}
bool releaseUIThread = false;
try
{
// If the SVsBuildManagerAccessor service is absent, we're not running within Visual Studio.
if (accessor != null)
{
if (requiresUIThread)
{
int result = accessor.ClaimUIThreadForBuild();
if (result < 0)
{
// Not allowed to claim the UI thread right now. Try again later.
return false;
}
releaseUIThread = true; // assume we need to release this immediately until we get through the whole gauntlet.
}
if (designTime)
{
int result = accessor.BeginDesignTimeBuild();
if (result < 0)
{
// Not allowed to begin a design-time build at this time. Try again later.
return false;
}
}
// We obtained all the resources we need. So don't release the UI thread until after the build is finished.
releaseUIThread = false;
}
else
{
BuildParameters buildParameters = new BuildParameters(this.buildEngine);
BuildManager.DefaultBuildManager.BeginBuild(buildParameters);
}
this.buildInProcess = true;
return true;
}
finally
{
// If we were denied the privilege of starting a design-time build,
// we need to release the UI thread.
if (releaseUIThread)
{
Debug.Assert(accessor != null, "We think we need to release the UI thread for an accessor we don't have!");
accessor.ReleaseUIThreadForBuild();
}
}
}
/// <summary>
/// Lets Visual Studio know that we're done with our design-time build so others can use the build manager.
/// </summary>
/// <param name="submission">The build submission that built, if any.</param>
/// <param name="designTime">This must be the same value as the one passed to <see cref="TryBeginBuild"/>.</param>
/// <param name="requiresUIThread">This must be the same value as the one passed to <see cref="TryBeginBuild"/>.</param>
/// <remarks>
/// This method must be called on the UI thread.
/// </remarks>
protected virtual void EndBuild(uint vsopts, BuildSubmission submission, bool designTime, bool requiresUIThread = false)
{
IVsBuildManagerAccessor accessor = null;
if (this.Site != null)
{
try
{
accessor = this.Site.GetService(typeof(SVsBuildManagerAccessor)) as IVsBuildManagerAccessor;
}
catch { }
}
if (accessor != null)
{
// It's very important that we try executing all three end-build steps, even if errors occur partway through.
try
{
if (submission != null)
{
Marshal.ThrowExceptionForHR(accessor.UnregisterLoggers(submission.SubmissionId));
}
}
catch (Exception ex)
{
if (ErrorHandler.IsCriticalException(ex))
{
throw;
}
Trace.TraceError(ex.ToString());
}
try
{
if (designTime)
{
Marshal.ThrowExceptionForHR(accessor.EndDesignTimeBuild());
}
}
catch (Exception ex)
{
if (ErrorHandler.IsCriticalException(ex))
{
throw;
}
Trace.TraceError(ex.ToString());
}
try
{
if (requiresUIThread)
{
Marshal.ThrowExceptionForHR(accessor.ReleaseUIThreadForBuild());
}
}
catch (Exception ex)
{
if (ErrorHandler.IsCriticalException(ex))
{
throw;
}
Trace.TraceError(ex.ToString());
}
}
else
{
BuildManager.DefaultBuildManager.EndBuild();
}
this.buildInProcess = false;
}
#endregion
#region IProjectEventsCallback Members
public virtual void BeforeClose()
{
}
#endregion
#region IVsProjectBuildSystem Members
public virtual int SetHostObject(string targetName, string taskName, object hostObject) {
Debug.Assert(targetName != null && taskName != null && this.buildProject != null && this.buildProject.Targets != null);
if (targetName == null || taskName == null || this.buildProject == null || this.buildProject.Targets == null) {
return VSConstants.E_INVALIDARG;
}
this.buildProject.ProjectCollection.HostServices.RegisterHostObject(this.buildProject.FullPath, targetName, taskName, (Microsoft.Build.Framework.ITaskHost)hostObject);
return VSConstants.S_OK;
}
public int BuildTarget(string targetName, out bool success) {
success = false;
MSBuildResult result = this.Build(targetName);
if (result == MSBuildResult.Successful) {
success = true;
}
return VSConstants.S_OK;
}
public virtual int CancelBatchEdit() {
return VSConstants.E_NOTIMPL;
}
public virtual int EndBatchEdit() {
return VSConstants.E_NOTIMPL;
}
public virtual int StartBatchEdit() {
return VSConstants.E_NOTIMPL;
}
/// <summary>
/// Used to determine the kind of build system, in VS 2005 there's only one defined kind: MSBuild
/// </summary>
/// <param name="kind"></param>
/// <returns></returns>
public virtual int GetBuildSystemKind(out uint kind) {
kind = (uint)_BuildSystemKindFlags2.BSK_MSBUILD_VS10;
return VSConstants.S_OK;
}
#endregion
/// <summary>
/// Finds a node by it's full path on disk.
/// </summary>
public HierarchyNode FindNodeByFullPath(string name) {
Debug.Assert(Path.IsPathRooted(name));
HierarchyNode res;
_diskNodes.TryGetValue(name, out res);
return res;
}
#region IVsUIHierarchy methods
public virtual int ExecCommand(uint itemId, ref Guid guidCmdGroup, uint nCmdId, uint nCmdExecOpt, IntPtr pvain, IntPtr p) {
return this.publicExecCommand(guidCmdGroup, nCmdId, nCmdExecOpt, pvain, p, CommandOrigin.UiHierarchy);
}
public virtual int QueryStatusCommand(uint itemId, ref Guid guidCmdGroup, uint cCmds, OLECMD[] cmds, IntPtr pCmdText) {
return this.QueryStatusSelection(guidCmdGroup, cCmds, cmds, pCmdText, CommandOrigin.UiHierarchy);
}
int IVsUIHierarchy.Close() {
return ((IVsHierarchy)this).Close();
}
#endregion
#region IVsHierarchy methods
public virtual int AdviseHierarchyEvents(IVsHierarchyEvents sink, out uint cookie) {
cookie = this._hierarchyEventSinks.Add(sink) + 1;
return VSConstants.S_OK;
}
/// <summary>
/// Closes the project node.
/// </summary>
/// <returns>A success or failure value.</returns>
int IVsHierarchy.Close() {
int hr = VSConstants.S_OK;
try {
// Walk the tree and close all nodes.
// This has to be done before the project closes, since we want still state available for the ProjectMgr on the nodes
// when nodes are closing.
CloseAllNodes(this);
} catch (COMException e) {
hr = e.ErrorCode;
} finally {
Close();
}
this.isClosed = true;
return hr;
}
/// <summary>
/// Sets the service provider from which to access the services.
/// </summary>
/// <param name="site">An instance to an Microsoft.VisualStudio.OLE.Interop object</param>
/// <returns>A success or failure value.</returns>
public virtual int SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider site) {
this.site = new ServiceProvider(site);
if (taskProvider != null) {
taskProvider.Dispose();
}
taskProvider = new TaskProvider(this.site);
return VSConstants.S_OK;
}
public virtual int GetCanonicalName(uint itemId, out string name) {
HierarchyNode n = NodeFromItemId(itemId);
name = (n != null) ? n.GetCanonicalName() : null;
return VSConstants.S_OK;
}
public virtual int GetGuidProperty(uint itemId, int propid, out Guid guid) {
guid = Guid.Empty;
HierarchyNode n = NodeFromItemId(itemId);
if (n != null) {
int hr = n.GetGuidProperty(propid, out guid);
__VSHPROPID vspropId = (__VSHPROPID)propid;
return hr;
}
if (guid == Guid.Empty) {
return VSConstants.DISP_E_MEMBERNOTFOUND;
}
return VSConstants.S_OK;
}
public virtual int GetProperty(uint itemId, int propId, out object propVal) {
propVal = null;
if (itemId != VSConstants.VSITEMID_ROOT && propId == (int)__VSHPROPID.VSHPROPID_IconImgList) {
return VSConstants.DISP_E_MEMBERNOTFOUND;
}
HierarchyNode n = NodeFromItemId(itemId);
if (n != null) {
propVal = n.GetProperty(propId);
}
if (propVal == null) {
return VSConstants.DISP_E_MEMBERNOTFOUND;
}
return VSConstants.S_OK;
}
public virtual int GetNestedHierarchy(uint itemId, ref Guid iidHierarchyNested, out IntPtr ppHierarchyNested, out uint pItemId) {
ppHierarchyNested = IntPtr.Zero;
pItemId = 0;
// If itemid is not a nested hierarchy we must return E_FAIL.
return VSConstants.E_FAIL;
}
public virtual int GetSite(out Microsoft.VisualStudio.OLE.Interop.IServiceProvider site) {
site = Site.GetService(typeof(Microsoft.VisualStudio.OLE.Interop.IServiceProvider)) as Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
return VSConstants.S_OK;
}
/// <summary>
/// the canonicalName of an item is it's URL, or better phrased,
/// the persistence data we put into @RelPath, which is a relative URL
/// to the root project
/// returning the itemID from this means scanning the list
/// </summary>
/// <param name="name"></param>
/// <param name="itemId"></param>
public virtual int ParseCanonicalName(string name, out uint itemId) {
// we always start at the current node and go it's children down, so
// if you want to scan the whole tree, better call
// the root
name = EnsureRootedPath(name);
itemId = 0;
var child = FindNodeByFullPath(name);
if (child != null) {
itemId = child.HierarchyId;
return VSConstants.S_OK;
}
return VSConstants.E_FAIL;
}
private string EnsureRootedPath(string name) {
if (!Path.IsPathRooted(name)) {
name = CommonUtils.GetAbsoluteDirectoryPath(
ProjectHome,
name
);
}
return name;
}
public virtual int QueryClose(out int fCanClose) {
fCanClose = 1;
return VSConstants.S_OK;
}
public virtual int SetGuidProperty(uint itemId, int propid, ref Guid guid) {
HierarchyNode n = NodeFromItemId(itemId);
int rc = VSConstants.E_INVALIDARG;
if (n != null) {
rc = n.SetGuidProperty(propid, ref guid);
}
return rc;
}
public virtual int SetProperty(uint itemId, int propid, object value) {
HierarchyNode n = NodeFromItemId(itemId);
if (n != null) {
return n.SetProperty(propid, value);
} else {
return VSConstants.DISP_E_MEMBERNOTFOUND;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "cookie-1")]
public virtual int UnadviseHierarchyEvents(uint cookie) {
this._hierarchyEventSinks.RemoveAt(cookie - 1);
return VSConstants.S_OK;
}
public int Unused0() {
return VSConstants.E_NOTIMPL;
}
public int Unused1() {
return VSConstants.E_NOTIMPL;
}
public int Unused2() {
return VSConstants.E_NOTIMPL;
}
public int Unused3() {
return VSConstants.E_NOTIMPL;
}
public int Unused4() {
return VSConstants.E_NOTIMPL;
}
#endregion
#region Hierarchy change notification
public void OnItemAdded(HierarchyNode parent, HierarchyNode child) {
VsUtilities.ArgumentNotNull("parent", parent);
VsUtilities.ArgumentNotNull("child", child);
IDiskBasedNode diskNode = child as IDiskBasedNode;
if (diskNode != null) {
_diskNodes[diskNode.Url] = child;
}
if ((EventTriggeringFlag & ProjectNode.EventTriggering.DoNotTriggerHierarchyEvents) != 0) {
return;
}
HierarchyNode prev = child.PreviousVisibleSibling;
uint prevId = (prev != null) ? prev.HierarchyId : VSConstants.VSITEMID_NIL;
foreach (IVsHierarchyEvents sink in _hierarchyEventSinks) {
int result = sink.OnItemAdded(parent.HierarchyId, prevId, child.HierarchyId);
if (ErrorHandler.Failed(result) && result != VSConstants.E_NOTIMPL) {
ErrorHandler.ThrowOnFailure(result);
}
}
}
public void OnItemDeleted(HierarchyNode deletedItem) {
IDiskBasedNode diskNode = deletedItem as IDiskBasedNode;
if (diskNode != null) {
_diskNodes.Remove(diskNode.Url);
}
if ((EventTriggeringFlag & ProjectNode.EventTriggering.DoNotTriggerHierarchyEvents) != 0) {
return;
}
if (_hierarchyEventSinks.Count > 0) {
// Note that in some cases (deletion of project node for example), an Advise
// may be removed while we are iterating over it. To get around this problem we
// take a snapshot of the advise list and walk that.
List<IVsHierarchyEvents> clonedSink = new List<IVsHierarchyEvents>();
foreach (IVsHierarchyEvents anEvent in _hierarchyEventSinks) {
clonedSink.Add(anEvent);
}
foreach (IVsHierarchyEvents clonedEvent in clonedSink) {
int result = clonedEvent.OnItemDeleted(deletedItem.HierarchyId);
if (ErrorHandler.Failed(result) && result != VSConstants.E_NOTIMPL) {
ErrorHandler.ThrowOnFailure(result);
}
}
}
}
public void OnItemsAppended(HierarchyNode parent) {
VsUtilities.ArgumentNotNull("parent", parent);
if ((EventTriggeringFlag & ProjectNode.EventTriggering.DoNotTriggerHierarchyEvents) != 0) {
return;
}
foreach (IVsHierarchyEvents sink in _hierarchyEventSinks) {
int result = sink.OnItemsAppended(parent.HierarchyId);
if (ErrorHandler.Failed(result) && result != VSConstants.E_NOTIMPL) {
ErrorHandler.ThrowOnFailure(result);
}
}
}
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "propid")]
public void OnPropertyChanged(HierarchyNode node, int propid, uint flags) {
VsUtilities.ArgumentNotNull("node", node);
if ((EventTriggeringFlag & ProjectNode.EventTriggering.DoNotTriggerHierarchyEvents) != 0) {
return;
}
foreach (IVsHierarchyEvents sink in _hierarchyEventSinks) {
int result = sink.OnPropertyChanged(node.HierarchyId, propid, flags);
if (ErrorHandler.Failed(result) && result != VSConstants.E_NOTIMPL) {
ErrorHandler.ThrowOnFailure(result);
}
}
}
/// <summary>
/// Causes the hierarchy to be redrawn.
/// </summary>
/// <param name="element">Used by the hierarchy to decide which element to redraw</param>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Re")]
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ReDraw")]
public void ReDrawNode(HierarchyNode node, UIHierarchyElement element) {
foreach (IVsHierarchyEvents sink in _hierarchyEventSinks) {
int result;
if ((element & UIHierarchyElement.Icon) != 0) {
result = sink.OnPropertyChanged(node.ID, (int)__VSHPROPID.VSHPROPID_IconIndex, 0);
Debug.Assert(ErrorHandler.Succeeded(result), "Redraw failed for node " + this.GetMkDocument());
}
if ((element & UIHierarchyElement.Caption) != 0) {
result = sink.OnPropertyChanged(node.ID, (int)__VSHPROPID.VSHPROPID_Caption, 0);
Debug.Assert(ErrorHandler.Succeeded(result), "Redraw failed for node " + this.GetMkDocument());
}
if ((element & UIHierarchyElement.SccState) != 0) {
result = sink.OnPropertyChanged(node.ID, (int)__VSHPROPID.VSHPROPID_StateIconIndex, 0);
Debug.Assert(ErrorHandler.Succeeded(result), "Redraw failed for node " + this.GetMkDocument());
}
}
}
#endregion
#region IVsHierarchyDeleteHandler methods
public virtual int DeleteItem(uint delItemOp, uint itemId) {
if (itemId == VSConstants.VSITEMID_SELECTION) {
return VSConstants.E_INVALIDARG;
}
HierarchyNode node = NodeFromItemId(itemId);
if (node != null) {
node.Remove((delItemOp & (uint)__VSDELETEITEMOPERATION.DELITEMOP_DeleteFromStorage) != 0);
return VSConstants.S_OK;
}
return VSConstants.E_FAIL;
}
public virtual int QueryDeleteItem(uint delItemOp, uint itemId, out int candelete) {
candelete = 0;
if (itemId == VSConstants.VSITEMID_SELECTION) {
return VSConstants.E_INVALIDARG;
}
// We ask the project what state it is. If he is a state that should not allow delete then we return.
if (IsCurrentStateASuppressCommandsMode()) {
return VSConstants.S_OK;
}
HierarchyNode node = NodeFromItemId(itemId);
if (node == null) {
return VSConstants.E_FAIL;
}
// Ask the nodes if they can remove the item.
bool canDeleteItem = node.CanDeleteItem((__VSDELETEITEMOPERATION)delItemOp);
if (canDeleteItem) {
candelete = 1;
}
return VSConstants.S_OK;
}
#endregion
#region IVsPersistHierarchyItem2 methods
/// <summary>
/// Saves the hierarchy item to disk.
/// </summary>
/// <param name="saveFlag">Flags whose values are taken from the VSSAVEFLAGS enumeration.</param>
/// <param name="silentSaveAsName">New filename when doing silent save as</param>
/// <param name="itemid">Item identifier of the hierarchy item saved from VSITEMID.</param>
/// <param name="docData">Item identifier of the hierarchy item saved from VSITEMID.</param>
/// <param name="cancelled">[out] true if the save action was canceled.</param>
/// <returns>[out] true if the save action was canceled.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
public virtual int SaveItem(VSSAVEFLAGS saveFlag, string silentSaveAsName, uint itemid, IntPtr docData, out int cancelled) {
cancelled = 0;
// Validate itemid
if (itemid == VSConstants.VSITEMID_ROOT || itemid == VSConstants.VSITEMID_SELECTION) {
return VSConstants.E_INVALIDARG;
}
HierarchyNode node = this.NodeFromItemId(itemid);
if (node == null) {
return VSConstants.E_FAIL;
}
string existingFileMoniker = node.GetMkDocument();
// We can only perform save if the document is open
if (docData == IntPtr.Zero) {
string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.GetString(SR.CanNotSaveFileNotOpeneInEditor, CultureInfo.CurrentUICulture), node.Url);
throw new InvalidOperationException(errorMessage);
}
string docNew = String.Empty;
int returnCode = VSConstants.S_OK;
IPersistFileFormat ff = null;
IVsPersistDocData dd = null;
IVsUIShell shell = Site.GetService(typeof(SVsUIShell)) as IVsUIShell;
VsUtilities.CheckNotNull(shell);
try {
//Save docdata object.
//For the saveas action a dialog is show in order to enter new location of file.
//In case of a save action and the file is readonly a dialog is also shown
//with a couple of options, SaveAs, Overwrite or Cancel.
ff = Marshal.GetObjectForIUnknown(docData) as IPersistFileFormat;
VsUtilities.CheckNotNull(ff);
if (VSSAVEFLAGS.VSSAVE_SilentSave == saveFlag) {
ErrorHandler.ThrowOnFailure(shell.SaveDocDataToFile(saveFlag, ff, silentSaveAsName, out docNew, out cancelled));
} else {
dd = Marshal.GetObjectForIUnknown(docData) as IVsPersistDocData;
VsUtilities.CheckNotNull(dd);
ErrorHandler.ThrowOnFailure(dd.SaveDocData(saveFlag, out docNew, out cancelled));
}
// We can be unloaded after the SaveDocData() call if the save caused a designer to add a file and this caused
// the project file to be reloaded (QEQS caused a newer version of the project file to be downloaded). So we check
// here.
if (IsClosed) {
cancelled = 1;
return (int)OleConstants.OLECMDERR_E_CANCELED;
} else {
// if a SaveAs occurred we need to update to the fact our item's name has changed.
// this includes the following:
// 1. call RenameDocument on the RunningDocumentTable
// 2. update the full path name for the item in our hierarchy
// 3. a directory-based project may need to transfer the open editor to the
// MiscFiles project if the new file is saved outside of the project directory.
// This is accomplished by calling IVsExternalFilesManager::TransferDocument
// we have three options for a saveas action to be performed
// 1. the flag was set (the save as command was triggered)
// 2. a silent save specifying a new document name
// 3. a save command was triggered but was not possible because the file has a read only attrib. Therefore
// the user has chosen to do a save as in the dialog that showed up
bool emptyOrSamePath = String.IsNullOrEmpty(docNew) || CommonUtils.IsSamePath(existingFileMoniker, docNew);
bool saveAs = ((saveFlag == VSSAVEFLAGS.VSSAVE_SaveAs)) ||
((saveFlag == VSSAVEFLAGS.VSSAVE_SilentSave) && !emptyOrSamePath) ||
((saveFlag == VSSAVEFLAGS.VSSAVE_Save) && !emptyOrSamePath);
if (saveAs) {
returnCode = node.AfterSaveItemAs(docData, docNew);
// If it has been cancelled recover the old name.
if ((returnCode == (int)OleConstants.OLECMDERR_E_CANCELED || returnCode == VSConstants.E_ABORT)) {
// Cleanup.
this.DeleteFromStorage(docNew);
if (ff != null) {
returnCode = shell.SaveDocDataToFile(VSSAVEFLAGS.VSSAVE_SilentSave, ff, existingFileMoniker, out docNew, out cancelled);
}
} else if (returnCode != VSConstants.S_OK) {
ErrorHandler.ThrowOnFailure(returnCode);
}
}
}
} catch (COMException e) {
Trace.WriteLine("Exception :" + e.Message);
returnCode = e.ErrorCode;
// Try to recover
// changed from MPFProj:
// http://mpfproj10.codeplex.com/WorkItem/View.aspx?WorkItemId=6982
if (ff != null && cancelled == 0) {
ErrorHandler.ThrowOnFailure(shell.SaveDocDataToFile(VSSAVEFLAGS.VSSAVE_SilentSave, ff, existingFileMoniker, out docNew, out cancelled));
}
}
return returnCode;
}
/// <summary>
/// Determines whether the hierarchy item changed.
/// </summary>
/// <param name="itemId">Item identifier of the hierarchy item contained in VSITEMID.</param>
/// <param name="docData">Pointer to the IUnknown interface of the hierarchy item.</param>
/// <param name="isDirty">true if the hierarchy item changed.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
public virtual int IsItemDirty(uint itemId, IntPtr docData, out int isDirty)
{
IVsPersistDocData pd = (IVsPersistDocData)Marshal.GetObjectForIUnknown(docData);
return ErrorHandler.ThrowOnFailure(pd.IsDocDataDirty(out isDirty));
}
/// <summary>
/// Flag indicating that changes to a file can be ignored when item is saved or reloaded.
/// </summary>
/// <param name="itemId">Specifies the item id from VSITEMID.</param>
/// <param name="ignoreFlag">Flag indicating whether or not to ignore changes (1 to ignore, 0 to stop ignoring).</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns>
public virtual int IgnoreItemFileChanges(uint itemId, int ignoreFlag) {
HierarchyNode n = this.NodeFromItemId(itemId);
if (n != null) {
n.IgnoreItemFileChanges(ignoreFlag == 0 ? false : true);
}
return VSConstants.S_OK;
}
/// <summary>
/// Called to determine whether a project item is reloadable before calling ReloadItem.
/// </summary>
/// <param name="itemId">Item identifier of an item in the hierarchy. Valid values are VSITEMID_NIL, VSITEMID_ROOT and VSITEMID_SELECTION.</param>
/// <param name="isReloadable">A flag indicating that the project item is reloadable (1 for reloadable, 0 for non-reloadable).</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Reloadable")]
public virtual int IsItemReloadable(uint itemId, out int isReloadable) {
isReloadable = 0;
HierarchyNode n = this.NodeFromItemId(itemId);
if (n != null) {
isReloadable = (n.IsItemReloadable()) ? 1 : 0;
}
return VSConstants.S_OK;
}
/// <summary>
/// Called to reload a project item.
/// </summary>
/// <param name="itemId">Specifies itemid from VSITEMID.</param>
/// <param name="reserved">Reserved.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
public virtual int ReloadItem(uint itemId, uint reserved) {
HierarchyNode n = this.NodeFromItemId(itemId);
if (n != null) {
n.ReloadItem(reserved);
}
return VSConstants.S_OK;
}
#endregion
public void UpdatePathForDeferredSave(string oldPath, string newPath) {
var existing = _diskNodes[oldPath];
_diskNodes.Remove(oldPath);
_diskNodes.Add(newPath, existing);
}
#region IVsSetTargetFrameworkWorkerCallback
public int UpdateTargetFramework(IVsHierarchy pHier, string currentTargetFramework, string newTargetFramework)
{
FrameworkName moniker = new FrameworkName(newTargetFramework);
SetProjectProperty("TargetFrameworkIdentifier", moniker.Identifier);
SetProjectProperty("TargetFrameworkVersion", "v" + moniker.Version);
SetProjectProperty("TargetFrameworkProfile", moniker.Profile);
return VSConstants.S_OK;
}
#endregion
/*public virtual void OnTargetFrameworkMonikerChanged(FrameworkName currentTargetFramework, FrameworkName newTargetFramework)
{
if (currentTargetFramework == null)
{
throw new ArgumentNullException("currentTargetFramework");
}
if (newTargetFramework == null)
{
throw new ArgumentNullException("newTargetFramework");
}
var retargetingService = this.site.GetService(typeof(SVsTrackProjectRetargeting)) as IVsTrackProjectRetargeting;
if (retargetingService == null)
{
// Probably in a unit test.
////throw new InvalidOperationException("Unable to acquire the SVsTrackProjectRetargeting service.");
Marshal.ThrowExceptionForHR(UpdateTargetFramework(this.GetOuterHierarchy(), currentTargetFramework.FullName, newTargetFramework.FullName));
}
else
{
Marshal.ThrowExceptionForHR(retargetingService.OnSetTargetFramework(this.GetOuterHierarchy(), currentTargetFramework.FullName, newTargetFramework.FullName, this, true));
}
}
public int UpgradeProject(uint grfUpgradeFlags)
{
int hr = VSConstants.S_OK;
if (!PerformTargetFrameworkCheck(this.TargetFrameworkMoniker.FullName))
{
// Just return OLE_E_PROMPTSAVECANCELLED here which will cause the shell
// to leave the project in an unloaded state.
hr = VSConstants.OLE_E_PROMPTSAVECANCELLED;
}
return hr;
}
public bool PerformTargetFrameworkCheck(string newFrameworkName)
{
if (this.IsFrameworkOnMachine(newFrameworkName))
{
// Nothing to do since the framework is present.
return true;
}
return ShowRetargetingDialog(newFrameworkName);
}
/// <summary>
///
/// </summary>
/// <returns>
/// <c>true</c> if the project will be retargeted. <c>false</c> to load project in unloaded state.
/// </returns>
private bool ShowRetargetingDialog(string newFrameworkName)
{
var retargetDialog = this.site.GetService(typeof(SVsFrameworkRetargetingDlg)) as IVsFrameworkRetargetingDlg;
if (retargetDialog == null)
{
throw new InvalidOperationException("Missing SVsFrameworkRetargetingDlg service.");
}
// We can only display the retargeting dialog if the IDE is not in command-line mode.
if (IsIdeInCommandLineMode)
{
string message = SR.GetString(SR.CannotLoadUnknownTargetFrameworkProject, this.FileName, this.TargetFrameworkMoniker);
var outputWindow = this.site.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
if (outputWindow != null)
{
IVsOutputWindowPane outputPane;
Guid outputPaneGuid = VSConstants.GUID_BuildOutputWindowPane;
if (outputWindow.GetPane(ref outputPaneGuid, out outputPane) >= 0 && outputPane != null)
{
Marshal.ThrowExceptionForHR(outputPane.OutputString(message));
}
}
throw new InvalidOperationException(message);
}
else
{
uint outcome;
int dontShowAgain;
Marshal.ThrowExceptionForHR(retargetDialog.ShowFrameworkRetargetingDlg(
this.ProjectMgr.ProjectType,
this.FileName,
newFrameworkName,
(uint)__FRD_FLAGS.FRDF_DEFAULT,
out outcome,
out dontShowAgain));
switch ((__FRD_OUTCOME)outcome)
{
case __FRD_OUTCOME.FRDO_GOTO_DOWNLOAD_SITE:
Marshal.ThrowExceptionForHR(retargetDialog.NavigateToFrameworkDownloadUrl());
return false;
case __FRD_OUTCOME.FRDO_LEAVE_UNLOADED:
return false;
case __FRD_OUTCOME.FRDO_RETARGET_TO_40:
// If we are retargeting to 4.0, then set the flag to set the appropriate Target Framework.
// This will dirty the project file, so we check it out of source control now -- so that
// the user can associate getting the checkout prompt with the "No Framework" dialog.
if (QueryEditProjectFile(false))
{
var retargetingService = this.site.GetService(typeof(SVsTrackProjectRetargeting)) as IVsTrackProjectRetargeting;
if (retargetingService != null)
{
// We surround our batch retargeting request with begin/end because in individual project load
// scenarios the solution load context hasn't done it for us.
Marshal.ThrowExceptionForHR(retargetingService.BeginRetargetingBatch());
Marshal.ThrowExceptionForHR(retargetingService.BatchRetargetProject(this.GetOuterHierarchy(), newFrameworkName, true));
Marshal.ThrowExceptionForHR(retargetingService.EndRetargetingBatch());
}
else
{
// Just setting the moniker to null will allow the default framework (.NETFX 4.0) to assert itself.
this.TargetFrameworkMoniker = null;
}
return true;
}
else
{
return false;
}
default:
throw new ArgumentException("Unexpected outcome from retargeting dialog.");
}
}
}
private bool IsFrameworkOnMachine(string newFrameworkMoniker)
{
var multiTargeting = this.site.GetService(typeof(SVsFrameworkMultiTargeting)) as IVsFrameworkMultiTargeting;
Array frameworks;
Marshal.ThrowExceptionForHR(multiTargeting.GetSupportedFrameworks(out frameworks));
foreach (string fx in frameworks)
{
uint compat;
int hr = multiTargeting.CheckFrameworkCompatibility(newFrameworkMoniker, fx, out compat);
if (hr < 0)
{
break;
}
if ((__VSFRAMEWORKCOMPATIBILITY)compat == __VSFRAMEWORKCOMPATIBILITY.VSFRAMEWORKCOMPATIBILITY_COMPATIBLE)
{
return true;
}
}
return false;
}*/
}
}