From 7ad5313c3b3f16d65ae3a9d836f14e363a1ea413 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 30 Apr 2019 11:05:43 +0200 Subject: [PATCH] LanguageClient: add locator filters for symbols in workspace/project Implement locator filter that is using the workspace/symbol request to search for symbols in a project. In total three filters were added: ':': searches for all kind of symbols 'c': searches for classes and structs 'm': searches for methods and functions Fixes: QTCREATORBUG-21915 Change-Id: Id62c9e0b1bcb29112e35b926b1a5cf04357751c4 Reviewed-by: Christian Stenger --- src/libs/languageserverprotocol/lsputils.h | 2 + .../languageclient/languageclient_global.h | 6 + .../languageclient/languageclientmanager.h | 3 + src/plugins/languageclient/locatorfilter.cpp | 181 ++++++++++++++---- src/plugins/languageclient/locatorfilter.h | 47 ++++- 5 files changed, 204 insertions(+), 35 deletions(-) diff --git a/src/libs/languageserverprotocol/lsputils.h b/src/libs/languageserverprotocol/lsputils.h index d7ae5525bb0..a0ce7533250 100644 --- a/src/libs/languageserverprotocol/lsputils.h +++ b/src/libs/languageserverprotocol/lsputils.h @@ -67,6 +67,8 @@ public: using Utils::variant, std::nullptr_t>::variant; using Utils::variant, std::nullptr_t>::operator=; + LanguageClientArray() {} + LanguageClientArray(const QList &list) { *this = list; } diff --git a/src/plugins/languageclient/languageclient_global.h b/src/plugins/languageclient/languageclient_global.h index 7609bdf0844..685379804df 100644 --- a/src/plugins/languageclient/languageclient_global.h +++ b/src/plugins/languageclient/languageclient_global.h @@ -35,6 +35,12 @@ const char LANGUAGECLIENT_SETTINGS_PAGE[] = "LanguageClient.General"; const char LANGUAGECLIENT_SETTINGS_TR[] = QT_TRANSLATE_NOOP("LanguageClient", "Language Client"); const char LANGUAGECLIENT_DOCUMENT_FILTER_ID[] = "Current Document Symbols"; const char LANGUAGECLIENT_DOCUMENT_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("LanguageClient", "Symbols in Current Document"); +const char LANGUAGECLIENT_WORKSPACE_FILTER_ID[] = "Workspace Symbols"; +const char LANGUAGECLIENT_WORKSPACE_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("LanguageClient", "Symbols in Workspace"); +const char LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_ID[] = "Workspace Classes and Structs"; +const char LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("LanguageClient", "Classes and Structs in Workspace"); +const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_ID[] = "Workspace Functions and Methods"; +const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("LanguageClient", "Functions and Methods in Workspace"); } // namespace Constants } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h index 6e1bb87eb00..a39d88f3c3f 100644 --- a/src/plugins/languageclient/languageclientmanager.h +++ b/src/plugins/languageclient/languageclientmanager.h @@ -107,5 +107,8 @@ private: QMap> m_clientsForSetting; QHash> m_exclusiveRequests; DocumentLocatorFilter m_currentDocumentLocatorFilter; + WorkspaceLocatorFilter m_workspaceLocatorFilter; + WorkspaceClassLocatorFilter m_workspaceClassLocatorFilter; + WorkspaceMethodLocatorFilter m_workspaceMethodLocatorFilter; }; } // namespace LanguageClient diff --git a/src/plugins/languageclient/locatorfilter.cpp b/src/plugins/languageclient/locatorfilter.cpp index e3f34eba5ee..92da6a642c0 100644 --- a/src/plugins/languageclient/locatorfilter.cpp +++ b/src/plugins/languageclient/locatorfilter.cpp @@ -30,6 +30,7 @@ #include "languageclientutils.h" #include +#include #include #include #include @@ -96,13 +97,40 @@ void DocumentLocatorFilter::resetSymbols() m_currentSymbols.reset(); } +Core::LocatorFilterEntry generateLocatorEntry(const SymbolInformation &info, + Core::ILocatorFilter *filter) +{ + Core::LocatorFilterEntry entry; + entry.filter = filter; + entry.displayName = info.name(); + if (Utils::optional container = info.containerName()) + entry.extraInfo = container.value_or(QString()); + entry.displayIcon = symbolIcon(info.kind()); + entry.internalData = qVariantFromValue(info.location().toLink()); + return entry; +} + +Core::LocatorFilterEntry generateLocatorEntry(const DocumentSymbol &info, + Core::ILocatorFilter *filter) +{ + Core::LocatorFilterEntry entry; + entry.filter = filter; + entry.displayName = info.name(); + if (Utils::optional detail = info.detail()) + entry.extraInfo = detail.value_or(QString()); + entry.displayIcon = symbolIcon(info.kind()); + const Position &pos = info.range().start(); + entry.internalData = qVariantFromValue(Utils::LineColumn(pos.line(), pos.character())); + return entry; +} + template QList DocumentLocatorFilter::generateEntries(const QList &list, const QString &filter) { QList entries; FuzzyMatcher::CaseSensitivity caseSensitivity - = DocumentLocatorFilter::caseSensitivity(filter) == Qt::CaseSensitive + = ILocatorFilter::caseSensitivity(filter) == Qt::CaseSensitive ? FuzzyMatcher::CaseSensitivity::CaseSensitive : FuzzyMatcher::CaseSensitivity::CaseInsensitive; const QRegularExpression regexp = FuzzyMatcher::createRegExp(filter, caseSensitivity); @@ -112,37 +140,11 @@ QList DocumentLocatorFilter::generateEntries(const QLi for (const T &item : list) { QRegularExpressionMatch match = regexp.match(item.name()); if (match.hasMatch()) - entries << generateLocatorEntry(item); + entries << generateLocatorEntry(item, this); } return entries; } -Core::LocatorFilterEntry DocumentLocatorFilter::generateLocatorEntry(const SymbolInformation &info) -{ - Core::LocatorFilterEntry entry; - entry.filter = this; - entry.displayName = info.name(); - if (Utils::optional container = info.containerName()) - entry.extraInfo = container.value_or(QString()); - entry.displayIcon = symbolIcon(info.kind()); - const Position &pos = info.location().range().start(); - entry.internalData = qVariantFromValue(Utils::LineColumn(pos.line(), pos.character())); - return entry; -} - -Core::LocatorFilterEntry DocumentLocatorFilter::generateLocatorEntry(const DocumentSymbol &info) -{ - Core::LocatorFilterEntry entry; - entry.filter = this; - entry.displayName = info.name(); - if (Utils::optional detail = info.detail()) - entry.extraInfo = detail.value_or(QString()); - entry.displayIcon = symbolIcon(info.kind()); - const Position &pos = info.range().start(); - entry.internalData = qVariantFromValue(Utils::LineColumn(pos.line(), pos.character())); - return entry; -} - void DocumentLocatorFilter::prepareSearch(const QString &/*entry*/) { QMutexLocker locker(&m_mutex); @@ -188,12 +190,127 @@ void DocumentLocatorFilter::accept(Core::LocatorFilterEntry selection, int * /*selectionStart*/, int * /*selectionLength*/) const { - auto lineColumn = qvariant_cast(selection.internalData); - Core::EditorManager::openEditorAt(m_currentUri.toFileName().toString(), - lineColumn.line + 1, - lineColumn.column); + if (selection.internalData.canConvert()) { + auto lineColumn = qvariant_cast(selection.internalData); + Core::EditorManager::openEditorAt(m_currentUri.toFileName().toString(), + lineColumn.line + 1, + lineColumn.column); + } else if (selection.internalData.canConvert()) { + auto link = qvariant_cast(selection.internalData); + Core::EditorManager::openEditorAt(link.targetFileName, link.targetLine, link.targetColumn); + } } void DocumentLocatorFilter::refresh(QFutureInterface & /*future*/) {} +WorkspaceLocatorFilter::WorkspaceLocatorFilter() + : WorkspaceLocatorFilter(QVector()) +{} + +WorkspaceLocatorFilter::WorkspaceLocatorFilter(const QVector &filter) + : m_filterKinds(filter) +{ + setId(Constants::LANGUAGECLIENT_WORKSPACE_FILTER_ID); + setDisplayName(Constants::LANGUAGECLIENT_WORKSPACE_FILTER_DISPLAY_NAME); + setShortcutString(":"); + setIncludedByDefault(false); + setPriority(ILocatorFilter::Low); +} + +void WorkspaceLocatorFilter::prepareSearch(const QString &entry) +{ + m_pendingRequests.clear(); + m_results.clear(); + + WorkspaceSymbolParams params; + params.setQuery(entry); + + QMutexLocker locker(&m_mutex); + for (auto client : Utils::filtered(LanguageClientManager::clients(), &Client::reachable)) { + if (client->capabilities().workspaceSymbolProvider().value_or(false)) { + WorkspaceSymbolRequest request(params); + request.setResponseCallback( + [this, client](const WorkspaceSymbolRequest::Response &response) { + handleResponse(client, response); + }); + m_pendingRequests[client] = request.id(); + client->sendContent(request); + } + } +} + +QList WorkspaceLocatorFilter::matchesFor( + QFutureInterface &future, const QString & /*entry*/) +{ + QMutexLocker locker(&m_mutex); + if (!m_pendingRequests.isEmpty()) { + QEventLoop loop; + connect(this, &WorkspaceLocatorFilter::allRequestsFinished, &loop, [&]() { loop.exit(1); }); + QFutureWatcher watcher; + watcher.setFuture(future.future()); + connect(&watcher, + &QFutureWatcher::canceled, + &loop, + &QEventLoop::quit); + locker.unlock(); + if (!loop.exec()) + return {}; + + locker.relock(); + } + + + if (!m_filterKinds.isEmpty()) { + m_results = Utils::filtered(m_results, [&](const SymbolInformation &info) { + return m_filterKinds.contains(SymbolKind(info.kind())); + }); + } + return Utils::transform(m_results, + [this](const SymbolInformation &info) { + return generateLocatorEntry(info, this); + }) + .toList(); +} + +void WorkspaceLocatorFilter::accept(Core::LocatorFilterEntry selection, + QString * /*newText*/, + int * /*selectionStart*/, + int * /*selectionLength*/) const +{ + if (selection.internalData.canConvert()) { + auto link = qvariant_cast(selection.internalData); + Core::EditorManager::openEditorAt(link.targetFileName, link.targetLine, link.targetColumn); + } +} + +void WorkspaceLocatorFilter::refresh(QFutureInterface & /*future*/) {} + +void WorkspaceLocatorFilter::handleResponse(Client *client, + const WorkspaceSymbolRequest::Response &response) +{ + QMutexLocker locker(&m_mutex); + m_pendingRequests.remove(client); + auto result = response.result().value_or(LanguageClientArray()); + if (!result.isNull()) + m_results.append(result.toList().toVector()); + if (m_pendingRequests.isEmpty()) + emit allRequestsFinished(QPrivateSignal()); +} + +WorkspaceClassLocatorFilter::WorkspaceClassLocatorFilter() + : WorkspaceLocatorFilter({SymbolKind::Class, SymbolKind::Struct}) +{ + setId(Constants::LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_ID); + setDisplayName(Constants::LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_DISPLAY_NAME); + setShortcutString("c"); +} + +WorkspaceMethodLocatorFilter::WorkspaceMethodLocatorFilter() + : WorkspaceLocatorFilter({SymbolKind::Method, SymbolKind::Function, SymbolKind::Constructor}) +{ + setId(Constants::LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_ID); + setDisplayName(Constants::LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DISPLAY_NAME); + setShortcutString("m"); +} + } // namespace LanguageClient diff --git a/src/plugins/languageclient/locatorfilter.h b/src/plugins/languageclient/locatorfilter.h index 17191eb3a28..a3aa9cd5975 100644 --- a/src/plugins/languageclient/locatorfilter.h +++ b/src/plugins/languageclient/locatorfilter.h @@ -30,6 +30,7 @@ #include #include #include +#include namespace Core { class IEditor; } @@ -63,9 +64,6 @@ private: const LanguageServerProtocol::DocumentSymbolsResult &symbols); void resetSymbols(); - Core::LocatorFilterEntry generateLocatorEntry( - const LanguageServerProtocol::SymbolInformation &info); - Core::LocatorFilterEntry generateLocatorEntry(const LanguageServerProtocol::DocumentSymbol &info); template QList generateEntries(const QList &list, const QString &filter); @@ -75,4 +73,47 @@ private: Utils::optional m_currentSymbols; }; +class WorkspaceLocatorFilter : public Core::ILocatorFilter +{ + Q_OBJECT +public: + WorkspaceLocatorFilter(); + + void prepareSearch(const QString &entry) override; + QList matchesFor(QFutureInterface &future, + const QString &entry) override; + void accept(Core::LocatorFilterEntry selection, + QString *newText, + int *selectionStart, + int *selectionLength) const override; + void refresh(QFutureInterface &future) override; + +signals: + void allRequestsFinished(QPrivateSignal); + +protected: + explicit WorkspaceLocatorFilter(const QVector &filter); + +private: + void handleResponse(Client *client, + const LanguageServerProtocol::WorkspaceSymbolRequest::Response &response); + + QMutex m_mutex; + QMap m_pendingRequests; + QVector m_results; + QVector m_filterKinds; +}; + +class WorkspaceClassLocatorFilter : public WorkspaceLocatorFilter +{ +public: + WorkspaceClassLocatorFilter(); +}; + +class WorkspaceMethodLocatorFilter : public WorkspaceLocatorFilter +{ +public: + WorkspaceMethodLocatorFilter(); +}; + } // namespace LanguageClient