diff options
author | David Schulz <david.schulz@qt.io> | 2018-11-23 09:45:51 +0100 |
---|---|---|
committer | David Schulz <david.schulz@qt.io> | 2019-01-10 07:26:08 +0000 |
commit | 0a8a4bad7ef2eaece6e15ae5a11ee1ff27885652 (patch) | |
tree | 3c2590dd06344dcd5295a8f4109670a85f528a4b | |
parent | 7b494c068eaa4012471b5f80be1ecfd964448cab (diff) | |
download | qt-creator-0a8a4bad7ef2eaece6e15ae5a11ee1ff27885652.tar.gz |
LSP: add outline support
Fixes: QTCREATORBUG-21573
Change-Id: If579715c84210eb80d258ec944e00f1fac1badbe
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
-rw-r--r-- | src/plugins/languageclient/baseclient.cpp | 10 | ||||
-rw-r--r-- | src/plugins/languageclient/baseclient.h | 3 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclient.pro | 3 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclient.qbs | 2 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclientmanager.cpp | 9 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclientmanager.h | 2 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclientoutline.cpp | 293 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclientoutline.h | 43 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclientplugin.h | 2 |
9 files changed, 367 insertions, 0 deletions
diff --git a/src/plugins/languageclient/baseclient.cpp b/src/plugins/languageclient/baseclient.cpp index e97f468862..a358ad41b8 100644 --- a/src/plugins/languageclient/baseclient.cpp +++ b/src/plugins/languageclient/baseclient.cpp @@ -549,6 +549,16 @@ void BaseClient::log(const QString &message, Core::MessageManager::PrintToOutput Core::MessageManager::write(QString("LanguageClient %1: %2").arg(name(), message), flag); } +const ServerCapabilities &BaseClient::capabilities() const +{ + return m_serverCapabilities; +} + +const DynamicCapabilities &BaseClient::dynamicCapabilities() const +{ + return m_dynamicCapabilities; +} + void BaseClient::log(const ShowMessageParams &message, Core::MessageManager::PrintToOutputPaneFlag flag) { diff --git a/src/plugins/languageclient/baseclient.h b/src/plugins/languageclient/baseclient.h index 1321ffa34f..4a3241f091 100644 --- a/src/plugins/languageclient/baseclient.h +++ b/src/plugins/languageclient/baseclient.h @@ -125,6 +125,9 @@ public: Core::MessageManager::PrintToOutputPaneFlag flag = Core::MessageManager::NoModeSwitch) { log(responseError.toString(), flag); } + const LanguageServerProtocol::ServerCapabilities &capabilities() const; + const DynamicCapabilities &dynamicCapabilities() const; + signals: void initialized(LanguageServerProtocol::ServerCapabilities capabilities); void finished(); diff --git a/src/plugins/languageclient/languageclient.pro b/src/plugins/languageclient/languageclient.pro index e17e0e0b6a..7522ceec5c 100644 --- a/src/plugins/languageclient/languageclient.pro +++ b/src/plugins/languageclient/languageclient.pro @@ -8,14 +8,17 @@ HEADERS += \ languageclient_global.h \ languageclientcodeassist.h \ languageclientmanager.h \ + languageclientoutline.h \ languageclientplugin.h \ languageclientsettings.h + SOURCES += \ baseclient.cpp \ dynamiccapabilities.cpp \ languageclientcodeassist.cpp \ languageclientmanager.cpp \ + languageclientoutline.cpp \ languageclientplugin.cpp \ languageclientsettings.cpp diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs index c808ec7813..a79af1e876 100644 --- a/src/plugins/languageclient/languageclient.qbs +++ b/src/plugins/languageclient/languageclient.qbs @@ -24,6 +24,8 @@ QtcPlugin { "languageclientcodeassist.h", "languageclientmanager.cpp", "languageclientmanager.h", + "languageclientoutline.cpp", + "languageclientoutline.h", "languageclientplugin.cpp", "languageclientplugin.h", "languageclientsettings.cpp", diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index 54b2210370..4e811d68d0 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -234,6 +234,15 @@ LanguageClientManager *LanguageClientManager::instance() return managerInstance; } +QList<BaseClient *> LanguageClientManager::clientsSupportingDocument( + const TextEditor::TextDocument *doc) +{ + QTC_ASSERT(doc, return {};); + return Utils::filtered(managerInstance->reachableClients(), [doc](BaseClient *client) { + return client->isSupportedDocument(doc); + }).toList(); +} + QVector<BaseClient *> LanguageClientManager::reachableClients() { return Utils::filtered(m_clients, &BaseClient::reachable); diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h index 9eb9fb03c9..70b21bd94e 100644 --- a/src/plugins/languageclient/languageclientmanager.h +++ b/src/plugins/languageclient/languageclientmanager.h @@ -75,6 +75,8 @@ public: static LanguageClientManager *instance(); + static QList<BaseClient *> clientsSupportingDocument(const TextEditor::TextDocument *doc); + signals: void shutdownFinished(); diff --git a/src/plugins/languageclient/languageclientoutline.cpp b/src/plugins/languageclient/languageclientoutline.cpp new file mode 100644 index 0000000000..841e9438ca --- /dev/null +++ b/src/plugins/languageclient/languageclientoutline.cpp @@ -0,0 +1,293 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "languageclientoutline.h" + +#include "languageclientmanager.h" + +#include <coreplugin/find/itemviewfind.h> +#include <coreplugin/editormanager/ieditor.h> +#include <languageserverprotocol/languagefeatures.h> +#include <texteditor/textdocument.h> +#include <texteditor/texteditor.h> +#include <utils/itemviews.h> +#include <utils/mimetypes/mimedatabase.h> +#include <utils/treemodel.h> +#include <utils/utilsicons.h> + +#include <QBoxLayout> + +using namespace LanguageServerProtocol; + +namespace LanguageClient { + +static const QIcon symbolIcon(int type) +{ + using namespace Utils::CodeModelIcon; + static QMap<SymbolKind, QIcon> icons; + if (type < int(SymbolKind::FirstSymbolKind) || type > int(SymbolKind::LastSymbolKind)) + return {}; + auto kind = static_cast<SymbolKind>(type); + if (icons.contains(kind)) { + switch (kind) { + case SymbolKind::File: icons[kind] = Utils::Icons::NEWFILE.icon(); break; + case SymbolKind::Module: icons[kind] = iconForType(Namespace); break; + case SymbolKind::Namespace: icons[kind] = iconForType(Namespace); break; + case SymbolKind::Package: icons[kind] = iconForType(Namespace); break; + case SymbolKind::Class: icons[kind] = iconForType(Class); break; + case SymbolKind::Method: icons[kind] = iconForType(FuncPublic); break; + case SymbolKind::Property: icons[kind] = iconForType(Property); break; + case SymbolKind::Field: icons[kind] = iconForType(VarPublic); break; + case SymbolKind::Constructor: icons[kind] = iconForType(Class); break; + case SymbolKind::Enum: icons[kind] = iconForType(Enum); break; + case SymbolKind::Interface: icons[kind] = iconForType(Class); break; + case SymbolKind::Function: icons[kind] = iconForType(FuncPublic); break; + case SymbolKind::Variable: icons[kind] = iconForType(VarPublic); break; + case SymbolKind::Constant: icons[kind] = iconForType(VarPublic); break; + case SymbolKind::String: icons[kind] = iconForType(VarPublic); break; + case SymbolKind::Number: icons[kind] = iconForType(VarPublic); break; + case SymbolKind::Boolean: icons[kind] = iconForType(VarPublic); break; + case SymbolKind::Array: icons[kind] = iconForType(VarPublic); break; + case SymbolKind::Object: icons[kind] = iconForType(Class); break; + case SymbolKind::Key: icons[kind] = iconForType(Keyword); break; + case SymbolKind::Null: icons[kind] = iconForType(Keyword); break; + case SymbolKind::EnumMember: icons[kind] = iconForType(Enumerator); break; + case SymbolKind::Struct: icons[kind] = iconForType(Struct); break; + case SymbolKind::Event: icons[kind] = iconForType(FuncPublic); break; + case SymbolKind::Operator: icons[kind] = iconForType(FuncPublic); break; + case SymbolKind::TypeParameter: icons[kind] = iconForType(VarPublic); break; + } + } + return icons[kind]; +} + +class LanguageClientOutlineItem : public Utils::TypedTreeItem<LanguageClientOutlineItem> +{ +public: + LanguageClientOutlineItem() = default; + LanguageClientOutlineItem(const SymbolInformation &info) + : m_name(info.name()) + , m_range(info.location().range()) + , m_type(info.kind()) + { } + + LanguageClientOutlineItem(const DocumentSymbol &info) + : m_name(info.name()) + , m_detail(info.detail().value_or(QString())) + , m_range(info.range()) + , m_type(info.kind()) + { + for (const DocumentSymbol &child : info.children().value_or(QList<DocumentSymbol>())) + appendChild(new LanguageClientOutlineItem(child)); + } + + // TreeItem interface + QVariant data(int column, int role) const override + { + switch (role) { + case Qt::DecorationRole: + return symbolIcon(m_type); + case Qt::DisplayRole: + return m_name; + default: + return Utils::TreeItem::data(column, role); + } + } + + Position pos() const { return m_range.start(); } + bool contains(const Position &pos) const { return m_range.contains(pos); } + +private: + QString m_name; + QString m_detail; + Range m_range; + int m_type = -1; +}; + +class LanguageClientOutlineModel : public Utils::TreeModel<LanguageClientOutlineItem> +{ +public: + using Utils::TreeModel<LanguageClientOutlineItem>::TreeModel; + void setInfo(const QList<SymbolInformation> &info) + { + clear(); + for (const SymbolInformation &symbol : info) + rootItem()->appendChild(new LanguageClientOutlineItem(symbol)); + } + void setInfo(const QList<DocumentSymbol> &info) + { + clear(); + for (const DocumentSymbol &symbol : info) + rootItem()->appendChild(new LanguageClientOutlineItem(symbol)); + } +}; + +class LanguageClientOutlineWidget : public TextEditor::IOutlineWidget +{ +public: + LanguageClientOutlineWidget(BaseClient *client, TextEditor::BaseTextEditor *editor); + + // IOutlineWidget interface +public: + QList<QAction *> filterMenuActions() const override; + void setCursorSynchronization(bool syncWithCursor) override; + +private: + void handleResponse(const LanguageServerProtocol::DocumentSymbolsRequest::Response &response); + void updateTextCursor(const QModelIndex &proxyIndex); + void updateSelectionInTree(const QTextCursor ¤tCursor); + void onItemActivated(const QModelIndex &index); + + QPointer<BaseClient> m_client; + QPointer<TextEditor::BaseTextEditor> m_editor; + LanguageClientOutlineModel m_model; + Utils::TreeView m_view; + bool m_sync = false; +}; + +LanguageClientOutlineWidget::LanguageClientOutlineWidget(BaseClient *client, + TextEditor::BaseTextEditor *editor) + : m_client(client) + , m_editor(editor) + , m_view(this) +{ + const DocumentSymbolParams params( + TextDocumentIdentifier( + DocumentUri::fromFileName(editor->textDocument()->filePath()))); + DocumentSymbolsRequest request(params); + request.setResponseCallback([self = QPointer<LanguageClientOutlineWidget>(this)] + (const DocumentSymbolsRequest::Response &response){ + if (self) + self->handleResponse(response); + }); + + auto *layout = new QVBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(Core::ItemViewFind::createSearchableWrapper(&m_view)); + setLayout(layout); + client->sendContent(request); + m_view.setModel(&m_model); + m_view.setHeaderHidden(true); + connect(&m_view, &QAbstractItemView::activated, + this, &LanguageClientOutlineWidget::onItemActivated); + connect(m_editor->editorWidget(), &TextEditor::TextEditorWidget::cursorPositionChanged, + this, [this](){ + if (m_sync) + updateSelectionInTree(m_editor->textCursor()); + }); +} + +QList<QAction *> LanguageClientOutlineWidget::filterMenuActions() const +{ + return {}; +} + +void LanguageClientOutlineWidget::setCursorSynchronization(bool syncWithCursor) +{ + m_sync = syncWithCursor; + if (m_sync && m_editor) + updateSelectionInTree(m_editor->textCursor()); +} + +void LanguageClientOutlineWidget::handleResponse(const DocumentSymbolsRequest::Response &response) +{ + if (Utils::optional<DocumentSymbolsRequest::Response::Error> error = response.error()) { + if (m_client) + m_client->log(error.value()); + } + if (Utils::optional<DocumentSymbolsResult> result = response.result()) { + if (Utils::holds_alternative<QList<SymbolInformation>>(result.value())) + m_model.setInfo(Utils::get<QList<SymbolInformation>>(result.value())); + else if (Utils::holds_alternative<QList<DocumentSymbol>>(result.value())) + m_model.setInfo(Utils::get<QList<DocumentSymbol>>(result.value())); + else + m_model.clear(); + } +} + +void LanguageClientOutlineWidget::updateTextCursor(const QModelIndex &proxyIndex) +{ + LanguageClientOutlineItem *item = m_model.itemForIndex(proxyIndex); + const Position &pos = item->pos(); + // line has to be 1 based, column 0 based! + m_editor->editorWidget()->gotoLine(pos.line() + 1, pos.character(), true, true); +} + +void LanguageClientOutlineWidget::updateSelectionInTree(const QTextCursor ¤tCursor) +{ + QItemSelection selection; + const Position pos(currentCursor); + m_model.forAllItems([&](const LanguageClientOutlineItem *item) { + if (item->contains(pos)) + selection.select(m_model.indexForItem(item), m_model.indexForItem(item)); + }); + m_view.selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); +} + +void LanguageClientOutlineWidget::onItemActivated(const QModelIndex &index) +{ + if (!index.isValid() || !m_editor) + return; + + updateTextCursor(index); + m_editor->widget()->setFocus(); +} + +static bool clientSupportsDocumentSymbols(const BaseClient *client, const TextEditor::TextDocument *doc) +{ + DynamicCapabilities dc = client->dynamicCapabilities(); + if (dc.isRegistered(DocumentSymbolsRequest::methodName).value_or(false)) { + TextDocumentRegistrationOptions options(dc.option(DocumentSymbolsRequest::methodName)); + return !options.isValid(nullptr) + || options.filterApplies(doc->filePath(), Utils::mimeTypeForName(doc->mimeType())); + } + return client->capabilities().documentSymbolProvider().value_or(false); +} + +bool LanguageClientOutlineWidgetFactory::supportsEditor(Core::IEditor *editor) const +{ + auto doc = qobject_cast<TextEditor::TextDocument *>(editor->document()); + if (!doc) + return false; + auto clients = LanguageClientManager::clientsSupportingDocument(doc); + return Utils::anyOf(clients, [doc](const BaseClient *client){ + return clientSupportsDocumentSymbols(client, doc); + }); +} + +TextEditor::IOutlineWidget *LanguageClientOutlineWidgetFactory::createWidget(Core::IEditor *editor) +{ + auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor); + QTC_ASSERT(textEditor, return nullptr); + QList<BaseClient *> clients = LanguageClientManager::clientsSupportingDocument(textEditor->textDocument()); + QTC_ASSERT(!clients.isEmpty(), return nullptr); + clients = Utils::filtered(clients, [doc = textEditor->textDocument()](const BaseClient *client){ + return clientSupportsDocumentSymbols(client, doc); + }); + return new LanguageClientOutlineWidget(clients.first(), textEditor); +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientoutline.h b/src/plugins/languageclient/languageclientoutline.h new file mode 100644 index 0000000000..100f8a783d --- /dev/null +++ b/src/plugins/languageclient/languageclientoutline.h @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 <texteditor/ioutlinewidget.h> + +namespace LanguageClient { + +class LanguageClientOutlineWidgetFactory : public TextEditor::IOutlineWidgetFactory +{ +public: + using IOutlineWidgetFactory::IOutlineWidgetFactory; + + // IOutlineWidgetFactory interface +public: + bool supportsEditor(Core::IEditor *editor) const override; + TextEditor::IOutlineWidget *createWidget(Core::IEditor *editor) override; +}; + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientplugin.h b/src/plugins/languageclient/languageclientplugin.h index 5b34c049a7..2cff52cd7b 100644 --- a/src/plugins/languageclient/languageclientplugin.h +++ b/src/plugins/languageclient/languageclientplugin.h @@ -26,6 +26,7 @@ #pragma once #include "languageclientmanager.h" +#include "languageclientoutline.h" #include "languageclientsettings.h" #include <extensionsystem/iplugin.h> @@ -47,6 +48,7 @@ private: private: LanguageClientManager m_clientManager; + LanguageClientOutlineWidgetFactory m_outlineFactory; }; } // namespace LanguageClient |