33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6+ import { PackageManager } from '@vscode/ts-package-manager' ;
7+ import { basename , join } from 'path' ;
68import * as vscode from 'vscode' ;
7- import { MemFs } from './memFs' ;
89import { URI } from 'vscode-uri' ;
9- import { PackageManager , FileSystem , packagePath } from '@vscode/ts-package-manager' ;
10- import { join , basename , dirname } from 'path' ;
10+ import { Throttler } from '../utils/async' ;
11+ import { Disposable } from '../utils/dispose' ;
12+ import { MemFs } from './memFs' ;
1113
1214const TEXT_DECODER = new TextDecoder ( 'utf-8' ) ;
1315const TEXT_ENCODER = new TextEncoder ( ) ;
1416
15- export class AutoInstallerFs implements vscode . FileSystemProvider {
17+ export class AutoInstallerFs extends Disposable implements vscode . FileSystemProvider {
1618
1719 private readonly memfs = new MemFs ( ) ;
18- private readonly fs : FileSystem ;
19- private readonly projectCache = new Map < string , Set < string > > ( ) ;
20- private readonly watcher : vscode . FileSystemWatcher ;
21- private readonly _emitter = new vscode . EventEmitter < vscode . FileChangeEvent [ ] > ( ) ;
20+ private readonly packageManager : PackageManager ;
21+ private readonly _projectCache = new Map < /* root */ string , {
22+ readonly throttler : Throttler ;
23+ } > ( ) ;
2224
23- readonly onDidChangeFile : vscode . Event < vscode . FileChangeEvent [ ] > = this . _emitter . event ;
25+ private readonly _emitter = this . _register ( new vscode . EventEmitter < vscode . FileChangeEvent [ ] > ( ) ) ;
26+ readonly onDidChangeFile = this . _emitter . event ;
2427
2528 constructor ( ) {
26- this . watcher = vscode . workspace . createFileSystemWatcher ( '**/{package.json,package-lock.json,package-lock.kdl}' ) ;
27- const handler = ( uri : URI ) => {
28- const root = dirname ( uri . path ) ;
29- if ( this . projectCache . delete ( root ) ) {
30- ( async ( ) => {
31- const pm = new PackageManager ( this . fs ) ;
32- const opts = await this . getInstallOpts ( uri , root ) ;
33- const proj = await pm . resolveProject ( root , opts ) ;
34- proj . pruneExtraneous ( ) ;
35- // TODO: should this fire on vscode-node-modules instead?
36- // NB(kmarchan): This should tell TSServer that there's
37- // been changes inside node_modules and it needs to
38- // re-evaluate things.
39- this . _emitter . fire ( [ {
40- type : vscode . FileChangeType . Changed ,
41- uri : uri . with ( { path : join ( root , 'node_modules' ) } )
42- } ] ) ;
43- } ) ( ) ;
44- }
45- } ;
46- this . watcher . onDidChange ( handler ) ;
47- this . watcher . onDidCreate ( handler ) ;
48- this . watcher . onDidDelete ( handler ) ;
29+ super ( ) ;
30+
4931 const memfs = this . memfs ;
5032 memfs . onDidChangeFile ( ( e ) => {
5133 this . _emitter . fire ( e . map ( ev => ( {
@@ -54,7 +36,8 @@ export class AutoInstallerFs implements vscode.FileSystemProvider {
5436 uri : ev . uri . with ( { scheme : 'memfs' } )
5537 } ) ) ) ;
5638 } ) ;
57- this . fs = {
39+
40+ this . packageManager = new PackageManager ( {
5841 readDirectory ( path : string , _extensions ?: readonly string [ ] , _exclude ?: readonly string [ ] , _include ?: readonly string [ ] , _depth ?: number ) : string [ ] {
5942 return memfs . readDirectory ( URI . file ( path ) ) . map ( ( [ name , _ ] ) => name ) ;
6043 } ,
@@ -87,7 +70,7 @@ export class AutoInstallerFs implements vscode.FileSystemProvider {
8770 return undefined ;
8871 }
8972 }
90- } ;
73+ } ) ;
9174 }
9275
9376 watch ( resource : vscode . Uri ) : vscode . Disposable {
@@ -151,8 +134,6 @@ export class AutoInstallerFs implements vscode.FileSystemProvider {
151134 }
152135
153136 private async ensurePackageContents ( incomingUri : MappedUri ) : Promise < void > {
154- // console.log('ensurePackageContents', incomingUri.path);
155-
156137 // If we're not looking for something inside node_modules, bail early.
157138 if ( ! incomingUri . path . includes ( 'node_modules' ) ) {
158139 throw vscode . FileSystemError . FileNotFound ( ) ;
@@ -164,25 +145,26 @@ export class AutoInstallerFs implements vscode.FileSystemProvider {
164145 }
165146
166147 const root = this . getProjectRoot ( incomingUri . path ) ;
167-
168- const pkgPath = packagePath ( incomingUri . path ) ;
169- if ( ! root || this . projectCache . get ( root ) ?. has ( pkgPath ) ) {
148+ if ( ! root ) {
170149 return ;
171150 }
151+ console . log ( 'ensurePackageContents' , incomingUri . path , root ) ;
172152
173- const proj = await ( new PackageManager ( this . fs ) ) . resolveProject ( root , await this . getInstallOpts ( incomingUri . original , root ) ) ;
174-
175- const restore = proj . restorePackageAt ( incomingUri . path ) ;
176- try {
177- await restore ;
178- } catch ( e ) {
179- console . error ( `failed to restore package at ${ incomingUri . path } : ` , e ) ;
180- throw e ;
153+ let projectEntry = this . _projectCache . get ( root ) ;
154+ if ( ! projectEntry ) {
155+ projectEntry = { throttler : new Throttler ( ) } ;
156+ this . _projectCache . set ( root , projectEntry ) ;
181157 }
182- if ( ! this . projectCache . has ( root ) ) {
183- this . projectCache . set ( root , new Set ( ) ) ;
184- }
185- this . projectCache . get ( root ) ! . add ( pkgPath ) ;
158+
159+ projectEntry . throttler . queue ( async ( ) => {
160+ const proj = await this . packageManager . resolveProject ( root , await this . getInstallOpts ( incomingUri . original , root ) ) ;
161+ try {
162+ await proj . restore ( ) ;
163+ } catch ( e ) {
164+ console . error ( `failed to restore package at ${ incomingUri . path } : ` , e ) ;
165+ throw e ;
166+ }
167+ } ) ;
186168 }
187169
188170 private async getInstallOpts ( originalUri : URI , root : string ) {
0 commit comments