77using System . ComponentModel . Composition ;
88using System . Linq ;
99using System . Runtime . InteropServices ;
10+ using System . Threading ;
11+ using System . Threading . Tasks ;
1012using Microsoft . CodeAnalysis ;
1113using Microsoft . CodeAnalysis . Editor ;
1214using Microsoft . CodeAnalysis . Editor . Shared . Utilities ;
@@ -31,7 +33,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation
3133 [ Export ]
3234 internal class VisualStudioActiveDocumentTracker : ForegroundThreadAffinitizedObject , IVsSelectionEvents
3335 {
36+ private readonly IAsyncServiceProvider _asyncServiceProvider ;
3437 private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService ;
38+ private IVsRunningDocumentTable4 ? _runningDocumentTable ;
3539
3640 /// <summary>
3741 /// The list of tracked frames. This can only be written by the UI thread, although can be read (with care) from any thread.
@@ -51,6 +55,7 @@ public VisualStudioActiveDocumentTracker(
5155 IVsEditorAdaptersFactoryService editorAdaptersFactoryService )
5256 : base ( threadingContext , assertIsForeground : false )
5357 {
58+ _asyncServiceProvider = asyncServiceProvider ;
5459 _editorAdaptersFactoryService = editorAdaptersFactoryService ;
5560 ThreadingContext . RunWithShutdownBlockAsync ( async cancellationToken =>
5661 {
@@ -59,14 +64,16 @@ public VisualStudioActiveDocumentTracker(
5964 var monitorSelectionService = ( IVsMonitorSelection ? ) await asyncServiceProvider . GetServiceAsync ( typeof ( SVsShellMonitorSelection ) ) . ConfigureAwait ( true ) ;
6065 Assumes . Present ( monitorSelectionService ) ;
6166
67+ var runningDocumentTable = await GetRunningDocumentTableAsync ( cancellationToken ) . ConfigureAwait ( true ) ;
68+
6269 // No need to track windows if we are shutting down
6370 cancellationToken . ThrowIfCancellationRequested ( ) ;
6471
6572 if ( ErrorHandler . Succeeded ( monitorSelectionService . GetCurrentElementValue ( ( uint ) VSConstants . VSSELELEMID . SEID_DocumentFrame , out var value ) ) )
6673 {
6774 if ( value is IVsWindowFrame windowFrame )
6875 {
69- TrackNewActiveWindowFrame ( windowFrame ) ;
76+ TrackNewActiveWindowFrame ( windowFrame , runningDocumentTable ) ;
7077 }
7178 }
7279
@@ -139,7 +146,7 @@ public ImmutableArray<DocumentId> GetVisibleDocuments(Workspace workspace)
139146 return ids . ToImmutableAndFree ( ) ;
140147 }
141148
142- public void TrackNewActiveWindowFrame ( IVsWindowFrame frame )
149+ public void TrackNewActiveWindowFrame ( IVsWindowFrame frame , IVsRunningDocumentTable4 runningDocumentTable )
143150 {
144151 AssertIsForeground ( ) ;
145152
@@ -150,20 +157,33 @@ public void TrackNewActiveWindowFrame(IVsWindowFrame frame)
150157 var existingFrame = _visibleFrames . FirstOrDefault ( f => f . Frame == frame ) ;
151158 if ( existingFrame == null )
152159 {
153- _visibleFrames = _visibleFrames . Add ( new FrameListener ( this , frame ) ) ;
160+ _visibleFrames = _visibleFrames . Add ( new FrameListener ( this , frame , runningDocumentTable ) ) ;
154161 }
155162 else if ( existingFrame . TextBuffer == null )
156163 {
157164 // If no text buffer is associated with existing frame, remove the existing frame and add the new one.
158165 // Note that we do not need to disconnect the existing frame here. It will get disconnected along with
159166 // the new frame whenever the document is closed or de-activated.
160167 _visibleFrames = _visibleFrames . Remove ( existingFrame ) ;
161- _visibleFrames = _visibleFrames . Add ( new FrameListener ( this , frame ) ) ;
168+ _visibleFrames = _visibleFrames . Add ( new FrameListener ( this , frame , runningDocumentTable ) ) ;
162169 }
163170
164171 this . DocumentsChanged ? . Invoke ( this , EventArgs . Empty ) ;
165172 }
166173
174+ private async ValueTask < IVsRunningDocumentTable4 > GetRunningDocumentTableAsync ( CancellationToken cancellationToken )
175+ {
176+ await ThreadingContext . JoinableTaskFactory . SwitchToMainThreadAsync ( cancellationToken ) ;
177+
178+ if ( _runningDocumentTable is null )
179+ {
180+ _runningDocumentTable = ( IVsRunningDocumentTable4 ? ) await _asyncServiceProvider . GetServiceAsync ( typeof ( SVsRunningDocumentTable ) ) . ConfigureAwait ( true ) ;
181+ Assumes . Present ( _runningDocumentTable ) ;
182+ }
183+
184+ return _runningDocumentTable ;
185+ }
186+
167187 private void RemoveFrame ( FrameListener frame )
168188 {
169189 AssertIsForeground ( ) ;
@@ -198,7 +218,8 @@ int IVsSelectionEvents.OnElementValueChanged([ComAliasName("Microsoft.VisualStud
198218 ErrorHandler . Succeeded ( frame . GetProperty ( ( int ) __VSFPROPID . VSFPROPID_Type , out var frameType ) ) &&
199219 ( int ) frameType == ( int ) __WindowFrameTypeFlags . WINDOWFRAMETYPE_Document )
200220 {
201- TrackNewActiveWindowFrame ( frame ) ;
221+ var runningDocumentTable = ThreadingContext . JoinableTaskFactory . Run ( ( ) => GetRunningDocumentTableAsync ( ThreadingContext . DisposalToken ) . AsTask ( ) ) ;
222+ TrackNewActiveWindowFrame ( frame , runningDocumentTable ) ;
202223 }
203224 }
204225
@@ -217,31 +238,22 @@ private class FrameListener : IVsWindowFrameNotify, IVsWindowFrameNotify2
217238 public readonly IVsWindowFrame Frame ;
218239
219240 private readonly VisualStudioActiveDocumentTracker _documentTracker ;
241+ private readonly IVsRunningDocumentTable4 _runningDocumentTable ;
220242 private readonly uint _frameEventsCookie ;
221243
222- internal ITextBuffer ? TextBuffer { get ; }
244+ internal ITextBuffer ? TextBuffer { get ; private set ; }
223245
224- public FrameListener ( VisualStudioActiveDocumentTracker service , IVsWindowFrame frame )
246+ public FrameListener ( VisualStudioActiveDocumentTracker service , IVsWindowFrame frame , IVsRunningDocumentTable4 runningDocumentTable )
225247 {
226248 _documentTracker = service ;
249+ _runningDocumentTable = runningDocumentTable ;
250+
227251 _documentTracker . AssertIsForeground ( ) ;
228252
229253 this . Frame = frame ;
230-
231254 ( ( IVsWindowFrame2 ) frame ) . Advise ( this , out _frameEventsCookie ) ;
232255
233- if ( ErrorHandler . Succeeded ( frame . GetProperty ( ( int ) __VSFPROPID . VSFPROPID_DocData , out var docData ) ) )
234- {
235- if ( docData is IVsTextBuffer bufferAdapter )
236- {
237- TextBuffer = _documentTracker . _editorAdaptersFactoryService . GetDocumentBuffer ( bufferAdapter ) ;
238-
239- if ( TextBuffer != null && ! TextBuffer . ContentType . IsOfType ( ContentTypeNames . RoslynContentType ) )
240- {
241- TextBuffer . Changed += NonRoslynTextBuffer_Changed ;
242- }
243- }
244- }
256+ TryInitializeTextBuffer ( ) ;
245257 }
246258
247259 private void NonRoslynTextBuffer_Changed ( object sender , TextContentChangedEventArgs e )
@@ -272,6 +284,17 @@ int IVsWindowFrameNotify.OnShow(int fShow)
272284 {
273285 switch ( ( __FRAMESHOW ) fShow )
274286 {
287+ case __FRAMESHOW . FRAMESHOW_WinShown when TextBuffer is null :
288+ TryInitializeTextBuffer ( ) ;
289+ if ( TextBuffer is not null )
290+ {
291+ // The current TextBuffer was initialized in the OnShow instead of being initialized in the
292+ // constructor. For consumers, treat this the same way as when the active document changes.
293+ _documentTracker . DocumentsChanged ? . Invoke ( _documentTracker , EventArgs . Empty ) ;
294+ }
295+
296+ return VSConstants . S_OK ;
297+
275298 case __FRAMESHOW . FRAMESHOW_WinClosed :
276299 case __FRAMESHOW . FRAMESHOW_WinHidden :
277300 case __FRAMESHOW . FRAMESHOW_TabDeactivated :
@@ -287,6 +310,38 @@ int IVsWindowFrameNotify.OnSize()
287310 int IVsWindowFrameNotify2 . OnClose ( ref uint pgrfSaveOptions )
288311 => Disconnect ( ) ;
289312
313+ private void TryInitializeTextBuffer ( )
314+ {
315+ RoslynDebug . Assert ( TextBuffer is null ) ;
316+
317+ _documentTracker . AssertIsForeground ( ) ;
318+
319+ if ( ErrorHandler . Succeeded ( Frame . GetProperty ( ( int ) __VSFPROPID . VSFPROPID_DocCookie , out var boxedDocCookie ) ) && boxedDocCookie is uint docCookie )
320+ {
321+ var flags = ( _VSRDTFLAGS ) _runningDocumentTable . GetDocumentFlags ( docCookie ) ;
322+ if ( ( flags & ( _VSRDTFLAGS ) _VSRDTFLAGS4 . RDT_PendingInitialization ) != 0 )
323+ {
324+ // This document is not yet initialized. Defer initialization to the next OnShow event.
325+ return ;
326+ }
327+ }
328+
329+ if ( ErrorHandler . Succeeded ( Frame . GetProperty ( ( int ) __VSFPROPID . VSFPROPID_DocData , out var docData ) ) )
330+ {
331+ if ( docData is IVsTextBuffer bufferAdapter )
332+ {
333+ TextBuffer = _documentTracker . _editorAdaptersFactoryService . GetDocumentBuffer ( bufferAdapter ) ;
334+
335+ if ( TextBuffer != null && ! TextBuffer . ContentType . IsOfType ( ContentTypeNames . RoslynContentType ) )
336+ {
337+ TextBuffer . Changed += NonRoslynTextBuffer_Changed ;
338+ }
339+ }
340+ }
341+
342+ return ;
343+ }
344+
290345 private int Disconnect ( )
291346 {
292347 _documentTracker . AssertIsForeground ( ) ;
0 commit comments