LanguageClient: add hover handler

Change-Id: Iddf30828ef26a157ab935d0abe708087ab123dd6
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2019-05-15 11:19:31 +02:00
parent e04339b1e8
commit c874f07d53
15 changed files with 346 additions and 53 deletions

View File

@@ -58,19 +58,14 @@ 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);
}));
return optionalArray<MarkupKind>(documentationFormatKey);
}
void
TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemCapbilities::
setDocumentationFormat(const QList<MarkupKind> &documentationFormat)
{
insert(documentationFormatKey, enumArrayToJsonArray<MarkupKind>(documentationFormat));
insertArray(documentationFormatKey, documentationFormat);
}
TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities::CompletionItemKindCapabilities()
@@ -107,36 +102,26 @@ setValueSet(const QList<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);
}));
return optionalArray<MarkupKind>(contentFormatKey);
}
void TextDocumentClientCapabilities::HoverCapabilities::setContentFormat(const QList<MarkupKind> &contentFormat)
{
insert(contentFormatKey, enumArrayToJsonArray<MarkupKind>(contentFormat));
insertArray(contentFormatKey, 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);
}));
return optionalArray<MarkupKind>(documentationFormatKey);
}
void
TextDocumentClientCapabilities::SignatureHelpCapabilities::SignatureInformationCapabilities::
setDocumentationFormat(const QList<MarkupKind> &documentationFormat)
{
insert(documentationFormatKey, enumArrayToJsonArray<MarkupKind>(documentationFormat));
insertArray(documentationFormatKey, documentationFormat);
}
InitializeParams::InitializeParams()

View File

@@ -69,6 +69,7 @@ constexpr char contentChangesKey[] = "contentChanges";
constexpr char contentCharsetName[] = "charset";
constexpr char contentFormatKey[] = "contentFormat";
constexpr char contentKey[] = "value";
constexpr char contentsKey[] = "contents";
constexpr char contentLengthFieldName[] = "Content-Length";
constexpr char contentTypeFieldName[] = "Content-Type";
constexpr char contextKey[] = "context";

View File

@@ -49,19 +49,19 @@ constexpr const char DocumentOnTypeFormattingRequest::methodName[];
constexpr const char RenameRequest::methodName[];
constexpr const char SignatureHelpRequest::methodName[];
MarkedString LanguageServerProtocol::Hover::content() const
HoverContent LanguageServerProtocol::Hover::content() const
{
return MarkedString(value(contentKey));
return HoverContent(value(contentsKey));
}
void Hover::setContent(const MarkedString &content)
void Hover::setContent(const HoverContent &content)
{
if (auto val = Utils::get_if<MarkedLanguageString>(&content))
insert(contentKey, *val);
if (auto val = Utils::get_if<MarkedString>(&content))
insert(contentsKey, *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());
insert(contentsKey, *val);
else if (auto val = Utils::get_if<QList<MarkedString>>(&content))
insert(contentsKey, LanguageClientArray<MarkedString>(*val).toJson());
else
QTC_ASSERT_STRING("LanguageClient Using unknown type Hover::setContent");
}
@@ -333,32 +333,53 @@ DocumentHighlightsResult::DocumentHighlightsResult(const QJsonValue &value)
}
MarkedString::MarkedString(const QJsonValue &value)
{
if (value.isObject()) {
MarkedLanguageString string(value.toObject());
if (string.isValid(nullptr))
emplace<MarkedLanguageString>(string);
} else if (value.isString()) {
emplace<QString>(value.toString());
}
}
LanguageServerProtocol::MarkedString::operator const QJsonValue() const
{
if (auto val = Utils::get_if<QString>(this))
return *val;
if (auto val = Utils::get_if<MarkedLanguageString>(this))
return QJsonValue(*val);
return {};
}
HoverContent::HoverContent(const QJsonValue &value)
{
if (value.isArray()) {
emplace<QList<MarkedLanguageString>>(
LanguageClientArray<MarkedLanguageString>(value).toList());
emplace<QList<MarkedString>>(LanguageClientArray<MarkedString>(value).toList());
} else if (value.isObject()) {
const QJsonObject &object = value.toObject();
MarkedLanguageString markedLanguageString(object);
if (markedLanguageString.isValid(nullptr))
emplace<MarkedLanguageString>(markedLanguageString);
emplace<MarkedString>(markedLanguageString);
else
emplace<MarkupContent>(MarkupContent(object));
} else if (value.isString()) {
emplace<MarkedString>(MarkedString(value.toString()));
}
}
bool MarkedString::isValid(QStringList *errorHierarchy) const
bool HoverContent::isValid(QStringList *errorHierarchy) const
{
if (Utils::holds_alternative<MarkedLanguageString>(*this)
if (Utils::holds_alternative<MarkedString>(*this)
|| Utils::holds_alternative<MarkupContent>(*this)
|| Utils::holds_alternative<QList<MarkedLanguageString>>(*this)) {
|| Utils::holds_alternative<QList<MarkedString>>(*this)) {
return true;
}
if (errorHierarchy) {
*errorHierarchy << QCoreApplication::translate(
"LanguageServerProtocol::MarkedString",
"MarkedString should be either MarkedLanguageString, "
"MarkupContent, or QList<MarkedLanguageString>.");
"LanguageServerProtocol::HoverContent",
"HoverContent should be either MarkedString, "
"MarkupContent, or QList<MarkedString>.");
}
return false;
}

View File

@@ -58,14 +58,31 @@ public:
{ return check<QString>(error, languageKey) && check<QString>(error, valueKey); }
};
class MarkedString : public Utils::variant<MarkedLanguageString, QList<MarkedLanguageString>, MarkupContent>
class LANGUAGESERVERPROTOCOL_EXPORT MarkedString
: public Utils::variant<QString, MarkedLanguageString>
{
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 MarkedLanguageString &other)
: variant(other)
{}
explicit MarkedString(const QString &other)
: variant(other)
{}
explicit MarkedString(const QJsonValue &value);
operator const QJsonValue() const;
};
class LANGUAGESERVERPROTOCOL_EXPORT HoverContent
: public Utils::variant<MarkedString, QList<MarkedString>, MarkupContent>
{
public:
HoverContent() = default;
explicit HoverContent(const MarkedString &other) : variant(other) {}
explicit HoverContent(const QList<MarkedString> &other) : variant(other) {}
explicit HoverContent(const MarkupContent &other) : variant(other) {}
explicit HoverContent(const QJsonValue &value);
bool isValid(QStringList *errorHierarchy) const;
};
@@ -74,15 +91,15 @@ class LANGUAGESERVERPROTOCOL_EXPORT Hover : public JsonObject
public:
using JsonObject::JsonObject;
MarkedString content() const;
void setContent(const MarkedString &content);
HoverContent content() const;
void setContent(const HoverContent &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); }
{ return check<HoverContent>(error, contentsKey) && checkOptional<Range>(error, rangeKey); }
};
class LANGUAGESERVERPROTOCOL_EXPORT HoverRequest

View File

@@ -415,4 +415,20 @@ Utils::FileName DocumentUri::toFileName() const
: Utils::FileName();
}
MarkupKind::MarkupKind(const QJsonValue &value)
{
m_value = value.toString() == "markdown" ? markdown : plaintext;
}
LanguageServerProtocol::MarkupKind::operator const QJsonValue() const
{
switch (m_value) {
case MarkupKind::markdown:
return "markdown";
case MarkupKind::plaintext:
return "plaintext";
}
return {};
}
} // namespace LanguageServerProtocol

View File

@@ -378,10 +378,24 @@ public:
bool isValid(QStringList *error) const override;
};
enum class MarkupKind
class LANGUAGESERVERPROTOCOL_EXPORT MarkupKind
{
plaintext,
markdown,
public:
enum Value { plaintext, markdown };
MarkupKind() = default;
MarkupKind(const Value value)
: m_value(value)
{}
MarkupKind(const QJsonValue &value);
operator const QJsonValue() const;
Value value() const { return m_value; }
bool operator==(const Value &value) const { return m_value == value; }
bool isValid(void *) const { return true; }
private:
Value m_value = plaintext;
};
class LANGUAGESERVERPROTOCOL_EXPORT MarkupContent : public JsonObject
@@ -390,15 +404,15 @@ 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)); }
MarkupKind kind() const { return value(kindKey); }
void setKind(MarkupKind kind) { insert(kindKey, 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); }
{ return check<MarkupKind>(error, kindKey) && check<QString>(error, contentKey); }
};
class LANGUAGESERVERPROTOCOL_EXPORT MarkupOrString : public Utils::variant<QString, MarkupContent>

View File

@@ -96,6 +96,7 @@ Client::Client(BaseClientInterface *clientInterface)
, m_quickFixProvider(this)
, m_clientInterface(clientInterface)
, m_documentSymbolCache(this)
, m_hoverHandler(this)
{
m_contentHandler.insert(JsonRpcMessageHandler::jsonRpcMimeType(),
&JsonRpcMessageHandler::parseContent);
@@ -129,6 +130,7 @@ Client::~Client()
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor)) {
TextEditorWidget *widget = textEditor->editorWidget();
widget->setRefactorMarkers(RefactorMarker::filterOutType(widget->refactorMarkers(), id()));
widget->removeHoverHandler(&m_hoverHandler);
}
}
for (const DocumentUri &uri : m_diagnostics.keys())
@@ -204,6 +206,12 @@ static ClientCapabilities generateClientCapabilities()
CodeActionKind(QList<QString>{"*"}));
codeActionCapabilities.setCodeActionLiteralSupport(literalSupport);
documentCapabilities.setCodeAction(codeActionCapabilities);
TextDocumentClientCapabilities::HoverCapabilities hover;
hover.setContentFormat({MarkupKind::plaintext});
hover.setDynamicRegistration(true);
documentCapabilities.setHover(hover);
documentCapabilities.setReferences(allowDynamicRegistration);
documentCapabilities.setDocumentHighlight(allowDynamicRegistration);
documentCapabilities.setDefinition(allowDynamicRegistration);
@@ -915,6 +923,11 @@ DocumentSymbolCache *Client::documentSymbolCache()
return &m_documentSymbolCache;
}
HoverHandler *Client::hoverHandler()
{
return &m_hoverHandler;
}
void Client::log(const ShowMessageParams &message,
Core::MessageManager::PrintToOutputPaneFlag flag)
{
@@ -1113,6 +1126,10 @@ void Client::intializeCallback(const InitializeRequest::Response &initResponse)
updateEditorToolBar(editor);
}
}
for (Core::IEditor *editor : Core::DocumentModel::editorsForOpenedDocuments()) {
if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor))
textEditor->editorWidget()->addHoverHandler(&m_hoverHandler);
}
emit initialized(m_serverCapabilities);
}

View File

@@ -30,6 +30,7 @@
#include "languageclientcompletionassist.h"
#include "languageclientquickfix.h"
#include "languageclientsettings.h"
#include "languageclienthoverhandler.h"
#include <coreplugin/id.h>
#include <coreplugin/messagemanager.h>
@@ -155,6 +156,7 @@ public:
const DynamicCapabilities &dynamicCapabilities() const;
const BaseClientInterface *clientInterface() const;
DocumentSymbolCache *documentSymbolCache();
HoverHandler *hoverHandler();
signals:
void initialized(LanguageServerProtocol::ServerCapabilities capabilities);
@@ -205,6 +207,7 @@ private:
QScopedPointer<BaseClientInterface> m_clientInterface;
QMap<LanguageServerProtocol::DocumentUri, QList<TextMark *>> m_diagnostics;
DocumentSymbolCache m_documentSymbolCache;
HoverHandler m_hoverHandler;
const ProjectExplorer::Project *m_project = nullptr;
};

View File

@@ -8,6 +8,7 @@ HEADERS += \
dynamiccapabilities.h \
languageclient_global.h \
languageclientcompletionassist.h \
languageclienthoverhandler.h \
languageclientinterface.h \
languageclientmanager.h \
languageclientoutline.h \
@@ -23,6 +24,7 @@ SOURCES += \
documentsymbolcache.cpp \
dynamiccapabilities.cpp \
languageclientcompletionassist.cpp \
languageclienthoverhandler.cpp \
languageclientinterface.cpp \
languageclientmanager.cpp \
languageclientoutline.cpp \

View File

@@ -22,6 +22,8 @@ QtcPlugin {
"dynamiccapabilities.h",
"languageclient.qrc",
"languageclient_global.h",
"languageclienthoverhandler.cpp",
"languageclienthoverhandler.h",
"languageclientinterface.cpp",
"languageclientinterface.h",
"languageclientcompletionassist.cpp",

View File

@@ -0,0 +1,148 @@
/****************************************************************************
**
** Copyright (C) 2019 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 "languageclienthoverhandler.h"
#include "client.h"
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <utils/mimetypes/mimedatabase.h>
#include <utils/qtcassert.h>
#include <utils/tooltip/tooltip.h>
using namespace LanguageServerProtocol;
namespace LanguageClient {
HoverHandler::HoverHandler(Client *client)
: m_client(client)
{}
HoverHandler::~HoverHandler()
{
abort();
}
void HoverHandler::abort()
{
if (m_client && m_client->reachable() && m_currentRequest.has_value())
m_client->cancelRequest(*m_currentRequest);
m_currentRequest.reset();
}
void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget,
int pos,
TextEditor::BaseHoverHandler::ReportPriority report)
{
if (m_currentRequest.has_value())
abort();
if (m_client.isNull()
|| !m_client->documentOpen(editorWidget->textDocument())
|| !m_client->capabilities().hoverProvider().value_or(false)) {
report(Priority_None);
return;
}
bool sendMessage = m_client->capabilities().hoverProvider().value_or(false);
if (Utils::optional<bool> registered = m_client->dynamicCapabilities().isRegistered(
HoverRequest::methodName)) {
sendMessage = registered.value();
if (sendMessage) {
const TextDocumentRegistrationOptions option(
m_client->dynamicCapabilities().option(HoverRequest::methodName).toObject());
if (option.isValid(nullptr)) {
sendMessage = option.filterApplies(editorWidget->textDocument()->filePath(),
Utils::mimeTypeForName(
editorWidget->textDocument()->mimeType()));
}
}
}
if (!sendMessage) {
report(Priority_None);
return;
}
m_report = report;
auto uri = DocumentUri::fromFileName(editorWidget->textDocument()->filePath());
QTextCursor cursor = editorWidget->textCursor();
cursor.setPosition(pos);
TextDocumentPositionParams params(uri, Position(cursor));
HoverRequest request(params);
request.setResponseCallback(
[this](const HoverRequest::Response &response) { handleResponse(response); });
m_client->sendContent(request);
}
void HoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point)
{
if (toolTip().isEmpty())
Utils::ToolTip::hide();
else
Utils::ToolTip::show(point, toolTip(), editorWidget);
}
void HoverHandler::handleResponse(const HoverRequest::Response &response)
{
m_currentRequest.reset();
if (Utils::optional<HoverRequest::Response::Error> error = response.error()) {
if (m_client)
m_client->log(error.value());
}
if (Utils::optional<Hover> result = response.result())
setContent(result.value().content());
m_report(priority());
}
static QString toolTipForMarkedStrings(const QList<MarkedString> &markedStrings)
{
QString tooltip;
for (const MarkedString &markedString : markedStrings) {
if (!tooltip.isEmpty())
tooltip += '\n';
if (auto string = Utils::get_if<QString>(&markedString))
tooltip += *string;
else if (auto string = Utils::get_if<MarkedLanguageString>(&markedString))
tooltip += string->value() + " [" + string->language() + ']';
}
return tooltip;
}
void HoverHandler::setContent(const HoverContent &hoverContent)
{
if (auto markupContent = Utils::get_if<MarkupContent>(&hoverContent)) {
const QString &content = markupContent->content();
if (markupContent->kind() == MarkupKind::plaintext)
setToolTip(content);
else if (m_client)
m_client->log(tr("Got unsupported markup hover content: ") + content);
} else if (auto markedString = Utils::get_if<MarkedString>(&hoverContent)) {
setToolTip(toolTipForMarkedStrings({*markedString}));
} else if (auto markedStrings = Utils::get_if<QList<MarkedString>>(&hoverContent)) {
setToolTip(toolTipForMarkedStrings(*markedStrings));
}
}
} // namespace LanguageClient

View File

@@ -0,0 +1,59 @@
/****************************************************************************
**
** Copyright (C) 2019 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/languagefeatures.h>
#include <texteditor/basehoverhandler.h>
namespace LanguageClient {
class Client;
class HoverHandler : public TextEditor::BaseHoverHandler
{
Q_DECLARE_TR_FUNCTIONS(HoverHandler)
public:
explicit HoverHandler(Client *client);
~HoverHandler() override;
void abort() override;
protected:
void identifyMatch(TextEditor::TextEditorWidget *editorWidget,
int pos,
ReportPriority report) override;
void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) override;
private:
void handleResponse(const LanguageServerProtocol::HoverRequest::Response &response);
void setContent(const LanguageServerProtocol::HoverContent &content);
QPointer<Client> m_client;
Utils::optional<LanguageServerProtocol::MessageId> m_currentRequest;
TextEditor::BaseHoverHandler::ReportPriority m_report;
};
} // namespace LanguageClient

View File

@@ -362,6 +362,8 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor)
});
});
updateEditorToolBar(editor);
for (auto client : reachableClients())
widget->addHoverHandler(client->hoverHandler());
}
}
}

View File

@@ -5822,6 +5822,11 @@ void TextEditorWidget::addHoverHandler(BaseHoverHandler *handler)
d->m_hoverHandlers.append(handler);
}
void TextEditorWidget::removeHoverHandler(BaseHoverHandler *handler)
{
d->m_hoverHandlers.removeAll(handler);
}
void TextEditorWidget::extraAreaLeaveEvent(QEvent *)
{
d->extraAreaPreviousMarkTooltipRequestedLine = -1;

View File

@@ -474,6 +474,9 @@ public:
Core::HighlightScrollBarController *highlightScrollBarController() const;
void addHoverHandler(BaseHoverHandler *handler);
void removeHoverHandler(BaseHoverHandler *handler);
signals:
void assistFinished(); // Used in tests.
void readOnlyChanged();
@@ -534,8 +537,6 @@ protected:
virtual void finalizeInitializationAfterDuplication(TextEditorWidget *) {}
static QTextCursor flippedCursor(const QTextCursor &cursor);
void addHoverHandler(BaseHoverHandler *handler);
public:
QString selectedText() const;