2020-02-20 10:13:14 +01:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2019 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.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2021-02-10 14:03:45 +01:00
|
|
|
#include "lspinspector.h"
|
2020-02-20 10:13:14 +01:00
|
|
|
|
|
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
#include <coreplugin/minisplitter.h>
|
|
|
|
|
#include <languageserverprotocol/jsonkeys.h>
|
|
|
|
|
#include <languageserverprotocol/jsonrpcmessages.h>
|
|
|
|
|
#include <utils/jsontreeitem.h>
|
|
|
|
|
#include <utils/listmodel.h>
|
|
|
|
|
|
2021-02-08 14:27:07 +01:00
|
|
|
#include <QAction>
|
2020-02-20 10:13:14 +01:00
|
|
|
#include <QDialog>
|
|
|
|
|
#include <QDialogButtonBox>
|
2021-07-13 11:00:14 +02:00
|
|
|
#include <QElapsedTimer>
|
2020-02-20 10:13:14 +01:00
|
|
|
#include <QFormLayout>
|
|
|
|
|
#include <QGroupBox>
|
|
|
|
|
#include <QHeaderView>
|
|
|
|
|
#include <QLabel>
|
|
|
|
|
#include <QListWidget>
|
|
|
|
|
#include <QPlainTextEdit>
|
2021-09-09 15:46:08 +02:00
|
|
|
#include <QPushButton>
|
2020-02-20 10:13:14 +01:00
|
|
|
#include <QSplitter>
|
|
|
|
|
#include <QStyledItemDelegate>
|
|
|
|
|
#include <QTextCodec>
|
|
|
|
|
#include <QTreeView>
|
|
|
|
|
|
|
|
|
|
using namespace LanguageServerProtocol;
|
2021-08-17 16:36:42 +02:00
|
|
|
using namespace Utils;
|
2020-02-20 10:13:14 +01:00
|
|
|
|
|
|
|
|
namespace LanguageClient {
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
class JsonTreeItemDelegate : public QStyledItemDelegate
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
QString displayText(const QVariant &value, const QLocale &) const override
|
|
|
|
|
{
|
|
|
|
|
QString result = value.toString();
|
|
|
|
|
if (result.size() == 1) {
|
|
|
|
|
switch (result.at(0).toLatin1()) {
|
|
|
|
|
case '\n':
|
|
|
|
|
return QString("\\n");
|
|
|
|
|
case '\t':
|
|
|
|
|
return QString("\\t");
|
|
|
|
|
case '\r':
|
|
|
|
|
return QString("\\r");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
using JsonModel = Utils::TreeModel<Utils::JsonTreeItem>;
|
|
|
|
|
|
|
|
|
|
JsonModel *createJsonModel(const QString &displayName, const QJsonValue &value)
|
|
|
|
|
{
|
|
|
|
|
if (value.isNull())
|
|
|
|
|
return nullptr;
|
|
|
|
|
auto root = new Utils::JsonTreeItem(displayName, value);
|
|
|
|
|
if (root->canFetchMore())
|
|
|
|
|
root->fetchMore();
|
|
|
|
|
|
|
|
|
|
auto model = new JsonModel(root);
|
|
|
|
|
model->setHeader({{"Name"}, {"Value"}, {"Type"}});
|
|
|
|
|
return model;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTreeView *createJsonTreeView()
|
|
|
|
|
{
|
|
|
|
|
auto view = new QTreeView;
|
|
|
|
|
view->setContextMenuPolicy(Qt::ActionsContextMenu);
|
|
|
|
|
auto action = new QAction(LspInspector::tr("Expand All"), view);
|
|
|
|
|
QObject::connect(action, &QAction::triggered, view, &QTreeView::expandAll);
|
|
|
|
|
view->addAction(action);
|
|
|
|
|
view->setAlternatingRowColors(true);
|
|
|
|
|
view->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
|
|
|
|
view->setItemDelegate(new JsonTreeItemDelegate);
|
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTreeView *createJsonTreeView(const QString &displayName, const QJsonValue &value)
|
|
|
|
|
{
|
|
|
|
|
auto view = createJsonTreeView();
|
|
|
|
|
view->setModel(createJsonModel(displayName, value));
|
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-20 10:13:14 +01:00
|
|
|
class MessageDetailWidget : public QGroupBox
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
MessageDetailWidget();
|
|
|
|
|
|
2021-07-13 11:00:14 +02:00
|
|
|
void setMessage(const LspLogMessage &message);
|
2020-02-20 10:13:14 +01:00
|
|
|
void clear();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QLabel *m_contentLength = nullptr;
|
|
|
|
|
QLabel *m_mimeType = nullptr;
|
|
|
|
|
};
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
class LspCapabilitiesWidget : public QWidget
|
2020-02-20 10:13:14 +01:00
|
|
|
{
|
2021-02-11 09:32:11 +01:00
|
|
|
Q_DECLARE_TR_FUNCTIONS(LspCapabilitiesWidget)
|
2020-02-20 10:13:14 +01:00
|
|
|
public:
|
2021-02-11 09:32:11 +01:00
|
|
|
LspCapabilitiesWidget();
|
|
|
|
|
void setCapabilities(const Capabilities &serverCapabilities);
|
2020-02-20 10:13:14 +01:00
|
|
|
|
|
|
|
|
private:
|
2021-02-11 09:32:11 +01:00
|
|
|
void updateOptionsView(const QString &method);
|
2020-02-20 10:13:14 +01:00
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
DynamicCapabilities m_dynamicCapabilities;
|
|
|
|
|
QTreeView *m_capabilitiesView = nullptr;
|
|
|
|
|
QListWidget *m_dynamicCapabilitiesView = nullptr;
|
|
|
|
|
QTreeView *m_dynamicOptionsView = nullptr;
|
|
|
|
|
QGroupBox *m_dynamicCapabilitiesGroup = nullptr;
|
2020-02-20 10:13:14 +01:00
|
|
|
};
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
LspCapabilitiesWidget::LspCapabilitiesWidget()
|
2020-02-20 10:13:14 +01:00
|
|
|
{
|
2021-02-11 09:32:11 +01:00
|
|
|
auto mainLayout = new QHBoxLayout;
|
|
|
|
|
|
|
|
|
|
auto group = new QGroupBox(tr("Capabilities:"));
|
|
|
|
|
QLayout *layout = new QHBoxLayout;
|
|
|
|
|
m_capabilitiesView = createJsonTreeView();
|
|
|
|
|
layout->addWidget(m_capabilitiesView);
|
|
|
|
|
group->setLayout(layout);
|
|
|
|
|
mainLayout->addWidget(group);
|
|
|
|
|
|
|
|
|
|
m_dynamicCapabilitiesGroup = new QGroupBox(tr("Dynamic Capabilities:"));
|
|
|
|
|
layout = new QVBoxLayout;
|
|
|
|
|
auto label = new QLabel(tr("Method:"));
|
|
|
|
|
layout->addWidget(label);
|
|
|
|
|
m_dynamicCapabilitiesView = new QListWidget();
|
|
|
|
|
layout->addWidget(m_dynamicCapabilitiesView);
|
|
|
|
|
label = new QLabel(tr("Options:"));
|
|
|
|
|
layout->addWidget(label);
|
|
|
|
|
m_dynamicOptionsView = createJsonTreeView();
|
|
|
|
|
layout->addWidget(m_dynamicOptionsView);
|
|
|
|
|
m_dynamicCapabilitiesGroup->setLayout(layout);
|
|
|
|
|
mainLayout->addWidget(m_dynamicCapabilitiesGroup);
|
|
|
|
|
|
|
|
|
|
setLayout(mainLayout);
|
|
|
|
|
|
|
|
|
|
connect(m_dynamicCapabilitiesView,
|
|
|
|
|
&QListWidget::currentTextChanged,
|
|
|
|
|
this,
|
|
|
|
|
&LspCapabilitiesWidget::updateOptionsView);
|
2020-02-20 10:13:14 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
void LspCapabilitiesWidget::setCapabilities(const Capabilities &serverCapabilities)
|
2020-02-20 10:13:14 +01:00
|
|
|
{
|
2021-02-11 09:32:11 +01:00
|
|
|
m_capabilitiesView->setModel(
|
|
|
|
|
createJsonModel(tr("Server Capabilities"), QJsonObject(serverCapabilities.capabilities)));
|
|
|
|
|
m_dynamicCapabilities = serverCapabilities.dynamicCapabilities;
|
|
|
|
|
const QStringList &methods = m_dynamicCapabilities.registeredMethods();
|
|
|
|
|
if (methods.isEmpty()) {
|
|
|
|
|
m_dynamicCapabilitiesGroup->hide();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
m_dynamicCapabilitiesGroup->show();
|
|
|
|
|
m_dynamicCapabilitiesView->clear();
|
|
|
|
|
m_dynamicCapabilitiesView->addItems(methods);
|
2020-02-20 10:13:14 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
void LspCapabilitiesWidget::updateOptionsView(const QString &method)
|
2020-02-20 10:13:14 +01:00
|
|
|
{
|
2021-02-11 09:32:11 +01:00
|
|
|
QAbstractItemModel *oldModel = m_dynamicOptionsView->model();
|
|
|
|
|
m_dynamicOptionsView->setModel(createJsonModel(method, m_dynamicCapabilities.option(method)));
|
|
|
|
|
delete oldModel;
|
2020-02-20 10:13:14 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
class LspLogWidget : public Core::MiniSplitter
|
2020-02-20 10:13:14 +01:00
|
|
|
{
|
2021-02-11 09:32:11 +01:00
|
|
|
public:
|
|
|
|
|
LspLogWidget();
|
|
|
|
|
|
|
|
|
|
void addMessage(const LspLogMessage &message);
|
|
|
|
|
void setMessages(const std::list<LspLogMessage> &messages);
|
|
|
|
|
void saveLog();
|
|
|
|
|
|
|
|
|
|
MessageDetailWidget *m_clientDetails = nullptr;
|
|
|
|
|
QListView *m_messages = nullptr;
|
|
|
|
|
MessageDetailWidget *m_serverDetails = nullptr;
|
|
|
|
|
Utils::ListModel<LspLogMessage> m_model;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void currentMessageChanged(const QModelIndex &index);
|
2021-07-13 11:00:14 +02:00
|
|
|
void selectMatchingMessage(const LspLogMessage &message);
|
2021-02-11 09:32:11 +01:00
|
|
|
};
|
2020-02-20 10:13:14 +01:00
|
|
|
|
|
|
|
|
static QVariant messageData(const LspLogMessage &message, int, int role)
|
|
|
|
|
{
|
2021-07-13 11:00:14 +02:00
|
|
|
if (role == Qt::DisplayRole)
|
|
|
|
|
return message.displayText();
|
2020-02-20 10:13:14 +01:00
|
|
|
if (role == Qt::TextAlignmentRole)
|
|
|
|
|
return message.sender == LspLogMessage::ClientMessage ? Qt::AlignLeft : Qt::AlignRight;
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
LspLogWidget::LspLogWidget()
|
2020-02-20 10:13:14 +01:00
|
|
|
{
|
2021-02-11 09:32:11 +01:00
|
|
|
setOrientation(Qt::Horizontal);
|
2020-02-20 10:13:14 +01:00
|
|
|
|
|
|
|
|
m_clientDetails = new MessageDetailWidget;
|
|
|
|
|
m_clientDetails->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
2021-03-19 10:46:11 +01:00
|
|
|
m_clientDetails->setTitle(LspInspector::tr("Client Message"));
|
2021-02-11 09:32:11 +01:00
|
|
|
addWidget(m_clientDetails);
|
|
|
|
|
setStretchFactor(0, 1);
|
2020-02-20 10:13:14 +01:00
|
|
|
|
|
|
|
|
m_model.setDataAccessor(&messageData);
|
|
|
|
|
m_messages = new QListView;
|
|
|
|
|
m_messages->setModel(&m_model);
|
|
|
|
|
m_messages->setAlternatingRowColors(true);
|
2021-03-19 10:46:11 +01:00
|
|
|
m_model.setHeader({LspInspector::tr("Messages")});
|
2020-02-20 10:13:14 +01:00
|
|
|
m_messages->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
|
|
|
|
|
m_messages->setSelectionMode(QAbstractItemView::MultiSelection);
|
2021-02-11 09:32:11 +01:00
|
|
|
addWidget(m_messages);
|
|
|
|
|
setStretchFactor(1, 0);
|
2020-02-20 10:13:14 +01:00
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
m_serverDetails = new MessageDetailWidget;
|
|
|
|
|
m_serverDetails->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
2021-03-19 10:46:11 +01:00
|
|
|
m_serverDetails->setTitle(LspInspector::tr("Server Message"));
|
2021-02-11 09:32:11 +01:00
|
|
|
addWidget(m_serverDetails);
|
|
|
|
|
setStretchFactor(2, 1);
|
2020-02-20 10:13:14 +01:00
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
connect(m_messages->selectionModel(),
|
|
|
|
|
&QItemSelectionModel::currentChanged,
|
|
|
|
|
this,
|
|
|
|
|
&LspLogWidget::currentMessageChanged);
|
2020-02-20 10:13:14 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
void LspLogWidget::currentMessageChanged(const QModelIndex &index)
|
2020-02-20 10:13:14 +01:00
|
|
|
{
|
|
|
|
|
m_messages->clearSelection();
|
|
|
|
|
if (!index.isValid())
|
|
|
|
|
return;
|
2021-07-13 11:00:14 +02:00
|
|
|
LspLogMessage message = m_model.itemAt(index.row())->itemData;
|
|
|
|
|
if (message.sender == LspLogMessage::ClientMessage)
|
2020-02-20 10:13:14 +01:00
|
|
|
m_clientDetails->setMessage(message);
|
|
|
|
|
else
|
|
|
|
|
m_serverDetails->setMessage(message);
|
2021-07-13 11:00:14 +02:00
|
|
|
selectMatchingMessage(message);
|
2020-02-20 10:13:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool matches(LspLogMessage::MessageSender sender,
|
2021-07-13 11:00:14 +02:00
|
|
|
const MessageId &id,
|
2020-02-20 10:13:14 +01:00
|
|
|
const LspLogMessage &message)
|
|
|
|
|
{
|
|
|
|
|
if (message.sender != sender)
|
|
|
|
|
return false;
|
|
|
|
|
if (message.message.mimeType != JsonRpcMessageHandler::jsonRpcMimeType())
|
|
|
|
|
return false;
|
2021-07-13 11:00:14 +02:00
|
|
|
return message.id() == id;
|
2020-02-20 10:13:14 +01:00
|
|
|
}
|
|
|
|
|
|
2021-07-13 11:00:14 +02:00
|
|
|
void LspLogWidget::selectMatchingMessage(const LspLogMessage &message)
|
2020-02-20 10:13:14 +01:00
|
|
|
{
|
2021-07-13 11:00:14 +02:00
|
|
|
MessageId id = message.id();
|
|
|
|
|
if (!id.isValid())
|
|
|
|
|
return;
|
|
|
|
|
LspLogMessage::MessageSender sender = message.sender == LspLogMessage::ServerMessage
|
|
|
|
|
? LspLogMessage::ClientMessage
|
|
|
|
|
: LspLogMessage::ServerMessage;
|
2020-02-20 10:13:14 +01:00
|
|
|
LspLogMessage *matchingMessage = m_model.findData(
|
|
|
|
|
[&](const LspLogMessage &message) { return matches(sender, id, message); });
|
|
|
|
|
if (!matchingMessage)
|
|
|
|
|
return;
|
2020-05-20 08:34:05 +02:00
|
|
|
auto index = m_model.findIndex(
|
2020-02-20 10:13:14 +01:00
|
|
|
[&](const LspLogMessage &message) { return &message == matchingMessage; });
|
|
|
|
|
|
2020-05-20 08:34:05 +02:00
|
|
|
m_messages->selectionModel()->select(index, QItemSelectionModel::Select);
|
2020-02-20 10:13:14 +01:00
|
|
|
if (matchingMessage->sender == LspLogMessage::ServerMessage)
|
2021-07-13 11:00:14 +02:00
|
|
|
m_serverDetails->setMessage(*matchingMessage);
|
2020-02-20 10:13:14 +01:00
|
|
|
else
|
2021-07-13 11:00:14 +02:00
|
|
|
m_clientDetails->setMessage(*matchingMessage);
|
2020-02-20 10:13:14 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
void LspLogWidget::addMessage(const LspLogMessage &message)
|
|
|
|
|
{
|
|
|
|
|
m_model.appendItem(message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LspLogWidget::setMessages(const std::list<LspLogMessage> &messages)
|
|
|
|
|
{
|
|
|
|
|
m_model.clear();
|
|
|
|
|
for (const LspLogMessage &message : messages)
|
|
|
|
|
m_model.appendItem(message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LspLogWidget::saveLog()
|
2020-02-20 10:13:14 +01:00
|
|
|
{
|
|
|
|
|
QString contents;
|
|
|
|
|
QTextStream stream(&contents);
|
2020-05-20 08:24:49 +02:00
|
|
|
m_model.forAllData([&](const LspLogMessage &message) {
|
2020-02-20 10:13:14 +01:00
|
|
|
stream << message.time.toString("hh:mm:ss.zzz") << ' ';
|
|
|
|
|
stream << (message.sender == LspLogMessage::ClientMessage ? QString{"Client"}
|
|
|
|
|
: QString{"Server"});
|
|
|
|
|
stream << '\n';
|
|
|
|
|
stream << message.message.codec->toUnicode(message.message.content);
|
|
|
|
|
stream << "\n\n";
|
|
|
|
|
});
|
|
|
|
|
|
2021-08-17 16:36:42 +02:00
|
|
|
const FilePath filePath = FileUtils::getSaveFilePath(this, LspInspector::tr("Log File"));
|
|
|
|
|
if (filePath.isEmpty())
|
2020-02-20 10:13:14 +01:00
|
|
|
return;
|
2021-08-17 16:36:42 +02:00
|
|
|
FileSaver saver(filePath, QIODevice::Text);
|
2020-02-20 10:13:14 +01:00
|
|
|
saver.write(contents.toUtf8());
|
|
|
|
|
if (!saver.finalize(this))
|
|
|
|
|
saveLog();
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
class LspInspectorWidget : public QDialog
|
|
|
|
|
{
|
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(LspInspectorWidget)
|
|
|
|
|
public:
|
|
|
|
|
explicit LspInspectorWidget(LspInspector *inspector);
|
|
|
|
|
|
2021-02-11 09:54:09 +01:00
|
|
|
void selectClient(const QString &clientName);
|
2021-02-11 09:32:11 +01:00
|
|
|
private:
|
|
|
|
|
void addMessage(const QString &clientName, const LspLogMessage &message);
|
|
|
|
|
void updateCapabilities(const QString &clientName);
|
2021-02-11 09:54:09 +01:00
|
|
|
void currentClientChanged(const QString &clientName);
|
2021-02-11 09:32:11 +01:00
|
|
|
|
|
|
|
|
LspInspector *m_inspector = nullptr;
|
|
|
|
|
LspLogWidget *m_log = nullptr;
|
|
|
|
|
LspCapabilitiesWidget *m_capabilities = nullptr;
|
|
|
|
|
QListWidget *m_clients = nullptr;
|
|
|
|
|
};
|
|
|
|
|
|
2021-02-11 09:54:09 +01:00
|
|
|
QWidget *LspInspector::createWidget(const QString &defaultClient)
|
2021-02-11 09:32:11 +01:00
|
|
|
{
|
2021-02-11 09:54:09 +01:00
|
|
|
auto *inspector = new LspInspectorWidget(this);
|
|
|
|
|
inspector->selectClient(defaultClient);
|
|
|
|
|
return inspector;
|
2021-02-11 09:32:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LspInspector::log(const LspLogMessage::MessageSender sender,
|
|
|
|
|
const QString &clientName,
|
|
|
|
|
const BaseMessage &message)
|
|
|
|
|
{
|
|
|
|
|
std::list<LspLogMessage> &clientLog = m_logs[clientName];
|
|
|
|
|
while (clientLog.size() >= static_cast<std::size_t>(m_logSize))
|
|
|
|
|
clientLog.pop_front();
|
|
|
|
|
clientLog.push_back({sender, QTime::currentTime(), message});
|
|
|
|
|
emit newMessage(clientName, clientLog.back());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LspInspector::clientInitialized(const QString &clientName, const ServerCapabilities &capabilities)
|
|
|
|
|
{
|
|
|
|
|
m_capabilities[clientName].capabilities = capabilities;
|
|
|
|
|
m_capabilities[clientName].dynamicCapabilities.reset();
|
|
|
|
|
emit capabilitiesUpdated(clientName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LspInspector::updateCapabilities(const QString &clientName,
|
|
|
|
|
const DynamicCapabilities &dynamicCapabilities)
|
|
|
|
|
{
|
|
|
|
|
m_capabilities[clientName].dynamicCapabilities = dynamicCapabilities;
|
|
|
|
|
emit capabilitiesUpdated(clientName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::list<LspLogMessage> LspInspector::messages(const QString &clientName) const
|
|
|
|
|
{
|
2021-02-11 09:54:09 +01:00
|
|
|
return m_logs.value(clientName);
|
2021-02-11 09:32:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Capabilities LspInspector::capabilities(const QString &clientName) const
|
|
|
|
|
{
|
|
|
|
|
return m_capabilities.value(clientName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<QString> LspInspector::clients() const
|
|
|
|
|
{
|
|
|
|
|
return m_logs.keys();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LspInspectorWidget::LspInspectorWidget(LspInspector *inspector)
|
|
|
|
|
: m_inspector(inspector)
|
|
|
|
|
{
|
|
|
|
|
setWindowTitle(tr("Language Client Inspector"));
|
|
|
|
|
|
|
|
|
|
connect(inspector, &LspInspector::newMessage, this, &LspInspectorWidget::addMessage);
|
|
|
|
|
connect(inspector, &LspInspector::capabilitiesUpdated,
|
|
|
|
|
this, &LspInspectorWidget::updateCapabilities);
|
|
|
|
|
connect(Core::ICore::instance(), &Core::ICore::coreAboutToClose, this, &QWidget::close);
|
|
|
|
|
|
|
|
|
|
m_clients = new QListWidget;
|
|
|
|
|
m_clients->addItems(inspector->clients());
|
|
|
|
|
m_clients->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding);
|
|
|
|
|
|
|
|
|
|
auto tabWidget = new QTabWidget;
|
|
|
|
|
|
|
|
|
|
auto mainLayout = new QVBoxLayout;
|
|
|
|
|
auto mainSplitter = new Core::MiniSplitter;
|
|
|
|
|
mainSplitter->setOrientation(Qt::Horizontal);
|
|
|
|
|
mainSplitter->addWidget(m_clients);
|
|
|
|
|
mainSplitter->addWidget(tabWidget);
|
|
|
|
|
mainSplitter->setStretchFactor(0, 0);
|
|
|
|
|
mainSplitter->setStretchFactor(1, 1);
|
|
|
|
|
m_log = new LspLogWidget;
|
|
|
|
|
m_capabilities = new LspCapabilitiesWidget;
|
|
|
|
|
tabWidget->addTab(m_log, tr("Log"));
|
|
|
|
|
tabWidget->addTab(m_capabilities, tr("Capabilities"));
|
|
|
|
|
mainLayout->addWidget(mainSplitter);
|
|
|
|
|
|
|
|
|
|
auto buttonBox = new QDialogButtonBox(this);
|
|
|
|
|
buttonBox->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Close);
|
2021-09-09 15:46:08 +02:00
|
|
|
const auto clearButton = buttonBox->addButton(tr("Clear"), QDialogButtonBox::ResetRole);
|
|
|
|
|
connect(clearButton, &QPushButton::clicked, this, [this] {
|
|
|
|
|
m_inspector->clear();
|
|
|
|
|
currentClientChanged(m_clients->currentItem()->text());
|
|
|
|
|
});
|
2021-02-11 09:32:11 +01:00
|
|
|
mainLayout->addWidget(buttonBox);
|
|
|
|
|
setLayout(mainLayout);
|
|
|
|
|
|
|
|
|
|
connect(m_clients,
|
|
|
|
|
&QListWidget::currentTextChanged,
|
|
|
|
|
this,
|
2021-02-11 09:54:09 +01:00
|
|
|
&LspInspectorWidget::currentClientChanged);
|
2021-02-11 09:32:11 +01:00
|
|
|
|
|
|
|
|
// save
|
|
|
|
|
connect(buttonBox, &QDialogButtonBox::accepted, m_log, &LspLogWidget::saveLog);
|
|
|
|
|
|
|
|
|
|
// close
|
|
|
|
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
|
|
|
|
resize(1024, 768);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 09:54:09 +01:00
|
|
|
void LspInspectorWidget::selectClient(const QString &clientName)
|
|
|
|
|
{
|
|
|
|
|
auto items = m_clients->findItems(clientName, Qt::MatchExactly);
|
|
|
|
|
if (items.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
m_clients->setCurrentItem(items.first());
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 09:32:11 +01:00
|
|
|
void LspInspectorWidget::addMessage(const QString &clientName, const LspLogMessage &message)
|
|
|
|
|
{
|
|
|
|
|
if (m_clients->findItems(clientName, Qt::MatchExactly).isEmpty())
|
|
|
|
|
m_clients->addItem(clientName);
|
2021-06-03 10:35:40 +02:00
|
|
|
if (const QListWidgetItem *currentItem = m_clients->currentItem();
|
|
|
|
|
currentItem && currentItem->text() == clientName) {
|
2021-02-11 09:32:11 +01:00
|
|
|
m_log->addMessage(message);
|
2021-06-03 10:35:40 +02:00
|
|
|
}
|
2021-02-11 09:32:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LspInspectorWidget::updateCapabilities(const QString &clientName)
|
|
|
|
|
{
|
|
|
|
|
if (m_clients->findItems(clientName, Qt::MatchExactly).isEmpty())
|
|
|
|
|
m_clients->addItem(clientName);
|
2021-06-03 10:35:40 +02:00
|
|
|
if (const QListWidgetItem *currentItem = m_clients->currentItem();
|
|
|
|
|
currentItem && clientName == currentItem->text()) {
|
2021-02-11 09:32:11 +01:00
|
|
|
m_capabilities->setCapabilities(m_inspector->capabilities(clientName));
|
2021-06-03 10:35:40 +02:00
|
|
|
}
|
2021-02-11 09:32:11 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 09:54:09 +01:00
|
|
|
void LspInspectorWidget::currentClientChanged(const QString &clientName)
|
2021-02-11 09:32:11 +01:00
|
|
|
{
|
|
|
|
|
m_log->setMessages(m_inspector->messages(clientName));
|
|
|
|
|
m_capabilities->setCapabilities(m_inspector->capabilities(clientName));
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-20 10:13:14 +01:00
|
|
|
MessageDetailWidget::MessageDetailWidget()
|
|
|
|
|
{
|
|
|
|
|
auto layout = new QFormLayout;
|
|
|
|
|
setLayout(layout);
|
|
|
|
|
|
|
|
|
|
m_contentLength = new QLabel;
|
|
|
|
|
m_mimeType = new QLabel;
|
|
|
|
|
|
|
|
|
|
layout->addRow("Content Length:", m_contentLength);
|
|
|
|
|
layout->addRow("MIME Type:", m_mimeType);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-13 11:00:14 +02:00
|
|
|
void MessageDetailWidget::setMessage(const LspLogMessage &message)
|
2020-02-20 10:13:14 +01:00
|
|
|
{
|
2021-07-13 11:00:14 +02:00
|
|
|
m_contentLength->setText(QString::number(message.message.contentLength));
|
|
|
|
|
m_mimeType->setText(QString::fromLatin1(message.message.mimeType));
|
2020-02-20 10:13:14 +01:00
|
|
|
|
|
|
|
|
QWidget *newContentWidget = nullptr;
|
2021-07-13 11:00:14 +02:00
|
|
|
if (message.message.mimeType == JsonRpcMessageHandler::jsonRpcMimeType()) {
|
|
|
|
|
newContentWidget = createJsonTreeView("content", message.json());
|
2020-02-20 10:13:14 +01:00
|
|
|
} else {
|
|
|
|
|
auto edit = new QPlainTextEdit();
|
|
|
|
|
edit->setReadOnly(true);
|
2021-07-13 11:00:14 +02:00
|
|
|
edit->setPlainText(message.message.codec->toUnicode(message.message.content));
|
2020-02-20 10:13:14 +01:00
|
|
|
newContentWidget = edit;
|
|
|
|
|
}
|
|
|
|
|
auto formLayout = static_cast<QFormLayout *>(layout());
|
|
|
|
|
if (formLayout->rowCount() > 2)
|
|
|
|
|
formLayout->removeRow(2);
|
|
|
|
|
formLayout->setWidget(2, QFormLayout::SpanningRole, newContentWidget);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MessageDetailWidget::clear()
|
|
|
|
|
{
|
|
|
|
|
m_contentLength->setText({});
|
|
|
|
|
m_mimeType->setText({});
|
|
|
|
|
auto formLayout = static_cast<QFormLayout *>(layout());
|
|
|
|
|
if (formLayout->rowCount() > 2)
|
|
|
|
|
formLayout->removeRow(2);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-13 11:00:14 +02:00
|
|
|
LspLogMessage::LspLogMessage() = default;
|
|
|
|
|
|
|
|
|
|
LspLogMessage::LspLogMessage(MessageSender sender,
|
|
|
|
|
const QTime &time,
|
|
|
|
|
const LanguageServerProtocol::BaseMessage &message)
|
|
|
|
|
: sender(sender)
|
|
|
|
|
, time(time)
|
|
|
|
|
, message(message)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
MessageId LspLogMessage::id() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_id.has_value())
|
|
|
|
|
m_id = MessageId(json().value(idKey));
|
|
|
|
|
return *m_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString LspLogMessage::displayText() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_displayText.has_value()) {
|
2021-07-14 12:37:07 +02:00
|
|
|
m_displayText = QString(time.toString("hh:mm:ss.zzz") + '\n');
|
2021-07-13 11:00:14 +02:00
|
|
|
if (message.mimeType == JsonRpcMessageHandler::jsonRpcMimeType())
|
|
|
|
|
m_displayText->append(json().value(QString{methodKey}).toString(id().toString()));
|
|
|
|
|
else
|
|
|
|
|
m_displayText->append(message.codec->toUnicode(message.content));
|
|
|
|
|
}
|
|
|
|
|
return *m_displayText;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QJsonObject &LspLogMessage::json() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_json.has_value()) {
|
|
|
|
|
if (message.mimeType == JsonRpcMessageHandler::jsonRpcMimeType()) {
|
|
|
|
|
QString error;
|
|
|
|
|
m_json = JsonRpcMessageHandler::toJsonObject(message.content, message.codec, error);
|
|
|
|
|
} else {
|
|
|
|
|
m_json = QJsonObject();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return *m_json;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-20 10:13:14 +01:00
|
|
|
} // namespace LanguageClient
|