forked from qt-creator/qt-creator
LanguageClient: move capabilities widget to inspector
The capabilities are not changeable for the user, but only there to check whether a server is capable of a specific task. This will also allow us to have more specialized settings widgets for specific servers like for the java language server without the need to add the capabilities to each of those special widgets. Also add the dynamic capabilities to the widget so users have a complete overview of the capabilities. Change-Id: I9f2ed6ed11b458f0d4c67be3df632fd810023286 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -597,11 +597,13 @@ void Client::registerCapabilities(const QList<Registration> ®istrations)
|
||||
for (auto document : m_openedDocument.keys())
|
||||
updateFunctionHintProvider(document);
|
||||
}
|
||||
emit capabilitiesChanged(m_dynamicCapabilities);
|
||||
}
|
||||
|
||||
void Client::unregisterCapabilities(const QList<Unregistration> &unregistrations)
|
||||
{
|
||||
m_dynamicCapabilities.unregisterCapability(unregistrations);
|
||||
emit capabilitiesChanged(m_dynamicCapabilities);
|
||||
}
|
||||
|
||||
TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation &info)
|
||||
@@ -1079,7 +1081,7 @@ void Client::handleMethod(const QString &method, const MessageId &id, const ICon
|
||||
} else if (method == UnregisterCapabilityRequest::methodName) {
|
||||
auto params = dynamic_cast<const UnregisterCapabilityRequest *>(content)->params().value_or(UnregistrationParams());
|
||||
if (params.isValid(&error))
|
||||
m_dynamicCapabilities.unregisterCapability(params.unregistrations());
|
||||
unregisterCapabilities(params.unregistrations());
|
||||
else
|
||||
logError(params);
|
||||
} else if (method == ApplyWorkspaceEditRequest::methodName) {
|
||||
|
@@ -170,7 +170,8 @@ public:
|
||||
bool documentUpdatePostponed(const Utils::FilePath &fileName) const;
|
||||
|
||||
signals:
|
||||
void initialized(LanguageServerProtocol::ServerCapabilities capabilities);
|
||||
void initialized(const LanguageServerProtocol::ServerCapabilities &capabilities);
|
||||
void capabilitiesChanged(const DynamicCapabilities &capabilities);
|
||||
void documentUpdated(TextEditor::TextDocument *document);
|
||||
void finished();
|
||||
|
||||
|
@@ -56,6 +56,11 @@ Utils::optional<bool> DynamicCapabilities::isRegistered(const QString &method) c
|
||||
return m_capability[method].enabled();
|
||||
}
|
||||
|
||||
QStringList DynamicCapabilities::registeredMethods() const
|
||||
{
|
||||
return m_capability.keys();
|
||||
}
|
||||
|
||||
void DynamicCapabilities::reset()
|
||||
{
|
||||
m_capability.clear();
|
||||
|
@@ -68,7 +68,8 @@ public:
|
||||
void unregisterCapability(const QList<LanguageServerProtocol::Unregistration> &unregistrations);
|
||||
|
||||
Utils::optional<bool> isRegistered(const QString &method) const;
|
||||
QJsonValue option(const QString &method) const { return m_capability[method].options(); }
|
||||
QJsonValue option(const QString &method) const { return m_capability.value(method).options(); }
|
||||
QStringList registeredMethods() const;
|
||||
|
||||
void reset();
|
||||
|
||||
|
@@ -108,13 +108,20 @@ void LanguageClientManager::clientStarted(Client *client)
|
||||
}
|
||||
if (!managerInstance->m_clients.contains(client)) {
|
||||
managerInstance->m_clients << client;
|
||||
connect(client, &Client::finished, managerInstance, [client](){
|
||||
clientFinished(client);
|
||||
});
|
||||
connect(client, &Client::finished, managerInstance, [client]() { clientFinished(client); });
|
||||
connect(client,
|
||||
&Client::initialized,
|
||||
&managerInstance->m_currentDocumentLocatorFilter,
|
||||
&DocumentLocatorFilter::updateCurrentClient);
|
||||
managerInstance,
|
||||
[client](const LanguageServerProtocol::ServerCapabilities &capabilities) {
|
||||
managerInstance->m_currentDocumentLocatorFilter.updateCurrentClient();
|
||||
managerInstance->m_inspector.clientInitialized(client->name(), capabilities);
|
||||
});
|
||||
connect(client,
|
||||
&Client::capabilitiesChanged,
|
||||
managerInstance,
|
||||
[client](const DynamicCapabilities &capabilities) {
|
||||
managerInstance->m_inspector.updateCapabilities(client->name(), capabilities);
|
||||
});
|
||||
}
|
||||
|
||||
client->initialize();
|
||||
|
@@ -740,24 +740,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static QWidget *createCapabilitiesView(const QJsonValue &capabilities)
|
||||
{
|
||||
auto root = new Utils::JsonTreeItem("Capabilities", capabilities);
|
||||
if (root->canFetchMore())
|
||||
root->fetchMore();
|
||||
|
||||
auto capabilitiesModel = new Utils::TreeModel<Utils::JsonTreeItem>(root);
|
||||
capabilitiesModel->setHeader({BaseSettingsWidget::tr("Name"),
|
||||
BaseSettingsWidget::tr("Value"),
|
||||
BaseSettingsWidget::tr("Type")});
|
||||
auto capabilitiesView = new QTreeView();
|
||||
capabilitiesView->setModel(capabilitiesModel);
|
||||
capabilitiesView->setAlternatingRowColors(true);
|
||||
capabilitiesView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
capabilitiesView->setItemDelegate(new JsonTreeItemDelegate);
|
||||
return capabilitiesView;
|
||||
}
|
||||
|
||||
static QString startupBehaviorString(BaseSettings::StartBehavior behavior)
|
||||
{
|
||||
switch (behavior) {
|
||||
@@ -810,32 +792,6 @@ BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *pa
|
||||
connect(addMimeTypeButton, &QPushButton::pressed,
|
||||
this, &BaseSettingsWidget::showAddMimeTypeDialog);
|
||||
|
||||
auto createInfoLabel = []() {
|
||||
return new QLabel(tr("Available after server was initialized"));
|
||||
};
|
||||
|
||||
mainLayout->addWidget(new QLabel(tr("Capabilities:")), ++row, 0, Qt::AlignTop);
|
||||
QVector<Client *> clients = LanguageClientManager::clientForSetting(settings);
|
||||
if (clients.isEmpty()) {
|
||||
mainLayout->addWidget(createInfoLabel());
|
||||
} else { // TODO move the capabilities view into a new widget outside of the settings
|
||||
Client *client = clients.first();
|
||||
if (client->state() == Client::Initialized)
|
||||
mainLayout->addWidget(createCapabilitiesView(QJsonValue(client->capabilities())));
|
||||
else
|
||||
mainLayout->addWidget(createInfoLabel(), row, 1);
|
||||
connect(client, &Client::finished, mainLayout, [mainLayout, row, createInfoLabel]() {
|
||||
delete mainLayout->itemAtPosition(row, 1)->widget();
|
||||
mainLayout->addWidget(createInfoLabel(), row, 1);
|
||||
});
|
||||
connect(client, &Client::initialized, mainLayout,
|
||||
[mainLayout, row](
|
||||
const LanguageServerProtocol::ServerCapabilities &capabilities) {
|
||||
delete mainLayout->itemAtPosition(row, 1)->widget();
|
||||
mainLayout->addWidget(createCapabilitiesView(QJsonValue(capabilities)), row, 1);
|
||||
});
|
||||
}
|
||||
|
||||
mainLayout->addWidget(new QLabel(tr("Initialization options:")), ++row, 0);
|
||||
mainLayout->addWidget(m_initializationOptions, row, 1);
|
||||
chooser->addSupportedWidget(m_initializationOptions);
|
||||
|
@@ -51,6 +51,61 @@ using namespace LanguageServerProtocol;
|
||||
|
||||
namespace LanguageClient {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
class MessageDetailWidget : public QGroupBox
|
||||
{
|
||||
public:
|
||||
@@ -64,53 +119,96 @@ private:
|
||||
QLabel *m_mimeType = nullptr;
|
||||
};
|
||||
|
||||
class LspInspectorWidget : public QDialog
|
||||
class LspCapabilitiesWidget : public QWidget
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(LspInspectorWidget)
|
||||
Q_DECLARE_TR_FUNCTIONS(LspCapabilitiesWidget)
|
||||
public:
|
||||
explicit LspInspectorWidget(LspInspector *inspector);
|
||||
LspCapabilitiesWidget();
|
||||
void setCapabilities(const Capabilities &serverCapabilities);
|
||||
|
||||
private:
|
||||
void addMessage(const QString &clientName, const LspLogMessage &message);
|
||||
void setCurrentClient(const QString &clientName);
|
||||
void currentMessageChanged(const QModelIndex &index);
|
||||
void selectMatchingMessage(LspLogMessage::MessageSender sender, const QJsonValue &id);
|
||||
void updateOptionsView(const QString &method);
|
||||
|
||||
DynamicCapabilities m_dynamicCapabilities;
|
||||
QTreeView *m_capabilitiesView = nullptr;
|
||||
QListWidget *m_dynamicCapabilitiesView = nullptr;
|
||||
QTreeView *m_dynamicOptionsView = nullptr;
|
||||
QGroupBox *m_dynamicCapabilitiesGroup = nullptr;
|
||||
};
|
||||
|
||||
LspCapabilitiesWidget::LspCapabilitiesWidget()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void LspCapabilitiesWidget::setCapabilities(const Capabilities &serverCapabilities)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void LspCapabilitiesWidget::updateOptionsView(const QString &method)
|
||||
{
|
||||
QAbstractItemModel *oldModel = m_dynamicOptionsView->model();
|
||||
m_dynamicOptionsView->setModel(createJsonModel(method, m_dynamicCapabilities.option(method)));
|
||||
delete oldModel;
|
||||
}
|
||||
|
||||
class LspLogWidget : public Core::MiniSplitter
|
||||
{
|
||||
public:
|
||||
LspLogWidget();
|
||||
|
||||
void addMessage(const LspLogMessage &message);
|
||||
void setMessages(const std::list<LspLogMessage> &messages);
|
||||
void saveLog();
|
||||
|
||||
LspInspector *m_inspector = nullptr;
|
||||
QListWidget *m_clients = nullptr;
|
||||
MessageDetailWidget *m_clientDetails = nullptr;
|
||||
QListView *m_messages = nullptr;
|
||||
MessageDetailWidget *m_serverDetails = nullptr;
|
||||
Utils::ListModel<LspLogMessage> m_model;
|
||||
|
||||
private:
|
||||
void currentMessageChanged(const QModelIndex &index);
|
||||
void selectMatchingMessage(LspLogMessage::MessageSender sender, const QJsonValue &id);
|
||||
};
|
||||
|
||||
QWidget *LspInspector::createWidget()
|
||||
{
|
||||
return new LspInspectorWidget(this);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
std::list<LspLogMessage> LspInspector::messages(const QString &clientName) const
|
||||
{
|
||||
return m_logs[clientName];
|
||||
}
|
||||
|
||||
QList<QString> LspInspector::clients() const
|
||||
{
|
||||
return m_logs.keys();
|
||||
}
|
||||
|
||||
static QVariant messageData(const LspLogMessage &message, int, int role)
|
||||
{
|
||||
if (role == Qt::DisplayRole) {
|
||||
@@ -131,84 +229,39 @@ static QVariant messageData(const LspLogMessage &message, int, int role)
|
||||
return {};
|
||||
}
|
||||
|
||||
LspInspectorWidget::LspInspectorWidget(LspInspector *inspector)
|
||||
: m_inspector(inspector)
|
||||
LspLogWidget::LspLogWidget()
|
||||
{
|
||||
setWindowTitle(tr("Language Client Inspector"));
|
||||
|
||||
connect(inspector, &LspInspector::newMessage, this, &LspInspectorWidget::addMessage);
|
||||
connect(Core::ICore::instance(), &Core::ICore::coreAboutToClose, this, &QWidget::close);
|
||||
|
||||
m_clients = new QListWidget;
|
||||
m_clients->addItems(inspector->clients());
|
||||
connect(m_clients,
|
||||
&QListWidget::currentTextChanged,
|
||||
this,
|
||||
&LspInspectorWidget::setCurrentClient);
|
||||
m_clients->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding);
|
||||
setOrientation(Qt::Horizontal);
|
||||
|
||||
m_clientDetails = new MessageDetailWidget;
|
||||
m_clientDetails->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
m_clientDetails->setTitle(tr("Client Message"));
|
||||
m_serverDetails = new MessageDetailWidget;
|
||||
m_serverDetails->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
m_serverDetails->setTitle(tr("Server Message"));
|
||||
addWidget(m_clientDetails);
|
||||
setStretchFactor(0, 1);
|
||||
|
||||
m_model.setDataAccessor(&messageData);
|
||||
m_messages = new QListView;
|
||||
m_messages->setModel(&m_model);
|
||||
m_messages->setAlternatingRowColors(true);
|
||||
m_model.setHeader({tr("Messages")});
|
||||
m_messages->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
|
||||
m_messages->setSelectionMode(QAbstractItemView::MultiSelection);
|
||||
addWidget(m_messages);
|
||||
setStretchFactor(1, 0);
|
||||
|
||||
m_serverDetails = new MessageDetailWidget;
|
||||
m_serverDetails->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
m_serverDetails->setTitle(tr("Server Message"));
|
||||
addWidget(m_serverDetails);
|
||||
setStretchFactor(2, 1);
|
||||
|
||||
connect(m_messages->selectionModel(),
|
||||
&QItemSelectionModel::currentChanged,
|
||||
this,
|
||||
&LspInspectorWidget::currentMessageChanged);
|
||||
m_messages->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
|
||||
m_messages->setSelectionMode(QAbstractItemView::MultiSelection);
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
setLayout(layout);
|
||||
auto splitter = new Core::MiniSplitter;
|
||||
splitter->setOrientation(Qt::Horizontal);
|
||||
splitter->addWidget(m_clients);
|
||||
splitter->addWidget(m_clientDetails);
|
||||
splitter->addWidget(m_messages);
|
||||
splitter->addWidget(m_serverDetails);
|
||||
splitter->setStretchFactor(0, 0);
|
||||
splitter->setStretchFactor(1, 1);
|
||||
splitter->setStretchFactor(2, 1);
|
||||
splitter->setStretchFactor(3, 1);
|
||||
layout->addWidget(splitter);
|
||||
|
||||
auto buttonBox = new QDialogButtonBox(this);
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Close);
|
||||
layout->addWidget(buttonBox);
|
||||
|
||||
// save
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &LspInspectorWidget::saveLog);
|
||||
|
||||
// close
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
resize(1024, 768);
|
||||
&LspLogWidget::currentMessageChanged);
|
||||
}
|
||||
|
||||
void LspInspectorWidget::addMessage(const QString &clientName, const LspLogMessage &message)
|
||||
{
|
||||
if (m_clients->findItems(clientName, Qt::MatchExactly).isEmpty())
|
||||
m_clients->addItem(clientName);
|
||||
if (clientName != m_clients->currentItem()->text())
|
||||
return;
|
||||
m_model.appendItem(message);
|
||||
}
|
||||
|
||||
void LspInspectorWidget::setCurrentClient(const QString &clientName)
|
||||
{
|
||||
m_model.clear();
|
||||
for (const LspLogMessage &message : m_inspector->messages(clientName))
|
||||
m_model.appendItem(message);
|
||||
}
|
||||
|
||||
void LspInspectorWidget::currentMessageChanged(const QModelIndex &index)
|
||||
void LspLogWidget::currentMessageChanged(const QModelIndex &index)
|
||||
{
|
||||
m_messages->clearSelection();
|
||||
if (!index.isValid())
|
||||
@@ -247,8 +300,7 @@ static bool matches(LspLogMessage::MessageSender sender,
|
||||
return json.value(QString{idKey}) == id;
|
||||
}
|
||||
|
||||
void LspInspectorWidget::selectMatchingMessage(LspLogMessage::MessageSender sender,
|
||||
const QJsonValue &id)
|
||||
void LspLogWidget::selectMatchingMessage(LspLogMessage::MessageSender sender, const QJsonValue &id)
|
||||
{
|
||||
LspLogMessage *matchingMessage = m_model.findData(
|
||||
[&](const LspLogMessage &message) { return matches(sender, id, message); });
|
||||
@@ -264,7 +316,19 @@ void LspInspectorWidget::selectMatchingMessage(LspLogMessage::MessageSender send
|
||||
m_clientDetails->setMessage(matchingMessage->message);
|
||||
}
|
||||
|
||||
void LspInspectorWidget::saveLog()
|
||||
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()
|
||||
{
|
||||
QString contents;
|
||||
QTextStream stream(&contents);
|
||||
@@ -286,6 +350,137 @@ void LspInspectorWidget::saveLog()
|
||||
saveLog();
|
||||
}
|
||||
|
||||
class LspInspectorWidget : public QDialog
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(LspInspectorWidget)
|
||||
public:
|
||||
explicit LspInspectorWidget(LspInspector *inspector);
|
||||
|
||||
private:
|
||||
void addMessage(const QString &clientName, const LspLogMessage &message);
|
||||
void updateCapabilities(const QString &clientName);
|
||||
void setCurrentClient(const QString &clientName);
|
||||
|
||||
LspInspector *m_inspector = nullptr;
|
||||
LspLogWidget *m_log = nullptr;
|
||||
LspCapabilitiesWidget *m_capabilities = nullptr;
|
||||
QListWidget *m_clients = nullptr;
|
||||
};
|
||||
|
||||
QWidget *LspInspector::createWidget()
|
||||
{
|
||||
return new LspInspectorWidget(this);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return m_logs[clientName];
|
||||
}
|
||||
|
||||
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);
|
||||
mainLayout->addWidget(buttonBox);
|
||||
setLayout(mainLayout);
|
||||
|
||||
connect(m_clients,
|
||||
&QListWidget::currentTextChanged,
|
||||
this,
|
||||
&LspInspectorWidget::setCurrentClient);
|
||||
|
||||
// save
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, m_log, &LspLogWidget::saveLog);
|
||||
|
||||
// close
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
resize(1024, 768);
|
||||
}
|
||||
|
||||
void LspInspectorWidget::addMessage(const QString &clientName, const LspLogMessage &message)
|
||||
{
|
||||
if (m_clients->findItems(clientName, Qt::MatchExactly).isEmpty())
|
||||
m_clients->addItem(clientName);
|
||||
if (clientName == m_clients->currentItem()->text())
|
||||
m_log->addMessage(message);
|
||||
}
|
||||
|
||||
void LspInspectorWidget::updateCapabilities(const QString &clientName)
|
||||
{
|
||||
if (m_clients->findItems(clientName, Qt::MatchExactly).isEmpty())
|
||||
m_clients->addItem(clientName);
|
||||
if (clientName != m_clients->currentItem()->text())
|
||||
m_capabilities->setCapabilities(m_inspector->capabilities(clientName));
|
||||
}
|
||||
|
||||
void LspInspectorWidget::setCurrentClient(const QString &clientName)
|
||||
{
|
||||
m_log->setMessages(m_inspector->messages(clientName));
|
||||
m_capabilities->setCapabilities(m_inspector->capabilities(clientName));
|
||||
}
|
||||
|
||||
MessageDetailWidget::MessageDetailWidget()
|
||||
{
|
||||
auto layout = new QFormLayout;
|
||||
@@ -298,26 +493,6 @@ MessageDetailWidget::MessageDetailWidget()
|
||||
layout->addRow("MIME Type:", m_mimeType);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
void MessageDetailWidget::setMessage(const BaseMessage &message)
|
||||
{
|
||||
m_contentLength->setText(QString::number(message.contentLength));
|
||||
@@ -327,26 +502,10 @@ void MessageDetailWidget::setMessage(const BaseMessage &message)
|
||||
if (message.mimeType == JsonRpcMessageHandler::jsonRpcMimeType()) {
|
||||
QString error;
|
||||
auto json = JsonRpcMessageHandler::toJsonObject(message.content, message.codec, error);
|
||||
if (json.isEmpty()) {
|
||||
if (json.isEmpty())
|
||||
newContentWidget = new QLabel(error);
|
||||
} else {
|
||||
auto root = new Utils::JsonTreeItem("content", json);
|
||||
if (root->canFetchMore())
|
||||
root->fetchMore();
|
||||
|
||||
auto model = new Utils::TreeModel<Utils::JsonTreeItem>(root);
|
||||
model->setHeader({{"Name"}, {"Value"}, {"Type"}});
|
||||
auto view = new QTreeView;
|
||||
view->setContextMenuPolicy(Qt::ActionsContextMenu);
|
||||
auto action = new QAction(tr("Expand All"), view);
|
||||
connect(action, &QAction::triggered, view, &QTreeView::expandAll);
|
||||
view->addAction(action);
|
||||
view->setModel(model);
|
||||
view->setAlternatingRowColors(true);
|
||||
view->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
view->setItemDelegate(new JsonTreeItemDelegate);
|
||||
newContentWidget = view;
|
||||
}
|
||||
else
|
||||
newContentWidget = createJsonTreeView("content", json);
|
||||
} else {
|
||||
auto edit = new QPlainTextEdit();
|
||||
edit->setReadOnly(true);
|
||||
|
@@ -25,10 +25,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dynamiccapabilities.h"
|
||||
|
||||
#include <QTime>
|
||||
#include <QWidget>
|
||||
|
||||
#include <languageserverprotocol/basemessage.h>
|
||||
#include <languageserverprotocol/servercapabilities.h>
|
||||
|
||||
#include <list>
|
||||
|
||||
@@ -41,6 +44,12 @@ struct LspLogMessage
|
||||
LanguageServerProtocol::BaseMessage message;
|
||||
};
|
||||
|
||||
struct Capabilities
|
||||
{
|
||||
LanguageServerProtocol::ServerCapabilities capabilities;
|
||||
DynamicCapabilities dynamicCapabilities;
|
||||
};
|
||||
|
||||
class LspInspector : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -53,15 +62,22 @@ public:
|
||||
void log(const LspLogMessage::MessageSender sender,
|
||||
const QString &clientName,
|
||||
const LanguageServerProtocol::BaseMessage &message);
|
||||
void clientInitialized(const QString &clientName,
|
||||
const LanguageServerProtocol::ServerCapabilities &capabilities);
|
||||
void updateCapabilities(const QString &clientName,
|
||||
const DynamicCapabilities &dynamicCapabilities);
|
||||
|
||||
std::list<LspLogMessage> messages(const QString &clientName) const;
|
||||
Capabilities capabilities(const QString &clientName) const;
|
||||
QList<QString> clients() const;
|
||||
|
||||
signals:
|
||||
void newMessage(const QString &clientName, const LspLogMessage &message);
|
||||
void capabilitiesUpdated(const QString &clientName);
|
||||
|
||||
private:
|
||||
QMap<QString, std::list<LspLogMessage>> m_logs;
|
||||
QMap<QString, Capabilities> m_capabilities;
|
||||
int m_logSize = 100; // default log size if no widget is currently visible
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user