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 <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2019-04-30 11:05:43 +02:00
parent 24ac4b9e73
commit 7ad5313c3b
5 changed files with 204 additions and 35 deletions

View File

@@ -67,6 +67,8 @@ public:
using Utils::variant<QList<T>, std::nullptr_t>::variant;
using Utils::variant<QList<T>, std::nullptr_t>::operator=;
LanguageClientArray() {}
LanguageClientArray(const QList<T> &list)
{ *this = list; }

View File

@@ -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

View File

@@ -107,5 +107,8 @@ private:
QMap<QString, QVector<Client *>> m_clientsForSetting;
QHash<LanguageServerProtocol::MessageId, QList<Client *>> m_exclusiveRequests;
DocumentLocatorFilter m_currentDocumentLocatorFilter;
WorkspaceLocatorFilter m_workspaceLocatorFilter;
WorkspaceClassLocatorFilter m_workspaceClassLocatorFilter;
WorkspaceMethodLocatorFilter m_workspaceMethodLocatorFilter;
};
} // namespace LanguageClient

View File

@@ -30,6 +30,7 @@
#include "languageclientutils.h"
#include <coreplugin/editormanager/editormanager.h>
#include <languageserverprotocol/servercapabilities.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <utils/fuzzymatcher.h>
@@ -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<QString> 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<QString> 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<class T>
QList<Core::LocatorFilterEntry> DocumentLocatorFilter::generateEntries(const QList<T> &list,
const QString &filter)
{
QList<Core::LocatorFilterEntry> 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<Core::LocatorFilterEntry> 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<QString> 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<QString> 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
{
if (selection.internalData.canConvert<Utils::LineColumn>()) {
auto lineColumn = qvariant_cast<Utils::LineColumn>(selection.internalData);
Core::EditorManager::openEditorAt(m_currentUri.toFileName().toString(),
lineColumn.line + 1,
lineColumn.column);
} else if (selection.internalData.canConvert<Utils::Link>()) {
auto link = qvariant_cast<Utils::Link>(selection.internalData);
Core::EditorManager::openEditorAt(link.targetFileName, link.targetLine, link.targetColumn);
}
}
void DocumentLocatorFilter::refresh(QFutureInterface<void> & /*future*/) {}
WorkspaceLocatorFilter::WorkspaceLocatorFilter()
: WorkspaceLocatorFilter(QVector<SymbolKind>())
{}
WorkspaceLocatorFilter::WorkspaceLocatorFilter(const QVector<SymbolKind> &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<Core::LocatorFilterEntry> WorkspaceLocatorFilter::matchesFor(
QFutureInterface<Core::LocatorFilterEntry> &future, const QString & /*entry*/)
{
QMutexLocker locker(&m_mutex);
if (!m_pendingRequests.isEmpty()) {
QEventLoop loop;
connect(this, &WorkspaceLocatorFilter::allRequestsFinished, &loop, [&]() { loop.exit(1); });
QFutureWatcher<Core::LocatorFilterEntry> watcher;
watcher.setFuture(future.future());
connect(&watcher,
&QFutureWatcher<Core::LocatorFilterEntry>::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<Utils::Link>()) {
auto link = qvariant_cast<Utils::Link>(selection.internalData);
Core::EditorManager::openEditorAt(link.targetFileName, link.targetLine, link.targetColumn);
}
}
void WorkspaceLocatorFilter::refresh(QFutureInterface<void> & /*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<SymbolInformation>());
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

View File

@@ -30,6 +30,7 @@
#include <coreplugin/locator/ilocatorfilter.h>
#include <languageserverprotocol/lsptypes.h>
#include <languageserverprotocol/languagefeatures.h>
#include <languageserverprotocol/workspace.h>
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<class T>
QList<Core::LocatorFilterEntry> generateEntries(const QList<T> &list, const QString &filter);
@@ -75,4 +73,47 @@ private:
Utils::optional<LanguageServerProtocol::DocumentSymbolsResult> m_currentSymbols;
};
class WorkspaceLocatorFilter : public Core::ILocatorFilter
{
Q_OBJECT
public:
WorkspaceLocatorFilter();
void prepareSearch(const QString &entry) override;
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
const QString &entry) override;
void accept(Core::LocatorFilterEntry selection,
QString *newText,
int *selectionStart,
int *selectionLength) const override;
void refresh(QFutureInterface<void> &future) override;
signals:
void allRequestsFinished(QPrivateSignal);
protected:
explicit WorkspaceLocatorFilter(const QVector<LanguageServerProtocol::SymbolKind> &filter);
private:
void handleResponse(Client *client,
const LanguageServerProtocol::WorkspaceSymbolRequest::Response &response);
QMutex m_mutex;
QMap<Client *, LanguageServerProtocol::MessageId> m_pendingRequests;
QVector<LanguageServerProtocol::SymbolInformation> m_results;
QVector<LanguageServerProtocol::SymbolKind> m_filterKinds;
};
class WorkspaceClassLocatorFilter : public WorkspaceLocatorFilter
{
public:
WorkspaceClassLocatorFilter();
};
class WorkspaceMethodLocatorFilter : public WorkspaceLocatorFilter
{
public:
WorkspaceMethodLocatorFilter();
};
} // namespace LanguageClient