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, const TUStatus &Status) 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 @@ -802,6 +802,10 @@ }); } +void ClangdLSPServer::onFileUpdated(PathRef File, const TUStatus &Status) { + notify("textDocument/clangd.fileStatus", Status.render(File)); +} + void ClangdLSPServer::reparseOpenedFiles() { for (const Path &FilePath : DraftMgr.getActiveFiles()) Server->addDocument(FilePath, *DraftMgr.getDraft(FilePath), Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -973,6 +973,33 @@ }; bool fromJSON(const llvm::json::Value &, ReferenceParams &); +enum class MessageType { + Error = 1, + Warning = 2, + Info = 3, + Log = 4, +}; +struct ShowMessageParams { + /// The message type. + MessageType type; + /// The actual message. + std::string message; +}; +llvm::json::Value toJSON(const ShowMessageParams &); + +/// Clangd extension: indicates the current state of the file in clangd, +/// sent from server via the `textDocument/fileStatus` notification. +struct FileStatus { + /// The text document's URI. + URIForFile uri; + /// The human-readable string presents the current state of the file, can be + /// shown in the UI (e.g. status bar). + std::string state; + /// Details of the state that are worth sufacing to users. + std::vector details; +}; +llvm::json::Value toJSON(const FileStatus &FStatus); + } // namespace clangd } // namespace clang Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -716,6 +716,20 @@ }; } +llvm::json::Value toJSON(const ShowMessageParams &S) { + return json::Object{ + {"type", static_cast(S.type)}, {"message", S.message}, + }; +} + +llvm::json::Value toJSON(const FileStatus &FStatus) { + return json::Object{ + {"uri", FStatus.uri}, + {"state", FStatus.state}, + {"details", FStatus.details}, + }; +} + raw_ostream &operator<<(raw_ostream &O, const DocumentHighlight &V) { O << V.range; if (V.kind == DocumentHighlightKind::Read) Index: clangd/TUScheduler.h =================================================================== --- clangd/TUScheduler.h +++ clangd/TUScheduler.h @@ -77,6 +77,8 @@ /// Indicates whether we reused the prebuilt AST. bool ReuseAST = false; }; + /// Serialize this to an LSP file status item. + FileStatus render(PathRef File) const; TUAction Action; BuildDetails Details; Index: clangd/TUScheduler.cpp =================================================================== --- clangd/TUScheduler.cpp +++ clangd/TUScheduler.cpp @@ -729,6 +729,34 @@ return wait(Lock, RequestsCV, Timeout, [&] { return Requests.empty(); }); } +// Render a TUAction to a user-facing string representation. +// TUAction represents clangd-internal states, we don't intend to expose them +// to users (say C++ programmers) directly to avoid confusion, we use terms that +// are familiar by C++ programmers. +std::string renderTUAction(const TUAction& Action) { + std::string Result; + raw_string_ostream OS(Result); + switch (Action.S) { + case TUAction::Queued: + OS << "Queued"; + break; + case TUAction::RunningAction: + OS << "RunningAction" + << "(" << Action.Name << ")"; + break; + case TUAction::BuildingPreamble: + OS << "Parsing includes"; + break; + case TUAction::BuildingFile: + OS << "Parsing main file"; + break; + case TUAction::Idle: + OS << "Idle"; + break; + } + return OS.str(); +} + } // namespace unsigned getDefaultAsyncThreadsCount() { @@ -741,6 +769,17 @@ return HardwareConcurrency; } +FileStatus TUStatus::render(PathRef File) const { + FileStatus FStatus; + FStatus.uri = URIForFile::canonicalize(File, /*TUPath=*/File); + FStatus.state = renderTUAction(Action); + if (Details.BuildFailed) + FStatus.details.push_back({MessageType::Error, "failed to build the file"}); + if (Details.ReuseAST) + FStatus.details.push_back({MessageType::Info, "reused the AST"}); + return FStatus; +} + struct TUScheduler::FileData { /// Latest inputs, passed to TUScheduler::update(). std::string Contents; Index: test/clangd/filestatus.test =================================================================== --- /dev/null +++ test/clangd/filestatus.test @@ -0,0 +1,14 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"int x; int y = x;"}}} +# CHECK: "method": "textDocument/clangd.fileStatus", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "details": [], +# CHECK-NEXT: "state": "Parsing includes", +# CHECK-NEXT: "uri": "{{.*}}/main.cpp" +# CHECK-NEXT: } +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"}