forked from OSchip/llvm-project
[clangd] Refactor ProtocolHandlers to decouple them from ClangdLSPServer
Summary: A refactoring to decouple ProtocolHandlers and Language Server input parsing loop from the ClangdLSPServer. The input parsing was extracted from `main` to a function(runLanguageServerLoop). ProtocolHandlers now provide an interface to handle various LSP methods, this interface is used by ClangdLSPServer. Methods for code formatting were moved from ProtocolHandlers to ClangdServer. ClangdLSPServer now provides a cleaner interface that only runs Language Server input loop. Reviewers: bkramer, krasimir Reviewed By: krasimir Subscribers: cfe-commits, klimek Tags: #clang-tools-extra Differential Revision: https://reviews.llvm.org/D33201 llvm-svn: 303173
This commit is contained in:
parent
23453c11ff
commit
afb555473e
|
@ -9,10 +9,35 @@
|
|||
|
||||
#include "ClangdLSPServer.h"
|
||||
#include "JSONRPCDispatcher.h"
|
||||
#include "ProtocolHandlers.h"
|
||||
|
||||
using namespace clang::clangd;
|
||||
using namespace clang;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string
|
||||
replacementsToEdits(StringRef Code,
|
||||
const std::vector<tooling::Replacement> &Replacements) {
|
||||
// Turn the replacements into the format specified by the Language Server
|
||||
// Protocol. Fuse them into one big JSON array.
|
||||
std::string Edits;
|
||||
for (auto &R : Replacements) {
|
||||
Range ReplacementRange = {
|
||||
offsetToPosition(Code, R.getOffset()),
|
||||
offsetToPosition(Code, R.getOffset() + R.getLength())};
|
||||
TextEdit TE = {ReplacementRange, R.getReplacementText()};
|
||||
Edits += TextEdit::unparse(TE);
|
||||
Edits += ',';
|
||||
}
|
||||
if (!Edits.empty())
|
||||
Edits.pop_back();
|
||||
|
||||
return Edits;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ClangdLSPServer::LSPDiagnosticsConsumer : public DiagnosticsConsumer {
|
||||
public:
|
||||
LSPDiagnosticsConsumer(ClangdLSPServer &Server) : Server(Server) {}
|
||||
|
@ -26,23 +51,170 @@ private:
|
|||
ClangdLSPServer &Server;
|
||||
};
|
||||
|
||||
class ClangdLSPServer::LSPProtocolCallbacks : public ProtocolCallbacks {
|
||||
public:
|
||||
LSPProtocolCallbacks(ClangdLSPServer &LangServer) : LangServer(LangServer) {}
|
||||
|
||||
void onInitialize(StringRef ID, JSONOutput &Out) override;
|
||||
void onShutdown(JSONOutput &Out) override;
|
||||
void onDocumentDidOpen(DidOpenTextDocumentParams Params,
|
||||
JSONOutput &Out) override;
|
||||
void onDocumentDidChange(DidChangeTextDocumentParams Params,
|
||||
JSONOutput &Out) override;
|
||||
void onDocumentDidClose(DidCloseTextDocumentParams Params,
|
||||
JSONOutput &Out) override;
|
||||
void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params,
|
||||
StringRef ID, JSONOutput &Out) override;
|
||||
void onDocumentRangeFormatting(DocumentRangeFormattingParams Params,
|
||||
StringRef ID, JSONOutput &Out) override;
|
||||
void onDocumentFormatting(DocumentFormattingParams Params, StringRef ID,
|
||||
JSONOutput &Out) override;
|
||||
void onCodeAction(CodeActionParams Params, StringRef ID,
|
||||
JSONOutput &Out) override;
|
||||
void onCompletion(TextDocumentPositionParams Params, StringRef ID,
|
||||
JSONOutput &Out) override;
|
||||
|
||||
private:
|
||||
ClangdLSPServer &LangServer;
|
||||
};
|
||||
|
||||
void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID,
|
||||
JSONOutput &Out) {
|
||||
Out.writeMessage(
|
||||
R"({"jsonrpc":"2.0","id":)" + ID +
|
||||
R"(,"result":{"capabilities":{
|
||||
"textDocumentSync": 1,
|
||||
"documentFormattingProvider": true,
|
||||
"documentRangeFormattingProvider": true,
|
||||
"documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
|
||||
"codeActionProvider": true,
|
||||
"completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
|
||||
}}})");
|
||||
}
|
||||
|
||||
void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) {
|
||||
LangServer.IsDone = true;
|
||||
}
|
||||
|
||||
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidOpen(
|
||||
DidOpenTextDocumentParams Params, JSONOutput &Out) {
|
||||
LangServer.Server.addDocument(Params.textDocument.uri.file,
|
||||
Params.textDocument.text);
|
||||
}
|
||||
|
||||
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange(
|
||||
DidChangeTextDocumentParams Params, JSONOutput &Out) {
|
||||
// We only support full syncing right now.
|
||||
LangServer.Server.addDocument(Params.textDocument.uri.file,
|
||||
Params.contentChanges[0].text);
|
||||
}
|
||||
|
||||
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose(
|
||||
DidCloseTextDocumentParams Params, JSONOutput &Out) {
|
||||
LangServer.Server.removeDocument(Params.textDocument.uri.file);
|
||||
}
|
||||
|
||||
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting(
|
||||
DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
|
||||
auto File = Params.textDocument.uri.file;
|
||||
std::string Code = LangServer.Server.getDocument(File);
|
||||
std::string Edits = replacementsToEdits(
|
||||
Code, LangServer.Server.formatOnType(File, Params.position));
|
||||
|
||||
Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
|
||||
R"(,"result":[)" + Edits + R"(]})");
|
||||
}
|
||||
|
||||
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting(
|
||||
DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
|
||||
auto File = Params.textDocument.uri.file;
|
||||
std::string Code = LangServer.Server.getDocument(File);
|
||||
std::string Edits = replacementsToEdits(
|
||||
Code, LangServer.Server.formatRange(File, Params.range));
|
||||
|
||||
Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
|
||||
R"(,"result":[)" + Edits + R"(]})");
|
||||
}
|
||||
|
||||
void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting(
|
||||
DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) {
|
||||
auto File = Params.textDocument.uri.file;
|
||||
std::string Code = LangServer.Server.getDocument(File);
|
||||
std::string Edits =
|
||||
replacementsToEdits(Code, LangServer.Server.formatFile(File));
|
||||
|
||||
Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
|
||||
R"(,"result":[)" + Edits + R"(]})");
|
||||
}
|
||||
|
||||
void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction(
|
||||
CodeActionParams Params, StringRef ID, JSONOutput &Out) {
|
||||
// We provide a code action for each diagnostic at the requested location
|
||||
// which has FixIts available.
|
||||
std::string Code =
|
||||
LangServer.Server.getDocument(Params.textDocument.uri.file);
|
||||
std::string Commands;
|
||||
for (Diagnostic &D : Params.context.diagnostics) {
|
||||
std::vector<clang::tooling::Replacement> Fixes =
|
||||
LangServer.getFixIts(Params.textDocument.uri.file, D);
|
||||
std::string Edits = replacementsToEdits(Code, Fixes);
|
||||
|
||||
if (!Edits.empty())
|
||||
Commands +=
|
||||
R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
|
||||
R"('", "command": "clangd.applyFix", "arguments": [")" +
|
||||
llvm::yaml::escape(Params.textDocument.uri.uri) +
|
||||
R"(", [)" + Edits +
|
||||
R"(]]},)";
|
||||
}
|
||||
if (!Commands.empty())
|
||||
Commands.pop_back();
|
||||
|
||||
Out.writeMessage(
|
||||
R"({"jsonrpc":"2.0","id":)" + ID.str() +
|
||||
R"(, "result": [)" + Commands +
|
||||
R"(]})");
|
||||
}
|
||||
|
||||
void ClangdLSPServer::LSPProtocolCallbacks::onCompletion(
|
||||
TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
|
||||
|
||||
auto Items = LangServer.Server.codeComplete(
|
||||
Params.textDocument.uri.file,
|
||||
Position{Params.position.line, Params.position.character});
|
||||
|
||||
std::string Completions;
|
||||
for (const auto &Item : Items) {
|
||||
Completions += CompletionItem::unparse(Item);
|
||||
Completions += ",";
|
||||
}
|
||||
if (!Completions.empty())
|
||||
Completions.pop_back();
|
||||
Out.writeMessage(
|
||||
R"({"jsonrpc":"2.0","id":)" + ID.str() +
|
||||
R"(,"result":[)" + Completions + R"(]})");
|
||||
}
|
||||
|
||||
ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously)
|
||||
: Out(Out),
|
||||
Server(llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(),
|
||||
llvm::make_unique<LSPDiagnosticsConsumer>(*this),
|
||||
RunSynchronously) {}
|
||||
|
||||
void ClangdLSPServer::openDocument(StringRef File, StringRef Contents) {
|
||||
Server.addDocument(File, Contents);
|
||||
}
|
||||
void ClangdLSPServer::run(std::istream &In) {
|
||||
assert(!IsDone && "Run was called before");
|
||||
|
||||
void ClangdLSPServer::closeDocument(StringRef File) {
|
||||
Server.removeDocument(File);
|
||||
}
|
||||
// Set up JSONRPCDispatcher.
|
||||
LSPProtocolCallbacks Callbacks(*this);
|
||||
JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
|
||||
regiterCallbackHandlers(Dispatcher, Out, Callbacks);
|
||||
|
||||
std::vector<CompletionItem> ClangdLSPServer::codeComplete(PathRef File,
|
||||
Position Pos) {
|
||||
return Server.codeComplete(File, Pos);
|
||||
// Run the Language Server loop.
|
||||
runLanguageServerLoop(In, Out, Dispatcher, IsDone);
|
||||
|
||||
// Make sure IsDone is set to true after this method exits to ensure assertion
|
||||
// at the start of the method fires if it's ever executed again.
|
||||
IsDone = true;
|
||||
}
|
||||
|
||||
std::vector<clang::tooling::Replacement>
|
||||
|
@ -60,10 +232,6 @@ ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
|
|||
return FixItsIter->second;
|
||||
}
|
||||
|
||||
std::string ClangdLSPServer::getDocument(PathRef File) {
|
||||
return Server.getDocument(File);
|
||||
}
|
||||
|
||||
void ClangdLSPServer::consumeDiagnostics(
|
||||
PathRef File, std::vector<DiagWithFixIts> Diagnostics) {
|
||||
std::string DiagnosticsJSON;
|
||||
|
|
|
@ -20,43 +20,25 @@ namespace clangd {
|
|||
|
||||
class JSONOutput;
|
||||
|
||||
/// This class serves as an intermediate layer of LSP server implementation,
|
||||
/// glueing the JSON LSP protocol layer and ClangdServer together. It doesn't
|
||||
/// directly handle input from LSP client.
|
||||
/// Most methods are synchronous and return their result directly, but
|
||||
/// diagnostics are provided asynchronously when ready via
|
||||
/// JSONOutput::writeMessage.
|
||||
/// This class provides implementation of an LSP server, glueing the JSON
|
||||
/// dispatch and ClangdServer together.
|
||||
class ClangdLSPServer {
|
||||
public:
|
||||
ClangdLSPServer(JSONOutput &Out, bool RunSynchronously);
|
||||
|
||||
/// Update the document text for \p File with \p Contents, schedule update of
|
||||
/// diagnostics. Out.writeMessage will called to push diagnostics to LSP
|
||||
/// client asynchronously when they are ready.
|
||||
void openDocument(PathRef File, StringRef Contents);
|
||||
/// Stop tracking the document for \p File.
|
||||
void closeDocument(PathRef File);
|
||||
|
||||
/// Run code completion synchronously.
|
||||
std::vector<CompletionItem> codeComplete(PathRef File, Position Pos);
|
||||
|
||||
/// Get the fixes associated with a certain diagnostic in a specified file as
|
||||
/// replacements.
|
||||
///
|
||||
/// This function is thread-safe. It returns a copy to avoid handing out
|
||||
/// references to unguarded data.
|
||||
std::vector<clang::tooling::Replacement>
|
||||
getFixIts(StringRef File, const clangd::Diagnostic &D);
|
||||
|
||||
/// Get the current document contents stored for \p File.
|
||||
/// FIXME(ibiryukov): This function is here to allow implementation of
|
||||
/// formatCode from ProtocolHandlers.cpp. We should move formatCode to
|
||||
/// ClangdServer class and remove this function from public interface.
|
||||
std::string getDocument(PathRef File);
|
||||
/// Run LSP server loop, receiving input for it from \p In. \p In must be
|
||||
/// opened in binary mode. Output will be written using Out variable passed to
|
||||
/// class constructor. This method must not be executed more than once for
|
||||
/// each instance of ClangdLSPServer.
|
||||
void run(std::istream &In);
|
||||
|
||||
private:
|
||||
class LSPProtocolCallbacks;
|
||||
class LSPDiagnosticsConsumer;
|
||||
|
||||
std::vector<clang::tooling::Replacement>
|
||||
getFixIts(StringRef File, const clangd::Diagnostic &D);
|
||||
|
||||
/// Function that will be called on a separate thread when diagnostics are
|
||||
/// ready. Sends the Dianostics to LSP client via Out.writeMessage and caches
|
||||
/// corresponding fixits in the FixItsMap.
|
||||
|
@ -64,6 +46,10 @@ private:
|
|||
std::vector<DiagWithFixIts> Diagnostics);
|
||||
|
||||
JSONOutput &Out;
|
||||
/// Used to indicate that the 'shutdown' request was received from the
|
||||
/// Language Server client.
|
||||
/// It's used to break out of the LSP parsing loop.
|
||||
bool IsDone = false;
|
||||
|
||||
std::mutex FixItsMutex;
|
||||
typedef std::map<clangd::Diagnostic, std::vector<clang::tooling::Replacement>>
|
||||
|
|
|
@ -7,10 +7,8 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "JSONRPCDispatcher.h"
|
||||
#include "ClangdLSPServer.h"
|
||||
#include "Protocol.h"
|
||||
#include "ProtocolHandlers.h"
|
||||
#include "JSONRPCDispatcher.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/Program.h"
|
||||
|
@ -29,6 +27,7 @@ static llvm::cl::opt<bool>
|
|||
|
||||
int main(int argc, char *argv[]) {
|
||||
llvm::cl::ParseCommandLineOptions(argc, argv, "clangd");
|
||||
|
||||
llvm::raw_ostream &Outs = llvm::outs();
|
||||
llvm::raw_ostream &Logs = llvm::errs();
|
||||
JSONOutput Out(Outs, Logs);
|
||||
|
@ -36,89 +35,6 @@ int main(int argc, char *argv[]) {
|
|||
// Change stdin to binary to not lose \r\n on windows.
|
||||
llvm::sys::ChangeStdinToBinary();
|
||||
|
||||
// Set up a document store and intialize all the method handlers for JSONRPC
|
||||
// dispatching.
|
||||
ClangdLSPServer LSPServer(Out, RunSynchronously);
|
||||
JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
|
||||
Dispatcher.registerHandler("initialize",
|
||||
llvm::make_unique<InitializeHandler>(Out));
|
||||
auto ShutdownPtr = llvm::make_unique<ShutdownHandler>(Out);
|
||||
auto *ShutdownHandler = ShutdownPtr.get();
|
||||
Dispatcher.registerHandler("shutdown", std::move(ShutdownPtr));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/didOpen",
|
||||
llvm::make_unique<TextDocumentDidOpenHandler>(Out, LSPServer));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/didClose",
|
||||
llvm::make_unique<TextDocumentDidCloseHandler>(Out, LSPServer));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/didChange",
|
||||
llvm::make_unique<TextDocumentDidChangeHandler>(Out, LSPServer));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/rangeFormatting",
|
||||
llvm::make_unique<TextDocumentRangeFormattingHandler>(Out, LSPServer));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/onTypeFormatting",
|
||||
llvm::make_unique<TextDocumentOnTypeFormattingHandler>(Out, LSPServer));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/formatting",
|
||||
llvm::make_unique<TextDocumentFormattingHandler>(Out, LSPServer));
|
||||
Dispatcher.registerHandler("textDocument/codeAction",
|
||||
llvm::make_unique<CodeActionHandler>(Out, LSPServer));
|
||||
Dispatcher.registerHandler("textDocument/completion",
|
||||
llvm::make_unique<CompletionHandler>(Out, LSPServer));
|
||||
|
||||
while (std::cin.good()) {
|
||||
// A Language Server Protocol message starts with a HTTP header, delimited
|
||||
// by \r\n.
|
||||
std::string Line;
|
||||
std::getline(std::cin, Line);
|
||||
if (!std::cin.good() && errno == EINTR) {
|
||||
std::cin.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip empty lines.
|
||||
llvm::StringRef LineRef(Line);
|
||||
if (LineRef.trim().empty())
|
||||
continue;
|
||||
|
||||
// We allow YAML-style comments. Technically this isn't part of the
|
||||
// LSP specification, but makes writing tests easier.
|
||||
if (LineRef.startswith("#"))
|
||||
continue;
|
||||
|
||||
unsigned long long Len = 0;
|
||||
// FIXME: Content-Type is a specified header, but does nothing.
|
||||
// Content-Length is a mandatory header. It specifies the length of the
|
||||
// following JSON.
|
||||
if (LineRef.consume_front("Content-Length: "))
|
||||
llvm::getAsUnsignedInteger(LineRef.trim(), 0, Len);
|
||||
|
||||
// Check if the next line only contains \r\n. If not this is another header,
|
||||
// which we ignore.
|
||||
char NewlineBuf[2];
|
||||
std::cin.read(NewlineBuf, 2);
|
||||
if (std::memcmp(NewlineBuf, "\r\n", 2) != 0)
|
||||
continue;
|
||||
|
||||
// Now read the JSON. Insert a trailing null byte as required by the YAML
|
||||
// parser.
|
||||
std::vector<char> JSON(Len + 1, '\0');
|
||||
std::cin.read(JSON.data(), Len);
|
||||
|
||||
if (Len > 0) {
|
||||
llvm::StringRef JSONRef(JSON.data(), Len);
|
||||
// Log the message.
|
||||
Out.log("<-- " + JSONRef + "\n");
|
||||
|
||||
// Finally, execute the action for this JSON message.
|
||||
if (!Dispatcher.call(JSONRef))
|
||||
Out.log("JSON dispatch failed!\n");
|
||||
|
||||
// If we're done, exit the loop.
|
||||
if (ShutdownHandler->isDone())
|
||||
break;
|
||||
}
|
||||
}
|
||||
LSPServer.run(std::cin);
|
||||
}
|
||||
|
|
|
@ -8,15 +8,54 @@
|
|||
//===-------------------------------------------------------------------===//
|
||||
|
||||
#include "ClangdServer.h"
|
||||
#include "clang/Format/Format.h"
|
||||
#include "clang/Frontend/ASTUnit.h"
|
||||
#include "clang/Frontend/CompilerInstance.h"
|
||||
#include "clang/Frontend/CompilerInvocation.h"
|
||||
#include "clang/Tooling/CompilationDatabase.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
|
||||
using namespace clang;
|
||||
using namespace clang::clangd;
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<tooling::Replacement> formatCode(StringRef Code, StringRef Filename,
|
||||
ArrayRef<tooling::Range> Ranges) {
|
||||
// Call clang-format.
|
||||
// FIXME: Don't ignore style.
|
||||
format::FormatStyle Style = format::getLLVMStyle();
|
||||
auto Result = format::reformat(Style, Code, Ranges, Filename);
|
||||
|
||||
return std::vector<tooling::Replacement>(Result.begin(), Result.end());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
size_t clangd::positionToOffset(StringRef Code, Position P) {
|
||||
size_t Offset = 0;
|
||||
for (int I = 0; I != P.line; ++I) {
|
||||
// FIXME: \r\n
|
||||
// FIXME: UTF-8
|
||||
size_t F = Code.find('\n', Offset);
|
||||
if (F == StringRef::npos)
|
||||
return 0; // FIXME: Is this reasonable?
|
||||
Offset = F + 1;
|
||||
}
|
||||
return (Offset == 0 ? 0 : (Offset - 1)) + P.character;
|
||||
}
|
||||
|
||||
/// Turn an offset in Code into a [line, column] pair.
|
||||
Position clangd::offsetToPosition(StringRef Code, size_t Offset) {
|
||||
StringRef JustBefore = Code.substr(0, Offset);
|
||||
// FIXME: \r\n
|
||||
// FIXME: UTF-8
|
||||
int Lines = JustBefore.count('\n');
|
||||
int Cols = JustBefore.size() - JustBefore.rfind('\n') - 1;
|
||||
return {Lines, Cols};
|
||||
}
|
||||
|
||||
WorkerRequest::WorkerRequest(WorkerRequestKind Kind, Path File,
|
||||
DocVersion Version)
|
||||
: Kind(Kind), File(File), Version(Version) {}
|
||||
|
@ -117,6 +156,34 @@ std::vector<CompletionItem> ClangdServer::codeComplete(PathRef File,
|
|||
});
|
||||
return Result;
|
||||
}
|
||||
std::vector<tooling::Replacement> ClangdServer::formatRange(PathRef File,
|
||||
Range Rng) {
|
||||
std::string Code = getDocument(File);
|
||||
|
||||
size_t Begin = positionToOffset(Code, Rng.start);
|
||||
size_t Len = positionToOffset(Code, Rng.end) - Begin;
|
||||
return formatCode(Code, File, {tooling::Range(Begin, Len)});
|
||||
}
|
||||
|
||||
std::vector<tooling::Replacement> ClangdServer::formatFile(PathRef File) {
|
||||
// Format everything.
|
||||
std::string Code = getDocument(File);
|
||||
return formatCode(Code, File, {tooling::Range(0, Code.size())});
|
||||
}
|
||||
|
||||
std::vector<tooling::Replacement> ClangdServer::formatOnType(PathRef File,
|
||||
Position Pos) {
|
||||
// Look for the previous opening brace from the character position and
|
||||
// format starting from there.
|
||||
std::string Code = getDocument(File);
|
||||
size_t CursorPos = positionToOffset(Code, Pos);
|
||||
size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
|
||||
if (PreviousLBracePos == StringRef::npos)
|
||||
PreviousLBracePos = CursorPos;
|
||||
size_t Len = 1 + CursorPos - PreviousLBracePos;
|
||||
|
||||
return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)});
|
||||
}
|
||||
|
||||
std::string ClangdServer::getDocument(PathRef File) {
|
||||
auto draft = DraftMgr.getDraft(File);
|
||||
|
|
|
@ -34,6 +34,12 @@ class PCHContainerOperations;
|
|||
|
||||
namespace clangd {
|
||||
|
||||
/// Turn a [line, column] pair into an offset in Code.
|
||||
size_t positionToOffset(StringRef Code, Position P);
|
||||
|
||||
/// Turn an offset in Code into a [line, column] pair.
|
||||
Position offsetToPosition(StringRef Code, size_t Offset);
|
||||
|
||||
class DiagnosticsConsumer {
|
||||
public:
|
||||
virtual ~DiagnosticsConsumer() = default;
|
||||
|
@ -100,7 +106,6 @@ public:
|
|||
/// separate thread. When the parsing is complete, DiagConsumer passed in
|
||||
/// constructor will receive onDiagnosticsReady callback.
|
||||
void addDocument(PathRef File, StringRef Contents);
|
||||
|
||||
/// Remove \p File from list of tracked files, schedule a request to free
|
||||
/// resources associated with it.
|
||||
void removeDocument(PathRef File);
|
||||
|
@ -108,11 +113,17 @@ public:
|
|||
/// Run code completion for \p File at \p Pos.
|
||||
std::vector<CompletionItem> codeComplete(PathRef File, Position Pos);
|
||||
|
||||
/// Run formatting for \p Rng inside \p File.
|
||||
std::vector<tooling::Replacement> formatRange(PathRef File, Range Rng);
|
||||
/// Run formatting for the whole \p File.
|
||||
std::vector<tooling::Replacement> formatFile(PathRef File);
|
||||
/// Run formatting after a character was typed at \p Pos in \p File.
|
||||
std::vector<tooling::Replacement> formatOnType(PathRef File, Position Pos);
|
||||
|
||||
/// Gets current document contents for \p File. \p File must point to a
|
||||
/// currently tracked file.
|
||||
/// FIXME(ibiryukov): This function is here to allow implementation of
|
||||
/// formatCode from ProtocolHandlers.cpp. We should move formatCode to this
|
||||
/// class and remove this function from public interface.
|
||||
/// FIXME(ibiryukov): This function is here to allow offset-to-Position
|
||||
/// conversions in outside code, maybe there's a way to get rid of it.
|
||||
std::string getDocument(PathRef File);
|
||||
|
||||
private:
|
||||
|
|
|
@ -129,3 +129,61 @@ bool JSONRPCDispatcher::call(StringRef Content) const {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
|
||||
JSONRPCDispatcher &Dispatcher,
|
||||
bool &IsDone) {
|
||||
while (In.good()) {
|
||||
// A Language Server Protocol message starts with a HTTP header, delimited
|
||||
// by \r\n.
|
||||
std::string Line;
|
||||
std::getline(In, Line);
|
||||
if (!In.good() && errno == EINTR) {
|
||||
In.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip empty lines.
|
||||
llvm::StringRef LineRef(Line);
|
||||
if (LineRef.trim().empty())
|
||||
continue;
|
||||
|
||||
// We allow YAML-style comments. Technically this isn't part of the
|
||||
// LSP specification, but makes writing tests easier.
|
||||
if (LineRef.startswith("#"))
|
||||
continue;
|
||||
|
||||
unsigned long long Len = 0;
|
||||
// FIXME: Content-Type is a specified header, but does nothing.
|
||||
// Content-Length is a mandatory header. It specifies the length of the
|
||||
// following JSON.
|
||||
if (LineRef.consume_front("Content-Length: "))
|
||||
llvm::getAsUnsignedInteger(LineRef.trim(), 0, Len);
|
||||
|
||||
// Check if the next line only contains \r\n. If not this is another header,
|
||||
// which we ignore.
|
||||
char NewlineBuf[2];
|
||||
In.read(NewlineBuf, 2);
|
||||
if (std::memcmp(NewlineBuf, "\r\n", 2) != 0)
|
||||
continue;
|
||||
|
||||
// Now read the JSON. Insert a trailing null byte as required by the YAML
|
||||
// parser.
|
||||
std::vector<char> JSON(Len + 1, '\0');
|
||||
In.read(JSON.data(), Len);
|
||||
|
||||
if (Len > 0) {
|
||||
llvm::StringRef JSONRef(JSON.data(), Len);
|
||||
// Log the message.
|
||||
Out.log("<-- " + JSONRef + "\n");
|
||||
|
||||
// Finally, execute the action for this JSON message.
|
||||
if (!Dispatcher.call(JSONRef))
|
||||
Out.log("JSON dispatch failed!\n");
|
||||
|
||||
// If we're done, exit the loop.
|
||||
if (IsDone)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,15 @@ private:
|
|||
std::unique_ptr<Handler> UnknownHandler;
|
||||
};
|
||||
|
||||
/// Parses input queries from LSP client (coming from \p In) and runs call
|
||||
/// method of \p Dispatcher for each query.
|
||||
/// After handling each query checks if \p IsDone is set true and exits the loop
|
||||
/// if it is.
|
||||
/// Input stream(\p In) must be opened in binary mode to avoid preliminary
|
||||
/// replacements of \r\n with \n.
|
||||
void runLanguageServerLoop(std::istream &In, JSONOutput &Out,
|
||||
JSONRPCDispatcher &Dispatcher, bool &IsDone);
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
|
|
|
@ -8,204 +8,215 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "ProtocolHandlers.h"
|
||||
#include "ClangdLSPServer.h"
|
||||
#include "ClangdServer.h"
|
||||
#include "DraftStore.h"
|
||||
#include "clang/Format/Format.h"
|
||||
#include "ClangdLSPServer.h"
|
||||
using namespace clang;
|
||||
using namespace clangd;
|
||||
|
||||
void TextDocumentDidOpenHandler::handleNotification(
|
||||
llvm::yaml::MappingNode *Params) {
|
||||
auto DOTDP = DidOpenTextDocumentParams::parse(Params);
|
||||
if (!DOTDP) {
|
||||
Output.log("Failed to decode DidOpenTextDocumentParams!\n");
|
||||
return;
|
||||
}
|
||||
AST.openDocument(DOTDP->textDocument.uri.file, DOTDP->textDocument.text);
|
||||
}
|
||||
namespace {
|
||||
|
||||
void TextDocumentDidCloseHandler::handleNotification(
|
||||
llvm::yaml::MappingNode *Params) {
|
||||
auto DCTDP = DidCloseTextDocumentParams::parse(Params);
|
||||
if (!DCTDP) {
|
||||
Output.log("Failed to decode DidCloseTextDocumentParams!\n");
|
||||
return;
|
||||
struct InitializeHandler : Handler {
|
||||
InitializeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
|
||||
: Handler(Output), Callbacks(Callbacks) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
|
||||
Callbacks.onInitialize(ID, Output);
|
||||
}
|
||||
|
||||
AST.closeDocument(DCTDP->textDocument.uri.file);
|
||||
}
|
||||
private:
|
||||
ProtocolCallbacks &Callbacks;
|
||||
};
|
||||
|
||||
void TextDocumentDidChangeHandler::handleNotification(
|
||||
llvm::yaml::MappingNode *Params) {
|
||||
auto DCTDP = DidChangeTextDocumentParams::parse(Params);
|
||||
if (!DCTDP || DCTDP->contentChanges.size() != 1) {
|
||||
Output.log("Failed to decode DidChangeTextDocumentParams!\n");
|
||||
return;
|
||||
}
|
||||
// We only support full syncing right now.
|
||||
AST.openDocument(DCTDP->textDocument.uri.file, DCTDP->contentChanges[0].text);
|
||||
}
|
||||
struct ShutdownHandler : Handler {
|
||||
ShutdownHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
|
||||
: Handler(Output), Callbacks(Callbacks) {}
|
||||
|
||||
/// Turn a [line, column] pair into an offset in Code.
|
||||
static size_t positionToOffset(StringRef Code, Position P) {
|
||||
size_t Offset = 0;
|
||||
for (int I = 0; I != P.line; ++I) {
|
||||
// FIXME: \r\n
|
||||
// FIXME: UTF-8
|
||||
size_t F = Code.find('\n', Offset);
|
||||
if (F == StringRef::npos)
|
||||
return 0; // FIXME: Is this reasonable?
|
||||
Offset = F + 1;
|
||||
}
|
||||
return (Offset == 0 ? 0 : (Offset - 1)) + P.character;
|
||||
}
|
||||
|
||||
/// Turn an offset in Code into a [line, column] pair.
|
||||
static Position offsetToPosition(StringRef Code, size_t Offset) {
|
||||
StringRef JustBefore = Code.substr(0, Offset);
|
||||
// FIXME: \r\n
|
||||
// FIXME: UTF-8
|
||||
int Lines = JustBefore.count('\n');
|
||||
int Cols = JustBefore.size() - JustBefore.rfind('\n') - 1;
|
||||
return {Lines, Cols};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::string replacementsToEdits(StringRef Code, const T &Replacements) {
|
||||
// Turn the replacements into the format specified by the Language Server
|
||||
// Protocol. Fuse them into one big JSON array.
|
||||
std::string Edits;
|
||||
for (auto &R : Replacements) {
|
||||
Range ReplacementRange = {
|
||||
offsetToPosition(Code, R.getOffset()),
|
||||
offsetToPosition(Code, R.getOffset() + R.getLength())};
|
||||
TextEdit TE = {ReplacementRange, R.getReplacementText()};
|
||||
Edits += TextEdit::unparse(TE);
|
||||
Edits += ',';
|
||||
}
|
||||
if (!Edits.empty())
|
||||
Edits.pop_back();
|
||||
|
||||
return Edits;
|
||||
}
|
||||
|
||||
static std::string formatCode(StringRef Code, StringRef Filename,
|
||||
ArrayRef<tooling::Range> Ranges, StringRef ID) {
|
||||
// Call clang-format.
|
||||
// FIXME: Don't ignore style.
|
||||
format::FormatStyle Style = format::getLLVMStyle();
|
||||
tooling::Replacements Replacements =
|
||||
format::reformat(Style, Code, Ranges, Filename);
|
||||
|
||||
std::string Edits = replacementsToEdits(Code, Replacements);
|
||||
return R"({"jsonrpc":"2.0","id":)" + ID.str() +
|
||||
R"(,"result":[)" + Edits + R"(]})";
|
||||
}
|
||||
|
||||
void TextDocumentRangeFormattingHandler::handleMethod(
|
||||
llvm::yaml::MappingNode *Params, StringRef ID) {
|
||||
auto DRFP = DocumentRangeFormattingParams::parse(Params);
|
||||
if (!DRFP) {
|
||||
Output.log("Failed to decode DocumentRangeFormattingParams!\n");
|
||||
return;
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
|
||||
Callbacks.onShutdown(Output);
|
||||
}
|
||||
|
||||
std::string Code = AST.getDocument(DRFP->textDocument.uri.file);
|
||||
private:
|
||||
ProtocolCallbacks &Callbacks;
|
||||
};
|
||||
|
||||
size_t Begin = positionToOffset(Code, DRFP->range.start);
|
||||
size_t Len = positionToOffset(Code, DRFP->range.end) - Begin;
|
||||
struct TextDocumentDidOpenHandler : Handler {
|
||||
TextDocumentDidOpenHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
|
||||
: Handler(Output), Callbacks(Callbacks) {}
|
||||
|
||||
writeMessage(formatCode(Code, DRFP->textDocument.uri.file,
|
||||
{clang::tooling::Range(Begin, Len)}, ID));
|
||||
}
|
||||
|
||||
void TextDocumentOnTypeFormattingHandler::handleMethod(
|
||||
llvm::yaml::MappingNode *Params, StringRef ID) {
|
||||
auto DOTFP = DocumentOnTypeFormattingParams::parse(Params);
|
||||
if (!DOTFP) {
|
||||
Output.log("Failed to decode DocumentOnTypeFormattingParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for the previous opening brace from the character position and format
|
||||
// starting from there.
|
||||
std::string Code = AST.getDocument(DOTFP->textDocument.uri.file);
|
||||
size_t CursorPos = positionToOffset(Code, DOTFP->position);
|
||||
size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
|
||||
if (PreviousLBracePos == StringRef::npos)
|
||||
PreviousLBracePos = CursorPos;
|
||||
size_t Len = 1 + CursorPos - PreviousLBracePos;
|
||||
|
||||
writeMessage(formatCode(Code, DOTFP->textDocument.uri.file,
|
||||
{clang::tooling::Range(PreviousLBracePos, Len)}, ID));
|
||||
}
|
||||
|
||||
void TextDocumentFormattingHandler::handleMethod(
|
||||
llvm::yaml::MappingNode *Params, StringRef ID) {
|
||||
auto DFP = DocumentFormattingParams::parse(Params);
|
||||
if (!DFP) {
|
||||
Output.log("Failed to decode DocumentFormattingParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Format everything.
|
||||
std::string Code = AST.getDocument(DFP->textDocument.uri.file);
|
||||
writeMessage(formatCode(Code, DFP->textDocument.uri.file,
|
||||
{clang::tooling::Range(0, Code.size())}, ID));
|
||||
}
|
||||
|
||||
void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
|
||||
StringRef ID) {
|
||||
auto CAP = CodeActionParams::parse(Params);
|
||||
if (!CAP) {
|
||||
Output.log("Failed to decode CodeActionParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// We provide a code action for each diagnostic at the requested location
|
||||
// which has FixIts available.
|
||||
std::string Code = AST.getDocument(CAP->textDocument.uri.file);
|
||||
std::string Commands;
|
||||
for (Diagnostic &D : CAP->context.diagnostics) {
|
||||
std::vector<clang::tooling::Replacement> Fixes = AST.getFixIts(CAP->textDocument.uri.file, D);
|
||||
std::string Edits = replacementsToEdits(Code, Fixes);
|
||||
|
||||
if (!Edits.empty())
|
||||
Commands +=
|
||||
R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
|
||||
R"('", "command": "clangd.applyFix", "arguments": [")" +
|
||||
llvm::yaml::escape(CAP->textDocument.uri.uri) +
|
||||
R"(", [)" + Edits +
|
||||
R"(]]},)";
|
||||
}
|
||||
if (!Commands.empty())
|
||||
Commands.pop_back();
|
||||
|
||||
writeMessage(
|
||||
R"({"jsonrpc":"2.0","id":)" + ID.str() +
|
||||
R"(, "result": [)" + Commands +
|
||||
R"(]})");
|
||||
}
|
||||
|
||||
void CompletionHandler::handleMethod(llvm::yaml::MappingNode *Params,
|
||||
StringRef ID) {
|
||||
auto TDPP = TextDocumentPositionParams::parse(Params);
|
||||
if (!TDPP) {
|
||||
Output.log("Failed to decode TextDocumentPositionParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto Items = AST.codeComplete(TDPP->textDocument.uri.file, Position{TDPP->position.line,
|
||||
TDPP->position.character});
|
||||
std::string Completions;
|
||||
for (const auto &Item : Items) {
|
||||
Completions += CompletionItem::unparse(Item);
|
||||
Completions += ",";
|
||||
}
|
||||
if (!Completions.empty())
|
||||
Completions.pop_back();
|
||||
writeMessage(
|
||||
R"({"jsonrpc":"2.0","id":)" + ID.str() +
|
||||
R"(,"result":[)" + Completions + R"(]})");
|
||||
void handleNotification(llvm::yaml::MappingNode *Params) override {
|
||||
auto DOTDP = DidOpenTextDocumentParams::parse(Params);
|
||||
if (!DOTDP) {
|
||||
Output.log("Failed to decode DidOpenTextDocumentParams!\n");
|
||||
return;
|
||||
}
|
||||
Callbacks.onDocumentDidOpen(*DOTDP, Output);
|
||||
}
|
||||
|
||||
private:
|
||||
ProtocolCallbacks &Callbacks;
|
||||
};
|
||||
|
||||
struct TextDocumentDidChangeHandler : Handler {
|
||||
TextDocumentDidChangeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
|
||||
: Handler(Output), Callbacks(Callbacks) {}
|
||||
|
||||
void handleNotification(llvm::yaml::MappingNode *Params) override {
|
||||
auto DCTDP = DidChangeTextDocumentParams::parse(Params);
|
||||
if (!DCTDP || DCTDP->contentChanges.size() != 1) {
|
||||
Output.log("Failed to decode DidChangeTextDocumentParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Callbacks.onDocumentDidChange(*DCTDP, Output);
|
||||
}
|
||||
|
||||
private:
|
||||
ProtocolCallbacks &Callbacks;
|
||||
};
|
||||
|
||||
struct TextDocumentDidCloseHandler : Handler {
|
||||
TextDocumentDidCloseHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
|
||||
: Handler(Output), Callbacks(Callbacks) {}
|
||||
|
||||
void handleNotification(llvm::yaml::MappingNode *Params) override {
|
||||
auto DCTDP = DidCloseTextDocumentParams::parse(Params);
|
||||
if (!DCTDP) {
|
||||
Output.log("Failed to decode DidCloseTextDocumentParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Callbacks.onDocumentDidClose(*DCTDP, Output);
|
||||
}
|
||||
|
||||
private:
|
||||
ProtocolCallbacks &Callbacks;
|
||||
};
|
||||
|
||||
struct TextDocumentOnTypeFormattingHandler : Handler {
|
||||
TextDocumentOnTypeFormattingHandler(JSONOutput &Output,
|
||||
ProtocolCallbacks &Callbacks)
|
||||
: Handler(Output), Callbacks(Callbacks) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
|
||||
auto DOTFP = DocumentOnTypeFormattingParams::parse(Params);
|
||||
if (!DOTFP) {
|
||||
Output.log("Failed to decode DocumentOnTypeFormattingParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Callbacks.onDocumentOnTypeFormatting(*DOTFP, ID, Output);
|
||||
}
|
||||
|
||||
private:
|
||||
ProtocolCallbacks &Callbacks;
|
||||
};
|
||||
|
||||
struct TextDocumentRangeFormattingHandler : Handler {
|
||||
TextDocumentRangeFormattingHandler(JSONOutput &Output,
|
||||
ProtocolCallbacks &Callbacks)
|
||||
: Handler(Output), Callbacks(Callbacks) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
|
||||
auto DRFP = DocumentRangeFormattingParams::parse(Params);
|
||||
if (!DRFP) {
|
||||
Output.log("Failed to decode DocumentRangeFormattingParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Callbacks.onDocumentRangeFormatting(*DRFP, ID, Output);
|
||||
}
|
||||
|
||||
private:
|
||||
ProtocolCallbacks &Callbacks;
|
||||
};
|
||||
|
||||
struct TextDocumentFormattingHandler : Handler {
|
||||
TextDocumentFormattingHandler(JSONOutput &Output,
|
||||
ProtocolCallbacks &Callbacks)
|
||||
: Handler(Output), Callbacks(Callbacks) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
|
||||
auto DFP = DocumentFormattingParams::parse(Params);
|
||||
if (!DFP) {
|
||||
Output.log("Failed to decode DocumentFormattingParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Callbacks.onDocumentFormatting(*DFP, ID, Output);
|
||||
}
|
||||
|
||||
private:
|
||||
ProtocolCallbacks &Callbacks;
|
||||
};
|
||||
|
||||
struct CodeActionHandler : Handler {
|
||||
CodeActionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
|
||||
: Handler(Output), Callbacks(Callbacks) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
|
||||
auto CAP = CodeActionParams::parse(Params);
|
||||
if (!CAP) {
|
||||
Output.log("Failed to decode CodeActionParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Callbacks.onCodeAction(*CAP, ID, Output);
|
||||
}
|
||||
|
||||
private:
|
||||
ProtocolCallbacks &Callbacks;
|
||||
};
|
||||
|
||||
struct CompletionHandler : Handler {
|
||||
CompletionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
|
||||
: Handler(Output), Callbacks(Callbacks) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
|
||||
auto TDPP = TextDocumentPositionParams::parse(Params);
|
||||
if (!TDPP) {
|
||||
Output.log("Failed to decode TextDocumentPositionParams!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Callbacks.onCompletion(*TDPP, ID, Output);
|
||||
}
|
||||
|
||||
private:
|
||||
ProtocolCallbacks &Callbacks;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void clangd::regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher,
|
||||
JSONOutput &Out,
|
||||
ProtocolCallbacks &Callbacks) {
|
||||
Dispatcher.registerHandler(
|
||||
"initialize", llvm::make_unique<InitializeHandler>(Out, Callbacks));
|
||||
Dispatcher.registerHandler(
|
||||
"shutdown", llvm::make_unique<ShutdownHandler>(Out, Callbacks));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/didOpen",
|
||||
llvm::make_unique<TextDocumentDidOpenHandler>(Out, Callbacks));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/didClose",
|
||||
llvm::make_unique<TextDocumentDidCloseHandler>(Out, Callbacks));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/didChange",
|
||||
llvm::make_unique<TextDocumentDidChangeHandler>(Out, Callbacks));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/rangeFormatting",
|
||||
llvm::make_unique<TextDocumentRangeFormattingHandler>(Out, Callbacks));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/onTypeFormatting",
|
||||
llvm::make_unique<TextDocumentOnTypeFormattingHandler>(Out, Callbacks));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/formatting",
|
||||
llvm::make_unique<TextDocumentFormattingHandler>(Out, Callbacks));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/codeAction",
|
||||
llvm::make_unique<CodeActionHandler>(Out, Callbacks));
|
||||
Dispatcher.registerHandler(
|
||||
"textDocument/completion",
|
||||
llvm::make_unique<CompletionHandler>(Out, Callbacks));
|
||||
}
|
||||
|
|
|
@ -22,118 +22,34 @@
|
|||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
class ClangdLSPServer;
|
||||
class ClangdLSPServer;
|
||||
|
||||
struct InitializeHandler : Handler {
|
||||
InitializeHandler(JSONOutput &Output) : Handler(Output) {}
|
||||
class ProtocolCallbacks {
|
||||
public:
|
||||
virtual ~ProtocolCallbacks() = default;
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
|
||||
writeMessage(
|
||||
R"({"jsonrpc":"2.0","id":)" + ID +
|
||||
R"(,"result":{"capabilities":{
|
||||
"textDocumentSync": 1,
|
||||
"documentFormattingProvider": true,
|
||||
"documentRangeFormattingProvider": true,
|
||||
"documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
|
||||
"codeActionProvider": true,
|
||||
"completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
|
||||
}}})");
|
||||
}
|
||||
virtual void onInitialize(StringRef ID, JSONOutput &Out) = 0;
|
||||
virtual void onShutdown(JSONOutput &Out) = 0;
|
||||
virtual void onDocumentDidOpen(DidOpenTextDocumentParams Params,
|
||||
JSONOutput &Out) = 0;
|
||||
virtual void onDocumentDidChange(DidChangeTextDocumentParams Params,
|
||||
JSONOutput &Out) = 0;
|
||||
|
||||
virtual void onDocumentDidClose(DidCloseTextDocumentParams Params,
|
||||
JSONOutput &Out) = 0;
|
||||
virtual void onDocumentFormatting(DocumentFormattingParams Params,
|
||||
StringRef ID, JSONOutput &Out) = 0;
|
||||
virtual void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params,
|
||||
StringRef ID, JSONOutput &Out) = 0;
|
||||
virtual void onDocumentRangeFormatting(DocumentRangeFormattingParams Params,
|
||||
StringRef ID, JSONOutput &Out) = 0;
|
||||
virtual void onCodeAction(CodeActionParams Params, StringRef ID,
|
||||
JSONOutput &Out) = 0;
|
||||
virtual void onCompletion(TextDocumentPositionParams Params, StringRef ID,
|
||||
JSONOutput &Out) = 0;
|
||||
};
|
||||
|
||||
struct ShutdownHandler : Handler {
|
||||
ShutdownHandler(JSONOutput &Output) : Handler(Output) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
|
||||
IsDone = true;
|
||||
}
|
||||
|
||||
bool isDone() const { return IsDone; }
|
||||
|
||||
private:
|
||||
bool IsDone = false;
|
||||
};
|
||||
|
||||
struct TextDocumentDidOpenHandler : Handler {
|
||||
TextDocumentDidOpenHandler(JSONOutput &Output, ClangdLSPServer &AST)
|
||||
: Handler(Output), AST(AST) {}
|
||||
|
||||
void handleNotification(llvm::yaml::MappingNode *Params) override;
|
||||
|
||||
private:
|
||||
ClangdLSPServer &AST;
|
||||
};
|
||||
|
||||
struct TextDocumentDidChangeHandler : Handler {
|
||||
TextDocumentDidChangeHandler(JSONOutput &Output, ClangdLSPServer &AST)
|
||||
: Handler(Output), AST(AST) {}
|
||||
|
||||
void handleNotification(llvm::yaml::MappingNode *Params) override;
|
||||
|
||||
private:
|
||||
ClangdLSPServer &AST;
|
||||
};
|
||||
|
||||
struct TextDocumentDidCloseHandler : Handler {
|
||||
TextDocumentDidCloseHandler(JSONOutput &Output, ClangdLSPServer &AST)
|
||||
: Handler(Output), AST(AST) {}
|
||||
|
||||
void handleNotification(llvm::yaml::MappingNode *Params) override;
|
||||
|
||||
private:
|
||||
ClangdLSPServer &AST;
|
||||
};
|
||||
|
||||
struct TextDocumentOnTypeFormattingHandler : Handler {
|
||||
TextDocumentOnTypeFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
|
||||
: Handler(Output), AST(AST) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
|
||||
|
||||
private:
|
||||
ClangdLSPServer &AST;
|
||||
};
|
||||
|
||||
struct TextDocumentRangeFormattingHandler : Handler {
|
||||
TextDocumentRangeFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
|
||||
: Handler(Output), AST(AST) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
|
||||
|
||||
private:
|
||||
ClangdLSPServer &AST;
|
||||
};
|
||||
|
||||
struct TextDocumentFormattingHandler : Handler {
|
||||
TextDocumentFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
|
||||
: Handler(Output), AST(AST) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
|
||||
|
||||
private:
|
||||
ClangdLSPServer &AST;
|
||||
};
|
||||
|
||||
struct CodeActionHandler : Handler {
|
||||
CodeActionHandler(JSONOutput &Output, ClangdLSPServer &AST)
|
||||
: Handler(Output), AST(AST) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
|
||||
|
||||
private:
|
||||
ClangdLSPServer &AST;
|
||||
};
|
||||
|
||||
struct CompletionHandler : Handler {
|
||||
CompletionHandler(JSONOutput &Output, ClangdLSPServer &AST)
|
||||
: Handler(Output), AST(AST) {}
|
||||
|
||||
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
|
||||
|
||||
private:
|
||||
ClangdLSPServer &AST;
|
||||
};
|
||||
void regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,
|
||||
ProtocolCallbacks &Callbacks);
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
|
Loading…
Reference in New Issue