Example #1
0
        /// <summary>
        /// Renames the file in the hierarchy by removing old node and adding a new node in the hierarchy.
        /// </summary>
        /// <param name="oldFileName">The old file name.</param>
        /// <param name="newFileName">The new file name</param>
        /// <param name="newParentId">The new parent id of the item.</param>
        /// <returns>The newly added FileNode.</returns>
        /// <remarks>While a new node will be used to represent the item, the underlying MSBuild item will be the same and as a result file properties saved in the project file will not be lost.</remarks>
        public virtual FileNode RenameFileNode(string oldFileName, string newFileName, uint newParentId)
        {
            if (string.Compare(oldFileName, newFileName, StringComparison.Ordinal) == 0)
            {
                // We do not want to rename the same file
                return(null);
            }

            string[] file = new string[1];
            file[0] = newFileName;
            VSADDRESULT[] result    = new VSADDRESULT[1];
            Guid          emptyGuid = Guid.Empty;

            FileNode childAdded      = null;
            string   originalInclude = this.ItemNode.Item.UnevaluatedInclude;

            return(Transactional.Try(
                       // Action
                       () =>
            {
                // It's unfortunate that MPF implements rename in terms of AddItemWithSpecific. Since this
                // is the case, we have to pass false to prevent AddITemWithSpecific to fire Add events on
                // the IVsTrackProjectDocuments2 tracker. Otherwise, clients listening to this event
                // (SCCI, for example) will be really confused.
                using (this.ProjectMgr.ExtensibilityEventsHelper.SuspendEvents())
                {
                    var currentId = this.ID;

                    // actual deletion is delayed until all checks are passed
                    Func <uint> getIdOfExistingItem =
                        () =>
                    {
                        this.OnItemDeleted();
                        this.Parent.RemoveChild(this);
                        return currentId;
                    };

                    // raises OnItemAdded inside
                    ErrorHandler.ThrowOnFailure(this.ProjectMgr.AddItemWithSpecific(newParentId, VSADDITEMOPERATION.VSADDITEMOP_OPENFILE, null, 0, file, IntPtr.Zero, 0, ref emptyGuid, null, ref emptyGuid, result, false, getIdOfExistingItem));
                    childAdded = this.ProjectMgr.FindChild(newFileName) as FileNode;
                    Debug.Assert(childAdded != null, "Could not find the renamed item in the hierarchy");
                }
            },
                       // Compensation
                       () =>
            {
                // it failed, but 'this' is dead and 'childAdded' is here to stay, so fix the latter back
                using (this.ProjectMgr.ExtensibilityEventsHelper.SuspendEvents())
                {
                    childAdded.ItemNode.Rename(originalInclude);
                    childAdded.ItemNode.RefreshProperties();
                }
            },

                       // Continuation
                       () =>
            {
                // Since this node has been removed all of its state is zombied at this point
                // Do not call virtual methods after this point since the object is in a deleted state.

                // Remove the item created by the add item. We need to do this otherwise we will have two items.
                // Please be aware that we have not removed the ItemNode associated to the removed file node from the hierrachy.
                // What we want to achieve here is to reuse the existing build item.
                // We want to link to the newly created node to the existing item node and addd the new include.

                //temporarily keep properties from new itemnode since we are going to overwrite it
                string newInclude = childAdded.ItemNode.Item.UnevaluatedInclude;
                string dependentOf = childAdded.ItemNode.GetMetadata(ProjectFileConstants.DependentUpon);
                childAdded.ItemNode.RemoveFromProjectFile();

                // Assign existing msbuild item to the new childnode
                childAdded.ItemNode = this.ItemNode;
                childAdded.ItemNode.Item.ItemType = this.ItemNode.ItemName;
                childAdded.ItemNode.Item.Xml.Include = newInclude;
                if (!string.IsNullOrEmpty(dependentOf))
                {
                    childAdded.ItemNode.SetMetadata(ProjectFileConstants.DependentUpon, dependentOf);
                }
                childAdded.ItemNode.RefreshProperties();

                // Extensibilty events has rename
                this.ProjectMgr.ExtensibilityEventsHelper.FireItemRenamed(childAdded, Path.GetFileName(originalInclude));

                //Update the new document in the RDT.
                try
                {
                    DocumentManager.RenameDocument(this.ProjectMgr.Site, oldFileName, newFileName, childAdded.ID);

                    // The current automation node is renamed, but the hierarchy ID is now pointing to the old item, which
                    // is invalid. Update it.
                    this.ID = childAdded.ID;

                    //Update FirstChild
                    childAdded.FirstChild = this.FirstChild;

                    //Update ChildNodes
                    SetNewParentOnChildNodes(childAdded);
                    RenameChildNodes(childAdded);

                    return childAdded;
                }
                finally
                {
                    //Select the new node in the hierarchy
                    IVsUIHierarchyWindow uiWindow = UIHierarchyUtilities.GetUIHierarchyWindow(this.ProjectMgr.Site, SolutionExplorer);
                    uiWindow.ExpandItem(this.ProjectMgr.InteropSafeIVsUIHierarchy, childAdded.ID, EXPANDFLAGS.EXPF_SelectItem);
                }
            }));
        }
Example #2
0
        /// <summary>
        /// Get's called to rename the eventually running document this hierarchyitem points to
        /// </summary>
        /// returns FALSE if the doc can not be renamed
        public 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
            {
                VSRENAMEFILEFLAGS renameflag = VSRENAMEFILEFLAGS.VSRENAMEFILEFLAGS_NoFlags;
                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.
                bool shouldRenameInStorage = IsFileOnDisk(oldName) || !IsFileOnDisk(newName);
                Transactional.Try(
                    // Action
                    () => { if (shouldRenameInStorage)
                            {
                                RenameInStorage(oldName, newName);
                            }
                    },
                    // Compensation
                    () => { if (shouldRenameInStorage)
                            {
                                RenameInStorage(newName, oldName);
                            }
                    },
                    // Continuation
                    () =>
                {
                    string newFileName = Path.GetFileName(newName);
                    string oldCaption  = this.Caption;
                    Transactional.Try(
                        // Action
                        () => DocumentManager.UpdateCaption(this.ProjectMgr.Site, newFileName, docData),
                        // Compensation
                        () => DocumentManager.UpdateCaption(this.ProjectMgr.Site, oldCaption, docData),
                        // Continuation
                        () =>
                    {
                        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);
                        }
                        bool extensionWasChanged = (0 != String.Compare(Path.GetExtension(oldName), Path.GetExtension(newName), StringComparison.OrdinalIgnoreCase));
                        if (extensionWasChanged)
                        {
                            // Update the BuildAction
                            this.ItemNode.ItemName = this.ProjectMgr.DefaultBuildAction(newName);
                        }
                        this.ProjectMgr.Tracker.OnItemRenamed(oldName, newName, renameflag);
                    });
                });
            }
            finally
            {
                if (docData != IntPtr.Zero)
                {
                    Marshal.Release(docData);
                }
                sfc.Resume(); // can throw, e.g. when RenameFileNode failed, but file was renamed on disk and now editor cannot find file
            }

            return(true);
        }
Example #3
0
        /// <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>
        public 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.
                var oldParent = this.Parent;
                if (!isSameFile || (oldParent.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(oldParent);
                    // This is what othe project systems do; for the purposes of source control, the old file is removed, and a new file is added
                    // (althought the old file stays on disk!)
                    this.ProjectMgr.Tracker.OnItemRemoved(oldName, VSREMOVEFILEFLAGS.VSREMOVEFILEFLAGS_NoFlags);
                    this.ProjectMgr.Tracker.OnItemAdded(newFilePath, VSADDFILEFLAGS.VSADDFILEFLAGS_NoFlags);
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine("Exception : " + e.Message);
                this.RecoverFromRenameFailure(newFilePath, oldrelPath);
                throw;
            }
            finally
            {
                sfc.Resume();
            }

            return(returnCode);
        }