3
3
4
4
using System ;
5
5
using System . Collections . Generic ;
6
+ using System . Collections . Immutable ;
6
7
using System . IO ;
7
8
using System . Threading ;
8
9
using System . Threading . Tasks ;
14
15
15
16
namespace Microsoft . AspNetCore . Razor . LanguageServer ;
16
17
17
- internal class ProjectConfigurationFileChangeDetector : IFileChangeDetector
18
+ internal class ProjectConfigurationFileChangeDetector (
19
+ IEnumerable < IProjectConfigurationFileChangeListener > listeners ,
20
+ LanguageServerFeatureOptions options ,
21
+ ILoggerFactory loggerFactory ) : IFileChangeDetector
18
22
{
19
- private readonly ProjectSnapshotManagerDispatcher _dispatcher ;
20
- private readonly IEnumerable < IProjectConfigurationFileChangeListener > _listeners ;
21
- private readonly LanguageServerFeatureOptions _options ;
22
- private readonly ILogger _logger ;
23
- private FileSystemWatcher ? _watcher ;
24
-
25
- private static readonly IReadOnlyCollection < string > s_ignoredDirectories = new string [ ]
26
- {
23
+ private static readonly ImmutableArray < string > s_ignoredDirectories =
24
+ [
27
25
"node_modules" ,
28
26
"bin" ,
29
27
".vs" ,
30
- } ;
28
+ ] ;
31
29
32
- public ProjectConfigurationFileChangeDetector (
33
- ProjectSnapshotManagerDispatcher dispatcher ,
34
- IEnumerable < IProjectConfigurationFileChangeListener > listeners ,
35
- LanguageServerFeatureOptions options ,
36
- ILoggerFactory loggerFactory )
37
- {
38
- if ( loggerFactory is null )
39
- {
40
- throw new ArgumentNullException ( nameof ( loggerFactory ) ) ;
41
- }
30
+ private readonly ImmutableArray < IProjectConfigurationFileChangeListener > _listeners = listeners . ToImmutableArray ( ) ;
31
+ private readonly LanguageServerFeatureOptions _options = options ;
32
+ private readonly ILogger _logger = loggerFactory . GetOrCreateLogger < ProjectConfigurationFileChangeDetector > ( ) ;
42
33
43
- _dispatcher = dispatcher ?? throw new ArgumentNullException ( nameof ( dispatcher ) ) ;
44
- _listeners = listeners ?? throw new ArgumentNullException ( nameof ( listeners ) ) ;
45
- _options = options ?? throw new ArgumentNullException ( nameof ( options ) ) ;
46
- _logger = loggerFactory . GetOrCreateLogger < ProjectConfigurationFileChangeDetector > ( ) ;
47
- }
34
+ private FileSystemWatcher ? _watcher ;
48
35
49
- public async Task StartAsync ( string workspaceDirectory , CancellationToken cancellationToken )
36
+ public Task StartAsync ( string workspaceDirectory , CancellationToken cancellationToken )
50
37
{
51
- if ( workspaceDirectory is null )
52
- {
53
- throw new ArgumentNullException ( nameof ( workspaceDirectory ) ) ;
54
- }
55
-
56
38
// Dive through existing project configuration files and fabricate "added" events so listeners can accurately listen to state changes for them.
57
39
58
40
workspaceDirectory = FilePathNormalizer . Normalize ( workspaceDirectory ) ;
59
41
var existingConfigurationFiles = GetExistingConfigurationFiles ( workspaceDirectory ) ;
60
42
61
43
_logger . LogDebug ( $ "Triggering events for existing project configuration files") ;
62
- await _dispatcher . RunAsync ( ( ) =>
44
+
45
+ foreach ( var configurationFilePath in existingConfigurationFiles )
63
46
{
64
- foreach ( var configurationFilePath in existingConfigurationFiles )
65
- {
66
- FileSystemWatcher_ProjectConfigurationFileEvent ( configurationFilePath , RazorFileChangeKind . Added ) ;
67
- }
68
- } , cancellationToken ) . ConfigureAwait ( false ) ;
47
+ NotifyListeners ( new ( configurationFilePath , RazorFileChangeKind . Added ) ) ;
48
+ }
69
49
70
50
// This is an entry point for testing
71
- OnInitializationFinished ( ) ;
72
-
73
- if ( cancellationToken . IsCancellationRequested )
51
+ if ( ! InitializeFileWatchers )
74
52
{
75
- // Client cancelled connection, no need to setup any file watchers. Server is about to tear down.
76
- return ;
53
+ return Task . CompletedTask ;
77
54
}
78
55
79
56
try
@@ -85,23 +62,19 @@ await _dispatcher.RunAsync(() =>
85
62
Directory . CreateDirectory ( workspaceDirectory ) ;
86
63
}
87
64
}
88
- catch ( OperationCanceledException )
89
- {
90
- return ;
91
- }
92
65
catch ( Exception ex )
93
66
{
94
67
// Directory.Exists will throw on things like long paths
95
68
_logger . LogError ( ex , $ "Failed validating that file watcher would be successful for '{ workspaceDirectory } '") ;
96
69
97
70
// No point continuing because the FileSystemWatcher constructor would just throw too.
98
- return ;
71
+ return Task . FromException ( ex ) ;
99
72
}
100
73
101
74
if ( cancellationToken . IsCancellationRequested )
102
75
{
103
76
// Client cancelled connection, no need to setup any file watchers. Server is about to tear down.
104
- return ;
77
+ return Task . FromCanceled ( cancellationToken ) ;
105
78
}
106
79
107
80
_logger . LogInformation ( $ "Starting configuration file change detector for '{ workspaceDirectory } '") ;
@@ -111,26 +84,28 @@ await _dispatcher.RunAsync(() =>
111
84
IncludeSubdirectories = true ,
112
85
} ;
113
86
114
- _watcher . Created += ( sender , args ) => FileSystemWatcher_ProjectConfigurationFileEvent_Background ( args . FullPath , RazorFileChangeKind . Added ) ;
115
- _watcher . Deleted += ( sender , args ) => FileSystemWatcher_ProjectConfigurationFileEvent_Background ( args . FullPath , RazorFileChangeKind . Removed ) ;
116
- _watcher . Changed += ( sender , args ) => FileSystemWatcher_ProjectConfigurationFileEvent_Background ( args . FullPath , RazorFileChangeKind . Changed ) ;
87
+ _watcher . Created += ( sender , args ) => NotifyListeners ( args . FullPath , RazorFileChangeKind . Added ) ;
88
+ _watcher . Deleted += ( sender , args ) => NotifyListeners ( args . FullPath , RazorFileChangeKind . Removed ) ;
89
+ _watcher . Changed += ( sender , args ) => NotifyListeners ( args . FullPath , RazorFileChangeKind . Changed ) ;
117
90
_watcher . Renamed += ( sender , args ) =>
118
91
{
119
92
// Translate file renames into remove / add
120
93
121
94
if ( args . OldFullPath . EndsWith ( _options . ProjectConfigurationFileName , FilePathComparison . Instance ) )
122
95
{
123
96
// Renaming from project configuration file to something else. Just remove the configuration file.
124
- FileSystemWatcher_ProjectConfigurationFileEvent_Background ( args . OldFullPath , RazorFileChangeKind . Removed ) ;
97
+ NotifyListeners ( args . OldFullPath , RazorFileChangeKind . Removed ) ;
125
98
}
126
99
else if ( args . FullPath . EndsWith ( _options . ProjectConfigurationFileName , FilePathComparison . Instance ) )
127
100
{
128
101
// Renaming from a non-project configuration file file to a real one. Just add the configuration file.
129
- FileSystemWatcher_ProjectConfigurationFileEvent_Background ( args . FullPath , RazorFileChangeKind . Added ) ;
102
+ NotifyListeners ( args . FullPath , RazorFileChangeKind . Added ) ;
130
103
}
131
104
} ;
132
105
133
106
_watcher . EnableRaisingEvents = true ;
107
+
108
+ return Task . CompletedTask ;
134
109
}
135
110
136
111
public void Stop ( )
@@ -142,33 +117,28 @@ public void Stop()
142
117
}
143
118
144
119
// Protected virtual for testing
145
- protected virtual void OnInitializationFinished ( )
146
- {
147
- }
120
+ protected virtual bool InitializeFileWatchers => true ;
148
121
149
122
// Protected virtual for testing
150
- protected virtual IEnumerable < string > GetExistingConfigurationFiles ( string workspaceDirectory )
123
+ protected virtual ImmutableArray < string > GetExistingConfigurationFiles ( string workspaceDirectory )
151
124
{
152
125
return DirectoryHelper . GetFilteredFiles (
153
126
workspaceDirectory ,
154
127
_options . ProjectConfigurationFileName ,
155
128
s_ignoredDirectories ,
156
- logger : _logger ) ;
129
+ logger : _logger ) . ToImmutableArray ( ) ;
157
130
}
158
131
159
- private void FileSystemWatcher_ProjectConfigurationFileEvent_Background ( string physicalFilePath , RazorFileChangeKind kind )
132
+ private void NotifyListeners ( string physicalFilePath , RazorFileChangeKind kind )
160
133
{
161
- _ = _dispatcher . RunAsync (
162
- ( ) => FileSystemWatcher_ProjectConfigurationFileEvent ( physicalFilePath , kind ) ,
163
- CancellationToken . None ) ;
134
+ NotifyListeners ( new ( physicalFilePath , kind ) ) ;
164
135
}
165
136
166
- private void FileSystemWatcher_ProjectConfigurationFileEvent ( string physicalFilePath , RazorFileChangeKind kind )
137
+ private void NotifyListeners ( ProjectConfigurationFileChangeEventArgs args )
167
138
{
168
- var args = new ProjectConfigurationFileChangeEventArgs ( physicalFilePath , kind ) ;
169
139
foreach ( var listener in _listeners )
170
140
{
171
- _logger . LogDebug ( $ "Notifying listener '{ listener } ' of config file path '{ physicalFilePath } ' change with kind '{ kind } '") ;
141
+ _logger . LogDebug ( $ "Notifying listener '{ listener } ' of config file path '{ args . ConfigurationFilePath } ' change with kind '{ args . Kind } '") ;
172
142
listener . ProjectConfigurationFileChanged ( args ) ;
173
143
}
174
144
}
0 commit comments