// fired when a document is about to be shown on screen for various
        // reasons
        //
        public override int OnBeforeDocumentWindowShow(
            uint itemCookie, int first, IVsWindowFrame wf)
        {
            ThreadHelper.ThrowIfNotOnUIThread();

            // this may be called any time after the solution is loaded since
            // window frames are lazily created when a tab is clicked for the
            // first time

            if (first == 0)
            {
                // only handle the first time a document is shown
                return(VSConstants.S_OK);
            }

            Trace(
                "OnBeforeDocumentWindowShow: {0}",
                VSDocument.DebugWindowFrameName(wf));

            var d = VSDocument.DocumentFromWindowFrame(wf);

            if (d == null)
            {
                Error("OnBeforeDocumentWindowShow: frame has no document");
                return(VSConstants.S_OK);
            }

            DocumentOpened?.Invoke(new VSDocument(d, wf));

            return(VSConstants.S_OK);
        }
        // fired when document attributes change, such as renaming, but also
        // dirty state, etc.
        //
        public override int OnAfterAttributeChangeEx(
            uint cookie, uint atts,
            IVsHierarchy oldHier, uint oldId, string oldPath,
            IVsHierarchy newHier, uint newId, string newPath)
        {
            ThreadHelper.ThrowIfNotOnUIThread();

            const uint RenameBit = (uint)__VSRDTATTRIB.RDTA_MkDocument;

            if ((atts & RenameBit) == 0)
            {
                // don't bother with anything else than rename
                return(VSConstants.S_OK);
            }

            // this is fired for:
            //
            // 1) renaming a C# project
            // ignored because it also fires HierarchyEvents.OnPropertyChanged
            //
            // 2) renaming a file for both C++ and C# projects
            // this is handled here
            //
            // 3) moving a C# file
            // this is handled here and ignored in HierarchyEvents.OnItemAdded

            Trace(
                "OnAfterAttributeChangeEx rename: cookie={0} atts={1} " +
                "oldId={2} oldPath={3} newId={4} newPath={5}",
                cookie, atts, oldId, oldPath, newId, newPath);

            var d = VSDocument.DocumentFromCookie(cookie);

            if (d == null)
            {
                // this happens when renaming a C# project, which is handled
                // elsewhere, so that's fine
                return(VSConstants.S_OK);
            }

            DocumentRenamed?.Invoke(new VSDocument(d));

            return(VSConstants.S_OK);
        }
        // fired when an item is moved or added
        //
        public override int OnItemAdded(
            uint parent, uint prevSibling, uint item)
        {
            ThreadHelper.ThrowIfNotOnUIThread();

            Trace(
                "OnItemAdded: parent={0} prevSibling={1} item={2}",
                parent, prevSibling, item);

            // it's generally impossible to differentiate between moves and
            // renames for either files or folders, so they're merged into one
            // rename event

            // OnItemAdded for folders and files is fired for C# projects on
            // move and rename, but only on move for C++ projects for move
            //
            // renaming a file or a folder for C++ projects fires
            // OnPropertyChanged, which is handled below

            if (VSTreeItem.GetIsFolder(Hierarchy, item))
            {
                FolderRenamed?.Invoke(new VSTreeItem(Hierarchy, item));
            }
            else
            {
                var d = VSDocument.DocumentFromItemID(Hierarchy, item);
                if (d == null)
                {
                    // this happens when renaming C# files for whatever reason,
                    // but it's fine because it's already handled in
                    // OnAfterAttributeChangeEx
                    return(VSConstants.S_OK);
                }

                DocumentRenamed?.Invoke(new VSDocument(d));
            }

            return(VSConstants.S_OK);
        }