/// <summary> /// Get's called to rename the eventually running document this hierarchyitem points to /// </summary> /// returns FALSE if the doc can not be renamed internal bool RenameDocument(string oldName, string newName) { IVsRunningDocumentTable pRDT = this.GetService(typeof(IVsRunningDocumentTable)) as IVsRunningDocumentTable; if(pRDT == null) return false; IntPtr docData = IntPtr.Zero; IVsHierarchy pIVsHierarchy; uint itemId; uint uiVsDocCookie; SuspendFileChanges sfc = new SuspendFileChanges(this.ProjectMgr.Site, oldName); sfc.Suspend(); try { // Suspend ms build since during a rename operation no msbuild re-evaluation should be performed until we have finished. // Scenario that could fail if we do not suspend. // We have a project system relying on MPF that triggers a Compile target build (re-evaluates itself) whenever the project changes. (example: a file is added, property changed.) // 1. User renames a file in the above project sytem relying on MPF // 2. Our rename funstionality implemented in this method removes and readds the file and as a post step copies all msbuild entries from the removed file to the added file. // 3. The project system mentioned will trigger an msbuild re-evaluate with the new item, because it was listening to OnItemAdded. // The problem is that the item at the "add" time is only partly added to the project, since the msbuild part has not yet been copied over as mentioned in part 2 of the last step of the rename process. // The result is that the project re-evaluates itself wrongly. VSRENAMEFILEFLAGS renameflag = VSRENAMEFILEFLAGS.VSRENAMEFILEFLAGS_NoFlags; try { this.ProjectMgr.SuspendMSBuild(); ErrorHandler.ThrowOnFailure(pRDT.FindAndLockDocument((uint)_VSRDTFLAGS.RDT_NoLock, oldName, out pIVsHierarchy, out itemId, out docData, out uiVsDocCookie)); if(pIVsHierarchy != null && !Utilities.IsSameComObject(pIVsHierarchy, this.ProjectMgr)) { // Don't rename it if it wasn't opened by us. return false; } // ask other potentially running packages if(!this.ProjectMgr.Tracker.CanRenameItem(oldName, newName, renameflag)) { return false; } // Allow the user to "fix" the project by renaming the item in the hierarchy // to the real name of the file on disk. if(IsFileOnDisk(oldName) || !IsFileOnDisk(newName)) { RenameInStorage(oldName, newName); } string newFileName = Path.GetFileName(newName); DocumentManager.UpdateCaption(this.ProjectMgr.Site, newFileName, docData); bool caseOnlyChange = NativeMethods.IsSamePath(oldName, newName); if(!caseOnlyChange) { // Check out the project file if necessary. if(!this.ProjectMgr.QueryEditProjectFile(false)) { throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED); } this.RenameFileNode(oldName, newName); } else { this.RenameCaseOnlyChange(newFileName); } } finally { this.ProjectMgr.ResumeMSBuild(this.ProjectMgr.ReEvaluateProjectFileTargetName); } this.ProjectMgr.Tracker.OnItemRenamed(oldName, newName, renameflag); } finally { sfc.Resume(); if(docData != IntPtr.Zero) { Marshal.Release(docData); } } return true; }
/// <summary> /// Performs a SaveAs operation of an open document. Called from SaveItem after the running document table has been updated with the new doc data. /// </summary> /// <param name="docData">A pointer to the document in the rdt</param> /// <param name="newFilePath">The new file path to the document</param> /// <returns></returns> protected override int AfterSaveItemAs(IntPtr docData, string newFilePath) { if(String.IsNullOrEmpty(newFilePath)) { throw new ArgumentException(SR.GetString(SR.ParameterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "newFilePath"); } int returnCode = VSConstants.S_OK; newFilePath = newFilePath.Trim(); //Identify if Path or FileName are the same for old and new file string newDirectoryName = Path.GetDirectoryName(newFilePath); Uri newDirectoryUri = new Uri(newDirectoryName); string newCanonicalDirectoryName = newDirectoryUri.LocalPath; newCanonicalDirectoryName = newCanonicalDirectoryName.TrimEnd(Path.DirectorySeparatorChar); string oldCanonicalDirectoryName = new Uri(Path.GetDirectoryName(this.GetMkDocument())).LocalPath; oldCanonicalDirectoryName = oldCanonicalDirectoryName.TrimEnd(Path.DirectorySeparatorChar); string errorMessage = String.Empty; bool isSamePath = NativeMethods.IsSamePath(newCanonicalDirectoryName, oldCanonicalDirectoryName); bool isSameFile = NativeMethods.IsSamePath(newFilePath, this.Url); // Currently we do not support if the new directory is located outside the project cone string projectCannonicalDirecoryName = new Uri(this.ProjectMgr.ProjectFolder).LocalPath; projectCannonicalDirecoryName = projectCannonicalDirecoryName.TrimEnd(Path.DirectorySeparatorChar); if(!isSamePath && newCanonicalDirectoryName.IndexOf(projectCannonicalDirecoryName, StringComparison.OrdinalIgnoreCase) == -1) { errorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.LinkedItemsAreNotSupported, CultureInfo.CurrentUICulture), Path.GetFileNameWithoutExtension(newFilePath)); throw new InvalidOperationException(errorMessage); } //Get target container HierarchyNode targetContainer = null; if(isSamePath) { targetContainer = this.Parent; } else if(NativeMethods.IsSamePath(newCanonicalDirectoryName, projectCannonicalDirecoryName)) { //the projectnode is the target container targetContainer = this.ProjectMgr; } else { //search for the target container among existing child nodes targetContainer = this.ProjectMgr.FindChild(newDirectoryName); if(targetContainer != null && (targetContainer is FileNode)) { // We already have a file node with this name in the hierarchy. errorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.FileAlreadyExistsAndCannotBeRenamed, CultureInfo.CurrentUICulture), Path.GetFileNameWithoutExtension(newFilePath)); throw new InvalidOperationException(errorMessage); } } if(targetContainer == null) { // Add a chain of subdirectories to the project. string relativeUri = PackageUtilities.GetPathDistance(this.ProjectMgr.BaseURI.Uri, newDirectoryUri); Debug.Assert(!String.IsNullOrEmpty(relativeUri) && relativeUri != newDirectoryUri.LocalPath, "Could not make pat distance of " + this.ProjectMgr.BaseURI.Uri.LocalPath + " and " + newDirectoryUri); targetContainer = this.ProjectMgr.CreateFolderNodes(relativeUri); } Debug.Assert(targetContainer != null, "We should have found a target node by now"); //Suspend file changes while we rename the document string oldrelPath = this.ItemNode.GetMetadata(ProjectFileConstants.Include); string oldName = Path.Combine(this.ProjectMgr.ProjectFolder, oldrelPath); SuspendFileChanges sfc = new SuspendFileChanges(this.ProjectMgr.Site, oldName); sfc.Suspend(); try { // Rename the node. DocumentManager.UpdateCaption(this.ProjectMgr.Site, Path.GetFileName(newFilePath), docData); // Check if the file name was actually changed. // In same cases (e.g. if the item is a file and the user has changed its encoding) this function // is called even if there is no real rename. if(!isSameFile || (this.Parent.ID != targetContainer.ID)) { // The path of the file is changed or its parent is changed; in both cases we have // to rename the item. this.RenameFileNode(oldName, newFilePath, targetContainer.ID); OnInvalidateItems(this.Parent); } } catch(Exception e) { Trace.WriteLine("Exception : " + e.Message); this.RecoverFromRenameFailure(newFilePath, oldrelPath); throw; } finally { sfc.Resume(); } return returnCode; }
/// <summary> /// Saves the project file on a new name. /// </summary> /// <param name="newFileName">The new name of the project file.</param> /// <returns>Success value or an error code.</returns> protected virtual int SaveAs(string newFileName) { Debug.Assert(!String.IsNullOrEmpty(newFileName), "Cannot save project file for an empty or null file name"); if (String.IsNullOrEmpty(newFileName)) { throw new ArgumentNullException("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 = SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture); } 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 (Utilities.ContainsInvalidFileNameChars(newFileName)) { errorMessage = SR.GetString(SR.ErrorInvalidFileName, CultureInfo.CurrentUICulture); } else { string url = Path.GetDirectoryName(newFileName); string oldUrl = Path.GetDirectoryName(this.Url); if (!NativeMethods.IsSamePath(oldUrl, url)) { errorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.SaveOfProjectFileOutsideCurrentDirectory, CultureInfo.CurrentUICulture), this.ProjectFolder); } } } } if (errorMessage.Length > 0) { // If it is not called from an automation method show a dialog box. if (!Utilities.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"); if (solution == null) { throw new InvalidOperationException(); } int canRenameContinue = 0; ErrorHandler.ThrowOnFailure(solution.QueryRenameProject(this, 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"); if (shell == null) { throw new InvalidOperationException(); } ErrorHandler.ThrowOnFailure(shell.RefreshPropertyBrowser(0)); } finally { fileChanges.Resume(); } return VSConstants.S_OK; }
/// <summary> /// Renames the project file /// </summary> /// <param name="newFile">The full path of the new project file.</param> protected virtual void RenameProjectFile(string newFile) { IVsUIShell shell = this.Site.GetService(typeof(SVsUIShell)) as IVsUIShell; Debug.Assert(shell != null, "Could not get the ui shell from the project"); if (shell == null) { throw new InvalidOperationException(); } // Do some name validation if (Microsoft.VisualStudio.Project.Utilities.ContainsInvalidFileNameChars(newFile)) { throw new InvalidOperationException(SR.GetString(SR.ErrorInvalidProjectName, CultureInfo.CurrentUICulture)); } // 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, oldFile, newFile, 0, out canContinue)) && canContinue != 0) { bool isFileSame = NativeMethods.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 (!Utilities.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); } }
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 (NativeMethods.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 this.buildProject.Save(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; }
/// <summary> /// Get's called to rename the eventually running document this hierarchyitem points to /// </summary> /// returns FALSE if the doc can not be renamed internal bool RenameDocument(string oldName, string newName) { IVsRunningDocumentTable pRDT = this.GetService(typeof(IVsRunningDocumentTable)) as IVsRunningDocumentTable; if (pRDT == null) { return(false); } IntPtr docData = IntPtr.Zero; IVsHierarchy pIVsHierarchy; uint itemId; uint uiVsDocCookie; SuspendFileChanges sfc = new SuspendFileChanges(this.ProjectMgr.Site, oldName); sfc.Suspend(); try { // Suspend ms build since during a rename operation no msbuild re-evaluation should be performed until we have finished. // Scenario that could fail if we do not suspend. // We have a project system relying on MPF that triggers a Compile target build (re-evaluates itself) whenever the project changes. (example: a file is added, property changed.) // 1. User renames a file in the above project sytem relying on MPF // 2. Our rename funstionality implemented in this method removes and readds the file and as a post step copies all msbuild entries from the removed file to the added file. // 3. The project system mentioned will trigger an msbuild re-evaluate with the new item, because it was listening to OnItemAdded. // The problem is that the item at the "add" time is only partly added to the project, since the msbuild part has not yet been copied over as mentioned in part 2 of the last step of the rename process. // The result is that the project re-evaluates itself wrongly. VSRENAMEFILEFLAGS renameflag = VSRENAMEFILEFLAGS.VSRENAMEFILEFLAGS_NoFlags; try { this.ProjectMgr.SuspendMSBuild(); ErrorHandler.ThrowOnFailure(pRDT.FindAndLockDocument((uint)_VSRDTFLAGS.RDT_NoLock, oldName, out pIVsHierarchy, out itemId, out docData, out uiVsDocCookie)); if (pIVsHierarchy != null && !Utilities.IsSameComObject(pIVsHierarchy, this.ProjectMgr)) { // Don't rename it if it wasn't opened by us. return(false); } // ask other potentially running packages if (!this.ProjectMgr.Tracker.CanRenameItem(oldName, newName, renameflag)) { return(false); } // Allow the user to "fix" the project by renaming the item in the hierarchy // to the real name of the file on disk. if (IsFileOnDisk(oldName) || !IsFileOnDisk(newName)) { RenameInStorage(oldName, newName); } string newFileName = Path.GetFileName(newName); DocumentManager.UpdateCaption(this.ProjectMgr.Site, newFileName, docData); bool caseOnlyChange = NativeMethods.IsSamePath(oldName, newName); if (!caseOnlyChange) { // Check out the project file if necessary. if (!this.ProjectMgr.QueryEditProjectFile(false)) { throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED); } this.RenameFileNode(oldName, newName); } else { this.RenameCaseOnlyChange(newFileName); } } finally { this.ProjectMgr.ResumeMSBuild(this.ProjectMgr.ReEvaluateProjectFileTargetName); } this.ProjectMgr.Tracker.OnItemRenamed(oldName, newName, renameflag); } finally { sfc.Resume(); if (docData != IntPtr.Zero) { Marshal.Release(docData); } } return(true); }
/// <summary> /// Performs a SaveAs operation of an open document. Called from SaveItem after the running document table has been updated with the new doc data. /// </summary> /// <param name="docData">A pointer to the document in the rdt</param> /// <param name="newFilePath">The new file path to the document</param> /// <returns></returns> protected override int AfterSaveItemAs(IntPtr docData, string newFilePath) { if (String.IsNullOrEmpty(newFilePath)) { throw new ArgumentException(SR.GetString(SR.ParameterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "newFilePath"); } int returnCode = VSConstants.S_OK; newFilePath = newFilePath.Trim(); //Identify if Path or FileName are the same for old and new file string newDirectoryName = Path.GetDirectoryName(newFilePath); Uri newDirectoryUri = new Uri(newDirectoryName); string newCanonicalDirectoryName = newDirectoryUri.LocalPath; newCanonicalDirectoryName = newCanonicalDirectoryName.TrimEnd(Path.DirectorySeparatorChar); string oldCanonicalDirectoryName = new Uri(Path.GetDirectoryName(this.GetMkDocument())).LocalPath; oldCanonicalDirectoryName = oldCanonicalDirectoryName.TrimEnd(Path.DirectorySeparatorChar); string errorMessage = String.Empty; bool isSamePath = NativeMethods.IsSamePath(newCanonicalDirectoryName, oldCanonicalDirectoryName); bool isSameFile = NativeMethods.IsSamePath(newFilePath, this.Url); // Currently we do not support if the new directory is located outside the project cone string projectCannonicalDirecoryName = new Uri(this.ProjectMgr.ProjectFolder).LocalPath; projectCannonicalDirecoryName = projectCannonicalDirecoryName.TrimEnd(Path.DirectorySeparatorChar); if (!isSamePath && newCanonicalDirectoryName.IndexOf(projectCannonicalDirecoryName, StringComparison.OrdinalIgnoreCase) == -1) { errorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.LinkedItemsAreNotSupported, CultureInfo.CurrentUICulture), Path.GetFileNameWithoutExtension(newFilePath)); throw new InvalidOperationException(errorMessage); } //Get target container HierarchyNode targetContainer = null; if (isSamePath) { targetContainer = this.Parent; } else if (NativeMethods.IsSamePath(newCanonicalDirectoryName, projectCannonicalDirecoryName)) { //the projectnode is the target container targetContainer = this.ProjectMgr; } else { //search for the target container among existing child nodes targetContainer = this.ProjectMgr.FindChild(newDirectoryName); if (targetContainer != null && (targetContainer is FileNode)) { // We already have a file node with this name in the hierarchy. errorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.FileAlreadyExistsAndCannotBeRenamed, CultureInfo.CurrentUICulture), Path.GetFileNameWithoutExtension(newFilePath)); throw new InvalidOperationException(errorMessage); } } if (targetContainer == null) { // Add a chain of subdirectories to the project. string relativeUri = PackageUtilities.GetPathDistance(this.ProjectMgr.BaseURI.Uri, newDirectoryUri); Debug.Assert(!String.IsNullOrEmpty(relativeUri) && relativeUri != newDirectoryUri.LocalPath, "Could not make pat distance of " + this.ProjectMgr.BaseURI.Uri.LocalPath + " and " + newDirectoryUri); targetContainer = this.ProjectMgr.CreateFolderNodes(relativeUri); } Debug.Assert(targetContainer != null, "We should have found a target node by now"); //Suspend file changes while we rename the document string oldrelPath = this.ItemNode.GetMetadata(ProjectFileConstants.Include); string oldName = Path.Combine(this.ProjectMgr.ProjectFolder, oldrelPath); SuspendFileChanges sfc = new SuspendFileChanges(this.ProjectMgr.Site, oldName); sfc.Suspend(); try { // Rename the node. DocumentManager.UpdateCaption(this.ProjectMgr.Site, Path.GetFileName(newFilePath), docData); // Check if the file name was actually changed. // In same cases (e.g. if the item is a file and the user has changed its encoding) this function // is called even if there is no real rename. if (!isSameFile || (this.Parent.ID != targetContainer.ID)) { // The path of the file is changed or its parent is changed; in both cases we have // to rename the item. this.RenameFileNode(oldName, newFilePath, targetContainer.ID); OnInvalidateItems(this.Parent); } } catch (Exception e) { Trace.WriteLine("Exception : " + e.Message); this.RecoverFromRenameFailure(newFilePath, oldrelPath); throw; } finally { sfc.Resume(); } return(returnCode); }