RunningDocumentTableEventTracker.cs 10.5 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
4

5 6
#nullable enable

7 8
using System;
using System.Collections.Generic;
9
using System.Diagnostics.CodeAnalysis;
10
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
11
using Microsoft.CodeAnalysis.PooledObjects;
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.TextManager.Interop;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
    /// <summary>
    /// Class to register with the RDT and forward RDT events.
    /// Handles common conditions and delegates implementation to the <see cref="IRunningDocumentTableEventListener"/>
    /// </summary>
    internal sealed class RunningDocumentTableEventTracker : IVsRunningDocTableEvents3, IDisposable
    {
        private bool _isDisposed = false; // To detect redundant calls

        private readonly ForegroundThreadAffinitizedObject _foregroundAffinitization;
        private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService;
        private readonly IVsRunningDocumentTable4 _runningDocumentTable;
D
David Barbet 已提交
31
        private readonly IRunningDocumentTableEventListener _listener;
32 33 34
        private uint _runningDocumentTableEventsCookie;

        public RunningDocumentTableEventTracker(IThreadingContext threadingContext, IVsEditorAdaptersFactoryService editorAdaptersFactoryService, IVsRunningDocumentTable runningDocumentTable,
D
David Barbet 已提交
35
            IRunningDocumentTableEventListener listener)
36 37 38 39
        {
            Contract.ThrowIfNull(threadingContext);
            Contract.ThrowIfNull(editorAdaptersFactoryService);
            Contract.ThrowIfNull(runningDocumentTable);
D
David Barbet 已提交
40
            Contract.ThrowIfNull(listener);
41 42 43 44 45 46

            _foregroundAffinitization = new ForegroundThreadAffinitizedObject(threadingContext, assertIsForeground: false);
            _runningDocumentTable = (IVsRunningDocumentTable4)runningDocumentTable;
            _editorAdaptersFactoryService = editorAdaptersFactoryService;
            _listener = listener;

D
David Barbet 已提交
47 48
            // Advise / Unadvise for the RDT is free threaded past 16.0
            ((IVsRunningDocumentTable)_runningDocumentTable).AdviseRunningDocTableEvents(this, out _runningDocumentTableEventsCookie);
49 50 51
        }

        public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
52
            => VSConstants.E_NOTIMPL;
53 54 55 56 57 58 59 60

        public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
        {
            if (dwReadLocksRemaining + dwEditLocksRemaining == 0)
            {
                _foregroundAffinitization.AssertIsForeground();
                if (_runningDocumentTable.IsDocumentInitialized(docCookie))
                {
D
David Barbet 已提交
61
                    _listener.OnCloseDocument(_runningDocumentTable.GetDocumentMoniker(docCookie));
62 63 64 65 66 67 68
                }
            }

            return VSConstants.S_OK;
        }

        public int OnAfterSave(uint docCookie)
69
            => VSConstants.E_NOTIMPL;
70 71

        public int OnAfterAttributeChange(uint docCookie, uint grfAttribs)
72
            => VSConstants.E_NOTIMPL;
73 74 75 76 77 78 79 80 81

        public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew)
        {
            // Did we rename?
            if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_MkDocument) != 0)
            {
                _foregroundAffinitization.AssertIsForeground();
                if (_runningDocumentTable.IsDocumentInitialized(docCookie) && TryGetBuffer(docCookie, out var buffer))
                {
D
David Barbet 已提交
82
                    _listener.OnRenameDocument(newMoniker: pszMkDocumentNew, oldMoniker: pszMkDocumentOld, textBuffer: buffer);
83 84 85
                }
            }

86 87 88 89 90
            // Either RDTA_DocDataReloaded or RDTA_DocumentInitialized will be triggered if there's a lazy load and the document is now available.
            // See https://devdiv.visualstudio.com/DevDiv/_workitems/edit/937712 for a scenario where we do need the RDTA_DocumentInitialized check.
            // We still check for RDTA_DocDataReloaded because the RDT will mark something as initialized as soon as there is something in the doc data,
            // but that might still not be associated with an ITextBuffer.
            if ((grfAttribs & ((uint)__VSRDTATTRIB.RDTA_DocDataReloaded | (uint)__VSRDTATTRIB3.RDTA_DocumentInitialized)) != 0)
91 92 93 94 95
            {
                _foregroundAffinitization.AssertIsForeground();
                if (_runningDocumentTable.IsDocumentInitialized(docCookie) && TryGetMoniker(docCookie, out var moniker) && TryGetBuffer(docCookie, out var buffer))
                {
                    _runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out _);
D
David Barbet 已提交
96
                    _listener.OnOpenDocument(moniker, buffer, hierarchy);
97 98 99 100 101 102 103 104 105
                }
            }

            if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_Hierarchy) != 0)
            {
                _foregroundAffinitization.AssertIsForeground();
                if (_runningDocumentTable.IsDocumentInitialized(docCookie) && TryGetMoniker(docCookie, out var moniker))
                {
                    _runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out _);
D
David Barbet 已提交
106
                    _listener.OnRefreshDocumentContext(moniker, hierarchy);
107 108 109 110 111 112 113 114
                }
            }

            return VSConstants.S_OK;
        }

        public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
        {
115 116 117 118
            // Doc data reloaded is not triggered for the underlying aspx.cs file when changes are made to the aspx file, so catch it here.
            if (fFirstShow != 0 && _runningDocumentTable.IsDocumentInitialized(docCookie) && TryGetMoniker(docCookie, out var moniker) && TryGetBuffer(docCookie, out var buffer))
            {
                _runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out _);
D
David Barbet 已提交
119
                _listener.OnOpenDocument(moniker, buffer, hierarchy);
120 121 122
            }

            return VSConstants.S_OK;
123 124 125
        }

        public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame)
126
            => VSConstants.E_NOTIMPL;
127 128

        public int OnBeforeSave(uint docCookie)
129
            => VSConstants.E_NOTIMPL;
130

131 132 133 134 135
        public bool IsFileOpen(string fileName)
        {
            _foregroundAffinitization.AssertIsForeground();
            return _runningDocumentTable.IsFileOpen(fileName);
        }
136 137 138 139 140 141 142

        /// <summary>
        /// Attempts to get a text buffer from the specified moniker.
        /// </summary>
        /// <param name="moniker">the moniker to retrieve the text buffer for.</param>
        /// <param name="textBuffer">the output text buffer or null if the moniker is invalid / document is not initialized.</param>
        /// <returns>true if the buffer was found with a non null value.</returns>
143
        public bool TryGetBufferFromMoniker(string moniker, [NotNullWhen(true)] out ITextBuffer? textBuffer)
144 145 146
        {
            _foregroundAffinitization.AssertIsForeground();

D
David Barbet 已提交
147
            return _runningDocumentTable.TryGetBufferFromMoniker(_editorAdaptersFactoryService, moniker, out textBuffer);
148 149
        }

150
        public IVsHierarchy? GetDocumentHierarchy(string moniker)
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
        {
            if (!IsFileOpen(moniker))
            {
                return null;
            }

            var cookie = _runningDocumentTable.GetDocumentCookie(moniker);
            _runningDocumentTable.GetDocumentHierarchyItem(cookie, out var hierarchy, out _);
            return hierarchy;
        }

        /// <summary>
        /// Enumerates the running document table to retrieve all initialized files.
        /// </summary>
        public IEnumerable<(string moniker, ITextBuffer textBuffer, IVsHierarchy hierarchy)> EnumerateDocumentSet()
        {
            _foregroundAffinitization.AssertIsForeground();

            var documents = ArrayBuilder<(string, ITextBuffer, IVsHierarchy)>.GetInstance();
            foreach (var cookie in GetInitializedRunningDocumentTableCookies())
            {
                if (TryGetMoniker(cookie, out var moniker) && TryGetBuffer(cookie, out var buffer))
                {
                    _runningDocumentTable.GetDocumentHierarchyItem(cookie, out var hierarchy, out _);
                    documents.Add((moniker, buffer, hierarchy));
                }
            }

            return documents.ToArray();
        }

        private IEnumerable<uint> GetInitializedRunningDocumentTableCookies()
        {
            // Some methods we need here only exist in IVsRunningDocumentTable and not the IVsRunningDocumentTable4 that we
            // hold onto as a field
            var runningDocumentTable = (IVsRunningDocumentTable)_runningDocumentTable;
            ErrorHandler.ThrowOnFailure(runningDocumentTable.GetRunningDocumentsEnum(out var enumRunningDocuments));
C
Use var  
Cyrus Najmabadi 已提交
188
            var cookies = new uint[16];
189 190 191 192

            while (ErrorHandler.Succeeded(enumRunningDocuments.Next((uint)cookies.Length, cookies, out var cookiesFetched))
                   && cookiesFetched > 0)
            {
C
Use var  
Cyrus Najmabadi 已提交
193
                for (var cookieIndex = 0; cookieIndex < cookiesFetched; cookieIndex++)
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
                {
                    var cookie = cookies[cookieIndex];

                    if (_runningDocumentTable.IsDocumentInitialized(cookie))
                    {
                        yield return cookie;
                    }
                }
            }
        }

        private bool TryGetMoniker(uint docCookie, out string moniker)
        {
            moniker = _runningDocumentTable.GetDocumentMoniker(docCookie);
            return !string.IsNullOrEmpty(moniker);
        }

211
        private bool TryGetBuffer(uint docCookie, [NotNullWhen(true)] out ITextBuffer? textBuffer)
D
David Barbet 已提交
212
            => _runningDocumentTable.TryGetBuffer(_editorAdaptersFactoryService, docCookie, out textBuffer);
213

214
        public void Dispose()
215
        {
216
            if (_isDisposed)
217
            {
218
                return;
219 220
            }

221 222 223 224 225
            var runningDocumentTableForEvents = (IVsRunningDocumentTable)_runningDocumentTable;
            runningDocumentTableForEvents.UnadviseRunningDocTableEvents(_runningDocumentTableEventsCookie);
            _runningDocumentTableEventsCookie = 0;

            _isDisposed = true;
226 227 228
        }
    }
}