Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -52,6 +52,7 @@ private: // Implement DiagnosticsConsumer. void onDiagnosticsReady(PathRef File, std::vector Diagnostics) override; + void onFileUpdated(PathRef File, FileStatus FS) override; // LSP methods. Notifications have signature void(const Params&). // Calls have signature void(const Params&, Callback). Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -781,6 +781,15 @@ }); } + void ClangdLSPServer::onFileUpdated(PathRef File, FileStatus FS) { + URIForFile URI(File); + notify("window/showMessage", + json::Object { + {"uri", URI}, + {"message", FS.Message}, + }); + } + void ClangdLSPServer::reparseOpenedFiles() { for (const Path &FilePath : DraftMgr.getActiveFiles()) Server->addDocument(FilePath, *DraftMgr.getDraft(FilePath), Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -43,6 +43,9 @@ /// Called by ClangdServer when \p Diagnostics for \p File are ready. virtual void onDiagnosticsReady(PathRef File, std::vector Diagnostics) = 0; + + /// Called by ClangdServer when the status of file is updated. + virtual void onFileUpdated(PathRef File, FileStatus Status) = 0; }; /// Manages a collection of source files and derived data (ASTs, indexes), @@ -229,6 +232,7 @@ typedef uint64_t DocVersion; void consumeDiagnostics(PathRef File, DocVersion Version, + FileStatus FS, std::vector Diags); tooling::CompileCommand getCompileCommand(PathRef File); Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -135,14 +135,19 @@ void ClangdServer::addDocument(PathRef File, StringRef Contents, WantDiagnostics WantDiags) { DocVersion Version = ++InternalVersion[File]; + // FIXME: notify clients if we are using fallback compilation command. + FileStatus FS{FileStatus::State::Preparing, + "Getting compilation command..."}; + consumeDiagnostics(File, Version, FS, {}); ParseInputs Inputs = {getCompileCommand(File), FSProvider.getFileSystem(), Contents.str()}; Path FileStr = File.str(); - WorkScheduler.update(File, std::move(Inputs), WantDiags, - [this, FileStr, Version](std::vector Diags) { - consumeDiagnostics(FileStr, Version, std::move(Diags)); - }); + WorkScheduler.update( + File, std::move(Inputs), WantDiags, + [this, FileStr, Version](FileStatus FS, std::vector Diags) { + consumeDiagnostics(FileStr, Version, FS, std::move(Diags)); + }); } void ClangdServer::removeDocument(PathRef File) { @@ -445,8 +450,9 @@ WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB))); } +// FIXME: this is a **hacky** way to emit FileStatus to client, revisit it. void ClangdServer::consumeDiagnostics(PathRef File, DocVersion Version, - std::vector Diags) { + FileStatus FS, std::vector Diags) { // We need to serialize access to resulting diagnostics to avoid calling // `onDiagnosticsReady` in the wrong order. std::lock_guard DiagsLock(DiagnosticsMutex); @@ -460,7 +466,10 @@ return; LastReportedDiagsVersion = Version; - DiagConsumer.onDiagnosticsReady(File, std::move(Diags)); + if (FS.S == FileStatus::State::Ready) + DiagConsumer.onDiagnosticsReady(File, std::move(Diags)); + + DiagConsumer.onFileUpdated(File, FS); } tooling::CompileCommand ClangdServer::getCompileCommand(PathRef File) { Index: clangd/TUScheduler.h =================================================================== --- clangd/TUScheduler.h +++ clangd/TUScheduler.h @@ -52,6 +52,27 @@ unsigned MaxRetainedASTs = 3; }; +/// Indicates the current status of the file in ClangdServer. +/// FIXME: should we add error code (e.g. severity)? +struct FileStatus { + enum class State { + Unknown, + Preparing, // Build system is preparing for the file. + Wait, // The file is waiting in the queue to be processed. + Building, // The file is being built. + Ready, // The file is ready. + + // FIXME: others? + }; + + + State S; + // A detailed message of the status, which can be shown in the status bar. + // This can contain details, e.g. the compile command used to build the file; + // whether the preamble/AST is reused. + std::string Message; +}; + class ParsingCallbacks { public: virtual ~ParsingCallbacks() = default; @@ -101,8 +122,9 @@ /// Schedule an update for \p File. Adds \p File to a list of tracked files if /// \p File was not part of it before. /// FIXME(ibiryukov): remove the callback from this function. - void update(PathRef File, ParseInputs Inputs, WantDiagnostics WD, - llvm::unique_function)> OnUpdated); + void update( + PathRef File, ParseInputs Inputs, WantDiagnostics WD, + llvm::unique_function)> OnUpdated); /// Remove \p File from the list of tracked files and schedule removal of its /// resources. Index: clangd/TUScheduler.cpp =================================================================== --- clangd/TUScheduler.cpp +++ clangd/TUScheduler.cpp @@ -176,7 +176,7 @@ ~ASTWorker(); void update(ParseInputs Inputs, WantDiagnostics, - llvm::unique_function)> OnUpdated); + llvm::unique_function)> OnUpdated); void runWithAST(StringRef Name, unique_function)> Action); bool blockUntilIdle(Deadline Timeout) const; @@ -333,8 +333,9 @@ } void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags, - unique_function)> OnUpdated) { + unique_function)> OnUpdated) { auto Task = [=](decltype(OnUpdated) OnUpdated) mutable { + OnUpdated(FileStatus{FileStatus::State::Building, "Building"}, {}); // Will be used to check if we can avoid rebuilding the AST. bool InputsAreTheSame = std::tie(FileInputs.CompileCommand, FileInputs.Contents) == @@ -397,6 +398,7 @@ // current file at this point? log("Skipping rebuild of the AST for {0}, inputs are the same.", FileName); + OnUpdated({FileStatus::State::Ready, "Ready"}, {}); return; } } @@ -417,7 +419,7 @@ // spam us with updates. // Note *AST can still be null if buildAST fails. if (*AST) { - OnUpdated((*AST)->getDiagnostics()); + OnUpdated({FileStatus::State::Ready, "Ready"}, (*AST)->getDiagnostics()); trace::Span Span("Running main AST callback"); Callbacks.onMainAST(FileName, **AST); DiagsWereReported = true; @@ -425,7 +427,7 @@ // Stash the AST in the cache for further use. IdleASTs.put(this, std::move(*AST)); }; - + OnUpdated({FileStatus::State::Wait, "Wait"}, {}); startTask("Update", Bind(Task, std::move(OnUpdated)), WantDiags); } @@ -697,7 +699,7 @@ void TUScheduler::update(PathRef File, ParseInputs Inputs, WantDiagnostics WantDiags, - unique_function)> OnUpdated) { + unique_function)> OnUpdated) { std::unique_ptr &FD = Files[File]; if (!FD) { // Create a new worker to process the AST-related tasks.