Skip to content

Commit 875ca01

Browse files
committed
close file watchers on renames for linux based machines, add the force flag for failed providers
1 parent 7ba01f8 commit 875ca01

File tree

2 files changed

+84
-47
lines changed

2 files changed

+84
-47
lines changed

Extension/src/LanguageServer/client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2039,9 +2039,9 @@ export class DefaultClient implements Client {
20392039
const provideConfigurationAsync: () => Thenable<SourceFileConfigurationItem[] | undefined> = async () => {
20402040
try {
20412041
if (!await provider.canProvideConfiguration(docUri, tokenSource.token)) {
2042-
const forceCheck: boolean = true;
2043-
this.configuration.checkCompileCommands(forceCheck);
2044-
console.log("provideCustomConfigurationAsync(): Provider cannot provide configuration for: ", docUri.path);
2042+
// some file cannot be provided by this provider
2043+
// e.g file not included in a CMake project
2044+
this.configuration.configurationProviderFailed();
20452045
return [];
20462046
}
20472047
} catch (err) {

Extension/src/LanguageServer/configurations.ts

Lines changed: 81 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,10 @@ export class CppProperties {
136136
private currentConfigurationIndex: PersistentFolderState<number> | undefined;
137137
private configFileWatcher: vscode.FileSystemWatcher | null = null;
138138
private configFileWatcherFallbackTime: Date = new Date(); // Used when file watching fails.
139-
private compileCommandsFile: vscode.Uri | undefined | null = undefined;
139+
private compileCommandsFile: string | undefined = undefined;
140140
private compileCommandsFileWatcher: fs.FSWatcher | undefined = undefined;
141141
private compileCommandsFileWatcherFallbackTime: Date = new Date(); // Used when file watching fails.
142+
private configurationProviderFailedToProvide: boolean = false;
142143
private defaultCompilerPath: string | null = null;
143144
private knownCompilers?: KnownCompiler[];
144145
private defaultCStandard: string | null = null;
@@ -319,8 +320,7 @@ export class CppProperties {
319320
void this.handleSquiggles().catch(logAndReturn.undefined);
320321
}
321322

322-
private onCompileCommandsChanged(path: string, caller: string): void {
323-
console.log('onCompileCommandsChanged ', path, caller);
323+
private onCompileCommandsChanged(path: string): void {
324324
this.compileCommandsChanged.fire(path);
325325
}
326326

@@ -1107,7 +1107,7 @@ export class CppProperties {
11071107
}
11081108
}
11091109

1110-
1110+
this.configurationProviderFailedToProvide = false;
11111111
this.updateCompileCommandsFileWatcher();
11121112
if (!this.configurationIncomplete) {
11131113
this.onConfigurationsChanged();
@@ -1119,38 +1119,51 @@ export class CppProperties {
11191119
// Dispose existing and loop through cpp and populate with each file (exists or not) as you go.
11201120
// paths are expected to have variables resolved already
11211121
public updateCompileCommandsFileWatcher(): void {
1122+
console.log("updateCompileCommandsFileWatcher called");
11221123
// close the existing watcher if it exists
11231124
this.compileCommandsFileWatcher?.close();
11241125
this.compileCommandsFileWatcher = undefined;
11251126

11261127
// check if the current configuration is using a configuration provider (e.g `CMake Tools`)
11271128
// if so, avoid setting up a `compile_commands.json` file watcher to avoid unnessary parsing
11281129
// by the language server
1129-
if (this.CurrentConfiguration?.configurationProvider) {
1130+
if (this.CurrentConfiguration?.configurationProvider && !this.configurationProviderFailedToProvide) {
1131+
console.log("Skipping compile_commands.json file watcher setup as configuration provider is set");
11301132
return;
11311133
}
11321134

1133-
if (this.configurationJson) {
1134-
const path: string = this.resolvePath(this.CurrentConfiguration?.compileCommands);
1135-
try {
1136-
this.compileCommandsFileWatcher = fs.watch(path, () => {
1137-
// Wait 1 second after a change to allow time for the write to finish.
1135+
const path: string = this.resolvePath(this.CurrentConfiguration?.compileCommands);
1136+
try {
1137+
this.compileCommandsFileWatcher = fs.watch(path, (eventType: fs.WatchEventType, filename: string | null) => {
1138+
// Wait 1 second after a change to allow time for the write to finish.
1139+
clearInterval(this.compileCommandsFileWatcherTimer);
1140+
this.compileCommandsFileWatcherTimer = setTimeout(() => {
1141+
console.log("file watcher triggered for compile_commands.json");
1142+
this.onCompileCommandsChanged(path);
11381143
clearInterval(this.compileCommandsFileWatcherTimer);
1139-
this.compileCommandsFileWatcherTimer = setTimeout(() => {
1140-
this.onCompileCommandsChanged(path, "file watcher");
1141-
clearInterval(this.compileCommandsFileWatcherTimer);
1142-
this.compileCommandsFileWatcherTimer = undefined;
1143-
}, 1000);
1144-
})
1145-
}
1146-
catch (e: any) {
1147-
// either file not created or too many watchers
1148-
// rely on polling until the file is created
1149-
// then, file watching will be attempted again
1150-
this.compileCommandsFileWatcher?.close();
1151-
this.compileCommandsFileWatcher = undefined;
1152-
}
1144+
this.compileCommandsFileWatcherTimer = undefined;
1145+
1146+
// if the file was deleted/renamed,
1147+
// linux based systems lose track of the file (inode deleted)
1148+
// we need to close the watcher and wait until file is created again
1149+
if (eventType === "rename") {
1150+
console.log("compile_commands.json was renamed or deleted, closing watcher");
1151+
this.compileCommandsFileWatcher?.close();
1152+
this.compileCommandsFileWatcher = undefined;
1153+
this.compileCommandsFile = undefined;
1154+
}
1155+
}, 1000);
1156+
})
1157+
}
1158+
catch (e: any) {
1159+
// either file not created or too many watchers
1160+
// rely on polling until the file is created
1161+
// then, file watching will be attempted again
1162+
console.log("failed to watch compile_commands.json", e);
1163+
this.compileCommandsFileWatcher?.close();
1164+
this.compileCommandsFileWatcher = undefined;
11531165
}
1166+
console.log("file watcher finished");
11541167
}
11551168

11561169
// onBeforeOpen will be called after c_cpp_properties.json have been created (if it did not exist), but before the document is opened.
@@ -2298,48 +2311,72 @@ export class CppProperties {
22982311
});
22992312
}
23002313

2314+
2315+
/**
2316+
* if `configurationProvider` is set, we don't watch for changes in the `compileCommands` file.
2317+
* calling this function means we need to start checking it.
2318+
*/
2319+
public configurationProviderFailed(): void {
2320+
this.configurationProviderFailedToProvide = true;
2321+
}
2322+
23012323
/**
23022324
* Manually check for changes in the compileCommands file.
23032325
*
23042326
* NOTE: The check is skipped on any of the following terms:
23052327
* - There is an active `compile_commands.json` file watcher
2306-
* - The `configurationProvider` property is set and the `force` parameter is not set to true
2328+
* - The `configurationProvider` property is set and `configurationProviderFailed()` was not called
23072329
* - The `compileCommands` property is not set
2308-
* @param bypassConfigurationProvider bypass the `ConfigurationProvider` is set condition
23092330
*/
2310-
public checkCompileCommands(bypassConfigurationProvider: boolean = false): void {
2311-
// if the file watcher is active, we don't need to check here for changes
2312-
if (this.compileCommandsFileWatcher) {
2331+
public checkCompileCommands(): void {
2332+
if (this.compileCommandsFileWatcher !== undefined) {
2333+
console.log("Skipping check for compileCommands file changes, file watcher is active.");
23132334
return;
23142335
}
2315-
// configuration provider didn't fail to provide a configuration
2316-
if (this.CurrentConfiguration?.configurationProvider && !bypassConfigurationProvider) {
2336+
if (this.CurrentConfiguration?.configurationProvider && !this.configurationProviderFailedToProvide) {
2337+
console.log("Skipping check for compileCommands file changes, configuration provider is set and not forced.");
23172338
return;
23182339
}
2319-
// Check for changes in case of file watcher failure.
23202340
const compileCommands: string | undefined = this.CurrentConfiguration?.compileCommands;
23212341
if (compileCommands === undefined) {
2342+
console.log("Skipping check for compileCommands file changes, compileCommands is not set.");
23222343
return;
23232344
}
23242345

2346+
console.log("Manually checking for compileCommands file changes.");
23252347
const compileCommandsFile: string | undefined = this.resolvePath(compileCommands);
2326-
fs.stat(compileCommandsFile, (err, stats) => {
2327-
if (err) {
2328-
if (err.code === "ENOENT" && this.compileCommandsFile) {
2329-
this.onCompileCommandsChanged(compileCommandsFile, "periodic checker - file deleted");
2330-
this.compileCommandsFile = null; // File deleted
2331-
}
2332-
} else if (stats.mtime > this.compileCommandsFileWatcherFallbackTime) {
2348+
try {
2349+
const stats = fs.statSync(compileCommandsFile);
2350+
if (this.compileCommandsFile === undefined || stats.mtime > this.compileCommandsFileWatcherFallbackTime) {
23332351
this.compileCommandsFileWatcherFallbackTime = new Date();
2334-
this.onCompileCommandsChanged(compileCommandsFile, "periodic checker - file created");
2335-
this.compileCommandsFile = vscode.Uri.file(compileCommandsFile); // File created.
2352+
console.log("checkCompileCommands(): compileCommands file changed");
2353+
this.onCompileCommandsChanged(compileCommandsFile);
2354+
this.compileCommandsFile = compileCommandsFile; // File created.
23362355
}
2337-
});
2356+
}
2357+
catch (err: any) {
2358+
if (err.code === "ENOENT" && this.compileCommandsFile) {
2359+
console.log("checkCompileCommands(): compileCommands file deleted");
2360+
this.onCompileCommandsChanged(compileCommandsFile);
2361+
this.compileCommandsFile = undefined; // File deleted
2362+
}
2363+
}
2364+
console.log("checkCompileCommands(): done");
23382365

2339-
// if the compileCommands file is set (and not using a configuration provider), try to watch it
2340-
if (this.compileCommandsFile) {
2366+
const providerInsufficient: boolean = !this.CurrentConfiguration?.configurationProvider || this.configurationProviderFailedToProvide;
2367+
if (this.compileCommandsFile !== undefined && providerInsufficient) {
2368+
console.log("try to watch compileCommands file");
23412369
this.updateCompileCommandsFileWatcher();
23422370
}
2371+
else {
2372+
// debug which condition was not met
2373+
if (this.compileCommandsFile === undefined) {
2374+
console.log("compileCommandsFile is undefined");
2375+
}
2376+
if (this.CurrentConfiguration?.configurationProvider) {
2377+
console.log("configurationProvider is set");
2378+
}
2379+
}
23432380
}
23442381

23452382
dispose(): void {

0 commit comments

Comments
 (0)