1- import { concat , defer , from , map , merge , mergeMap , Observable , Subject , switchMap } from 'rxjs' ;
1+ import {
2+ concat ,
3+ concatMap ,
4+ connectable ,
5+ defer ,
6+ finalize ,
7+ from ,
8+ map ,
9+ merge ,
10+ mergeMap ,
11+ Observable ,
12+ ReplaySubject ,
13+ Subject ,
14+ switchMap ,
15+ } from 'rxjs' ;
216
317import { TextEdit } from '../common/text-edit' ;
418import { Document } from '../document/document' ;
@@ -23,26 +37,26 @@ export interface DiagnosticProvider<T = TextEdit> {
2337
2438export function activeDiagnosticsChanged$ < T extends Document > (
2539 documents : DocumentAccessor < T > ,
26- validateDocument : ( doc : T ) => Diagnostic [ ] ,
40+ validateDocument : ( doc : T ) => Promise < Diagnostic [ ] > ,
2741 refreshSubject ?: Subject < string > ,
2842) : Observable < DiagnosticsChanged > {
2943 const streams : Observable < DiagnosticsChanged > [ ] = [
3044 documents . opened$ . pipe (
31- map ( ( e ) => ( {
45+ concatMap ( async ( e ) => ( {
3246 uri : e . document . uri ,
3347 version : e . document . version ,
34- diagnostics : validateDocument ( e . document ) ,
48+ diagnostics : await validateDocument ( e . document ) ,
3549 } ) ) ,
3650 ) ,
3751 documents . changed$ . pipe (
38- map ( ( e ) => ( {
52+ concatMap ( async ( e ) => ( {
3953 uri : e . document . uri ,
4054 version : e . document . version ,
41- diagnostics : validateDocument ( e . document ) ,
55+ diagnostics : await validateDocument ( e . document ) ,
4256 } ) ) ,
4357 ) ,
4458 documents . closed$ . pipe (
45- switchMap ( async ( e ) => {
59+ concatMap ( async ( e ) => {
4660 const doc = await documents . get ( e . uri ) ;
4761 return { uri : e . uri , version : doc ?. version , diagnostics : [ ] } ;
4862 } ) ,
@@ -51,9 +65,9 @@ export function activeDiagnosticsChanged$<T extends Document>(
5165 if ( refreshSubject != null ) {
5266 streams . push (
5367 refreshSubject . pipe (
54- switchMap ( async ( uri ) : Promise < DiagnosticsChanged > => {
68+ concatMap ( async ( uri ) : Promise < DiagnosticsChanged > => {
5569 const doc = await documents . get ( uri ) ;
56- return { uri, version : doc ?. version , diagnostics : doc != null ? validateDocument ( doc ) : [ ] } ;
70+ return { uri, version : doc ?. version , diagnostics : doc != null ? await validateDocument ( doc ) : [ ] } ;
5771 } ) ,
5872 ) ,
5973 ) ;
@@ -63,53 +77,76 @@ export function activeDiagnosticsChanged$<T extends Document>(
6377
6478export function allDiagnosticsChanged$ < T extends Document > (
6579 documents : DocumentAccessor < T > ,
66- validateDocument : ( doc : T ) => Diagnostic [ ] ,
80+ validateDocument : ( doc : T ) => Promise < Diagnostic [ ] > ,
6781 refreshSubject ?: Subject < string > ,
6882) : Observable < DiagnosticsChanged > {
83+ // Live document lifecycle streams that may emit while initial full validation is running.
6984 const streams : Observable < DiagnosticsChanged > [ ] = [
7085 documents . opened$ . pipe (
71- map ( ( e ) => ( {
86+ concatMap ( async ( e ) => ( {
7287 uri : e . document . uri ,
7388 version : e . document . version ,
74- diagnostics : validateDocument ( e . document ) ,
89+ diagnostics : await validateDocument ( e . document ) ,
7590 } ) ) ,
7691 ) ,
7792 documents . changed$ . pipe (
78- map ( ( e ) => ( {
93+ concatMap ( async ( e ) => ( {
7994 uri : e . document . uri ,
8095 version : e . document . version ,
81- diagnostics : validateDocument ( e . document ) ,
96+ diagnostics : await validateDocument ( e . document ) ,
8297 } ) ) ,
8398 ) ,
8499 documents . created$ . pipe (
85- map ( ( e ) => ( {
100+ concatMap ( async ( e ) => ( {
86101 uri : e . document . uri ,
87102 version : e . document . version ,
88- diagnostics : validateDocument ( e . document ) ,
103+ diagnostics : await validateDocument ( e . document ) ,
89104 } ) ) ,
90105 ) ,
91106 documents . deleted$ . pipe ( map ( ( e ) => ( { uri : e . uri , diagnostics : [ ] } ) ) ) ,
92107 documents . reset$ . pipe (
93108 switchMap ( ( _ ) => documents . all ( ) ) ,
94109 mergeMap ( ( docs ) => docs ) ,
95- map ( ( doc ) => ( { uri : doc . uri , version : doc . version , diagnostics : validateDocument ( doc ) } ) ) ,
110+ mergeMap ( async ( doc ) => ( { uri : doc . uri , version : doc . version , diagnostics : await validateDocument ( doc ) } ) ) ,
96111 ) ,
97112 ] ;
98113 if ( refreshSubject != null ) {
99114 streams . push (
100115 refreshSubject . pipe (
101- switchMap ( async ( uri ) : Promise < DiagnosticsChanged > => {
116+ switchMap ( async ( uri ) => {
102117 const doc = await documents . get ( uri ) ;
103- return { uri, version : doc ?. version , diagnostics : doc != null ? validateDocument ( doc ) : [ ] } ;
118+ return { uri, version : doc ?. version , diagnostics : doc != null ? await validateDocument ( doc ) : [ ] } ;
104119 } ) ,
105120 ) ,
106121 ) ;
107122 }
108- return concat (
109- defer ( ( ) => from ( documents . all ( ) ) ) . pipe (
110- mergeMap ( ( docs ) => docs ) ,
111- map ( ( doc ) => ( { uri : doc . uri , version : doc . version , diagnostics : validateDocument ( doc ) } ) ) ,
112- ) ,
113- merge ( ...streams ) ,
114- ) ;
123+
124+ // Two-phase emission strategy:
125+ // 1) Emit diagnostics for all currently known documents (initial snapshot pass).
126+ // 2) Then continue with live document events.
127+ //
128+ // To avoid missing live events that occur during phase (1), we connect and buffer
129+ // live streams immediately using a ReplaySubject-backed connectable observable.
130+ return defer ( ( ) => {
131+ const liveStreams$ = connectable ( merge ( ...streams ) , {
132+ connector : ( ) => new ReplaySubject < DiagnosticsChanged > ( ) ,
133+ } ) ;
134+ // Start capturing live events now, before initial documents are validated.
135+ const connection = liveStreams$ . connect ( ) ;
136+
137+ return concat (
138+ // Initial full validation pass over all known documents.
139+ defer ( ( ) => from ( documents . all ( ) ) ) . pipe (
140+ mergeMap ( ( docs ) => docs ) ,
141+ mergeMap ( async ( doc ) => ( { uri : doc . uri , version : doc . version , diagnostics : await validateDocument ( doc ) } ) ) ,
142+ ) ,
143+ // Replay any buffered live events, then continue streaming new live events.
144+ liveStreams$ ,
145+ ) . pipe (
146+ finalize ( ( ) => {
147+ // Ensure we tear down the live connection when downstream unsubscribes/completes.
148+ connection . unsubscribe ( ) ;
149+ } ) ,
150+ ) ;
151+ } ) ;
115152}
0 commit comments