250 lines
12 KiB
C#
Raw Normal View History

/* ****************************************************************************
*
* Copyright (c) Microsoft Corporation.
*
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
* copy of the license can be found in the License.html file at the root of this distribution. If
* you cannot locate the Apache License, Version 2.0, please send an email to
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
* by the terms of the Apache License, Version 2.0.
*
* You must not remove this notice, or any other, from this software.
*
* ***************************************************************************/
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using EnvDTE;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.VisualStudio.Project.Automation {
/// <summary>
/// Contains ProjectItem objects
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
[ComVisible(true)]
public class OAProjectItems : OANavigableProjectItems {
#region ctor
public OAProjectItems(OAProject project, HierarchyNode nodeWithItems)
: base(project, nodeWithItems) {
}
#endregion
#region EnvDTE.ProjectItems
/// <summary>
/// Creates a new project item from an existing directory and all files and subdirectories
/// contained within it.
/// </summary>
/// <param name="directory">The full path of the directory to add.</param>
/// <returns>A ProjectItem object.</returns>
public override ProjectItem AddFromDirectory(string directory) {
CheckProjectIsValid();
return UIThread.Instance.RunSync<EnvDTE.ProjectItem>(() => {
ProjectItem result = AddFolder(directory, null);
foreach (string subdirectory in Directory.EnumerateDirectories(directory)) {
result.ProjectItems.AddFromDirectory(Path.Combine(directory, subdirectory));
}
foreach (var extension in this.Project.ProjectNode.CodeFileExtensions) {
foreach (string filename in Directory.EnumerateFiles(directory, "*" + extension)) {
result.ProjectItems.AddFromFile(Path.Combine(directory, filename));
}
foreach (string filename in Directory.EnumerateFiles(directory, "*" + extension)) {
result.ProjectItems.AddFromFile(Path.Combine(directory, filename));
}
}
return result;
});
}
/// <summary>
/// Creates a new project item from an existing item template file and adds it to the project.
/// </summary>
/// <param name="fileName">The full path and file name of the template project file.</param>
/// <param name="name">The file name to use for the new project item.</param>
/// <returns>A ProjectItem object. </returns>
public override EnvDTE.ProjectItem AddFromTemplate(string fileName, string name) {
CheckProjectIsValid();
ProjectNode proj = this.Project.ProjectNode;
EnvDTE.ProjectItem itemAdded = null;
using (AutomationScope scope = new AutomationScope(this.Project.ProjectNode.Site)) {
// Determine the operation based on the extension of the filename.
// We should run the wizard only if the extension is vstemplate
// otherwise it's a clone operation
VSADDITEMOPERATION op;
UIThread.Instance.RunSync(() => {
if (VsUtilities.IsTemplateFile(fileName)) {
op = VSADDITEMOPERATION.VSADDITEMOP_RUNWIZARD;
} else {
op = VSADDITEMOPERATION.VSADDITEMOP_CLONEFILE;
}
VSADDRESULT[] result = new VSADDRESULT[1];
// It is not a very good idea to throw since the AddItem might return Cancel or Abort.
// The problem is that up in the call stack the wizard code does not check whether it has received a ProjectItem or not and will crash.
// The other problem is that we cannot get add wizard dialog back if a cancel or abort was returned because we throw and that code will never be executed. Typical catch 22.
ErrorHandler.ThrowOnFailure(proj.AddItem(this.NodeWithItems.ID, op, name, 0, new string[1] { fileName }, IntPtr.Zero, result));
string fileDirectory = proj.GetBaseDirectoryForAddingFiles(this.NodeWithItems);
string templateFilePath = System.IO.Path.Combine(fileDirectory, name);
itemAdded = this.EvaluateAddResult(result[0], templateFilePath);
});
}
return itemAdded;
}
private void CheckProjectIsValid() {
if (this.Project == null || this.Project.ProjectNode == null || this.Project.ProjectNode.Site == null || this.Project.ProjectNode.IsClosed) {
throw new InvalidOperationException();
}
}
/// <summary>
/// Adds a folder to the collection of ProjectItems with the given name.
///
/// The kind must be null, empty string, or the string value of vsProjectItemKindPhysicalFolder.
/// Virtual folders are not supported by this implementation.
/// </summary>
/// <param name="name">The name of the new folder to add</param>
/// <param name="kind">A string representing a Guid of the folder kind.</param>
/// <returns>A ProjectItem representing the newly added folder.</returns>
public override ProjectItem AddFolder(string name, string kind) {
Project.CheckProjectIsValid();
//Verify name is not null or empty
VsUtilities.ValidateFileName(this.Project.ProjectNode.Site, name);
//Verify that kind is null, empty, or a physical folder
if (!(string.IsNullOrEmpty(kind) || kind.Equals(EnvDTE.Constants.vsProjectItemKindPhysicalFolder))) {
throw new ArgumentException("Parameter specification for AddFolder was not meet", "kind");
}
var existingChild = this.NodeWithItems.FindImmediateChildByName(name);
if (existingChild != null) {
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Folder already exists with the name '{0}'", name));
}
ProjectNode proj = this.Project.ProjectNode;
HierarchyNode newFolder = null;
using (AutomationScope scope = new AutomationScope(this.Project.ProjectNode.Site)) {
//In the case that we are adding a folder to a folder, we need to build up
//the path to the project node.
name = Path.Combine(NodeWithItems.FullPathToChildren, name);
newFolder = proj.CreateFolderNodes(name);
}
return newFolder.GetAutomationObject() as ProjectItem;
}
/// <summary>
/// Copies a source file and adds it to the project.
/// </summary>
/// <param name="filePath">The path and file name of the project item to be added.</param>
/// <returns>A ProjectItem object. </returns>
public override EnvDTE.ProjectItem AddFromFileCopy(string filePath) {
return this.AddItem(filePath, VSADDITEMOPERATION.VSADDITEMOP_CLONEFILE);
}
/// <summary>
/// Adds a project item from a file that is installed in a project directory structure.
/// </summary>
/// <param name="fileName">The file name of the item to add as a project item. </param>
/// <returns>A ProjectItem object. </returns>
public override EnvDTE.ProjectItem AddFromFile(string fileName) {
// TODO: VSADDITEMOP_LINKTOFILE
return this.AddItem(fileName, VSADDITEMOPERATION.VSADDITEMOP_OPENFILE);
}
#endregion
#region helper methods
/// <summary>
/// Adds an item to the project.
/// </summary>
/// <param name="path">The full path of the item to add.</param>
/// <param name="op">The <paramref name="VSADDITEMOPERATION"/> to use when adding the item.</param>
/// <returns>A ProjectItem object. </returns>
protected virtual EnvDTE.ProjectItem AddItem(string path, VSADDITEMOPERATION op) {
CheckProjectIsValid();
return UIThread.Instance.RunSync<EnvDTE.ProjectItem>(() => {
string ext = Path.GetExtension(path);
foreach (var extension in this.Project.ProjectNode.CodeFileExtensions) {
// http://pytools.codeplex.com/workitem/617
// We are currently in create project from existing code mode. The wizard walks all of the top-level
// files and adds them. It then lets us handle any subdirectories by calling AddFromDirectory.
// But we want to filter the files for both top-level and subdirectories. Therefore we derive from
// PageManager and track when we're running the wizard and adding files for the wizard. If we are
// currently adding them ignore anything other than a .py/.pyw files - returnning null is fine
// here, the wizard doesn't care about the result.
if (String.Compare(ext, extension, StringComparison.OrdinalIgnoreCase) == 0) {
ProjectNode proj = this.Project.ProjectNode;
EnvDTE.ProjectItem itemAdded = null;
using (AutomationScope scope = new AutomationScope(this.Project.ProjectNode.Site)) {
VSADDRESULT[] result = new VSADDRESULT[1];
ErrorHandler.ThrowOnFailure(proj.AddItem(this.NodeWithItems.ID, op, path, 0, new string[1] { path }, IntPtr.Zero, result));
string fileName = System.IO.Path.GetFileName(path);
string fileDirectory = proj.GetBaseDirectoryForAddingFiles(this.NodeWithItems);
string filePathInProject = System.IO.Path.Combine(fileDirectory, fileName);
itemAdded = this.EvaluateAddResult(result[0], filePathInProject);
}
return itemAdded;
}
}
return null;
});
}
/// <summary>
/// Evaluates the result of an add operation.
/// </summary>
/// <param name="result">The <paramref name="VSADDRESULT"/> returned by the Add methods</param>
/// <param name="path">The full path of the item added.</param>
/// <returns>A ProjectItem object.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
private EnvDTE.ProjectItem EvaluateAddResult(VSADDRESULT result, string path) {
return UIThread.Instance.RunSync<EnvDTE.ProjectItem>(() => {
if (result == VSADDRESULT.ADDRESULT_Success) {
if (Directory.Exists(path) && !CommonUtils.HasEndSeparator(path)) {
path = path + Path.DirectorySeparatorChar;
}
HierarchyNode nodeAdded = this.NodeWithItems.ProjectMgr.FindNodeByFullPath(path);
Debug.Assert(nodeAdded != null, "We should have been able to find the new element in the hierarchy");
if (nodeAdded != null) {
EnvDTE.ProjectItem item = null;
if (nodeAdded is FileNode) {
item = new OAFileItem(this.Project, nodeAdded as FileNode);
} else {
item = new OAProjectItem(this.Project, nodeAdded);
}
return item;
}
}
return null;
});
}
#endregion
}
}