From b0039f1ec8acf4c98196f6862a507f0e52a44845 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Fri, 17 May 2019 14:37:06 +0200 Subject: [PATCH] LanguageClient: add signature help provider Change-Id: Ia89c28b574c92802bbfda280115a50f5955f0854 Reviewed-by: Christian Stenger --- src/plugins/languageclient/CMakeLists.txt | 1 + src/plugins/languageclient/client.cpp | 9 ++ src/plugins/languageclient/client.h | 2 + src/plugins/languageclient/languageclient.pro | 2 + src/plugins/languageclient/languageclient.qbs | 2 + .../languageclientfunctionhint.cpp | 144 ++++++++++++++++++ .../languageclientfunctionhint.h | 53 +++++++ .../texteditor/codeassist/codeassistant.cpp | 37 +++-- src/plugins/texteditor/textdocument.cpp | 11 ++ src/plugins/texteditor/textdocument.h | 2 + 10 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 src/plugins/languageclient/languageclientfunctionhint.cpp create mode 100644 src/plugins/languageclient/languageclientfunctionhint.h diff --git a/src/plugins/languageclient/CMakeLists.txt b/src/plugins/languageclient/CMakeLists.txt index bb1222dd573..a94a5209f3c 100644 --- a/src/plugins/languageclient/CMakeLists.txt +++ b/src/plugins/languageclient/CMakeLists.txt @@ -7,6 +7,7 @@ add_qtc_plugin(LanguageClient dynamiccapabilities.cpp dynamiccapabilities.h languageclient.qrc languageclientcompletionassist.cpp languageclientcompletionassist.h + languageclientfunctionhint.cpp languageclientfunctionhint.h languageclienthoverhandler.cpp languageclienthoverhandler.h languageclientinterface.cpp languageclientinterface.h languageclientmanager.cpp languageclientmanager.h diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index b6eecb4fdb4..4f1333981de 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -93,6 +93,7 @@ private: Client::Client(BaseClientInterface *clientInterface) : m_id(Core::Id::fromString(QUuid::createUuid().toString())) , m_completionProvider(this) + , m_functionHintProvider(this) , m_quickFixProvider(this) , m_clientInterface(clientInterface) , m_documentSymbolCache(this) @@ -124,6 +125,7 @@ Client::~Client() for (TextDocument *document : m_resetAssistProvider.keys()) { if (document->completionAssistProvider() == &m_completionProvider) document->setCompletionAssistProvider(m_resetAssistProvider[document]); + document->setFunctionHintAssistProvider(nullptr); document->setQuickFixAssistProvider(nullptr); } for (Core::IEditor * editor : Core::DocumentModel::editorsForOpenedDocuments()) { @@ -315,6 +317,13 @@ bool Client::openDocument(Core::IDocument *document) textDocument->setCompletionAssistProvider(&m_completionProvider); } m_resetAssistProvider[textDocument] = oldCompletionProvider; + m_functionHintProvider.setTriggerCharacters( + m_serverCapabilities.signatureHelpProvider() + .value_or(ServerCapabilities::SignatureHelpOptions()) + .triggerCharacters() + .value_or(QList())); + textDocument->setCompletionAssistProvider(&m_completionProvider); + textDocument->setFunctionHintAssistProvider(&m_functionHintProvider); textDocument->setQuickFixAssistProvider(&m_quickFixProvider); connect(textDocument, &QObject::destroyed, this, [this, textDocument]{ m_resetAssistProvider.remove(textDocument); diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index 81bb101d141..0f57907a8c5 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -28,6 +28,7 @@ #include "documentsymbolcache.h" #include "dynamiccapabilities.h" #include "languageclientcompletionassist.h" +#include "languageclientfunctionhint.h" #include "languageclientquickfix.h" #include "languageclientsettings.h" #include "languageclienthoverhandler.h" @@ -200,6 +201,7 @@ private: LanguageServerProtocol::ServerCapabilities m_serverCapabilities; DynamicCapabilities m_dynamicCapabilities; LanguageClientCompletionAssistProvider m_completionProvider; + FunctionHintAssistProvider m_functionHintProvider; LanguageClientQuickFixProvider m_quickFixProvider; QMap> m_resetAssistProvider; QHash m_highlightRequests; diff --git a/src/plugins/languageclient/languageclient.pro b/src/plugins/languageclient/languageclient.pro index e3bfd5870a3..b2d39acf16c 100644 --- a/src/plugins/languageclient/languageclient.pro +++ b/src/plugins/languageclient/languageclient.pro @@ -8,6 +8,7 @@ HEADERS += \ dynamiccapabilities.h \ languageclient_global.h \ languageclientcompletionassist.h \ + languageclientfunctionhint.h \ languageclienthoverhandler.h \ languageclientinterface.h \ languageclientmanager.h \ @@ -24,6 +25,7 @@ SOURCES += \ documentsymbolcache.cpp \ dynamiccapabilities.cpp \ languageclientcompletionassist.cpp \ + languageclientfunctionhint.cpp \ languageclienthoverhandler.cpp \ languageclientinterface.cpp \ languageclientmanager.cpp \ diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs index 13cba8201b0..13ca2105703 100644 --- a/src/plugins/languageclient/languageclient.qbs +++ b/src/plugins/languageclient/languageclient.qbs @@ -24,6 +24,8 @@ QtcPlugin { "languageclient_global.h", "languageclienthoverhandler.cpp", "languageclienthoverhandler.h", + "languageclientfunctionhint.cpp", + "languageclientfunctionhint.h", "languageclientinterface.cpp", "languageclientinterface.h", "languageclientcompletionassist.cpp", diff --git a/src/plugins/languageclient/languageclientfunctionhint.cpp b/src/plugins/languageclient/languageclientfunctionhint.cpp new file mode 100644 index 00000000000..f2aed1f72c8 --- /dev/null +++ b/src/plugins/languageclient/languageclientfunctionhint.cpp @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** 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 "languageclientfunctionhint.h" +#include "client.h" + +#include +#include +#include +#include +#include + +using namespace TextEditor; +using namespace LanguageServerProtocol; + +namespace LanguageClient { + +class FunctionHintProposalModel : public IFunctionHintProposalModel +{ +public: + explicit FunctionHintProposalModel(SignatureHelp signature) + : m_sigis(signature) + {} + void reset() override {} + int size() const override + { return m_sigis.signatures().size(); } + QString text(int index) const override; + + int activeArgument(const QString &/*prefix*/) const override + { return m_sigis.activeParameter().value_or(0); } + +private: + LanguageServerProtocol::SignatureHelp m_sigis; +}; + +QString FunctionHintProposalModel::text(int index) const +{ + return m_sigis.signatures().size() > index ? m_sigis.signatures().at(index).label() : QString(); +} + +class FunctionHintProcessor : public IAssistProcessor +{ +public: + explicit FunctionHintProcessor(Client *client) : m_client(client) {} + IAssistProposal *perform(const AssistInterface *interface) override; + bool running() override { return m_running; } + bool needsRestart() const override { return true; } + +private: + void handleSignatureResponse(const SignatureHelpRequest::Response &response); + + QPointer m_client; + bool m_running = false; + int m_pos = -1; +}; + +IAssistProposal *FunctionHintProcessor::perform(const AssistInterface *interface) +{ + QTC_ASSERT(m_client, return nullptr); + m_pos = interface->position(); + QTextCursor cursor(interface->textDocument()); + cursor.setPosition(m_pos); + auto uri = DocumentUri::fromFileName(Utils::FileName::fromString(interface->fileName())); + SignatureHelpRequest request; + request.setParams(TextDocumentPositionParams(TextDocumentIdentifier(uri), Position(cursor))); + request.setResponseCallback([this](auto response) { this->handleSignatureResponse(response); }); + m_client->sendContent(request); + m_running = true; + return nullptr; +} + +void FunctionHintProcessor::handleSignatureResponse(const SignatureHelpRequest::Response &response) +{ + m_running = false; + if (auto error = response.error()) + m_client->log(error.value()); + FunctionHintProposalModelPtr model( + new FunctionHintProposalModel(response.result().value().value())); + setAsyncProposalAvailable(new FunctionHintProposal(m_pos, model)); +} + +FunctionHintAssistProvider::FunctionHintAssistProvider(Client *client) + : m_client(client) +{} + +TextEditor::IAssistProcessor *FunctionHintAssistProvider::createProcessor() const +{ + return new FunctionHintProcessor(m_client); +} + +IAssistProvider::RunType FunctionHintAssistProvider::runType() const +{ + return Asynchronous; +} + +int FunctionHintAssistProvider::activationCharSequenceLength() const +{ + return m_activationCharSequenceLength; +} + +bool FunctionHintAssistProvider::isActivationCharSequence( + const QString &sequence) const +{ + return Utils::anyOf(m_triggerChars, + [sequence](const QString &trigger) { return trigger.endsWith(sequence); }); +} + +bool FunctionHintAssistProvider::isContinuationChar(const QChar &/*c*/) const +{ + return true; +} + +void FunctionHintAssistProvider::setTriggerCharacters(QList 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/languageclientfunctionhint.h b/src/plugins/languageclient/languageclientfunctionhint.h new file mode 100644 index 00000000000..cf5f96d812a --- /dev/null +++ b/src/plugins/languageclient/languageclientfunctionhint.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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 + +namespace LanguageClient { + +class Client; + +class FunctionHintAssistProvider : public TextEditor::CompletionAssistProvider +{ +public: + explicit FunctionHintAssistProvider(Client *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 &c) const override; + + void setTriggerCharacters(QList triggerChars); +private: + QList m_triggerChars; + int m_activationCharSequenceLength = 0; + Client *m_client = nullptr; // not owned +}; + +} // namespace LanguageClient diff --git a/src/plugins/texteditor/codeassist/codeassistant.cpp b/src/plugins/texteditor/codeassist/codeassistant.cpp index b531f44c80a..4f9605d1e32 100644 --- a/src/plugins/texteditor/codeassist/codeassistant.cpp +++ b/src/plugins/texteditor/codeassist/codeassistant.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -431,22 +432,28 @@ void CodeAssistantPrivate::invalidateCurrentRequestData() CompletionAssistProvider *CodeAssistantPrivate::identifyActivationSequence() { - CompletionAssistProvider *completionProvider = m_editorWidget->textDocument()->completionAssistProvider(); - if (!completionProvider) - return nullptr; + auto checkActivationSequence = [this](CompletionAssistProvider *provider) { + if (!provider) + return false; + const int length = provider->activationCharSequenceLength(); + if (!length) + return false; + QString sequence = m_editorWidget->textAt(m_editorWidget->position() - length, length); + // In pretty much all cases the sequence will have the appropriate length. Only in the + // case of typing the very first characters in the document for providers that request a + // length greater than 1 (currently only C++, which specifies 3), the sequence needs to + // be prepended so it has the expected length. + const int lengthDiff = length - sequence.length(); + for (int j = 0; j < lengthDiff; ++j) + sequence.prepend(m_null); + return provider->isActivationCharSequence(sequence); + }; - const int length = completionProvider->activationCharSequenceLength(); - if (length == 0) - return nullptr; - QString sequence = m_editorWidget->textAt(m_editorWidget->position() - length, length); - // In pretty much all cases the sequence will have the appropriate length. Only in the - // case of typing the very first characters in the document for providers that request a - // length greater than 1 (currently only C++, which specifies 3), the sequence needs to - // be prepended so it has the expected length. - const int lengthDiff = length - sequence.length(); - for (int j = 0; j < lengthDiff; ++j) - sequence.prepend(m_null); - return completionProvider->isActivationCharSequence(sequence) ? completionProvider : nullptr; + auto provider = { + m_editorWidget->textDocument()->completionAssistProvider(), + m_editorWidget->textDocument()->functionHintAssistProvider() + }; + return Utils::findOrDefault(provider, checkActivationSequence); } void CodeAssistantPrivate::notifyChange() diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp index 2bfb51439a1..d619213923d 100644 --- a/src/plugins/texteditor/textdocument.cpp +++ b/src/plugins/texteditor/textdocument.cpp @@ -100,6 +100,7 @@ public: QTextDocument m_document; SyntaxHighlighter *m_highlighter = nullptr; CompletionAssistProvider *m_completionAssistProvider = nullptr; + CompletionAssistProvider *m_functionHintAssistProvider = nullptr; IAssistProvider *m_quickFixProvider = nullptr; QScopedPointer m_indenter; @@ -394,6 +395,16 @@ CompletionAssistProvider *TextDocument::completionAssistProvider() const return d->m_completionAssistProvider; } +void TextDocument::setFunctionHintAssistProvider(CompletionAssistProvider *provider) +{ + d->m_functionHintAssistProvider = provider; +} + +CompletionAssistProvider *TextDocument::functionHintAssistProvider() const +{ + return d->m_functionHintAssistProvider; +} + void TextDocument::setQuickFixAssistProvider(IAssistProvider *provider) const { d->m_quickFixProvider = provider; diff --git a/src/plugins/texteditor/textdocument.h b/src/plugins/texteditor/textdocument.h index 9ea1751b112..d09d606c4b2 100644 --- a/src/plugins/texteditor/textdocument.h +++ b/src/plugins/texteditor/textdocument.h @@ -141,6 +141,8 @@ public: void setCompletionAssistProvider(CompletionAssistProvider *provider); virtual CompletionAssistProvider *completionAssistProvider() const; + void setFunctionHintAssistProvider(CompletionAssistProvider *provider); + virtual CompletionAssistProvider *functionHintAssistProvider() const; void setQuickFixAssistProvider(IAssistProvider *provider) const; virtual IAssistProvider *quickFixAssistProvider() const;