@@ -2,6 +2,72 @@ import ts from 'typescript';
22import { DocumentSnapshot , JSOrTSDocumentSnapshot } from './DocumentSnapshot' ;
33import { Logger } from '../../logger' ;
44import { TextDocumentContentChangeEvent } from 'vscode-languageserver' ;
5+ import { normalizePath } from '../../utils' ;
6+ import { EventEmitter } from 'events' ;
7+
8+ /**
9+ * Every snapshot corresponds to a unique file on disk.
10+ * A snapshot can be part of multiple projects, but for a given file path
11+ * there can be only one snapshot.
12+ */
13+ export class GlobalSnapshotsManager {
14+ private emitter = new EventEmitter ( ) ;
15+ private documents = new Map < string , DocumentSnapshot > ( ) ;
16+
17+ get ( fileName : string ) {
18+ fileName = normalizePath ( fileName ) ;
19+ return this . documents . get ( fileName ) ;
20+ }
21+
22+ set ( fileName : string , document : DocumentSnapshot ) {
23+ fileName = normalizePath ( fileName ) ;
24+ const prev = this . get ( fileName ) ;
25+ if ( prev ) {
26+ prev . destroyFragment ( ) ;
27+ }
28+
29+ this . documents . set ( fileName , document ) ;
30+ this . emitter . emit ( 'change' , fileName , document ) ;
31+ }
32+
33+ delete ( fileName : string ) {
34+ fileName = normalizePath ( fileName ) ;
35+ this . documents . delete ( fileName ) ;
36+ this . emitter . emit ( 'change' , fileName , undefined ) ;
37+ }
38+
39+ updateTsOrJsFile (
40+ fileName : string ,
41+ changes ?: TextDocumentContentChangeEvent [ ]
42+ ) : JSOrTSDocumentSnapshot | undefined {
43+ fileName = normalizePath ( fileName ) ;
44+ const previousSnapshot = this . get ( fileName ) ;
45+
46+ if ( changes ) {
47+ if ( ! ( previousSnapshot instanceof JSOrTSDocumentSnapshot ) ) {
48+ return ;
49+ }
50+ previousSnapshot . update ( changes ) ;
51+ return previousSnapshot ;
52+ } else {
53+ const newSnapshot = DocumentSnapshot . fromNonSvelteFilePath ( fileName ) ;
54+
55+ if ( previousSnapshot ) {
56+ newSnapshot . version = previousSnapshot . version + 1 ;
57+ } else {
58+ // ensure it's greater than initial version
59+ // so that ts server picks up the change
60+ newSnapshot . version += 1 ;
61+ }
62+ this . set ( fileName , newSnapshot ) ;
63+ return newSnapshot ;
64+ }
65+ }
66+
67+ onChange ( listener : ( fileName : string , newDocument : DocumentSnapshot | undefined ) => void ) {
68+ this . emitter . on ( 'change' , listener ) ;
69+ }
70+ }
571
672export interface TsFilesSpec {
773 include ?: readonly string [ ] ;
@@ -12,7 +78,7 @@ export interface TsFilesSpec {
1278 * Should only be used by `service.ts`
1379 */
1480export class SnapshotManager {
15- private documents : Map < string , DocumentSnapshot > = new Map ( ) ;
81+ private documents = new Map < string , DocumentSnapshot > ( ) ;
1682 private lastLogged = new Date ( new Date ( ) . getTime ( ) - 60_001 ) ;
1783
1884 private readonly watchExtensions = [
@@ -25,12 +91,26 @@ export class SnapshotManager {
2591 ] ;
2692
2793 constructor (
94+ private globalSnapshotsManager : GlobalSnapshotsManager ,
2895 private projectFiles : string [ ] ,
2996 private fileSpec : TsFilesSpec ,
3097 private workspaceRoot : string
31- ) { }
98+ ) {
99+ this . globalSnapshotsManager . onChange ( ( fileName , document ) => {
100+ // Only delete/update snapshots, don't add new ones,
101+ // as they could be from another TS service and this
102+ // snapshot manager can't reach this file.
103+ // For these, instead wait on a `get` method invocation
104+ // and set them "manually" in the set/update methods.
105+ if ( ! document ) {
106+ this . documents . delete ( fileName ) ;
107+ } else if ( this . documents . has ( fileName ) ) {
108+ this . documents . set ( fileName , document ) ;
109+ }
110+ } ) ;
111+ }
32112
33- updateProjectFiles ( ) {
113+ updateProjectFiles ( ) : void {
34114 const { include, exclude } = this . fileSpec ;
35115
36116 // Since we default to not include anything,
@@ -39,67 +119,58 @@ export class SnapshotManager {
39119 return ;
40120 }
41121
42- const projectFiles = ts . sys . readDirectory (
43- this . workspaceRoot ,
44- this . watchExtensions ,
45- exclude ,
46- include
47- ) ;
122+ const projectFiles = ts . sys
123+ . readDirectory ( this . workspaceRoot , this . watchExtensions , exclude , include )
124+ . map ( normalizePath ) ;
48125
49126 this . projectFiles = Array . from ( new Set ( [ ...this . projectFiles , ...projectFiles ] ) ) ;
50127 }
51128
52129 updateTsOrJsFile ( fileName : string , changes ?: TextDocumentContentChangeEvent [ ] ) : void {
53- const previousSnapshot = this . get ( fileName ) ;
54-
55- if ( changes ) {
56- if ( ! ( previousSnapshot instanceof JSOrTSDocumentSnapshot ) ) {
57- return ;
58- }
59- previousSnapshot . update ( changes ) ;
60- } else {
61- const newSnapshot = DocumentSnapshot . fromNonSvelteFilePath ( fileName ) ;
62-
63- if ( previousSnapshot ) {
64- newSnapshot . version = previousSnapshot . version + 1 ;
65- } else {
66- // ensure it's greater than initial version
67- // so that ts server picks up the change
68- newSnapshot . version += 1 ;
69- }
70- this . set ( fileName , newSnapshot ) ;
130+ const snapshot = this . globalSnapshotsManager . updateTsOrJsFile ( fileName , changes ) ;
131+ // This isn't duplicated logic to the listener, because this could
132+ // be a new snapshot which the listener wouldn't add.
133+ if ( snapshot ) {
134+ this . documents . set ( normalizePath ( fileName ) , snapshot ) ;
71135 }
72136 }
73137
74- has ( fileName : string ) {
138+ has ( fileName : string ) : boolean {
139+ fileName = normalizePath ( fileName ) ;
75140 return this . projectFiles . includes ( fileName ) || this . getFileNames ( ) . includes ( fileName ) ;
76141 }
77142
78- set ( fileName : string , snapshot : DocumentSnapshot ) {
79- const prev = this . get ( fileName ) ;
80- if ( prev ) {
81- prev . destroyFragment ( ) ;
82- }
83-
143+ set ( fileName : string , snapshot : DocumentSnapshot ) : void {
144+ this . globalSnapshotsManager . set ( fileName , snapshot ) ;
145+ // This isn't duplicated logic to the listener, because this could
146+ // be a new snapshot which the listener wouldn't add.
147+ this . documents . set ( normalizePath ( fileName ) , snapshot ) ;
84148 this . logStatistics ( ) ;
85-
86- return this . documents . set ( fileName , snapshot ) ;
87149 }
88150
89- get ( fileName : string ) {
90- return this . documents . get ( fileName ) ;
151+ get ( fileName : string ) : DocumentSnapshot | undefined {
152+ fileName = normalizePath ( fileName ) ;
153+ let snapshot = this . documents . get ( fileName ) ;
154+ if ( ! snapshot ) {
155+ snapshot = this . globalSnapshotsManager . get ( fileName ) ;
156+ if ( snapshot ) {
157+ this . documents . set ( fileName , snapshot ) ;
158+ }
159+ }
160+ return snapshot ;
91161 }
92162
93- delete ( fileName : string ) {
163+ delete ( fileName : string ) : void {
164+ fileName = normalizePath ( fileName ) ;
94165 this . projectFiles = this . projectFiles . filter ( ( s ) => s !== fileName ) ;
95- return this . documents . delete ( fileName ) ;
166+ this . globalSnapshotsManager . delete ( fileName ) ;
96167 }
97168
98- getFileNames ( ) {
169+ getFileNames ( ) : string [ ] {
99170 return Array . from ( this . documents . keys ( ) ) ;
100171 }
101172
102- getProjectFileNames ( ) {
173+ getProjectFileNames ( ) : string [ ] {
103174 return [ ...this . projectFiles ] ;
104175 }
105176
0 commit comments