PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/Tools/IronStudio/IronStudio/IronStudio/Project/CommonProjectNode.cs

http://github.com/IronLanguages/main
C# | 980 lines | 850 code | 57 blank | 73 comment | 50 complexity | 916affa175ada82ae22a99b91e757ea7 MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation.
  4. *
  5. * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
  6. * copy of the license can be found in the License.html file at the root of this distribution. If
  7. * you cannot locate the Apache License, Version 2.0, please send an email to
  8. * ironpy@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. * by the terms of the Apache License, Version 2.0.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. * ***************************************************************************/
  14. using System;
  15. using System.Collections.Generic;
  16. using System.Diagnostics;
  17. using System.Drawing;
  18. using System.IO;
  19. using System.Linq;
  20. using System.Runtime.InteropServices;
  21. using System.Windows.Forms;
  22. //
  23. using Microsoft.VisualStudio;
  24. using Microsoft.VisualStudio.Project;
  25. using Microsoft.VisualStudio.Project.Automation;
  26. using Microsoft.VisualStudio.Shell;
  27. using Microsoft.VisualStudio.Shell.Interop;
  28. using Microsoft.Scripting.Utils;
  29. using Microsoft.Windows.Design.Host;
  30. namespace Microsoft.IronStudio.Project {
  31. using Microsoft.IronStudio.Navigation;
  32. using VSConstants = Microsoft.VisualStudio.VSConstants;
  33. public enum CommonImageName {
  34. File = 0,
  35. Project = 1,
  36. SearchPathContainer,
  37. SearchPath,
  38. MissingSearchPath,
  39. StartupFile
  40. }
  41. public abstract class CommonProjectNode : ProjectNode, IVsProjectSpecificEditorMap2, IVsDeferredSaveProject {
  42. #region abstract methods
  43. public abstract Type GetProjectFactoryType();
  44. public abstract Type GetEditorFactoryType();
  45. public abstract string GetProjectName();
  46. public abstract string GetCodeFileExtension();
  47. public virtual CommonFileNode CreateCodeFileNode(ProjectElement item) {
  48. return new CommonFileNode(this, item);
  49. }
  50. public virtual CommonFileNode CreateNonCodeFileNode(ProjectElement item) {
  51. return new CommonNonCodeFileNode(this, item);
  52. }
  53. public abstract string GetFormatList();
  54. public abstract Type GetGeneralPropertyPageType();
  55. public abstract Type GetLibraryManagerType();
  56. public abstract string GetProjectFileExtension();
  57. #endregion
  58. #region fields
  59. private CommonProjectPackage/*!*/ _package;
  60. private Guid _mruPageGuid = new Guid(CommonConstants.AddReferenceMRUPageGuid);
  61. private VSLangProj.VSProject _vsProject = null;
  62. private static ImageList _imageList;
  63. private ProjectDocumentsListenerForStartupFileUpdates _projectDocListenerForStartupFileUpdates;
  64. private static int _imageOffset;
  65. private CommonSearchPathContainerNode _searchPathContainer;
  66. private string _projectDir;
  67. private bool _isRefreshing;
  68. private object _automationObject;
  69. #endregion
  70. #region Properties
  71. public new CommonProjectPackage/*!*/ Package {
  72. get { return _package; }
  73. }
  74. public static int ImageOffset {
  75. get { return _imageOffset; }
  76. }
  77. /// <summary>
  78. /// Get the VSProject corresponding to this project
  79. /// </summary>
  80. protected internal VSLangProj.VSProject VSProject {
  81. get {
  82. if (_vsProject == null)
  83. _vsProject = new OAVSProject(this);
  84. return _vsProject;
  85. }
  86. }
  87. private IVsHierarchy InteropSafeHierarchy {
  88. get {
  89. IntPtr unknownPtr = Utilities.QueryInterfaceIUnknown(this);
  90. if (IntPtr.Zero == unknownPtr) {
  91. return null;
  92. }
  93. IVsHierarchy hier = Marshal.GetObjectForIUnknown(unknownPtr) as IVsHierarchy;
  94. return hier;
  95. }
  96. }
  97. /// <summary>
  98. /// Returns project's directory name.
  99. /// </summary>
  100. public string ProjectDir {
  101. get { return _projectDir; }
  102. }
  103. /// <summary>
  104. /// Indicates whether the project is currently is busy refreshing its hierarchy.
  105. /// </summary>
  106. public bool IsRefreshing {
  107. get { return _isRefreshing; }
  108. }
  109. /// <summary>
  110. /// Language specific project images
  111. /// </summary>
  112. public static ImageList ImageList {
  113. get {
  114. return _imageList;
  115. }
  116. set {
  117. _imageList = value;
  118. }
  119. }
  120. #endregion
  121. #region ctor
  122. public CommonProjectNode(CommonProjectPackage/*!*/ package, ImageList/*!*/ imageList) {
  123. ContractUtils.RequiresNotNull(package, "package");
  124. ContractUtils.RequiresNotNull(imageList, "imageList");
  125. _package = package;
  126. CanFileNodesHaveChilds = true;
  127. OleServiceProvider.AddService(typeof(VSLangProj.VSProject), new OleServiceProvider.ServiceCreatorCallback(CreateServices), false);
  128. SupportsProjectDesigner = true;
  129. _imageList = imageList;
  130. //Store the number of images in ProjectNode so we know the offset of the language icons.
  131. _imageOffset = ImageHandler.ImageList.Images.Count;
  132. foreach (Image img in ImageList.Images) {
  133. ImageHandler.AddImage(img);
  134. }
  135. InitializeCATIDs();
  136. }
  137. #endregion
  138. #region overridden properties
  139. /// <summary>
  140. /// Since we appended the language images to the base image list in the constructor,
  141. /// this should be the offset in the ImageList of the langauge project icon.
  142. /// </summary>
  143. public override int ImageIndex {
  144. get {
  145. return _imageOffset + (int)CommonImageName.Project;
  146. }
  147. }
  148. public override Guid ProjectGuid {
  149. get {
  150. return GetProjectFactoryType().GUID;
  151. }
  152. }
  153. public override string ProjectType {
  154. get {
  155. return GetProjectName();
  156. }
  157. }
  158. internal override object Object {
  159. get {
  160. return VSProject;
  161. }
  162. }
  163. #endregion
  164. #region overridden methods
  165. public override object GetAutomationObject() {
  166. if (_automationObject == null) {
  167. _automationObject = base.GetAutomationObject();
  168. }
  169. return _automationObject;
  170. }
  171. protected override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) {
  172. if (cmdGroup == CommonConstants.Std97CmdGroupGuid) {
  173. switch ((VSConstants.VSStd97CmdID)cmd) {
  174. case VSConstants.VSStd97CmdID.BuildCtx:
  175. case VSConstants.VSStd97CmdID.RebuildCtx:
  176. case VSConstants.VSStd97CmdID.CleanCtx:
  177. result = QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE;
  178. return VSConstants.S_OK;
  179. }
  180. } else if (cmdGroup == GuidList.guidIronStudioCmdSet) {
  181. switch ((int)cmd) {
  182. case CommonConstants.AddSearchPathCommandId:
  183. case CommonConstants.StartWithoutDebuggingCmdId:
  184. result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
  185. return VSConstants.S_OK;
  186. }
  187. }
  188. return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result);
  189. }
  190. protected override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) {
  191. if (cmdGroup == GuidList.guidIronStudioCmdSet) {
  192. switch ((int)cmd) {
  193. case CommonConstants.AddSearchPathCommandId:
  194. AddSearchPath();
  195. return VSConstants.S_OK;
  196. case CommonConstants.StartWithoutDebuggingCmdId:
  197. EnvDTE.Project automationObject = this.GetAutomationObject() as EnvDTE.Project;
  198. string activeConfigName = Utilities.GetActiveConfigurationName(automationObject);
  199. CommonProjectConfig config = new CommonProjectConfig(this, activeConfigName);
  200. return config.DebugLaunch((uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_NoDebug);
  201. }
  202. }
  203. return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut);
  204. }
  205. /// <summary>
  206. /// As we don't register files/folders in the project file, removing an item is a noop.
  207. /// </summary>
  208. public override int RemoveItem(uint reserved, uint itemId, out int result) {
  209. result = 1;
  210. return VSConstants.S_OK;
  211. }
  212. //No build for dynamic languages by default!
  213. public override MSBuildResult Build(uint vsopts, string config, IVsOutputWindowPane output, string target) {
  214. return MSBuildResult.Successful;
  215. }
  216. internal override void BuildAsync(uint vsopts, string config, IVsOutputWindowPane output, string target, Action<MSBuildResult, string> uiThreadCallback) {
  217. uiThreadCallback(MSBuildResult.Successful, target);
  218. }
  219. /// <summary>
  220. /// Overriding main project loading method to inject our hierarachy of nodes.
  221. /// </summary>
  222. protected override void Reload() {
  223. _projectDir = Path.GetDirectoryName(this.BaseURI.Uri.LocalPath);
  224. _searchPathContainer = new CommonSearchPathContainerNode(this);
  225. this.AddChild(_searchPathContainer);
  226. base.Reload();
  227. RefreshHierarchy();
  228. OnProjectPropertyChanged += new EventHandler<ProjectPropertyChangedArgs>(CommonProjectNode_OnProjectPropertyChanged);
  229. }
  230. protected override ReferenceContainerNode CreateReferenceContainerNode() {
  231. return new CommonReferenceContainerNode(this);
  232. }
  233. public override int GetGuidProperty(int propid, out Guid guid) {
  234. if ((__VSHPROPID)propid == __VSHPROPID.VSHPROPID_PreferredLanguageSID) {
  235. guid = new Guid("{EFB9A1D6-EA71-4F38-9BA7-368C33FCE8DC}");// GetLanguageServiceType().GUID;
  236. } else {
  237. return base.GetGuidProperty(propid, out guid);
  238. }
  239. return VSConstants.S_OK;
  240. }
  241. protected override bool IsItemTypeFileType(string type) {
  242. if (!base.IsItemTypeFileType(type)) {
  243. if (String.Compare(type, "Page", StringComparison.OrdinalIgnoreCase) == 0
  244. || String.Compare(type, "ApplicationDefinition", StringComparison.OrdinalIgnoreCase) == 0
  245. || String.Compare(type, "Resource", StringComparison.OrdinalIgnoreCase) == 0) {
  246. return true;
  247. } else {
  248. return false;
  249. }
  250. } else {
  251. //This is a well known item node type, so return true.
  252. return true;
  253. }
  254. }
  255. protected override NodeProperties CreatePropertiesObject() {
  256. return new CommonProjectNodeProperties(this);
  257. }
  258. public override int SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider site) {
  259. base.SetSite(site);
  260. //Initialize a new object to track project document changes so that we can update the StartupFile Property accordingly
  261. _projectDocListenerForStartupFileUpdates = new ProjectDocumentsListenerForStartupFileUpdates((ServiceProvider)Site, this);
  262. _projectDocListenerForStartupFileUpdates.Init();
  263. return VSConstants.S_OK;
  264. }
  265. public override int Close() {
  266. if (null != _projectDocListenerForStartupFileUpdates) {
  267. _projectDocListenerForStartupFileUpdates.Dispose();
  268. _projectDocListenerForStartupFileUpdates = null;
  269. }
  270. if (null != Site) {
  271. LibraryManager libraryManager = Site.GetService(GetLibraryManagerType()) as LibraryManager;
  272. if (null != libraryManager) {
  273. libraryManager.UnregisterHierarchy(InteropSafeHierarchy);
  274. }
  275. }
  276. return base.Close();
  277. }
  278. public override void Load(string filename, string location, string name, uint flags, ref Guid iidProject, out int canceled) {
  279. base.Load(filename, location, name, flags, ref iidProject, out canceled);
  280. LibraryManager libraryManager = Site.GetService(GetLibraryManagerType()) as LibraryManager;
  281. if (null != libraryManager) {
  282. libraryManager.RegisterHierarchy(InteropSafeHierarchy);
  283. }
  284. //If this is a WPFFlavor-ed project, then add a project-level DesignerContext service to provide
  285. //event handler generation (EventBindingProvider) for the XAML designer.
  286. this.OleServiceProvider.AddService(typeof(DesignerContext), new OleServiceProvider.ServiceCreatorCallback(this.CreateServices), false);
  287. }
  288. /// <summary>
  289. /// Overriding to provide project general property page
  290. /// </summary>
  291. /// <returns></returns>
  292. protected override Guid[] GetConfigurationIndependentPropertyPages() {
  293. Guid[] result = new Guid[1];
  294. result[0] = GetGeneralPropertyPageType().GUID;
  295. return result;
  296. }
  297. /// <summary>
  298. /// Overriding to provide customization of files on add files.
  299. /// This will replace tokens in the file with actual value (namespace, class name,...)
  300. /// </summary>
  301. /// <param name="source">Full path to template file</param>
  302. /// <param name="target">Full path to destination file</param>
  303. public override void AddFileFromTemplate(string source, string target) {
  304. if (!System.IO.File.Exists(source))
  305. throw new FileNotFoundException(String.Format("Template file not found: {0}", source));
  306. // We assume that there is no token inside the file because the only
  307. // way to add a new element should be through the template wizard that
  308. // take care of expanding and replacing the tokens.
  309. // The only task to perform is to copy the source file in the
  310. // target location.
  311. string targetFolder = Path.GetDirectoryName(target);
  312. if (!Directory.Exists(targetFolder)) {
  313. Directory.CreateDirectory(targetFolder);
  314. }
  315. File.Copy(source, target);
  316. }
  317. /// <summary>
  318. /// Evaluates if a file is a current language code file based on is extension
  319. /// </summary>
  320. /// <param name="strFileName">The filename to be evaluated</param>
  321. /// <returns>true if is a code file</returns>
  322. public override bool IsCodeFile(string strFileName) {
  323. // We do not want to assert here, just return silently.
  324. if (String.IsNullOrEmpty(strFileName)) {
  325. return false;
  326. }
  327. return (String.Compare(Path.GetExtension(strFileName),
  328. GetCodeFileExtension(),
  329. StringComparison.OrdinalIgnoreCase) == 0);
  330. }
  331. /// <summary>
  332. /// Create a file node based on an msbuild item.
  333. /// </summary>
  334. /// <param name="item">The msbuild item to be analyzed</param>
  335. public override FileNode CreateFileNode(ProjectElement item) {
  336. if (item == null) {
  337. throw new ArgumentNullException("item");
  338. }
  339. CommonFileNode newNode;
  340. if (string.Compare(GetCodeFileExtension(), Path.GetExtension(item.GetFullPathForElement()), StringComparison.OrdinalIgnoreCase) == 0) {
  341. newNode = CreateCodeFileNode(item);
  342. } else {
  343. newNode = CreateNonCodeFileNode(item);
  344. }
  345. string include = item.GetMetadata(ProjectFileConstants.Include);
  346. newNode.OleServiceProvider.AddService(typeof(EnvDTE.Project),
  347. new OleServiceProvider.ServiceCreatorCallback(CreateServices), false);
  348. newNode.OleServiceProvider.AddService(typeof(EnvDTE.ProjectItem), newNode.ServiceCreator, false);
  349. if (!string.IsNullOrEmpty(include) && Path.GetExtension(include).Equals(".xaml", StringComparison.OrdinalIgnoreCase)) {
  350. //Create a DesignerContext for the XAML designer for this file
  351. newNode.OleServiceProvider.AddService(typeof(DesignerContext), newNode.ServiceCreator, false);
  352. }
  353. newNode.OleServiceProvider.AddService(typeof(VSLangProj.VSProject),
  354. new OleServiceProvider.ServiceCreatorCallback(CreateServices), false);
  355. return newNode;
  356. }
  357. /// <summary>
  358. /// Create a file node based on absolute file name.
  359. /// </summary>
  360. public override FileNode CreateFileNode(string absFileName) {
  361. // Avoid adding files to the project multiple times. Ultimately
  362. // we should not use project items and instead should have virtual items.
  363. string path = absFileName;
  364. if (absFileName.Length > ProjectDir.Length &&
  365. String.Compare(ProjectDir, 0, absFileName, 0, ProjectDir.Length, StringComparison.OrdinalIgnoreCase) == 0) {
  366. path = absFileName.Substring(ProjectDir.Length);
  367. if (path.StartsWith("\\")) {
  368. path = path.Substring(1);
  369. }
  370. }
  371. var prjItem = GetExistingItem(absFileName) ?? BuildProject.AddItem("None", path)[0];
  372. ProjectElement prjElem = new ProjectElement(this, prjItem, false);
  373. return CreateFileNode(prjElem);
  374. }
  375. protected Microsoft.Build.Evaluation.ProjectItem GetExistingItem(string absFileName) {
  376. Microsoft.Build.Evaluation.ProjectItem prjItem = null;
  377. foreach (var item in BuildProject.Items) {
  378. if (item.UnevaluatedInclude == absFileName) {
  379. prjItem = item;
  380. break;
  381. }
  382. }
  383. return prjItem;
  384. }
  385. public ProjectElement MakeProjectElement(string type, string path) {
  386. var item = BuildProject.AddItem(type, path)[0];
  387. return new ProjectElement(this, item, false);
  388. }
  389. public override int IsDirty(out int isDirty) {
  390. isDirty = 0;
  391. if (IsProjectFileDirty) {
  392. isDirty = 1;
  393. return VSConstants.S_OK;
  394. }
  395. isDirty = IsFlavorDirty();
  396. return VSConstants.S_OK;
  397. }
  398. protected override void AddNewFileNodeToHierarchy(HierarchyNode parentNode, string fileName) {
  399. base.AddNewFileNodeToHierarchy(parentNode, fileName);
  400. SetProjectFileDirty(true);
  401. }
  402. public override DependentFileNode CreateDependentFileNode(ProjectElement item) {
  403. DependentFileNode node = base.CreateDependentFileNode(item);
  404. if (null != node) {
  405. string include = item.GetMetadata(ProjectFileConstants.Include);
  406. if (IsCodeFile(include)) {
  407. node.OleServiceProvider.AddService(
  408. typeof(SVSMDCodeDomProvider), new OleServiceProvider.ServiceCreatorCallback(CreateServices), false);
  409. }
  410. }
  411. return node;
  412. }
  413. /// <summary>
  414. /// Creates the format list for the open file dialog
  415. /// </summary>
  416. /// <param name="formatlist">The formatlist to return</param>
  417. /// <returns>Success</returns>
  418. public override int GetFormatList(out string formatlist) {
  419. formatlist = GetFormatList();
  420. return VSConstants.S_OK;
  421. }
  422. /// <summary>
  423. /// This overrides the base class method to show the VS 2005 style Add reference dialog. The ProjectNode implementation
  424. /// shows the VS 2003 style Add Reference dialog.
  425. /// </summary>
  426. /// <returns>S_OK if succeeded. Failure other wise</returns>
  427. public override int AddProjectReference() {
  428. IVsComponentSelectorDlg2 componentDialog;
  429. Guid guidEmpty = Guid.Empty;
  430. VSCOMPONENTSELECTORTABINIT[] tabInit = new VSCOMPONENTSELECTORTABINIT[1];
  431. string strBrowseLocations = Path.GetDirectoryName(BaseURI.Uri.LocalPath);
  432. //Add the Project page
  433. tabInit[0].dwSize = (uint)Marshal.SizeOf(typeof(VSCOMPONENTSELECTORTABINIT));
  434. // Tell the Add Reference dialog to call hierarchies GetProperty with the following
  435. // propID to enable filtering out ourself from the Project to Project reference
  436. tabInit[0].varTabInitInfo = (int)__VSHPROPID.VSHPROPID_ShowProjInSolutionPage;
  437. tabInit[0].guidTab = VSConstants.GUID_SolutionPage;
  438. uint pX = 0, pY = 0;
  439. componentDialog = GetService(typeof(SVsComponentSelectorDlg)) as IVsComponentSelectorDlg2;
  440. try {
  441. // call the container to open the add reference dialog.
  442. if (componentDialog != null) {
  443. // Let the project know not to show itself in the Add Project Reference Dialog page
  444. ShowProjectInSolutionPage = false;
  445. // call the container to open the add reference dialog.
  446. ErrorHandler.ThrowOnFailure(componentDialog.ComponentSelectorDlg2(
  447. (System.UInt32)(__VSCOMPSELFLAGS.VSCOMSEL_MultiSelectMode | __VSCOMPSELFLAGS.VSCOMSEL_IgnoreMachineName),
  448. (IVsComponentUser)this,
  449. 0,
  450. null,
  451. DynamicProjectSR.GetString(Microsoft.VisualStudio.Project.SR.AddReferenceDialogTitle), // Title
  452. "VS.AddReference", // Help topic
  453. ref pX,
  454. ref pY,
  455. (uint)tabInit.Length,
  456. tabInit,
  457. ref guidEmpty,
  458. "*.dll",
  459. ref strBrowseLocations));
  460. }
  461. } catch (COMException e) {
  462. Trace.WriteLine("Exception : " + e.Message);
  463. return e.ErrorCode;
  464. } finally {
  465. // Let the project know it can show itself in the Add Project Reference Dialog page
  466. ShowProjectInSolutionPage = true;
  467. }
  468. return VSConstants.S_OK;
  469. }
  470. protected override ConfigProvider CreateConfigProvider() {
  471. return new CommonConfigProvider(this);
  472. }
  473. #endregion
  474. #region Methods
  475. /// <summary>
  476. /// Main method for refreshing project hierarchy. It's called on project loading
  477. /// and each time the project property is changing.
  478. /// </summary>
  479. protected void RefreshHierarchy() {
  480. try {
  481. _isRefreshing = true;
  482. string projHome = GetProjectHomeDir();
  483. string workDir = GetWorkingDirectory();
  484. IList<string> searchPath = ParseSearchPath();
  485. //Refresh CWD node
  486. bool needCWD = !CommonUtils.AreTheSameDirectories(projHome, workDir);
  487. var cwdNode = FindImmediateChild<CurrentWorkingDirectoryNode>(_searchPathContainer);
  488. if (needCWD) {
  489. if (cwdNode == null) {
  490. //No cwd node yet
  491. _searchPathContainer.AddChild(new CurrentWorkingDirectoryNode(this, workDir));
  492. } else if (!CommonUtils.AreTheSameDirectories(cwdNode.Url, workDir)) {
  493. //CWD has changed, recreate the node
  494. cwdNode.Remove(false);
  495. _searchPathContainer.AddChild(new CurrentWorkingDirectoryNode(this, workDir));
  496. }
  497. } else {
  498. //No need to show CWD, remove if exists
  499. if (cwdNode != null) {
  500. cwdNode.Remove(false);
  501. }
  502. }
  503. //Refresh regular search path nodes
  504. //We need to update search path nodes according to the search path property.
  505. //It's quite expensive to remove all and build all nodes from scratch,
  506. //so we are going to perform some smarter update.
  507. //We are looping over paths in the search path and if a corresponding node
  508. //exists, we only update its index (sort order), creating new node otherwise.
  509. //At the end all nodes that haven't been updated have to be removed - they are
  510. //not in the search path anymore.
  511. var searchPathNodes = new List<CommonSearchPathNode>();
  512. this.FindNodesOfType<CommonSearchPathNode>(searchPathNodes);
  513. bool[] updatedNodes = new bool[searchPathNodes.Count];
  514. int index;
  515. for (int i = 0; i < searchPath.Count; i++) {
  516. string path = searchPath[i];
  517. //ParseSearchPath() must resolve all paths
  518. Debug.Assert(Path.IsPathRooted(path));
  519. var node = FindSearchPathNodeByPath(searchPathNodes, path, out index);
  520. bool alreadyShown = CommonUtils.AreTheSameDirectories(workDir, path) ||
  521. CommonUtils.AreTheSameDirectories(projHome, path);
  522. if (!alreadyShown) {
  523. if (node != null) {
  524. //existing path, update index (sort order)
  525. node.Index = i;
  526. updatedNodes[index] = true;
  527. } else {
  528. //new path - create new node
  529. _searchPathContainer.AddChild(new CommonSearchPathNode(this, path, i));
  530. }
  531. }
  532. }
  533. //Refresh nodes and remove non-updated ones
  534. for (int i = 0; i < searchPathNodes.Count; i++) {
  535. if (!updatedNodes[i]) {
  536. searchPathNodes[i].Remove();
  537. }
  538. }
  539. // TODO: Port, fix me
  540. //_searchPathContainer.UpdateSortOrder();
  541. _searchPathContainer.OnInvalidateItems(this);
  542. } finally {
  543. _isRefreshing = false;
  544. }
  545. }
  546. /// <summary>
  547. /// Returns resolved value of the current working directory property.
  548. /// </summary>
  549. public string GetWorkingDirectory() {
  550. string workDir = this.ProjectMgr.GetProjectProperty(CommonConstants.WorkingDirectory, true);
  551. if (string.IsNullOrEmpty(workDir)) {
  552. //If empty - take project directory as working directory
  553. workDir = _projectDir;
  554. } else if (!Path.IsPathRooted(workDir)) {
  555. //If relative path - resolve it based on project home
  556. workDir = Path.Combine(_projectDir, workDir);
  557. }
  558. return CommonUtils.NormalizeDirectoryPath(workDir);
  559. }
  560. /// <summary>
  561. /// Returns resolved value of the project home directory property.
  562. /// </summary>
  563. internal string GetProjectHomeDir() {
  564. string projHome = this.ProjectMgr.GetProjectProperty(CommonConstants.ProjectHome, true);
  565. if (string.IsNullOrEmpty(projHome)) {
  566. //If empty - take project directory as project home
  567. projHome = _projectDir;
  568. } else if (!Path.IsPathRooted(projHome)) {
  569. //If relative path - resolve it based on project directory
  570. projHome = Path.Combine(_projectDir, projHome);
  571. }
  572. return CommonUtils.NormalizeDirectoryPath(projHome);
  573. }
  574. /// <summary>
  575. /// Returns resolved value of the startup file property.
  576. /// </summary>
  577. internal string GetStartupFile() {
  578. string startupFile = ProjectMgr.GetProjectProperty(CommonConstants.StartupFile, true);
  579. if (string.IsNullOrEmpty(startupFile)) {
  580. //No startup file is assigned
  581. return null;
  582. } else if (!Path.IsPathRooted(startupFile)) {
  583. //If relative path - resolve it based on project home
  584. return Path.Combine(GetProjectHomeDir(), startupFile);
  585. }
  586. return startupFile;
  587. }
  588. /// <summary>
  589. /// Whenever project property has changed - refresh project hierarachy.
  590. /// </summary>
  591. private void CommonProjectNode_OnProjectPropertyChanged(object sender, ProjectPropertyChangedArgs e) {
  592. RefreshHierarchy();
  593. }
  594. /// <summary>
  595. /// Returns first immediate child node (non-recursive) of a given type.
  596. /// </summary>
  597. private static T FindImmediateChild<T>(HierarchyNode parent)
  598. where T : HierarchyNode {
  599. for (HierarchyNode n = parent.FirstChild; n != null; n = n.NextSibling) {
  600. if (n is T) {
  601. return (T)n;
  602. }
  603. }
  604. return null;
  605. }
  606. /// <summary>
  607. /// Finds Search Path node by a given search path and returns it along with the node's index.
  608. /// </summary>
  609. private CommonSearchPathNode FindSearchPathNodeByPath(IList<CommonSearchPathNode> nodes, string path, out int index) {
  610. index = 0;
  611. for (int j = 0; j < nodes.Count; j++) {
  612. if (CommonUtils.AreTheSameDirectories(nodes[j].Url, path)) {
  613. index = j;
  614. return nodes[j];
  615. }
  616. }
  617. return null;
  618. }
  619. /// <summary>
  620. /// Provide mapping from our browse objects and automation objects to our CATIDs
  621. /// </summary>
  622. private void InitializeCATIDs() {
  623. Type projectNodePropsType = typeof(CommonProjectNodeProperties);
  624. Type fileNodePropsType = typeof(CommonFileNodeProperties);
  625. // The following properties classes are specific to current language so we can use their GUIDs directly
  626. AddCATIDMapping(projectNodePropsType, projectNodePropsType.GUID);
  627. AddCATIDMapping(fileNodePropsType, fileNodePropsType.GUID);
  628. // The following is not language specific and as such we need a separate GUID
  629. AddCATIDMapping(typeof(FolderNodeProperties), new Guid(CommonConstants.FolderNodePropertiesGuid));
  630. // This one we use the same as language file nodes since both refer to files
  631. AddCATIDMapping(typeof(FileNodeProperties), fileNodePropsType.GUID);
  632. // Because our property page pass itself as the object to display in its grid,
  633. // we need to make it have the same CATID
  634. // as the browse object of the project node so that filtering is possible.
  635. AddCATIDMapping(GetGeneralPropertyPageType(), projectNodePropsType.GUID);
  636. // We could also provide CATIDs for references and the references container node, if we wanted to.
  637. }
  638. /// <summary>
  639. /// Parses SearchPath property into a list of distinct absolute paths, preserving the order.
  640. /// </summary>
  641. private IList<string> ParseSearchPath() {
  642. string searchPath = this.ProjectMgr.GetProjectProperty(CommonConstants.SearchPath, true);
  643. List<string> parsedPaths = new List<string>();
  644. if (!string.IsNullOrEmpty(searchPath)) {
  645. foreach (string path in searchPath.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) {
  646. string resolvedPath = CommonUtils.NormalizeDirectoryPath(Path.Combine(_projectDir, path));
  647. if (!parsedPaths.Contains(resolvedPath)) {
  648. parsedPaths.Add(resolvedPath);
  649. }
  650. }
  651. }
  652. return parsedPaths;
  653. }
  654. /// <summary>
  655. /// Saves list of paths back as SearchPath project property.
  656. /// </summary>
  657. private void SaveSearchPath(IList<string> value) {
  658. string valueStr = "";
  659. if (value != null && value.Count > 0) {
  660. valueStr = value.Aggregate((joined, path) => joined + ';' + path);
  661. }
  662. this.ProjectMgr.SetProjectProperty(CommonConstants.SearchPath, valueStr);
  663. }
  664. /// <summary>
  665. /// Adds new search path to the SearchPath project property.
  666. /// </summary>
  667. private void AddSearchPathEntry(string newpath) {
  668. if (newpath == null) {
  669. throw new ArgumentNullException("newpath");
  670. }
  671. IList<string> searchPath = ParseSearchPath();
  672. if (searchPath.Contains(newpath, StringComparer.CurrentCultureIgnoreCase)) {
  673. return;
  674. }
  675. searchPath.Add(newpath);
  676. SaveSearchPath(searchPath);
  677. }
  678. /// <summary>
  679. /// Removes a given path from the SearchPath property.
  680. /// </summary>
  681. internal void RemoveSearchPathEntry(string path) {
  682. IList<string> searchPath = ParseSearchPath();
  683. if (searchPath.Remove(path)) {
  684. SaveSearchPath(searchPath);
  685. }
  686. }
  687. /// <summary>
  688. /// Creates the services exposed by this project.
  689. /// </summary>
  690. private object CreateServices(Type serviceType) {
  691. object service = null;
  692. if (typeof(VSLangProj.VSProject) == serviceType) {
  693. service = VSProject;
  694. } else if (typeof(EnvDTE.Project) == serviceType) {
  695. service = GetAutomationObject();
  696. } else if (typeof(DesignerContext) == serviceType) {
  697. service = this.DesignerContext;
  698. }
  699. return service;
  700. }
  701. protected virtual internal Microsoft.Windows.Design.Host.DesignerContext DesignerContext {
  702. get {
  703. return null;
  704. }
  705. }
  706. /// <summary>
  707. /// Executes Add Search Path menu command.
  708. /// </summary>
  709. internal int AddSearchPath() {
  710. // Get a reference to the UIShell.
  711. IVsUIShell uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell;
  712. if (null == uiShell) {
  713. return VSConstants.S_FALSE;
  714. }
  715. //Create a fill in a structure that defines Browse for folder dialog
  716. VSBROWSEINFOW[] browseInfo = new VSBROWSEINFOW[1];
  717. //Dialog title
  718. browseInfo[0].pwzDlgTitle = DynamicProjectSR.GetString(DynamicProjectSR.SelectFolderForSearchPath);
  719. //Initial directory - project directory
  720. browseInfo[0].pwzInitialDir = _projectDir;
  721. //Parent window
  722. uiShell.GetDialogOwnerHwnd(out browseInfo[0].hwndOwner);
  723. //Max path length
  724. browseInfo[0].nMaxDirName = NativeMethods.MAX_PATH;
  725. //This struct size
  726. browseInfo[0].lStructSize = (uint)Marshal.SizeOf(typeof(VSBROWSEINFOW));
  727. //Memory to write selected directory to.
  728. //Note: this one allocates unmanaged memory, which must be freed later
  729. IntPtr pDirName = Marshal.AllocCoTaskMem(NativeMethods.MAX_PATH);
  730. browseInfo[0].pwzDirName = pDirName;
  731. try {
  732. //Show the dialog
  733. int hr = uiShell.GetDirectoryViaBrowseDlg(browseInfo);
  734. if (hr == VSConstants.OLE_E_PROMPTSAVECANCELLED) {
  735. //User cancelled the dialog
  736. return VSConstants.S_OK;
  737. }
  738. //Check for any failures
  739. ErrorHandler.ThrowOnFailure(hr);
  740. //Get selected directory
  741. string dirName = Marshal.PtrToStringAuto(browseInfo[0].pwzDirName);
  742. AddSearchPathEntry(dirName);
  743. } finally {
  744. //Free allocated unmanaged memory
  745. if (pDirName != IntPtr.Zero) {
  746. Marshal.FreeCoTaskMem(pDirName);
  747. }
  748. }
  749. return VSConstants.S_OK;
  750. }
  751. #endregion
  752. #region IVsProjectSpecificEditorMap2 Members
  753. public int GetSpecificEditorProperty(string mkDocument, int propid, out object result) {
  754. // initialize output params
  755. result = null;
  756. //Validate input
  757. if (string.IsNullOrEmpty(mkDocument))
  758. throw new ArgumentException("Was null or empty", "mkDocument");
  759. // Make sure that the document moniker passed to us is part of this project
  760. // We also don't care if it is not a dynamic language file node
  761. uint itemid;
  762. ErrorHandler.ThrowOnFailure(ParseCanonicalName(mkDocument, out itemid));
  763. HierarchyNode hierNode = NodeFromItemId(itemid);
  764. if (hierNode == null || ((hierNode as CommonFileNode) == null))
  765. return VSConstants.E_NOTIMPL;
  766. switch (propid) {
  767. case (int)__VSPSEPROPID.VSPSEPROPID_UseGlobalEditorByDefault:
  768. // we do not want to use global editor for form files
  769. result = true;
  770. break;
  771. //case (int)__VSPSEPROPID.VSPSEPROPID_ProjectDefaultEditorName:
  772. // result = "Python Form Editor";
  773. // break;
  774. }
  775. return VSConstants.S_OK;
  776. }
  777. public int GetSpecificEditorType(string mkDocument, out Guid guidEditorType) {
  778. // Ideally we should at this point initalize a File extension to EditorFactory guid Map e.g.
  779. // in the registry hive so that more editors can be added without changing this part of the
  780. // code. Dynamic languages only make usage of one Editor Factory and therefore we will return
  781. // that guid
  782. guidEditorType = GetEditorFactoryType().GUID;
  783. return VSConstants.S_OK;
  784. }
  785. public int GetSpecificLanguageService(string mkDocument, out Guid guidLanguageService) {
  786. guidLanguageService = Guid.Empty;
  787. return VSConstants.E_NOTIMPL;
  788. }
  789. public int SetSpecificEditorProperty(string mkDocument, int propid, object value) {
  790. return VSConstants.E_NOTIMPL;
  791. }
  792. #endregion
  793. #region IVsDeferredSaveProject Members
  794. /// <summary>
  795. /// Implements deferred save support. Enabled by unchecking Tools->Options->Solutions and Projects->Save New Projects Created.
  796. ///
  797. /// In this mode we save the project when the user selects Save All. We need to move all the files in the project
  798. /// over to the new location.
  799. /// </summary>
  800. public virtual int SaveProjectToLocation(string pszProjectFilename) {
  801. string oldName = Url;
  802. string basePath = Path.GetDirectoryName(this.FileName) + Path.DirectorySeparatorChar;
  803. string newName = Path.GetDirectoryName(pszProjectFilename);
  804. // we don't use RenameProjectFile because it sends the OnAfterRenameProject event too soon
  805. // and causes VS to think the solution has changed on disk. We need to send it after all
  806. // updates are complete.
  807. // save the new project to to disk
  808. SaveMSBuildProjectFileAs(pszProjectFilename);
  809. // remove all the children, saving any dirty files, and collecting the list of open files
  810. MoveFilesForDeferredSave(this, basePath, newName);
  811. _projectDir = newName;
  812. // save the project again w/ updated file info
  813. BuildProject.Save();
  814. SetProjectFileDirty(false);
  815. // update VS that we've changed the project
  816. this.OnPropertyChanged(this, (int)__VSHPROPID.VSHPROPID_Caption, 0);
  817. IVsUIShell shell = this.Site.GetService(typeof(SVsUIShell)) as IVsUIShell;
  818. IVsSolution vsSolution = (IVsSolution)this.GetService(typeof(SVsSolution));
  819. // Update solution
  820. ErrorHandler.ThrowOnFailure(vsSolution.OnAfterRenameProject((IVsProject)this, oldName, pszProjectFilename, 0));
  821. ErrorHandler.ThrowOnFailure(shell.RefreshPropertyBrowser(0));
  822. return VSConstants.S_OK;
  823. }
  824. private static string GetNewFilePathForDeferredSave(string baseOldPath, string baseNewPath, string itemPath) {
  825. var relativeName = itemPath.Substring(baseOldPath.Length);
  826. return Path.Combine(baseNewPath, relativeName);
  827. }
  828. private void MoveFilesForDeferredSave(HierarchyNode node, string basePath, string baseNewPath) {
  829. if (node != null) {
  830. for (var child = node.FirstChild; child != null; child = child.NextSibling) {
  831. bool isOpen, isDirty, isOpenedByUs;
  832. uint docCookie;
  833. IVsPersistDocData persist;
  834. var docMgr = child.GetDocumentManager();
  835. if (docMgr != null) {
  836. docMgr.GetDocInfo(out isOpen, out isDirty, out isOpenedByUs, out docCookie, out persist);
  837. int cancelled;
  838. if (isDirty) {
  839. child.SaveItem(VSSAVEFLAGS.VSSAVE_Save, null, docCookie, IntPtr.Zero, out cancelled);
  840. }
  841. FileNode fn = child as FileNode;
  842. if (fn != null) {
  843. string newLoc = GetNewFilePathForDeferredSave(basePath, baseNewPath, child.Url);
  844. // make sure the directory is there
  845. Directory.CreateDirectory(Path.GetDirectoryName(newLoc));
  846. fn.RenameDocument(child.Url, newLoc);
  847. }
  848. FolderNode folder = child as FolderNode;
  849. if (folder != null) {
  850. folder.VirtualNodeName = GetNewFilePathForDeferredSave(basePath, baseNewPath, child.Url);
  851. }
  852. }
  853. MoveFilesForDeferredSave(child, basePath, baseNewPath);
  854. }
  855. }
  856. }
  857. #endregion
  858. }
  859. }