Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -69,7 +69,7 @@ void onGoToDefinition(TextDocumentPositionParams &Params) override; void onSwitchSourceHeader(TextDocumentIdentifier &Params) override; void onDocumentHighlight(TextDocumentPositionParams &Params) override; - void onFileEvent(DidChangeWatchedFilesParams &Params) override; + void onDidChangeWatchedFiles(DidChangeWatchedFilesParams &Params) override; void onCommand(ExecuteCommandParams &Params) override; void onWorkspaceSymbol(WorkspaceSymbolParams &Params) override; void onRename(RenameParams &Parames) override; @@ -78,10 +78,36 @@ std::vector getFixes(StringRef File, const clangd::Diagnostic &D); - /// Forces a reparse of all currently opened files. As a result, this method - /// may be very expensive. This method is normally called when the - /// compilation database is changed. - void reparseOpenedFiles(); + /// Structure used to save a "snapshot" of the compile commands for the open + /// files. This is used to avoid unnecessary re-parses. + /// + /// When the compile commands configuration changes (either we point to a new + /// compile_commands.json, or a compile_commands.json file has changed), we + /// 1. Request the compile commands for all open files, save then in this + /// structure. + /// 2. Clear our in-memory cache of the compile commands. + /// 3. Request the compile commands for all open files again. If a file has + /// different compile commands, we re-process it. + + struct CompileCommandsChangeData { + struct FileNameCompileCommandPair { + FileNameCompileCommandPair( + std::string FileName, + llvm::Optional CompileCommand) + : FileName(std::move(FileName)), + CompileCommand(std::move(CompileCommand)) {} + std::string FileName; + llvm::Optional CompileCommand; + }; + std::vector CompileCommands; + }; + + /// Take a snapshot of the compile commands for the currently open files. + CompileCommandsChangeData compileCommandsChangePre(); + /// Compare the new compile comamnds of all open files with the commands + /// saved in \p Data. If any file has different compile commands than before, + // re-parse/process it. + void compileCommandsChangePost(const CompileCommandsChangeData &Data); JSONOutput &Out; /// Used to indicate that the 'shutdown' request was received from the Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -167,8 +167,26 @@ Server.addDocument(File, *Contents, WantDiags); } -void ClangdLSPServer::onFileEvent(DidChangeWatchedFilesParams &Params) { +void ClangdLSPServer::onDidChangeWatchedFiles( + DidChangeWatchedFilesParams &Params) { Server.onFileEvent(Params); + + // Check if any created/modified/deleted file is called compile_commands.json. + bool CompileCommandsChanged = false; + for (const FileEvent &FE : Params.changes) { + StringRef FileName = llvm::sys::path::filename(FE.uri.file()); + if (FileName == "compile_commands.json") { + CompileCommandsChanged = true; + break; + } + } + + if (CompileCommandsChanged) { + auto CCChangeData = compileCommandsChangePre(); + NonCachedCDB.clear(); + CDB.clear(); + compileCommandsChangePost(CCChangeData); + } } void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) { @@ -405,11 +423,11 @@ // Compilation database change. if (Settings.compilationDatabasePath.hasValue()) { + auto CCChangeData = compileCommandsChangePre(); NonCachedCDB.setCompileCommandsDir( Settings.compilationDatabasePath.getValue()); CDB.clear(); - - reparseOpenedFiles(); + compileCommandsChangePost(CCChangeData); } } @@ -493,8 +511,39 @@ }); } -void ClangdLSPServer::reparseOpenedFiles() { - for (const Path &FilePath : DraftMgr.getActiveFiles()) - Server.addDocument(FilePath, *DraftMgr.getDraft(FilePath), - WantDiagnostics::Auto); +ClangdLSPServer::CompileCommandsChangeData +ClangdLSPServer::compileCommandsChangePre() { + ClangdLSPServer::CompileCommandsChangeData Data; + std::vector OpenFiles = this->DraftMgr.getActiveFiles(); + + for (std::string &File : OpenFiles) { + llvm::Optional Command = + this->CDB.getCompileCommand(File); + + Data.CompileCommands.emplace_back(std::move(File), std::move(Command)); + } + + return Data; +} + +void ClangdLSPServer::compileCommandsChangePost( + const ClangdLSPServer::CompileCommandsChangeData &Data) { + for (auto &Item : Data.CompileCommands) { + llvm::Optional NewCompileCommand = + this->CDB.getCompileCommand(Item.FileName); + const llvm::Optional &OldCompileCommand = + Item.CompileCommand; + + if (NewCompileCommand != OldCompileCommand) { + Server.addDocument(Item.FileName, *DraftMgr.getDraft(Item.FileName), + WantDiagnostics::Auto); + vlog("compileCommandsChangePost: File {0} requires reparse after compile " + "commands change.", + Item.FileName); + } else { + vlog("compileCommandsChangePost: File {0} does not require reparse after " + "compile commands change.", + Item.FileName); + } + } } Index: clangd/GlobalCompilationDatabase.h =================================================================== --- clangd/GlobalCompilationDatabase.h +++ clangd/GlobalCompilationDatabase.h @@ -68,6 +68,9 @@ /// Sets the extra flags that should be added to a file. void setExtraFlagsForFile(PathRef File, std::vector ExtraFlags); + /// Forget the compilation flags cached in this object. + void clear(); + private: tooling::CompilationDatabase *getCDBForFile(PathRef File) const; tooling::CompilationDatabase *getCDBInDirLocked(PathRef File) const; Index: clangd/GlobalCompilationDatabase.cpp =================================================================== --- clangd/GlobalCompilationDatabase.cpp +++ clangd/GlobalCompilationDatabase.cpp @@ -66,6 +66,11 @@ CompilationDatabases.clear(); } +void DirectoryBasedGlobalCompilationDatabase::clear() { + std::lock_guard Lock(Mutex); + CompilationDatabases.clear(); +} + void DirectoryBasedGlobalCompilationDatabase::setExtraFlagsForFile( PathRef File, std::vector ExtraFlags) { std::lock_guard Lock(Mutex); Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -48,7 +48,7 @@ virtual void onSignatureHelp(TextDocumentPositionParams &Params) = 0; virtual void onGoToDefinition(TextDocumentPositionParams &Params) = 0; virtual void onSwitchSourceHeader(TextDocumentIdentifier &Params) = 0; - virtual void onFileEvent(DidChangeWatchedFilesParams &Params) = 0; + virtual void onDidChangeWatchedFiles(DidChangeWatchedFilesParams &Params) = 0; virtual void onCommand(ExecuteCommandParams &Params) = 0; virtual void onWorkspaceSymbol(WorkspaceSymbolParams &Params) = 0; virtual void onRename(RenameParams &Parames) = 0; Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -68,7 +68,8 @@ Register("textDocument/rename", &ProtocolCallbacks::onRename); Register("textDocument/hover", &ProtocolCallbacks::onHover); Register("textDocument/documentSymbol", &ProtocolCallbacks::onDocumentSymbol); - Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); + Register("workspace/didChangeWatchedFiles", + &ProtocolCallbacks::onDidChangeWatchedFiles); Register("workspace/executeCommand", &ProtocolCallbacks::onCommand); Register("textDocument/documentHighlight", &ProtocolCallbacks::onDocumentHighlight); Index: clangd/clients/clangd-vscode/src/extension.ts =================================================================== --- clangd/clients/clangd-vscode/src/extension.ts +++ clangd/clients/clangd-vscode/src/extension.ts @@ -30,14 +30,18 @@ } const serverOptions: vscodelc.ServerOptions = clangd; - const filePattern: string = '**/*.{' + + const sourceFilePattern: string = '**/*.{' + ['cpp', 'c', 'cc', 'cxx', 'c++', 'm', 'mm', 'h', 'hh', 'hpp', 'hxx', 'inc'].join() + '}'; + const compileCommandsFilePattern: string = '**/compile_commands.json'; const clientOptions: vscodelc.LanguageClientOptions = { // Register the server for C/C++ files - documentSelector: [{ scheme: 'file', pattern: filePattern }], + documentSelector: [{ scheme: 'file', pattern: sourceFilePattern }], synchronize: !syncFileEvents ? undefined : { - fileEvents: vscode.workspace.createFileSystemWatcher(filePattern) - }, + fileEvents : [ + vscode.workspace.createFileSystemWatcher(sourceFilePattern), + vscode.workspace.createFileSystemWatcher(compileCommandsFilePattern), + ] + }, // Resolve symlinks for all files provided by clangd. // This is a workaround for a bazel + clangd issue - bazel produces a symlink tree to build in, // and when navigating to the included file, clangd passes its path inside the symlink tree