diff options
75 files changed, 10377 insertions, 20 deletions
diff --git a/src/libs/3rdparty/variant/variant.hpp b/src/libs/3rdparty/variant/variant.hpp index 42f018447e..29d7feb29f 100644 --- a/src/libs/3rdparty/variant/variant.hpp +++ b/src/libs/3rdparty/variant/variant.hpp @@ -243,8 +243,10 @@ namespace std { #endif #if defined(__cpp_constexpr) && __cpp_constexpr >= 201304 +#if !defined(_MSC_VER) || _MSC_VER < 1915 // compile issue in msvc 2017 update 8 #define MPARK_CPP14_CONSTEXPR #endif +#endif #if __has_feature(cxx_exceptions) || defined(__cpp_exceptions) || \ (defined(_MSC_VER) && defined(_CPPUNWIND)) @@ -1883,8 +1885,7 @@ namespace mpark { template < typename Front = lib::type_pack_element_t<0, Ts...>, lib::enable_if_t<std::is_default_constructible<Front>::value, int> = 0> - inline constexpr variant() noexcept( - std::is_nothrow_default_constructible<Front>::value) + inline constexpr variant() : impl_(in_place_index_t<0>{}) {} variant(const variant &) = default; @@ -1976,9 +1977,7 @@ namespace mpark { lib::enable_if_t<(std::is_assignable<T &, Arg>::value && std::is_constructible<T, Arg>::value), int> = 0> - inline variant &operator=(Arg &&arg) noexcept( - (std::is_nothrow_assignable<T &, Arg>::value && - std::is_nothrow_constructible<T, Arg>::value)) { + inline variant &operator=(Arg &&arg) { impl_.template assign<I>(lib::forward<Arg>(arg)); return *this; } diff --git a/src/libs/languageserverprotocol/basemessage.cpp b/src/libs/languageserverprotocol/basemessage.cpp new file mode 100644 index 0000000000..7c92e8732e --- /dev/null +++ b/src/libs/languageserverprotocol/basemessage.cpp @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "basemessage.h" + +#include "jsonrpcmessages.h" + +#include <utils/mimetypes/mimedatabase.h> + +#include <QBuffer> +#include <QTextCodec> + +#include <cstring> +#include <utility> + +namespace LanguageServerProtocol { + +BaseMessage::BaseMessage() + : mimeType(JsonRpcMessageHandler::jsonRpcMimeType()) +{ } + +BaseMessage::BaseMessage(const QByteArray &mimeType, const QByteArray &content, + int expectedLength, QTextCodec *codec) + : mimeType(mimeType.isEmpty() ? JsonRpcMessageHandler::jsonRpcMimeType() : mimeType) + , content(content) + , contentLength(expectedLength) + , codec(codec) +{ } + +BaseMessage::BaseMessage(const QByteArray &mimeType, const QByteArray &content) + : BaseMessage(mimeType, content, content.length(), defaultCodec()) +{ } + +bool BaseMessage::operator==(const BaseMessage &other) const +{ + if (mimeType != other.mimeType || content != other.content) + return false; + if (codec) { + if (other.codec) + return codec->mibEnum() == other.codec->mibEnum(); + return codec->mibEnum() == defaultCodec()->mibEnum(); + } + if (other.codec) + return other.codec->mibEnum() == defaultCodec()->mibEnum(); + + return true; +} + +static QPair<QByteArray, QByteArray> splitHeaderFieldLine( + const QByteArray &headerFieldLine, QString &parseError) +{ + static const int fieldSeparatorLength = std::strlen(headerFieldSeparator); + int assignmentIndex = headerFieldLine.indexOf(headerFieldSeparator); + if (assignmentIndex >= 0) { + return {headerFieldLine.mid(0, assignmentIndex), + headerFieldLine.mid(assignmentIndex + fieldSeparatorLength)}; + } + parseError = BaseMessage::tr("Unexpected header line \"%1\"") + .arg(QLatin1String(headerFieldLine)); + return {}; +} + +static void parseContentType(BaseMessage &message, QByteArray contentType, QString &parseError) +{ + if (contentType.startsWith('"') && contentType.endsWith('"')) + contentType = contentType.mid(1, contentType.length() - 2); + QList<QByteArray> contentTypeElements = contentType.split(';'); + QByteArray mimeTypeName = contentTypeElements.takeFirst(); + QTextCodec *codec = nullptr; + for (const QByteArray &_contentTypeElement : contentTypeElements) { + const QByteArray &contentTypeElement = _contentTypeElement.trimmed(); + if (contentTypeElement.startsWith(contentCharsetName)) { + const int equalindex = contentTypeElement.indexOf('='); + const QByteArray charset = contentTypeElement.mid(equalindex + 1); + if (equalindex > 0) + codec = QTextCodec::codecForName(charset); + if (!codec) { + parseError = BaseMessage::tr("Cannot decode content with \"%1\" " + "falling back to \"%2\"") + .arg(QLatin1String(charset), + QLatin1String(defaultCharset)); + } + } + } + message.mimeType = mimeTypeName; + message.codec = codec ? codec : BaseMessage::defaultCodec(); +} + +static void parseContentLength(BaseMessage &message, QByteArray contentLength, QString &parseError) +{ + bool ok = true; + message.contentLength = QString::fromLatin1(contentLength).toInt(&ok); + if (!ok) { + parseError = BaseMessage::tr("Expected an integer in \"%1\", but got \"%2\"") + .arg(contentLengthFieldName, QLatin1String(contentLength)); + } +} + +void BaseMessage::parse(QBuffer *data, QString &parseError, BaseMessage &message) +{ + const qint64 startPos = data->pos(); + + if (message.isValid()) { // incomplete message from last parse + message.content.append(data->read(message.contentLength - message.content.length())); + return; + } + + while (!data->atEnd()) { + const QByteArray &headerFieldLine = data->readLine(); + if (headerFieldLine == headerSeparator) { + if (message.isValid()) + message.content = data->read(message.contentLength); + return; + } + const QPair<QByteArray, QByteArray> nameAndValue = + splitHeaderFieldLine(headerFieldLine, parseError); + const QByteArray &headerFieldName = nameAndValue.first.trimmed(); + const QByteArray &headerFieldValue = nameAndValue.second.trimmed(); + + if (headerFieldName.isEmpty()) + continue; + if (headerFieldName == contentLengthFieldName) { + parseContentLength(message, headerFieldValue, parseError); + } else if (headerFieldName == contentTypeFieldName) { + parseContentType(message, headerFieldValue, parseError); + } else { + parseError = tr("Unexpected header field \"%1\" in \"%2\"") + .arg(QLatin1String(headerFieldName), + QLatin1String(headerFieldLine)); + } + } + + // the complete header wasn't received jet, waiting for the rest of it and reparse + data->seek(startPos); +} + +QTextCodec *BaseMessage::defaultCodec() +{ + static QTextCodec *codec = QTextCodec::codecForName(defaultCharset); + return codec; +} + +bool BaseMessage::isComplete() const +{ + if (!isValid()) + return false; + QTC_ASSERT(content.length() <= contentLength, return true); + return content.length() == contentLength; +} + +bool BaseMessage::isValid() const +{ + return contentLength >= 0; +} + +QByteArray BaseMessage::toData() +{ + return header() + content; +} + +QByteArray BaseMessage::header() const +{ + QByteArray header; + header.append(lengthHeader()); + if (codec != defaultCodec() + || (!mimeType.isEmpty() && mimeType != JsonRpcMessageHandler::jsonRpcMimeType())) { + header.append(typeHeader()); + } + header.append(headerSeparator); + return header; +} + +QByteArray BaseMessage::lengthHeader() const +{ + return QByteArray(contentLengthFieldName) + + QByteArray(headerFieldSeparator) + + QString::number(content.size()).toLatin1() + + QByteArray(headerSeparator); +} + +QByteArray BaseMessage::typeHeader() const +{ + return QByteArray(contentTypeFieldName) + + QByteArray(headerFieldSeparator) + + mimeType + "; " + contentCharsetName + "=" + codec->name() + + QByteArray(headerSeparator); +} + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/basemessage.h b/src/libs/languageserverprotocol/basemessage.h new file mode 100644 index 0000000000..9c35ece1b0 --- /dev/null +++ b/src/libs/languageserverprotocol/basemessage.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "languageserverprotocol_global.h" + +#include <utils/mimetypes/mimetype.h> + +#include <QByteArray> +#include <QCoreApplication> + +QT_BEGIN_NAMESPACE +class QBuffer; +class QTextCodec; +QT_END_NAMESPACE + +namespace LanguageServerProtocol { + +class LANGUAGESERVERPROTOCOL_EXPORT BaseMessage +{ + Q_DECLARE_TR_FUNCTIONS(BaseMessage) +public: + BaseMessage(); + BaseMessage(const QByteArray &mimeType, const QByteArray &content, + int expectedLength, QTextCodec *codec); + BaseMessage(const QByteArray &mimeType, const QByteArray &content); + + bool operator==(const BaseMessage &other) const; + + static void parse(QBuffer *data, QString &parseError, BaseMessage &message); + static QTextCodec *defaultCodec(); + + bool isComplete() const; + bool isValid() const; + QByteArray toData(); + + QByteArray mimeType; + QByteArray content; + int contentLength = -1; + QTextCodec *codec = defaultCodec(); + +private: + QByteArray header() const; + QByteArray lengthHeader() const; + QByteArray typeHeader() const; +}; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/client.cpp b/src/libs/languageserverprotocol/client.cpp new file mode 100644 index 0000000000..fd62fa548f --- /dev/null +++ b/src/libs/languageserverprotocol/client.cpp @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "client.h" + +namespace LanguageServerProtocol { + +constexpr const char RegisterCapabilityRequest::methodName[]; +constexpr const char UnregisterCapabilityRequest::methodName[]; + +RegisterCapabilityRequest::RegisterCapabilityRequest(const RegistrationParams ¶ms) + : RegisterCapabilityRequest(methodName, params) { } + +UnregisterCapabilityRequest::UnregisterCapabilityRequest(const UnregistrationParams ¶ms) + : UnregisterCapabilityRequest(methodName, params) { } + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/client.h b/src/libs/languageserverprotocol/client.h new file mode 100644 index 0000000000..f8f601f9d4 --- /dev/null +++ b/src/libs/languageserverprotocol/client.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "jsonrpcmessages.h" + +namespace LanguageServerProtocol { + +class Registration : public JsonObject +{ +public: + Registration() : Registration(QString()) {} + Registration(const QString &method) + { + setId(QUuid::createUuid().toString()); + setMethod(method); + } + using JsonObject::JsonObject; + + QString id() const { return typedValue<QString>(idKey); } + void setId(const QString &id) { insert(idKey, id); } + + QString method() const { return typedValue<QString>(methodKey); } + void setMethod(const QString &method) { insert(methodKey, method); } + + QJsonValue registerOptions() const { return value(registerOptionsKey); } + void setRegisterOptions(const QJsonValue ®isterOptions) + { insert(registerOptionsKey, registerOptions); } + + bool isValid(QStringList *error) const override + { return check<QString>(error, idKey) && check<QString>(error, methodKey); } +}; + +class RegistrationParams : public JsonObject +{ +public: + RegistrationParams() : RegistrationParams(QList<Registration>()) {} + RegistrationParams(const QList<Registration> ®istrations) { setRegistrations(registrations); } + using JsonObject::JsonObject; + + QList<Registration> registrations() const { return array<Registration>(registrationsKey); } + void setRegistrations(const QList<Registration> ®istrations) + { insertArray(registrationsKey, registrations); } + + bool isValid(QStringList *error) const override + { return checkArray<Registration>(error, registrationsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT RegisterCapabilityRequest : public Request< + LanguageClientNull, LanguageClientNull, RegistrationParams> +{ +public: + RegisterCapabilityRequest(const RegistrationParams ¶ms = RegistrationParams()); + using Request::Request; + constexpr static const char methodName[] = "client/registerCapability"; +}; + +class Unregistration : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString id() const { return typedValue<QString>(idKey); } + void setId(const QString &id) { insert(idKey, id); } + + QString method() const { return typedValue<QString>(methodKey); } + void setMethod(const QString &method) { insert(methodKey, method); } + + bool isValid(QStringList *error) const override + { return check<QString>(error, idKey) && check<QString>(error, methodKey); } +}; + +class UnregistrationParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QList<Unregistration> unregistrations() const + { return array<Unregistration>(unregistrationsKey); } + void setUnregistrations(const QList<Unregistration> &unregistrations) + { insertArray(unregistrationsKey, unregistrations); } + + bool isValid(QStringList *error) const override + { return checkArray<Unregistration>(error, unregistrationsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT UnregisterCapabilityRequest : public Request< + LanguageClientNull, LanguageClientNull, UnregistrationParams> +{ +public: + UnregisterCapabilityRequest(const UnregistrationParams ¶ms = UnregistrationParams()); + using Request::Request; + constexpr static const char methodName[] = "client/unregisterCapability"; +}; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/clientcapabilities.cpp b/src/libs/languageserverprotocol/clientcapabilities.cpp new file mode 100644 index 0000000000..8401bf83d0 --- /dev/null +++ b/src/libs/languageserverprotocol/clientcapabilities.cpp @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clientcapabilities.h" + +namespace LanguageServerProtocol { + +SymbolCapabilities::SymbolKindCapabilities::SymbolKindCapabilities() +{ + setValueSet({SymbolKind::File, SymbolKind::Module, SymbolKind::Namespace, + SymbolKind::Package, SymbolKind::Class, SymbolKind::Method, + SymbolKind::Property, SymbolKind::Field, SymbolKind::Constructor, + SymbolKind::Enum, SymbolKind::Interface, SymbolKind::Function, + SymbolKind::Variable, SymbolKind::Constant, SymbolKind::String, + SymbolKind::Number, SymbolKind::Boolean, SymbolKind::Array, + SymbolKind::Object, SymbolKind::Key, SymbolKind::Null, + SymbolKind::EnumMember, SymbolKind::Struct, SymbolKind::Event, + SymbolKind::Operator, SymbolKind::TypeParameter}); +} + +Utils::optional<QList<SymbolKind> > SymbolCapabilities::SymbolKindCapabilities::valueSet() const +{ + Utils::optional<QList<int>> array = optionalArray<int>(valueSetKey); + if (!array) + return Utils::nullopt; + return Utils::make_optional(Utils::transform(array.value(), [] (int value) { + return static_cast<SymbolKind>(value); + })); +} + +void SymbolCapabilities::SymbolKindCapabilities::setValueSet(const QList<SymbolKind> &valueSet) +{ + insert(valueSetKey, enumArrayToJsonArray<SymbolKind>(valueSet)); +} + + +ClientCapabilities::ClientCapabilities() +{ + setTextDocument(TextDocumentClientCapabilities()); + setWorkspace(WorkspaceClientCapabilities()); +} + +bool ClientCapabilities::isValid(QStringList *error) const +{ + return checkOptional<WorkspaceClientCapabilities>(error, workspaceKey) + && checkOptional<TextDocumentClientCapabilities>(error, textDocumentKey); +} + +WorkspaceClientCapabilities::WorkspaceClientCapabilities() +{ + setWorkspaceFolders(true); +} + +bool WorkspaceClientCapabilities::isValid(QStringList *error) const +{ + return checkOptional<bool>(error,applyEditKey) + && checkOptional<WorkspaceEditCapabilities>(error,workspaceEditKey) + && checkOptional<DynamicRegistrationCapabilities>(error,didChangeConfigurationKey) + && checkOptional<DynamicRegistrationCapabilities>(error,didChangeWatchedFilesKey) + && checkOptional<SymbolCapabilities>(error,symbolKey) + && checkOptional<DynamicRegistrationCapabilities>(error,executeCommandKey) + && checkOptional<bool>(error,workspaceFoldersKey) + && checkOptional<bool>(error,configurationKey); +} + +TextDocumentClientCapabilities::SynchronizationCapabilities::SynchronizationCapabilities() +{ + setDynamicRegistration(true); + setWillSave(true); + setWillSaveWaitUntil(false); + setDidSave(true); +} + +bool TextDocumentClientCapabilities::SynchronizationCapabilities::isValid(QStringList *error) const +{ + return DynamicRegistrationCapabilities::isValid(error) + && checkOptional<bool>(error, willSaveKey) + && checkOptional<bool>(error, willSaveWaitUntilKey) + && checkOptional<bool>(error, didSaveKey); +} + +TextDocumentClientCapabilities::TextDocumentClientCapabilities() +{ + setSynchronization(SynchronizationCapabilities()); + setDocumentSymbol(SymbolCapabilities()); + setCompletion(CompletionCapabilities()); +} + +bool TextDocumentClientCapabilities::isValid(QStringList *error) const +{ + return checkOptional<SynchronizationCapabilities>(error, synchronizationKey) + && checkOptional<CompletionCapabilities>(error, completionKey) + && checkOptional<HoverCapabilities>(error, hoverKey) + && checkOptional<SignatureHelpCapabilities>(error, signatureHelpKey) + && checkOptional<DynamicRegistrationCapabilities>(error, referencesKey) + && checkOptional<DynamicRegistrationCapabilities>(error, documentHighlightKey) + && checkOptional<SymbolCapabilities>(error, documentSymbolKey) + && checkOptional<DynamicRegistrationCapabilities>(error, formattingKey) + && checkOptional<DynamicRegistrationCapabilities>(error, rangeFormattingKey) + && checkOptional<DynamicRegistrationCapabilities>(error, onTypeFormattingKey) + && checkOptional<DynamicRegistrationCapabilities>(error, definitionKey) + && checkOptional<DynamicRegistrationCapabilities>(error, typeDefinitionKey) + && checkOptional<DynamicRegistrationCapabilities>(error, implementationKey) + && checkOptional<DynamicRegistrationCapabilities>(error, codeActionKey) + && checkOptional<DynamicRegistrationCapabilities>(error, codeLensKey) + && checkOptional<DynamicRegistrationCapabilities>(error, documentLinkKey) + && checkOptional<DynamicRegistrationCapabilities>(error, colorProviderKey) + && checkOptional<DynamicRegistrationCapabilities>(error, renameKey); +} + +SymbolCapabilities::SymbolCapabilities() +{ + setSymbolKind(SymbolKindCapabilities()); +} + +bool SymbolCapabilities::isValid(QStringList *error) const +{ + return DynamicRegistrationCapabilities::isValid(error) + && checkOptional<SymbolKindCapabilities>(error, symbolKindKey); +} + +TextDocumentClientCapabilities::CompletionCapabilities::CompletionCapabilities() +{ + setDynamicRegistration(true); + setCompletionItem(CompletionItemCapbilities()); + setCompletionItemKind(CompletionItemKindCapabilities()); +} + +bool TextDocumentClientCapabilities::CompletionCapabilities::isValid(QStringList *error) const +{ + return DynamicRegistrationCapabilities::isValid(error) + && checkOptional<CompletionItemCapbilities>(error, completionItemKey) + && checkOptional<CompletionItemKindCapabilities>(error, completionItemKindKey) + && checkOptional<bool>(error, contextSupportKey); +} + +bool TextDocumentClientCapabilities::HoverCapabilities::isValid(QStringList *error) const +{ + return DynamicRegistrationCapabilities::isValid(error) + && checkOptionalArray<int>(error, contentFormatKey); +} + +bool TextDocumentClientCapabilities::SignatureHelpCapabilities::isValid(QStringList *error) const +{ + return DynamicRegistrationCapabilities::isValid(error) + && checkOptional<SignatureHelpCapabilities>(error, signatureInformationKey); +} + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/clientcapabilities.h b/src/libs/languageserverprotocol/clientcapabilities.h new file mode 100644 index 0000000000..260f498efd --- /dev/null +++ b/src/libs/languageserverprotocol/clientcapabilities.h @@ -0,0 +1,507 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "jsonkeys.h" +#include "lsptypes.h" + +namespace LanguageServerProtocol { + +class LANGUAGESERVERPROTOCOL_EXPORT DynamicRegistrationCapabilities : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Utils::optional<bool> dynamicRegistration() const { return optionalValue<bool>(dynamicRegistrationKey); } + void setDynamicRegistration(bool dynamicRegistration) { insert(dynamicRegistrationKey, dynamicRegistration); } + void clearDynamicRegistration() { remove(dynamicRegistrationKey); } + + bool isValid(QStringList *error) const override + { return checkOptional<bool>(error, dynamicRegistrationKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SymbolCapabilities : public DynamicRegistrationCapabilities +{ +public: + SymbolCapabilities(); + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + + class SymbolKindCapabilities : public JsonObject + { + public: + SymbolKindCapabilities(); + using JsonObject::JsonObject; + + /* + * The symbol kind values the client supports. When this + * property exists the client also guarantees that it will + * handle values outside its set gracefully and falls back + * to a default value when unknown. + * + * If this property is not present the client only supports + * the symbol kinds from `File` to `Array` as defined in + * the initial version of the protocol. + */ + Utils::optional<QList<SymbolKind>> valueSet() const; + void setValueSet(const QList<SymbolKind> &valueSet); + void clearValueSet() { remove(valueSetKey); } + + bool isValid(QStringList *error) const override + { return checkOptionalArray<int>(error, valueSetKey); } + }; + + // Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. + Utils::optional<SymbolKindCapabilities> symbolKind() const + { return optionalValue<SymbolKindCapabilities>(symbolKindKey); } + void setSymbolKind(const SymbolKindCapabilities &symbolKind) { insert(symbolKindKey, symbolKind); } + void clearSymbolKind() { remove(symbolKindKey); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentClientCapabilities : public JsonObject +{ +public: + TextDocumentClientCapabilities(); + using JsonObject::JsonObject; + + class SynchronizationCapabilities : public DynamicRegistrationCapabilities + { + public: + SynchronizationCapabilities(); + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + + // The client supports sending will save notifications. + Utils::optional<bool> willSave() const { return optionalValue<bool>(willSaveKey); } + void setWillSave(bool willSave) { insert(willSaveKey, willSave); } + void clearWillSave() { remove(willSaveKey); } + + /* + * The client supports sending a will save request and + * waits for a response providing text edits which will + * be applied to the document before it is saved. + */ + Utils::optional<bool> willSaveWaitUntil() const + { return optionalValue<bool>(willSaveWaitUntilKey); } + void setWillSaveWaitUntil(bool willSaveWaitUntil) + { insert(willSaveWaitUntilKey, willSaveWaitUntil); } + void clearWillSaveWaitUntil() { remove(willSaveWaitUntilKey); } + + // The client supports did save notifications. + Utils::optional<bool> didSave() const { return optionalValue<bool>(didSaveKey); } + void setDidSave(bool didSave) { insert(didSaveKey, didSave); } + void clearDidSave() { remove(didSaveKey); } + + bool isValid(QStringList *error) const override; + }; + + Utils::optional<SynchronizationCapabilities> synchronization() const + { return optionalValue<SynchronizationCapabilities>(synchronizationKey); } + void setSynchronization(const SynchronizationCapabilities &synchronization) + { insert(synchronizationKey, synchronization); } + void clearSynchronization() { remove(synchronizationKey); } + + class CompletionCapabilities : public DynamicRegistrationCapabilities + { + public: + CompletionCapabilities(); + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + + class CompletionItemCapbilities : public JsonObject + { + public: + CompletionItemCapbilities(); + using JsonObject::JsonObject; + + /* + * Client supports snippets as insert text. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are linked, + * that is typing in one will update others too. + */ + Utils::optional<bool> snippetSupport() const + { return optionalValue<bool>(snippetSupportKey); } + void setSnippetSupport(bool snippetSupport) + { insert(snippetSupportKey, snippetSupport); } + void clearSnippetSupport() { remove(snippetSupportKey); } + + // Client supports commit characters on a completion item. + Utils::optional<bool> commitCharacterSupport() const + { return optionalValue<bool>(commitCharacterSupportKey); } + void setCommitCharacterSupport(bool commitCharacterSupport) + { insert(commitCharacterSupportKey, commitCharacterSupport); } + void clearCommitCharacterSupport() { remove(commitCharacterSupportKey); } + + /* + * Client supports the follow content formats for the documentation + * property. The order describes the preferred format of the client. + */ + Utils::optional<QList<MarkupKind>> documentationFormat() const; + void setDocumentationFormat(const QList<MarkupKind> &documentationFormat); + void clearDocumentationFormat() { remove(documentationFormatKey); } + + bool isValid(QStringList *error) const override + { + return checkOptional<bool>(error, snippetSupportKey) + && checkOptional<bool>(error, commitCharacterSupportKey) + && checkOptionalArray<int>(error, documentationFormatKey); + } + }; + + // The client supports the following `CompletionItem` specific capabilities. + Utils::optional<CompletionItemCapbilities> completionItem() const + { return optionalValue<CompletionItemCapbilities>(completionItemKey); } + void setCompletionItem(const CompletionItemCapbilities &completionItem) + { insert(completionItemKey, completionItem); } + void clearCompletionItem() { remove(completionItemKey); } + + class CompletionItemKindCapabilities : public JsonObject + { + public: + CompletionItemKindCapabilities(); + using JsonObject::JsonObject; + /* + * The completion item kind values the client supports. When this + * property exists the client also guarantees that it will + * handle values outside its set gracefully and falls back + * to a default value when unknown. + * + * If this property is not present the client only supports + * the completion items kinds from `Text` to `Reference` as defined in + * the initial version of the protocol. + */ + Utils::optional<QList<CompletionItemKind::Kind>> valueSet() const; + void setValueSet(const QList<CompletionItemKind::Kind> &valueSet); + void clearValueSet() { remove(valueSetKey); } + + bool isValid(QStringList *error) const override + { return checkOptionalArray<int>(error, valueSetKey); } + }; + + Utils::optional<CompletionItemKindCapabilities> completionItemKind() const + { return optionalValue<CompletionItemKindCapabilities>(completionItemKindKey); } + void setCompletionItemKind(const CompletionItemKindCapabilities &completionItemKind) + { insert(completionItemKindKey, completionItemKind); } + void clearCompletionItemKind() { remove(completionItemKindKey); } + + /* + * The client supports to send additional context information for a + * `textDocument/completion` request. + */ + Utils::optional<bool> contextSupport() const { return optionalValue<bool>(contextSupportKey); } + void setContextSupport(bool contextSupport) { insert(contextSupportKey, contextSupport); } + void clearContextSupport() { remove(contextSupportKey); } + + bool isValid(QStringList *error) const override; + }; + + // Capabilities specific to the `textDocument/completion` + Utils::optional<CompletionCapabilities> completion() const + { return optionalValue<CompletionCapabilities>(completionKey); } + void setCompletion(const CompletionCapabilities &completion) + { insert(completionKey, completion); } + void clearCompletion() { remove(completionKey); } + + class HoverCapabilities : public DynamicRegistrationCapabilities + { + public: + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + /* + * Client supports the follow content formats for the content + * property. The order describes the preferred format of the client. + */ + Utils::optional<QList<MarkupKind>> contentFormat() const; + void setContentFormat(const QList<MarkupKind> &contentFormat); + void clearContentFormat() { remove(contentFormatKey); } + + bool isValid(QStringList *error) const override; + }; + + Utils::optional<HoverCapabilities> hover() const { return optionalValue<HoverCapabilities>(hoverKey); } + void setHover(const HoverCapabilities &hover) { insert(hoverKey, hover); } + void clearHover() { remove(hoverKey); } + + class SignatureHelpCapabilities : public DynamicRegistrationCapabilities + { + public: + using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities; + + class SignatureInformationCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + /* + * Client supports the follow content formats for the documentation + * property. The order describes the preferred format of the client. + */ + Utils::optional<QList<MarkupKind>> documentationFormat() const; + void setDocumentationFormat(const QList<MarkupKind> &documentationFormat); + void clearDocumentationFormat() { remove(documentationFormatKey); } + + bool isValid(QStringList *error) const override + { return checkOptionalArray<int>(error, documentationFormatKey); } + }; + + // The client supports the following `SignatureInformation` specific properties. + Utils::optional<SignatureHelpCapabilities> signatureInformation() const + { return optionalValue<SignatureHelpCapabilities>(signatureInformationKey); } + void setSignatureInformation(const SignatureHelpCapabilities &signatureInformation) + { insert(signatureInformationKey, signatureInformation); } + void clearSignatureInformation() { remove(signatureInformationKey); } + + bool isValid(QStringList *error) const override; + }; + + // Capabilities specific to the `textDocument/signatureHelp` + Utils::optional<SignatureHelpCapabilities> signatureHelp() const + { return optionalValue<SignatureHelpCapabilities>(signatureHelpKey); } + void setSignatureHelp(const SignatureHelpCapabilities &signatureHelp) + { insert(signatureHelpKey, signatureHelp); } + void clearSignatureHelp() { remove(signatureHelpKey); } + + // Whether references supports dynamic registration. + Utils::optional<DynamicRegistrationCapabilities> references() const + { return optionalValue<DynamicRegistrationCapabilities>(referencesKey); } + void setReferences(const DynamicRegistrationCapabilities &references) + { insert(referencesKey, references); } + void clearReferences() { remove(referencesKey); } + + // Whether document highlight supports dynamic registration. + Utils::optional<DynamicRegistrationCapabilities> documentHighlight() const + { return optionalValue<DynamicRegistrationCapabilities>(documentHighlightKey); } + void setDocumentHighlight(const DynamicRegistrationCapabilities &documentHighlight) + { insert(documentHighlightKey, documentHighlight); } + void clearDocumentHighlight() { remove(documentHighlightKey); } + + // Capabilities specific to the `textDocument/documentSymbol` + Utils::optional<SymbolCapabilities> documentSymbol() const + { return optionalValue<SymbolCapabilities>(documentSymbolKey); } + void setDocumentSymbol(const SymbolCapabilities &documentSymbol) + { insert(documentSymbolKey, documentSymbol); } + void clearDocumentSymbol() { remove(documentSymbolKey); } + + // Whether formatting supports dynamic registration. + Utils::optional<DynamicRegistrationCapabilities> formatting() const + { return optionalValue<DynamicRegistrationCapabilities>(formattingKey); } + void setFormatting(const DynamicRegistrationCapabilities &formatting) + { insert(formattingKey, formatting); } + void clearFormatting() { remove(formattingKey); } + + // Whether range formatting supports dynamic registration. + Utils::optional<DynamicRegistrationCapabilities> rangeFormatting() const + { return optionalValue<DynamicRegistrationCapabilities>(rangeFormattingKey); } + void setRangeFormatting(const DynamicRegistrationCapabilities &rangeFormatting) + { insert(rangeFormattingKey, rangeFormatting); } + void clearRangeFormatting() { remove(rangeFormattingKey); } + + // Whether on type formatting supports dynamic registration. + Utils::optional<DynamicRegistrationCapabilities> onTypeFormatting() const + { return optionalValue<DynamicRegistrationCapabilities>(onTypeFormattingKey); } + void setOnTypeFormatting(const DynamicRegistrationCapabilities &onTypeFormatting) + { insert(onTypeFormattingKey, onTypeFormatting); } + void clearOnTypeFormatting() { remove(onTypeFormattingKey); } + + // Whether definition supports dynamic registration. + Utils::optional<DynamicRegistrationCapabilities> definition() const + { return optionalValue<DynamicRegistrationCapabilities>(definitionKey); } + void setDefinition(const DynamicRegistrationCapabilities &definition) + { insert(definitionKey, definition); } + void clearDefinition() { remove(definitionKey); } + + /* + * Whether typeDefinition supports dynamic registration. If this is set to `true` + * the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + * return value for the corresponding server capability as well. + */ + Utils::optional<DynamicRegistrationCapabilities> typeDefinition() const + { return optionalValue<DynamicRegistrationCapabilities>(typeDefinitionKey); } + void setTypeDefinition(const DynamicRegistrationCapabilities &typeDefinition) + { insert(typeDefinitionKey, typeDefinition); } + void clearTypeDefinition() { remove(typeDefinitionKey); } + + /* + * Whether implementation supports dynamic registration. If this is set to `true` + * the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + * return value for the corresponding server capability as well. + */ + Utils::optional<DynamicRegistrationCapabilities> implementation() const + { return optionalValue<DynamicRegistrationCapabilities>(implementationKey); } + void setImplementation(const DynamicRegistrationCapabilities &implementation) + { insert(implementationKey, implementation); } + void clearImplementation() { remove(implementationKey); } + + // Whether code action supports dynamic registration. + Utils::optional<DynamicRegistrationCapabilities> codeAction() const + { return optionalValue<DynamicRegistrationCapabilities>(codeActionKey); } + void setCodeAction(const DynamicRegistrationCapabilities &codeAction) + { insert(codeActionKey, codeAction); } + void clearCodeAction() { remove(codeActionKey); } + + // Whether code lens supports dynamic registration. + Utils::optional<DynamicRegistrationCapabilities> codeLens() const + { return optionalValue<DynamicRegistrationCapabilities>(codeLensKey); } + void setCodeLens(const DynamicRegistrationCapabilities &codeLens) + { insert(codeLensKey, codeLens); } + void clearCodeLens() { remove(codeLensKey); } + + // Whether document link supports dynamic registration. + Utils::optional<DynamicRegistrationCapabilities> documentLink() const + { return optionalValue<DynamicRegistrationCapabilities>(documentLinkKey); } + void setDocumentLink(const DynamicRegistrationCapabilities &documentLink) + { insert(documentLinkKey, documentLink); } + void clearDocumentLink() { remove(documentLinkKey); } + + /* + * Whether colorProvider supports dynamic registration. If this is set to `true` + * the client supports the new `(ColorProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)` + * return value for the corresponding server capability as well. + */ + Utils::optional<DynamicRegistrationCapabilities> colorProvider() const + { return optionalValue<DynamicRegistrationCapabilities>(colorProviderKey); } + void setColorProvider(const DynamicRegistrationCapabilities &colorProvider) + { insert(colorProviderKey, colorProvider); } + void clearColorProvider() { remove(colorProviderKey); } + + // Whether rename supports dynamic registration. + Utils::optional<DynamicRegistrationCapabilities> rename() const + { return optionalValue<DynamicRegistrationCapabilities>(renameKey); } + void setRename(const DynamicRegistrationCapabilities &rename) + { insert(renameKey, rename); } + void clearRename() { remove(renameKey); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceClientCapabilities : public JsonObject +{ +public: + WorkspaceClientCapabilities(); + using JsonObject::JsonObject; + + /* + * The client supports applying batch edits to the workspace by supporting the request + * 'workspace/applyEdit' + */ + Utils::optional<bool> applyEdit() const { return optionalValue<bool>(applyEditKey); } + void setApplyEdit(bool applyEdit) { insert(applyEditKey, applyEdit); } + void clearApplyEdit() { remove(applyEditKey); } + + class WorkspaceEditCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + // The client supports versioned document changes in `WorkspaceEdit`s + Utils::optional<bool> documentChanges() const + { return optionalValue<bool>(documentChangesKey); } + void setDocumentChanges(bool documentChanges) + { insert(documentChangesKey, documentChanges); } + void clearDocumentChanges() { remove(documentChangesKey); } + + bool isValid(QStringList *error) const override + { return checkOptional<bool>(error, documentChangesKey); } + }; + + // Capabilities specific to `WorkspaceEdit`s + Utils::optional<WorkspaceEditCapabilities> workspaceEdit() const + { return optionalValue<WorkspaceEditCapabilities>(workspaceEditKey); } + void setWorkspaceEdit(const WorkspaceEditCapabilities &workspaceEdit) + { insert(workspaceEditKey, workspaceEdit); } + void clearWorkspaceEdit() { remove(workspaceEditKey); } + + // Capabilities specific to the `workspace/didChangeConfiguration` notification. + Utils::optional<DynamicRegistrationCapabilities> didChangeConfiguration() const + { return optionalValue<DynamicRegistrationCapabilities>(didChangeConfigurationKey); } + void setDidChangeConfiguration(const DynamicRegistrationCapabilities &didChangeConfiguration) + { insert(didChangeConfigurationKey, didChangeConfiguration); } + void clearDidChangeConfiguration() { remove(didChangeConfigurationKey); } + + // Capabilities specific to the `workspace/didChangeWatchedFiles` notification. + Utils::optional<DynamicRegistrationCapabilities> didChangeWatchedFiles() const + { return optionalValue<DynamicRegistrationCapabilities>(didChangeWatchedFilesKey); } + void setDidChangeWatchedFiles(const DynamicRegistrationCapabilities &didChangeWatchedFiles) + { insert(didChangeWatchedFilesKey, didChangeWatchedFiles); } + void clearDidChangeWatchedFiles() { remove(didChangeWatchedFilesKey); } + + // Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. + Utils::optional<SymbolCapabilities> symbol() const + { return optionalValue<SymbolCapabilities>(symbolKey); } + void setSymbol(const SymbolCapabilities &symbol) { insert(symbolKey, symbol); } + void clearSymbol() { remove(symbolKey); } + + // Capabilities specific to the `workspace/executeCommand` request. + Utils::optional<DynamicRegistrationCapabilities> executeCommand() const + { return optionalValue<DynamicRegistrationCapabilities>(executeCommandKey); } + void setExecuteCommand(const DynamicRegistrationCapabilities &executeCommand) + { insert(executeCommandKey, executeCommand); } + void clearExecuteCommand() { remove(executeCommandKey); } + + // The client has support for workspace folders. Since 3.6.0 + Utils::optional<bool> workspaceFolders() const + { return optionalValue<bool>(workspaceFoldersKey); } + void setWorkspaceFolders(bool workspaceFolders) + { insert(workspaceFoldersKey, workspaceFolders); } + void clearWorkspaceFolders() { remove(workspaceFoldersKey); } + + // The client supports `workspace/configuration` requests. Since 3.6.0 + Utils::optional<bool> configuration() const { return optionalValue<bool>(configurationKey); } + void setConfiguration(bool configuration) { insert(configurationKey, configuration); } + void clearConfiguration() { remove(configurationKey); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ClientCapabilities : public JsonObject +{ +public: + ClientCapabilities(); + using JsonObject::JsonObject; + + // Workspace specific client capabilities. + Utils::optional<WorkspaceClientCapabilities> workspace() const + { return optionalValue<WorkspaceClientCapabilities>(workspaceKey); } + void setWorkspace(const WorkspaceClientCapabilities &workspace) + { insert(workspaceKey, workspace); } + void clearWorkspace() { remove(workspaceKey); } + + // Text document specific client capabilities. + Utils::optional<TextDocumentClientCapabilities> textDocument() const + { return optionalValue<TextDocumentClientCapabilities>(textDocumentKey); } + void setTextDocument(const TextDocumentClientCapabilities &textDocument) + { insert(textDocumentKey, textDocument); } + void clearTextDocument() { remove(textDocumentKey); } + + // Experimental client capabilities. + QJsonValue experimental() const { return value(experimentalKey); } + void setExperimental(const QJsonValue &experimental) { insert(experimentalKey, experimental); } + void clearExperimental() { remove(experimentalKey); } + + bool isValid(QStringList *error) const override; +}; + +} diff --git a/src/libs/languageserverprotocol/completion.cpp b/src/libs/languageserverprotocol/completion.cpp new file mode 100644 index 0000000000..eb5f22cf6a --- /dev/null +++ b/src/libs/languageserverprotocol/completion.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "completion.h" + +namespace LanguageServerProtocol { + +constexpr const char CompletionRequest::methodName[]; +constexpr const char CompletionItemResolveRequest::methodName[]; + +CompletionRequest::CompletionRequest(const CompletionParams ¶ms) + : Request(methodName, params) +{ } + +Utils::optional<MarkupOrString> CompletionItem::documentation() const +{ + QJsonValue documentation = value(documentationKey); + if (documentation.isUndefined()) + return Utils::nullopt; + return MarkupOrString(documentation); +} + +Utils::optional<CompletionItem::InsertTextFormat> CompletionItem::insertTextFormat() const +{ + Utils::optional<int> value = optionalValue<int>(insertTextFormatKey); + return value.has_value() ? Utils::nullopt + : Utils::make_optional(CompletionItem::InsertTextFormat(value.value())); +} + +bool CompletionItem::isValid(QStringList *error) const +{ + return check<QString>(error, labelKey) + && checkOptional<int>(error, kindKey) + && checkOptional<QString>(error, detailKey) + && checkOptional<QString>(error, documentationKey) + && checkOptional<QString>(error, sortTextKey) + && checkOptional<QString>(error, filterTextKey) + && checkOptional<QString>(error, insertTextKey) + && checkOptional<int>(error, insertTextFormatKey) + && checkOptional<TextEdit>(error, textEditKey) + && checkOptionalArray<TextEdit>(error, additionalTextEditsKey) + && checkOptionalArray<QString>(error, commitCharactersKey) + && checkOptional<Command>(error, commandKey) + && checkOptional<QJsonValue>(error, dataKey); +} + +CompletionItemResolveRequest::CompletionItemResolveRequest(const CompletionItem ¶ms) + : Request(methodName, params) +{ } + +bool CompletionList::isValid(QStringList *error) const +{ + return check<bool>(error, isIncompleteKey) + && checkOptionalArray<CompletionItem>(error, itemsKey); + +} + +bool CompletionParams::isValid(QStringList *error) const +{ + return TextDocumentPositionParams::isValid(error) + && checkOptional<CompletionContext>(error, contextKey); +} + +CompletionResult::CompletionResult(const QJsonValue &value) +{ + if (value.isNull()) { + emplace<std::nullptr_t>(nullptr); + } else if (value.isArray()) { + QList<CompletionItem> items; + for (auto arrayElement : value.toArray()) + items << CompletionItem(arrayElement); + emplace<QList<CompletionItem>>(items); + } else if (value.isObject()) { + emplace<CompletionList>(CompletionList(value)); + } +} + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/completion.h b/src/libs/languageserverprotocol/completion.h new file mode 100644 index 0000000000..b05825cd00 --- /dev/null +++ b/src/libs/languageserverprotocol/completion.h @@ -0,0 +1,273 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "jsonrpcmessages.h" +#include "languagefeatures.h" + +namespace LanguageServerProtocol { + +class LANGUAGESERVERPROTOCOL_EXPORT CompletionParams : public TextDocumentPositionParams +{ +public: + using TextDocumentPositionParams::TextDocumentPositionParams; + + enum CompletionTriggerKind { + /** + * Completion was triggered by typing an identifier (24x7 code + * complete), manual invocation (e.g Ctrl+Space) or via API. + */ + Invoked = 1, + /** + * Completion was triggered by a trigger character specified by + * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. + */ + TriggerCharacter = 2, + /// Completion was re-triggered as the current completion list is incomplete. + TriggerForIncompleteCompletions = 3 + }; + + class CompletionContext : public JsonObject + { + public: + using JsonObject::JsonObject; + + /// How the completion was triggered. + CompletionTriggerKind triggerKind() const + { return CompletionTriggerKind(typedValue<int>(triggerKindKey)); } + void setTriggerKind(CompletionTriggerKind triggerKind) + { insert(triggerKindKey, triggerKind); } + + /** + * The trigger character (a single character) that has trigger code complete. + * Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` + */ + Utils::optional<QString> triggerCharacter() const + { return optionalValue<QString>(triggerCharacterKey); } + void setTriggerCharacter(const QString &triggerCharacter) + { insert(triggerCharacterKey, triggerCharacter); } + void clearTriggerCharacter() { remove(triggerCharacterKey); } + + bool isValid(QStringList *error) const override + { + return check<int>(error, triggerKindKey) + && checkOptional<QString>(error, triggerCharacterKey); + } + }; + + /** + * The completion context. This is only available it the client specifies + * to send this using `ClientCapabilities.textDocument.completion.contextSupport === true` + */ + Utils::optional<CompletionContext> context() const + { return optionalValue<CompletionContext>(contextKey); } + void setContext(const CompletionContext &context) + { insert(contextKey, context); } + void clearContext() { remove(contextKey); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CompletionItem : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /** + * The label of this completion item. By default also the text that is inserted when selecting + * this completion. + */ + QString label() const { return typedValue<QString>(labelKey); } + void setLabel(const QString &label) { insert(labelKey, label); } + + /// The kind of this completion item. Based of the kind an icon is chosen by the editor. + Utils::optional<int> kind() const { return optionalValue<int>(kindKey); } + void setKind(int kind) { insert(kindKey, kind); } + void clearKind() { remove(kindKey); } + + /// A human-readable string with additional information about this item, like type information. + Utils::optional<QString> detail() const { return optionalValue<QString>(detailKey); } + void setDetail(const QString &detail) { insert(detailKey, detail); } + void clearDetail() { remove(detailKey); } + + /// A human-readable string that represents a doc-comment. + Utils::optional<MarkupOrString> documentation() const; + void setDocumentation(const MarkupOrString &documentation) + { insert(documentationKey, documentation.toJson()); } + void cleatDocumentation() { remove(documentationKey); } + + /// A string that should be used when comparing this item + /// with other items. When `falsy` the label is used. + Utils::optional<QString> sortText() const { return optionalValue<QString>(sortTextKey); } + void setSortText(const QString &sortText) { insert(sortTextKey, sortText); } + void clearSortText() { remove(sortTextKey); } + + /// A string that should be used when filtering a set of + /// completion items. When `falsy` the label is used. + Utils::optional<QString> filterText() const { return optionalValue<QString>(filterTextKey); } + void setFilterText(const QString &filterText) { insert(filterTextKey, filterText); } + void clearFilterText() { remove(filterTextKey); } + + /** + * A string that should be inserted into a document when selecting + * this completion. When `falsy` the label is used. + * + * The `insertText` is subject to interpretation by the client side. + * Some tools might not take the string literally. For example + * VS Code when code complete is requested in this example `con<cursor position>` + * and a completion item with an `insertText` of `console` is provided it + * will only insert `sole`. Therefore it is recommended to use `textEdit` instead + * since it avoids additional client side interpretation. + * + * @deprecated Use textEdit instead. + */ + Utils::optional<QString> insertText() const { return optionalValue<QString>(insertTextKey); } + void setInsertText(const QString &insertText) { insert(insertTextKey, insertText); } + void clearInsertText() { remove(insertTextKey); } + + enum InsertTextFormat { + /// The primary text to be inserted is treated as a plain string. + PlainText = 1, + + /** + * The primary text to be inserted is treated as a snippet. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are linked, + * that is typing in one will update others too. + */ + Snippet = 2 + }; + + /// The format of the insert text. The format applies to both the `insertText` property + /// and the `newText` property of a provided `textEdit`. + Utils::optional<InsertTextFormat> insertTextFormat() const; + void setInsertTextFormat(const InsertTextFormat &insertTextFormat) + { insert(insertTextFormatKey, insertTextFormat); } + void clearInsertTextFormat() { remove(insertTextFormatKey); } + + /** + * An edit which is applied to a document when selecting this completion. When an edit is provided the value of + * `insertText` is ignored. + * + * *Note:* The range of the edit must be a single line range and it must contain the position at which completion + * has been requested. + */ + Utils::optional<TextEdit> textEdit() const { return optionalValue<TextEdit>(textEditKey); } + void setTextEdit(const TextEdit &textEdit) { insert(textEditKey, textEdit); } + void clearTextEdit() { remove(textEditKey); } + + /** + * An optional array of additional text edits that are applied when + * selecting this completion. Edits must not overlap with the main edit + * nor with themselves. + */ + Utils::optional<QList<TextEdit>> additionalTextEdits() const + { return optionalArray<TextEdit>(additionalTextEditsKey); } + void setAdditionalTextEdits(const QList<TextEdit> &additionalTextEdits) + { insertArray(additionalTextEditsKey, additionalTextEdits); } + void clearAdditionalTextEdits() { remove(additionalTextEditsKey); } + + /** + * An optional set of characters that when pressed while this completion is active will accept it first and + * then type that character. *Note* that all commit characters should have `length=1` and that superfluous + * characters will be ignored. + */ + Utils::optional<QList<QString>> commitCharacters() const + { return optionalArray<QString>(commitCharactersKey); } + void setCommitCharacters(const QList<QString> &commitCharacters) + { insertArray(commitCharactersKey, commitCharacters); } + void clearCommitCharacters() { remove(commitCharactersKey); } + + /** + * An optional command that is executed *after* inserting this completion. *Note* that + * additional modifications to the current document should be described with the + * additionalTextEdits-property. + */ + Utils::optional<Command> command() const { return optionalValue<Command>(commandKey); } + void setCommand(const Command &command) { insert(commandKey, command); } + void clearCommand() { remove(commandKey); } + + /** + * An data entry field that is preserved on a completion item between + * a completion and a completion resolve request. + */ + Utils::optional<QJsonValue> data() const { return optionalValue<QJsonValue>(dataKey); } + void setData(const QJsonValue &data) { insert(dataKey, data); } + void clearData() { remove(dataKey); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CompletionList : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /** + * This list it not complete. Further typing should result in recomputing + * this list. + */ + bool isIncomplete() const { return typedValue<bool>(isIncompleteKey); } + void setIsIncomplete(bool isIncomplete) { insert(isIncompleteKey, isIncomplete); } + + /// The completion items. + Utils::optional<QList<CompletionItem>> items() const { return array<CompletionItem>(itemsKey); } + void setItems(const QList<CompletionItem> &items) { insertArray(itemsKey, items); } + void clearItems() { remove(itemsKey); } + + bool isValid(QStringList *error) const override; +}; + +/// The result of a completion is CompletionItem[] | CompletionList | null +class LANGUAGESERVERPROTOCOL_EXPORT CompletionResult + : public Utils::variant<QList<CompletionItem>, CompletionList, std::nullptr_t> +{ +public: + using variant::variant; + CompletionResult(const QJsonValue &value); +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CompletionRequest : public Request< + CompletionResult, LanguageClientNull, CompletionParams> +{ +public: + CompletionRequest(const CompletionParams ¶ms = CompletionParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/completion"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CompletionItemResolveRequest : public Request< + CompletionItem, LanguageClientNull, CompletionItem> +{ +public: + CompletionItemResolveRequest(const CompletionItem ¶ms = CompletionItem()); + using Request::Request; + constexpr static const char methodName[] = "completionItem/resolve"; +}; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/diagnostics.cpp b/src/libs/languageserverprotocol/diagnostics.cpp new file mode 100644 index 0000000000..86db870b2a --- /dev/null +++ b/src/libs/languageserverprotocol/diagnostics.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "diagnostics.h" + +namespace LanguageServerProtocol { + +constexpr const char PublishDiagnosticsNotification::methodName[]; + +PublishDiagnosticsNotification::PublishDiagnosticsNotification(const PublishDiagnosticsParams ¶ms) + : Notification(methodName, params) { } + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/diagnostics.h b/src/libs/languageserverprotocol/diagnostics.h new file mode 100644 index 0000000000..30f5081f07 --- /dev/null +++ b/src/libs/languageserverprotocol/diagnostics.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "jsonrpcmessages.h" +#include "languagefeatures.h" + +namespace LanguageServerProtocol { + +class PublishDiagnosticsParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + QList<Diagnostic> diagnostics() const { return array<Diagnostic>(diagnosticsKey); } + void setDiagnostics(const QList<Diagnostic> &diagnostics) + { insertArray(diagnosticsKey, diagnostics); } + + bool isValid(QStringList *error) const override + { return checkArray<Diagnostic>(error, diagnosticsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT PublishDiagnosticsNotification : public Notification<PublishDiagnosticsParams> +{ +public: + PublishDiagnosticsNotification( + const PublishDiagnosticsParams ¶ms = PublishDiagnosticsParams()); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/publishDiagnostics"; +}; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/icontent.h b/src/libs/languageserverprotocol/icontent.h new file mode 100644 index 0000000000..0991838f64 --- /dev/null +++ b/src/libs/languageserverprotocol/icontent.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "basemessage.h" + +#include <utils/mimetypes/mimetype.h> +#include <utils/qtcassert.h> +#include <utils/variant.h> + +#include <QByteArray> +#include <QDebug> +#include <QHash> +#include <QJsonValue> + +#include <functional> + +namespace LanguageServerProtocol { + +class IContent; + +class LANGUAGESERVERPROTOCOL_EXPORT MessageId : public Utils::variant<int, QString> +{ +public: + MessageId() = default; + MessageId(int id) : variant(id) {} + MessageId(QString id) : variant(id) {} + explicit MessageId(const QJsonValue &value) + { + if (value.isUndefined()) + return; + QTC_CHECK(value.isDouble() || value.isString()); + if (value.isDouble()) + *this = value.toInt(); + else if (value.isString()) + *this = value.toString(); + } + + QJsonValue toJson() const + { + QTC_CHECK(Utils::holds_alternative<int>(*this) || Utils::holds_alternative<QString>(*this)); + if (auto id = Utils::get_if<int>(this)) + return *id; + if (auto id = Utils::get_if<QString>(this)) + return *id; + return QJsonValue(); + } + + bool isValid(QStringList *error = nullptr) const + { + if (Utils::holds_alternative<int>(*this) || Utils::holds_alternative<QString>(*this)) + return true; + if (error) + error->append("Expected int or string as MessageId"); + return false; + } +}; + +using ResponseHandler = std::function<void(const QByteArray &, QTextCodec *)>; +using ResponseHandlers = std::function<void(MessageId, const QByteArray &, QTextCodec *)>; +using MethodHandler = std::function<void(const QString, MessageId, const IContent *)>; + +inline LANGUAGESERVERPROTOCOL_EXPORT uint qHash(const LanguageServerProtocol::MessageId &id) +{ + if (Utils::holds_alternative<int>(id)) + return QT_PREPEND_NAMESPACE(qHash(Utils::get<int>(id))); + if (Utils::holds_alternative<QString>(id)) + return QT_PREPEND_NAMESPACE(qHash(Utils::get<QString>(id))); + return QT_PREPEND_NAMESPACE(qHash(0)); +} + +template <typename Error> +inline LANGUAGESERVERPROTOCOL_EXPORT QDebug operator<<(QDebug stream, + const LanguageServerProtocol::MessageId &id) +{ + if (Utils::holds_alternative<int>(id)) + stream << Utils::get<int>(id); + else + stream << Utils::get<QString>(id); + return stream; +} + +class LANGUAGESERVERPROTOCOL_EXPORT IContent +{ +public: + virtual ~IContent() = default; + + virtual QByteArray toRawData() const = 0; + virtual QByteArray mimeType() const = 0; + virtual bool isValid(QString *errorMessage) const = 0; + + virtual void registerResponseHandler(QHash<MessageId, ResponseHandler> *) const { } + + BaseMessage toBaseMessage() const + { return BaseMessage(mimeType(), toRawData()); } +}; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/initializemessages.cpp b/src/libs/languageserverprotocol/initializemessages.cpp new file mode 100644 index 0000000000..0464f8a13c --- /dev/null +++ b/src/libs/languageserverprotocol/initializemessages.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "initializemessages.h" + +#include <QCoreApplication> + +namespace LanguageServerProtocol { + +constexpr const char InitializeRequest::methodName[]; +constexpr const char InitializeNotification::methodName[]; +constexpr Trace::Values s_trace = Trace::off; + +Trace Trace::fromString(const QString &val) +{ + if (val == "messages") + return messages; + if (val == "verbose") + return verbose; + return off; +} + +#define RETURN_CASE(name) case Trace::name: return QString(#name); +QString Trace::toString() const +{ + switch (m_value) { + RETURN_CASE(off); + RETURN_CASE(messages); + RETURN_CASE(verbose); + } + return QString("off"); +} +#undef RETURN_CASE + +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemCapbilities::CompletionItemCapbilities() +{ +} + +Utils::optional<QList<MarkupKind>> +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemCapbilities:: +documentationFormat() const +{ + Utils::optional<QList<int>> array = optionalArray<int>(documentationFormatKey); + if (!array) + return Utils::nullopt; + return Utils::make_optional(Utils::transform(array.value(), [] (int value) { + return static_cast<MarkupKind>(value); + })); +} + +void +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemCapbilities:: +setDocumentationFormat(const QList<MarkupKind> &documentationFormat) +{ + insert(documentationFormatKey, enumArrayToJsonArray<MarkupKind>(documentationFormat)); +} + +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities::CompletionItemKindCapabilities() +{ + setValueSet({CompletionItemKind::Text, CompletionItemKind::Method, CompletionItemKind::Function, + CompletionItemKind::Constructor, CompletionItemKind::Field, CompletionItemKind::Variable, + CompletionItemKind::Class, CompletionItemKind::Interface, CompletionItemKind::Module, + CompletionItemKind::Property, CompletionItemKind::Unit, CompletionItemKind::Value, + CompletionItemKind::Enum, CompletionItemKind::Keyword, CompletionItemKind::Snippet, + CompletionItemKind::Color, CompletionItemKind::File, CompletionItemKind::Reference, + CompletionItemKind::Folder, CompletionItemKind::EnumMember, CompletionItemKind::Constant, + CompletionItemKind::Struct, CompletionItemKind::Event, CompletionItemKind::Operator, + CompletionItemKind::TypeParameter}); +} + +Utils::optional<QList<CompletionItemKind::Kind>> +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities:: +valueSet() const +{ + Utils::optional<QList<int>> array = optionalArray<int>(valueSetKey); + if (!array) + return Utils::nullopt; + return Utils::make_optional(Utils::transform(array.value(), [] (int value) { + return static_cast<CompletionItemKind::Kind>(value); + })); +} + +void +TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities:: +setValueSet(const QList<CompletionItemKind::Kind> &valueSet) +{ + insert(valueSetKey, enumArrayToJsonArray<CompletionItemKind::Kind>(valueSet)); +} + +Utils::optional<QList<MarkupKind> > TextDocumentClientCapabilities::HoverCapabilities::contentFormat() const +{ + Utils::optional<QList<int>> array = optionalArray<int>(contentFormatKey); + if (!array) + return Utils::nullopt; + return Utils::make_optional(Utils::transform(array.value(), [] (int value) { + return static_cast<MarkupKind>(value); + })); +} + +void TextDocumentClientCapabilities::HoverCapabilities::setContentFormat(const QList<MarkupKind> &contentFormat) +{ + insert(contentFormatKey, enumArrayToJsonArray<MarkupKind>(contentFormat)); +} + +Utils::optional<QList<MarkupKind>> +TextDocumentClientCapabilities::SignatureHelpCapabilities::SignatureInformationCapabilities:: +documentationFormat() const +{ + Utils::optional<QList<int>> array = optionalArray<int>(documentationFormatKey); + if (!array) + return Utils::nullopt; + return Utils::make_optional(Utils::transform(array.value(), [] (int value) { + return static_cast<MarkupKind>(value); + })); +} + +void +TextDocumentClientCapabilities::SignatureHelpCapabilities::SignatureInformationCapabilities:: +setDocumentationFormat(const QList<MarkupKind> &documentationFormat) +{ + insert(documentationFormatKey, enumArrayToJsonArray<MarkupKind>(documentationFormat)); +} + +InitializeParams::InitializeParams() +{ + setProcessId(int(QCoreApplication::applicationPid())); + setRootUri(LanguageClientValue<DocumentUri>()); + setCapabilities(ClientCapabilities()); + setTrace(s_trace); +} + +Utils::optional<Trace> InitializeParams::trace() const +{ + const QJsonValue &traceValue = value(traceKey); + if (traceValue.isUndefined()) + return Utils::nullopt; + return Utils::make_optional(Trace(traceValue.toString())); +} + +bool InitializeParams::isValid(QStringList *error) const +{ + return check<int, std::nullptr_t>(error, processIdKey) + && checkOptional<QString, std::nullptr_t>(error, rootPathKey) + && checkOptional<QString, std::nullptr_t>(error, rootUriKey) + && check<ClientCapabilities>(error, capabilitiesKey) + && checkOptional<int>(error, traceKey) + && (checkOptional<std::nullptr_t>(error, workSpaceFoldersKey) + || checkOptionalArray<WorkSpaceFolder>(error, workSpaceFoldersKey)); +} + +InitializeRequest::InitializeRequest(const InitializeParams ¶ms) + : Request(methodName, params) +{ } + +InitializeNotification::InitializeNotification() + : Notification(methodName) +{ } + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/initializemessages.h b/src/libs/languageserverprotocol/initializemessages.h new file mode 100644 index 0000000000..d9ad330a8a --- /dev/null +++ b/src/libs/languageserverprotocol/initializemessages.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "clientcapabilities.h" +#include "jsonrpcmessages.h" +#include "lsptypes.h" +#include "servercapabilities.h" + +namespace LanguageServerProtocol { + +class LANGUAGESERVERPROTOCOL_EXPORT Trace +{ +public: + enum Values + { + off, + messages, + verbose + }; + + Trace() = default; + Trace(Values val) : m_value(val) {} + Trace(QString val) : Trace(fromString(val)) {} + + static Trace fromString(const QString& val); + QString toString() const; + +private: + Values m_value = off; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT InitializeParams : public JsonObject +{ +public: + InitializeParams(); + using JsonObject::JsonObject; + + /* + * The process Id of the parent process that started + * the server. Is null if the process has not been started by another process. + * If the parent process is not alive then the server should exit (see exit notification) + * its process. + */ + LanguageClientValue<int> processId() const { return clientValue<int>(processIdKey); } + void setProcessId(const LanguageClientValue<int> &id) { insert(processIdKey, id.toJson()); } + + /* + * The rootPath of the workspace. Is null + * if no folder is open. + * + * @deprecated in favor of rootUri. + */ + Utils::optional<LanguageClientValue<QString>> rootPath() const + { return optionalClientValue<QString>(rootPathKey); } + void setRootPath(const LanguageClientValue<QString> &path) + { insert(rootPathKey, path.toJson()); } + void clearRootPath() { remove(rootPathKey); } + + /* + * The rootUri of the workspace. Is null if no + * folder is open. If both `rootPath` and `rootUri` are set + * `rootUri` wins. + */ + LanguageClientValue<DocumentUri> rootUri() const + { return clientValue<QString>(rootUriKey).transform<DocumentUri>(); } + void setRootUri(const LanguageClientValue<DocumentUri> &uri) + { insert(rootUriKey, uri.toJson()); } + + // User provided initialization options. + Utils::optional<QJsonObject> initializationOptions() const + { return optionalValue<QJsonObject>(initializationOptionsKey); } + void setInitializationOptions(const QJsonObject &options) + { insert(initializationOptionsKey, options); } + void clearInitializationOptions() { remove(initializationOptionsKey); } + + // The capabilities provided by the client (editor or tool) + ClientCapabilities capabilities() const { return typedValue<ClientCapabilities>(capabilitiesKey); } + void setCapabilities(const ClientCapabilities &capabilities) + { insert(capabilitiesKey, capabilities); } + + // The initial trace setting. If omitted trace is disabled ('off'). + Utils::optional<Trace> trace() const; + void setTrace(Trace trace) { insert(traceKey, trace.toString()); } + void clearTrace() { remove(traceKey); } + + /* + * The workspace folders configured in the client when the server starts. + * This property is only available if the client supports workspace folders. + * It can be `null` if the client supports workspace folders but none are + * configured. + * + * Since 3.6.0 + */ + Utils::optional<LanguageClientArray<WorkSpaceFolder>> workSpaceFolders() const + { return optionalClientArray<WorkSpaceFolder>(workSpaceFoldersKey); } + void setWorkSpaceFolders(const LanguageClientArray<WorkSpaceFolder> &folders) + { insert(workSpaceFoldersKey, folders.toJson()); } + void clearWorkSpaceFolders() { remove(workSpaceFoldersKey); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT InitializeNotification : public Notification<LanguageClientNull> +{ +public: + InitializeNotification(); + using Notification::Notification; + constexpr static const char methodName[] = "initialized"; + + bool parametersAreValid(QString * /*errorMessage*/) const final { return true; } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT InitializeResult : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Utils::optional<ServerCapabilities> capabilities() const + { return optionalValue<ServerCapabilities>(capabilitiesKey); } + void setCapabilities(const ServerCapabilities &capabilities) + { insert(capabilitiesKey, capabilities); } + void clearCapabilities() { remove(capabilitiesKey); } + + bool isValid(QStringList *error) const override + { return checkOptional<ServerCapabilities>(error, capabilitiesKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT InitializeError : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /* + * Indicates whether the client execute the following retry logic: + * (1) show the message provided by the ResponseError to the user + * (2) user selects retry or cancel + * (3) if user selected retry the initialize method is sent again. + */ + Utils::optional<bool> retry() const { return optionalValue<bool>(retryKey); } + void setRetry(bool retry) { insert(retryKey, retry); } + void clearRetry() { remove(retryKey); } + + bool isValid(QStringList *error) const override { return checkOptional<bool>(error, retryKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT InitializeRequest : public Request< + InitializeResult, InitializeError, InitializeParams> +{ +public: + InitializeRequest(const InitializeParams ¶ms = InitializeParams()); + using Request::Request; + constexpr static const char methodName[] = "initialize"; +}; + +using InitializeResponse = Response<InitializeResult, InitializeError>; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/jsonkeys.h b/src/libs/languageserverprotocol/jsonkeys.h new file mode 100644 index 0000000000..4a3cfcc55f --- /dev/null +++ b/src/libs/languageserverprotocol/jsonkeys.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +namespace LanguageServerProtocol { + +constexpr char actionsKey[] = "actions"; +constexpr char activeParameterKey[] = "activeParameter"; +constexpr char activeSignatureKey[] = "activeSignature"; +constexpr char addedKey[] = "added"; +constexpr char additionalTextEditsKey[] = "additionalTextEdits"; +constexpr char alphaKey[] = "alpha"; +constexpr char appliedKey[] = "applied"; +constexpr char applyEditKey[] = "applyEdit"; +constexpr char argumentsKey[] = "arguments"; +constexpr char blueKey[] = "blue"; +constexpr char capabilitiesKey[] = "capabilities"; +constexpr char chKey[] = "ch"; +constexpr char changeKey[] = "change"; +constexpr char changeNotificationsKey[] = "changeNotifications"; +constexpr char changesKey[] = "changes"; +constexpr char characterKey[] = "character"; +constexpr char codeActionKey[] = "codeAction"; +constexpr char codeActionProviderKey[] = "codeActionProvider"; +constexpr char codeKey[] = "code"; +constexpr char codeLensKey[] = "codeLens"; +constexpr char codeLensProviderKey[] = "codeLensProvider"; +constexpr char colorInfoKey[] = "colorInfo"; +constexpr char colorKey[] = "color"; +constexpr char colorProviderKey[] = "colorProvider"; +constexpr char commandKey[] = "command"; +constexpr char commandsKey[] = "commands"; +constexpr char commitCharacterSupportKey[] = "commitCharacterSupport"; +constexpr char commitCharactersKey[] = "commitCharacters"; +constexpr char completionItemKey[] = "completionItem"; +constexpr char completionItemKindKey[] = "completionItemKind"; +constexpr char completionKey[] = "completion"; +constexpr char completionProviderKey[] = "completionProvider"; +constexpr char configurationKey[] = "configuration"; +constexpr char containerNameKey[] = "containerName"; +constexpr char contentChangesKey[] = "contentChanges"; +constexpr char contentCharsetName[] = "charset"; +constexpr char contentFormatKey[] = "contentFormat"; +constexpr char contentKey[] = "value"; +constexpr char contentLengthFieldName[] = "Content-Length"; +constexpr char contentTypeFieldName[] = "Content-Type"; +constexpr char contextKey[] = "context"; +constexpr char contextSupportKey[] = "contextSupport"; +constexpr char dataKey[] = "data"; +constexpr char defaultCharset[] = "utf-8"; +constexpr char definitionKey[] = "definition"; +constexpr char definitionProviderKey[] = "definitionProvider"; +constexpr char detailKey[] = "detail"; +constexpr char diagnosticsKey[] = "diagnostics"; +constexpr char didChangeConfigurationKey[] = "didChangeConfiguration"; +constexpr char didChangeWatchedFilesKey[] = "didChangeWatchedFiles"; +constexpr char didSaveKey[] = "didSave"; +constexpr char documentChangesKey[] = "documentChanges"; +constexpr char documentFormattingProviderKey[] = "documentFormattingProvider"; +constexpr char documentHighlightKey[] = "documentHighlight"; +constexpr char documentHighlightProviderKey[] = "documentHighlightProvider"; +constexpr char documentLinkKey[] = "documentLink"; +constexpr char documentLinkProviderKey[] = "documentLinkProvider"; +constexpr char documentRangeFormattingProviderKey[] = "documentRangeFormattingProvider"; +constexpr char documentSelectorKey[] = "documentSelector"; +constexpr char documentSymbolKey[] = "documentSymbol"; +constexpr char documentSymbolProviderKey[] = "documentSymbolProvider"; +constexpr char documentationFormatKey[] = "documentationFormat"; +constexpr char documentationKey[] = "documentation"; +constexpr char dynamicRegistrationKey[] = "dynamicRegistration"; +constexpr char editKey[] = "edit"; +constexpr char editsKey[] = "edits"; +constexpr char endKey[] = "end"; +constexpr char errorKey[] = "error"; +constexpr char eventKey[] = "event"; +constexpr char executeCommandKey[] = "executeCommand"; +constexpr char executeCommandProviderKey[] = "executeCommandProvider"; +constexpr char experimentalKey[] = "experimental"; +constexpr char filterTextKey[] = "filterText"; +constexpr char firstTriggerCharacterKey[] = "firstTriggerCharacter"; +constexpr char formattingKey[] = "formatting"; +constexpr char greenKey[] = "green"; +constexpr char headerFieldSeparator[] = ": "; +constexpr char headerSeparator[] = "\r\n"; +constexpr char hoverKey[] = "hover"; +constexpr char hoverProviderKey[] = "hoverProvider"; +constexpr char idKey[] = "id"; +constexpr char implementationKey[] = "implementation"; +constexpr char implementationProviderKey[] = "implementationProvider"; +constexpr char includeDeclarationKey[] = "includeDeclaration"; +constexpr char includeTextKey[] = "includeText"; +constexpr char initializationOptionsKey[] = "initializationOptions"; +constexpr char insertSpaceKey[] = "insertSpace"; +constexpr char insertTextFormatKey[] = "insertTextFormat"; +constexpr char insertTextKey[] = "insertText"; +constexpr char isIncompleteKey[] = "isIncomplete"; +constexpr char itemsKey[] = "items"; +constexpr char jsonRpcVersionKey[] = "jsonrpc"; +constexpr char kindKey[] = "kind"; +constexpr char labelKey[] = "label"; +constexpr char languageIdKey[] = "languageId"; +constexpr char languageKey[] = "language"; +constexpr char lineKey[] = "line"; +constexpr char locationKey[] = "location"; +constexpr char messageKey[] = "message"; +constexpr char methodKey[] = "method"; +constexpr char moreTriggerCharacterKey[] = "moreTriggerCharacter"; +constexpr char nameKey[] = "name"; +constexpr char newNameKey[] = "newName"; +constexpr char newTextKey[] = "newText"; +constexpr char onTypeFormattingKey[] = "onTypeFormatting"; +constexpr char openCloseKey[] = "openClose"; +constexpr char optionsKey[] = "options"; +constexpr char parametersKey[] = "params"; +constexpr char patternKey[] = "pattern"; +constexpr char positionKey[] = "position"; +constexpr char processIdKey[] = "processId"; +constexpr char queryKey[] = "query"; +constexpr char rangeFormattingKey[] = "rangeFormatting"; +constexpr char rangeKey[] = "range"; +constexpr char rangeLengthKey[] = "rangeLength"; +constexpr char reasonKey[] = "reason"; +constexpr char redKey[] = "red"; +constexpr char referenceProviderKey[] = "referenceProvider"; +constexpr char referencesKey[] = "references"; +constexpr char registerOptionsKey[] = "registerOptions"; +constexpr char registrationsKey[] = "registrations"; +constexpr char removedKey[] = "removed"; +constexpr char renameKey[] = "rename"; +constexpr char renameProviderKey[] = "renameProvider"; +constexpr char resolveProviderKey[] = "resolveProvider"; +constexpr char resultKey[] = "result"; +constexpr char retryKey[] = "retry"; +constexpr char rootPathKey[] = "rootPath"; +constexpr char rootUriKey[] = "rootUri"; +constexpr char saveKey[] = "save"; +constexpr char schemeKey[] = "scheme"; +constexpr char scopeUriKey[] = "scopeUri"; +constexpr char sectionKey[] = "section"; +constexpr char settingsKey[] = "settings"; +constexpr char severityKey[] = "severity"; +constexpr char signatureHelpKey[] = "signatureHelp"; +constexpr char signatureHelpProviderKey[] = "signatureHelpProvider"; +constexpr char signatureInformationKey[] = "signatureInformation"; +constexpr char signaturesKey[] = "signatures"; +constexpr char snippetSupportKey[] = "snippetSupport"; +constexpr char sortTextKey[] = "sortText"; +constexpr char sourceKey[] = "source"; +constexpr char startKey[] = "start"; +constexpr char supportedKey[] = "supported"; +constexpr char symbolKey[] = "symbol"; +constexpr char symbolKindKey[] = "symbolKind"; +constexpr char syncKindKey[] = "syncKind"; +constexpr char synchronizationKey[] = "synchronization"; +constexpr char tabSizeKey[] = "tabSize"; +constexpr char targetKey[] = "target"; +constexpr char textDocumentKey[] = "textDocument"; +constexpr char textDocumentSyncKey[] = "textDocumentSync"; +constexpr char textEditKey[] = "textEdit"; +constexpr char textKey[] = "text"; +constexpr char titleKey[] = "title"; +constexpr char traceKey[] = "trace"; +constexpr char triggerCharacterKey[] = "triggerCharacter"; +constexpr char triggerCharactersKey[] = "triggerCharacters"; +constexpr char triggerKindKey[] = "triggerKind"; +constexpr char typeDefinitionKey[] = "typeDefinition"; +constexpr char typeDefinitionProviderKey[] = "typeDefinitionProvider"; +constexpr char typeKey[] = "type"; +constexpr char unregistrationsKey[] = "unregistrations"; +constexpr char uriKey[] = "uri"; +constexpr char valueKey[] = "value"; +constexpr char valueSetKey[] = "valueSet"; +constexpr char versionKey[] = "version"; +constexpr char willSaveKey[] = "willSave"; +constexpr char willSaveWaitUntilKey[] = "willSaveWaitUntil"; +constexpr char workSpaceFoldersKey[] = "workSpaceFolders"; +constexpr char workspaceEditKey[] = "workspaceEdit"; +constexpr char workspaceFoldersKey[] = "workspaceFolders"; +constexpr char workspaceKey[] = "workspace"; +constexpr char workspaceSymbolProviderKey[] = "workspaceSymbolProvider"; + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/jsonobject.cpp b/src/libs/languageserverprotocol/jsonobject.cpp new file mode 100644 index 0000000000..ea61092ec3 --- /dev/null +++ b/src/libs/languageserverprotocol/jsonobject.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "jsonobject.h" + +#include <QCoreApplication> + +namespace LanguageServerProtocol { + +template <> +bool JsonObject::checkVal<QString>(QStringList *errorHierarchy, const QJsonValue &val) +{ return checkType(val.type(), QJsonValue::String, errorHierarchy); } + +template <> +bool JsonObject::checkVal<int>(QStringList *errorHierarchy, const QJsonValue &val) +{ return checkType(val.type(), QJsonValue::Double, errorHierarchy); } + +template <> +bool JsonObject::checkVal<double>(QStringList *errorHierarchy, const QJsonValue &val) +{ return checkType(val.type(), QJsonValue::Double, errorHierarchy); } + +template <> +bool JsonObject::checkVal<bool>(QStringList *errorHierarchy, const QJsonValue &val) +{ return checkType(val.type(), QJsonValue::Bool, errorHierarchy); } + +template <> +bool JsonObject::checkVal<std::nullptr_t>(QStringList *errorHierarchy, const QJsonValue &val) +{ return checkType(val.type(), QJsonValue::Null, errorHierarchy); } + +template<> +bool JsonObject::checkVal<QJsonArray>(QStringList *errorHierarchy, const QJsonValue &val) +{ return checkType(val.type(), QJsonValue::Array, errorHierarchy); } + +template<> +bool JsonObject::checkVal<QJsonValue>(QStringList * /*errorHierarchy*/, const QJsonValue &/*val*/) +{ return true; } + +JsonObject &JsonObject::operator=(const JsonObject &other) = default; + +JsonObject &JsonObject::operator=(JsonObject &&other) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + m_jsonObject.swap(other.m_jsonObject); +#else + m_jsonObject = other.m_jsonObject; // NOTE use QJsonObject::swap when minimum required Qt version >= 5.10 +#endif + return *this; +} + +QJsonObject::iterator JsonObject::insert(const QString &key, const JsonObject &object) +{ + return m_jsonObject.insert(key, object.m_jsonObject); +} + +QJsonObject::iterator JsonObject::insert(const QString &key, const QJsonValue &value) +{ + return m_jsonObject.insert(key, value); +} + +bool JsonObject::checkKey(QStringList *errorHierarchy, const QString &key, + const std::function<bool (const QJsonValue &)> &predicate) const +{ + const bool valid = predicate(m_jsonObject.value(key)); + if (!valid && errorHierarchy) + errorHierarchy->append(key); + return valid; +} + +QString JsonObject::valueTypeString(QJsonValue::Type type) +{ + switch (type) { + case QJsonValue::Null: return QString("Null"); + case QJsonValue::Bool: return QString("Bool"); + case QJsonValue::Double: return QString("Double"); + case QJsonValue::String: return QString("String"); + case QJsonValue::Array: return QString("Array"); + case QJsonValue::Object: return QString("Object"); + case QJsonValue::Undefined: return QString("Undefined"); + } + return QString(); +} + +QString JsonObject::errorString(QJsonValue::Type expected, QJsonValue::Type actual) +{ + return tr("Expected Type %1 but value contained %2") + .arg(valueTypeString(expected), valueTypeString(actual)); +} + +bool JsonObject::checkType(QJsonValue::Type type, + QJsonValue::Type expectedType, + QStringList *errorHierarchy) +{ + const bool ret = type == expectedType; + if (!ret && errorHierarchy) + errorHierarchy->append(errorString(expectedType, type)); + return ret; +} + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/jsonobject.h b/src/libs/languageserverprotocol/jsonobject.h new file mode 100644 index 0000000000..3b7329881b --- /dev/null +++ b/src/libs/languageserverprotocol/jsonobject.h @@ -0,0 +1,295 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "languageserverprotocol_global.h" +#include "lsputils.h" + +#include <QCoreApplication> +#include <QDebug> +#include <QJsonObject> + +namespace LanguageServerProtocol { + +class LANGUAGESERVERPROTOCOL_EXPORT JsonObject +{ + Q_DECLARE_TR_FUNCTIONS(LanguageServerProtocol::JsonObject) + +public: + JsonObject() = default; + + explicit JsonObject(const QJsonObject &object) : m_jsonObject(object) { } + explicit JsonObject(QJsonObject &&object) : m_jsonObject(std::move(object)) { } + + JsonObject(const JsonObject &object) : m_jsonObject(object.m_jsonObject) { } + JsonObject(JsonObject &&object) : m_jsonObject(std::move(object.m_jsonObject)) { } + + explicit JsonObject(const QJsonValue &value) : m_jsonObject(value.toObject()) { } + + virtual ~JsonObject() = default; + + JsonObject &operator=(const JsonObject &other); + JsonObject &operator=(JsonObject &&other); + + bool operator==(const JsonObject &other) const { return m_jsonObject == other.m_jsonObject; } + + operator const QJsonObject&() const { return m_jsonObject; } + + virtual bool isValid(QStringList * /*errorHierarchy*/) const { return true; } + +protected: + using iterator = QJsonObject::iterator; + using const_iterator = QJsonObject::const_iterator; + + iterator insert(const QString &key, const JsonObject &value); + iterator insert(const QString &key, const QJsonValue &value); + + // QJSonObject redirections + QJsonValue value(const QString &key) const { return m_jsonObject.value(key); } + bool contains(const QString &key) const { return m_jsonObject.contains(key); } + iterator find(const QString &key) { return m_jsonObject.find(key); } + const_iterator find(const QString &key) const { return m_jsonObject.find(key); } + iterator end() { return m_jsonObject.end(); } + const_iterator end() const { return m_jsonObject.end(); } + void remove(const QString &key) { m_jsonObject.remove(key); } + QStringList keys() const { return m_jsonObject.keys(); } + + // convenience value access + template<typename T> + T typedValue(const QString &key) const; + template<typename T> + Utils::optional<T> optionalValue(const QString &key) const; + template<typename T> + LanguageClientValue<T> clientValue(const QString &key) const; + template<typename T> + Utils::optional<LanguageClientValue<T>> optionalClientValue(const QString &key) const; + template<typename T> + QList<T> array(const QString &key) const; + template<typename T> + Utils::optional<QList<T>> optionalArray(const QString &key) const; + template<typename T> + LanguageClientArray<T> clientArray(const QString &key) const; + template<typename T> + Utils::optional<LanguageClientArray<T>> optionalClientArray(const QString &key) const; + template<typename T> + void insertArray(const QString &key, const QList<T> &array); + template<typename> + void insertArray(const QString &key, const QList<JsonObject> &array); + + // value checking + bool checkKey(QStringList *errorHierarchy, const QString &key, + const std::function<bool(const QJsonValue &val)> &predicate) const; + static QString valueTypeString(QJsonValue::Type type); + static QString errorString(QJsonValue::Type expected, QJsonValue::Type type2); + static bool checkType(QJsonValue::Type type, + QJsonValue::Type expectedType, + QStringList *errorHierarchy); + template <typename T> + static bool checkVal(QStringList *errorHierarchy, const QJsonValue &val); + template <typename T1, typename T2, typename... Args> + bool check(QStringList *errorHierarchy, const QString &key) const; + template <typename T> + bool check(QStringList *errorHierarchy, const QString &key) const; + template <typename T> + bool checkArray(QStringList *errorHierarchy, const QString &key) const; + template <typename T1, typename T2, typename... Args> + bool checkOptional(QStringList *errorHierarchy, const QString &key) const; + template <typename T> + bool checkOptional(QStringList *errorHierarchy, const QString &key) const; + template <typename T> + bool checkOptionalArray(QStringList *errorHierarchy, const QString &key) const; + +private: + QJsonObject m_jsonObject; +}; + +template<typename T> +T JsonObject::typedValue(const QString &key) const +{ + return fromJsonValue<T>(value(key)); +} + +template<typename T> +Utils::optional<T> JsonObject::optionalValue(const QString &key) const +{ + const QJsonValue &val = value(key); + return val.isUndefined() ? Utils::nullopt : Utils::make_optional(fromJsonValue<T>(val)); +} + +template<typename T> +LanguageClientValue<T> JsonObject::clientValue(const QString &key) const +{ + return LanguageClientValue<T>(value(key)); +} + +template<typename T> +Utils::optional<LanguageClientValue<T>> JsonObject::optionalClientValue(const QString &key) const +{ + return contains(key) ? Utils::make_optional(clientValue<T>(key)) : Utils::nullopt; +} + +template<typename T> +QList<T> JsonObject::array(const QString &key) const +{ + return LanguageClientArray<T>(value(key)).toList(); +} + +template<typename T> +Utils::optional<QList<T>> JsonObject::optionalArray(const QString &key) const +{ + using Result = Utils::optional<QList<T>>; + return contains(key) ? Result(LanguageClientArray<T>(value(key)).toList()) + : Result(Utils::nullopt); +} + +template<typename T> +LanguageClientArray<T> JsonObject::clientArray(const QString &key) const +{ + return LanguageClientArray<T>(value(key)); +} + +template<typename T> +Utils::optional<LanguageClientArray<T>> JsonObject::optionalClientArray(const QString &key) const +{ + const QJsonValue &val = value(key); + return !val.isUndefined() ? Utils::make_optional(LanguageClientArray<T>(value(key))) + : Utils::nullopt; +} + +template<typename T> +void JsonObject::insertArray(const QString &key, const QList<T> &array) +{ + QJsonArray jsonArray; + for (const T &item : array) + jsonArray.append(QJsonValue(item)); + insert(key, jsonArray); +} + +template<typename > +void JsonObject::insertArray(const QString &key, const QList<JsonObject> &array) +{ + QJsonArray jsonArray; + for (const JsonObject &item : array) + jsonArray.append(item.m_jsonObject); + insert(key, jsonArray); +} + +template <typename T> +bool JsonObject::checkVal(QStringList *errorHierarchy, const QJsonValue &val) +{ + return checkType(val.type(), QJsonValue::Object, errorHierarchy) + && T(val).isValid(errorHierarchy); +} + +template <typename T1, typename T2, typename... Args> +bool JsonObject::check(QStringList *errorHierarchy, const QString &key) const +{ + const QStringList errorBackUp = errorHierarchy ? *errorHierarchy : QStringList(); + if (check<T1>(errorHierarchy, key)) + return true; + + const bool ret = check<T2, Args...>(errorHierarchy, key); + if (ret && errorHierarchy) + *errorHierarchy = errorBackUp; + return ret; +} + +template <typename T> +bool JsonObject::check(QStringList *errorHierarchy, const QString &key) const +{ + return checkKey(errorHierarchy, key, [errorHierarchy](const QJsonValue &val){ + return checkVal<T>(errorHierarchy, val); + }); +} + +template <typename T> +bool JsonObject::checkArray(QStringList *errorHierarchy, const QString &key) const +{ + return checkKey(errorHierarchy, key, [errorHierarchy](const QJsonValue &val){ + return val.isArray() && Utils::allOf(val.toArray(), [&errorHierarchy](const QJsonValue &value){ + return checkVal<T>(errorHierarchy, value); + }); + }); +} + +template <typename T1, typename T2, typename... Args> +bool JsonObject::checkOptional(QStringList *errorHierarchy, const QString &key) const +{ + const QStringList errorBackUp = errorHierarchy ? *errorHierarchy : QStringList(); + if (checkOptional<T1>(errorHierarchy, key)) + return true; + + const bool ret = checkOptional<T2, Args...>(errorHierarchy, key); + if (ret && errorHierarchy) + *errorHierarchy = errorBackUp; + return ret; +} + +template <typename T> +bool JsonObject::checkOptional(QStringList *errorHierarchy, const QString &key) const +{ + if (contains(key)) + return check<T>(errorHierarchy, key); + return true; +} + +template <typename T> +bool JsonObject::checkOptionalArray(QStringList *errorHierarchy, const QString &key) const +{ + if (contains(key)) + return checkArray<T>(errorHierarchy, key); + return true; +} + +template <> +LANGUAGESERVERPROTOCOL_EXPORT bool JsonObject::checkVal<QString>(QStringList *errorHierarchy, + const QJsonValue &val); + +template <> +LANGUAGESERVERPROTOCOL_EXPORT bool JsonObject::checkVal<int>(QStringList *errorHierarchy, + const QJsonValue &val); + +template <> +LANGUAGESERVERPROTOCOL_EXPORT bool JsonObject::checkVal<double>(QStringList *errorHierarchy, + const QJsonValue &val); + +template <> +LANGUAGESERVERPROTOCOL_EXPORT bool JsonObject::checkVal<bool>(QStringList *errorHierarchy, + const QJsonValue &val); + +template <> +LANGUAGESERVERPROTOCOL_EXPORT bool JsonObject::checkVal<std::nullptr_t>(QStringList *errorHierarchy, + const QJsonValue &val); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT bool JsonObject::checkVal<QJsonArray>(QStringList *errorHierarchy, + const QJsonValue &val); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT bool JsonObject::checkVal<QJsonValue>(QStringList *errorHierarchy, + const QJsonValue &val); + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/jsonrpcmessages.cpp b/src/libs/languageserverprotocol/jsonrpcmessages.cpp new file mode 100644 index 0000000000..51e2c2b35e --- /dev/null +++ b/src/libs/languageserverprotocol/jsonrpcmessages.cpp @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "jsonrpcmessages.h" + +#include "lsputils.h" +#include "initializemessages.h" + +#include <utils/mimetypes/mimedatabase.h> +#include <utils/qtcassert.h> + +#include <QCoreApplication> +#include <QObject> +#include <QJsonDocument> +#include <QTextCodec> + +namespace LanguageServerProtocol { + +constexpr const char CancelRequest::methodName[]; + +QHash<QString, JsonRpcMessageHandler::MessageProvider> JsonRpcMessageHandler::m_messageProvider; + +QByteArray JsonRpcMessage::toRawData() const +{ + return QJsonDocument(m_jsonObject).toJson(QJsonDocument::Compact); +} + +QByteArray JsonRpcMessage::mimeType() const +{ + return JsonRpcMessageHandler::jsonRpcMimeType(); +} + +bool JsonRpcMessage::isValid(QString * /*errorMessage*/) const +{ + return m_jsonObject[jsonRpcVersionKey] == "2.0"; +} + +JsonRpcMessage::JsonRpcMessage() +{ + // The language server protocol always uses “2.0” as the jsonrpc version + m_jsonObject[jsonRpcVersionKey] = "2.0"; +} + +JsonRpcMessage::JsonRpcMessage(const QJsonObject &jsonObject) + : m_jsonObject(jsonObject) +{ } + + +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, + ResponseHandlers responseHandlers, + MethodHandler methodHandler) +{ + const QJsonObject &jsonObject = toJsonObject(content, codec, parseError); + if (jsonObject.isEmpty()) + return; + + 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); +} + +constexpr int utf8mib = 106; + +static QString docTypeName(const QJsonDocument &doc) +{ + if (doc.isArray()) + return QString("array"); + if (doc.isEmpty()) + return QString("empty"); + if (doc.isNull()) + return QString("null"); + if (doc.isObject()) + return QString("object"); + return {}; +} + +QJsonObject JsonRpcMessageHandler::toJsonObject(const QByteArray &_content, + QTextCodec *codec, + QString &parseError) +{ + auto tr = [](const char *message){ + return QCoreApplication::translate("JsonRpcMessageHandler", message); + }; + if (_content.isEmpty()) + return QJsonObject(); + QByteArray content; + if (codec && codec->mibEnum() != utf8mib) { + QTextCodec *utf8 = QTextCodec::codecForMib(utf8mib); + if (utf8) + content = utf8->fromUnicode(codec->toUnicode(_content)); + } + if (content.isEmpty()) + content = _content; + QJsonParseError error = {0 , QJsonParseError::NoError}; + const QJsonDocument doc = QJsonDocument::fromJson(content, &error); + if (doc.isObject()) + return doc.object(); + if (doc.isNull()) + parseError = tr("Could not parse Json message '%1'").arg(error.errorString()); + else + parseError = tr("Expected Json object, but got a json '%1'").arg(docTypeName(doc)); + return QJsonObject(); +} + +CancelRequest::CancelRequest(const CancelParameter ¶ms) + : Notification(methodName, params) +{ } + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/jsonrpcmessages.h b/src/libs/languageserverprotocol/jsonrpcmessages.h new file mode 100644 index 0000000000..adba364cc1 --- /dev/null +++ b/src/libs/languageserverprotocol/jsonrpcmessages.h @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "icontent.h" +#include "lsptypes.h" +#include "jsonkeys.h" + +#include <utils/optional.h> +#include <utils/qtcassert.h> +#include <utils/variant.h> + +#include <QDebug> +#include <QHash> +#include <QJsonObject> +#include <QJsonValue> +#include <QCoreApplication> +#include <QUuid> + +namespace LanguageServerProtocol { + +using LanguageClientNull = JsonObject; + +class LANGUAGESERVERPROTOCOL_EXPORT JsonRpcMessage : public IContent +{ +public: + JsonRpcMessage(); + JsonRpcMessage(const QJsonObject &jsonObject); + + QByteArray toRawData() const final; + QByteArray mimeType() const final; + bool isValid(QString *errorMessage) const override; + +protected: + QJsonObject m_jsonObject; + +private: + QString m_parseError; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT JsonRpcMessageHandler +{ +public: + using MessageProvider = std::function<IContent *(const QJsonObject &)>; + static void registerMessageProvider(const QString &method, MessageProvider provider); + static QByteArray jsonRpcMimeType(); + static void parseContent(const QByteArray &content, QTextCodec *codec, QString &errorMessage, + ResponseHandlers responseHandlers, + MethodHandler methodHandler); + static QJsonObject toJsonObject(const QByteArray &content, QTextCodec *codec, QString &parseError); + +private: + static QHash<QString, MessageProvider> m_messageProvider; +}; + +template <typename Params> +class Notification : public JsonRpcMessage +{ +public: + Notification() : Notification(QString()) {} + Notification(const QString &methodName, const Params ¶ms = Params()) + { + setMethod(methodName); + setParams(params); + } + using JsonRpcMessage::JsonRpcMessage; + + QString method() const + { return fromJsonValue<QString>(m_jsonObject.value(methodKey)); } + void setMethod(const QString &method) + { m_jsonObject.insert(methodKey, method); } + + Utils::optional<Params> params() const + { + const QJsonValue ¶ms = m_jsonObject.value(parametersKey); + return params.isUndefined() ? Utils::nullopt : Utils::make_optional(Params(params)); + } + void setParams(const Params ¶ms) + { m_jsonObject.insert(parametersKey, QJsonValue(params)); } + void clearParams() { m_jsonObject.remove(parametersKey); } + + bool isValid(QString *errorMessage) const override + { + return JsonRpcMessage::isValid(errorMessage) + && m_jsonObject.value(methodKey).isString() + && parametersAreValid(errorMessage); + } + + virtual bool parametersAreValid(QString *errorMessage) const + { + if (auto parameter = params()) { + QStringList error; + return parameter.value().isValid(&error); + } + if (errorMessage) + *errorMessage = QCoreApplication::translate("LanguageServerProtocol::Notification", + "No parameters in '%1'").arg(method()); + return false; + } +}; + +template <typename Error> +class ResponseError : public JsonObject +{ +public: + using JsonObject::JsonObject; + + int code() const { return typedValue<int>(codeKey); } + void setCode(int code) { insert(codeKey, code); } + + QString message() const { return typedValue<QString>(messageKey); } + void setMessage(const QString &message) { insert(messageKey, message); } + + Utils::optional<Error> data() const { return optionalValue<Error>(dataKey); } + void setData(const Error &data) { insert(dataKey, data); } + void clearData() { remove(dataKey); } + + bool isValid(QStringList *error) const override + { + return check<int>(error, codeKey) + && check<QString>(error, messageKey) + && checkOptional<Error>(error, dataKey); + } + + // predefined error codes + enum ErrorCodes { + // Defined by JSON RPC + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + serverErrorStart = -32099, + serverErrorEnd = -32000, + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + + // Defined by the protocol. + RequestCancelled = -32800 + }; + +#define CASE_ERRORCODES(x) case ErrorCodes::x: return QLatin1String(#x) + static QString errorCodesToString(int code) + { + switch (code) { + CASE_ERRORCODES(ParseError); + CASE_ERRORCODES(InvalidRequest); + CASE_ERRORCODES(MethodNotFound); + CASE_ERRORCODES(InvalidParams); + CASE_ERRORCODES(InternalError); + CASE_ERRORCODES(serverErrorStart); + CASE_ERRORCODES(serverErrorEnd); + CASE_ERRORCODES(ServerNotInitialized); + CASE_ERRORCODES(UnknownErrorCode); + CASE_ERRORCODES(RequestCancelled); + default: + return QCoreApplication::translate("LanguageClient::ResponseError", + "Error %1").arg(code); + } + } +#undef CASE_ERRORCODES +}; + +template <typename Result, typename Error> +class Response : public JsonRpcMessage +{ +public: + using JsonRpcMessage::JsonRpcMessage; + + MessageId id() const + { return MessageId(m_jsonObject.value(idKey)); } + void setId(MessageId id) + { this->m_jsonObject.insert(idKey, id.toJson()); } + + Utils::optional<Result> result() const + { + const QJsonValue &result = m_jsonObject.value("result"); + if (result.isUndefined()) + return Utils::nullopt; + return Utils::make_optional(Result(result)); + } + void setResult(const Result &result) { m_jsonObject.insert(resultKey, result); } + void clearResult() { m_jsonObject.remove(resultKey); } + + Utils::optional<ResponseError<Error>> error() const + { + const QJsonValue &val = m_jsonObject.value(errorKey); + return val.isUndefined() ? Utils::nullopt + : Utils::make_optional(fromJsonValue<ResponseError<Error>>(val)); + } + void setError(const ResponseError<Error> &error) + { m_jsonObject.insert(errorKey, QJsonValue(error)); } + void clearError() { m_jsonObject.remove(errorKey); } + + bool isValid(QString *errorMessage) const override + { return JsonRpcMessage::isValid(errorMessage) && id().isValid(); } +}; + +template <typename Result, typename Error, typename Params> +class Request : public Notification<Params> +{ +public: + Request() : Notification<Params>(), m_callBack(0) { setId(QUuid::createUuid().toString()); } + Request(const QString &methodName, const Params ¶ms = Params()) + : Notification<Params>(methodName, params), m_callBack(0) + { setId(QUuid::createUuid().toString()); } + + MessageId id() const + { return MessageId(JsonRpcMessage::m_jsonObject.value(idKey)); } + void setId(const MessageId &id) + { JsonRpcMessage::m_jsonObject.insert(idKey, id.toJson()); } + + using ResponseCallback = std::function<void(Response<Result, Error>)>; + void setResponseCallback(const ResponseCallback &callback) + { m_callBack = callback; } + + void registerResponseHandler(QHash<MessageId, ResponseHandler> *handlers) const final + { + auto callback = m_callBack; + handlers->insert(id(), [callback](const QByteArray &content, QTextCodec *codec){ + if (!callback) + return; + QString parseError; + const QJsonObject &object = + JsonRpcMessageHandler::toJsonObject(content, codec, parseError); + Response<Result, Error> response(object); + if (object.isEmpty()) { + ResponseError<Error> error; + error.setMessage(parseError); + response.setError(ResponseError<Error>()); + } + callback(Response<Result, Error>(object)); + }); + } + + bool isValid(QString *errorMessage) const override + { + if (!Notification<Params>::isValid(errorMessage)) + return false; + if (id().isValid()) + return true; + if (errorMessage) { + *errorMessage = QCoreApplication::translate("LanguageServerProtocol::Request", + "No id set in '%1'").arg(this->method()); + } + return false; + } + +private: + ResponseCallback m_callBack; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CancelParameter : public JsonObject +{ +public: + CancelParameter(const MessageId &id) { setId(id); } + CancelParameter() = default; + using JsonObject::JsonObject; + + MessageId id() const { return MessageId(value(idKey)); } + void setId(const MessageId &id) { insert(idKey, id.toJson()); } + + bool isValid(QStringList *error) const override + { + if (MessageId(value(idKey)).isValid(error)) + return true; + if (error) + error->append(idKey); + return false; + } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CancelRequest : public Notification<CancelParameter> +{ +public: + CancelRequest(const CancelParameter ¶ms = CancelParameter()); + using Notification::Notification; + constexpr static const char methodName[] = "$/cancelRequest"; +}; + +} // namespace LanguageClient + +template <typename Error> +inline QDebug operator<<(QDebug stream, + const LanguageServerProtocol::ResponseError<Error> &error) +{ + stream.nospace() << LanguageServerProtocol::ResponseError<Error>::errorCodesToString(error.code()) + << ":" + << error.message(); + return stream; +} diff --git a/src/libs/languageserverprotocol/languagefeatures.cpp b/src/libs/languageserverprotocol/languagefeatures.cpp new file mode 100644 index 0000000000..6581588201 --- /dev/null +++ b/src/libs/languageserverprotocol/languagefeatures.cpp @@ -0,0 +1,368 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "languagefeatures.h" + +namespace LanguageServerProtocol { + +constexpr const char HoverRequest::methodName[]; +constexpr const char GotoDefinitionRequest::methodName[]; +constexpr const char GotoTypeDefinitionRequest::methodName[]; +constexpr const char GotoImplementationRequest::methodName[]; +constexpr const char FindReferencesRequest::methodName[]; +constexpr const char DocumentHighlightsRequest::methodName[]; +constexpr const char DocumentSymbolsRequest::methodName[]; +constexpr const char CodeActionRequest::methodName[]; +constexpr const char CodeLensRequest::methodName[]; +constexpr const char CodeLensResolveRequest::methodName[]; +constexpr const char DocumentLinkRequest::methodName[]; +constexpr const char DocumentLinkResolveRequest::methodName[]; +constexpr const char DocumentColorRequest::methodName[]; +constexpr const char ColorPresentationRequest::methodName[]; +constexpr const char DocumentFormattingRequest::methodName[]; +constexpr const char DocumentRangeFormattingRequest::methodName[]; +constexpr const char DocumentOnTypeFormattingRequest::methodName[]; +constexpr const char RenameRequest::methodName[]; +constexpr const char SignatureHelpRequest::methodName[]; + +MarkedString LanguageServerProtocol::Hover::content() const +{ + return MarkedString(value(contentKey)); +} + +void Hover::setContent(const MarkedString &content) +{ + if (auto val = Utils::get_if<MarkedLanguageString>(&content)) + insert(contentKey, *val); + else if (auto val = Utils::get_if<MarkupContent>(&content)) + insert(contentKey, *val); + else if (auto val = Utils::get_if<QList<MarkedLanguageString>>(&content)) + insert(contentKey, LanguageClientArray<MarkedLanguageString>(*val).toJson()); + else + QTC_ASSERT_STRING("LanguageClient Using unknown type Hover::setContent"); +} + +HoverRequest::HoverRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +Utils::optional<MarkupOrString> ParameterInformation::documentation() const +{ + QJsonValue documentation = value(documentationKey); + if (documentation.isUndefined()) + return Utils::nullopt; + return MarkupOrString(documentation); +} + +bool SignatureHelp::isValid(QStringList *error) const +{ + return checkArray<SignatureInformation>(error, signaturesKey); +} + +GotoDefinitionRequest::GotoDefinitionRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +GotoTypeDefinitionRequest::GotoTypeDefinitionRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +GotoImplementationRequest::GotoImplementationRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +FindReferencesRequest::FindReferencesRequest(const ReferenceParams ¶ms) + : Request(methodName, params) +{ } + +DocumentHighlightsRequest::DocumentHighlightsRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +DocumentSymbolsRequest::DocumentSymbolsRequest(const DocumentSymbolParams ¶ms) + : Request(methodName, params) +{ } + +bool CodeActionParams::CodeActionContext::isValid(QStringList *error) const +{ + return checkArray<Diagnostic>(error, diagnosticsKey); +} + +bool CodeActionParams::isValid(QStringList *error) const +{ + return check<TextDocumentIdentifier>(error, textDocumentKey) + && check<Range>(error, rangeKey) + && check<CodeActionContext>(error, contextKey); +} + +CodeActionRequest::CodeActionRequest(const CodeActionParams ¶ms) + : Request(methodName, params) +{ } + +CodeLensRequest::CodeLensRequest(const CodeLensParams ¶ms) + : Request(methodName, params) +{ } + +CodeLensResolveRequest::CodeLensResolveRequest(const CodeLens ¶ms) + : Request(methodName, params) +{ } + +DocumentLinkRequest::DocumentLinkRequest(const DocumentLinkParams ¶ms) + : Request(methodName, params) +{ } + +DocumentLinkResolveRequest::DocumentLinkResolveRequest(const DocumentLink ¶ms) + : Request(methodName, params) +{ } + +DocumentColorRequest::DocumentColorRequest(const DocumentColorParams ¶ms) + : Request(methodName, params) +{ } + +bool Color::isValid(QStringList *error) const +{ + return check<int>(error, redKey) + && check<int>(error, greenKey) + && check<int>(error, blueKey) + && check<int>(error, alphaKey); +} + +bool ColorPresentationParams::isValid(QStringList *error) const +{ + return check<TextDocumentIdentifier>(error, textDocumentKey) + && check<Color>(error, colorInfoKey) + && check<Range>(error, rangeKey); +} + +ColorPresentationRequest::ColorPresentationRequest(const ColorPresentationParams ¶ms) + : Request(methodName, params) +{ } + +QHash<QString, DocumentFormattingProperty> FormattingOptions::properties() const +{ + QHash<QString, DocumentFormattingProperty> ret; + for (const QString &key : keys()) { + if (key == tabSizeKey || key == insertSpaceKey) + continue; + QJsonValue property = value(key); + if (property.isBool()) + ret[key] = property.toBool(); + if (property.isDouble()) + ret[key] = property.toDouble(); + if (property.isString()) + ret[key] = property.toString(); + } + return ret; +} + +void FormattingOptions::setProperty(const QString &key, const DocumentFormattingProperty &property) +{ + using namespace Utils; + if (auto val = get_if<double>(&property)) + insert(key, *val); + else if (auto val = get_if<QString>(&property)) + insert(key, *val); + else if (auto val = get_if<bool>(&property)) + insert(key, *val); +} + +bool FormattingOptions::isValid(QStringList *error) const +{ + return Utils::allOf(keys(), [this, &error](auto key){ + return (key == tabSizeKey && this->check<int>(error, key)) + || (key == insertSpaceKey && this->check<bool>(error, key)) + || this->check<DocumentFormattingProperty>(error, key); + }); +} + +bool DocumentFormattingParams::isValid(QStringList *error) const +{ + return check<TextDocumentIdentifier>(error, textDocumentKey) + && check<FormattingOptions>(error, optionsKey); +} + +DocumentFormattingRequest::DocumentFormattingRequest(const DocumentFormattingParams ¶ms) + : Request(methodName, params) +{ } + +bool DocumentRangeFormattingParams::isValid(QStringList *error) const +{ + return check<TextDocumentIdentifier>(error, textDocumentKey) + && check<Range>(error, rangeKey) + && check<FormattingOptions>(error, optionsKey); +} + +DocumentRangeFormattingRequest::DocumentRangeFormattingRequest( + const DocumentFormattingParams ¶ms) + : Request(methodName, params) +{ } + +bool DocumentOnTypeFormattingParams::isValid(QStringList *error) const +{ + return check<TextDocumentIdentifier>(error, textDocumentKey) + && check<Position>(error, positionKey) + && check<QString>(error, chKey) + && check<FormattingOptions>(error, optionsKey); +} + +DocumentOnTypeFormattingRequest::DocumentOnTypeFormattingRequest( + const DocumentFormattingParams ¶ms) + : Request(methodName, params) +{ } + +bool RenameParams::isValid(QStringList *error) const +{ + return check<TextDocumentIdentifier>(error, textDocumentKey) + && check<Position>(error, positionKey) + && check<QString>(error, newNameKey); +} + +RenameRequest::RenameRequest(const RenameParams ¶ms) + : Request(methodName, params) +{ } + +Utils::optional<DocumentUri> DocumentLink::target() const +{ + Utils::optional<QString> optionalTarget = optionalValue<QString>(targetKey); + return optionalTarget.has_value() + ? Utils::make_optional(DocumentUri::fromProtocol(optionalTarget.value())) + : Utils::nullopt; +} + +TextDocumentParams::TextDocumentParams() + : TextDocumentParams(TextDocumentIdentifier()) +{ } + +TextDocumentParams::TextDocumentParams(const TextDocumentIdentifier &identifier) + : JsonObject() +{ + setTextDocument(identifier); +} + +GotoResult::GotoResult(const QJsonValue &value) +{ + if (value.isArray()) { + QList<Location> locations; + for (auto arrayValue : value.toArray()) { + if (arrayValue.isObject()) + locations.append(Location(arrayValue.toObject())); + } + emplace<QList<Location>>(locations); + } else if (value.isObject()) { + emplace<Location>(value.toObject()); + } else { + emplace<std::nullptr_t>(nullptr); + } +} + +DocumentSymbolsResult::DocumentSymbolsResult(const QJsonValue &value) +{ + if (value.isArray()) { + QList<SymbolInformation> symbols; + for (auto arrayValue : value.toArray()) { + if (arrayValue.isObject()) + symbols.append(SymbolInformation(arrayValue.toObject())); + } + *this = symbols; + } else { + *this = nullptr; + } +} + +DocumentHighlightsResult::DocumentHighlightsResult(const QJsonValue &value) +{ + if (value.isArray()) { + QList<DocumentHighlight> highlights; + for (auto arrayValue : value.toArray()) { + if (arrayValue.isObject()) + highlights.append(DocumentHighlight(arrayValue.toObject())); + } + *this = highlights; + } else { + *this = nullptr; + } +} + +MarkedString::MarkedString(const QJsonValue &value) +{ + if (value.isArray()) { + emplace<QList<MarkedLanguageString>>( + LanguageClientArray<MarkedLanguageString>(value).toList()); + } else if (value.isObject()) { + const QJsonObject &object = value.toObject(); + MarkedLanguageString markedLanguageString(object); + if (markedLanguageString.isValid(nullptr)) + emplace<MarkedLanguageString>(markedLanguageString); + else + emplace<MarkupContent>(MarkupContent(object)); + } +} + +bool MarkedString::isValid(QStringList *errorHierarchy) const +{ + if (Utils::holds_alternative<MarkedLanguageString>(*this) + || Utils::holds_alternative<MarkupContent>(*this) + || Utils::holds_alternative<QList<MarkedLanguageString>>(*this)) { + return true; + } + if (errorHierarchy) { + *errorHierarchy << QCoreApplication::translate( + "LanguageServerProtocol::MarkedString", + "MarkedString should be either MarkedLanguageString, " + "MarkupContent or QList<MarkedLanguageString>"); + } + return false; +} + +DocumentFormattingProperty::DocumentFormattingProperty(const QJsonValue &value) +{ + if (value.isBool()) + *this = value.toBool(); + if (value.isDouble()) + *this = value.toDouble(); + if (value.isString()) + *this = value.toString(); +} + +bool DocumentFormattingProperty::isValid(QStringList *error) const +{ + if (Utils::holds_alternative<bool>(*this) + || Utils::holds_alternative<double>(*this) + || Utils::holds_alternative<QString>(*this)) { + return true; + } + if (error) { + *error << QCoreApplication::translate( + "LanguageServerProtocol::MarkedString", + "DocumentFormattingProperty should be either bool, double or QString"); + } + return false; +} + +SignatureHelpRequest::SignatureHelpRequest(const TextDocumentPositionParams ¶ms) + : Request(methodName, params) +{ } + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/languagefeatures.h b/src/libs/languageserverprotocol/languagefeatures.h new file mode 100644 index 0000000000..15af8ab322 --- /dev/null +++ b/src/libs/languageserverprotocol/languagefeatures.h @@ -0,0 +1,709 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "jsonrpcmessages.h" + +namespace LanguageServerProtocol { + +/** + * MarkedString can be used to render human readable text. It is either a markdown string + * or a code-block that provides a language and a code snippet. The language identifier + * is semantically equal to the optional language identifier in fenced code blocks in GitHub + * issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting + * + * The pair of a language and a value is an equivalent to markdown: + * ```${language} + * ${value} + * ``` + * + * Note that markdown strings will be sanitized - that means html will be escaped. +* @deprecated use MarkupContent instead. +*/ +class LANGUAGESERVERPROTOCOL_EXPORT MarkedLanguageString : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString language() const { return typedValue<QString>(languageKey); } + void setLanguage(const QString &language) { insert(languageKey, language); } + + QString value() const { return typedValue<QString>(valueKey); } + void setValue(const QString &value) { insert(valueKey, value); } + + bool isValid(QStringList *error) const override + { return check<QString>(error, languageKey) && check<QString>(error, valueKey); } +}; + +class MarkedString : public Utils::variant<MarkedLanguageString, QList<MarkedLanguageString>, MarkupContent> +{ +public: + MarkedString() = default; + explicit MarkedString(const MarkedLanguageString &other) : variant(other) {} + explicit MarkedString(const QList<MarkedLanguageString> &other) : variant(other) {} + explicit MarkedString(const MarkupContent &other) : variant(other) {} + explicit MarkedString(const QJsonValue &value); + bool isValid(QStringList *errorHierarchy) const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT Hover : public JsonObject +{ +public: + using JsonObject::JsonObject; + + MarkedString content() const; + void setContent(const MarkedString &content); + + Utils::optional<Range> range() const { return optionalValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + void clearRange() { remove(rangeKey); } + + bool isValid(QStringList *error) const override + { return check<MarkedString>(error, contentKey) && checkOptional<Range>(error, rangeKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT HoverRequest : public Request< + Hover, LanguageClientNull, TextDocumentPositionParams> +{ +public: + HoverRequest(const TextDocumentPositionParams ¶ms = TextDocumentPositionParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/hover"; +}; + +/** + * Represents a parameter of a callable-signature. A parameter can + * have a label and a doc-comment. + */ +class LANGUAGESERVERPROTOCOL_EXPORT ParameterInformation : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString label() const { return typedValue<QString>(labelKey); } + void setLabel(const QString &label) { insert(labelKey, label); } + + Utils::optional<MarkupOrString> documentation() const; + void setDocumentation(const MarkupOrString &documentation) + { insert(documentationKey, documentation.toJson()); } + void clearDocumentation() { remove(documentationKey); } + + bool isValid(QStringList *error) const override + { + return check<QString>(error, labelKey) + && checkOptional<MarkupOrString>(error, documentationKey); + } +}; + +/** + * Represents the signature of something callable. A signature + * can have a label, like a function-name, a doc-comment, and + * a set of parameters. + */ +class LANGUAGESERVERPROTOCOL_EXPORT SignatureInformation : public ParameterInformation +{ +public: + using ParameterInformation::ParameterInformation; + + Utils::optional<QList<ParameterInformation>> parameters() const + { return optionalArray<ParameterInformation>(parametersKey); } + void setParameters(const QList<ParameterInformation> ¶meters) + { insertArray(parametersKey, parameters); } + void clearParameters() { remove(parametersKey); } +}; + +/** + * Signature help represents the signature of something + * callable. There can be multiple signature but only one + * active and only one active parameter. + */ +class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelp : public JsonObject +{ +public: + using JsonObject::JsonObject; + + /// One or more signatures. + QList<SignatureInformation> signatures() const + { return array<SignatureInformation>(signaturesKey); } + void setSignatures(const QList<SignatureInformation> &signatures) + { insertArray(signaturesKey, signatures); } + + /** + * The active signature. If omitted or the value lies outside the + * range of `signatures` the value defaults to zero or is ignored if + * `signatures.length === 0`. Whenever possible implementors should + * make an active decision about the active signature and shouldn't + * rely on a default value. + * In future version of the protocol this property might become + * mandatory to better express this. + */ + Utils::optional<int> activeSignature() const { return optionalValue<int>(activeSignatureKey); } + void setActiveSignature(int activeSignature) { insert(activeSignatureKey, activeSignature); } + void clearActiveSignature() { remove(activeSignatureKey); } + + /** + * The active parameter of the active signature. If omitted or the value + * lies outside the range of `signatures[activeSignature].parameters` + * defaults to 0 if the active signature has parameters. If + * the active signature has no parameters it is ignored. + * In future version of the protocol this property might become + * mandatory to better express the active parameter if the + * active signature does have any. + */ + Utils::optional<int> activeParameter() const { return optionalValue<int>(activeParameterKey); } + void setActiveParameter(int activeParameter) { insert(activeParameterKey, activeParameter); } + void clearActiveParameter() { remove(activeParameterKey); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelpRequest : public Request< + LanguageClientValue<SignatureHelp>, LanguageClientNull, TextDocumentPositionParams> +{ +public: + SignatureHelpRequest(const TextDocumentPositionParams ¶ms = TextDocumentPositionParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/signatureHelp"; +}; + +/// The result of a goto request can either be a location, a list of locations or null +class LANGUAGESERVERPROTOCOL_EXPORT GotoResult + : public Utils::variant<Location, QList<Location>, std::nullptr_t> +{ +public: + GotoResult(const QJsonValue &value); + using variant::variant; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT GotoDefinitionRequest : public Request< + GotoResult, LanguageClientNull, TextDocumentPositionParams> +{ +public: + GotoDefinitionRequest(const TextDocumentPositionParams ¶ms = TextDocumentPositionParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/definition"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT GotoTypeDefinitionRequest : public Request< + GotoResult, LanguageClientNull, TextDocumentPositionParams> +{ +public: + GotoTypeDefinitionRequest( + const TextDocumentPositionParams ¶ms = TextDocumentPositionParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/typeDefinition"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT GotoImplementationRequest : public Request< + GotoResult, LanguageClientNull, TextDocumentPositionParams> +{ +public: + GotoImplementationRequest( + const TextDocumentPositionParams ¶ms = TextDocumentPositionParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/implementation"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ReferenceParams : public TextDocumentPositionParams +{ +public: + using TextDocumentPositionParams::TextDocumentPositionParams; + + class ReferenceContext : public JsonObject + { + public: + using JsonObject::JsonObject; + bool includeDeclaration() const { return typedValue<bool>(includeDeclarationKey); } + void setIncludeDeclaration(bool includeDeclaration) + { insert(includeDeclarationKey, includeDeclaration); } + + bool isValid(QStringList *error) const override + { return check<bool>(error, includeDeclarationKey); } + }; + + ReferenceContext context() const { return typedValue<ReferenceContext>(contextKey); } + void setContext(const ReferenceContext &context) { insert(contextKey, context); } + + bool isValid(QStringList *error) const override + { + return TextDocumentPositionParams::isValid(error) + && check<ReferenceContext>(error, contextKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT FindReferencesRequest : public Request< + LanguageClientArray<Location>, LanguageClientNull, ReferenceParams> +{ +public: + FindReferencesRequest(const ReferenceParams ¶ms = ReferenceParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/references"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentHighlight : public JsonObject +{ +public: + using JsonObject::JsonObject; + + enum DocumentHighlightKind { + Text = 1, + Read = 2, + Write = 3 + }; + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + Utils::optional<int> kind() const { return optionalValue<int>(kindKey); } + void setKind(int kind) { insert(kindKey, kind); } + void clearKind() { remove(kindKey); } + + bool isValid(QStringList *error) const override + { return check<Range>(error, rangeKey) && checkOptional<int>(error, kindKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentHighlightsResult + : public Utils::variant<QList<DocumentHighlight>, std::nullptr_t> +{ +public: + using variant::variant; + DocumentHighlightsResult() : variant(nullptr) {} + DocumentHighlightsResult(const QJsonValue &value); + using variant::operator=; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentHighlightsRequest : public Request< + DocumentHighlightsResult, LanguageClientNull, TextDocumentPositionParams> +{ +public: + DocumentHighlightsRequest( + const TextDocumentPositionParams ¶ms = TextDocumentPositionParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/documentHighlight"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentParams : public JsonObject +{ +public: + TextDocumentParams(); + TextDocumentParams(const TextDocumentIdentifier &identifier); + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + bool isValid(QStringList *error) const override + { return check<TextDocumentIdentifier>(error, textDocumentKey); } +}; + +using DocumentSymbolParams = TextDocumentParams; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentSymbolsResult + : public Utils::variant<QList<SymbolInformation>, std::nullptr_t> +{ +public: + using variant::variant; + DocumentSymbolsResult() : variant(nullptr) {} + DocumentSymbolsResult(const QJsonValue &value); + using variant::operator=; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentSymbolsRequest + : public Request<DocumentSymbolsResult, LanguageClientNull, DocumentSymbolParams> +{ +public: + DocumentSymbolsRequest(const DocumentSymbolParams ¶ms = DocumentSymbolParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/documentSymbol"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeActionParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + class CodeActionContext : public JsonObject + { + public: + using JsonObject::JsonObject; + + QList<Diagnostic> diagnostics() const { return array<Diagnostic>(diagnosticsKey); } + void setDiagnostics(const QList<Diagnostic> &diagnostics) + { insertArray(diagnosticsKey, diagnostics); } + + bool isValid(QStringList *error) const override; + }; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + CodeActionContext context() const { return typedValue<CodeActionContext>(contextKey); } + void setContext(const CodeActionContext &context) { insert(contextKey, context); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeActionRequest : public Request< + LanguageClientArray<Command>, LanguageClientNull, CodeActionParams> +{ +public: + CodeActionRequest(const CodeActionParams ¶ms = CodeActionParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/codeAction"; +}; + +using CodeLensParams = TextDocumentParams; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeLens : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + Utils::optional<Command> command() const { return optionalValue<Command>(commandKey); } + void setCommand(const Command &command) { insert(commandKey, command); } + void clearCommand() { remove(commandKey); } + + Utils::optional<QJsonValue> data() const { return optionalValue<QJsonValue>(dataKey); } + void setData(const QJsonValue &data) { insert(dataKey, data); } + void clearData() { remove(dataKey); } + + bool isValid(QStringList *error) const override + { return check<Range>(error, rangeKey) && checkOptional<Command>(error, commandKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeLensRequest : public Request< + LanguageClientArray<CodeLens>, LanguageClientNull, CodeLensParams> +{ +public: + CodeLensRequest(const CodeLensParams ¶ms = CodeLensParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/codeLens"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT CodeLensResolveRequest : public Request< + CodeLens, LanguageClientNull, CodeLens> +{ +public: + CodeLensResolveRequest(const CodeLens ¶ms = CodeLens()); + using Request::Request; + constexpr static const char methodName[] = "codeLens/resolve"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentLink : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + Utils::optional<DocumentUri> target() const; + void setTarget(const DocumentUri &target) { insert(targetKey, target.toString()); } + void clearTarget() { remove(targetKey); } + + Utils::optional<QJsonValue> data() const { return optionalValue<QJsonValue>(dataKey); } + void setData(const QJsonValue &data) { insert(dataKey, data); } + void clearData() { remove(dataKey); } + + bool isValid(QStringList *error) const override + { return check<Range>(error, rangeKey) && checkOptional<QString>(error, targetKey); } +}; + +using DocumentLinkParams = TextDocumentParams; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentLinkRequest : public Request< + LanguageClientValue<DocumentLink>, LanguageClientNull, DocumentLinkParams> +{ +public: + DocumentLinkRequest(const DocumentLinkParams ¶ms = DocumentLinkParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/documentLink"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentLinkResolveRequest : public Request< + DocumentLink, LanguageClientNull, DocumentLink> +{ +public: + DocumentLinkResolveRequest(const DocumentLink ¶ms = DocumentLink()); + using Request::Request; + constexpr static const char methodName[] = "documentLink/resolve"; +}; + +using DocumentColorParams = TextDocumentParams; + +class LANGUAGESERVERPROTOCOL_EXPORT Color : public JsonObject +{ +public: + using JsonObject::JsonObject; + + double red() const { return typedValue<double>(redKey); } + void setRed(double red) { insert(redKey, red); } + + double green() const { return typedValue<double>(greenKey); } + void setGreen(double green) { insert(greenKey, green); } + + double blue() const { return typedValue<double>(blueKey); } + void setBlue(double blue) { insert(blueKey, blue); } + + double alpha() const { return typedValue<double>(alphaKey); } + void setAlpha(double alpha) { insert(alphaKey, alpha); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ColorInformation : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(Range range) { insert(rangeKey, range); } + + Color color() const { return typedValue<Color>(colorKey); } + void setColor(const Color &color) { insert(colorKey, color); } + + bool isValid(QStringList *error) const override + { return check<Range>(error, rangeKey) && check<Color>(error, colorKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentColorRequest : public Request< + QList<ColorInformation>, LanguageClientNull, DocumentColorParams> +{ +public: + DocumentColorRequest(const DocumentColorParams ¶ms = DocumentColorParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/documentColor"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ColorPresentationParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + Color colorInfo() const { return typedValue<Color>(colorInfoKey); } + void setColorInfo(const Color &colorInfo) { insert(colorInfoKey, colorInfo); } + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ColorPresentation : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString label() const { return typedValue<QString>(labelKey); } + void setLabel(const QString &label) { insert(labelKey, label); } + + Utils::optional<TextEdit> textEdit() const { return optionalValue<TextEdit>(textEditKey); } + void setTextEdit(const TextEdit &textEdit) { insert(textEditKey, textEdit); } + void clearTextEdit() { remove(textEditKey); } + + Utils::optional<QList<TextEdit>> additionalTextEdits() const + { return optionalArray<TextEdit>(additionalTextEditsKey); } + void setAdditionalTextEdits(const QList<TextEdit> &additionalTextEdits) + { insertArray(additionalTextEditsKey, additionalTextEdits); } + void clearAdditionalTextEdits() { remove(additionalTextEditsKey); } + + bool isValid(QStringList *error) const override + { + return check<QString>(error, labelKey) + && checkOptional<TextEdit>(error, textEditKey) + && checkOptionalArray<TextEdit>(error, additionalTextEditsKey); + } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ColorPresentationRequest : public Request< + QList<ColorPresentation>, LanguageClientNull, ColorPresentationParams> +{ +public: + ColorPresentationRequest(const ColorPresentationParams ¶ms = ColorPresentationParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/colorPresentation"; +}; + +class DocumentFormattingProperty : public Utils::variant<bool, double, QString> +{ +public: + DocumentFormattingProperty() = default; + DocumentFormattingProperty(const QJsonValue &value); + DocumentFormattingProperty(const DocumentFormattingProperty &other) + : Utils::variant<bool, double, QString>(other) {} + + using variant::variant; + using variant::operator=; + + bool isValid(QStringList *error) const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT FormattingOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + int tabSize() const { return typedValue<int>(tabSizeKey); } + void setTabSize(int tabSize) { insert(tabSizeKey, tabSize); } + + bool insertSpace() const { return typedValue<bool>(insertSpaceKey); } + void setInsertSpace(bool insertSpace) { insert(insertSpaceKey, insertSpace); } + + QHash<QString, DocumentFormattingProperty> properties() const; + void setProperty(const QString &key, const DocumentFormattingProperty &property); + void removeProperty(const QString &key) { remove(key); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentFormattingParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + FormattingOptions options() const { return typedValue<FormattingOptions>(optionsKey); } + void setOptions(const FormattingOptions &options) { insert(optionsKey, options); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentFormattingRequest : public Request< + LanguageClientArray<TextEdit>, LanguageClientNull, DocumentFormattingParams> +{ +public: + DocumentFormattingRequest(const DocumentFormattingParams ¶ms = DocumentFormattingParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/formatting"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentRangeFormattingParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + FormattingOptions options() const { return typedValue<FormattingOptions>(optionsKey); } + void setOptions(const FormattingOptions &options) { insert(optionsKey, options); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentRangeFormattingRequest : public Request< + QList<TextEdit>, LanguageClientNull, DocumentFormattingParams> +{ +public: + DocumentRangeFormattingRequest(const DocumentFormattingParams ¶ms = DocumentFormattingParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/rangeFormatting"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentOnTypeFormattingParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + Position position() const { return typedValue<Position>(positionKey); } + void setPosition(const Position &position) { insert(positionKey, position); } + + QString ch() const { return typedValue<QString>(chKey); } + void setCh(const QString &ch) { insert(chKey, ch); } + + FormattingOptions options() const { return typedValue<FormattingOptions>(optionsKey); } + void setOptions(const FormattingOptions &options) { insert(optionsKey, options); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentOnTypeFormattingRequest : public Request< + QList<TextEdit>, LanguageClientNull, DocumentFormattingParams> +{ +public: + DocumentOnTypeFormattingRequest( + const DocumentFormattingParams ¶ms = DocumentFormattingParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/onTypeFormatting"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT RenameParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + Position position() const { return typedValue<Position>(positionKey); } + void setPosition(const Position &position) { insert(positionKey, position); } + + QString newName() const { return typedValue<QString>(newNameKey); } + void setNewName(const QString &newName) { insert(newNameKey, newName); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT RenameRequest : public Request< + WorkspaceEdit, LanguageClientNull, RenameParams> +{ +public: + RenameRequest(const RenameParams ¶ms = RenameParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/rename"; +}; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/languageserverprotocol.pro b/src/libs/languageserverprotocol/languageserverprotocol.pro new file mode 100644 index 0000000000..d9460cbf02 --- /dev/null +++ b/src/libs/languageserverprotocol/languageserverprotocol.pro @@ -0,0 +1,41 @@ +include(../../qtcreatorlibrary.pri) + +DEFINES += LANGUAGESERVERPROTOCOL_LIBRARY + +HEADERS += \ + basemessage.h \ + client.h \ + clientcapabilities.h \ + completion.h \ + diagnostics.h \ + icontent.h \ + initializemessages.h \ + jsonobject.h \ + jsonrpcmessages.h \ + languagefeatures.h \ + languageserverprotocol_global.h \ + lsptypes.h \ + lsputils.h \ + messages.h \ + servercapabilities.h \ + shutdownmessages.h \ + textsynchronization.h \ + workspace.h + +SOURCES += \ + basemessage.cpp \ + clientcapabilities.cpp \ + completion.cpp \ + initializemessages.cpp \ + jsonobject.cpp \ + jsonrpcmessages.cpp \ + languagefeatures.cpp \ + lsptypes.cpp \ + lsputils.cpp \ + messages.cpp \ + servercapabilities.cpp \ + textsynchronization.cpp \ + workspace.cpp \ + client.cpp \ + shutdownmessages.cpp \ + diagnostics.cpp diff --git a/src/libs/languageserverprotocol/languageserverprotocol.qbs b/src/libs/languageserverprotocol/languageserverprotocol.qbs new file mode 100644 index 0000000000..699c0e71ef --- /dev/null +++ b/src/libs/languageserverprotocol/languageserverprotocol.qbs @@ -0,0 +1,50 @@ +import qbs 1.0 + +Project { + name: "LanguageServerProtocol" + + QtcDevHeaders { } + + QtcLibrary { + Depends { name: "Utils" } + cpp.defines: base.concat("LANGUAGESERVERPROTOCOL_LIBRARY") + + files: [ + "basemessage.cpp", + "basemessage.h", + "client.cpp", + "client.h", + "clientcapabilities.cpp", + "clientcapabilities.h", + "completion.cpp", + "completion.h", + "diagnostics.cpp", + "diagnostics.h", + "icontent.h", + "initializemessages.cpp", + "initializemessages.h", + "jsonkeys.h", + "jsonobject.cpp", + "jsonobject.h", + "jsonrpcmessages.cpp", + "jsonrpcmessages.h", + "languagefeatures.cpp", + "languagefeatures.h", + "languageserverprotocol_global.h", + "lsptypes.cpp", + "lsptypes.h", + "lsputils.cpp", + "lsputils.h", + "messages.cpp", + "messages.h", + "servercapabilities.cpp", + "servercapabilities.h", + "shutdownmessages.cpp", + "shutdownmessages.h", + "textsynchronization.cpp", + "textsynchronization.h", + "workspace.cpp", + "workspace.h", + ] + } +} diff --git a/src/libs/languageserverprotocol/languageserverprotocol_dependencies.pri b/src/libs/languageserverprotocol/languageserverprotocol_dependencies.pri new file mode 100644 index 0000000000..c9b21bfbde --- /dev/null +++ b/src/libs/languageserverprotocol/languageserverprotocol_dependencies.pri @@ -0,0 +1,3 @@ +QTC_LIB_NAME = LanguageServerProtocol + +QTC_LIB_DEPENDS += utils diff --git a/src/libs/languageserverprotocol/languageserverprotocol_global.h b/src/libs/languageserverprotocol/languageserverprotocol_global.h new file mode 100644 index 0000000000..83e070f8c2 --- /dev/null +++ b/src/libs/languageserverprotocol/languageserverprotocol_global.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QtGlobal> + +#if defined(LANGUAGESERVERPROTOCOL_LIBRARY) +# define LANGUAGESERVERPROTOCOL_EXPORT Q_DECL_EXPORT +#else +# define LANGUAGESERVERPROTOCOL_EXPORT Q_DECL_IMPORT +#endif diff --git a/src/libs/languageserverprotocol/lsptypes.cpp b/src/libs/languageserverprotocol/lsptypes.cpp new file mode 100644 index 0000000000..c76d4ff6dd --- /dev/null +++ b/src/libs/languageserverprotocol/lsptypes.cpp @@ -0,0 +1,394 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "lsptypes.h" +#include "lsputils.h" + +#include "utils/mimetypes/mimedatabase.h" + +#include <QHash> +#include <QTextBlock> +#include <QTextDocument> +#include <QJsonArray> +#include <QMap> +#include <QVector> +#include <QFile> + +namespace LanguageServerProtocol { + +Utils::optional<DiagnosticSeverity> Diagnostic::severity() const +{ + if (auto val = optionalValue<int>(severityKey)) + return Utils::make_optional(static_cast<DiagnosticSeverity>(val.value())); + return Utils::nullopt; +} + +Utils::optional<Diagnostic::Code> Diagnostic::code() const +{ + QJsonValue codeValue = value(codeKey); + auto it = find(codeKey); + if (codeValue.isUndefined()) + return Utils::nullopt; + QJsonValue::Type type = it.value().type(); + QTC_ASSERT(type == QJsonValue::String || type == QJsonValue::Double, + return Utils::make_optional(Code(QString()))); + return Utils::make_optional(codeValue.isDouble() ? Code(codeValue.toInt()) + : Code(codeValue.toString())); +} + +void Diagnostic::setCode(const Diagnostic::Code &code) +{ + if (auto val = Utils::get_if<int>(&code)) + insert(codeKey, *val); + else if (auto val = Utils::get_if<QString>(&code)) + insert(codeKey, *val); +} + +bool Diagnostic::isValid(QStringList *error) const +{ + return check<Range>(error, rangeKey) + && checkOptional<int>(error, severityKey) + && (checkOptional<int>(error, codeKey) || checkOptional<QString>(error, codeKey)) + && checkOptional<QString>(error, sourceKey) + && check<QString>(error, messageKey); +} + +Utils::optional<QMap<QString, QList<TextEdit>>> WorkspaceEdit::changes() const +{ + using Changes = Utils::optional<QMap<QString, QList<TextEdit>>>; + Changes changes; + auto it = find(changesKey); + if (it != end()) + return Utils::nullopt; + QTC_ASSERT(it.value().type() == QJsonValue::Object, return Changes()); + QJsonObject changesObject(it.value().toObject()); + QMap<QString, QList<TextEdit>> changesResult; + for (const QString &key : changesObject.keys()) + changesResult[key] = LanguageClientArray<TextEdit>(changesObject.value(key)).toList(); + return Utils::make_optional(changesResult); +} + +void WorkspaceEdit::setChanges(const QMap<QString, QList<TextEdit> > &changes) +{ + QJsonObject changesObject; + const auto end = changes.end(); + for (auto it = changes.begin(); it != end; ++it) { + QJsonArray edits; + for (const TextEdit &edit : it.value()) + edits.append(QJsonValue(edit)); + changesObject.insert(it.key(), edits); + } + insert(changesKey, changesObject); +} + +WorkSpaceFolder::WorkSpaceFolder(const QString &uri, const QString &name) +{ + setUri(uri); + setName(name); +} + +MarkupOrString::MarkupOrString(const Utils::variant<QString, MarkupContent> &val) + : Utils::variant<QString, MarkupContent>(val) +{ } + +MarkupOrString::MarkupOrString(const QString &val) + : Utils::variant<QString, MarkupContent>(val) +{ } + +MarkupOrString::MarkupOrString(const MarkupContent &val) + : Utils::variant<QString, MarkupContent>(val) +{ } + +MarkupOrString::MarkupOrString(const QJsonValue &val) +{ + QTC_ASSERT(val.isString() | val.isObject(), return); + if (val.isString()) + emplace<QString>(val.toString()); + else + emplace<MarkupContent>(MarkupContent(val.toObject())); +} + +bool MarkupOrString::isValid(QStringList *error) const +{ + if (Utils::holds_alternative<MarkupContent>(*this) || Utils::holds_alternative<QString>(*this)) + return true; + if (error) { + *error << QCoreApplication::translate("LanguageServerProtocoll::MarkupOrString", + "Expected a string or MarkupContent in MarkupOrString"); + } + return false; +} + +QJsonValue MarkupOrString::toJson() const +{ + if (Utils::holds_alternative<QString>(*this)) + return Utils::get<QString>(*this); + if (Utils::holds_alternative<MarkupContent>(*this)) + return QJsonValue(Utils::get<MarkupContent>(*this)); + return {}; +} + +bool SymbolInformation::isValid(QStringList *error) const +{ + return check<QString>(error, nameKey) + && check<int>(error, kindKey) + && check<Location>(error, locationKey) + && checkOptional<QString>(error, containerNameKey); +} + +bool TextDocumentEdit::isValid(QStringList *error) const +{ + return check<VersionedTextDocumentIdentifier>(error, idKey) + && checkArray<TextEdit>(error, editsKey); +} + +bool TextDocumentItem::isValid(QStringList *error) const +{ + return check<QString>(error, uriKey) + && check<QString>(error, languageIdKey) + && check<int>(error, versionKey) + && check<QString>(error, textKey); +} + +static QHash<Utils::MimeType, QString> mimeTypeLanguageIdMap() +{ + static QHash<Utils::MimeType, QString> hash; + if (!hash.isEmpty()) + return hash; + const QVector<QPair<QString, QString>> languageIdsForMimeTypeNames{ + {"text/x-python", "python"}, + {"text/x-bibtex", "bibtex"}, + {"application/vnd.coffeescript", "coffeescript"}, + {"text/x-chdr", "c"}, + {"text/x-csrc", "c"}, + {"text/x-c++hdr", "cpp"}, + {"text/x-c++src", "cpp"}, + {"text/x-moc", "cpp"}, + {"text/x-csharp", "csharp"}, + {"text/vnd.qtcreator.git.commit", "git-commit"}, + {"text/vnd.qtcreator.git.rebase", "git-rebase"}, + {"text/x-go", "go"}, + {"text/html", "html"}, + {"text/x-java", "java"}, + {"application/javascript", "javascript"}, + {"application/json", "json"}, + {"text/x-tex", "tex"}, + {"text/x-lua", "lua"}, + {"text/x-makefile", "makefile"}, + {"text/markdown", "markdown"}, + {"text/x-objcsrc", "objective-c"}, + {"text/x-objc++src", "objective-cpp"}, + {"application/x-perl", "perl"}, + {"application/x-php", "php"}, + {"application/x-ruby", "ruby"}, + {"text/rust", "rust"}, + {"text/x-sass", "sass"}, + {"text/x-scss", "scss"}, + {"application/x-shellscript", "shellscript"}, + {"application/xml", "xml"}, + {"application/xslt+xml", "xsl"}, + {"application/x-yaml", "yaml"}, + }; + for (const QPair<QString, QString> &languageIdForMimeTypeName : languageIdsForMimeTypeNames) { + const Utils::MimeType &mimeType = Utils::mimeTypeForName(languageIdForMimeTypeName.first); + if (mimeType.isValid()) { + hash[mimeType] = languageIdForMimeTypeName.second; + for (const QString &aliasName : mimeType.aliases()) { + const Utils::MimeType &alias = Utils::mimeTypeForName(aliasName); + if (alias.isValid()) + hash[alias] = languageIdForMimeTypeName.second; + } + } + } + return hash; +} + +QMap<QString, QString> languageIds() +{ + static const QMap<QString, QString> languages({ + {"Windows Bat","bat" }, + {"BibTeX","bibtex" }, + {"Clojure","clojure" }, + {"Coffeescript","coffeescript" }, + {"C","c" }, + {"C++","cpp" }, + {"C#","csharp" }, + {"CSS","css" }, + {"Diff","diff" }, + {"Dockerfile","dockerfile" }, + {"F#","fsharp" }, + {"Git commit","git-commit" }, + {"Git rebase","git-rebase" }, + {"Go","go" }, + {"Groovy","groovy" }, + {"Handlebars","handlebars" }, + {"HTML","html" }, + {"Ini","ini" }, + {"Java","java" }, + {"JavaScript","javascript" }, + {"JSON","json" }, + {"LaTeX","latex" }, + {"Less","less" }, + {"Lua","lua" }, + {"Makefile","makefile" }, + {"Markdown","markdown" }, + {"Objective-C","objective-c" }, + {"Objective-C++","objective-cpp" }, + {"Perl6","perl6" }, + {"Perl","perl" }, + {"PHP","php" }, + {"Powershell","powershell" }, + {"Pug","jade" }, + {"Python","python" }, + {"R","r" }, + {"Razor (cshtml)","razor" }, + {"Ruby","ruby" }, + {"Rust","rust" }, + {"Scss (syntax using curly brackets)","scss"}, + {"Sass (indented syntax)","sass" }, + {"ShaderLab","shaderlab" }, + {"Shell Script (Bash)","shellscript" }, + {"SQL","sql" }, + {"Swift","swift" }, + {"TypeScript","typescript" }, + {"TeX","tex" }, + {"Visual Basic","vb" }, + {"XML","xml" }, + {"XSL","xsl" }, + {"YAML","yaml" } + }); + return languages; +} + +QString TextDocumentItem::mimeTypeToLanguageId(const Utils::MimeType &mimeType) +{ + return mimeTypeLanguageIdMap()[mimeType]; +} + +QString TextDocumentItem::mimeTypeToLanguageId(const QString &mimeTypeName) +{ + return mimeTypeToLanguageId(Utils::mimeTypeForName(mimeTypeName)); +} + +TextDocumentPositionParams::TextDocumentPositionParams() + : TextDocumentPositionParams(TextDocumentIdentifier(), Position()) +{ + +} + +TextDocumentPositionParams::TextDocumentPositionParams( + const TextDocumentIdentifier &document, const Position &position) +{ + setTextDocument(document); + setPosition(position); +} + +bool TextDocumentPositionParams::isValid(QStringList *error) const +{ + return check<TextDocumentIdentifier>(error, textDocumentKey) + && check<Position>(error, positionKey); +} + +Position::Position(int line, int character) +{ + setLine(line); + setCharacter(character); +} + +Position::Position(const QTextCursor &cursor) + : Position(cursor.blockNumber(), cursor.positionInBlock()) +{ } + +int Position::toPositionInDocument(QTextDocument *doc) const +{ + const QTextBlock block = doc->findBlockByNumber(line()); + if (!block.isValid() || block.length() <= character()) + return -1; + return block.position() + character(); +} + +Range::Range(const Position &start, const Position &end) +{ + setStart(start); + setEnd(end); +} + +bool DocumentFilter::applies(const Utils::FileName &fileName, const Utils::MimeType &mimeType) const +{ + if (Utils::optional<QString> _scheme = scheme()) { + if (_scheme.value() == fileName.toString()) + return true; + } + if (Utils::optional<QString> _pattern = pattern()) { + QRegExp regexp(_pattern.value(), + Utils::HostOsInfo::fileNameCaseSensitivity(), + QRegExp::Wildcard); + if (regexp.exactMatch(fileName.toString())) + return true; + } + if (Utils::optional<QString> _lang = language()) { + auto match = [&_lang](const Utils::MimeType &mimeType){ + return _lang.value() == TextDocumentItem::mimeTypeToLanguageId(mimeType); + }; + if (mimeType.isValid() && match(mimeType)) + return true; + return Utils::anyOf(Utils::mimeTypesForFileName(fileName.toString()), match); + } + // return false when any of the filter didn't match but return true when no filter was defined + return !contains(schemeKey) && !contains(languageKey) && !contains(patternKey); +} + +bool DocumentFilter::isValid(QStringList *error) const +{ + return Utils::allOf(QStringList{languageKey, schemeKey, patternKey}, [this, &error](auto key){ + return this->checkOptional<QString>(error, key); + }); +} + +Utils::Link Location::toLink() const +{ + if (!isValid(nullptr)) + return Utils::Link(); + auto file = uri().toString(QUrl::FullyDecoded | QUrl::PreferLocalFile); + return Utils::Link(file, range().start().line() + 1, range().start().character()); +} + +DocumentUri::DocumentUri(const QString &other) + : QUrl(QUrl::fromPercentEncoding(other.toLocal8Bit())) +{ + QTC_ASSERT(isValid(), qWarning() << other); +} + +DocumentUri::DocumentUri(const Utils::FileName &other) + : QUrl(QUrl::fromLocalFile(other.toString())) +{ } + +Utils::FileName DocumentUri::toFileName() const +{ + return isLocalFile() ? Utils::FileName::fromUserInput(QUrl(*this).toLocalFile()) + : Utils::FileName(); +} + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/lsptypes.h b/src/libs/languageserverprotocol/lsptypes.h new file mode 100644 index 0000000000..014e67161b --- /dev/null +++ b/src/libs/languageserverprotocol/lsptypes.h @@ -0,0 +1,505 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "jsonkeys.h" +#include "jsonobject.h" +#include "lsputils.h" + +#include <utils/fileutils.h> +#include <utils/optional.h> +#include <utils/variant.h> +#include <utils/link.h> + +#include <QTextCursor> +#include <QJsonObject> +#include <QUrl> +#include <QList> + +namespace LanguageServerProtocol { + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentUri : public QUrl +{ +public: + DocumentUri() = default; + Utils::FileName toFileName() const; + + static DocumentUri fromProtocol(const QString &uri) { return DocumentUri(uri); } + static DocumentUri fromFileName(const Utils::FileName &file) { return DocumentUri(file); } + + operator QJsonValue() const { return QJsonValue(toString()); } + +private: + DocumentUri(const QString &other); + DocumentUri(const Utils::FileName &other); + + friend class LanguageClientValue<QString>; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT Position : public JsonObject +{ +public: + Position() = default; + Position(int line, int character); + Position(const QTextCursor &cursor); + using JsonObject::JsonObject; + + // Line position in a document (zero-based). + int line() const { return typedValue<int>(lineKey); } + void setLine(int line) { insert(lineKey, line); } + + /* + * Character offset on a line in a document (zero-based). Assuming that the line is + * represented as a string, the `character` value represents the gap between the + * `character` and `character + 1`. + * + * If the character value is greater than the line length it defaults back to the + * line length. + */ + int character() const { return typedValue<int>(characterKey); } + void setCharacter(int character) { insert(characterKey, character); } + + bool isValid(QStringList *error) const override + { return check<int>(error, lineKey) && check<int>(error, characterKey); } + + int toPositionInDocument(QTextDocument *doc) const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT Range : public JsonObject +{ +public: + Range() = default; + Range(const Position &start, const Position &end); + using JsonObject::JsonObject; + + // The range's start position. + Position start() const { return typedValue<Position>(startKey); } + void setStart(const Position &start) { insert(startKey, start); } + + // The range's end position. + Position end() const { return typedValue<Position>(endKey); } + void setEnd(const Position &end) { insert(endKey, end); } + + bool isValid(QStringList *error) const override + { return check<Position>(error, startKey) && check<Position>(error, endKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT Location : public JsonObject +{ +public: + using JsonObject::JsonObject; + using JsonObject::operator=; + + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + Utils::Link toLink() const; + + bool isValid(QStringList *error) const override + { return check<QString>(error, uriKey) && check<Range>(error, rangeKey); } +}; + +enum class DiagnosticSeverity +{ + Error = 1, + Warning = 2, + Information = 3, + Hint = 4 + +}; + +class LANGUAGESERVERPROTOCOL_EXPORT Diagnostic : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The range at which the message applies. + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + // The diagnostic's severity. Can be omitted. If omitted it is up to the + // client to interpret diagnostics as error, warning, info or hint. + Utils::optional<DiagnosticSeverity> severity() const; + void setSeverity(const DiagnosticSeverity &severity) + { insert(severityKey, static_cast<int>(severity)); } + void clearSeverity() { remove(severityKey); } + + // The diagnostic's code, which might appear in the user interface. + using Code = Utils::variant<int, QString>; + Utils::optional<Code> code() const; + void setCode(const Code &code); + void clearCode() { remove(codeKey); } + + // A human-readable string describing the source of this + // diagnostic, e.g. 'typescript' or 'super lint'. + Utils::optional<QString> source() const + { return optionalValue<QString>(sourceKey); } + void setSource(const QString &source) { insert(sourceKey, source); } + void clearSource() { remove(sourceKey); } + + // The diagnostic's message. + QString message() const + { return typedValue<QString>(messageKey); } + void setMessage(const QString &message) { insert(messageKey, message); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT Command : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // Title of the command, like `save`. + QString title() const { return typedValue<QString>(titleKey); } + void setTitle(const QString &title) { insert(titleKey, title); } + + // The identifier of the actual command handler. + QString command() const { return typedValue<QString>(commandKey); } + void setCommand(const QString &command) { insert(commandKey, command); } + + // Arguments that the command handler should be invoked with. + Utils::optional<QJsonArray> arguments() const { return typedValue<QJsonArray>(argumentsKey); } + void setArguments(const QJsonObject &arguments) { insert(argumentsKey, arguments); } + + bool isValid(QStringList *error) const override + { return check<QString>(error, titleKey) + && check<QString>(error, commandKey) + && checkOptional<QJsonArray>(error, argumentsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextEdit : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The range of the text document to be manipulated. To insert + // text into a document create a range where start === end. + Range range() const { return typedValue<Range>(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + // The string to be inserted. For delete operations use an empty string. + QString newText() const { return typedValue<QString>(newTextKey); } + void setNewText(const QString &text) { insert(newTextKey, text); } + + bool isValid(QStringList *error) const override + { return check<Range>(error, rangeKey) && check<QString>(error, newTextKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentIdentifier : public JsonObject +{ +public: + TextDocumentIdentifier() : TextDocumentIdentifier(DocumentUri()) {} + TextDocumentIdentifier(const DocumentUri &uri) { setUri(uri); } + using JsonObject::JsonObject; + + // The text document's URI. + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + bool isValid(QStringList *error) const override { return check<QString>(error, uriKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT VersionedTextDocumentIdentifier : public TextDocumentIdentifier +{ +public: + using TextDocumentIdentifier::TextDocumentIdentifier; + + /* + * The version number of this document. If a versioned text document identifier + * is sent from the server to the client and the file is not open in the editor + * (the server has not received an open notification before) the server can send + * `null` to indicate that the version is known and the content on disk is the + * truth (as speced with document content ownership) + */ + LanguageClientValue<int> version() const { return clientValue<int>(versionKey); } + void setVersion(LanguageClientValue<int> version) { insert(versionKey, version.toJson()); } + + bool isValid(QStringList *error) const override + { return TextDocumentIdentifier::isValid(error) && check<int, std::nullptr_t>(error, versionKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentEdit : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The text document to change. + VersionedTextDocumentIdentifier id() const + { return typedValue<VersionedTextDocumentIdentifier>(idKey); } + void setId(const VersionedTextDocumentIdentifier &id) { insert(idKey, id); } + + // The edits to be applied. + QList<TextEdit> edits() const { return array<TextEdit>(editsKey); } + void setEdits(const QList<TextEdit> edits) { insertArray(editsKey, edits); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceEdit : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // Holds changes to existing resources. + Utils::optional<QMap<QString, QList<TextEdit>>> changes() const; + void setChanges(const QMap<QString, QList<TextEdit>> &changes); + + /* + * An array of `TextDocumentEdit`s to express changes to n different text documents + * where each text document edit addresses a specific version of a text document. + * Whether a client supports versioned document edits is expressed via + * `WorkspaceClientCapabilities.workspaceEdit.documentChanges`. + * + * Note: If the client can handle versioned document edits and if documentChanges are present, + * the latter are preferred over changes. + */ + Utils::optional<QList<TextDocumentEdit>> documentChanges() const + { return optionalArray<TextDocumentEdit>(documentChangesKey); } + void setDocumentChanges(const QList<TextDocumentEdit> &changes) + { insertArray(changesKey, changes); } + + bool isValid(QStringList *error) const override + { return checkOptionalArray<TextDocumentEdit>(error, documentChangesKey); } +}; + +LANGUAGESERVERPROTOCOL_EXPORT QMap<QString, QString> languageIds(); + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentItem : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The text document's URI. + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + // The text document's language identifier. + QString languageId() const { return typedValue<QString>(languageIdKey); } + void setLanguageId(const QString &id) { insert(languageIdKey, id); } + + // The version number of this document (it will increase after each change, including undo/redo + int version() const { return typedValue<int>(versionKey); } + void setVersion(int version) { insert(versionKey, version); } + + // The content of the opened text document. + QString text() const { return typedValue<QString>(textKey); } + void setText(const QString &text) { insert(textKey, text); } + + bool isValid(QStringList *error) const override; + + static QString mimeTypeToLanguageId(const Utils::MimeType &mimeType); + static QString mimeTypeToLanguageId(const QString &mimeTypeName); +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentPositionParams : public JsonObject +{ +public: + TextDocumentPositionParams(); + TextDocumentPositionParams(const TextDocumentIdentifier &document, const Position &position); + using JsonObject::JsonObject; + + // The text document. + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); } + + // The position inside the text document. + Position position() const { return typedValue<Position>(positionKey); } + void setPosition(const Position &position) { insert(positionKey, position); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DocumentFilter : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // A language id, like `typescript`. + Utils::optional<QString> language() const { return optionalValue<QString>(languageKey); } + void setLanguage(const QString &language) { insert(languageKey, language); } + void clearLanguage() { remove(languageKey); } + + // A Uri [scheme](#Uri.scheme), like `file` or `untitled`. + Utils::optional<QString> scheme() const { return optionalValue<QString>(schemeKey); } + void setScheme(const QString &scheme) { insert(schemeKey, scheme); } + void clearScheme() { remove(schemeKey); } + + // A glob pattern, like `*.{ts,js}`. + Utils::optional<QString> pattern() const { return optionalValue<QString>(patternKey); } + void setPattern(const QString &pattern) { insert(patternKey, pattern); } + void clearPattern() { remove(patternKey); } + + bool applies(const Utils::FileName &fileName, + const Utils::MimeType &mimeType = Utils::MimeType()) const; + + bool isValid(QStringList *error) const override; +}; + +enum class MarkupKind +{ + plaintext, + markdown, +}; + +class LANGUAGESERVERPROTOCOL_EXPORT MarkupContent : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The type of the Markup + MarkupKind kind() const { return static_cast<MarkupKind>(typedValue<int>(kindKey)); } + void setKind(MarkupKind kind) { insert(kindKey, static_cast<int>(kind)); } + + // The content itself + QString content() const { return typedValue<QString>(contentKey); } + void setContent(const QString &content) { insert(contentKey, content); } + + bool isValid(QStringList *error) const override + { return check<int>(error, kindKey) && check<QString>(error, contentKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT MarkupOrString : public Utils::variant<QString, MarkupContent> +{ +public: + MarkupOrString() = default; + MarkupOrString(const Utils::variant<QString, MarkupContent> &val); + explicit MarkupOrString(const QString &val); + explicit MarkupOrString(const MarkupContent &val); + MarkupOrString(const QJsonValue &val); + + bool isValid(QStringList *error) const; + + QJsonValue toJson() const; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkSpaceFolder : public JsonObject +{ +public: + WorkSpaceFolder() = default; + WorkSpaceFolder(const QString &uri, const QString &name); + using JsonObject::JsonObject; + + // The associated URI for this workspace folder. + QString uri() const { return typedValue<QString>(uriKey); } + void setUri(const QString &uri) { insert(uriKey, uri); } + + // The name of the workspace folder. Defaults to the uri's basename. + QString name() const { return typedValue<QString>(nameKey); } + void setName(const QString &name) { insert(nameKey, name); } + + bool isValid(QStringList *error) const override + { return check<QString>(error, uriKey) && check<QString>(error, nameKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SymbolInformation : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString name() const { return typedValue<QString>(nameKey); } + void setName(const QString &name) { insert(nameKey, name); } + + int kind() const { return typedValue<int>(kindKey); } + void setKind(int kind) { insert(kindKey, kind); } + + Location location() const { return typedValue<Location>(locationKey); } + void setLocation(const Location &location) { insert(locationKey, location); } + + Utils::optional<QString> containerName() const + { return optionalValue<QString>(containerNameKey); } + void setContainerName(const QString &containerName) { insert(containerNameKey, containerName); } + void clearContainerName() { remove(containerNameKey); } + + bool isValid(QStringList *error) const override; +}; + +enum class SymbolKind { + File = 1, + FirstSymbolKind = File, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26, + LastSymbolKind = TypeParameter, +}; + +namespace CompletionItemKind { +enum Kind { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25 +}; +} // namespace CompletionItemKind + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/lsputils.cpp b/src/libs/languageserverprotocol/lsputils.cpp new file mode 100644 index 0000000000..40d782c9f6 --- /dev/null +++ b/src/libs/languageserverprotocol/lsputils.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "lsputils.h" + +#include <utils/mimetypes/mimedatabase.h> + +#include <QHash> +#include <QVector> + +namespace LanguageServerProtocol { + +template<> +QString fromJsonValue<QString>(const QJsonValue &value) +{ + QTC_ASSERT(value.isString(), return QString()); + return value.toString(); +} + +template<> +int fromJsonValue<int>(const QJsonValue &value) +{ + QTC_ASSERT(value.isDouble(), return 0); + return int(value.toDouble()); +} + +template<> +double fromJsonValue<double>(const QJsonValue &value) +{ + QTC_ASSERT(value.isDouble(), return 0); + return value.toDouble(); +} + +template<> +bool fromJsonValue<bool>(const QJsonValue &value) +{ + QTC_ASSERT(value.isBool(), return false); + return value.toBool(); +} + +template<> +QJsonArray fromJsonValue<QJsonArray>(const QJsonValue &value) +{ + QTC_ASSERT(value.isArray(), return QJsonArray()); + return value.toArray(); +} + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/lsputils.h b/src/libs/languageserverprotocol/lsputils.h new file mode 100644 index 0000000000..17c95084db --- /dev/null +++ b/src/libs/languageserverprotocol/lsputils.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "languageserverprotocol_global.h" + +#include <utils/algorithm.h> +#include <utils/mimetypes/mimetype.h> +#include <utils/optional.h> +#include <utils/qtcassert.h> +#include <utils/variant.h> + +#include <QJsonArray> +#include <QJsonObject> + +namespace LanguageServerProtocol { + +template <typename T> +T fromJsonValue(const QJsonValue &value) +{ + QTC_ASSERT(value.isObject(), return T()); + return T(value.toObject()); +} + +template<> +LANGUAGESERVERPROTOCOL_EXPORT QString fromJsonValue<QString>(const QJsonValue &value); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT int fromJsonValue<int>(const QJsonValue &value); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT double fromJsonValue<double>(const QJsonValue &value); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT bool fromJsonValue<bool>(const QJsonValue &value); + +template<> +LANGUAGESERVERPROTOCOL_EXPORT QJsonArray fromJsonValue<QJsonArray>(const QJsonValue &value); + +template <typename T> +class LanguageClientArray : public Utils::variant<QList<T>, std::nullptr_t> +{ +public: + using Utils::variant<QList<T>, std::nullptr_t>::variant; + using Utils::variant<QList<T>, std::nullptr_t>::operator=; + + LanguageClientArray(const QList<T> &list) + { *this = list; } + + LanguageClientArray(const QJsonValue &value) + { + if (value.isArray()) { + QList<T> values; + values.reserve(value.toArray().count()); + for (auto arrayValue : value.toArray()) + values << fromJsonValue<T>(arrayValue); + *this = values; + } else { + *this = nullptr; + } + } + + QJsonValue toJson() const + { + if (const auto list = Utils::get_if<QList<T>>(this)) { + QJsonArray array; + for (const T &value : *list) + array.append(QJsonValue(value)); + return array; + } + return QJsonValue(); + } + + QList<T> toList() const + { + QTC_ASSERT(Utils::holds_alternative<QList<T>>(*this), return {}); + return Utils::get<QList<T>>(*this); + } + bool isNull() const { return Utils::holds_alternative<std::nullptr_t>(*this); } +}; + +template <typename T> +class LanguageClientValue : public Utils::variant<T, std::nullptr_t> +{ +public: + using Utils::variant<T, std::nullptr_t>::operator=; + + LanguageClientValue() : Utils::variant<T, std::nullptr_t>(nullptr) { } + LanguageClientValue(const T &value) : Utils::variant<T, std::nullptr_t>(value) { } + LanguageClientValue(const QJsonValue &value) + { + if (QTC_GUARD(value.isUndefined()) || value.isNull()) + *this = nullptr; + else + *this = fromJsonValue<T>(value); + } + + T value(const T &defaultValue = T()) const + { + QTC_ASSERT(Utils::holds_alternative<T>(*this), return defaultValue); + return Utils::get<T>(*this); + } + + template<typename Type> + LanguageClientValue<Type> transform() + { + QTC_ASSERT(!Utils::holds_alternative<T>(*this), return LanguageClientValue<Type>()); + return Type(Utils::get<T>(*this)); + } + + QJsonValue toJson() const + { + if (auto val = Utils::get_if<T>(this)) + return QJsonValue(*val); + return QJsonValue(); + } + + bool isNull() const { return Utils::holds_alternative<std::nullptr_t>(*this); } +}; + +template <typename T> +QJsonArray enumArrayToJsonArray(const QList<T> &values) +{ + QJsonArray array; + for (T value : values) + array.append(static_cast<int>(value)); + return array; +} + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/messages.cpp b/src/libs/languageserverprotocol/messages.cpp new file mode 100644 index 0000000000..310dd88ebe --- /dev/null +++ b/src/libs/languageserverprotocol/messages.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "messages.h" + +#include "icontent.h" + +namespace LanguageServerProtocol { + +constexpr const char ShowMessageNotification::methodName[]; +constexpr const char ShowMessageRequest::methodName[]; +constexpr const char LogMessageNotification::methodName[]; +constexpr const char TelemetryNotification::methodName[]; + +ShowMessageNotification::ShowMessageNotification(const ShowMessageParams ¶ms) + : Notification(methodName, params) +{ } + +ShowMessageRequest::ShowMessageRequest(const ShowMessageRequestParams ¶ms) + : Request(methodName, params) +{ } + +LogMessageNotification::LogMessageNotification() + : Notification(methodName) +{ } + +TelemetryNotification::TelemetryNotification() + : Notification(methodName) +{ } + +bool ShowMessageRequestParams::isValid(QStringList *error) const +{ + return ShowMessageParams::isValid(error) + && checkOptionalArray<MessageActionItem>(error, actionsKey); +} + +static QString messageTypeName(int messageType) +{ + switch (messageType) { + case Error: return QString("Error"); + case Warning: return QString("Warning"); + case Info: return QString("Info"); + case Log: return QString("Log"); + } + return QString(""); +} + +QString ShowMessageParams::toString() +{ + return messageTypeName(type()) + ": " + message(); +} + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/messages.h b/src/libs/languageserverprotocol/messages.h new file mode 100644 index 0000000000..a5ddf5df90 --- /dev/null +++ b/src/libs/languageserverprotocol/messages.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "jsonrpcmessages.h" + +namespace LanguageServerProtocol { + +enum MessageType { + Error = 1, + Warning = 2, + Info = 3, + Log = 4, +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + int type() const { return typedValue<int>(typeKey); } + void setType(int type) { insert(typeKey, type); } + + QString message() const { return typedValue<QString>(messageKey); } + void setMessage(QString message) { insert(messageKey, message); } + + QString toString(); + + bool isValid(QStringList *error) const override + { return check<int>(error, typeKey) && check<QString>(error, messageKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageNotification : public Notification<ShowMessageParams> +{ +public: + ShowMessageNotification(const ShowMessageParams ¶ms = ShowMessageParams()); + using Notification::Notification; + constexpr static const char methodName[] = "window/showMessage"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT MessageActionItem : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString title() const { return typedValue<QString>(titleKey); } + void setTitle(QString title) { insert(titleKey, title); } + + bool isValid(QStringList *error) const override { return check<QString>(error, titleKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageRequestParams : public ShowMessageParams +{ +public: + using ShowMessageParams::ShowMessageParams; + + Utils::optional<QList<MessageActionItem>> actions() const + { return optionalArray<MessageActionItem>(actionsKey); } + void setActions(const QList<MessageActionItem> &actions) { insertArray(actionsKey, actions); } + void clearActions() { remove(actionsKey); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageRequest : public Request< + LanguageClientValue<MessageActionItem>, LanguageClientNull, ShowMessageRequestParams> +{ +public: + ShowMessageRequest(const ShowMessageRequestParams ¶ms = ShowMessageRequestParams()); + using Request::Request; + constexpr static const char methodName[] = "window/showMessageRequest"; +}; + +using ShowMessageResponse = Response<LanguageClientValue<MessageActionItem>, LanguageClientNull>; + +using LogMessageParams = ShowMessageParams; + +class LANGUAGESERVERPROTOCOL_EXPORT LogMessageNotification : public Notification<LogMessageParams> +{ +public: + LogMessageNotification(); + using Notification::Notification; + constexpr static const char methodName[] = "window/logMessage"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TelemetryNotification : public Notification<JsonObject> +{ +public: + TelemetryNotification(); + using Notification::Notification; + constexpr static const char methodName[] = "telemetry/event"; + + bool parametersAreValid(QString * /*error*/) const override { return params().has_value(); } +}; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/servercapabilities.cpp b/src/libs/languageserverprotocol/servercapabilities.cpp new file mode 100644 index 0000000000..b728d35908 --- /dev/null +++ b/src/libs/languageserverprotocol/servercapabilities.cpp @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "servercapabilities.h" + +namespace LanguageServerProtocol { + +Utils::optional<ServerCapabilities::TextDocumentSync> ServerCapabilities::textDocumentSync() const +{ + const QJsonValue &sync = value(textDocumentSyncKey); + if (sync.isUndefined()) + return Utils::nullopt; + return Utils::make_optional(sync.isDouble() ? TextDocumentSync(sync.toInt()) + : TextDocumentSync(TextDocumentSyncOptions(sync.toObject()))); +} + +void ServerCapabilities::setTextDocumentSync(const ServerCapabilities::TextDocumentSync &textDocumentSync) +{ + if (auto val = Utils::get_if<int>(&textDocumentSync)) + insert(textDocumentSyncKey, *val); + else if (auto val = Utils::get_if<TextDocumentSyncOptions>(&textDocumentSync)) + insert(textDocumentSyncKey, *val); +} + +TextDocumentSyncKind ServerCapabilities::textDocumentSyncKindHelper() +{ + Utils::optional<TextDocumentSync> sync = textDocumentSync(); + if (sync.has_value()) { + if (auto kind = Utils::get_if<int>(&sync.value())) + return static_cast<TextDocumentSyncKind>(*kind); + if (auto options = Utils::get_if<TextDocumentSyncOptions>(&sync.value())) { + if (const Utils::optional<int> &change = options->change()) + return static_cast<TextDocumentSyncKind>(change.value()); + } + } + return TextDocumentSyncKind::None; +} + +Utils::optional<Utils::variant<bool, ServerCapabilities::RegistrationOptions>> +ServerCapabilities::typeDefinitionProvider() const +{ + using RetType = Utils::variant<bool, ServerCapabilities::RegistrationOptions>; + QJsonValue provider = value(typeDefinitionProviderKey); + if (provider.isUndefined()) + return Utils::nullopt; + return Utils::make_optional(provider.isBool() ? RetType(provider.toBool()) + : RetType(RegistrationOptions(provider.toObject()))); +} + +void ServerCapabilities::setTypeDefinitionProvider( + const Utils::variant<bool, ServerCapabilities::RegistrationOptions> &typeDefinitionProvider) +{ + if (auto activated = Utils::get_if<bool>(&typeDefinitionProvider)) + insert(typeDefinitionProviderKey, *activated); + else if (auto options = Utils::get_if<RegistrationOptions>(&typeDefinitionProvider)) + insert(typeDefinitionProviderKey, *options); +} + +Utils::optional<Utils::variant<bool, ServerCapabilities::RegistrationOptions>> +ServerCapabilities::implementationProvider() const +{ + using RetType = Utils::variant<bool, ServerCapabilities::RegistrationOptions>; + QJsonValue provider = value(implementationProviderKey); + if (provider.isUndefined()) + return Utils::nullopt; + return Utils::make_optional(provider.isBool() ? RetType(provider.toBool()) + : RetType(RegistrationOptions(provider.toObject()))); +} + +void ServerCapabilities::setImplementationProvider( + const Utils::variant<bool, ServerCapabilities::RegistrationOptions> &implementationProvider) +{ + if (Utils::holds_alternative<bool>(implementationProvider)) + insert(implementationProviderKey, Utils::get<bool>(implementationProvider)); + else + insert(implementationProviderKey, Utils::get<RegistrationOptions>(implementationProvider)); +} + +bool ServerCapabilities::isValid(QStringList *error) const +{ + return checkOptional<TextDocumentSyncOptions, int>(error, textDocumentSyncKey) + && checkOptional<bool>(error, hoverProviderKey) + && checkOptional<CompletionOptions>(error, completionProviderKey) + && checkOptional<SignatureHelpOptions>(error, signatureHelpProviderKey) + && checkOptional<bool>(error, definitionProviderKey) + && checkOptional<bool, RegistrationOptions>(error, typeDefinitionProviderKey) + && checkOptional<bool, RegistrationOptions>(error, implementationProviderKey) + && checkOptional<bool>(error, referenceProviderKey) + && checkOptional<bool>(error, documentHighlightProviderKey) + && checkOptional<bool>(error, documentSymbolProviderKey) + && checkOptional<bool>(error, workspaceSymbolProviderKey) + && checkOptional<bool>(error, codeActionProviderKey) + && checkOptional<CodeLensOptions>(error, codeLensProviderKey) + && checkOptional<bool>(error, documentFormattingProviderKey) + && checkOptional<bool>(error, documentRangeFormattingProviderKey) + && checkOptional<bool>(error, renameProviderKey) + && checkOptional<DocumentLinkOptions>(error, documentLinkProviderKey) + && checkOptional<TextDocumentRegistrationOptions>(error, colorProviderKey) + && checkOptional<ExecuteCommandOptions>(error, executeCommandProviderKey) + && checkOptional<WorkspaceServerCapabilities>(error, workspaceKey); +} + +Utils::optional<Utils::variant<QString, bool> > +ServerCapabilities::WorkspaceServerCapabilities::WorkspaceFoldersCapabilities::changeNotifications() const +{ + using RetType = Utils::variant<QString, bool>; + QJsonValue provider = value(implementationProviderKey); + if (provider.isUndefined()) + return Utils::nullopt; + return Utils::make_optional(provider.isBool() ? RetType(provider.toBool()) + : RetType(provider.toString())); +} + +void ServerCapabilities::WorkspaceServerCapabilities::WorkspaceFoldersCapabilities::setChangeNotifications( + Utils::variant<QString, bool> changeNotifications) +{ + if (auto val = Utils::get_if<bool>(&changeNotifications)) + insert(changeNotificationsKey, *val); + else if (auto val = Utils::get_if<QString>(&changeNotifications)) + insert(changeNotificationsKey, *val); +} + +bool ServerCapabilities::WorkspaceServerCapabilities::WorkspaceFoldersCapabilities::isValid(QStringList *error) const +{ + return checkOptional<bool>(error, supportedKey) + && checkOptional<QString, bool>(error, changeNotificationsKey); +} + +bool TextDocumentRegistrationOptions::filterApplies(const Utils::FileName &fileName, + const Utils::MimeType &mimeType) const +{ + const LanguageClientArray<DocumentFilter> &selector = documentSelector(); + return selector.isNull() + || selector.toList().isEmpty() + || Utils::anyOf(selector.toList(), [&](auto filter){ + return filter.applies(fileName, mimeType); + }); +} + +bool ServerCapabilities::RegistrationOptions::isValid(QStringList *error) const +{ + return TextDocumentRegistrationOptions::isValid(error) + && StaticRegistrationOptions::isValid(error); +} + +bool TextDocumentSyncOptions::isValid(QStringList *error) const +{ + return checkOptional<bool>(error, openCloseKey) + && checkOptional<int>(error, changeKey) + && checkOptional<bool>(error, willSaveKey) + && checkOptional<bool>(error, willSaveWaitUntilKey) + && checkOptional<SaveOptions>(error, saveKey); +} + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/servercapabilities.h b/src/libs/languageserverprotocol/servercapabilities.h new file mode 100644 index 0000000000..ab1990ee3a --- /dev/null +++ b/src/libs/languageserverprotocol/servercapabilities.h @@ -0,0 +1,388 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "lsptypes.h" + +namespace LanguageServerProtocol { + +class LANGUAGESERVERPROTOCOL_EXPORT ResolveProviderOption : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Utils::optional<bool> resolveProvider() const { return optionalValue<bool>(resolveProviderKey); } + void setResolveProvider(bool resolveProvider) { insert(resolveProviderKey, resolveProvider); } + void clearResolveProvider() { remove(resolveProviderKey); } + + bool isValid(QStringList *error) const override + { return checkOptional<bool>(error, resolveProviderKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentRegistrationOptions : public virtual JsonObject +{ +public: + using JsonObject::JsonObject; + + LanguageClientArray<DocumentFilter> documentSelector() const + { return clientArray<DocumentFilter>(documentSelectorKey); } + void setDocumentSelector(const LanguageClientArray<DocumentFilter> &documentSelector) + { insert(documentSelectorKey, documentSelector.toJson()); } + + bool filterApplies(const Utils::FileName &fileName, + const Utils::MimeType &mimeType = Utils::MimeType()) const; + + bool isValid(QStringList *error) const override + { return checkArray<DocumentFilter>(error, documentSelectorKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SaveOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // The client is supposed to include the content on save. + Utils::optional<bool> includeText() const { return optionalValue<bool>(includeTextKey); } + void setIncludeText(bool includeText) { insert(includeTextKey, includeText); } + void clearIncludeText() { remove(includeTextKey); } + + bool isValid(QStringList *error) const override + { return checkOptional<bool>(error, includeTextKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentSyncOptions : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // Open and close notifications are sent to the server. + Utils::optional<bool> openClose() const { return optionalValue<bool>(openCloseKey); } + void setOpenClose(bool openClose) { insert(openCloseKey, openClose); } + void clearOpenClose() { remove(openCloseKey); } + + // Change notifications are sent to the server. See TextDocumentSyncKind.None, + // TextDocumentSyncKind.Full and TextDocumentSyncKind.Incremental. + Utils::optional<int> change() const { return optionalValue<int>(changeKey); } + void setChange(int change) { insert(changeKey, change); } + void clearChange() { remove(changeKey); } + + // Will save notifications are sent to the server. + Utils::optional<bool> willSave() const { return optionalValue<bool>(willSaveKey); } + void setWillSave(bool willSave) { insert(willSaveKey, willSave); } + void clearWillSave() { remove(willSaveKey); } + + // Will save wait until requests are sent to the server. + Utils::optional<bool> willSaveWaitUntil() const + { return optionalValue<bool>(willSaveWaitUntilKey); } + void setWillSaveWaitUntil(bool willSaveWaitUntil) + { insert(willSaveWaitUntilKey, willSaveWaitUntil); } + void clearWillSaveWaitUntil() { remove(willSaveWaitUntilKey); } + + // Save notifications are sent to the server. + Utils::optional<SaveOptions> save() const { return optionalValue<SaveOptions>(saveKey); } + void setSave(const SaveOptions &save) { insert(saveKey, save); } + void clearSave() { remove(saveKey); } + + bool isValid(QStringList *error) const override; +}; + +enum class TextDocumentSyncKind +{ + // Documents should not be synced at all. + None = 0, + // Documents are synced by always sending the full content of the document. + Full = 1, + // Documents are synced by sending the full content on open. + // After that only incremental updates to the document are send. + Incremental = 2 +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ServerCapabilities : public JsonObject +{ +public: + using JsonObject::JsonObject; + + // Defines how the host (editor) should sync document changes to the language server. + + class LANGUAGESERVERPROTOCOL_EXPORT CompletionOptions : public ResolveProviderOption + { + public: + using ResolveProviderOption::ResolveProviderOption; + + // The characters that trigger completion automatically. + Utils::optional<QList<QString>> triggerCharacters() const + { return optionalArray<QString>(triggerCharactersKey); } + void setTriggerCharacters(const QList<QString> &triggerCharacters) + { insertArray(triggerCharactersKey, triggerCharacters); } + void clearTriggerCharacters() { remove(triggerCharactersKey); } + + bool isValid(QStringList *error) const override + { return checkOptionalArray<QString>(error, triggerCharactersKey); } + }; + + class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelpOptions : public JsonObject + { + public: + using JsonObject::JsonObject; + + // The characters that trigger signature help automatically. + Utils::optional<QList<QString>> triggerCharacters() const + { return optionalArray<QString>(triggerCharactersKey); } + void setTriggerCharacters(const QList<QString> &triggerCharacters) + { insertArray(triggerCharactersKey, triggerCharacters); } + void clearTriggerCharacters() { remove(triggerCharactersKey); } + }; + + using CodeLensOptions = ResolveProviderOption; + + class LANGUAGESERVERPROTOCOL_EXPORT DocumentOnTypeFormattingOptions : public JsonObject + { + public: + using JsonObject::JsonObject; + + // A character on which formatting should be triggered, like `}`. + QString firstTriggerCharacter() const { return typedValue<QString>(firstTriggerCharacterKey); } + void setFirstTriggerCharacter(QString firstTriggerCharacter) + { insert(firstTriggerCharacterKey, firstTriggerCharacter); } + + // More trigger characters. + Utils::optional<QList<QString>> moreTriggerCharacter() const + { return optionalArray<QString>(moreTriggerCharacterKey); } + void setMoreTriggerCharacter(const QList<QString> &moreTriggerCharacter) + { insertArray(moreTriggerCharacterKey, moreTriggerCharacter); } + void clearMoreTriggerCharacter() { remove(moreTriggerCharacterKey); } + + bool isValid(QStringList *error) const override + { + return check<QString>(error, firstTriggerCharacterKey) + && checkOptionalArray<QString>(error, moreTriggerCharacterKey); + } + }; + + using DocumentLinkOptions = ResolveProviderOption; + + class LANGUAGESERVERPROTOCOL_EXPORT ExecuteCommandOptions : public JsonObject + { + public: + using JsonObject::JsonObject; + + QList<QString> commands() const { return array<QString>(commandsKey); } + void setCommands(const QList<QString> &commands) { insertArray(commandsKey, commands); } + + bool isValid(QStringList *error) const override + { return checkArray<QString>(error, commandsKey); } + }; + + using ColorProviderOptions = JsonObject; + + class LANGUAGESERVERPROTOCOL_EXPORT StaticRegistrationOptions : public virtual JsonObject + { + public: + using JsonObject::JsonObject; + + // The id used to register the request. The id can be used to deregister + // the request again. See also Registration#id. + Utils::optional<QString> id() const { return optionalValue<QString>(idKey); } + void setId(const QString &id) { insert(idKey, id); } + void clearId() { remove(idKey); } + }; + + // Defines how text documents are synced. Is either a detailed structure defining each + // notification or for backwards compatibility the TextDocumentSyncKind number. + using TextDocumentSync = Utils::variant<TextDocumentSyncOptions, int>; + Utils::optional<TextDocumentSync> textDocumentSync() const; + void setTextDocumentSync(const TextDocumentSync &textDocumentSync); + void clearTextDocumentSync() { remove(textDocumentSyncKey); } + + TextDocumentSyncKind textDocumentSyncKindHelper(); + + // The server provides hover support. + Utils::optional<bool> hoverProvider() const { return optionalValue<bool>(hoverProviderKey); } + void setHoverProvider(bool hoverProvider) { insert(hoverProviderKey, hoverProvider); } + void clearHoverProvider() { remove(hoverProviderKey); } + + // The server provides completion support. + Utils::optional<CompletionOptions> completionProvider() const + { return optionalValue<CompletionOptions>(completionProviderKey); } + void setCompletionProvider(const CompletionOptions &completionProvider) + { insert(completionProviderKey, completionProvider); } + void clearCompletionProvider() { remove(completionProviderKey); } + + // The server provides signature help support. + Utils::optional<SignatureHelpOptions> signatureHelpProvider() const + { return optionalValue<SignatureHelpOptions>(signatureHelpProviderKey); } + void setSignatureHelpProvider(const SignatureHelpOptions &signatureHelpProvider) + { insert(signatureHelpProviderKey, signatureHelpProvider); } + void clearSignatureHelpProvider() { remove(signatureHelpProviderKey); } + + // The server provides goto definition support. + Utils::optional<bool> definitionProvider() const + { return optionalValue<bool>(definitionProviderKey); } + void setDefinitionProvider(bool definitionProvider) + { insert(definitionProviderKey, definitionProvider); } + void clearDefinitionProvider() { remove(definitionProviderKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT RegistrationOptions + : public TextDocumentRegistrationOptions, public StaticRegistrationOptions + { + public: + using TextDocumentRegistrationOptions::TextDocumentRegistrationOptions; + bool isValid(QStringList *error) const override; + }; + + // The server provides Goto Type Definition support. + Utils::optional<Utils::variant<bool, RegistrationOptions>> typeDefinitionProvider() const; + void setTypeDefinitionProvider(const Utils::variant<bool, RegistrationOptions> &typeDefinitionProvider); + void clearTypeDefinitionProvider() { remove(typeDefinitionProviderKey); } + + // The server provides Goto Implementation support. + Utils::optional<Utils::variant<bool, RegistrationOptions>> implementationProvider() const; + void setImplementationProvider(const Utils::variant<bool, RegistrationOptions> &implementationProvider); + void clearImplementationProvider() { remove(implementationProviderKey); } + + // The server provides find references support. + Utils::optional<bool> referenceProvider() const { return optionalValue<bool>(referenceProviderKey); } + void setReferenceProvider(bool referenceProvider) { insert(referenceProviderKey, referenceProvider); } + void clearReferenceProvider() { remove(referenceProviderKey); } + + // The server provides document highlight support. + Utils::optional<bool> documentHighlightProvider() const + { return optionalValue<bool>(documentHighlightProviderKey); } + void setDocumentHighlightProvider(bool documentHighlightProvider) + { insert(documentHighlightProviderKey, documentHighlightProvider); } + void clearDocumentHighlightProvider() { remove(documentHighlightProviderKey); } + + // The server provides document symbol support. + Utils::optional<bool> documentSymbolProvider() const + { return optionalValue<bool>(documentSymbolProviderKey); } + void setDocumentSymbolProvider(bool documentSymbolProvider) + { insert(documentSymbolProviderKey, documentSymbolProvider); } + void clearDocumentSymbolProvider() { remove(documentSymbolProviderKey); } + + // The server provides workspace symbol support. + Utils::optional<bool> workspaceSymbolProvider() const + { return optionalValue<bool>(workspaceSymbolProviderKey); } + void setWorkspaceSymbolProvider(bool workspaceSymbolProvider) + { insert(workspaceSymbolProviderKey, workspaceSymbolProvider); } + void clearWorkspaceSymbolProvider() { remove(workspaceSymbolProviderKey); } + + // The server provides code actions. + Utils::optional<bool> codeActionProvider() const + { return optionalValue<bool>(codeActionProviderKey); } + void setCodeActionProvider(bool codeActionProvider) + { insert(codeActionProviderKey, codeActionProvider); } + void clearCodeActionProvider() { remove(codeActionProviderKey); } + + // The server provides code lens. + Utils::optional<CodeLensOptions> codeLensProvider() const + { return optionalValue<CodeLensOptions>(codeLensProviderKey); } + void setCodeLensProvider(CodeLensOptions codeLensProvider) + { insert(codeLensProviderKey, codeLensProvider); } + void clearCodeLensProvider() { remove(codeLensProviderKey); } + + // The server provides document formatting. + Utils::optional<bool> documentFormattingProvider() const + { return optionalValue<bool>(documentFormattingProviderKey); } + void setDocumentFormattingProvider(bool documentFormattingProvider) + { insert(documentFormattingProviderKey, documentFormattingProvider); } + void clearDocumentFormattingProvider() { remove(documentFormattingProviderKey); } + + // The server provides document formatting on typing. + Utils::optional<bool> documentRangeFormattingProvider() const + { return optionalValue<bool>(documentRangeFormattingProviderKey); } + void setDocumentRangeFormattingProvider(bool documentRangeFormattingProvider) + { insert(documentRangeFormattingProviderKey, documentRangeFormattingProvider); } + void clearDocumentRangeFormattingProvider() { remove(documentRangeFormattingProviderKey); } + + // The server provides rename support. + Utils::optional<bool> renameProvider() const { return optionalValue<bool>(renameProviderKey); } + void setRenameProvider(bool renameProvider) { insert(renameProviderKey, renameProvider); } + void clearRenameProvider() { remove(renameProviderKey); } + + // The server provides document link support. + Utils::optional<DocumentLinkOptions> documentLinkProvider() const + { return optionalValue<DocumentLinkOptions>(documentLinkProviderKey); } + void setDocumentLinkProvider(const DocumentLinkOptions &documentLinkProvider) + { insert(documentLinkProviderKey, documentLinkProvider); } + void clearDocumentLinkProvider() { remove(documentLinkProviderKey); } + + // The server provides color provider support. + Utils::optional<TextDocumentRegistrationOptions> colorProvider() const + { return optionalValue<TextDocumentRegistrationOptions>(colorProviderKey); } + void setColorProvider(TextDocumentRegistrationOptions colorProvider) + { insert(colorProviderKey, colorProvider); } + void clearColorProvider() { remove(colorProviderKey); } + + // The server provides execute command support. + Utils::optional<ExecuteCommandOptions> executeCommandProvider() const + { return optionalValue<ExecuteCommandOptions>(executeCommandProviderKey); } + void setExecuteCommandProvider(ExecuteCommandOptions executeCommandProvider) + { insert(executeCommandProviderKey, executeCommandProvider); } + void clearExecuteCommandProvider() { remove(executeCommandProviderKey); } + + class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceServerCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceFoldersCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + // The server has support for workspace folders + Utils::optional<bool> supported() const { return optionalValue<bool>(supportedKey); } + void setSupported(bool supported) { insert(supportedKey, supported); } + void clearSupported() { remove(supportedKey); } + + Utils::optional<Utils::variant<QString, bool>> changeNotifications() const; + void setChangeNotifications(Utils::variant<QString, bool> changeNotifications); + void clearChangeNotifications() { remove(changeNotificationsKey); } + + bool isValid(QStringList *error) const override; + }; + + Utils::optional<WorkspaceFoldersCapabilities> workspaceFolders() const + { return optionalValue<WorkspaceFoldersCapabilities>(workspaceFoldersKey); } + void setWorkspaceFolders(const WorkspaceFoldersCapabilities &workspaceFolders) + { insert(workspaceFoldersKey, workspaceFolders); } + void clearWorkspaceFolders() { remove(workspaceFoldersKey); } + }; + + Utils::optional<WorkspaceServerCapabilities> workspace() const + { return optionalValue<WorkspaceServerCapabilities>(workspaceKey); } + void setWorkspace(const WorkspaceServerCapabilities &workspace) + { insert(workspaceKey, workspace); } + void clearWorkspace() { remove(workspaceKey); } + + Utils::optional<JsonObject> experimental() const { return optionalValue<JsonObject>(experimentalKey); } + void setExperimental(const JsonObject &experimental) { insert(experimentalKey, experimental); } + void clearExperimental() { remove(experimentalKey); } + + bool isValid(QStringList *error) const override; +}; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/shutdownmessages.cpp b/src/libs/languageserverprotocol/shutdownmessages.cpp new file mode 100644 index 0000000000..720ac6a695 --- /dev/null +++ b/src/libs/languageserverprotocol/shutdownmessages.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "shutdownmessages.h" + +namespace LanguageServerProtocol { + +constexpr const char ShutdownRequest::methodName[]; +constexpr const char ExitNotification::methodName[]; +ShutdownRequest::ShutdownRequest() : Request(methodName) { } +ExitNotification::ExitNotification() : Notification(methodName) { } + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/shutdownmessages.h b/src/libs/languageserverprotocol/shutdownmessages.h new file mode 100644 index 0000000000..04efe868ab --- /dev/null +++ b/src/libs/languageserverprotocol/shutdownmessages.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "jsonrpcmessages.h" + +namespace LanguageServerProtocol { + +using ShutdownResponse = Response<LanguageClientNull, LanguageClientNull>; + +class LANGUAGESERVERPROTOCOL_EXPORT ShutdownRequest : public Request< + LanguageClientNull, LanguageClientNull, LanguageClientNull> +{ +public: + ShutdownRequest(); + using Request::Request; + constexpr static const char methodName[] = "shutdown"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ExitNotification : public Notification<LanguageClientNull> +{ +public: + ExitNotification(); + using Notification::Notification; + constexpr static const char methodName[] = "exit"; + + bool parametersAreValid(QString * /*errorMessage*/) const final { return true; } +}; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/textsynchronization.cpp b/src/libs/languageserverprotocol/textsynchronization.cpp new file mode 100644 index 0000000000..e352921fba --- /dev/null +++ b/src/libs/languageserverprotocol/textsynchronization.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "textsynchronization.h" + +namespace LanguageServerProtocol { + +constexpr const char DidOpenTextDocumentNotification::methodName[]; +constexpr const char DidChangeTextDocumentNotification::methodName[]; +constexpr const char WillSaveTextDocumentNotification::methodName[]; +constexpr const char WillSaveWaitUntilTextDocumentRequest::methodName[]; +constexpr const char DidSaveTextDocumentNotification::methodName[]; +constexpr const char DidCloseTextDocumentNotification::methodName[]; + +DidOpenTextDocumentNotification::DidOpenTextDocumentNotification( + const DidOpenTextDocumentParams ¶ms) + : Notification(methodName, params) +{ } + +DidChangeTextDocumentNotification::DidChangeTextDocumentNotification( + const DidChangeTextDocumentParams ¶ms) + : DidChangeTextDocumentNotification(methodName, params) +{ } + +WillSaveTextDocumentNotification::WillSaveTextDocumentNotification( + const WillSaveTextDocumentParams ¶ms) + : Notification(methodName, params) +{ } + +WillSaveWaitUntilTextDocumentRequest::WillSaveWaitUntilTextDocumentRequest(const WillSaveTextDocumentParams ¶ms) + : Request(methodName, params) +{ } + +DidSaveTextDocumentNotification::DidSaveTextDocumentNotification( + const DidSaveTextDocumentParams ¶ms) + : Notification(methodName, params) +{ } + +DidCloseTextDocumentNotification::DidCloseTextDocumentNotification( + const DidCloseTextDocumentParams ¶ms) + : Notification(methodName, params) +{ } + +DidChangeTextDocumentParams::DidChangeTextDocumentParams() + : DidChangeTextDocumentParams(VersionedTextDocumentIdentifier()) +{ } + +DidChangeTextDocumentParams::DidChangeTextDocumentParams( + const VersionedTextDocumentIdentifier &docId, const QString &text) +{ + setTextDocument(docId); + setContentChanges({text}); +} + +bool DidChangeTextDocumentParams::isValid(QStringList *error) const +{ + return check<VersionedTextDocumentIdentifier>(error, textDocumentKey) + && checkArray<TextDocumentContentChangeEvent>(error, contentChangesKey); +} + +DidOpenTextDocumentParams::DidOpenTextDocumentParams(const TextDocumentItem &document) +{ + setTextDocument(document); +} + +DidCloseTextDocumentParams::DidCloseTextDocumentParams(const TextDocumentIdentifier &document) +{ + setTextDocument(document); +} + +DidChangeTextDocumentParams::TextDocumentContentChangeEvent::TextDocumentContentChangeEvent( + const QString text) +{ + setText(text); +} + +bool DidChangeTextDocumentParams::TextDocumentContentChangeEvent::isValid(QStringList *error) const +{ + return checkOptional<Range>(error, rangeKey) + && checkOptional<int>(error, rangeLengthKey) + && check<QString>(error, textKey); +} + +DidSaveTextDocumentParams::DidSaveTextDocumentParams(const TextDocumentIdentifier &document) +{ + setTextDocument(document); +} + +bool DidSaveTextDocumentParams::isValid(QStringList *error) const +{ + return check<TextDocumentIdentifier>(error, textDocumentKey) + && checkOptional<QString>(error, textKey); +} + +WillSaveTextDocumentParams::WillSaveTextDocumentParams( + const TextDocumentIdentifier &document, + const WillSaveTextDocumentParams::TextDocumentSaveReason &reason) +{ + setTextDocument(document); + setReason(reason); +} + +bool WillSaveTextDocumentParams::isValid(QStringList *error) const +{ + return check<TextDocumentIdentifier>(error, textDocumentKey) + && check<int>(error, reasonKey); +} + +bool TextDocumentSaveRegistrationOptions::isValid(QStringList *error) const +{ + return TextDocumentRegistrationOptions::isValid(error) + && checkOptional<bool>(error, includeTextKey); +} + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/textsynchronization.h b/src/libs/languageserverprotocol/textsynchronization.h new file mode 100644 index 0000000000..8569bc6ef9 --- /dev/null +++ b/src/libs/languageserverprotocol/textsynchronization.h @@ -0,0 +1,245 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "jsonrpcmessages.h" +#include "servercapabilities.h" + +namespace LanguageServerProtocol { + +class LANGUAGESERVERPROTOCOL_EXPORT DidOpenTextDocumentParams : public JsonObject +{ +public: + DidOpenTextDocumentParams() = default; + DidOpenTextDocumentParams(const TextDocumentItem &document); + using JsonObject::JsonObject; + + TextDocumentItem textDocument() const { return typedValue<TextDocumentItem>(textDocumentKey); } + void setTextDocument(TextDocumentItem textDocument) { insert(textDocumentKey, textDocument); } + + bool isValid(QStringList *error) const override + { return check<TextDocumentItem>(error, textDocumentKey); } +}; + + +class LANGUAGESERVERPROTOCOL_EXPORT DidOpenTextDocumentNotification : public Notification< + DidOpenTextDocumentParams> +{ +public: + DidOpenTextDocumentNotification( + const DidOpenTextDocumentParams ¶ms = DidOpenTextDocumentParams()); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/didOpen"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentChangeRegistrationOptions : public JsonObject +{ +public: + TextDocumentChangeRegistrationOptions(); + TextDocumentChangeRegistrationOptions(TextDocumentSyncKind kind); + using JsonObject::JsonObject; + + TextDocumentSyncKind syncKind() const + { return static_cast<TextDocumentSyncKind>(typedValue<int>(syncKindKey)); } + void setSyncKind(TextDocumentSyncKind syncKind) + { insert(syncKindKey, static_cast<int>(syncKind)); } + + bool isValid(QStringList *error) const override { return check<int>(error, syncKindKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeTextDocumentParams : public JsonObject +{ +public: + DidChangeTextDocumentParams(); + DidChangeTextDocumentParams(const VersionedTextDocumentIdentifier &docId, + const QString &text = QString()); + using JsonObject::JsonObject; + + VersionedTextDocumentIdentifier textDocument() const + { return typedValue<VersionedTextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const VersionedTextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + class TextDocumentContentChangeEvent : public JsonObject + { + /* + * An event describing a change to a text document. If range and rangeLength are omitted + * the new text is considered to be the full content of the document. + */ + public: + TextDocumentContentChangeEvent() = default; + TextDocumentContentChangeEvent(const QString text); + using JsonObject::JsonObject; + + // The range of the document that changed. + Utils::optional<Range> range() const { return optionalValue<Range>(rangeKey); } + void setRange(Range range) { insert(rangeKey, range); } + void clearRange() { remove(rangeKey); } + + // The length of the range that got replaced. + Utils::optional<int> rangeLength() const { return optionalValue<int>(rangeLengthKey); } + void setRangeLength(int rangeLength) { insert(rangeLengthKey, rangeLength); } + void clearRangeLength() { remove(rangeLengthKey); } + + // The new text of the range/document. + QString text() const { return typedValue<QString>(textKey); } + void setText(const QString &text) { insert(textKey, text); } + + bool isValid(QStringList *error) const override; + }; + + QList<TextDocumentContentChangeEvent> contentChanges() const + { return array<TextDocumentContentChangeEvent>(contentChangesKey); } + void setContentChanges(const QList<TextDocumentContentChangeEvent> &contentChanges) + { insertArray(contentChangesKey, contentChanges); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeTextDocumentNotification : public Notification< + DidChangeTextDocumentParams> +{ +public: + DidChangeTextDocumentNotification(const DidChangeTextDocumentParams ¶ms = DidChangeTextDocumentParams()); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/didChange"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WillSaveTextDocumentParams : public JsonObject +{ +public: + enum class TextDocumentSaveReason { + Manual = 1, + AfterDelay = 2, + FocusOut = 3 + }; + + WillSaveTextDocumentParams() : WillSaveTextDocumentParams(TextDocumentIdentifier()) {} + WillSaveTextDocumentParams(const TextDocumentIdentifier &document, + const TextDocumentSaveReason &reason = TextDocumentSaveReason::Manual); + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + TextDocumentSaveReason reason() const + { return static_cast<TextDocumentSaveReason>(typedValue<int>(reasonKey)); } + void setReason(TextDocumentSaveReason reason) { insert(reasonKey, static_cast<int>(reason)); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WillSaveTextDocumentNotification : public Notification< + WillSaveTextDocumentParams> +{ +public: + WillSaveTextDocumentNotification( + const WillSaveTextDocumentParams ¶ms = WillSaveTextDocumentParams()); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/willSave"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WillSaveWaitUntilTextDocumentRequest : public Request< + LanguageClientArray<TextEdit>, LanguageClientNull, WillSaveTextDocumentParams> +{ +public: + WillSaveWaitUntilTextDocumentRequest( + const WillSaveTextDocumentParams ¶ms = WillSaveTextDocumentParams()); + using Request::Request; + constexpr static const char methodName[] = "textDocument/willSaveWaitUntil"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentSaveRegistrationOptions + : public TextDocumentRegistrationOptions +{ +public: + using TextDocumentRegistrationOptions::TextDocumentRegistrationOptions; + + Utils::optional<bool> includeText() const { return optionalValue<bool>(includeTextKey); } + void setIncludeText(bool includeText) { insert(includeTextKey, includeText); } + void clearIncludeText() { remove(includeTextKey); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidSaveTextDocumentParams : public JsonObject +{ +public: + DidSaveTextDocumentParams() : DidSaveTextDocumentParams(TextDocumentIdentifier()) {} + DidSaveTextDocumentParams(const TextDocumentIdentifier &document); + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() + const { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(TextDocumentIdentifier textDocument) + { insert(textDocumentKey, textDocument); } + + Utils::optional<QString> text() const { return optionalValue<QString>(textKey); } + void setText(const QString &text) { insert(textKey, text); } + void clearText() { remove(textKey); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidSaveTextDocumentNotification : public Notification< + DidSaveTextDocumentParams> +{ +public: + DidSaveTextDocumentNotification( + const DidSaveTextDocumentParams ¶ms = DidSaveTextDocumentParams()); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/didSave"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidCloseTextDocumentParams : public JsonObject +{ +public: + DidCloseTextDocumentParams() = default; + DidCloseTextDocumentParams(const TextDocumentIdentifier &document); + using JsonObject::JsonObject; + + TextDocumentIdentifier textDocument() const + { return typedValue<TextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + bool isValid(QStringList *error) const override + { return check<TextDocumentIdentifier>(error, textDocumentKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidCloseTextDocumentNotification : public Notification< + DidCloseTextDocumentParams> +{ +public: + DidCloseTextDocumentNotification( + const DidCloseTextDocumentParams ¶ms = DidCloseTextDocumentParams()); + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/didClose"; +}; + +} // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/workspace.cpp b/src/libs/languageserverprotocol/workspace.cpp new file mode 100644 index 0000000000..d4b8d3118e --- /dev/null +++ b/src/libs/languageserverprotocol/workspace.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "workspace.h" + +namespace LanguageServerProtocol { + +constexpr const char WorkSpaceFolderRequest::methodName[]; +constexpr const char DidChangeWorkspaceFoldersNotification::methodName[]; +constexpr const char DidChangeConfigurationNotification::methodName[]; +constexpr const char ConfigurationRequest::methodName[]; +constexpr const char WorkspaceSymbolRequest::methodName[]; +constexpr const char ExecuteCommandRequest::methodName[]; +constexpr const char ApplyWorkspaceEditRequest::methodName[]; +constexpr const char DidChangeWatchedFilesNotification::methodName[]; + +WorkSpaceFolderRequest::WorkSpaceFolderRequest() + : Request(methodName) +{ } + +DidChangeWorkspaceFoldersNotification::DidChangeWorkspaceFoldersNotification( + const DidChangeWorkspaceFoldersParams ¶ms) + : Notification(methodName, params) +{ } + +DidChangeConfigurationNotification::DidChangeConfigurationNotification( + const DidChangeConfigurationParams ¶ms) + : Notification(methodName, params) +{ } + +ConfigurationRequest::ConfigurationRequest(const ConfigurationParams ¶ms) + : Request(methodName, params) +{ } + +WorkspaceSymbolRequest::WorkspaceSymbolRequest(const WorkspaceSymbolParams ¶ms) + : Request(methodName, params) +{ } + +ExecuteCommandRequest::ExecuteCommandRequest(const ExecuteCommandParams ¶ms) + : Request(methodName, params) +{ } + +ApplyWorkspaceEditRequest::ApplyWorkspaceEditRequest(const ApplyWorkspaceEditParams ¶ms) + : Request(methodName, params) +{ } + +bool WorkspaceFoldersChangeEvent::isValid(QStringList *error) const +{ + return checkArray<WorkSpaceFolder>(error, addedKey) + && checkArray<WorkSpaceFolder>(error, removedKey); +} + +bool ConfigurationParams::ConfigureationItem::isValid(QStringList *error) const +{ + return checkOptional<QString>(error, scopeUriKey) + && checkOptional<QString>(error, sectionKey); +} + +bool DidChangeConfigurationParams::isValid(QStringList *error) const +{ + if (contains(settingsKey)) + return true; + *error << settingsKey; + return false; +} + +DidChangeWatchedFilesNotification::DidChangeWatchedFilesNotification( + const DidChangeWatchedFilesParams ¶ms) + : Notification(methodName, params) +{ } + +} // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/workspace.h b/src/libs/languageserverprotocol/workspace.h new file mode 100644 index 0000000000..8674165c07 --- /dev/null +++ b/src/libs/languageserverprotocol/workspace.h @@ -0,0 +1,265 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "jsonrpcmessages.h" + +namespace LanguageServerProtocol { + +class LANGUAGESERVERPROTOCOL_EXPORT WorkSpaceFolderRequest : public Request< + Utils::variant<QList<WorkSpaceFolder>, Utils::nullopt_t>, LanguageClientNull, LanguageClientNull> +{ +public: + WorkSpaceFolderRequest(); + using Request::Request; + constexpr static const char methodName[] = "workspace/workspaceFolders"; + + bool parametersAreValid(QString * /*errorMessage*/) const override { return true; } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceFoldersChangeEvent : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QList<WorkSpaceFolder> added() const { return array<WorkSpaceFolder>(addedKey); } + void setAdded(const QList<WorkSpaceFolder> &added) { insertArray(addedKey, added); } + + QList<WorkSpaceFolder> removed() const { return array<WorkSpaceFolder>(removedKey); } + void setRemoved(const QList<WorkSpaceFolder> &removed) { insertArray(removedKey, removed); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWorkspaceFoldersParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + WorkspaceFoldersChangeEvent event() const + { return typedValue<WorkspaceFoldersChangeEvent>(eventKey); } + void setEvent(const WorkspaceFoldersChangeEvent &event) { insert(eventKey, event); } + + bool isValid(QStringList *error) const override + { return check<WorkspaceFoldersChangeEvent>(error, eventKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWorkspaceFoldersNotification : public Notification< + DidChangeWorkspaceFoldersParams> +{ +public: + DidChangeWorkspaceFoldersNotification( + const DidChangeWorkspaceFoldersParams ¶ms = DidChangeWorkspaceFoldersParams()); + constexpr static const char methodName[] = "workspace/didChangeWorkspaceFolders"; + using Notification::Notification; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeConfigurationParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QJsonValue settings() const { return typedValue<QJsonValue>(settingsKey); } + void setSettings(QJsonValue settings) { insert(settingsKey, settings); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeConfigurationNotification : public Notification< + DidChangeConfigurationParams> +{ +public: + DidChangeConfigurationNotification( + const DidChangeConfigurationParams ¶ms = DidChangeConfigurationParams()); + using Notification::Notification; + constexpr static const char methodName[] = "workspace/didChangeConfiguration"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ConfigurationParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + class ConfigureationItem : public JsonObject + { + public: + using JsonObject::JsonObject; + + Utils::optional<QString> scopeUri() const { return optionalValue<QString>(scopeUriKey); } + void setScopeUri(const QString &scopeUri) { insert(scopeUriKey, scopeUri); } + void clearScopeUri() { remove(scopeUriKey); } + + Utils::optional<QString> section() const { return optionalValue<QString>(sectionKey); } + void setSection(const QString §ion) { insert(sectionKey, section); } + void clearSection() { remove(sectionKey); } + + bool isValid(QStringList *error) const override; + }; + + QList<ConfigureationItem> items() const { return array<ConfigureationItem>(itemsKey); } + void setItems(const QList<ConfigureationItem> &items) { insertArray(itemsKey, items); } + + bool isValid(QStringList *error) const override + { return checkArray<ConfigureationItem>(error, itemsKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ConfigurationRequest : public Request< + LanguageClientArray<QJsonValue>, LanguageClientNull, ConfigurationParams> +{ +public: + ConfigurationRequest(const ConfigurationParams ¶ms = ConfigurationParams()); + using Request::Request; + constexpr static const char methodName[] = "workspace/configuration"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWatchedFilesParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + class FileEvent : public JsonObject + { + public: + using JsonObject::JsonObject; + + DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); } + void setUri(const DocumentUri &uri) { insert(uriKey, uri); } + + int type() const { return typedValue<int>(typeKey); } + void setType(int type) { insert(typeKey, type); } + + enum FileChangeType { + Created = 1, + Changed = 2, + Deleted = 3 + }; + + bool isValid(QStringList *error) const override + { return check<QString>(error, uriKey) && check<int>(error, typeKey); } + }; + + QList<FileEvent> changes() const { return array<FileEvent>(changesKey); } + void setChanges(const QList<FileEvent> &changes) { insertArray(changesKey, changes); } + + bool isValid(QStringList *error) const override + { return checkArray<FileEvent>(error, changesKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWatchedFilesNotification : public Notification< + DidChangeWatchedFilesParams> +{ +public: + DidChangeWatchedFilesNotification(const DidChangeWatchedFilesParams ¶ms = DidChangeWatchedFilesParams()); + using Notification::Notification; + constexpr static const char methodName[] = "workspace/didChangeWatchedFiles"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceSymbolParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString query() const { return typedValue<QString>(queryKey); } + void setQuery(const QString &query) { insert(queryKey, query); } + + bool isValid(QStringList *error) const override { return check<QString>(error, queryKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceSymbolRequest : public Request< + LanguageClientArray<SymbolInformation>, LanguageClientNull, WorkspaceSymbolParams> +{ +public: + WorkspaceSymbolRequest(const WorkspaceSymbolParams ¶ms = WorkspaceSymbolParams()); + using Request::Request; + constexpr static const char methodName[] = "workspace/symbol"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ExecuteCommandParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + QString command() const { return typedValue<QString>(commandKey); } + void setCommand(const QString &command) { insert(commandKey, command); } + + Utils::optional<QList<QJsonValue>> arguments() const + { return optionalArray<QJsonValue>(argumentsKey); } + void setArguments(const QList<QJsonValue> &arguments) + { insertArray(argumentsKey, arguments); } + void clearArguments() { remove(argumentsKey); } + + bool isValid(QStringList *error) const override + { + return check<QString>(error, commandKey) + && checkOptionalArray<QJsonValue>(error, argumentsKey); + } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ExecuteCommandRequest : public Request< + QJsonValue, LanguageClientNull, ExecuteCommandParams> +{ +public: + ExecuteCommandRequest(const ExecuteCommandParams ¶ms = ExecuteCommandParams()); + using Request::Request; + constexpr static const char methodName[] = "workspace/executeCommand"; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ApplyWorkspaceEditParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + Utils::optional<QString> label() const { return optionalValue<QString>(labelKey); } + void setLabel(const QString &label) { insert(labelKey, label); } + void clearLabel() { remove(labelKey); } + + WorkspaceEdit edit() const { return typedValue<WorkspaceEdit>(editKey); } + void setEdit(const WorkspaceEdit &edit) { insert(editKey, edit); } + + bool isValid(QStringList *error) const override + { return check<WorkspaceEdit>(error, editKey) && checkOptional<QString>(error, labelKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ApplyWorkspaceEditResponse : public JsonObject +{ +public: + using JsonObject::JsonObject; + + bool applied() const { return typedValue<bool>(appliedKey); } + void setApplied(bool applied) { insert(appliedKey, applied); } + + bool isValid(QStringList *error) const override { return check<bool>(error, appliedKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT ApplyWorkspaceEditRequest : public Request< + ApplyWorkspaceEditResponse, LanguageClientNull, ApplyWorkspaceEditParams> +{ +public: + ApplyWorkspaceEditRequest(const ApplyWorkspaceEditParams ¶ms = ApplyWorkspaceEditParams()); + using Request::Request; + constexpr static const char methodName[] = "workspace/applyEdit"; +}; + +} // namespace LanguageClient diff --git a/src/libs/libs.pro b/src/libs/libs.pro index f032a0e4b0..fecb62925a 100644 --- a/src/libs/libs.pro +++ b/src/libs/libs.pro @@ -17,7 +17,8 @@ SUBDIRS += \ glsl \ ssh \ sqlite \ - clangsupport + clangsupport \ + languageserverprotocol qtHaveModule(quick) { SUBDIRS += \ diff --git a/src/libs/libs.qbs b/src/libs/libs.qbs index 715cb7d456..e3bfe71003 100644 --- a/src/libs/libs.qbs +++ b/src/libs/libs.qbs @@ -9,6 +9,7 @@ Project { "cplusplus/cplusplus.qbs", "extensionsystem/extensionsystem.qbs", "glsl/glsl.qbs", + "languageserverprotocol/languageserverprotocol.qbs", "languageutils/languageutils.qbs", "modelinglib/modelinglib.qbs", "qmleditorwidgets/qmleditorwidgets.qbs", diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index b9fbeae907..840325dd97 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -57,7 +57,7 @@ OptionalLineColumn convertPosition(const QTextDocument *document, int pos) return optional; } -int positionInText(QTextDocument *textDocument, int line, int column) +int positionInText(const QTextDocument *textDocument, int line, int column) { // Deduct 1 from line and column since they are 1-based. // Column should already be converted from UTF-8 byte offset to the TextEditor column. diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index c03e7ce2ce..d5414e3194 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -45,7 +45,7 @@ QTCREATOR_UTILS_EXPORT OptionalLineColumn convertPosition(const QTextDocument *document, int pos); // line and column are 1-based -QTCREATOR_UTILS_EXPORT int positionInText(QTextDocument *textDocument, int line, int column); +QTCREATOR_UTILS_EXPORT int positionInText(const QTextDocument *textDocument, int line, int column); QTCREATOR_UTILS_EXPORT QString textAt(QTextCursor tc, int pos, int length); diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp index f33143bc40..5c8f39747d 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.cpp +++ b/src/plugins/coreplugin/editormanager/editormanager.cpp @@ -2166,8 +2166,10 @@ bool EditorManagerPrivate::saveDocument(IDocument *document) success = DocumentManager::saveDocument(document); } - if (success) + if (success) { addDocumentToRecentFiles(document); + emit m_instance->saved(document); + } return success; } @@ -2200,8 +2202,10 @@ bool EditorManagerPrivate::saveDocumentAs(IDocument *document) // a good way out either (also the undo stack would be lost). Perhaps the best is to // re-think part of the editors design. - if (success) + if (success) { addDocumentToRecentFiles(document); + emit m_instance->saved(document); + } updateActions(); return success; diff --git a/src/plugins/coreplugin/editormanager/editormanager.h b/src/plugins/coreplugin/editormanager/editormanager.h index 30023e2bc7..6cd9dd88a1 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.h +++ b/src/plugins/coreplugin/editormanager/editormanager.h @@ -179,6 +179,7 @@ signals: void findOnFileSystemRequest(const QString &path); void openFileProperties(const Utils::FileName &path); void aboutToSave(IDocument *document); + void saved(IDocument *document); void autoSaved(); void currentEditorAboutToChange(Core::IEditor *editor); diff --git a/src/plugins/languageclient/LanguageClient.json.in b/src/plugins/languageclient/LanguageClient.json.in new file mode 100644 index 0000000000..c48a74c937 --- /dev/null +++ b/src/plugins/languageclient/LanguageClient.json.in @@ -0,0 +1,20 @@ +{ + \"Name\" : \"LanguageClient\", + \"Version\" : \"$$QTCREATOR_VERSION\", + \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", + \"Experimental\" : true, + \"Vendor\" : \"The Qt Company Ltd\", + \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\", + \"License\" : [ \"Commercial Usage\", + \"\", + \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\", + \"\", + \"GNU General Public License Usage\", + \"\", + \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\" + ], + \"Category\" : \"Other Languages\", + \"Description\" : \"Language Server Protocol client component. See https://microsoft.github.io/language-server-protocol/overview for an overview on Language Servers.\", + \"Url\" : \"http://www.qt.io\", + $$dependencyList +} diff --git a/src/plugins/languageclient/baseclient.cpp b/src/plugins/languageclient/baseclient.cpp new file mode 100644 index 0000000000..3bac57b2b2 --- /dev/null +++ b/src/plugins/languageclient/baseclient.cpp @@ -0,0 +1,746 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "baseclient.h" +#include "languageclientcodeassist.h" +#include "languageclientmanager.h" + +#include <coreplugin/icore.h> +#include <coreplugin/idocument.h> +#include <coreplugin/messagemanager.h> +#include <languageserverprotocol/diagnostics.h> +#include <languageserverprotocol/languagefeatures.h> +#include <languageserverprotocol/messages.h> +#include <languageserverprotocol/workspace.h> +#include <texteditor/semantichighlighter.h> +#include <texteditor/textdocument.h> +#include <texteditor/texteditor.h> +#include <projectexplorer/project.h> +#include <projectexplorer/session.h> +#include <utils/mimetypes/mimedatabase.h> +#include <utils/synchronousprocess.h> + +#include <QDebug> +#include <QLoggingCategory> +#include <QMessageBox> +#include <QPointer> +#include <QTextBlock> +#include <QTextCursor> +#include <QTextDocument> + +using namespace LanguageServerProtocol; +using namespace Utils; + +namespace LanguageClient { + +static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client"); +static Q_LOGGING_CATEGORY(LOGLSPCLIENTV, "qtc.languageclient.messages"); + +BaseClient::BaseClient() + : m_id(Core::Id::fromString(QUuid::createUuid().toString())) +{ + m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); + m_contentHandler.insert(JsonRpcMessageHandler::jsonRpcMimeType(), + &JsonRpcMessageHandler::parseContent); +} + +BaseClient::~BaseClient() +{ + m_buffer.close(); +} + +void BaseClient::initialize() +{ + using namespace ProjectExplorer; + QTC_ASSERT(m_state == Uninitialized, return); + qCDebug(LOGLSPCLIENT) << "initializing language server " << m_displayName; + auto initRequest = new InitializeRequest(); + if (auto startupProject = SessionManager::startupProject()) { + auto params = initRequest->params().value_or(InitializeParams()); + params.setRootUri(DocumentUri::fromFileName(startupProject->projectDirectory())); + initRequest->setParams(params); + params.setWorkSpaceFolders(Utils::transform(SessionManager::projects(), [](Project *pro){ + return WorkSpaceFolder(pro->projectDirectory().toString(), pro->displayName()); + })); + } + initRequest->setResponseCallback([this](const InitializeResponse &initResponse){ + intializeCallback(initResponse); + }); + // directly send data otherwise the state check would fail; + initRequest->registerResponseHandler(&m_responseHandlers); + sendData(initRequest->toBaseMessage().toData()); + m_state = InitializeRequested; +} + +void BaseClient::shutdown() +{ + QTC_ASSERT(m_state == Initialized, emit finished(); return); + qCDebug(LOGLSPCLIENT) << "shutdown language server " << m_displayName; + ShutdownRequest shutdown; + shutdown.setResponseCallback([this](const ShutdownResponse &shutdownResponse){ + shutDownCallback(shutdownResponse); + }); + sendContent(shutdown); + m_state = ShutdownRequested; +} + +BaseClient::State BaseClient::state() const +{ + return m_state; +} + +void BaseClient::openDocument(Core::IDocument *document) +{ + using namespace TextEditor; + const QString languageId = TextDocumentItem::mimeTypeToLanguageId(document->mimeType()); + if (!isSupportedLanguage(languageId)) + return; + const FileName &filePath = document->filePath(); + const QString method(DidOpenTextDocumentNotification::methodName); + if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) { + if (!registered.value()) + return; + const TextDocumentRegistrationOptions option( + m_dynamicCapabilities.option(method).toObject()); + if (option.isValid(nullptr) + && !option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType()))) { + return; + } + } else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync + = m_serverCapabilities.textDocumentSync()) { + if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) { + if (!options->openClose().value_or(true)) + return; + } + } + auto textDocument = qobject_cast<TextDocument *>(document); + TextDocumentItem item; + item.setLanguageId(languageId); + item.setUri(DocumentUri::fromFileName(filePath)); + item.setText(QString::fromUtf8(document->contents())); + item.setVersion(textDocument ? textDocument->document()->revision() : 0); + + connect(document, &Core::IDocument::contentsChanged, this, + [this, document](){ + documentContentsChanged(document); + }); + if (textDocument) { + textDocument->setCompletionAssistProvider(new LanguageClientCompletionAssistProvider(this)); + if (BaseTextEditor *editor = BaseTextEditor::textEditorForDocument(textDocument)) { + if (TextEditorWidget *widget = editor->editorWidget()) { + connect(widget, &TextEditorWidget::cursorPositionChanged, this, [this, widget](){ + cursorPositionChanged(widget); + }); + } + } + } + + m_openedDocument.append(document->filePath()); + sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item))); + if (textDocument) + requestDocumentSymbols(textDocument); +} + +void BaseClient::sendContent(const IContent &content) +{ + QTC_ASSERT(m_state == Initialized, return); + content.registerResponseHandler(&m_responseHandlers); + QString error; + if (!QTC_GUARD(content.isValid(&error))) + Core::MessageManager::write(error); + sendData(content.toBaseMessage().toData()); +} + +void BaseClient::sendContent(const DocumentUri &uri, const IContent &content) +{ + if (!m_openedDocument.contains(uri.toFileName())) + return; + sendContent(content); +} + +void BaseClient::cancelRequest(const MessageId &id) +{ + m_responseHandlers.remove(id); + sendContent(CancelRequest(CancelParameter(id))); +} + +void BaseClient::closeDocument(const DidCloseTextDocumentParams ¶ms) +{ + sendContent(params.textDocument().uri(), DidCloseTextDocumentNotification(params)); +} + +void BaseClient::documentContentsSaved(Core::IDocument *document) +{ + if (!m_openedDocument.contains(document->filePath())) + return; + bool sendMessage = true; + bool includeText = false; + const QString method(DidSaveTextDocumentNotification::methodName); + if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) { + sendMessage = registered.value(); + if (sendMessage) { + const TextDocumentSaveRegistrationOptions option( + m_dynamicCapabilities.option(method).toObject()); + if (option.isValid(nullptr)) { + sendMessage = option.filterApplies(document->filePath(), + Utils::mimeTypeForName(document->mimeType())); + includeText = option.includeText().value_or(includeText); + } + } + } else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync + = m_serverCapabilities.textDocumentSync()) { + if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) { + if (Utils::optional<SaveOptions> saveOptions = options->save()) + includeText = saveOptions.value().includeText().value_or(includeText); + } + } + if (!sendMessage) + return; + DidSaveTextDocumentParams params( + TextDocumentIdentifier(DocumentUri::fromFileName(document->filePath()))); + if (includeText) + params.setText(QString::fromUtf8(document->contents())); + sendContent(DidSaveTextDocumentNotification(params)); +} + +void BaseClient::documentWillSave(Core::IDocument *document) +{ + const FileName &filePath = document->filePath(); + if (!m_openedDocument.contains(filePath)) + return; + bool sendMessage = true; + const QString method(WillSaveTextDocumentNotification::methodName); + if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) { + sendMessage = registered.value(); + if (sendMessage) { + const TextDocumentRegistrationOptions option(m_dynamicCapabilities.option(method)); + if (option.isValid(nullptr)) { + sendMessage = option.filterApplies(filePath, + Utils::mimeTypeForName(document->mimeType())); + } + } + } else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync + = m_serverCapabilities.textDocumentSync()) { + if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) + sendMessage = options->willSave().value_or(sendMessage); + } + if (!sendMessage) + return; + const WillSaveTextDocumentParams params( + TextDocumentIdentifier(DocumentUri::fromFileName(document->filePath()))); + sendContent(WillSaveTextDocumentNotification(params)); +} + +void BaseClient::documentContentsChanged(Core::IDocument *document) +{ + if (!m_openedDocument.contains(document->filePath())) + return; + const QString method(DidChangeTextDocumentNotification::methodName); + TextDocumentSyncKind syncKind = m_serverCapabilities.textDocumentSyncKindHelper(); + if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) { + syncKind = registered.value() ? TextDocumentSyncKind::None : TextDocumentSyncKind::Full; + if (syncKind != TextDocumentSyncKind::None) { + const TextDocumentChangeRegistrationOptions option( + m_dynamicCapabilities.option(method).toObject()); + syncKind = option.isValid(nullptr) ? option.syncKind() : syncKind; + } + } + auto textDocument = qobject_cast<TextEditor::TextDocument *>(document); + if (syncKind != TextDocumentSyncKind::None) { + const auto uri = DocumentUri::fromFileName(document->filePath()); + VersionedTextDocumentIdentifier docId(uri); + docId.setVersion(textDocument ? textDocument->document()->revision() : 0); + const DidChangeTextDocumentParams params(docId, QString::fromUtf8(document->contents())); + sendContent(DidChangeTextDocumentNotification(params)); + } + if (textDocument) + requestDocumentSymbols(textDocument); +} + +void BaseClient::registerCapabilities(const QList<Registration> ®istrations) +{ + m_dynamicCapabilities.registerCapability(registrations); +} + +void BaseClient::unregisterCapabilities(const QList<Unregistration> &unregistrations) +{ + m_dynamicCapabilities.unregisterCapability(unregistrations); +} + +bool BaseClient::findLinkAt(GotoDefinitionRequest &request) +{ + bool sendMessage = m_dynamicCapabilities.isRegistered( + GotoDefinitionRequest::methodName).value_or(false); + if (sendMessage) { + const TextDocumentRegistrationOptions option( + m_dynamicCapabilities.option(GotoDefinitionRequest::methodName)); + if (option.isValid(nullptr)) + sendMessage = option.filterApplies(Utils::FileName::fromString(QUrl(request.params()->textDocument().uri()).adjusted(QUrl::PreferLocalFile).toString())); + } else { + sendMessage = m_serverCapabilities.definitionProvider().value_or(sendMessage); + } + if (sendMessage) + sendContent(request); + return sendMessage; +} + +TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation &info) +{ + if (!info.isValid(nullptr)) + return {}; + const Position &start = info.location().range().start(); + return TextEditor::HighlightingResult(start.line() + 1, start.character() + 1, + info.name().length(), info.kind()); +} + +void BaseClient::requestDocumentSymbols(TextEditor::TextDocument *document) +{ + // TODO: Do not use this information for highlighting but the overview model + return; + const FileName &filePath = document->filePath(); + bool sendMessage = m_dynamicCapabilities.isRegistered(DocumentSymbolsRequest::methodName).value_or(false); + if (sendMessage) { + const TextDocumentRegistrationOptions option(m_dynamicCapabilities.option(DocumentSymbolsRequest::methodName)); + if (option.isValid(nullptr)) + sendMessage = option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType())); + } else { + sendMessage = m_serverCapabilities.documentSymbolProvider().value_or(false); + } + if (!sendMessage) + return; + DocumentSymbolsRequest request( + DocumentSymbolParams(TextDocumentIdentifier(DocumentUri::fromFileName(filePath)))); + request.setResponseCallback( + [doc = QPointer<TextEditor::TextDocument>(document)] + (Response<DocumentSymbolsResult, LanguageClientNull> response){ + if (!doc) + return; + const DocumentSymbolsResult result = response.result().value_or(DocumentSymbolsResult()); + if (!holds_alternative<QList<SymbolInformation>>(result)) + return; + const auto &symbols = get<QList<SymbolInformation>>(result); + + QFutureInterface<TextEditor::HighlightingResult> future; + for (const SymbolInformation &symbol : symbols) + future.reportResult(createHighlightingResult(symbol)); + + const TextEditor::FontSettings &fs = doc->fontSettings(); + QHash<int, QTextCharFormat> formatMap; + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::File )] + = fs.toTextCharFormat(TextEditor::C_STRING); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Module )] + = fs.toTextCharFormat(TextEditor::C_STRING); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Namespace )] + = fs.toTextCharFormat(TextEditor::C_STRING); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Package )] + = fs.toTextCharFormat(TextEditor::C_STRING); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Class )] + = fs.toTextCharFormat(TextEditor::C_TYPE); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Method )] + = fs.toTextCharFormat(TextEditor::C_FUNCTION); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Property )] + = fs.toTextCharFormat(TextEditor::C_FIELD); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Field )] + = fs.toTextCharFormat(TextEditor::C_FIELD); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Constructor )] + = fs.toTextCharFormat(TextEditor::C_FUNCTION); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Enum )] + = fs.toTextCharFormat(TextEditor::C_TYPE); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Interface )] + = fs.toTextCharFormat(TextEditor::C_TYPE); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Function )] + = fs.toTextCharFormat(TextEditor::C_FUNCTION); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Variable )] + = fs.toTextCharFormat(TextEditor::C_LOCAL); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Constant )] + = fs.toTextCharFormat(TextEditor::C_LOCAL); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::String )] + = fs.toTextCharFormat(TextEditor::C_STRING); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Number )] + = fs.toTextCharFormat(TextEditor::C_NUMBER); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Boolean )] + = fs.toTextCharFormat(TextEditor::C_KEYWORD); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Array )] + = fs.toTextCharFormat(TextEditor::C_STRING); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Object )] + = fs.toTextCharFormat(TextEditor::C_LOCAL); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Key )] + = fs.toTextCharFormat(TextEditor::C_LOCAL); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Null )] + = fs.toTextCharFormat(TextEditor::C_KEYWORD); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::EnumMember )] + = fs.toTextCharFormat(TextEditor::C_ENUMERATION); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Struct )] + = fs.toTextCharFormat(TextEditor::C_TYPE); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Event )] + = fs.toTextCharFormat(TextEditor::C_STRING); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::Operator )] + = fs.toTextCharFormat(TextEditor::C_OPERATOR); + formatMap[static_cast<int>(LanguageServerProtocol::SymbolKind::TypeParameter)] + = fs.toTextCharFormat(TextEditor::C_LOCAL); + + TextEditor::SemanticHighlighter::incrementalApplyExtraAdditionalFormats( + doc->syntaxHighlighter(), future.future(), 0, future.resultCount() - 1, + formatMap); + }); + sendContent(request); +} + +void BaseClient::cursorPositionChanged(TextEditor::TextEditorWidget *widget) +{ + const auto uri = DocumentUri::fromFileName(widget->textDocument()->filePath()); + if (m_dynamicCapabilities.isRegistered(DocumentHighlightsRequest::methodName).value_or(false)) { + TextDocumentRegistrationOptions option( + m_dynamicCapabilities.option(DocumentHighlightsRequest::methodName)); + if (!option.filterApplies(widget->textDocument()->filePath())) + return; + } else if (!m_serverCapabilities.documentHighlightProvider().value_or(false)) { + return; + } + + auto runningRequest = m_highlightRequests.find(uri); + if (runningRequest != m_highlightRequests.end()) + cancelRequest(runningRequest.value()); + + DocumentHighlightsRequest request(TextDocumentPositionParams(uri, widget->textCursor())); + request.setResponseCallback( + [widget = QPointer<TextEditor::TextEditorWidget>(widget), this, uri] + (Response<DocumentHighlightsResult, LanguageClientNull> response) + { + m_highlightRequests.remove(uri); + if (!widget) + return; + + QList<QTextEdit::ExtraSelection> selections; + const DocumentHighlightsResult result = response.result().value_or(DocumentHighlightsResult()); + if (!holds_alternative<QList<DocumentHighlight>>(result)) { + widget->setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, selections); + return; + } + + const QTextCharFormat &format = + widget->textDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES); + QTextDocument *document = widget->document(); + for (const auto &highlight : get<QList<DocumentHighlight>>(result)) { + QTextEdit::ExtraSelection selection{widget->textCursor(), format}; + const int &start = highlight.range().start().toPositionInDocument(document); + const int &end = highlight.range().end().toPositionInDocument(document); + if (start < 0 || end < 0) + continue; + selection.cursor.setPosition(start); + selection.cursor.setPosition(end, QTextCursor::KeepAnchor); + selections << selection; + } + widget->setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, selections); + }); + m_highlightRequests[uri] = request.id(); + sendContent(request); +} + +void BaseClient::projectOpened(ProjectExplorer::Project *project) +{ + if (!sendWorkspceFolderChanges()) + return; + WorkspaceFoldersChangeEvent event; + event.setAdded({WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())}); + DidChangeWorkspaceFoldersParams params; + params.setEvent(event); + DidChangeWorkspaceFoldersNotification change(params); + sendContent(change); +} + +void BaseClient::projectClosed(ProjectExplorer::Project *project) +{ + if (!sendWorkspceFolderChanges()) + return; + WorkspaceFoldersChangeEvent event; + event.setRemoved({WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())}); + DidChangeWorkspaceFoldersParams params; + params.setEvent(event); + DidChangeWorkspaceFoldersNotification change(params); + sendContent(change); +} + +void BaseClient::setSupportedLanguages(const QStringList &supportedLanguages) +{ + m_supportedLanguageIds = supportedLanguages; +} + +bool BaseClient::isSupportedLanguage(const QString &language) const +{ + return m_supportedLanguageIds.isEmpty() || m_supportedLanguageIds.contains(language); +} + +void BaseClient::reset() +{ + m_state = Uninitialized; + m_responseHandlers.clear(); + m_buffer.close(); + m_buffer.setData(nullptr); + m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); + m_openedDocument.clear(); + m_serverCapabilities = ServerCapabilities(); + m_dynamicCapabilities.reset(); +} + +void BaseClient::setError(const QString &message) +{ + log(message); + m_state = Error; +} + +void BaseClient::log(const QString &message, Core::MessageManager::PrintToOutputPaneFlag flag) +{ + Core::MessageManager::write(QString("LanguageClient %1: %2").arg(name(), message), flag); +} + +void BaseClient::log(LogMessageParams &message, Core::MessageManager::PrintToOutputPaneFlag flag) +{ + log(message.toString(), flag); +} + +void BaseClient::handleResponse(const MessageId &id, const QByteArray &content, QTextCodec *codec) +{ + if (auto handler = m_responseHandlers[id]) + handler(content, codec); +} + +void BaseClient::handleMethod(const QString &method, MessageId id, const IContent *content) +{ + QStringList error; + bool paramsValid = true; + if (method == PublishDiagnosticsNotification::methodName) { + auto params = dynamic_cast<const PublishDiagnosticsNotification *>(content)->params().value_or(PublishDiagnosticsParams()); + paramsValid = params.isValid(&error); + if (paramsValid) + LanguageClientManager::publishDiagnostics(m_id, params); + } else if (method == LogMessageNotification::methodName) { + auto params = dynamic_cast<const LogMessageNotification *>(content)->params().value_or(LogMessageParams()); + paramsValid = params.isValid(&error); + if (paramsValid) + log(params); + } else if (method == RegisterCapabilityRequest::methodName) { + auto params = dynamic_cast<const RegisterCapabilityRequest *>(content)->params().value_or(RegistrationParams()); + paramsValid = params.isValid(&error); + if (paramsValid) + m_dynamicCapabilities.registerCapability(params.registrations()); + } else if (method == UnregisterCapabilityRequest::methodName) { + auto params = dynamic_cast<const UnregisterCapabilityRequest *>(content)->params().value_or(UnregistrationParams()); + paramsValid = params.isValid(&error); + if (paramsValid) + m_dynamicCapabilities.unregisterCapability(params.unregistrations()); + } else if (id.isValid(&error)) { + Response<JsonObject, JsonObject> response; + response.setId(id); + ResponseError<JsonObject> error; + error.setCode(ResponseError<JsonObject>::MethodNotFound); + response.setError(error); + sendContent(response); + } + std::reverse(error.begin(), error.end()); + if (!paramsValid) { + log(tr("Invalid parameter in \"%1\": %2").arg(method, error.join("->")), + Core::MessageManager::Flash); + } + delete content; +} + +void BaseClient::intializeCallback(const InitializeResponse &initResponse) +{ + QTC_ASSERT(m_state == InitializeRequested, return); + if (optional<ResponseError<InitializeError>> error = initResponse.error()) { + if (error.value().data().has_value() + && error.value().data().value().retry().value_or(false)) { + const QString title(tr("Language Server \"%1\" initialize error")); + auto result = QMessageBox::warning(Core::ICore::dialogParent(), + title, + error.value().message(), + QMessageBox::Retry | QMessageBox::Cancel, + QMessageBox::Retry); + if (result == QMessageBox::Retry) { + m_state = Uninitialized; + initialize(); + return; + } + } + setError(tr("Initialize error: ") + error.value().message()); + emit finished(); + return; + } + const optional<InitializeResult> &_result = initResponse.result(); + if (!_result.has_value()) {// continue on ill formed result + log(tr("No initialize result.")); + } else { + const InitializeResult &result = _result.value(); + QStringList error; + if (!result.isValid(&error)) // continue on ill formed result + log(tr("Initialize result is not valid: ") + error.join("->")); + + m_serverCapabilities = result.capabilities().value_or(ServerCapabilities()); + } + qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " initialized"; + m_state = Initialized; + sendContent(InitializeNotification()); + emit initialized(m_serverCapabilities); + for (auto openedDocument : Core::DocumentModel::openedDocuments()) + openDocument(openedDocument); +} + +void BaseClient::shutDownCallback(const ShutdownResponse &shutdownResponse) +{ + QTC_ASSERT(m_state == ShutdownRequested, return); + optional<ResponseError<JsonObject>> errorValue = shutdownResponse.error(); + if (errorValue.has_value()) { + ResponseError<JsonObject> error = errorValue.value(); + qDebug() << error; + return; + } + // directly send data otherwise the state check would fail; + sendData(ExitNotification().toBaseMessage().toData()); + qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " shutdown"; + m_state = Shutdown; +} + +bool BaseClient::sendWorkspceFolderChanges() const +{ + if (m_dynamicCapabilities.isRegistered( + DidChangeWorkspaceFoldersNotification::methodName).value_or(false)) { + return true; + } + if (auto workspace = m_serverCapabilities.workspace()) { + if (auto folder = workspace.value().workspaceFolders()) { + if (folder.value().supported().value_or(false)) { + // holds either the Id for deregistration or whether it is registered + auto notification = folder.value().changeNotifications().value_or(false); + return holds_alternative<QString>(notification) + || (holds_alternative<bool>(notification) && get<bool>(notification)); + } + } + } + return false; +} + +void BaseClient::parseData(const QByteArray &data) +{ + const qint64 preWritePosition = m_buffer.pos(); + m_buffer.write(data); + m_buffer.seek(preWritePosition); + while (!m_buffer.atEnd()) { + QString parseError; + BaseMessage::parse(&m_buffer, parseError, m_currentMessage); + if (!parseError.isEmpty()) + log(parseError); + if (!m_currentMessage.isComplete()) + break; + if (auto handler = m_contentHandler[m_currentMessage.mimeType]){ + QString parseError; + handler(m_currentMessage.content, m_currentMessage.codec, parseError, + [this](MessageId id, const QByteArray &content, QTextCodec *codec){ + this->handleResponse(id, content, codec); + }, + [this](const QString &method, MessageId id, const IContent *content){ + this->handleMethod(method, id, content); + }); + if (!parseError.isEmpty()) + log(parseError); + } else { + log(tr("Cannot handle content of type: %1").arg(QLatin1String(m_currentMessage.mimeType))); + } + m_currentMessage = BaseMessage(); + } +} + +StdIOClient::StdIOClient(const QString &command, const QStringList &args) +{ + connect(&m_process, &QProcess::readyReadStandardError, + this, &StdIOClient::readError); + connect(&m_process, &QProcess::readyReadStandardOutput, + this, &StdIOClient::readOutput); + connect(&m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), + this, &StdIOClient::onProcessFinished); + + m_process.setArguments(args); + m_process.setProgram(command); +} + +StdIOClient::~StdIOClient() +{ + Utils::SynchronousProcess::stopProcess(m_process); +} + +bool StdIOClient::start() +{ + m_process.start(); + if (!m_process.waitForStarted() && m_process.state() != QProcess::Running) { + setError(m_process.errorString()); + return false; + } + return true; +} + +void StdIOClient::setWorkingDirectory(const QString &workingDirectory) +{ + m_process.setWorkingDirectory(workingDirectory); +} + +bool StdIOClient::matches(const LanguageClientSettings &setting) +{ + return setting.m_executable == m_process.program() + && setting.m_arguments == m_process.arguments(); +} + +void StdIOClient::sendData(const QByteArray &data) +{ + if (m_process.state() != QProcess::Running) { + log(tr("Cannot send data to unstarted server %1").arg(m_process.program())); + return; + } + qCDebug(LOGLSPCLIENTV) << "StdIOClient send data:"; + qCDebug(LOGLSPCLIENTV).noquote() << data; + m_process.write(data); +} + +void StdIOClient::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitStatus == QProcess::CrashExit) + setError(tr("Crashed with exit code %1 : %2").arg(exitCode).arg(m_process.error())); + emit finished(); +} + +void StdIOClient::readError() +{ + qCDebug(LOGLSPCLIENTV) << "StdIOClient std err:\n"; + qCDebug(LOGLSPCLIENTV).noquote() << m_process.readAllStandardError(); +} + +void StdIOClient::readOutput() +{ + const QByteArray &out = m_process.readAllStandardOutput(); + qDebug(LOGLSPCLIENTV) << "StdIOClient std out:\n"; + qDebug(LOGLSPCLIENTV).noquote() << out; + parseData(out); +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/baseclient.h b/src/plugins/languageclient/baseclient.h new file mode 100644 index 0000000000..211b4b7a15 --- /dev/null +++ b/src/plugins/languageclient/baseclient.h @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "dynamiccapabilities.h" +#include "languageclientsettings.h" + +#include <coreplugin/id.h> +#include <coreplugin/messagemanager.h> +#include <utils/link.h> + +#include <languageserverprotocol/initializemessages.h> +#include <languageserverprotocol/shutdownmessages.h> +#include <languageserverprotocol/textsynchronization.h> +#include <languageserverprotocol/messages.h> +#include <languageserverprotocol/client.h> +#include <languageserverprotocol/languagefeatures.h> + +#include <QBuffer> +#include <QHash> +#include <QProcess> +#include <QJsonDocument> +#include <QTextCursor> + +namespace Core { class IDocument; } +namespace ProjectExplorer { class Project; } +namespace TextEditor +{ + class TextDocument; + class TextEditorWidget; +} + +namespace LanguageClient { + +class BaseClient : public QObject +{ + Q_OBJECT + +public: + BaseClient(); + ~BaseClient() override; + + BaseClient(const BaseClient &) = delete; + BaseClient(BaseClient &&) = delete; + BaseClient &operator=(const BaseClient &) = delete; + BaseClient &operator=(BaseClient &&) = delete; + + enum State { + Uninitialized, + InitializeRequested, + Initialized, + ShutdownRequested, + Shutdown, + Error + }; + + void initialize(); + void shutdown(); + State state() const; + bool reachable() const { return m_state == Initialized; } + + // document synchronization + void openDocument(Core::IDocument *document); + void closeDocument(const LanguageServerProtocol::DidCloseTextDocumentParams ¶ms); + bool documentOpen(const LanguageServerProtocol::DocumentUri &uri) const; + void documentContentsSaved(Core::IDocument *document); + void documentWillSave(Core::IDocument *document); + void documentContentsChanged(Core::IDocument *document); + void registerCapabilities(const QList<LanguageServerProtocol::Registration> ®istrations); + void unregisterCapabilities(const QList<LanguageServerProtocol::Unregistration> &unregistrations); + bool findLinkAt(LanguageServerProtocol::GotoDefinitionRequest &request); + void requestDocumentSymbols(TextEditor::TextDocument *document); + void cursorPositionChanged(TextEditor::TextEditorWidget *widget); + + // workspace control + void projectOpened(ProjectExplorer::Project *project); + void projectClosed(ProjectExplorer::Project *project); + + void sendContent(const LanguageServerProtocol::IContent &content); + void sendContent(const LanguageServerProtocol::DocumentUri &uri, + const LanguageServerProtocol::IContent &content); + void cancelRequest(const LanguageServerProtocol::MessageId &id); + + void setSupportedLanguages(const QStringList &supportedLanguages); + bool isSupportedLanguage(const QString &language) const; + + void setName(const QString &name) { m_displayName = name; } + QString name() const { return m_displayName; } + + Core::Id id() const { return m_id; } + + virtual bool start() { return true; } + virtual bool matches(const LanguageClientSettings &/*setting*/) { return false; } + virtual void reset(); + + void log(const QString &message, + Core::MessageManager::PrintToOutputPaneFlag flag = Core::MessageManager::NoModeSwitch); + void log(LanguageServerProtocol::LogMessageParams &message, + Core::MessageManager::PrintToOutputPaneFlag flag = Core::MessageManager::NoModeSwitch); + +signals: + void initialized(LanguageServerProtocol::ServerCapabilities capabilities); + void finished(); + +protected: + void setError(const QString &message); + virtual void sendData(const QByteArray &data) = 0; + void parseData(const QByteArray &data); + +private: + void handleResponse(const LanguageServerProtocol::MessageId &id, const QByteArray &content, + QTextCodec *codec); + void handleMethod(const QString &method, LanguageServerProtocol::MessageId id, + const LanguageServerProtocol::IContent *content); + + void intializeCallback(const LanguageServerProtocol::InitializeResponse &initResponse); + void shutDownCallback(const LanguageServerProtocol::ShutdownResponse &shutdownResponse); + bool sendWorkspceFolderChanges() const; + + using ContentHandler = std::function<void(const QByteArray &, QTextCodec *, QString &, + LanguageServerProtocol::ResponseHandlers, + LanguageServerProtocol::MethodHandler)>; + + State m_state = Uninitialized; + QHash<LanguageServerProtocol::MessageId, LanguageServerProtocol::ResponseHandler> m_responseHandlers; + QHash<QByteArray, ContentHandler> m_contentHandler; + QBuffer m_buffer; + QString m_displayName; + QStringList m_supportedLanguageIds; + QList<Utils::FileName> m_openedDocument; + Core::Id m_id; + LanguageServerProtocol::ServerCapabilities m_serverCapabilities; + DynamicCapabilities m_dynamicCapabilities; + LanguageServerProtocol::BaseMessage m_currentMessage; + QHash<LanguageServerProtocol::DocumentUri, LanguageServerProtocol::MessageId> m_highlightRequests; +}; + +class StdIOClient : public BaseClient +{ + Q_OBJECT +public: + StdIOClient(const QString &command, const QStringList &args = QStringList()); + ~StdIOClient() override; + + StdIOClient() = delete; + StdIOClient(const StdIOClient &) = delete; + StdIOClient(StdIOClient &&) = delete; + StdIOClient &operator=(const StdIOClient &) = delete; + StdIOClient &operator=(StdIOClient &&) = delete; + + bool start() override; + + void setWorkingDirectory(const QString &workingDirectory); + + bool matches(const LanguageClientSettings &setting) override; + +protected: + void sendData(const QByteArray &data) final; + QProcess m_process; + +private: + void readError(); + void readOutput(); + void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); +}; + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/dynamiccapabilities.cpp b/src/plugins/languageclient/dynamiccapabilities.cpp new file mode 100644 index 0000000000..1b0aef037f --- /dev/null +++ b/src/plugins/languageclient/dynamiccapabilities.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dynamiccapabilities.h" + +using namespace LanguageServerProtocol; + +namespace LanguageClient { + +void DynamicCapabilities::registerCapability(const QList<Registration> ®istrations) +{ + for (const Registration& registration : registrations) { + const QString &method = registration.method(); + m_capability[method].enable(registration.id(), registration.registerOptions()); + m_methodForId.insert(registration.id(), method); + } +} + +void DynamicCapabilities::unregisterCapability(const QList<Unregistration> &unregistrations) +{ + for (const Unregistration& unregistration : unregistrations) { + QString method = unregistration.method(); + if (method.isEmpty()) + method = m_methodForId[unregistration.id()]; + m_capability[method].disable(); + m_methodForId.remove(unregistration.id()); + } +} + +Utils::optional<bool> DynamicCapabilities::isRegistered(const QString &method) const +{ + if (!m_capability.contains(method)) + return Utils::nullopt; + return m_capability[method].enabled(); +} + +void DynamicCapabilities::reset() +{ + m_capability.clear(); + m_methodForId.clear(); +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/dynamiccapabilities.h b/src/plugins/languageclient/dynamiccapabilities.h new file mode 100644 index 0000000000..6270c0699d --- /dev/null +++ b/src/plugins/languageclient/dynamiccapabilities.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <languageserverprotocol/client.h> + +namespace LanguageClient { + +class DynamicCapability +{ +public: + DynamicCapability() = default; + void enable(QString id, QJsonValue options) + { + QTC_CHECK(!m_enabled); + m_enabled = true; + m_id = id; + m_options = options; + } + + void disable() + { + m_enabled = true; + m_id.clear(); + m_options = QJsonValue(); + } + + bool enabled() const { return m_enabled; } + + QJsonValue options() const { return m_options; } + +private: + bool m_enabled = false; + QString m_id; + QJsonValue m_options; + +}; + +class DynamicCapabilities +{ +public: + DynamicCapabilities() = default; + + void registerCapability(const QList<LanguageServerProtocol::Registration> ®istrations); + void unregisterCapability(const QList<LanguageServerProtocol::Unregistration> &unregistrations); + + Utils::optional<bool> isRegistered(const QString &method) const; + QJsonValue option(const QString &method) const { return m_capability[method].options(); } + + void reset(); + +private: + QHash<QString, DynamicCapability> m_capability; + QHash<QString, QString> m_methodForId; +}; + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclient.pro b/src/plugins/languageclient/languageclient.pro new file mode 100644 index 0000000000..e34f11ddd6 --- /dev/null +++ b/src/plugins/languageclient/languageclient.pro @@ -0,0 +1,20 @@ +include(../../qtcreatorplugin.pri) + +DEFINES += LANGUAGECLIENT_LIBRARY + +HEADERS += \ + baseclient.h \ + dynamiccapabilities.h \ + languageclient_global.h \ + languageclientcodeassist.h \ + languageclientmanager.h \ + languageclientplugin.h \ + languageclientsettings.h + +SOURCES += \ + baseclient.cpp \ + dynamiccapabilities.cpp \ + languageclientcodeassist.cpp \ + languageclientmanager.cpp \ + languageclientplugin.cpp \ + languageclientsettings.cpp diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs new file mode 100644 index 0000000000..3b426ceb49 --- /dev/null +++ b/src/plugins/languageclient/languageclient.qbs @@ -0,0 +1,31 @@ +import qbs 1.0 + +QtcPlugin { + + name: "LanguageClient" + + Depends { name: "Qt.core" } + + Depends { name: "Utils" } + Depends { name: "ProjectExplorer" } + Depends { name: "LanguageServerProtocol" } + + Depends { name: "Core" } + Depends { name: "TextEditor" } + + files: [ + "baseclient.cpp", + "baseclient.h", + "dynamiccapabilities.cpp", + "dynamiccapabilities.h", + "languageclient_global.h", + "languageclientcodeassist.cpp", + "languageclientcodeassist.h", + "languageclientmanager.cpp", + "languageclientmanager.h", + "languageclientplugin.cpp", + "languageclientplugin.h", + "languageclientsettings.cpp", + "languageclientsettings.h", + ] +} diff --git a/src/plugins/languageclient/languageclient_dependencies.pri b/src/plugins/languageclient/languageclient_dependencies.pri new file mode 100644 index 0000000000..2b2a4211bf --- /dev/null +++ b/src/plugins/languageclient/languageclient_dependencies.pri @@ -0,0 +1,10 @@ +QTC_PLUGIN_NAME = LanguageClient + +QTC_LIB_DEPENDS += \ + utils \ + languageserverprotocol + +QTC_PLUGIN_DEPENDS += \ + coreplugin \ + projectexplorer \ + texteditor diff --git a/src/plugins/languageclient/languageclient_global.h b/src/plugins/languageclient/languageclient_global.h new file mode 100644 index 0000000000..a4cd255d90 --- /dev/null +++ b/src/plugins/languageclient/languageclient_global.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QtGlobal> + +namespace LanguageClient { +namespace Constants { + +const char LANGUAGECLIENT_SETTINGS_CATEGORY[] = "ZY.LanguageClient"; +const char LANGUAGECLIENT_SETTINGS_TR[] = QT_TRANSLATE_NOOP("LanguageClient", "Language Client"); + +} // namespace Constants +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientcodeassist.cpp b/src/plugins/languageclient/languageclientcodeassist.cpp new file mode 100644 index 0000000000..16c1d18e16 --- /dev/null +++ b/src/plugins/languageclient/languageclientcodeassist.cpp @@ -0,0 +1,354 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "languageclientcodeassist.h" +#include "baseclient.h" + +#include <languageserverprotocol/completion.h> +#include <texteditor/codeassist/assistinterface.h> +#include <texteditor/codeassist/assistproposalitem.h> +#include <texteditor/codeassist/iassistprocessor.h> +#include <texteditor/codeassist/genericproposal.h> +#include <texteditor/codeassist/genericproposalmodel.h> +#include <utils/algorithm.h> +#include <utils/textutils.h> +#include <utils/utilsicons.h> + +#include <QDebug> +#include <QLoggingCategory> +#include <QRegExp> +#include <QTextBlock> +#include <QTextDocument> +#include <QTime> + +static Q_LOGGING_CATEGORY(LOGLSPCOMPLETION, "qtc.languageclient.completion"); + +using namespace LanguageServerProtocol; + +namespace LanguageClient { + +class LanguageClientCompletionItem : public TextEditor::AssistProposalItemInterface +{ +public: + LanguageClientCompletionItem(CompletionItem item); + + // AssistProposalItemInterface interface + QString text() const override; + bool implicitlyApplies() const override; + bool prematurelyApplies(const QChar &typedCharacter) const override; + void apply(TextEditor::TextDocumentManipulatorInterface &manipulator, int basePosition) const override; + QIcon icon() const override; + QString detail() const override; + bool isSnippet() const override; + bool isValid() const override; + quint64 hash() const override; + + const QString &sortText() const; + + bool operator <(const LanguageClientCompletionItem &other) const; + +private: + CompletionItem m_item; + mutable QString m_sortText; +}; + +LanguageClientCompletionItem::LanguageClientCompletionItem(CompletionItem item) + : m_item(std::move(item)) +{ } + +QString LanguageClientCompletionItem::text() const +{ return m_item.label(); } + +bool LanguageClientCompletionItem::implicitlyApplies() const +{ return true; } + +bool LanguageClientCompletionItem::prematurelyApplies(const QChar &/*typedCharacter*/) const +{ return false; } + +static void applyTextEdit(TextEditor::TextDocumentManipulatorInterface &manipulator, + const TextEdit &edit) +{ + using namespace Utils::Text; + const Range range = edit.range(); + const QTextDocument *doc = manipulator.textCursorAt(manipulator.currentPosition()).document(); + const int start = positionInText(doc, range.start().line() + 1, range.start().character() + 1); + const int end = positionInText(doc, range.end().line() + 1, range.end().character() + 1); + manipulator.replace(start, end - start, edit.newText()); +} + +void LanguageClientCompletionItem::apply(TextEditor::TextDocumentManipulatorInterface &manipulator, + int /*basePosition*/) const +{ + const int pos = manipulator.currentPosition(); + if (auto edit = m_item.textEdit()) { + applyTextEdit(manipulator, *edit); + } else { + const QString textToInsert(m_item.insertText().value_or(text())); + int length = 0; + for (auto it = textToInsert.crbegin(); it != textToInsert.crend(); ++it) { + auto ch = *it; + if (ch == manipulator.characterAt(pos - length - 1)) + ++length; + else if (length != 0) + length = 0; + } + manipulator.replace(pos - length, length, textToInsert); + } + + if (auto additionalEdits = m_item.additionalTextEdits()) { + for (const auto &edit : *additionalEdits) + applyTextEdit(manipulator, edit); + } +} + +QIcon LanguageClientCompletionItem::icon() const +{ + QIcon icon; + using namespace Utils::CodeModelIcon; + const int kind = m_item.kind().value_or(CompletionItemKind::Text); + switch (kind) { + case CompletionItemKind::Method: + case CompletionItemKind::Function: + case CompletionItemKind::Constructor: icon = iconForType(FuncPublic); break; + case CompletionItemKind::Field: icon = iconForType(VarPublic); break; + case CompletionItemKind::Variable: icon = iconForType(VarPublic); break; + case CompletionItemKind::Class: icon = iconForType(Class); break; + case CompletionItemKind::Module: icon = iconForType(Namespace); break; + case CompletionItemKind::Property: icon = iconForType(Property); break; + case CompletionItemKind::Enum: icon = iconForType(Enum); break; + case CompletionItemKind::Keyword: icon = iconForType(Keyword); break; + case CompletionItemKind::Snippet: icon = QIcon(":/texteditor/images/snippet.png"); break; + case CompletionItemKind::EnumMember: icon = iconForType(Enumerator); break; + case CompletionItemKind::Struct: icon = iconForType(Struct); break; + default: + break; + } + return icon; +} + +QString LanguageClientCompletionItem::detail() const +{ + if (auto _doc = m_item.documentation()) { + auto doc = *_doc; + QString detailDocText; + if (Utils::holds_alternative<QString>(doc)) { + detailDocText = Utils::get<QString>(doc); + } else if (Utils::holds_alternative<MarkupContent>(doc)) { + // TODO markdown parser? + detailDocText = Utils::get<MarkupContent>(doc).content(); + } + if (!detailDocText.isEmpty()) + return detailDocText; + } + return m_item.detail().value_or(text()); +} + +bool LanguageClientCompletionItem::isSnippet() const +{ + // FIXME add lsp > creator snippet converter + // return m_item.insertTextFormat().value_or(CompletionItem::PlainText); + return false; +} + +bool LanguageClientCompletionItem::isValid() const +{ + return m_item.isValid(nullptr); +} + +quint64 LanguageClientCompletionItem::hash() const +{ + return qHash(m_item.label()); // TODO: naaaa +} + +const QString &LanguageClientCompletionItem::sortText() const +{ + if (m_sortText.isEmpty()) + m_sortText = m_item.sortText().has_value() ? *m_item.sortText() : m_item.label(); + return m_sortText; +} + +bool LanguageClientCompletionItem::operator <(const LanguageClientCompletionItem &other) const +{ + return sortText() < other.sortText(); +} + +class LanguageClientCompletionModel : public TextEditor::GenericProposalModel +{ +public: + // GenericProposalModel interface + bool isSortable(const QString &/*prefix*/) const override { return true; } + void sort(const QString &/*prefix*/) override; + bool supportsPrefixExpansion() const override { return false; } +}; + +void LanguageClientCompletionModel::sort(const QString &/*prefix*/) +{ + using namespace TextEditor; + std::sort(m_currentItems.begin(), m_currentItems.end(), + [] (AssistProposalItemInterface *a, AssistProposalItemInterface *b){ + return *(dynamic_cast<LanguageClientCompletionItem *>(a)) < *( + dynamic_cast<LanguageClientCompletionItem *>(b)); + }); +} + +class LanguageClientCompletionAssistProcessor : public TextEditor::IAssistProcessor +{ +public: + LanguageClientCompletionAssistProcessor(BaseClient *client); + TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override; + bool running() override; + +private: + void handleCompletionResponse(const Response<CompletionResult, LanguageClientNull> &response); + + QPointer<BaseClient> m_client; + bool m_running = false; + int m_pos = -1; +}; + +LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor(BaseClient *client) + : m_client(client) +{ } + +static QString assistReasonString(TextEditor::AssistReason reason) +{ + switch (reason) { + case TextEditor::IdleEditor: return QString("idle editor"); + case TextEditor::ActivationCharacter: return QString("activation character"); + case TextEditor::ExplicitlyInvoked: return QString("explicitly invoking"); + } + return QString("unknown reason"); +} + +TextEditor::IAssistProposal *LanguageClientCompletionAssistProcessor::perform( + const TextEditor::AssistInterface *interface) +{ + QTC_ASSERT(m_client, return nullptr); + m_pos = interface->position(); +// const QRegExp regexp("[_a-zA-Z][_a-zA-Z0-9]*"); // FIXME +// int delta = 0; +// while (m_pos - delta > 0 && regexp.exactMatch(interface->textAt(m_pos - delta - 1, delta + 1))) +// ++delta; +// m_pos -= delta; + CompletionRequest completionRequest; + CompletionParams::CompletionContext context; + context.setTriggerKind(interface->reason() == TextEditor::ActivationCharacter + ? CompletionParams::TriggerCharacter + : CompletionParams::Invoked); + auto params = completionRequest.params().value_or(CompletionParams()); + int line; + int column; + if (!Utils::Text::convertPosition(interface->textDocument(), m_pos, &line, &column)) + return nullptr; + --line; // line is 0 based in the protocol + params.setPosition({line, column}); + params.setTextDocument( + DocumentUri::fromFileName(Utils::FileName::fromString(interface->fileName()))); + completionRequest.setResponseCallback([this](auto response) { + this->handleCompletionResponse(response); + }); + completionRequest.setParams(params); + m_client->sendContent(completionRequest); + m_running = true; + qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() + << " : request completions at " << m_pos + << " by " << assistReasonString(interface->reason()); + return nullptr; +} + +bool LanguageClientCompletionAssistProcessor::running() +{ + return m_running; +} + +void LanguageClientCompletionAssistProcessor::handleCompletionResponse( + const Response<CompletionResult, LanguageClientNull> &response) +{ + using namespace TextEditor; + qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() << " : got completions"; + m_running = false; + QTC_ASSERT(m_client, return); + if (auto error = response.error()) { + m_client->log(error.value().message()); + return; + } + const Utils::optional<CompletionResult> &result = response.result(); + if (!result || Utils::holds_alternative<std::nullptr_t>(*result)) + return; + + QList<CompletionItem> items; + if (Utils::holds_alternative<CompletionList>(*result)) { + const auto &list = Utils::get<CompletionList>(*result); + items = list.items().value_or(QList<CompletionItem>()); + } else if (Utils::holds_alternative<QList<CompletionItem>>(*result)) { + items = Utils::get<QList<CompletionItem>>(*result); + } + auto model = new LanguageClientCompletionModel(); + model->loadContent(Utils::transform(items, [](const CompletionItem &item){ + return static_cast<AssistProposalItemInterface *>(new LanguageClientCompletionItem(item)); + })); + auto proposal = new GenericProposal(m_pos, GenericProposalModelPtr(model)); + proposal->setFragile(true); + setAsyncProposalAvailable(proposal); + qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() << " : " + << items.count() << " completions handled"; +} + +LanguageClientCompletionAssistProvider::LanguageClientCompletionAssistProvider(BaseClient *client) + : m_client(client) +{ } + +TextEditor::IAssistProcessor *LanguageClientCompletionAssistProvider::createProcessor() const +{ + return new LanguageClientCompletionAssistProcessor(m_client); +} + +TextEditor::IAssistProvider::RunType LanguageClientCompletionAssistProvider::runType() const +{ + return TextEditor::IAssistProvider::Asynchronous; +} + +int LanguageClientCompletionAssistProvider::activationCharSequenceLength() const +{ + return m_activationCharSequenceLength; +} + +bool LanguageClientCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const +{ + return Utils::anyOf(m_triggerChars, [sequence](const QString &trigger){ + return trigger.endsWith(sequence); + }); +} + +void LanguageClientCompletionAssistProvider::setTriggerCharacters(QList<QString> triggerChars) +{ + m_triggerChars = triggerChars; + for (const QString &trigger : triggerChars) { + if (trigger.length() > m_activationCharSequenceLength) + m_activationCharSequenceLength = trigger.length(); + } +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientcodeassist.h b/src/plugins/languageclient/languageclientcodeassist.h new file mode 100644 index 0000000000..14af748e72 --- /dev/null +++ b/src/plugins/languageclient/languageclientcodeassist.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <texteditor/codeassist/completionassistprovider.h> + +namespace LanguageClient { + +class BaseClient; + +class LanguageClientCompletionAssistProvider : public TextEditor::CompletionAssistProvider +{ +public: + LanguageClientCompletionAssistProvider(BaseClient *client); + + TextEditor::IAssistProcessor *createProcessor() const override; + RunType runType() const override; + + int activationCharSequenceLength() const override; + bool isActivationCharSequence(const QString &sequence) const override; + bool isContinuationChar(const QChar &) const override { return true; } + + void setTriggerCharacters(QList<QString> triggerChars); + +private: + QList<QString> m_triggerChars; + int m_activationCharSequenceLength = 0; + BaseClient *m_client; +}; + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp new file mode 100644 index 0000000000..6b9e54d114 --- /dev/null +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "languageclientmanager.h" + +#include <coreplugin/documentmanager.h> +#include <coreplugin/editormanager/documentmodel.h> +#include <coreplugin/editormanager/editormanager.h> +#include <coreplugin/editormanager/ieditor.h> +#include <languageserverprotocol/messages.h> +#include <projectexplorer/session.h> +#include <projectexplorer/project.h> +#include <texteditor/texteditor.h> +#include <texteditor/textmark.h> +#include <texteditor/textdocument.h> +#include <utils/mimetypes/mimedatabase.h> +#include <utils/theme/theme.h> +#include <utils/utilsicons.h> + +#include <QTimer> + +using namespace LanguageServerProtocol; + +namespace LanguageClient { + +static LanguageClientManager *managerInstance = nullptr; + +class LanguageClientMark : public TextEditor::TextMark +{ +public: + LanguageClientMark(const Utils::FileName &fileName, const Diagnostic &diag) + : TextEditor::TextMark(fileName, diag.range().start().line() + 1, "lspmark") + { + using namespace Utils; + setLineAnnotation(diag.message()); + setToolTip(diag.message()); + const bool isError + = diag.severity().value_or(DiagnosticSeverity::Hint) == DiagnosticSeverity::Error; + setColor(isError ? Theme::CodeModel_Error_TextMarkColor + : Theme::CodeModel_Warning_TextMarkColor); + + setIcon(isError ? Icons::CODEMODEL_ERROR.icon() + : Icons::CODEMODEL_WARNING.icon()); + } + + void removedFromEditor() override + { + LanguageClientManager::removeMark(this); + } +}; + +LanguageClientManager::LanguageClientManager() +{ + JsonRpcMessageHandler::registerMessageProvider("textDocument/publishDiagnostics", + [](const QJsonObject &object){ + return new PublishDiagnosticsNotification(object); + }); + JsonRpcMessageHandler::registerMessageProvider(LogMessageNotification::methodName, + [](const QJsonObject &object){ + return new LogMessageNotification(object); + }); + managerInstance = this; +} + +LanguageClientManager::~LanguageClientManager() +{ + for (auto interface : Utils::filtered(m_clients, &BaseClient::reachable)) + interface->shutdown(); +} + +void LanguageClientManager::init() +{ + using namespace Core; + using namespace ProjectExplorer; + QTC_ASSERT(managerInstance, return); + connect(EditorManager::instance(), &EditorManager::editorOpened, + managerInstance, &LanguageClientManager::editorOpened); + connect(EditorManager::instance(), &EditorManager::editorsClosed, + managerInstance, &LanguageClientManager::editorsClosed); + connect(EditorManager::instance(), &EditorManager::saved, + managerInstance, &LanguageClientManager::documentContentsSaved); + connect(EditorManager::instance(), &EditorManager::aboutToSave, + managerInstance, &LanguageClientManager::documentWillSave); + connect(SessionManager::instance(), &SessionManager::projectAdded, + managerInstance, &LanguageClientManager::projectAdded); + connect(SessionManager::instance(), &SessionManager::projectRemoved, + managerInstance, &LanguageClientManager::projectRemoved); +} + +void LanguageClientManager::publishDiagnostics(const Core::Id &id, + const PublishDiagnosticsParams ¶ms) +{ + const Utils::FileName filePath = params.uri().toFileName(); + auto doc = qobject_cast<TextEditor::TextDocument *>( + Core::DocumentModel::documentForFilePath(filePath.toString())); + if (!doc) + return; + + removeMarks(filePath, id); + managerInstance->m_marks[filePath][id].reserve(params.diagnostics().size()); + for (const Diagnostic& diagnostic : params.diagnostics()) { + auto mark = new LanguageClientMark(filePath, diagnostic); + managerInstance->m_marks[filePath][id].append(mark); + doc->addMark(mark); + } +} + +void LanguageClientManager::removeMark(LanguageClientMark *mark) +{ + for (auto &marks : managerInstance->m_marks[mark->fileName()]) + marks.removeAll(mark); + delete mark; +} + +void LanguageClientManager::removeMarks(const Utils::FileName &fileName) +{ + auto doc = qobject_cast<TextEditor::TextDocument *>( + Core::DocumentModel::documentForFilePath(fileName.toString())); + if (!doc) + return; + + for (auto marks : managerInstance->m_marks[fileName]) { + for (TextEditor::TextMark *mark : marks) { + doc->removeMark(mark); + delete mark; + } + } + managerInstance->m_marks[fileName].clear(); +} + +void LanguageClientManager::removeMarks(const Utils::FileName &fileName, const Core::Id &id) +{ + auto doc = qobject_cast<TextEditor::TextDocument *>( + Core::DocumentModel::documentForFilePath(fileName.toString())); + if (!doc) + return; + + for (TextEditor::TextMark *mark : managerInstance->m_marks[fileName][id]) { + doc->removeMark(mark); + delete mark; + } + managerInstance->m_marks[fileName][id].clear(); +} + +void LanguageClientManager::removeMarks(const Core::Id &id) +{ + for (const Utils::FileName &fileName : managerInstance->m_marks.keys()) + removeMarks(fileName, id); +} + +void LanguageClientManager::startClient(BaseClient *client) +{ + managerInstance->m_clients.append(client); + connect(client, &BaseClient::finished, managerInstance, [client](){ + managerInstance->clientFinished(client); + }); + if (client->start()) + client->initialize(); + else + managerInstance->clientFinished(client); +} + +QVector<BaseClient *> LanguageClientManager::clients() +{ + return managerInstance->m_clients; +} + +void LanguageClientManager::addExclusiveRequest(const MessageId &id, BaseClient *client) +{ + managerInstance->m_exclusiveRequests[id] << client; +} + +void LanguageClientManager::reportFinished(const MessageId &id, BaseClient *byClient) +{ + for (BaseClient *client : managerInstance->m_exclusiveRequests[id]) { + if (client != byClient) + client->cancelRequest(id); + } + managerInstance->m_exclusiveRequests.remove(id); +} + +QVector<BaseClient *> LanguageClientManager::reachableClients() +{ + return Utils::filtered(m_clients, &BaseClient::reachable); +} + +static void sendToInterfaces(const IContent &content, const QVector<BaseClient *> &interfaces) +{ + for (BaseClient *interface : interfaces) + interface->sendContent(content); +} + +void LanguageClientManager::sendToAllReachableServers(const IContent &content) +{ + sendToInterfaces(content, reachableClients()); +} + +void LanguageClientManager::clientFinished(BaseClient *client) +{ + constexpr int restartTimeoutS = 5; + const bool unexpectedFinish = client->state() != BaseClient::Shutdown + && client->state() != BaseClient::ShutdownRequested; + managerInstance->removeMarks(client->id()); + managerInstance->m_clients.removeAll(client); + if (unexpectedFinish) { + client->log(tr("Unexpectedly finished. Restarting in %1 seconds.").arg(restartTimeoutS), + Core::MessageManager::Flash); + client->reset(); + QTimer::singleShot(restartTimeoutS * 1000, this, [client](){ startClient(client); }); + } else { + delete client; + } +} + +void LanguageClientManager::editorOpened(Core::IEditor *iEditor) +{ + using namespace TextEditor; + Core::IDocument *document = iEditor->document(); + for (BaseClient *interface : reachableClients()) + interface->openDocument(document); + + if (auto textDocument = qobject_cast<TextDocument *>(document)) { + if (BaseTextEditor *editor = BaseTextEditor::textEditorForDocument(textDocument)) { + if (TextEditorWidget *widget = editor->editorWidget()) { + connect(widget, &TextEditorWidget::requestLinkAt, this, + [this, filePath = document->filePath()] + (const QTextCursor &cursor, Utils::ProcessLinkCallback &callback){ + findLinkAt(filePath, cursor, callback); + }); + } + } + } +} + +void LanguageClientManager::editorsClosed(const QList<Core::IEditor *> editors) +{ + for (auto iEditor : editors) { + if (auto editor = qobject_cast<TextEditor::BaseTextEditor *>(iEditor)) { + removeMarks(editor->document()->filePath()); + const DidCloseTextDocumentParams params(TextDocumentIdentifier( + DocumentUri::fromFileName(editor->document()->filePath()))); + for (BaseClient *interface : reachableClients()) + interface->closeDocument(params); + } + } +} + +void LanguageClientManager::documentContentsSaved(Core::IDocument *document) +{ + for (BaseClient *interface : reachableClients()) + interface->documentContentsSaved(document); +} + +void LanguageClientManager::documentWillSave(Core::IDocument *document) +{ + for (BaseClient *interface : reachableClients()) + interface->documentContentsSaved(document); +} + +void LanguageClientManager::findLinkAt(const Utils::FileName &filePath, + const QTextCursor &cursor, + Utils::ProcessLinkCallback callback) +{ + const DocumentUri uri = DocumentUri::fromFileName(filePath); + const TextDocumentIdentifier document(uri); + const Position pos(cursor); + TextDocumentPositionParams params(document, pos); + GotoDefinitionRequest request(params); + request.setResponseCallback([callback](const Response<GotoResult, LanguageClientNull> &response){ + if (Utils::optional<GotoResult> _result = response.result()) { + const GotoResult result = _result.value(); + if (Utils::holds_alternative<std::nullptr_t>(result)) + return; + if (auto ploc = Utils::get_if<Location>(&result)) { + callback(ploc->toLink()); + } else if (auto plloc = Utils::get_if<QList<Location>>(&result)) { + if (!plloc->isEmpty()) + callback(plloc->value(0).toLink()); + } + } + }); + for (BaseClient *interface : reachableClients()) { + if (interface->findLinkAt(request)) + m_exclusiveRequests[request.id()] << interface; + } +} + +void LanguageClientManager::projectAdded(ProjectExplorer::Project *project) +{ + for (BaseClient *interface : reachableClients()) + interface->projectOpened(project); +} + +void LanguageClientManager::projectRemoved(ProjectExplorer::Project *project) +{ + for (BaseClient *interface : reachableClients()) + interface->projectClosed(project); +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h new file mode 100644 index 0000000000..5c115643fd --- /dev/null +++ b/src/plugins/languageclient/languageclientmanager.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "baseclient.h" + +#include <coreplugin/id.h> + +#include <languageserverprotocol/diagnostics.h> +#include <languageserverprotocol/languagefeatures.h> +#include <languageserverprotocol/textsynchronization.h> + +namespace Core { +class IEditor; +class IDocument; +} + +namespace ProjectExplorer { class Project; } + +namespace LanguageClient { + +class LanguageClientMark; + +class LanguageClientManager : public QObject +{ + Q_OBJECT +public: + ~LanguageClientManager() override; + + static void init(); + + static void publishDiagnostics(const Core::Id &id, + const LanguageServerProtocol::PublishDiagnosticsParams ¶ms); + + static void removeMark(LanguageClientMark *mark); + static void removeMarks(const Utils::FileName &fileName); + static void removeMarks(const Utils::FileName &fileName, const Core::Id &id); + static void removeMarks(const Core::Id &id); + + static void startClient(BaseClient *client); + static QVector<BaseClient *> clients(); + + static void addExclusiveRequest(const LanguageServerProtocol::MessageId &id, BaseClient *client); + static void reportFinished(const LanguageServerProtocol::MessageId &id, BaseClient *byClient); + +private: + LanguageClientManager(); + LanguageClientManager(const LanguageClientManager &other) = delete; + LanguageClientManager(LanguageClientManager &&other) = delete; + + void editorOpened(Core::IEditor *editor); + void editorsClosed(const QList<Core::IEditor *> editors); + void documentContentsSaved(Core::IDocument *document); + void documentWillSave(Core::IDocument *document); + void findLinkAt(const Utils::FileName &filePath, const QTextCursor &cursor, + Utils::ProcessLinkCallback callback); + + void projectAdded(ProjectExplorer::Project *project); + void projectRemoved(ProjectExplorer::Project *project); + + QVector<BaseClient *> reachableClients(); + void sendToAllReachableServers(const LanguageServerProtocol::IContent &content); + + void clientFinished(BaseClient *client); + + QVector<BaseClient *> m_clients; + QHash<Utils::FileName, QHash<Core::Id, QVector<LanguageClientMark *>>> m_marks; + QHash<LanguageServerProtocol::MessageId, QList<BaseClient *>> m_exclusiveRequests; + + friend class LanguageClientPlugin; +}; +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientplugin.cpp b/src/plugins/languageclient/languageclientplugin.cpp new file mode 100644 index 0000000000..4fd3cedfcf --- /dev/null +++ b/src/plugins/languageclient/languageclientplugin.cpp @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "languageclientplugin.h" + +namespace LanguageClient { + +bool LanguageClientPlugin::initialize(const QStringList & /*arguments*/, QString * /*errorString*/) +{ + return true; +} + +void LanguageClientPlugin::extensionsInitialized() +{ + LanguageClientManager::init(); + LanguageClientSettings::init(); +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientplugin.h b/src/plugins/languageclient/languageclientplugin.h new file mode 100644 index 0000000000..31a4c1ae99 --- /dev/null +++ b/src/plugins/languageclient/languageclientplugin.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "languageclientmanager.h" +#include "languageclientsettings.h" + +#include <extensionsystem/iplugin.h> + +namespace LanguageClient { + +class LanguageClientPlugin : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "LanguageClient.json") +public: + LanguageClientPlugin() = default; + + // IPlugin interface +private: + bool initialize(const QStringList &arguments, QString *errorString) override; + void extensionsInitialized() override; + +private: + LanguageClientManager m_clientManager; +}; + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientsettings.cpp b/src/plugins/languageclient/languageclientsettings.cpp new file mode 100644 index 0000000000..e1470edf05 --- /dev/null +++ b/src/plugins/languageclient/languageclientsettings.cpp @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "languageclientmanager.h" +#include "languageclientsettings.h" +#include "languageclient_global.h" + +#include <coreplugin/icore.h> +#include <utils/algorithm.h> +#include <utils/delegates.h> +#include <languageserverprotocol/lsptypes.h> + +#include <QBoxLayout> +#include <QComboBox> +#include <QCoreApplication> +#include <QFileInfo> +#include <QHeaderView> +#include <QPushButton> +#include <QSettings> +#include <QStyledItemDelegate> +#include <QTreeView> + +constexpr char nameKey[] = "name"; +constexpr char enabledKey[] = "enabled"; +constexpr char languageKey[] = "language"; +constexpr char executableKey[] = "executable"; +constexpr char argumentsKey[] = "arguments"; +constexpr char settingsGroupKey[] = "LanguageClient"; +constexpr char clientsKey[] = "clients"; + +namespace LanguageClient { + +class LanguageClientSettingsModel : public QAbstractTableModel +{ +public: + LanguageClientSettingsModel() = default; + + // QAbstractItemModel interface + int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const override { return m_settings.count(); } + int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const override { return ColumnCount; } + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + bool removeRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()) override; + bool insertRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()) override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + + void toSettings(QSettings *settings) const; + void fromSettings(QSettings *settings); + + void applyChanges(); + + enum Columns { + DisplayNameColumn = 0, + EnabledColumn, + LanguageColumn, + ExecutableColumn, + ArgumentsColumn, + ColumnCount + }; + +private: + QList<LanguageClientSettings> m_settings; +}; + +class LanguageClientSettingsPageWidget : public QWidget +{ +public: + LanguageClientSettingsPageWidget(LanguageClientSettingsModel &settings); + +private: + LanguageClientSettingsModel &m_settings; + QTreeView *m_view; + + void addItem(); + void deleteItem(); +}; + +class LanguageClientSettingsPage : public Core::IOptionsPage +{ +public: + LanguageClientSettingsPage(); + ~LanguageClientSettingsPage() override; + + void init(); + + // IOptionsPage interface + QWidget *widget() override; + void apply() override; + void finish() override; + +private: + LanguageClientSettingsModel m_settings; + QPointer<LanguageClientSettingsPageWidget> m_widget; +}; + +class LanguageChooseDelegate : public QStyledItemDelegate +{ +public: + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + void setEditorData(QWidget *editor, const QModelIndex &index) const override; +}; + +QWidget *LanguageChooseDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + Q_UNUSED(option); + Q_UNUSED(index); + auto editor = new QComboBox(parent); + editor->addItem(noLanguageFilter); + editor->addItems(LanguageServerProtocol::languageIds().values()); + return editor; +} + +void LanguageChooseDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + if (auto comboBox = qobject_cast<QComboBox*>(editor)) + comboBox->setCurrentText(index.data().toString()); +} + +LanguageClientSettingsPageWidget::LanguageClientSettingsPageWidget(LanguageClientSettingsModel &settings) + : m_settings(settings) + , m_view(new QTreeView()) +{ + auto layout = new QHBoxLayout(); + m_view->setModel(&m_settings); + m_view->header()->setStretchLastSection(true); + m_view->setRootIsDecorated(false); + m_view->setItemsExpandable(false); + m_view->setItemDelegateForColumn(LanguageClientSettingsModel::LanguageColumn, new LanguageChooseDelegate()); + auto executableDelegate = new Utils::PathChooserDelegate(); + executableDelegate->setExpectedKind(Utils::PathChooser::File); + executableDelegate->setHistoryCompleter("LanguageClient.ServerPathHistory"); + m_view->setItemDelegateForColumn(LanguageClientSettingsModel::ExecutableColumn, executableDelegate); + auto buttonLayout = new QVBoxLayout(); + auto addButton = new QPushButton(tr("&Add")); + connect(addButton, &QPushButton::pressed, this, &LanguageClientSettingsPageWidget::addItem); + auto deleteButton = new QPushButton(tr("&Delete")); + connect(deleteButton, &QPushButton::pressed, this, &LanguageClientSettingsPageWidget::deleteItem); + + setLayout(layout); + layout->addWidget(m_view); + layout->addLayout(buttonLayout); + buttonLayout->addWidget(addButton); + buttonLayout->addWidget(deleteButton); + buttonLayout->addStretch(10); +} + +void LanguageClientSettingsPageWidget::addItem() +{ + const int row = m_settings.rowCount(); + m_settings.insertRows(row); +} + +void LanguageClientSettingsPageWidget::deleteItem() +{ + auto index = m_view->currentIndex(); + if (index.isValid()) + m_settings.removeRows(index.row()); +} + +LanguageClientSettingsPage::LanguageClientSettingsPage() +{ + setId("LanguageClient.General"); + setDisplayName(tr("General")); + setCategory(Constants::LANGUAGECLIENT_SETTINGS_CATEGORY); + setDisplayCategory(QCoreApplication::translate("LanguageClient", + Constants::LANGUAGECLIENT_SETTINGS_TR)); + //setCategoryIcon( /* TODO */ ); +} + +LanguageClientSettingsPage::~LanguageClientSettingsPage() +{ + if (m_widget) + delete m_widget; +} + +void LanguageClientSettingsPage::init() +{ + m_settings.fromSettings(Core::ICore::settings()); + m_settings.applyChanges(); +} + +QWidget *LanguageClientSettingsPage::widget() +{ + if (!m_widget) + m_widget = new LanguageClientSettingsPageWidget(m_settings); + return m_widget; +} + +void LanguageClientSettingsPage::apply() +{ + m_settings.toSettings(Core::ICore::settings()); + m_settings.applyChanges(); +} + +void LanguageClientSettingsPage::finish() +{ + m_settings.fromSettings(Core::ICore::settings()); +} + +QVariant LanguageClientSettingsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + LanguageClientSettings setting = m_settings[index.row()]; + if (role == Qt::DisplayRole || role == Qt::EditRole) { + switch (index.column()) { + case DisplayNameColumn: return setting.m_name; + case LanguageColumn: return setting.m_language; + case ExecutableColumn: return setting.m_executable; + case ArgumentsColumn: return setting.m_arguments.join(' '); + } + } else if (role == Qt::CheckStateRole && index.column() == EnabledColumn) { + return setting.m_enabled ? Qt::Checked : Qt::Unchecked; + } + return QVariant(); +} + +QVariant LanguageClientSettingsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QVariant(); + + switch (section) { + case DisplayNameColumn: return tr("Name"); + case EnabledColumn: return tr("Enabled"); + case LanguageColumn: return tr("Language"); + case ExecutableColumn: return tr("Executable"); + case ArgumentsColumn: return tr("Arguments"); + } + return QVariant(); +} + +bool LanguageClientSettingsModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (row >= int(m_settings.size())) + return false; + const auto first = m_settings.begin() + row; + const int end = qMin(row + count - 1, int(m_settings.size()) - 1); + beginRemoveRows(parent, row, end); + m_settings.erase(first, first + count); + endRemoveRows(); + return true; +} + +bool LanguageClientSettingsModel::insertRows(int row, int count, const QModelIndex &parent) +{ + if (row > m_settings.size() || row < 0) + return false; + beginInsertRows(parent, row, row + count - 1); + for (int i = 0; i < count; ++i) + m_settings.insert(row + i, {}); + endInsertRows(); + return true; +} + +bool LanguageClientSettingsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + LanguageClientSettings &setting = m_settings[index.row()]; + if (role == Qt::DisplayRole || role == Qt::EditRole) { + switch (index.column()) { + case DisplayNameColumn: setting.m_name = value.toString(); break; + case LanguageColumn: setting.m_language = value.toString(); break; + case ExecutableColumn: setting.m_executable = value.toString(); break; + case ArgumentsColumn: setting.m_arguments = value.toString().split(' '); break; + default: + return false; + } + emit dataChanged(index, index, { Qt::EditRole, Qt::DisplayRole }); + return true; + } + if (role == Qt::CheckStateRole && index.column() == EnabledColumn) { + setting.m_enabled = value.toBool(); + emit dataChanged(index, index, { Qt::CheckStateRole }); + return true; + } + return false; +} + +Qt::ItemFlags LanguageClientSettingsModel::flags(const QModelIndex &index) const +{ + const auto defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; + if (index.column() == EnabledColumn) + return defaultFlags | Qt::ItemIsUserCheckable; + return defaultFlags; +} + +void LanguageClientSettingsModel::toSettings(QSettings *settings) const +{ + settings->beginGroup(settingsGroupKey); + settings->setValue(clientsKey, Utils::transform(m_settings, + [](const LanguageClientSettings & setting){ + return QVariant(setting.toMap()); + })); + settings->endGroup(); +} + +void LanguageClientSettingsModel::fromSettings(QSettings *settings) +{ + settings->beginGroup(settingsGroupKey); + auto variants = settings->value(clientsKey).toList(); + m_settings.reserve(variants.size()); + m_settings = Utils::transform(variants, [](const QVariant& var){ + return LanguageClientSettings::fromMap(var.toMap()); + }); + settings->endGroup(); +} + +void LanguageClientSettingsModel::applyChanges() +{ + const QVector<BaseClient *> interfaces(LanguageClientManager::clients()); + QVector<BaseClient *> toShutdown; + QList<LanguageClientSettings> toStart = m_settings; + // check currently registered interfaces + for (auto interface : interfaces) { + auto setting = Utils::findOr(m_settings, LanguageClientSettings(), [interface](const LanguageClientSettings &setting){ + return interface->matches(setting); + }); + if (setting.isValid() && setting.m_enabled) { + toStart.removeAll(setting); + if (!interface->isSupportedLanguage(setting.m_language)) + interface->setSupportedLanguages({setting.m_language}); + } else { + toShutdown << interface; + } + } + for (auto interface : toShutdown) + interface->shutdown(); + for (auto setting : toStart) { + if (setting.isValid() && setting.m_enabled) { + auto client = new StdIOClient(setting.m_executable, setting.m_arguments); + client->setName(setting.m_name); + if (setting.m_language != noLanguageFilter) + client->setSupportedLanguages({setting.m_language}); + LanguageClientManager::startClient(client); + } + } +} + +bool LanguageClientSettings::isValid() +{ + return !m_name.isEmpty() && !m_executable.isEmpty() && QFile::exists(m_executable); +} + +bool LanguageClientSettings::operator==(const LanguageClientSettings &other) const +{ + return m_name == other.m_name + && m_enabled == other.m_enabled + && m_language == other.m_language + && m_executable == other.m_executable + && m_arguments == other.m_arguments; +} + +QVariantMap LanguageClientSettings::toMap() const +{ + QVariantMap map; + map.insert(nameKey, m_name); + map.insert(enabledKey, m_enabled); + map.insert(languageKey, m_language); + map.insert(executableKey, m_executable); + map.insert(argumentsKey, m_arguments); + return map; +} + +LanguageClientSettings LanguageClientSettings::fromMap(const QVariantMap &map) +{ + return { map[nameKey].toString(), + map[enabledKey].toBool(), + map[languageKey].toString(), + map[executableKey].toString(), + map[argumentsKey].toStringList() }; +} + +void LanguageClientSettings::init() +{ + static LanguageClientSettingsPage settingsPage; + settingsPage.init(); +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientsettings.h b/src/plugins/languageclient/languageclientsettings.h new file mode 100644 index 0000000000..d06481e763 --- /dev/null +++ b/src/plugins/languageclient/languageclientsettings.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <coreplugin/dialogs/ioptionspage.h> + +#include <QAbstractItemModel> +#include <QPointer> +#include <QWidget> + +namespace LanguageClient { + +constexpr char noLanguageFilter[] = "No Filter"; + +class LanguageClientSettings +{ +public: + LanguageClientSettings() = default; + LanguageClientSettings(const QString &name, bool enabled, const QString &language, + const QString &executable, const QStringList &arguments) + : m_name(name) + , m_enabled(enabled) + , m_language(language) + , m_executable(executable) + , m_arguments(arguments) + {} + QString m_name = QString("New Language Server"); + bool m_enabled = true; + QString m_language = QLatin1String(noLanguageFilter); + QString m_executable; + QStringList m_arguments; + + bool isValid(); + + bool operator==(const LanguageClientSettings &other) const; + + QVariantMap toMap() const; + static LanguageClientSettings fromMap(const QVariantMap &map); + static void init(); +}; + + +} // namespace LanguageClient diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index a540713d7a..1f1cf99648 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -55,7 +55,8 @@ SUBDIRS = \ updateinfo \ scxmleditor \ welcome \ - silversearcher + silversearcher \ + languageclient qtHaveModule(serialport) { SUBDIRS += serialterminal diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs index 17af43d2ba..9217c9e284 100644 --- a/src/plugins/plugins.qbs +++ b/src/plugins/plugins.qbs @@ -39,6 +39,7 @@ Project { "help/help.qbs", "imageviewer/imageviewer.qbs", "ios/ios.qbs", + "languageclient/languageclient.qbs", "macros/macros.qbs", "mercurial/mercurial.qbs", "modeleditor/modeleditor.qbs", diff --git a/src/plugins/pythoneditor/pythoneditor.cpp b/src/plugins/pythoneditor/pythoneditor.cpp index c3a608e8d3..4c8b95ad92 100644 --- a/src/plugins/pythoneditor/pythoneditor.cpp +++ b/src/plugins/pythoneditor/pythoneditor.cpp @@ -49,8 +49,9 @@ PythonEditorFactory::PythonEditorFactory() addMimeType(Constants::C_PY_MIMETYPE); setEditorActionHandlers(TextEditorActionHandler::Format - | TextEditorActionHandler::UnCommentSelection - | TextEditorActionHandler::UnCollapseAll); + | TextEditorActionHandler::UnCommentSelection + | TextEditorActionHandler::UnCollapseAll + | TextEditorActionHandler::FollowSymbolUnderCursor); setDocumentCreator([] { return new TextDocument(Constants::C_PYTHONEDITOR_ID); }); setIndenterCreator([] { return new PythonIndenter; }); diff --git a/src/plugins/texteditor/codeassist/codeassistant.cpp b/src/plugins/texteditor/codeassist/codeassistant.cpp index 0c57dab5c7..cede626ea3 100644 --- a/src/plugins/texteditor/codeassist/codeassistant.cpp +++ b/src/plugins/texteditor/codeassist/codeassistant.cpp @@ -207,6 +207,7 @@ void CodeAssistantPrivate::requestProposal(AssistReason reason, return; m_assistKind = kind; + m_requestProvider = provider; IAssistProcessor *processor = provider->createProcessor(); switch (provider->runType()) { @@ -220,7 +221,6 @@ void CodeAssistantPrivate::requestProposal(AssistReason reason, if (IAssistProposal *newProposal = processor->immediateProposal(assistInterface)) displayProposal(newProposal, reason); - m_requestProvider = provider; m_requestRunner = new ProcessorRunner; m_runnerConnection = connect(m_requestRunner, &ProcessorRunner::finished, this, [this, reason](){ @@ -433,10 +433,13 @@ void CodeAssistantPrivate::notifyChange() QTC_ASSERT(m_proposal, return); if (m_editorWidget->position() < m_proposal->basePosition()) { destroyContext(); - } else { + } else if (!m_proposal->isFragile()) { m_proposalWidget->updateProposal( m_editorWidget->textAt(m_proposal->basePosition(), m_editorWidget->position() - m_proposal->basePosition())); + } else { + destroyContext(); + requestProposal(ExplicitlyInvoked, m_assistKind, m_requestProvider); } } } diff --git a/src/plugins/texteditor/plaintexteditorfactory.cpp b/src/plugins/texteditor/plaintexteditorfactory.cpp index b0932bf3b5..3e39cf810a 100644 --- a/src/plugins/texteditor/plaintexteditorfactory.cpp +++ b/src/plugins/texteditor/plaintexteditorfactory.cpp @@ -69,8 +69,9 @@ PlainTextEditorFactory::PlainTextEditorFactory() setUseGenericHighlighter(true); setEditorActionHandlers(TextEditorActionHandler::Format | - TextEditorActionHandler::UnCommentSelection | - TextEditorActionHandler::UnCollapseAll); + TextEditorActionHandler::UnCommentSelection | + TextEditorActionHandler::UnCollapseAll | + TextEditorActionHandler::FollowSymbolUnderCursor); } PlainTextEditorFactory *PlainTextEditorFactory::instance() diff --git a/src/plugins/texteditor/textdocument.h b/src/plugins/texteditor/textdocument.h index 8a4e05a4f7..3b526c24a7 100644 --- a/src/plugins/texteditor/textdocument.h +++ b/src/plugins/texteditor/textdocument.h @@ -29,6 +29,7 @@ #include <coreplugin/id.h> #include <coreplugin/textdocument.h> +#include <utils/link.h> #include <QList> #include <QMap> diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 5e3d952db7..9e02bd440c 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -6187,8 +6187,12 @@ void TextEditorWidget::zoomReset() showZoomIndicator(this, 100); } -void TextEditorWidget::findLinkAt(const QTextCursor &, Utils::ProcessLinkCallback &&, bool, bool) +void TextEditorWidget::findLinkAt(const QTextCursor &cursor, + Utils::ProcessLinkCallback &&callback, + bool resolveTarget, + bool inNextSplit) { + emit requestLinkAt(cursor, callback, resolveTarget, inNextSplit); } bool TextEditorWidget::openLink(const Utils::Link &link, bool inNextSplit) @@ -8394,6 +8398,15 @@ BaseTextEditor *BaseTextEditor::currentTextEditor() return qobject_cast<BaseTextEditor *>(EditorManager::currentEditor()); } +BaseTextEditor *BaseTextEditor::textEditorForDocument(TextDocument *textDocument) +{ + for (IEditor *editor : Core::DocumentModel::editorsForDocument(textDocument)) { + if (auto textEditor = qobject_cast<BaseTextEditor *>(editor)) + return textEditor; + } + return nullptr; +} + TextEditorWidget *BaseTextEditor::editorWidget() const { QTC_ASSERT(qobject_cast<TextEditorWidget *>(m_widget.data()), return nullptr); diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h index 8025f41147..e11517c530 100644 --- a/src/plugins/texteditor/texteditor.h +++ b/src/plugins/texteditor/texteditor.h @@ -108,6 +108,7 @@ public: virtual void finalizeInitialization() {} static BaseTextEditor *currentTextEditor(); + static BaseTextEditor *textEditorForDocument(TextDocument *textDocument); TextEditorWidget *editorWidget() const; TextDocument *textDocument() const; @@ -472,6 +473,9 @@ signals: void requestBlockUpdate(const QTextBlock &); + void requestLinkAt(const QTextCursor &cursor, Utils::ProcessLinkCallback &callback, + bool resolveTarget, bool inNextSplit); + protected: QTextBlock blockForVisibleRow(int row) const; QTextBlock blockForVerticalOffset(int offset) const; diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index aeacf4bbd8..14affe9fae 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -21,6 +21,7 @@ SUBDIRS += \ filesearch \ mapreduce \ runextensions \ + languageserverprotocol \ sdktool \ valgrind diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs index 0d5eb31edd..3521115470 100644 --- a/tests/auto/auto.qbs +++ b/tests/auto/auto.qbs @@ -16,15 +16,16 @@ Project { "filesearch/filesearch.qbs", "generichighlighter/generichighlighter.qbs", "json/json.qbs", + "languageserverprotocol/languageserverprotocol.qbs", "profilewriter/profilewriter.qbs", "qml/qml.qbs", "qtcprocess/qtcprocess.qbs", "runextensions/runextensions.qbs", "sdktool/sdktool.qbs", + "toolchaincache/toolchaincache.qbs", "tracing/tracing.qbs", "treeviewfind/treeviewfind.qbs", - "toolchaincache/toolchaincache.qbs", "utils/utils.qbs", - "valgrind/valgrind.qbs" + "valgrind/valgrind.qbs", ].concat(project.additionalAutotests) } diff --git a/tests/auto/languageserverprotocol/languageserverprotocol.pro b/tests/auto/languageserverprotocol/languageserverprotocol.pro new file mode 100644 index 0000000000..eafc4478ef --- /dev/null +++ b/tests/auto/languageserverprotocol/languageserverprotocol.pro @@ -0,0 +1,5 @@ +QTC_LIB_DEPENDS += utils +QTC_LIB_DEPENDS += languageserverprotocol +include(../qttest.pri) + +SOURCES += tst_languageserverprotocol.cpp diff --git a/tests/auto/languageserverprotocol/languageserverprotocol.qbs b/tests/auto/languageserverprotocol/languageserverprotocol.qbs new file mode 100644 index 0000000000..bb226fed0f --- /dev/null +++ b/tests/auto/languageserverprotocol/languageserverprotocol.qbs @@ -0,0 +1,8 @@ +import qbs + +QtcAutotest { + name: "Language Server Protocol autotest" + Depends { name: "Utils" } + Depends { name: "LanguageServerProtocol" } + files: "tst_languageserverprotocol.cpp" +} diff --git a/tests/auto/languageserverprotocol/tst_languageserverprotocol.cpp b/tests/auto/languageserverprotocol/tst_languageserverprotocol.cpp new file mode 100644 index 0000000000..3a7a83c7bf --- /dev/null +++ b/tests/auto/languageserverprotocol/tst_languageserverprotocol.cpp @@ -0,0 +1,653 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include <languageserverprotocol/basemessage.h> +#include <languageserverprotocol/jsonobject.h> +#include <languageserverprotocol/jsonrpcmessages.h> +#include <utils/mimetypes/mimetype.h> +#include <utils/mimetypes/mimedatabase.h> + +#include <QtTest> + +using namespace LanguageServerProtocol; + +Q_DECLARE_METATYPE(QTextCodec *) +Q_DECLARE_METATYPE(BaseMessage) +Q_DECLARE_METATYPE(JsonRpcMessage) +Q_DECLARE_METATYPE(DocumentUri) + +class tst_LanguageServerProtocol : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + + void baseMessageParse_data(); + void baseMessageParse(); + + void baseMessageToData_data(); + void baseMessageToData(); + + void fromJsonValue(); + + void toJsonObject_data(); + void toJsonObject(); + + void jsonMessageToBaseMessage_data(); + void jsonMessageToBaseMessage(); + + void jsonObject(); + + void documentUri_data(); + void documentUri(); + +private: + QByteArray defaultMimeType; + QTextCodec *defaultCodec = nullptr; +}; + +void tst_LanguageServerProtocol::initTestCase() +{ + defaultMimeType = JsonRpcMessageHandler::jsonRpcMimeType(); + defaultCodec = QTextCodec::codecForName("utf-8"); +} + +void tst_LanguageServerProtocol::baseMessageParse_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QByteArray>("mimeType"); + QTest::addColumn<QByteArray>("content"); + QTest::addColumn<bool>("complete"); + QTest::addColumn<bool>("valid"); + QTest::addColumn<bool>("error"); + QTest::addColumn<QTextCodec*>("codec"); + QTest::addColumn<BaseMessage>("partial"); + + QTest::newRow("empty content") + << QByteArray("") + << defaultMimeType + << QByteArray() + << false // complete + << false // valid + << false // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("garbage") + << QByteArray("garbage\r\n") + << defaultMimeType + << QByteArray() + << false // complete + << false // valid + << true // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("minimum message") + << QByteArray("Content-Length: 0\r\n" + "\r\n") + << defaultMimeType + << QByteArray() + << true // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("minimum message with content") + << QByteArray("Content-Length: 3\r\n" + "\r\n" + "foo") + << defaultMimeType + << QByteArray("foo") + << true // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("minimum message with incomplete content") + << QByteArray("Content-Length: 6\r\n" + "\r\n" + "foo") + << defaultMimeType + << QByteArray("foo") + << false // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("default mime type") + << QByteArray("Content-Length: 0\r\n" + "Content-Type: application/vscode-jsonrpc\r\n" + "\r\n") + << defaultMimeType + << QByteArray() + << true // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("default mime type and charset") + << QByteArray("Content-Length: 0\r\n" + "Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n" + "\r\n") + << defaultMimeType + << QByteArray() + << true // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(); + + // For backwards compatibility it is highly recommended that a client and a server + // treats the string utf8 as utf-8. (lsp documentation) + QTest::newRow("default mime type and old charset") + << QByteArray("Content-Length: 0\r\n" + "Content-Type: application/vscode-jsonrpc; charset=utf8\r\n" + "\r\n") + << defaultMimeType + << QByteArray() + << true // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("non default mime type with default charset") + << QByteArray("Content-Length: 0\r\n" + "Content-Type: text/x-python\r\n" + "\r\n") + << QByteArray("text/x-python") + << QByteArray() + << true // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("non default mime type and charset") + << QByteArray("Content-Length: 0\r\n" + "Content-Type: text/x-python; charset=iso-8859-1\r\n" + "\r\n") + << QByteArray("text/x-python") + << QByteArray() + << true // complete + << true // valid + << false // errorMessage + << QTextCodec::codecForName("iso-8859-1") + << BaseMessage(); + + QTest::newRow("data after message") + << QByteArray("Content-Length: 3\r\n" + "\r\n" + "foobar") + << defaultMimeType + << QByteArray("foo") + << true // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("Unexpected header field") + << QByteArray("Content-Length: 6\r\n" + "Foo: bar\r\n" + "\r\n" + "foobar") + << defaultMimeType + << QByteArray("foobar") + << true // complete + << true // valid + << true // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("Unexpected header line") + << QByteArray("Content-Length: 6\r\n" + "Foobar\r\n" + "\r\n" + "foobar") + << defaultMimeType + << QByteArray("foobar") + << true // complete + << true // valid + << true // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("Unknown mimeType") + << QByteArray("Content-Length: 6\r\n" + "Content-Type: foobar\r\n" + "\r\n" + "foobar") + << QByteArray("foobar") + << QByteArray("foobar") + << true // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("Unknown charset") + << QByteArray("Content-Length: 6\r\n" + "Content-Type: application/vscode-jsonrpc; charset=foobar\r\n" + "\r\n" + "foobar") + << defaultMimeType + << QByteArray("foobar") + << true // complete + << true // valid + << true // errorMessage + << defaultCodec + << BaseMessage(); + + QTest::newRow("completing content") + << QByteArray("bar") + << defaultMimeType + << QByteArray("foobar") + << true // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(defaultMimeType, "foo", 6, defaultCodec); + + QTest::newRow("still incomplet content") + << QByteArray("bar") + << defaultMimeType + << QByteArray("foobar") + << false // complete + << true // valid + << false // errorMessage + << defaultCodec + << BaseMessage(defaultMimeType, "foo", 7, defaultCodec); +} + +void tst_LanguageServerProtocol::baseMessageParse() +{ + QFETCH(QByteArray, data); + QFETCH(QByteArray, mimeType); + QFETCH(QByteArray, content); + QFETCH(bool, complete); + QFETCH(bool, valid); + QFETCH(QTextCodec *, codec); + QFETCH(bool, error); + QFETCH(BaseMessage, partial); + + QBuffer buffer(&data); + buffer.open(QIODevice::ReadWrite); + QString parseError; + BaseMessage::parse(&buffer, parseError, partial); + + if (!parseError.isEmpty() && !error) // show message if there is an error message we do not expect + QWARN(parseError.toLatin1()); + QCOMPARE(!parseError.isEmpty(), error); + QCOMPARE(partial.content, content); + QCOMPARE(partial.isValid(), valid); + QCOMPARE(partial.isComplete(), complete); + QCOMPARE(partial.mimeType, mimeType); + QVERIFY(partial.codec != nullptr); + QVERIFY(codec != nullptr); + QCOMPARE(partial.codec->mibEnum(), codec->mibEnum()); +} + +void tst_LanguageServerProtocol::baseMessageToData_data() +{ + QTest::addColumn<BaseMessage>("message"); + QTest::addColumn<QByteArray>("data"); + + QTest::newRow("empty") + << BaseMessage(defaultMimeType, "") + << QByteArray("Content-Length: 0\r\n" + "\r\n"); + + QTest::newRow("content") + << BaseMessage(defaultMimeType, "foo") + << QByteArray("Content-Length: 3\r\n" + "\r\n" + "foo"); + + QTest::newRow("custom mime type") + << BaseMessage("text/x-python", "") + << QByteArray("Content-Length: 0\r\n" + "Content-Type: text/x-python; charset=UTF-8\r\n" + "\r\n"); + + QTextCodec *codec = QTextCodec::codecForName("iso-8859-1"); + QTest::newRow("custom mime type and codec") + << BaseMessage("text/x-python", "", 0, codec) + << QByteArray("Content-Length: 0\r\n" + "Content-Type: text/x-python; charset=ISO-8859-1\r\n" + "\r\n"); + + QTest::newRow("custom codec") + << BaseMessage(defaultMimeType, "", 0, codec) + << QByteArray("Content-Length: 0\r\n" + "Content-Type: application/vscode-jsonrpc; charset=ISO-8859-1\r\n" + "\r\n"); +} + +void tst_LanguageServerProtocol::baseMessageToData() +{ + QFETCH(BaseMessage, message); + QFETCH(QByteArray, data); + + QCOMPARE(message.toData(), data); +} + +void tst_LanguageServerProtocol::fromJsonValue() +{ + const QString strVal("foobar"); + QCOMPARE(LanguageServerProtocol::fromJsonValue<QString>(QJsonValue(strVal)), strVal); + const int intVal = 42; + QCOMPARE(LanguageServerProtocol::fromJsonValue<int>(QJsonValue(intVal)), intVal); + const double doubleVal = 4.2; + QCOMPARE(LanguageServerProtocol::fromJsonValue<double>(QJsonValue(doubleVal)), doubleVal); + const bool boolVal = false; + QCOMPARE(LanguageServerProtocol::fromJsonValue<bool>(QJsonValue(boolVal)), boolVal); + const QJsonArray array = QJsonArray::fromStringList({"foo", "bar"}); + QCOMPARE(LanguageServerProtocol::fromJsonValue<QJsonArray>(array), array); + QJsonObject object; + object.insert("asd", "foo"); + QCOMPARE(LanguageServerProtocol::fromJsonValue<QJsonObject>(object), object); +} + +void tst_LanguageServerProtocol::toJsonObject_data() +{ + QTest::addColumn<QByteArray>("content"); + QTest::addColumn<QTextCodec *>("codec"); + QTest::addColumn<bool>("error"); + QTest::addColumn<QJsonObject>("expected"); + + QJsonObject tstObject; + tstObject.insert("jsonrpc", "2.0"); + + QTest::newRow("empty") + << QByteArray("") + << defaultCodec + << false + << QJsonObject(); + + QTest::newRow("garbage") + << QByteArray("foobar") + << defaultCodec + << true + << QJsonObject(); + + QTest::newRow("empty object") + << QByteArray("{}") + << defaultCodec + << false + << QJsonObject(); + + QTest::newRow("object") + << QByteArray(R"({"jsonrpc": "2.0"})") + << defaultCodec + << false + << tstObject; + + QTextCodec *codec = QTextCodec::codecForName("iso-8859-1"); + QJsonObject tstCodecObject; + tstCodecObject.insert("foo", QString::fromLatin1("b\xe4r")); + QTest::newRow("object88591") + << QByteArray("{\"foo\": \"b\xe4r\"}") + << codec + << false + << tstCodecObject; + + QTest::newRow("object and garbage") + << QByteArray(R"({"jsonrpc": "2.0"} foobar)") + << defaultCodec + << true + << QJsonObject(); // TODO can be improved + + QTest::newRow("empty array") + << QByteArray("[]") + << defaultCodec + << true + << QJsonObject(); + + QTest::newRow("null") + << QByteArray("null") + << defaultCodec + << true + << QJsonObject(); +} + +void tst_LanguageServerProtocol::toJsonObject() +{ + QFETCH(QByteArray, content); + QFETCH(QTextCodec *, codec); + QFETCH(bool, error); + QFETCH(QJsonObject, expected); + + QString parseError; + const QJsonObject object = JsonRpcMessageHandler::toJsonObject(content, codec, parseError); + + if (!error && !parseError.isEmpty()) + QFAIL(parseError.toLocal8Bit().data()); + QCOMPARE(object, expected); + QCOMPARE(!parseError.isEmpty(), error); +} + +void tst_LanguageServerProtocol::jsonMessageToBaseMessage_data() +{ + QTest::addColumn<JsonRpcMessage>("jsonMessage"); + QTest::addColumn<BaseMessage>("baseMessage"); + + QTest::newRow("empty object") << JsonRpcMessage(QJsonObject()) + << BaseMessage(JsonRpcMessageHandler::jsonRpcMimeType(), + "{}"); + + QTest::newRow("key value pair") << JsonRpcMessage({{"key", "value"}}) + << BaseMessage(JsonRpcMessageHandler::jsonRpcMimeType(), + R"({"key":"value"})"); +} + +void tst_LanguageServerProtocol::jsonMessageToBaseMessage() +{ + QFETCH(JsonRpcMessage, jsonMessage); + QFETCH(BaseMessage, baseMessage); + + QCOMPARE(jsonMessage.toBaseMessage(), baseMessage); +} + +class JsonTestObject : public JsonObject +{ +public: + using JsonObject::JsonObject; + using JsonObject::insert; + using JsonObject::value; + using JsonObject::contains; + using JsonObject::find; + using JsonObject::end; + using JsonObject::remove; + using JsonObject::keys; + using JsonObject::typedValue; + using JsonObject::optionalValue; + using JsonObject::clientValue; + using JsonObject::optionalClientValue; + using JsonObject::array; + using JsonObject::optionalArray; + using JsonObject::clientArray; + using JsonObject::optionalClientArray; + using JsonObject::insertArray; + using JsonObject::checkKey; + using JsonObject::valueTypeString; + using JsonObject::check; + using JsonObject::checkType; + using JsonObject::checkVal; + using JsonObject::checkArray; + using JsonObject::checkOptional; + using JsonObject::checkOptionalArray; + using JsonObject::errorString; + using JsonObject::operator==; +}; + +void tst_LanguageServerProtocol::jsonObject() +{ + JsonTestObject obj; + + obj.insert("integer", 42); + obj.insert("double", 42.42); + obj.insert("bool", false); + obj.insert("null", QJsonValue::Null); + obj.insert("string", "foobar"); + obj.insertArray("strings", QStringList{"foo", "bar"}); + const JsonTestObject innerObj(obj); + obj.insert("object", innerObj); + + QCOMPARE(obj.value("integer"), QJsonValue(42)); + QCOMPARE(obj.value("double"), QJsonValue(42.42)); + QCOMPARE(obj.value("bool"), QJsonValue(false)); + QCOMPARE(obj.value("null"), QJsonValue(QJsonValue::Null)); + QCOMPARE(obj.value("string"), QJsonValue("foobar")); + QCOMPARE(obj.value("strings"), QJsonValue(QJsonArray({"foo", "bar"}))); + QCOMPARE(obj.value("object"), QJsonValue(QJsonObject(innerObj))); + + QCOMPARE(obj.typedValue<int>("integer"), 42); + QCOMPARE(obj.typedValue<double>("double"), 42.42); + QCOMPARE(obj.typedValue<bool>("bool"), false); + QCOMPARE(obj.typedValue<QString>("string"), QString("foobar")); + QCOMPARE(obj.typedValue<JsonTestObject>("object"), innerObj); + + QVERIFY(!obj.optionalValue<int>("doesNotExist").has_value()); + QVERIFY(obj.optionalValue<int>("integer").has_value()); + QCOMPARE(obj.optionalValue<int>("integer").value_or(0), 42); + + QVERIFY(obj.clientValue<int>("null").isNull()); + QVERIFY(!obj.clientValue<int>("integer").isNull()); + QCOMPARE(obj.clientValue<int>("integer").value(), 42); + + QVERIFY(!obj.optionalClientValue<int>("doesNotExist").has_value()); + QVERIFY(obj.optionalClientValue<int>("null").has_value()); + QVERIFY(obj.optionalClientValue<int>("null").value().isNull()); + QVERIFY(obj.optionalClientValue<int>("integer").has_value()); + QVERIFY(!obj.optionalClientValue<int>("integer").value().isNull()); + QCOMPARE(obj.optionalClientValue<int>("integer").value().value(0), 42); + + QCOMPARE(obj.array<QString>("strings"), QList<QString>({"foo", "bar"})); + + QVERIFY(!obj.optionalArray<QString>("doesNotExist").has_value()); + QVERIFY(obj.optionalArray<QString>("strings").has_value()); + QCOMPARE(obj.optionalArray<QString>("strings").value_or(QList<QString>()), + QList<QString>({"foo", "bar"})); + + QVERIFY(obj.clientArray<QString>("null").isNull()); + QVERIFY(!obj.clientArray<QString>("strings").isNull()); + QCOMPARE(obj.clientArray<QString>("strings").toList(), QList<QString>({"foo", "bar"})); + + QVERIFY(!obj.optionalClientArray<QString>("doesNotExist").has_value()); + QVERIFY(obj.optionalClientArray<QString>("null").has_value()); + QVERIFY(obj.optionalClientArray<QString>("null").value().isNull()); + QVERIFY(obj.optionalClientArray<QString>("strings").has_value()); + QVERIFY(!obj.optionalClientArray<QString>("strings").value().isNull()); + QCOMPARE(obj.optionalClientArray<QString>("strings").value().toList(), + QList<QString>({"foo", "bar"})); + + QStringList errorHierarchy; + QVERIFY(!obj.check<int>(&errorHierarchy, "doesNotExist")); + QCOMPARE(errorHierarchy, QStringList({obj.errorString(QJsonValue::Double, QJsonValue::Undefined), "doesNotExist"})); + errorHierarchy.clear(); + + QVERIFY(!obj.check<int>(&errorHierarchy, "bool")); + QCOMPARE(errorHierarchy, QStringList({obj.errorString(QJsonValue::Double, QJsonValue::Bool), "bool"})); + errorHierarchy.clear(); + + QVERIFY(obj.check<int>(&errorHierarchy, "integer")); + QVERIFY(errorHierarchy.isEmpty()); + QVERIFY(obj.check<double>(&errorHierarchy, "double")); + QVERIFY(errorHierarchy.isEmpty()); + QVERIFY(obj.check<bool>(&errorHierarchy, "bool")); + QVERIFY(errorHierarchy.isEmpty()); + QVERIFY(obj.check<std::nullptr_t>(&errorHierarchy, "null")); + QVERIFY(errorHierarchy.isEmpty()); + QVERIFY(obj.check<QString>(&errorHierarchy, "string")); + QVERIFY(errorHierarchy.isEmpty()); +} + +void tst_LanguageServerProtocol::documentUri_data() +{ + QTest::addColumn<DocumentUri>("uri"); + QTest::addColumn<bool>("isValid"); + QTest::addColumn<Utils::FileName>("fileName"); + QTest::addColumn<QString>("string"); + + const QString filePrefix("file:///"); + + QTest::newRow("empty uri") + << DocumentUri() + << false + << Utils::FileName() + << QString(); + + + QTest::newRow("home dir") + << DocumentUri::fromFileName(Utils::FileName::fromString(QDir::homePath())) + << true + << Utils::FileName::fromUserInput(QDir::homePath()) + << QString(filePrefix + QDir::homePath()); + + const QString argv0 = qApp->arguments().first(); + const auto argv0FileName = Utils::FileName::fromUserInput(argv0); + QTest::newRow("argv0 file name") + << DocumentUri::fromFileName(argv0FileName) + << true + << argv0FileName + << QString(filePrefix + QDir::fromNativeSeparators(argv0)); + + QTest::newRow("http") + << DocumentUri::fromProtocol("https://www.qt.io/") + << true + << Utils::FileName() + << "https://www.qt.io/"; + + const QString winUserPercent("file:///C%3A/Users/"); + const QString winUser("C:\\Users\\"); + QTest::newRow("percent encoding") + << DocumentUri::fromProtocol(winUserPercent) + << true + << Utils::FileName::fromUserInput(winUser) + << QString(filePrefix + QDir::fromNativeSeparators(winUser)); +} + +void tst_LanguageServerProtocol::documentUri() +{ + QFETCH(DocumentUri, uri); + QFETCH(bool, isValid); + QFETCH(Utils::FileName, fileName); + QFETCH(QString, string); + + QCOMPARE(uri.isValid(), isValid); + QCOMPARE(uri.toFileName(), fileName); + QCOMPARE(uri.toString(), string); +} + +QTEST_MAIN(tst_LanguageServerProtocol) + +#include "tst_languageserverprotocol.moc" |