summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Schulz <david.schulz@qt.io>2018-11-23 09:45:51 +0100
committerDavid Schulz <david.schulz@qt.io>2019-01-10 07:26:08 +0000
commit0a8a4bad7ef2eaece6e15ae5a11ee1ff27885652 (patch)
tree3c2590dd06344dcd5295a8f4109670a85f528a4b
parent7b494c068eaa4012471b5f80be1ecfd964448cab (diff)
downloadqt-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.cpp10
-rw-r--r--src/plugins/languageclient/baseclient.h3
-rw-r--r--src/plugins/languageclient/languageclient.pro3
-rw-r--r--src/plugins/languageclient/languageclient.qbs2
-rw-r--r--src/plugins/languageclient/languageclientmanager.cpp9
-rw-r--r--src/plugins/languageclient/languageclientmanager.h2
-rw-r--r--src/plugins/languageclient/languageclientoutline.cpp293
-rw-r--r--src/plugins/languageclient/languageclientoutline.h43
-rw-r--r--src/plugins/languageclient/languageclientplugin.h2
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 &currentCursor);
+ 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 &currentCursor)
+{
+ 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