@@ -7,7 +7,8 @@ import * as minimatch from 'minimatch';
77
88import {
99 Disposable , languages as Languages , window as Window , workspace as Workspace , CancellationToken , ProviderResult , Diagnostic as VDiagnostic ,
10- CancellationTokenSource , TextDocument , CancellationError , Event as VEvent , EventEmitter , DiagnosticCollection , Uri , TabInputText , TabInputTextDiff
10+ CancellationTokenSource , TextDocument , CancellationError , Event as VEvent , EventEmitter , DiagnosticCollection , Uri , TabInputText , TabInputTextDiff ,
11+ TabChangeEvent , Event
1112} from 'vscode' ;
1213
1314import {
@@ -174,27 +175,57 @@ type RequestState = {
174175 document : TextDocument | Uri ;
175176} ;
176177
178+
179+ /**
180+ * Manages the open tabs. We don't directly use the tab API since for
181+ * diagnostics we need to de-dupe tabs that show the same resources since
182+ * we pull on the model not the UI.
183+ */
177184class Tabs {
178185
179- private readonly open : Set < string > ;
186+ private open : Set < string > ;
187+ private readonly _onOpen : EventEmitter < Set < Uri > > ;
188+ private readonly _onClose : EventEmitter < Set < Uri > > ;
180189 private readonly disposable : Disposable ;
181190
182191 constructor ( ) {
183192 this . open = new Set ( ) ;
184- const openTabsHandler = ( ) => {
185- this . open . clear ( ) ;
186- for ( const group of Window . tabGroups . all ) {
187- for ( const tab of group . tabs ) {
188- const input = tab . input ;
189- if ( input instanceof TabInputText ) {
190- this . open . add ( input . uri . toString ( ) ) ;
191- } else if ( input instanceof TabInputTextDiff ) {
192- this . open . add ( input . modified . toString ( ) ) ;
193- }
193+ this . _onOpen = new EventEmitter ( ) ;
194+ this . _onClose = new EventEmitter ( ) ;
195+ Tabs . fillTabResources ( this . open ) ;
196+ const openTabsHandler = ( event : TabChangeEvent ) => {
197+ if ( event . closed . length === 0 && event . opened . length === 0 ) {
198+ return ;
199+ }
200+ const oldTabs = this . open ;
201+ const currentTabs : Set < string > = new Set ( ) ;
202+ Tabs . fillTabResources ( currentTabs ) ;
203+
204+ const closed : Set < string > = new Set ( ) ;
205+ const opened : Set < string > = new Set ( currentTabs ) ;
206+ for ( const tab of oldTabs . values ( ) ) {
207+ if ( currentTabs . has ( tab ) ) {
208+ opened . delete ( tab ) ;
209+ } else {
210+ closed . add ( tab ) ;
211+ }
212+ }
213+ this . open = currentTabs ;
214+ if ( closed . size > 0 ) {
215+ const toFire : Set < Uri > = new Set ( ) ;
216+ for ( const item of closed ) {
217+ toFire . add ( Uri . parse ( item ) ) ;
218+ }
219+ this . _onClose . fire ( toFire ) ;
220+ }
221+ if ( opened . size > 0 ) {
222+ const toFire : Set < Uri > = new Set ( ) ;
223+ for ( const item of opened ) {
224+ toFire . add ( Uri . parse ( item ) ) ;
194225 }
226+ this . _onOpen . fire ( toFire ) ;
195227 }
196228 } ;
197- openTabsHandler ( ) ;
198229
199230 if ( Window . tabGroups . onDidChangeTabs !== undefined ) {
200231 this . disposable = Window . tabGroups . onDidChangeTabs ( openTabsHandler ) ;
@@ -203,6 +234,14 @@ class Tabs {
203234 }
204235 }
205236
237+ public get onClose ( ) : Event < Set < Uri > > {
238+ return this . _onClose . event ;
239+ }
240+
241+ public get onOpen ( ) : Event < Set < Uri > > {
242+ return this . _onOpen . event ;
243+ }
244+
206245 public dispose ( ) : void {
207246 this . disposable . dispose ( ) ;
208247 }
@@ -218,19 +257,29 @@ class Tabs {
218257 return this . open . has ( uri . toString ( ) ) ;
219258 }
220259
221- public getTabResources ( ) : Uri [ ] {
222- const result : Uri [ ] = [ ] ;
260+ public getTabResources ( ) : Set < Uri > {
261+ const result : Set < Uri > = new Set ( ) ;
262+ Tabs . fillTabResources ( new Set ( ) , result ) ;
263+ return result ;
264+ }
265+
266+ private static fillTabResources ( strings : Set < string > | undefined , uris ?: Set < Uri > ) : void {
267+ const seen = strings ?? new Set ( ) ;
223268 for ( const group of Window . tabGroups . all ) {
224269 for ( const tab of group . tabs ) {
225270 const input = tab . input ;
271+ let uri : Uri | undefined ;
226272 if ( input instanceof TabInputText ) {
227- result . push ( input . uri ) ;
273+ uri = input . uri ;
228274 } else if ( input instanceof TabInputTextDiff ) {
229- result . push ( input . modified ) ;
275+ uri = input . modified ;
276+ }
277+ if ( uri !== undefined && ! seen . has ( uri . toString ( ) ) ) {
278+ seen . add ( uri . toString ( ) ) ;
279+ uris !== undefined && uris . add ( uri ) ;
230280 }
231281 }
232282 }
233- return result ;
234283 }
235284}
236285
@@ -294,16 +343,12 @@ class DocumentPullStateTracker {
294343 states . delete ( key ) ;
295344 }
296345
297- public tracks ( kind : PullState , textDocument : TextDocument ) : boolean ;
298- public tracks ( kind : PullState , uri : Uri ) : boolean ;
299346 public tracks ( kind : PullState , document : TextDocument | Uri ) : boolean {
300347 const key = document instanceof Uri ? document . toString ( ) : document . uri . toString ( ) ;
301348 const states = kind === PullState . document ? this . documentPullStates : this . workspacePullStates ;
302349 return states . has ( key ) ;
303350 }
304351
305- public getResultId ( kind : PullState , document : TextDocument ) : string | undefined ;
306- public getResultId ( kind : PullState , document : Uri ) : string | undefined ;
307352 public getResultId ( kind : PullState , document : TextDocument | Uri ) : string | undefined {
308353 const key = document instanceof Uri ? document . toString ( ) : document . uri . toString ( ) ;
309354 const states = kind === PullState . document ? this . documentPullStates : this . workspacePullStates ;
@@ -356,8 +401,12 @@ class DiagnosticRequestor implements Disposable {
356401 this . workspaceErrorCounter = 0 ;
357402 }
358403
359- public knows ( kind : PullState , textDocument : TextDocument ) : boolean {
360- return this . documentStates . tracks ( kind , textDocument ) ;
404+ public knows ( kind : PullState , document : TextDocument | Uri ) : boolean {
405+ return this . documentStates . tracks ( kind , document ) ;
406+ }
407+
408+ public forget ( kind : PullState , document : TextDocument | Uri ) : void {
409+ this . documentStates . unTrack ( kind , document ) ;
361410 }
362411
363412 public pull ( document : TextDocument | Uri , cb ?: ( ) => void ) : void {
@@ -440,24 +489,32 @@ class DiagnosticRequestor implements Disposable {
440489 }
441490 }
442491
443- public cleanupPull ( document : TextDocument | Uri ) : void {
492+ public forgetDocument ( document : TextDocument | Uri ) : void {
444493 const uri = document instanceof Uri ? document : document . uri ;
445494 const key = uri . toString ( ) ;
446495 const request = this . openRequests . get ( key ) ;
447- if ( this . options . workspaceDiagnostics || this . options . interFileDependencies ) {
496+ if ( this . options . workspaceDiagnostics ) {
497+ // If we run workspace diagnostic pull a last time for the diagnostics
498+ // and the rely on getting them from the workspace result.
448499 if ( request !== undefined ) {
449500 this . openRequests . set ( key , { state : RequestStateKind . reschedule , document : document } ) ;
450501 } else {
451- this . pull ( document ) ;
502+ this . pull ( document , ( ) => {
503+ this . forget ( PullState . document , document ) ;
504+ } ) ;
452505 }
453506 } else {
507+ // We have normal pull or inter file dependencies. In this case we
508+ // clear the diagnostics (to have the same start as after startup).
509+ // We also cancel outstanding requests.
454510 if ( request !== undefined ) {
455511 if ( request . state === RequestStateKind . active ) {
456512 request . tokenSource . cancel ( ) ;
457513 }
458514 this . openRequests . set ( key , { state : RequestStateKind . outDated , document : document } ) ;
459515 }
460516 this . diagnostics . delete ( uri ) ;
517+ this . forget ( PullState . document , document ) ;
461518 }
462519 }
463520
@@ -814,6 +871,13 @@ class DiagnosticFeatureProviderImpl implements DiagnosticProviderShape {
814871 }
815872 } ) ;
816873
874+ // For pull model diagnostics we pull for documents visible in the UI.
875+ // From an eventing point of view we still rely on open document events
876+ // and filter the documents that are not visible in the UI instead of
877+ // listening to Tab events. Major reason is event timing since we need
878+ // to ensure that the pull is send after the document open has reached
879+ // the server.
880+
817881 // We always pull on open.
818882 const openFeature = client . getFeature ( DidOpenTextDocumentNotification . method ) ;
819883 disposables . push ( openFeature . onNotificationSent ( ( event ) => {
@@ -824,23 +888,31 @@ class DiagnosticFeatureProviderImpl implements DiagnosticProviderShape {
824888 } ) ) ;
825889
826890 // Pull all diagnostics for documents that are already open
827- const pullTextDocuments : Set < string > = new Set ( ) ;
891+ const pulledTextDocuments : Set < string > = new Set ( ) ;
828892 for ( const textDocument of Workspace . textDocuments ) {
829893 if ( matches ( textDocument ) ) {
830894 this . diagnosticRequestor . pull ( textDocument , ( ) => { addToBackgroundIfNeeded ( textDocument ) ; } ) ;
831- pullTextDocuments . add ( textDocument . uri . toString ( ) ) ;
895+ pulledTextDocuments . add ( textDocument . uri . toString ( ) ) ;
832896 }
833897 }
834898
835- // Pull all tabs if not already pull as text document
899+ // Pull all tabs if not already pulled as text document
836900 if ( diagnosticPullOptions . onTabs === true ) {
837901 for ( const resource of tabs . getTabResources ( ) ) {
838- if ( ! pullTextDocuments . has ( resource . toString ( ) ) && matches ( resource ) ) {
902+ if ( ! pulledTextDocuments . has ( resource . toString ( ) ) && matches ( resource ) ) {
839903 this . diagnosticRequestor . pull ( resource , ( ) => { addToBackgroundIfNeeded ( resource ) ; } ) ;
840904 }
841905 }
842906 }
843907
908+ tabs . onOpen ( ( opened ) => {
909+ for ( const document of opened ) {
910+ if ( matches ( document ) && ! this . diagnosticRequestor . knows ( PullState . document , document ) ) {
911+ this . diagnosticRequestor . pull ( document , ( ) => { addToBackgroundIfNeeded ( document ) ; } ) ;
912+ }
913+ }
914+ } ) ;
915+
844916 if ( diagnosticPullOptions . onChange === true ) {
845917 const changeFeature = client . getFeature ( DidChangeTextDocumentNotification . method ) ;
846918 disposables . push ( changeFeature . onNotificationSent ( async ( event ) => {
@@ -864,11 +936,16 @@ class DiagnosticFeatureProviderImpl implements DiagnosticProviderShape {
864936 // When the document closes clear things up
865937 const closeFeature = client . getFeature ( DidCloseTextDocumentNotification . method ) ;
866938 disposables . push ( closeFeature . onNotificationSent ( ( event ) => {
867- const textDocument = event . original ;
868- this . diagnosticRequestor . cleanupPull ( textDocument ) ;
869- this . backgroundScheduler . remove ( textDocument ) ;
939+ this . cleanUpDocument ( event . original ) ;
870940 } ) ) ;
871941
942+ // Same when a tabs closes.
943+ tabs . onClose ( ( closed ) => {
944+ for ( const document of closed ) {
945+ this . cleanUpDocument ( document ) ;
946+ }
947+ } ) ;
948+
872949 // We received a did change from the server.
873950 this . diagnosticRequestor . onDidChangeDiagnosticsEmitter . event ( ( ) => {
874951 for ( const textDocument of Workspace . textDocuments ) {
@@ -893,6 +970,13 @@ class DiagnosticFeatureProviderImpl implements DiagnosticProviderShape {
893970 public get diagnostics ( ) : vsdiag . DiagnosticProvider {
894971 return this . diagnosticRequestor . provider ;
895972 }
973+
974+ private cleanUpDocument ( document : TextDocument | Uri ) : void {
975+ if ( this . diagnosticRequestor . knows ( PullState . document , document ) ) {
976+ this . diagnosticRequestor . forgetDocument ( document ) ;
977+ this . backgroundScheduler . remove ( document ) ;
978+ }
979+ }
896980}
897981
898982export class DiagnosticFeature extends TextDocumentLanguageFeature < DiagnosticOptions , DiagnosticRegistrationOptions , DiagnosticProviderShape , DiagnosticProviderMiddleware , $DiagnosticPullOptions > {
0 commit comments