diff --git a/src/plugins/languageclient/CMakeLists.txt b/src/plugins/languageclient/CMakeLists.txt index 9d04e7df1b7..85d2227e128 100644 --- a/src/plugins/languageclient/CMakeLists.txt +++ b/src/plugins/languageclient/CMakeLists.txt @@ -16,6 +16,7 @@ add_qtc_plugin(LanguageClient languageclientplugin.cpp languageclientplugin.h languageclientquickfix.cpp languageclientquickfix.h languageclientsettings.cpp languageclientsettings.h + languageclientsymbolsupport.cpp languageclientsymbolsupport.h languageclientutils.cpp languageclientutils.h languageclient_global.h locatorfilter.cpp locatorfilter.h diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 4fbebdbc873..dcd19fd5c1c 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -100,6 +100,7 @@ Client::Client(BaseClientInterface *clientInterface) , m_clientInterface(clientInterface) , m_documentSymbolCache(this) , m_hoverHandler(this) + , m_symbolSupport(this) { m_clientProviders.completionAssistProvider = new LanguageClientCompletionAssistProvider(this); m_clientProviders.functionHintProvider = new FunctionHintAssistProvider(this); @@ -553,43 +554,6 @@ void Client::unregisterCapabilities(const QList &unregistrations m_dynamicCapabilities.unregisterCapability(unregistrations); } -template -static bool sendTextDocumentPositionParamsRequest(Client *client, - const Request &request, - const DynamicCapabilities &dynamicCapabilities, - const optional &serverCapability) -{ - if (!request.isValid(nullptr)) - return false; - const DocumentUri uri = request.params().value().textDocument().uri(); - const bool supportedFile = client->isSupportedUri(uri); - bool sendMessage = dynamicCapabilities.isRegistered(Request::methodName).value_or(false); - if (sendMessage) { - const TextDocumentRegistrationOptions option(dynamicCapabilities.option(Request::methodName)); - if (option.isValid(nullptr)) - sendMessage = option.filterApplies(FilePath::fromString(QUrl(uri).adjusted(QUrl::PreferLocalFile).toString())); - else - sendMessage = supportedFile; - } else { - sendMessage = serverCapability.value_or(sendMessage) && supportedFile; - } - if (sendMessage) - client->sendContent(request); - return sendMessage; -} - -bool Client::findLinkAt(GotoDefinitionRequest &request) -{ - return LanguageClient::sendTextDocumentPositionParamsRequest( - this, request, m_dynamicCapabilities, m_serverCapabilities.definitionProvider()); -} - -bool Client::findUsages(FindReferencesRequest &request) -{ - return LanguageClient::sendTextDocumentPositionParamsRequest( - this, request, m_dynamicCapabilities, m_serverCapabilities.referencesProvider()); -} - TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation &info) { if (!info.isValid(nullptr)) @@ -652,6 +616,11 @@ void Client::cursorPositionChanged(TextEditor::TextEditorWidget *widget) sendContent(request); } +SymbolSupport &Client::symbolSupport() +{ + return m_symbolSupport; +} + void Client::requestCodeActions(const DocumentUri &uri, const QList &diagnostics) { const Utils::FilePath fileName = uri.toFilePath(); diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index e311d750af7..97078e12d2e 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -34,6 +34,7 @@ #include "languageclienthoverhandler.h" #include "languageclientquickfix.h" #include "languageclientsettings.h" +#include "languageclientsymbolsupport.h" #include #include @@ -111,10 +112,10 @@ public: int charsAdded); void registerCapabilities(const QList ®istrations); void unregisterCapabilities(const QList &unregistrations); - bool findLinkAt(LanguageServerProtocol::GotoDefinitionRequest &request); - bool findUsages(LanguageServerProtocol::FindReferencesRequest &request); void cursorPositionChanged(TextEditor::TextEditorWidget *widget); + SymbolSupport &symbolSupport(); + void requestCodeActions(const LanguageServerProtocol::DocumentUri &uri, const QList &diagnostics); void requestCodeActions(const LanguageServerProtocol::CodeActionRequest &request); @@ -239,6 +240,7 @@ private: QHash m_highlights; const ProjectExplorer::Project *m_project = nullptr; QSet m_runningAssistProcessors; + SymbolSupport m_symbolSupport; }; } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclient.pro b/src/plugins/languageclient/languageclient.pro index f6a2930d71a..25fe9b5fccb 100644 --- a/src/plugins/languageclient/languageclient.pro +++ b/src/plugins/languageclient/languageclient.pro @@ -17,6 +17,7 @@ HEADERS += \ languageclientplugin.h \ languageclientquickfix.h \ languageclientsettings.h \ + languageclientsymbolsupport.h \ languageclientutils.h \ locatorfilter.h \ lsplogger.h \ @@ -37,6 +38,7 @@ SOURCES += \ languageclientplugin.cpp \ languageclientquickfix.cpp \ languageclientsettings.cpp \ + languageclientsymbolsupport.cpp \ languageclientutils.cpp \ locatorfilter.cpp \ lsplogger.cpp \ diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs index 0502921b9ef..18b34c963cf 100644 --- a/src/plugins/languageclient/languageclient.qbs +++ b/src/plugins/languageclient/languageclient.qbs @@ -42,6 +42,8 @@ QtcPlugin { "languageclientquickfix.h", "languageclientsettings.cpp", "languageclientsettings.h", + "languageclientsymbolsupport.cpp", + "languageclientsymbolsupport.h", "languageclientutils.cpp", "languageclientutils.h", "locatorfilter.cpp", diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index d0740056950..1147aae74ed 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -405,13 +405,15 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor) if (auto *textEditor = qobject_cast(editor)) { if (TextEditorWidget *widget = textEditor->editorWidget()) { connect(widget, &TextEditorWidget::requestLinkAt, this, - [this, document = textEditor->textDocument()] + [document = textEditor->textDocument()] (const QTextCursor &cursor, Utils::ProcessLinkCallback &callback, bool resolveTarget) { - findLinkAt(document, cursor, callback, resolveTarget); + if (auto client = clientForDocument(document)) + client->symbolSupport().findLinkAt(document, cursor, callback, resolveTarget); }); connect(widget, &TextEditorWidget::requestUsages, this, - [this, document = textEditor->textDocument()](const QTextCursor &cursor) { - findUsages(document, cursor); + [document = textEditor->textDocument()](const QTextCursor &cursor) { + if (auto client = clientForDocument(document)) + client->symbolSupport().findUsages(document, cursor); }); connect(widget, &TextEditorWidget::cursorPositionChanged, this, [this, widget]() { // TODO This would better be a compressing timer @@ -494,116 +496,6 @@ void LanguageClientManager::documentWillSave(Core::IDocument *document) } } -void LanguageClientManager::findLinkAt(TextEditor::TextDocument *document, - const QTextCursor &cursor, - Utils::ProcessLinkCallback callback, - bool resolveTarget) -{ - const DocumentUri uri = DocumentUri::fromFilePath(document->filePath()); - const TextDocumentIdentifier documentId(uri); - const Position pos(cursor); - TextDocumentPositionParams params(documentId, pos); - GotoDefinitionRequest request(params); - request.setResponseCallback([callback, filePath = document->filePath(), cursor, resolveTarget] - (const GotoDefinitionRequest::Response &response) { - if (Utils::optional _result = response.result()) { - const GotoResult result = _result.value(); - if (Utils::holds_alternative(result)) - return; - auto wordUnderCursor = [cursor, filePath]() { - QTextCursor linkCursor = cursor; - linkCursor.select(QTextCursor::WordUnderCursor); - Utils::Link link(filePath.toString(), - linkCursor.blockNumber() + 1, - linkCursor.positionInBlock()); - link.linkTextStart = linkCursor.selectionStart(); - link.linkTextEnd = linkCursor.selectionEnd(); - return link; - }; - if (auto ploc = Utils::get_if(&result)) { - callback(resolveTarget ? ploc->toLink() : wordUnderCursor()); - } else if (auto plloc = Utils::get_if>(&result)) { - if (!plloc->isEmpty()) - callback(resolveTarget ? plloc->value(0).toLink() : wordUnderCursor()); - } - } - }); - if (Client *client = clientForUri(uri)) { - if (client->reachable()) - client->findLinkAt(request); - } -} - -QList generateSearchResultItems(const LanguageClientArray &locations) -{ - auto convertPosition = [](const Position &pos){ - return Core::Search::TextPosition(pos.line() + 1, pos.character()); - }; - auto convertRange = [convertPosition](const Range &range){ - return Core::Search::TextRange(convertPosition(range.start()), convertPosition(range.end())); - }; - QList result; - if (locations.isNull()) - return result; - QMap> rangesInDocument; - for (const Location &location : locations.toList()) - rangesInDocument[location.uri().toFilePath().toString()] << convertRange(location.range()); - for (auto it = rangesInDocument.begin(); it != rangesInDocument.end(); ++it) { - const QString &fileName = it.key(); - QFile file(fileName); - file.open(QFile::ReadOnly); - - Core::SearchResultItem item; - item.path = QStringList() << fileName; - item.useTextEditorFont = true; - - QStringList lines = QString::fromLocal8Bit(file.readAll()).split(QChar::LineFeed); - for (const Core::Search::TextRange &range : it.value()) { - item.mainRange = range; - if (file.isOpen() && range.begin.line > 0 && range.begin.line <= lines.size()) - item.text = lines[range.begin.line - 1]; - else - item.text.clear(); - result << item; - } - } - return result; -} - -void LanguageClientManager::findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor) -{ - const DocumentUri uri = DocumentUri::fromFilePath(document->filePath()); - const TextDocumentIdentifier documentId(uri); - const Position pos(cursor); - QTextCursor termCursor(cursor); - termCursor.select(QTextCursor::WordUnderCursor); - ReferenceParams params(TextDocumentPositionParams(documentId, pos)); - params.setContext(ReferenceParams::ReferenceContext(true)); - FindReferencesRequest request(params); - auto callback = [this, wordUnderCursor = termCursor.selectedText()] - (const QString &clientName, const FindReferencesRequest::Response &response){ - if (auto result = response.result()) { - Core::SearchResult *search = Core::SearchResultWindow::instance()->startNewSearch( - tr("Find References with %1 for:").arg(clientName), "", wordUnderCursor); - search->addResults(generateSearchResultItems(result.value()), Core::SearchResult::AddOrdered); - QObject::connect(search, &Core::SearchResult::activated, - [](const Core::SearchResultItem& item) { - Core::EditorManager::openEditorAtSearchResult(item); - }); - search->finishSearch(false); - search->popup(); - } - }; - for (Client *client : reachableClients()) { - request.setResponseCallback([callback, clientName = client->name()] - (const FindReferencesRequest::Response &response){ - callback(clientName, response); - }); - if (client->findUsages(request)) - m_exclusiveRequests[request.id()] << client; - } -} - void LanguageClientManager::updateProject(ProjectExplorer::Project *project) { for (BaseSettings *setting : m_currentSettings) { diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h index 2a1c42e62de..64c54d8533e 100644 --- a/src/plugins/languageclient/languageclientmanager.h +++ b/src/plugins/languageclient/languageclientmanager.h @@ -107,9 +107,6 @@ private: void documentClosed(Core::IDocument *document); void documentContentsSaved(Core::IDocument *document); void documentWillSave(Core::IDocument *document); - void findLinkAt(TextEditor::TextDocument *document, const QTextCursor &cursor, - Utils::ProcessLinkCallback callback, const bool resolveTarget); - void findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor); void updateProject(ProjectExplorer::Project *project); void projectRemoved(ProjectExplorer::Project *project); diff --git a/src/plugins/languageclient/languageclientsymbolsupport.cpp b/src/plugins/languageclient/languageclientsymbolsupport.cpp new file mode 100644 index 00000000000..db065ed061e --- /dev/null +++ b/src/plugins/languageclient/languageclientsymbolsupport.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "languageclientsymbolsupport.h" + +#include "client.h" + +#include +#include + +using namespace LanguageServerProtocol; + +namespace LanguageClient { + +SymbolSupport::SymbolSupport(Client *client) : m_client(client) +{} + +template +static void sendTextDocumentPositionParamsRequest(Client *client, + const Request &request, + const DynamicCapabilities &dynamicCapabilities, + const Utils::optional &serverCapability) +{ + if (!request.isValid(nullptr)) + return; + const DocumentUri uri = request.params().value().textDocument().uri(); + const bool supportedFile = client->isSupportedUri(uri); + bool sendMessage = dynamicCapabilities.isRegistered(Request::methodName).value_or(false); + if (sendMessage) { + const TextDocumentRegistrationOptions option( + dynamicCapabilities.option(Request::methodName)); + if (option.isValid(nullptr)) + sendMessage = option.filterApplies( + Utils::FilePath::fromString(QUrl(uri).adjusted(QUrl::PreferLocalFile).toString())); + else + sendMessage = supportedFile; + } else { + sendMessage = serverCapability.value_or(sendMessage) && supportedFile; + } + if (sendMessage) + client->sendContent(request); +} + +static void handleGotoDefinitionResponse(const GotoDefinitionRequest::Response &response, + Utils::ProcessLinkCallback callback, + Utils::optional linkUnderCursor) +{ + if (Utils::optional _result = response.result()) { + const GotoResult result = _result.value(); + if (Utils::holds_alternative(result)) + return; + if (auto ploc = Utils::get_if(&result)) { + callback(linkUnderCursor.value_or(ploc->toLink())); + } else if (auto plloc = Utils::get_if>(&result)) { + if (!plloc->isEmpty()) + callback(linkUnderCursor.value_or(plloc->value(0).toLink())); + } + } +} + +static TextDocumentPositionParams generateDocPosParams(TextEditor::TextDocument *document, + const QTextCursor &cursor) +{ + const DocumentUri uri = DocumentUri::fromFilePath(document->filePath()); + const TextDocumentIdentifier documentId(uri); + const Position pos(cursor); + return TextDocumentPositionParams(documentId, pos); +} + +void SymbolSupport::findLinkAt(TextEditor::TextDocument *document, + const QTextCursor &cursor, + Utils::ProcessLinkCallback callback, + const bool resolveTarget) +{ + if (!m_client->reachable()) + return; + GotoDefinitionRequest request(generateDocPosParams(document, cursor)); + Utils::optional linkUnderCursor; + if (!resolveTarget) { + QTextCursor linkCursor = cursor; + linkCursor.select(QTextCursor::WordUnderCursor); + Utils::Link link(document->filePath().toString(), + linkCursor.blockNumber() + 1, + linkCursor.positionInBlock()); + link.linkTextStart = linkCursor.selectionStart(); + link.linkTextEnd = linkCursor.selectionEnd(); + linkUnderCursor = link; + } + request.setResponseCallback( + [callback, linkUnderCursor](const GotoDefinitionRequest::Response &response) { + handleGotoDefinitionResponse(response, callback, linkUnderCursor); + }); + + sendTextDocumentPositionParamsRequest(m_client, + request, + m_client->dynamicCapabilities(), + m_client->capabilities().referencesProvider()); + +} + +QList generateSearchResultItems( + const LanguageClientArray &locations) +{ + auto convertPosition = [](const Position &pos) { + return Core::Search::TextPosition(pos.line() + 1, pos.character()); + }; + auto convertRange = [convertPosition](const Range &range) { + return Core::Search::TextRange(convertPosition(range.start()), convertPosition(range.end())); + }; + QList result; + if (locations.isNull()) + return result; + QMap> rangesInDocument; + for (const Location &location : locations.toList()) + rangesInDocument[location.uri().toFilePath().toString()] << convertRange(location.range()); + for (auto it = rangesInDocument.begin(); it != rangesInDocument.end(); ++it) { + const QString &fileName = it.key(); + QFile file(fileName); + file.open(QFile::ReadOnly); + + Core::SearchResultItem item; + item.path = QStringList() << fileName; + item.useTextEditorFont = true; + + QStringList lines = QString::fromLocal8Bit(file.readAll()).split(QChar::LineFeed); + for (const Core::Search::TextRange &range : it.value()) { + item.mainRange = range; + if (file.isOpen() && range.begin.line > 0 && range.begin.line <= lines.size()) + item.text = lines[range.begin.line - 1]; + else + item.text.clear(); + result << item; + } + } + return result; +} + +void SymbolSupport::handleFindReferencesResponse(const FindReferencesRequest::Response &response, + const QString &wordUnderCursor) +{ + if (auto result = response.result()) { + Core::SearchResult *search = Core::SearchResultWindow::instance()->startNewSearch( + tr("Find References with %1 for:").arg(m_client->name()), "", wordUnderCursor); + search->addResults(generateSearchResultItems(result.value()), + Core::SearchResult::AddOrdered); + QObject::connect(search, + &Core::SearchResult::activated, + [](const Core::SearchResultItem &item) { + Core::EditorManager::openEditorAtSearchResult(item); + }); + search->finishSearch(false); + search->popup(); + } +} + +void SymbolSupport::findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor) +{ + if (!m_client->reachable()) + return; + ReferenceParams params(generateDocPosParams(document, cursor)); + params.setContext(ReferenceParams::ReferenceContext(true)); + FindReferencesRequest request(params); + QTextCursor termCursor(cursor); + termCursor.select(QTextCursor::WordUnderCursor); + request.setResponseCallback([this, wordUnderCursor = termCursor.selectedText()]( + const FindReferencesRequest::Response &response) { + handleFindReferencesResponse(response, wordUnderCursor); + }); + + sendTextDocumentPositionParamsRequest(m_client, + request, + m_client->dynamicCapabilities(), + m_client->capabilities().referencesProvider()); +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientsymbolsupport.h b/src/plugins/languageclient/languageclientsymbolsupport.h new file mode 100644 index 00000000000..7d09545e7f3 --- /dev/null +++ b/src/plugins/languageclient/languageclientsymbolsupport.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 + +#include + +namespace LanguageClient { + +class Client; + +class SymbolSupport +{ + Q_DECLARE_TR_FUNCTIONS(SymbolSupport) +public: + explicit SymbolSupport(Client *client); + + void findLinkAt(TextEditor::TextDocument *document, + const QTextCursor &cursor, + Utils::ProcessLinkCallback callback, + const bool resolveTarget); + void findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor); + +private: + void handleFindReferencesResponse( + const LanguageServerProtocol::FindReferencesRequest::Response &response, + const QString &wordUnderCursor); + + Client *m_client = nullptr; +}; + +} // namespace LanguageClient