/* **************************************************************************** * * 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 { /// /// Manages the persistent state of the project (References, options, files, etc.) and deals with user interaction via a GUI in the form a hierarchy. /// 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 } /// /// Flags for specifying which events to stop triggering. /// [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 /// /// The user file extension. /// 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 /// /// List of output groups names and their associated target /// private static KeyValuePair[] outputGroupNames = { // Name Target (MSBuild) new KeyValuePair("Built", "BuiltProjectOutputGroup"), new KeyValuePair("ContentFiles", "ContentFilesProjectOutputGroup"), new KeyValuePair("LocalizedResourceDlls", "SatelliteDllsProjectOutputGroup"), new KeyValuePair("Documentation", "DocumentationProjectOutputGroup"), new KeyValuePair("Symbols", "DebugSymbolsProjectOutputGroup"), new KeyValuePair("SourceFiles", "SourceFilesProjectOutputGroup"), new KeyValuePair("XmlSerializer", "SGenFilesOutputGroup"), }; private EventSinkCollection _hierarchyEventSinks = new EventSinkCollection(); /// A project will only try to build if it can obtain a lock on this object private volatile static object BuildLock = new object(); /// Maps integer ids to project item instances private EventSinkCollection itemIdMap = new EventSinkCollection(); /// A service provider call back object provided by the IDE hosting the project manager private ServiceProvider site; private TrackDocumentsHelper tracker; /// /// 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. /// private DateTime lastModifiedTime; /// /// MSBuild engine we are going to use /// 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; /// /// The build dependency list passed to IVsDependencyProvider::EnumDependencies /// private List buildDependencyList = new List(); /// /// Defines if Project System supports Project Designer /// private bool supportsProjectDesigner; private bool showProjectInSolutionPage = true; private bool buildInProcess; private string sccProjectName; private string sccLocalPath; private string sccAuxPath; private string sccProvider; /// /// Flag for controling how many times we register with the Scc manager. /// private bool isRegisteredWithScc; /// /// Flag for controling query edit should communicate with the scc manager. /// protected bool disableQueryEdit; /// /// Control if command with potential destructive behavior such as delete should /// be enabled for nodes of this project. /// private bool canProjectDeleteItems; /// /// Member to store output base relative path. Used by OutputBaseRelativePath property /// private string outputBaseRelativePath = "bin"; /// /// Used for flavoring to hold the XML fragments /// private XmlDocument xmlFragments; /// /// 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. /// private Dictionary catidMapping = new Dictionary(); /// /// The public package implementation. /// private ProjectPackage package; /// /// Mapping from item names to their hierarchy nodes for all disk-based nodes. /// protected readonly Dictionary _diskNodes = new Dictionary(StringComparer.OrdinalIgnoreCase); // Has the object been disposed. private bool isDisposed; private IVsHierarchy parentHierarchy; private int parentHierarchyItemId; private List itemsDraggedOrCutOrCopied; private CopyPasteDragSource sourceDraggedOrCutOrCopied; /// /// 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. /// private FolderNode _folderBeingCreated; /// /// 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. /// protected bool isChangingFramework = false; #endregion #region abstract properties /// /// 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. /// public abstract Guid ProjectGuid { get; } /// /// Returns a caption for VSHPROPID_TypeName. /// /// public abstract string ProjectType { get; } #endregion #region virtual properties public virtual FrameworkName TargetFrameworkMoniker { get { return DefaultTargetFrameworkMoniker; } set { } } /// /// Indicates whether or not the project system supports Show All Files. /// /// Subclasses will need to return true here, and will need to handle calls /// public virtual bool CanShowAllFiles { get { return false; } } /// /// Indicates whether or not the project is currently in the mode where its showing all files. /// public virtual bool IsShowingAllFiles { get { return false; } } /// /// 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. /// public virtual Guid SharedCommandGuid { get { return CommonConstants.NoSharedCommandsGuid; } } /// /// This is the project instance guid that is peristed in the project file /// [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; } } /// /// Gets the folder node which is currently being added to the project via /// Solution Explorer. /// 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 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; } } /// /// Override this property to specify when the project file is dirty. /// protected virtual bool IsProjectFileDirty { get { string document = this.GetMkDocument(); if (String.IsNullOrEmpty(document)) { return this.isDirty; } return (this.isDirty || !File.Exists(document)); } } /// /// True if the project uses the Project Designer Editor instead of the property page frame to edit project properties. /// protected virtual bool SupportsProjectDesigner { get { return this.supportsProjectDesigner; } set { this.supportsProjectDesigner = value; } } protected virtual Guid ProjectDesignerEditor { get { return VSConstants.GUID_ProjectDesignerEditor; } } /// /// Defines the flag that supports the VSHPROPID.ShowProjInSolutionPage /// protected virtual bool ShowProjectInSolutionPage { get { return this.showProjectInSolutionPage; } set { this.showProjectInSolutionPage = value; } } #endregion /// /// 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. /// public bool CanFileNodesHaveChilds { get { return canFileNodesHaveChilds; } set { canFileNodesHaveChilds = value; } } /// /// Gets a service provider object provided by the IDE hosting the project /// [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] public IServiceProvider Site { get { return this.site; } } /// /// Gets an ImageHandler for the project node. /// 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"); } } /// /// Gets the path to the root folder of the project. /// 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; } } /// /// Gets the path to the folder containing the project. /// public string ProjectFolder { get { return Path.GetDirectoryName(this.filename); } } /// /// Gets or sets the project filename. /// public string ProjectFile { get { return Path.GetFileName(this.filename); } set { this.SetEditLabel(value); } } /// /// Gets the Base Uniform Resource Identifier (URI). /// [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; } /// /// Gets whether or not the project is closed. /// public bool IsClosed { get { return this.isClosed; } } /// /// Gets whether or not the project is being built. /// public bool BuildInProgress { get { return buildInProcess; } } /// /// Gets or set the relative path to the folder containing the project ouput. /// 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; } } /// /// Gets a collection of integer ids that maps to project item instances /// public EventSinkCollection ItemIdMap { get { return this.itemIdMap; } } /// /// Get the helper object that track document changes. /// public TrackDocumentsHelper Tracker { get { return this.tracker; } } /// /// Gets or sets the build logger. /// protected Microsoft.Build.Framework.ILogger BuildLogger { get { return this.buildLogger; } set { this.buildLogger = value; this.useProvidedLogger = true; } } /// /// Gets the taskprovider. /// protected TaskProvider TaskProvider { get { return this.taskProvider; } } /// /// Gets or sets the project file name. /// protected string FileName { get { return this.filename; } set { this.filename = value; } } /// /// Gets the configuration provider. /// public ConfigProvider ConfigProvider { get { if (this.configProvider == null) { this.configProvider = CreateConfigProvider(); } return this.configProvider; } } /// /// 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. /// public bool CanProjectDeleteItems { get { return canProjectDeleteItems; } set { canProjectDeleteItems = value; } } /// /// Gets or sets event triggering flags. /// public EventTriggering EventTriggeringFlag { get { return this.eventTriggeringFlag; } set { this.eventTriggeringFlag = value; } } /// /// Defines the build project that has loaded the project file. /// public MSBuild.Project BuildProject { get { return this.buildProject; } set { SetBuildProject(value); } } /// /// Defines the build engine that is used to build the project file. /// public MSBuild.ProjectCollection BuildEngine { get { return this.buildEngine; } set { this.buildEngine = value; } } /// /// The public package implementation. /// [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); } /// /// Sets the properties for the project node. /// /// Identifier of the hierarchy property. For a list of propid values, /// The value to set. /// A success or failure value. 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); } /// /// Renames the project node. /// /// The new name /// A success or failure value. 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; } /// /// Gets the automation object for the project node. /// /// An instance of an EnvDTE.Project implementation object representing the automation object for the project. public override object GetAutomationObject() { return new Automation.OAProject(this); } /// /// Gets the properties of the project node. /// /// The __VSHPROPID of the property. /// A property dependent value. See: for details. 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); } /// /// Gets the GUID value of the node. /// /// A __VSHPROPID or __VSHPROPID2 value of the guid property /// The guid to return for the property. /// A success or failure value. 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; } /// /// Sets Guid properties for the project node. /// /// A __VSHPROPID or __VSHPROPID2 value of the guid property /// The guid value to set. /// A success or failure value. 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; } /// /// Removes items from the hierarchy. /// /// Project overwrites this. 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(), 0)); } /// /// Gets the moniker for the project node. That is the full path of the project file. /// /// The moniker for the project file. 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); } /// /// Disposes the project node object. /// /// Flag determining ehether it was deterministic or non deterministic clean up. 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; } } /// /// Handles command status on the project node. If a command cannot be handled then the base should be called. /// /// A unique identifier of the command group. The pguidCmdGroup parameter can be NULL to specify the standard group. /// The command to query status for. /// 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. /// An out parameter specifying the QueryStatusResult of the command. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. 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); } /// /// Handles command execution. /// /// Unique identifier of the command group /// The command to be executed. /// Values describe how the object should execute the command. /// Pointer to a VARIANTARG structure containing input arguments. Can be NULL /// VARIANTARG structure to receive command output. Can be NULL. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. 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; } /// /// Get the boolean value for the deletion of a project item /// /// A flag that specifies the type of delete operation (delete from storage or remove from project) /// true if item can be deleted from project public override bool CanDeleteItem(__VSDELETEITEMOPERATION deleteOperation) { if (deleteOperation == __VSDELETEITEMOPERATION.DELITEMOP_RemoveFromProject) { return true; } return false; } /// /// Returns a specific Document manager to handle opening and closing of the Project(Application) Designer if projectdesigner is supported. /// /// Document manager object 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); } /// /// 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. /// public virtual ReferenceNode CreateReferenceNodeForFile(string filename) { #if FALSE return new ComReferenceNode(this.ProjectMgr, selectorData); #endif return null; } /// /// Executes a wizard. /// /// The node to which the wizard should add item(s). /// The name of the file that the user typed in. /// The name of the wizard to run. /// The owner of the dialog box. /// A VSADDRESULT enum value describing success or failure. [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; } } /// /// 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. /// /// S_OK if succeeded. Failure other wise 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 sdkPaths = new List(); Array sdks = frameworkService2.GetSDKRootFolders(); foreach (string path in sdks) { sdkPaths.AddRange(frameworkService2.GetSDKReferences(path).Cast()); } 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"; } } /// /// Returns the Compiler associated to the project /// /// Null public virtual ICodeCompiler GetCompiler() { return null; } /// /// Override this method if you have your own project specific /// subclass of ProjectOptions /// /// This method returns a new instance of the ProjectOptions base class. public virtual CompilerParameters CreateProjectOptions() { return new CompilerParameters(); } /// /// Loads a project file. Called from the factory CreateProject to load the project. /// /// File name of the project that will be created. /// Location where the project will be created. /// If applicable, the name of the template to use when cloning a new project. /// Set of flag values taken from the VSCREATEPROJFLAGS enumeration. /// Identifier of the interface that the caller wants returned. /// An out parameter specifying if the project creation was canceled [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; } } } /// /// Called to add a file to the project from a template. /// Override to do it yourself if you want to customize the file /// /// Full path of template file /// Full path of file once added to the project 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); } } /// /// Called when the project opens an editor window for the given file /// public virtual void OnOpenItem(string fullPathToSourceFile) { } /// /// 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 /// /// /// List of subitems not yet added to the hierarchy /// Key to retrieve the target item from the subitems list /// Newly added node /// If the parent node was found we add the dependent item to it otherwise we add the item ignoring the "DependentUpon" metatdata protected virtual HierarchyNode AddDependentFileNode(IDictionary 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; } /// /// Do the build by invoking msbuild /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "vsopts")] public virtual void BuildAsync(uint vsopts, string config, string platform, IVsOutputWindowPane output, string target, IEnumerable files, Action 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; } /// /// Return the value of a project property /// /// Name of the property to get /// True to avoid using the cache /// null if property does not exist, otherwise value of the property public virtual string GetProjectProperty(string propertyName, bool resetCache) { MSBuildExecution.ProjectPropertyInstance property = GetMsBuildProperty(propertyName, resetCache); if (property == null) return null; return property.EvaluatedValue; } /// /// Return the value of a project property in it's unevalauted form. /// /// New in 1.5. /// /// Name of the property to get public virtual string GetUnevaluatedProperty(string propertyName) { var res = this.buildProject.GetProperty(propertyName); if (res != null) { return res.UnevaluatedValue; } return null; } /// /// Set value of project property /// /// Name of property /// Value of property 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); } /// /// Get the assembly name for a give configuration /// /// the matching configuration in the msbuild file /// assembly name public virtual string GetAssemblyName(string config, string platform) { this.SetConfiguration(config, platform); return GetAssemblyName(this.currentConfig); } /// /// Determines whether a file is a code file. /// /// Name of the file to be evaluated /// false by default for any fileName public virtual bool IsCodeFile(string fileName) { return false; } public virtual string[] CodeFileExtensions { get { return new string[0]; } } /// /// Determines whether the given file is a resource file (resx file). /// /// Name of the file to be evaluated. /// true if the file is a resx file, otherwise false. public virtual bool IsEmbeddedResource(string fileName) { return String.Equals(Path.GetExtension(fileName), ".ResX", StringComparison.OrdinalIgnoreCase); } /// /// Create a file node based on an msbuild item. /// /// msbuild item /// FileNode added public abstract FileNode CreateFileNode(ProjectElement item); /// /// Create a file node based on a string. /// /// filename of the new filenode /// File node added public abstract FileNode CreateFileNode(string file); /// /// Create dependent file node based on an msbuild item /// /// msbuild item /// dependent file node public virtual DependentFileNode CreateDependentFileNode(MsBuildProjectElement item) { return new DependentFileNode(this, item); } /// /// Create a dependent file node based on a string. /// /// filename of the new dependent file node /// Dependent node added public virtual DependentFileNode CreateDependentFileNode(string file) { var item = AddFileToMsBuild(file); return this.CreateDependentFileNode(item); } /// /// Walks the subpaths of a project relative path and checks if the folder nodes hierarchy is already there, if not creates it. /// /// Path of the folder, can be relative to project or absolute 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; } /// /// Defines if Node has Designer. By default we do not support designers for nodes /// /// Path to item to query for designer support /// true if node has designer public virtual bool NodeHasDesigner(string itemPath) { return false; } /// /// List of Guids of the config independent property pages. It is called by the GetProperty for VSHPROPID_PropertyPagesCLSIDList property. /// /// protected virtual Guid[] GetConfigurationIndependentPropertyPages() { return new Guid[0]; } /// /// Returns a list of Guids of the configuration dependent property pages. It is called by the GetProperty for VSHPROPID_CfgPropertyPagesCLSIDList property. /// /// protected virtual Guid[] GetConfigurationDependentPropertyPages() { return new Guid[0]; } /// /// An ordered list of guids of the prefered property pages. See /// /// An array of guids. protected virtual Guid[] GetPriorityProjectDesignerPages() { return new Guid[] { Guid.Empty }; } /// /// 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. /// /// full path to the subfolder we want to verify. /// the parent node where to add the subfolder if it does not exist. /// the foldernode correcsponding to the path. [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; } /// /// To support virtual folders, override this method to return your own folder nodes /// /// Path to store for this folder /// Element corresponding to the folder /// A FolderNode that can then be added to the hierarchy public virtual FolderNode CreateFolderNode(ProjectElement element) { return new FolderNode(this, element); } /// /// Gets the list of selected HierarchyNode objects /// /// A list of HierarchyNode objects public virtual IList GetSelectedNodes() { // Retrieve shell interface in order to get current selection IVsMonitorSelection monitorSelection = this.GetService(typeof(IVsMonitorSelection)) as IVsMonitorSelection; VsUtilities.CheckNotNull(monitorSelection); List selectedNodes = new List(); 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; } /// /// Recursevily walks the hierarchy nodes and redraws the state icons /// public override void UpdateSccStateIcons() { if (this.FirstChild == null) { return; } for (HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling) { n.UpdateSccStateIcons(); } } /// /// Handles the shows all objects command. /// /// public virtual int ShowAllFiles() { return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; } /// /// Unloads the project. /// /// public virtual int UnloadProject() { return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; } /// /// Handles the clean project command. /// /// protected virtual int CleanProject() { return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; } /// /// Reload project from project file /// 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)); } /// /// Renames the project file /// /// The full path of the new project file. 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(), 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); } } /// /// 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 /// /// Type name /// True = items of this type should be included in the project 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; } /// /// Filter items that should not be processed as file items. Example: Folders and References. /// 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); } /// /// Associate window output pane to the build logger /// /// 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; } } /// /// Set configuration properties for a specific configuration /// /// configuration name 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); } } /// /// This execute an MSBuild target for a design-time build. /// /// Name of the MSBuild target to execute /// Result from executing the target (success/failure) /// /// 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 /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ms")] public virtual MSBuildResult InvokeMsBuild(uint vsopts, string target, string config, string platform, IEnumerable 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; } /// /// Initialize common project properties with default value if they are empty /// /// 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 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); } } /// /// Factory method for configuration provider /// /// Configuration provider created protected abstract ConfigProvider CreateConfigProvider(); /// /// Factory method for reference container node /// /// ReferenceContainerNode created protected virtual ReferenceContainerNode CreateReferenceContainerNode() { return new ReferenceContainerNode(this); } /// /// Saves the project file on a new name. /// /// The new name of the project file. /// Success value or an error code. 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 #¤&%"¤&"% 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(), 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; } /// /// 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. /// /// The new full path of the project file 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); } /// /// Adds a file to the msbuild project. /// /// The file to be added. /// A Projectelement describing the newly added file. [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; } /// /// Adds a folder to the msbuild project. /// /// The folder to be added. /// A ProjectElement describing the newly added folder. [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 /// /// 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 /// /// /// /// /// 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; } /// /// Adds a new file node to the hierarchy. /// /// The parent of the new fileNode /// The file name 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); } /// /// Defines whther the current mode of the project is in a supress command mode. /// /// 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; } /// /// 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 /// /// List of output group name and corresponding MSBuild target public virtual IList> GetOutputGroupNames() { return new List>(outputGroupNames); } /// /// Get the display name of the given output group. /// /// Canonical name of the output group /// Display name 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; } /// /// Get the description of the given output group. /// /// Canonical name of the output group /// Description 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; } /// /// Set the configuration in MSBuild. /// This does not get persisted and is used to evaluate msbuild conditions /// which are based on the $(Configuration) property. /// 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)); } /// /// 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. /// /// Configuration name 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(); } } /// /// Loads reference items from the project file into the hierarchy. /// 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); } /// /// Loads folders from the project file into the hierarchy. /// 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); } } /// /// Loads file items from the project file into the hierarchy. /// public virtual void ProcessFiles() { List subitemsKeys = new List(); Dictionary subitems = new Dictionary(); // Define a set for our build items. The value does not really matter here. Dictionary items = new Dictionary(); // 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 // items that are generated from any 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); } } /// /// Processes dependent filenodes from list of subitems. Multi level supported, but not circular dependencies. /// /// List of sub item keys /// public virtual void ProcessDependentFileNodes(IList subitemsKeys, Dictionary 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); } } /// /// For flavored projects which implement IPersistXMLFragment, load the information now /// public virtual void LoadNonBuildInformation() { IPersistXMLFragment outerHierarchy = GetOuterInterface(); if (outerHierarchy != null) { this.LoadXmlFragment(outerHierarchy, null, null); } } /// /// Used to sort nodes in the hierarchy. /// 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(); } /// /// Overloaded method. Invokes MSBuild using the default configuration and does without logging on the output window pane. /// public MSBuildResult Build(string target) { return this.Build(0, String.Empty, string.Empty, null, target); } /// /// 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 /// 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; } /// /// Do the build by invoking msbuild /// [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; } } /// /// Get value of Project property /// /// Name of Property to retrieve /// Value of property public string GetProjectProperty(string propertyName) { return this.GetProjectProperty(propertyName, true); } /// /// Set dirty state of project /// /// boolean value indicating dirty state public void SetProjectFileDirty(bool value) { this.isDirty = value; if (this.isDirty) { this.lastModifiedTime = DateTime.Now; this.buildIsPrepared = false; } } /// /// Get Node from ItemID. /// /// ItemID for the requested node /// Node if found 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]; } /// /// This method return new project element, and add new MSBuild item to the project/build hierarchy /// /// file name /// MSBuild item type /// new project element [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ms")] public MsBuildProjectElement CreateMsBuildFileItem(string file, string itemType) { return new MsBuildProjectElement(this, file, itemType); } /// /// This method returns new project element based on existing MSBuild item. It does not modify/add project/build hierarchy at all. /// /// MSBuild item instance /// wrapping project element public MsBuildProjectElement GetProjectElement(MSBuild.ProjectItem item) { return new MsBuildProjectElement(this, item); } /// /// Create FolderNode from Path /// /// Path to folder /// FolderNode created that can be added to the hierarchy public FolderNode CreateFolderNode(string path) { ProjectElement item = this.AddFolderToMsBuild(path); FolderNode folderNode = CreateFolderNode(item); return folderNode; } /// /// 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. /// 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(), 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(), 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; } /// /// 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. /// 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; } /// /// 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. /// /// PropertyGroup to clone /// 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; } /// /// Get the project extensions /// /// public MSBuildConstruction.ProjectExtensionsElement GetProjectExtensions() { var extensionsElement = this.buildProject.Xml.ChildrenReversed.OfType().FirstOrDefault(); if (extensionsElement == null) { extensionsElement = this.buildProject.Xml.CreateProjectExtensionsElement(); this.buildProject.Xml.AppendChild(extensionsElement); } return extensionsElement; } /// /// Set the xmlText as a project extension element with the id passed. /// /// The id of the project extension element. /// The value to set for a project extension. 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; } /// /// Register the project with the Scc manager. /// [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; } } /// /// Unregisters us from the SCC manager /// [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; } } /// /// Get the CATID corresponding to the specified type. /// /// Type of the object for which you want the CATID /// CATID [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; } /// /// 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. /// /// Type of the extensible object /// GUID that extender can use to uniquely identify your object type [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); } /// /// Initialize an object with an XML fragment. /// /// Object that support being initialized with an XML fragment /// Name of the configuration being initialized, null if it is the project 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, "{0}", 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)); } } } /// /// Retrieve all XML fragments that need to be saved from the flavors and store the information in msbuild. /// [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(); // 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; } /// /// Get the outer IVsHierarchy implementation. /// This is used for scenario where a flavor may be modifying the behavior /// 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() 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(); 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 /// /// Callback from the additem dialog. Deals with adding new and existing items /// 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); } /// /// Creates new items in a project, adds existing files to a project, or causes Add Item wizards to be run /// /// /// /// /// /// 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). /// /// /// /// /// /// /// S_OK if it succeeds /// The result array is initalized to failure. [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 filesToAdd = new List(); 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; } /// /// 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. /// 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)); } /// /// 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 /// 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; } /// /// 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. /// /// Old document name /// New document name /// Optional frame if the document is open /// 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 /// /// 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 /// /// The component operation to be performed. /// Number of components to be added /// array of component selector data /// Handle to the component picker dialog /// Result to be returned to the caller 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 /// /// This method is called to determine which files should be placed under source control for a given VSITEMID within this hierarchy. /// /// Identifier for the VSITEMID being queried. /// Pointer to an array of CALPOLESTR strings containing the file names for this item. /// Pointer to a CADWORD array of flags stored in DWORDs indicating that some of the files have special behaviors. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. 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 files = new List(); List flags = new List(); 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 files, IList flags) { for (HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling) { n.GetSccFiles(files, flags); } } public override void GetSccSpecialFiles(string sccFile, IList files, IList flags) { for (HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling) { n.GetSccSpecialFiles(sccFile, files, flags); } } /// /// This method is called to discover special (hidden files) associated with a given VSITEMID within this hierarchy. /// /// Identifier for the VSITEMID being queried. /// One of the files associated with the node /// Pointer to an array of CALPOLESTR strings containing the file names for this item. /// Pointer to a CADWORD array of flags stored in DWORDs indicating that some of the files have special behaviors. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. /// 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. 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 files = new List(); List flags = new List(); 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; } /// /// 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. /// /// Count of changed nodes. /// An array of VSITEMID identifiers of the changed nodes. /// An array of VsStateIcon glyphs representing the new state of the corresponding item in rgitemidAffectedNodes. /// An array of status flags from SccStatus corresponding to rgitemidAffectedNodes. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. 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; } /// /// 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. /// /// String, opaque to the project, that identifies the project location on the server. Persist this string in the project file. /// String, opaque to the project, that identifies the path to the server. Persist this string in the project file. /// String, opaque to the project, that identifies the local path to the project. Persist this string in the project file. /// String, opaque to the project, that identifies the source control package. Persist this string in the project file. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. 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 /// /// Allows you to query the project for special files and optionally create them. /// /// __PSFFILEID of the file /// __PSFFLAGS flags for the file /// The itemid of the node in the hierarchy /// The file name of the special file. /// 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 /// /// Get the inner object of an aggregated hierarchy /// /// A HierarchyNode public virtual HierarchyNode GetInner() { return this; } #endregion #region IReferenceDataProvider Members /// /// Returns the reference container node. /// /// 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 /// /// Retrieve the list of project GUIDs that are aggregated together to make this project. /// /// Semi colon separated list of Guids. Typically, the last GUID would be the GUID of the base project factory /// HResult 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; } /// /// This is where the initialization occurs. /// 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; } /// /// This is called after the project is done initializing the different layer of the aggregations /// /// HResult public virtual int OnAggregationComplete() { return VSConstants.S_OK; } /// /// Set the list of GUIDs that are aggregated together to create this project. /// /// Semi-colon separated list of GUIDs, the last one is usually the project factory of the base project factory /// HResult public int SetAggregateProjectTypeGuids(string projectTypeGuids) { this.SetProjectProperty(ProjectFileConstants.ProjectTypeGuids, projectTypeGuids); return VSConstants.S_OK; } /// /// We are always the inner most part of the aggregation /// and as such we don't support setting an inner project /// 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 /// /// Get the property of an item /// /// ItemID /// Name of the property /// Value of the property (out parameter) /// HRESULT 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; } /// /// Get the value of the property in the project file /// /// Name of the property to remove /// Configuration for which to remove the property /// Project or user file (_PersistStorageType) /// Value of the property (out parameter) /// HRESULT 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; } /// /// Delete a property /// In our case this simply mean defining it as null /// /// Name of the property to remove /// Configuration for which to remove the property /// Project or user file (_PersistStorageType) /// HRESULT int IVsBuildPropertyStorage.RemoveProperty(string propertyName, string configName, uint storage) { return ((IVsBuildPropertyStorage)this).SetPropertyValue(propertyName, configName, storage, null); } /// /// Set a property on an item /// /// ItemID /// Name of the property /// New value for the property /// HRESULT 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; } /// /// Set a project property /// /// Name of the property to set /// Configuration for which to set the property /// Project file or user file (_PersistStorageType) /// New value for that property /// HRESULT 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 /// /// Initialize projectNode /// 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); } /// /// Add an item to the hierarchy based on the item path /// /// Item to add /// Added node private HierarchyNode AddIndependentFileNode(MSBuild.ProjectItem item, HierarchyNode parent) { return AddFileNodeToNode(item, parent); } /// /// Add a dependent file node to the hierarchy /// /// msbuild item to add /// Parent Node /// Added node 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; } /// /// Add a file node to the hierarchy /// /// msbuild item to add /// Parent Node /// Added node private HierarchyNode AddFileNodeToNode(MSBuild.ProjectItem item, HierarchyNode parentNode) { FileNode node = this.CreateFileNode(new MsBuildProjectElement(this, item)); parentNode.AddChild(node); return node; } /// /// Get the parent node of an msbuild item /// /// msbuild item /// parent node 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; } /// /// Updates our scc project settings. /// /// String, opaque to the project, that identifies the project location on the server. Persist this string in the project file. /// String, opaque to the project, that identifies the path to the server. Persist this string in the project file. /// String, opaque to the project, that identifies the local path to the project. Persist this string in the project file. /// String, opaque to the project, that identifies the source control package. Persist this string in the project file. /// Returns true if something changed. 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; } /// /// Sets the scc info from the project file. /// 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; } /// /// 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. /// 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; } } } /// /// Helper for sharing common code between Build() and BuildAsync() /// /// /// 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; } /// /// Recusively parses the tree and closes all nodes. /// /// The subtree to close. private static void CloseAllNodes(HierarchyNode node) { for (HierarchyNode n = node.FirstChild; n != null; n = n.NextSibling) { if (n.FirstChild != null) { CloseAllNodes(n); } n.Close(); } } /// /// Set the build project with the new project instance value /// /// The new build project instance private void SetBuildProject(MSBuild.Project project) { bool isNewBuildProject = (this.buildProject != project); this.buildProject = project; if (this.buildProject != null) { SetupProjectGlobalPropertiesThatAllProjectSystemsMustSet(); } if (isNewBuildProject) { NewBuildProject(project); } } /// /// Called when a new value for is available. /// protected virtual void NewBuildProject(MSBuild.Project project) { } /// /// Setup the global properties for project instance. /// 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); } /// /// Attempts to lock in the privilege of running a build in Visual Studio. /// /// false if this build was called for by the Solution Build Manager; true otherwise. /// /// 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*(). /// /// A value indicating whether a build may proceed. /// /// This method must be called on the UI thread. /// 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(); } } } /// /// Lets Visual Studio know that we're done with our design-time build so others can use the build manager. /// /// The build submission that built, if any. /// This must be the same value as the one passed to . /// This must be the same value as the one passed to . /// /// This method must be called on the UI thread. /// 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; } /// /// Used to determine the kind of build system, in VS 2005 there's only one defined kind: MSBuild /// /// /// public virtual int GetBuildSystemKind(out uint kind) { kind = (uint)_BuildSystemKindFlags2.BSK_MSBUILD_VS10; return VSConstants.S_OK; } #endregion /// /// Finds a node by it's full path on disk. /// 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; } /// /// Closes the project node. /// /// A success or failure value. 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; } /// /// Sets the service provider from which to access the services. /// /// An instance to an Microsoft.VisualStudio.OLE.Interop object /// A success or failure value. 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; } /// /// 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 /// /// /// 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 clonedSink = new List(); 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); } } } /// /// Causes the hierarchy to be redrawn. /// /// Used by the hierarchy to decide which element to redraw [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 /// /// Saves the hierarchy item to disk. /// /// Flags whose values are taken from the VSSAVEFLAGS enumeration. /// New filename when doing silent save as /// Item identifier of the hierarchy item saved from VSITEMID. /// Item identifier of the hierarchy item saved from VSITEMID. /// [out] true if the save action was canceled. /// [out] true if the save action was canceled. [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; } /// /// Determines whether the hierarchy item changed. /// /// Item identifier of the hierarchy item contained in VSITEMID. /// Pointer to the IUnknown interface of the hierarchy item. /// true if the hierarchy item changed. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public virtual int IsItemDirty(uint itemId, IntPtr docData, out int isDirty) { IVsPersistDocData pd = (IVsPersistDocData)Marshal.GetObjectForIUnknown(docData); return ErrorHandler.ThrowOnFailure(pd.IsDocDataDirty(out isDirty)); } /// /// Flag indicating that changes to a file can be ignored when item is saved or reloaded. /// /// Specifies the item id from VSITEMID. /// Flag indicating whether or not to ignore changes (1 to ignore, 0 to stop ignoring). /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. 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; } /// /// Called to determine whether a project item is reloadable before calling ReloadItem. /// /// Item identifier of an item in the hierarchy. Valid values are VSITEMID_NIL, VSITEMID_ROOT and VSITEMID_SELECTION. /// A flag indicating that the project item is reloadable (1 for reloadable, 0 for non-reloadable). /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. [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; } /// /// Called to reload a project item. /// /// Specifies itemid from VSITEMID. /// Reserved. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. 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); } /// /// /// /// /// true if the project will be retargeted. false to load project in unloaded state. /// 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; }*/ } }