@@ -17,6 +17,7 @@ import { realpath } from '../../../../../base/node/extpath.js';
17
17
import { Promises } from '../../../../../base/node/pfs.js' ;
18
18
import { FileChangeType , IFileChange } from '../../../common/files.js' ;
19
19
import { ILogMessage , coalesceEvents , INonRecursiveWatchRequest , parseWatcherPatterns , IRecursiveWatcherWithSubscribe , isFiltered , isWatchRequestWithCorrelation } from '../../../common/watcher.js' ;
20
+ import { Lazy } from '../../../../../base/common/lazy.js' ;
20
21
21
22
export class NodeJSFileWatcherLibrary extends Disposable {
22
23
@@ -55,6 +56,28 @@ export class NodeJSFileWatcherLibrary extends Disposable {
55
56
56
57
private readonly cts = new CancellationTokenSource ( ) ;
57
58
59
+ private readonly realPath = new Lazy ( async ( ) => {
60
+
61
+ // This property is intentionally `Lazy` and not using `realcase()` as the counterpart
62
+ // in the recursive watcher because of the amount of paths this watcher is dealing with.
63
+ // We try as much as possible to avoid even needing `realpath()` if we can because even
64
+ // that method does an `lstat()` per segment of the path.
65
+
66
+ let result = this . request . path ;
67
+
68
+ try {
69
+ result = await realpath ( this . request . path ) ;
70
+
71
+ if ( this . request . path !== result ) {
72
+ this . trace ( `correcting a path to watch that seems to be a symbolic link (original: ${ this . request . path } , real: ${ result } )` ) ;
73
+ }
74
+ } catch ( error ) {
75
+ // ignore
76
+ }
77
+
78
+ return result ;
79
+ } ) ;
80
+
58
81
readonly ready = this . watch ( ) ;
59
82
60
83
private _isReusingRecursiveWatcher = false ;
@@ -76,19 +99,13 @@ export class NodeJSFileWatcherLibrary extends Disposable {
76
99
77
100
private async watch ( ) : Promise < void > {
78
101
try {
79
- const realPath = await this . normalizePath ( this . request ) ;
102
+ const stat = await promises . stat ( this . request . path ) ;
80
103
81
104
if ( this . cts . token . isCancellationRequested ) {
82
105
return ;
83
106
}
84
107
85
- const stat = await promises . stat ( realPath ) ;
86
-
87
- if ( this . cts . token . isCancellationRequested ) {
88
- return ;
89
- }
90
-
91
- this . _register ( await this . doWatch ( realPath , stat . isDirectory ( ) ) ) ;
108
+ this . _register ( await this . doWatch ( stat . isDirectory ( ) ) ) ;
92
109
} catch ( error ) {
93
110
if ( error . code !== 'ENOENT' ) {
94
111
this . error ( error ) ;
@@ -106,46 +123,21 @@ export class NodeJSFileWatcherLibrary extends Disposable {
106
123
this . onDidWatchFail ?.( ) ;
107
124
}
108
125
109
- private async normalizePath ( request : INonRecursiveWatchRequest ) : Promise < string > {
110
- let realPath = request . path ;
111
-
112
- try {
113
-
114
- // Check for symbolic link
115
- realPath = await realpath ( request . path ) ;
116
-
117
- // Note: we used to also call `realcase()` here, but
118
- // that operation is very expensive for large amounts
119
- // of files and is actually not needed for single
120
- // file/folder watching where we report on the original
121
- // path anyway.
122
- // (https://github.com/microsoft/vscode/issues/237351)
123
-
124
- if ( request . path !== realPath ) {
125
- this . trace ( `correcting a path to watch that seems to be a symbolic link (original: ${ request . path } , real: ${ realPath } )` ) ;
126
- }
127
- } catch ( error ) {
128
- // ignore
129
- }
130
-
131
- return realPath ;
132
- }
133
-
134
- private async doWatch ( realPath : string , isDirectory : boolean ) : Promise < IDisposable > {
126
+ private async doWatch ( isDirectory : boolean ) : Promise < IDisposable > {
135
127
const disposables = new DisposableStore ( ) ;
136
128
137
- if ( this . doWatchWithExistingWatcher ( realPath , isDirectory , disposables ) ) {
129
+ if ( this . doWatchWithExistingWatcher ( isDirectory , disposables ) ) {
138
130
this . trace ( `reusing an existing recursive watcher for ${ this . request . path } ` ) ;
139
131
this . _isReusingRecursiveWatcher = true ;
140
132
} else {
141
133
this . _isReusingRecursiveWatcher = false ;
142
- await this . doWatchWithNodeJS ( realPath , isDirectory , disposables ) ;
134
+ await this . doWatchWithNodeJS ( isDirectory , disposables ) ;
143
135
}
144
136
145
137
return disposables ;
146
138
}
147
139
148
- private doWatchWithExistingWatcher ( realPath : string , isDirectory : boolean , disposables : DisposableStore ) : boolean {
140
+ private doWatchWithExistingWatcher ( isDirectory : boolean , disposables : DisposableStore ) : boolean {
149
141
if ( isDirectory ) {
150
142
// Recursive watcher re-use is currently not enabled for when
151
143
// folders are watched. this is because the dispatching in the
@@ -162,7 +154,7 @@ export class NodeJSFileWatcherLibrary extends Disposable {
162
154
}
163
155
164
156
if ( error ) {
165
- const watchDisposable = await this . doWatch ( realPath , isDirectory ) ;
157
+ const watchDisposable = await this . doWatch ( isDirectory ) ;
166
158
if ( ! disposables . isDisposed ) {
167
159
disposables . add ( watchDisposable ) ;
168
160
} else {
@@ -188,7 +180,8 @@ export class NodeJSFileWatcherLibrary extends Disposable {
188
180
return false ;
189
181
}
190
182
191
- private async doWatchWithNodeJS ( realPath : string , isDirectory : boolean , disposables : DisposableStore ) : Promise < void > {
183
+ private async doWatchWithNodeJS ( isDirectory : boolean , disposables : DisposableStore ) : Promise < void > {
184
+ const realPath = await this . realPath . value ;
192
185
193
186
// macOS: watching samba shares can crash VSCode so we do
194
187
// a simple check for the file path pointing to /Volumes
@@ -407,7 +400,7 @@ export class NodeJSFileWatcherLibrary extends Disposable {
407
400
if ( fileExists ) {
408
401
this . onFileChange ( { resource : requestResource , type : FileChangeType . UPDATED , cId : this . request . correlationId } , true /* skip excludes/includes (file is explicitly watched) */ ) ;
409
402
410
- watcherDisposables . add ( await this . doWatch ( realPath , false ) ) ;
403
+ watcherDisposables . add ( await this . doWatch ( false ) ) ;
411
404
}
412
405
413
406
// File seems to be really gone, so emit a deleted and failed event
0 commit comments