forked from qt-creator/qt-creator
LSP: Simplify content parsing
JsonRpcMessageHandler::toJsonObject can be expensive for huge json objects like the clangd ast responses for big c++ files. Avoid calling this function in Request::responseHandler. We already generate a JsonRpcMessage in JsonRpcMessageHandler::parseContent to get the id of the response so pass this around. While at it also pass around references instead of pointers to simplify the memory handling of generated messages. Change-Id: I9a3c7e85428fc064d1ea1197d897739725265192 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -94,12 +94,12 @@ private:
|
||||
struct ResponseHandler
|
||||
{
|
||||
MessageId id;
|
||||
using Callback = std::function<void(const QByteArray &, QTextCodec *)>;
|
||||
using Callback = std::function<void(const IContent &)>;
|
||||
Callback callback;
|
||||
};
|
||||
|
||||
using ResponseHandlers = std::function<void(const MessageId &, const QByteArray &, QTextCodec *)>;
|
||||
using MethodHandler = std::function<void(const QString &, const MessageId &, const IContent *)>;
|
||||
using ResponseHandlers = std::function<void(const MessageId &, const IContent &)>;
|
||||
using MethodHandler = std::function<void(const QString &, const MessageId &, const IContent &)>;
|
||||
|
||||
template <typename Error>
|
||||
inline QDebug operator<<(QDebug stream, const LanguageServerProtocol::MessageId &id)
|
||||
|
@@ -40,8 +40,6 @@ Q_LOGGING_CATEGORY(timingLog, "qtc.languageserverprotocol.timing", QtWarningMsg)
|
||||
|
||||
constexpr const char CancelRequest::methodName[];
|
||||
|
||||
QHash<QString, JsonRpcMessageHandler::MessageProvider> JsonRpcMessageHandler::m_messageProvider;
|
||||
|
||||
QByteArray JsonRpcMessage::toRawData() const
|
||||
{
|
||||
return QJsonDocument(m_jsonObject).toJson(QJsonDocument::Compact);
|
||||
@@ -57,6 +55,11 @@ bool JsonRpcMessage::isValid(QString * /*errorMessage*/) const
|
||||
return m_jsonObject[jsonRpcVersionKey] == "2.0";
|
||||
}
|
||||
|
||||
const QJsonObject &JsonRpcMessage::toJsonObject() const
|
||||
{
|
||||
return m_jsonObject;
|
||||
}
|
||||
|
||||
JsonRpcMessage::JsonRpcMessage()
|
||||
{
|
||||
// The language server protocol always uses “2.0” as the jsonrpc version
|
||||
@@ -76,12 +79,6 @@ QByteArray JsonRpcMessageHandler::jsonRpcMimeType()
|
||||
return "application/vscode-jsonrpc";
|
||||
}
|
||||
|
||||
void JsonRpcMessageHandler::registerMessageProvider(
|
||||
const QString &method, JsonRpcMessageHandler::MessageProvider provider)
|
||||
{
|
||||
m_messageProvider.insert(method, provider);
|
||||
}
|
||||
|
||||
void JsonRpcMessageHandler::parseContent(const QByteArray &content,
|
||||
QTextCodec *codec,
|
||||
QString &parseError,
|
||||
@@ -94,14 +91,11 @@ void JsonRpcMessageHandler::parseContent(const QByteArray &content,
|
||||
|
||||
const MessageId id(jsonObject.value(idKey));
|
||||
const QString &method = jsonObject.value(methodKey).toString();
|
||||
if (!method.isEmpty()) {
|
||||
if (auto provider = m_messageProvider[method]) {
|
||||
methodHandler(method, id, provider(jsonObject));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
responseHandlers(id, content, codec);
|
||||
const JsonRpcMessage jsonContent(jsonObject);
|
||||
if (method.isEmpty())
|
||||
responseHandlers(id, jsonContent);
|
||||
else
|
||||
methodHandler(method, id, jsonContent);
|
||||
}
|
||||
|
||||
constexpr int utf8mib = 106;
|
||||
|
@@ -54,6 +54,7 @@ public:
|
||||
QByteArray mimeType() const final;
|
||||
bool isValid(QString *errorMessage) const override;
|
||||
|
||||
const QJsonObject &toJsonObject() const;
|
||||
protected:
|
||||
QJsonObject m_jsonObject;
|
||||
|
||||
@@ -66,23 +67,11 @@ class LANGUAGESERVERPROTOCOL_EXPORT JsonRpcMessageHandler
|
||||
Q_DECLARE_TR_FUNCTIONS(JsonRpcMessageHandler)
|
||||
|
||||
public:
|
||||
using MessageProvider = std::function<IContent *(const QJsonObject &)>;
|
||||
static void registerMessageProvider(const QString &method, MessageProvider provider);
|
||||
template<typename T>
|
||||
static void registerMessageProvider()
|
||||
{
|
||||
registerMessageProvider(T::methodName, [](const QJsonObject &object){
|
||||
return new T(object);
|
||||
});
|
||||
}
|
||||
static QByteArray jsonRpcMimeType();
|
||||
static void parseContent(const QByteArray &content, QTextCodec *codec, QString &errorMessage,
|
||||
const ResponseHandlers &responseHandlers,
|
||||
const MethodHandler &methodHandler);
|
||||
static QJsonObject toJsonObject(const QByteArray &content, QTextCodec *codec, QString &parseError);
|
||||
|
||||
private:
|
||||
static QHash<QString, MessageProvider> m_messageProvider;
|
||||
};
|
||||
|
||||
template <typename Params>
|
||||
@@ -322,21 +311,12 @@ public:
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
auto callback = [callback = m_callBack, method = this->method(), t = std::move(timer)]
|
||||
(const QByteArray &content, QTextCodec *codec) {
|
||||
(const IContent &content) {
|
||||
if (!callback)
|
||||
return;
|
||||
logElapsedTime(method, t);
|
||||
QString parseError;
|
||||
const QJsonObject &object = JsonRpcMessageHandler::toJsonObject(content,
|
||||
codec,
|
||||
parseError);
|
||||
Response response(object);
|
||||
if (object.isEmpty()) {
|
||||
ResponseError<ErrorDataType> error;
|
||||
error.setMessage(parseError);
|
||||
response.setError(error);
|
||||
}
|
||||
callback(Response(object));
|
||||
|
||||
callback(Response(static_cast<const JsonRpcMessage &>(content).toJsonObject()));
|
||||
};
|
||||
return Utils::make_optional(ResponseHandler{id(), callback});
|
||||
}
|
||||
|
@@ -1205,10 +1205,10 @@ void Client::handleMessage(const BaseMessage &message)
|
||||
if (auto handler = m_contentHandler[message.mimeType]) {
|
||||
QString parseError;
|
||||
handler(message.content, message.codec, parseError,
|
||||
[this](const MessageId &id, const QByteArray &content, QTextCodec *codec){
|
||||
this->handleResponse(id, content, codec);
|
||||
[this](const MessageId &id, const IContent &content){
|
||||
this->handleResponse(id, content);
|
||||
},
|
||||
[this](const QString &method, const MessageId &id, const IContent *content){
|
||||
[this](const QString &method, const MessageId &id, const IContent &content){
|
||||
this->handleMethod(method, id, content);
|
||||
});
|
||||
if (!parseError.isEmpty())
|
||||
@@ -1346,10 +1346,10 @@ void Client::sendPostponedDocumentUpdates(Schedule semanticTokensSchedule)
|
||||
}
|
||||
}
|
||||
|
||||
void Client::handleResponse(const MessageId &id, const QByteArray &content, QTextCodec *codec)
|
||||
void Client::handleResponse(const MessageId &id, const IContent &content)
|
||||
{
|
||||
if (auto handler = m_responseHandlers[id])
|
||||
handler(content, codec);
|
||||
handler(content);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -1361,118 +1361,137 @@ static ResponseError<T> createInvalidParamsError(const QString &message)
|
||||
return error;
|
||||
}
|
||||
|
||||
void Client::handleMethod(const QString &method, const MessageId &id, const IContent *content)
|
||||
template<typename T>
|
||||
static T asJsonContent(const IContent &content) {
|
||||
return T(static_cast<const JsonRpcMessage &>(content).toJsonObject());
|
||||
}
|
||||
|
||||
void Client::handleMethod(const QString &method, const MessageId &id, const IContent &content)
|
||||
{
|
||||
auto invalidParamsErrorMessage = [&](const JsonObject ¶ms) {
|
||||
return tr("Invalid parameter in \"%1\":\n%2")
|
||||
.arg(method, QString::fromUtf8(QJsonDocument(params).toJson(QJsonDocument::Indented)));
|
||||
};
|
||||
|
||||
auto createDefaultResponse = [&]() -> IContent * {
|
||||
Response<std::nullptr_t, JsonObject> *response = nullptr;
|
||||
if (id.isValid()) {
|
||||
response = new Response<std::nullptr_t, JsonObject>(id);
|
||||
response->setResult(nullptr);
|
||||
}
|
||||
auto createDefaultResponse = [&]() {
|
||||
Response<std::nullptr_t, JsonObject> response;
|
||||
if (QTC_GUARD(id.isValid()))
|
||||
response.setId(id);
|
||||
response.setResult(nullptr);
|
||||
return response;
|
||||
};
|
||||
|
||||
const bool isRequest = id.isValid();
|
||||
IContent *response = nullptr;
|
||||
|
||||
bool responseSend = false;
|
||||
auto sendResponse =
|
||||
[&](const IContent &response) {
|
||||
responseSend = true;
|
||||
if (reachable()) {
|
||||
sendContent(response);
|
||||
} else {
|
||||
qCDebug(LOGLSPCLIENT)
|
||||
<< QString("Dropped response to request %1 id %2 for unreachable server %3")
|
||||
.arg(method, id.toString(), name());
|
||||
}
|
||||
};
|
||||
|
||||
if (method == PublishDiagnosticsNotification::methodName) {
|
||||
auto params = dynamic_cast<const PublishDiagnosticsNotification *>(content)->params().value_or(PublishDiagnosticsParams());
|
||||
auto params = asJsonContent<PublishDiagnosticsNotification>(content).params().value_or(
|
||||
PublishDiagnosticsParams());
|
||||
if (params.isValid())
|
||||
handleDiagnostics(params);
|
||||
else
|
||||
log(invalidParamsErrorMessage(params));
|
||||
} else if (method == LogMessageNotification::methodName) {
|
||||
auto params = dynamic_cast<const LogMessageNotification *>(content)->params().value_or(LogMessageParams());
|
||||
auto params = asJsonContent<LogMessageNotification>(content).params().value_or(
|
||||
LogMessageParams());
|
||||
if (params.isValid())
|
||||
log(params);
|
||||
else
|
||||
log(invalidParamsErrorMessage(params));
|
||||
} else if (method == ShowMessageNotification::methodName) {
|
||||
auto params = dynamic_cast<const ShowMessageNotification *>(content)->params().value_or(ShowMessageParams());
|
||||
auto params = asJsonContent<ShowMessageNotification>(content).params().value_or(
|
||||
ShowMessageParams());
|
||||
if (params.isValid())
|
||||
log(params);
|
||||
else
|
||||
log(invalidParamsErrorMessage(params));
|
||||
} else if (method == ShowMessageRequest::methodName) {
|
||||
auto request = dynamic_cast<const ShowMessageRequest *>(content);
|
||||
auto showMessageResponse = new ShowMessageRequest::Response(id);
|
||||
auto params = request->params().value_or(ShowMessageRequestParams());
|
||||
auto request = asJsonContent<ShowMessageRequest>(content);
|
||||
ShowMessageRequest::Response response(id);
|
||||
auto params = request.params().value_or(ShowMessageRequestParams());
|
||||
if (params.isValid()) {
|
||||
showMessageResponse->setResult(showMessageBox(params));
|
||||
response.setResult(showMessageBox(params));
|
||||
} else {
|
||||
const QString errorMessage = invalidParamsErrorMessage(params);
|
||||
log(errorMessage);
|
||||
showMessageResponse->setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
||||
response.setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
||||
}
|
||||
response = showMessageResponse;
|
||||
sendResponse(response);
|
||||
} else if (method == RegisterCapabilityRequest::methodName) {
|
||||
auto params = dynamic_cast<const RegisterCapabilityRequest *>(content)->params().value_or(
|
||||
auto params = asJsonContent<RegisterCapabilityRequest>(content).params().value_or(
|
||||
RegistrationParams());
|
||||
if (params.isValid()) {
|
||||
registerCapabilities(params.registrations());
|
||||
response = createDefaultResponse();
|
||||
sendResponse(createDefaultResponse());
|
||||
} else {
|
||||
const QString errorMessage = invalidParamsErrorMessage(params);
|
||||
log(invalidParamsErrorMessage(params));
|
||||
auto registerResponse = new RegisterCapabilityRequest::Response(id);
|
||||
registerResponse->setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
||||
response = registerResponse;
|
||||
RegisterCapabilityRequest::Response response(id);
|
||||
response.setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
||||
sendResponse(response);
|
||||
}
|
||||
} else if (method == UnregisterCapabilityRequest::methodName) {
|
||||
auto params = dynamic_cast<const UnregisterCapabilityRequest *>(content)->params().value_or(
|
||||
auto params = asJsonContent<UnregisterCapabilityRequest>(content).params().value_or(
|
||||
UnregistrationParams());
|
||||
if (params.isValid()) {
|
||||
unregisterCapabilities(params.unregistrations());
|
||||
response = createDefaultResponse();
|
||||
sendResponse(createDefaultResponse());
|
||||
} else {
|
||||
const QString errorMessage = invalidParamsErrorMessage(params);
|
||||
log(invalidParamsErrorMessage(params));
|
||||
auto registerResponse = new UnregisterCapabilityRequest::Response(id);
|
||||
registerResponse->setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
||||
response = registerResponse;
|
||||
UnregisterCapabilityRequest::Response response(id);
|
||||
response.setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
||||
sendResponse(response);
|
||||
}
|
||||
} else if (method == ApplyWorkspaceEditRequest::methodName) {
|
||||
auto editResponse = new ApplyWorkspaceEditRequest::Response(id);
|
||||
auto params = dynamic_cast<const ApplyWorkspaceEditRequest *>(content)->params().value_or(
|
||||
ApplyWorkspaceEditRequest::Response response(id);
|
||||
auto params = asJsonContent<ApplyWorkspaceEditRequest>(content).params().value_or(
|
||||
ApplyWorkspaceEditParams());
|
||||
if (params.isValid()) {
|
||||
ApplyWorkspaceEditResult result;
|
||||
result.setApplied(applyWorkspaceEdit(this, params.edit()));
|
||||
editResponse->setResult(result);
|
||||
response.setResult(result);
|
||||
} else {
|
||||
const QString errorMessage = invalidParamsErrorMessage(params);
|
||||
log(errorMessage);
|
||||
editResponse->setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
||||
response.setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
||||
}
|
||||
response = editResponse;
|
||||
sendResponse(response);
|
||||
} else if (method == WorkSpaceFolderRequest::methodName) {
|
||||
auto workSpaceFolderResponse = new WorkSpaceFolderRequest::Response(id);
|
||||
WorkSpaceFolderRequest::Response response(id);
|
||||
const QList<ProjectExplorer::Project *> projects
|
||||
= ProjectExplorer::SessionManager::projects();
|
||||
WorkSpaceFolderResult result;
|
||||
if (projects.isEmpty()) {
|
||||
result = nullptr;
|
||||
response.setResult(nullptr);
|
||||
} else {
|
||||
result = Utils::transform(projects, [](ProjectExplorer::Project *project) {
|
||||
response.setResult(Utils::transform(
|
||||
projects,
|
||||
[](ProjectExplorer::Project *project) {
|
||||
return WorkSpaceFolder(DocumentUri::fromFilePath(project->projectDirectory()),
|
||||
project->displayName());
|
||||
});
|
||||
}));
|
||||
}
|
||||
workSpaceFolderResponse->setResult(result);
|
||||
response = workSpaceFolderResponse;
|
||||
sendResponse(response);
|
||||
} else if (method == WorkDoneProgressCreateRequest::methodName) {
|
||||
response = createDefaultResponse();
|
||||
sendResponse(createDefaultResponse());
|
||||
} else if (method == SemanticTokensRefreshRequest::methodName) {
|
||||
m_tokenSupport.refresh();
|
||||
response = createDefaultResponse();
|
||||
sendResponse(createDefaultResponse());
|
||||
} else if (method == ProgressNotification::methodName) {
|
||||
if (Utils::optional<ProgressParams> params
|
||||
= dynamic_cast<const ProgressNotification *>(content)->params()) {
|
||||
= asJsonContent<ProgressNotification>(content).params()) {
|
||||
if (!params->isValid())
|
||||
log(invalidParamsErrorMessage(*params));
|
||||
m_progressManager.handleProgress(*params);
|
||||
@@ -1480,27 +1499,15 @@ void Client::handleMethod(const QString &method, const MessageId &id, const ICon
|
||||
emit workDone(params->token());
|
||||
}
|
||||
} else if (isRequest) {
|
||||
auto methodNotFoundResponse = new Response<JsonObject, JsonObject>(id);
|
||||
Response<JsonObject, JsonObject> response(id);
|
||||
ResponseError<JsonObject> error;
|
||||
error.setCode(ResponseError<JsonObject>::MethodNotFound);
|
||||
methodNotFoundResponse->setError(error);
|
||||
response = methodNotFoundResponse;
|
||||
response.setError(error);
|
||||
sendResponse(response);
|
||||
}
|
||||
|
||||
// we got a request and handled it somewhere above but we missed to generate a response for it
|
||||
QTC_ASSERT(!isRequest || response, response = createDefaultResponse());
|
||||
|
||||
if (response) {
|
||||
if (reachable()) {
|
||||
sendContent(*response);
|
||||
} else {
|
||||
qCDebug(LOGLSPCLIENT)
|
||||
<< QString("Dropped response to request %1 id %2 for unreachable server %3")
|
||||
.arg(method, id.toString(), name());
|
||||
}
|
||||
delete response;
|
||||
}
|
||||
delete content;
|
||||
QTC_ASSERT(!isRequest || responseSend, sendResponse(createDefaultResponse()));
|
||||
}
|
||||
|
||||
void Client::handleDiagnostics(const PublishDiagnosticsParams ¶ms)
|
||||
|
@@ -229,10 +229,11 @@ protected:
|
||||
|
||||
private:
|
||||
void sendMessage(const LanguageServerProtocol::BaseMessage &message);
|
||||
void handleResponse(const LanguageServerProtocol::MessageId &id, const QByteArray &content,
|
||||
QTextCodec *codec);
|
||||
void handleMethod(const QString &method, const LanguageServerProtocol::MessageId &id,
|
||||
const LanguageServerProtocol::IContent *content);
|
||||
void handleResponse(const LanguageServerProtocol::MessageId &id,
|
||||
const LanguageServerProtocol::IContent &content);
|
||||
void handleMethod(const QString &method,
|
||||
const LanguageServerProtocol::MessageId &id,
|
||||
const LanguageServerProtocol::IContent &content);
|
||||
|
||||
void initializeCallback(const LanguageServerProtocol::InitializeRequest::Response &initResponse);
|
||||
void shutDownCallback(const LanguageServerProtocol::ShutdownRequest::Response &shutdownResponse);
|
||||
|
@@ -61,17 +61,6 @@ LanguageClientManager::LanguageClientManager(QObject *parent)
|
||||
{
|
||||
using namespace Core;
|
||||
using namespace ProjectExplorer;
|
||||
JsonRpcMessageHandler::registerMessageProvider<PublishDiagnosticsNotification>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<ApplyWorkspaceEditRequest>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<LogMessageNotification>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<ShowMessageRequest>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<ShowMessageNotification>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<WorkSpaceFolderRequest>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<RegisterCapabilityRequest>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<UnregisterCapabilityRequest>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<WorkDoneProgressCreateRequest>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<ProgressNotification>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<SemanticTokensRefreshRequest>();
|
||||
connect(EditorManager::instance(), &EditorManager::editorOpened,
|
||||
this, &LanguageClientManager::editorOpened);
|
||||
connect(EditorManager::instance(), &EditorManager::documentOpened,
|
||||
|
Reference in New Issue
Block a user