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). @@ -132,11 +133,12 @@ SymbolKindBitset SupportedSymbolKinds; /// The supported completion item kinds of the client. CompletionItemKindBitset SupportedCompletionItemKinds; - // Whether the client supports CodeAction response objects. + /// Whether the client supports CodeAction response objects. bool SupportsCodeAction = false; /// From capabilities of textDocument/documentSymbol. bool SupportsHierarchicalDocumentSymbol = false; - + /// Whether the client supports showing file status. + bool SupportFileStatus = false; // Store of the current versions of the open documents. DraftStore DraftMgr; Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -294,7 +294,7 @@ SupportsCodeAction = Params.capabilities.CodeActionStructure; SupportsHierarchicalDocumentSymbol = Params.capabilities.HierarchicalDocumentSymbol; - + SupportFileStatus = Params.initializationOptions.FileStatus; Reply(json::Object{ {{"capabilities", json::Object{ @@ -802,6 +802,19 @@ }); } +void ClangdLSPServer::onFileUpdated(PathRef File, const TUStatus &Status) { + if (!SupportFileStatus) + return; + // FIXME: we don't emit "BuildingFile" and `RunningAction`, as these + // two statuses are running faster in practice, which leads the UI constantly + // changing, and doesn't provide much value. We may want to emit status at a + // reasonable time interval (e.g. 0.5s). + if (Status.Action.S == TUAction::BuildingFile || + Status.Action.S == TUAction::RunningAction) + return; + 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 @@ -397,6 +397,9 @@ // the compilation database doesn't describe an opened file. // The command used will be approximately `clang $FILE $fallbackFlags`. std::vector fallbackFlags; + + /// Clients supports show file status for textDocument/clangd.fileStatus. + bool FileStatus = false; }; bool fromJSON(const llvm::json::Value &, InitializationOptions &); @@ -973,6 +976,18 @@ }; bool fromJSON(const llvm::json::Value &, ReferenceParams &); +/// Clangd extension: indicates the current state of the file in clangd, +/// sent from server via the `textDocument/clangd.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; + // FIXME: add detail messages. +}; +llvm::json::Value toJSON(const FileStatus &FStatus); + } // namespace clangd } // namespace clang Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -716,6 +716,12 @@ }; } +llvm::json::Value toJSON(const FileStatus &FStatus) { + return json::Object{ + {"uri", FStatus.uri}, {"state", FStatus.state}, + }; +} + raw_ostream &operator<<(raw_ostream &O, const DocumentHighlight &V) { O << V.range; if (V.kind == DocumentHighlightKind::Read) @@ -752,6 +758,7 @@ fromJSON(Params, Opts.ConfigSettings); O.map("compilationDatabasePath", Opts.compilationDatabasePath); O.map("fallbackFlags", Opts.fallbackFlags); + O.map("clangdFileStatus", Opts.FileStatus); return true; } 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,33 @@ 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 << "file is queued"; + break; + case TUAction::RunningAction: + OS << "running " << 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 +768,13 @@ return HardwareConcurrency; } +FileStatus TUStatus::render(PathRef File) const { + FileStatus FStatus; + FStatus.uri = URIForFile::canonicalize(File, /*TUPath=*/File); + FStatus.state = renderTUAction(Action); + return FStatus; +} + struct TUScheduler::FileData { /// Latest inputs, passed to TUScheduler::update(). std::string Contents; Index: test/clangd/filestatus.test =================================================================== --- test/clangd/filestatus.test +++ test/clangd/filestatus.test @@ -0,0 +1,13 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"initializationOptions":{"clangdFileStatus": true},"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: "state": "parsing includes", +# CHECK-NEXT: "uri": "{{.*}}/main.cpp" +# CHECK-NEXT: } +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"}