Commit cfe7764c authored by Mark Nauwelaerts's avatar Mark Nauwelaerts
Browse files

lspclient: add tab for server and client messages

Fixes issue #9
parent f4695ae7
......@@ -82,11 +82,13 @@ LSPClientConfigPage::LSPClientConfigPage(QWidget *parent, LSPClientPlugin *plugi
ui->chkRefDeclaration,
ui->chkDiagnostics,
ui->chkDiagnosticsMark,
ui->chkMessages,
ui->chkOnTypeFormatting,
ui->chkIncrementalSync,
ui->chkSemanticHighlighting,
ui->chkAutoHover})
connect(cb, &QCheckBox::toggled, this, &LSPClientConfigPage::changed);
connect(ui->comboMessagesSwitch, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this](int) { changed(); });
connect(ui->edtConfigPath, &KUrlRequester::textChanged, this, &LSPClientConfigPage::configUrlChanged);
connect(ui->edtConfigPath, &KUrlRequester::urlSelected, this, &LSPClientConfigPage::configUrlChanged);
connect(ui->userConfig, &QTextEdit::textChanged, this, &LSPClientConfigPage::configTextChanged);
......@@ -96,6 +98,8 @@ LSPClientConfigPage::LSPClientConfigPage(QWidget *parent, LSPClientPlugin *plugi
bool enabled = ui->chkDiagnostics->isChecked();
ui->chkDiagnosticsHighlight->setEnabled(enabled);
ui->chkDiagnosticsMark->setEnabled(enabled);
enabled = ui->chkMessages->isChecked();
ui->comboMessagesSwitch->setEnabled(enabled);
};
connect(this, &LSPClientConfigPage::changed, this, h);
}
......@@ -139,6 +143,9 @@ void LSPClientConfigPage::apply()
m_plugin->m_incrementalSync = ui->chkIncrementalSync->isChecked();
m_plugin->m_semanticHighlighting = ui->chkSemanticHighlighting->isChecked();
m_plugin->m_messages = ui->chkMessages->isChecked();
m_plugin->m_messagesAutoSwitch = ui->comboMessagesSwitch->currentIndex();
m_plugin->m_configPath = ui->edtConfigPath->url();
// own scope to ensure file is flushed before we signal below in writeConfig!
......@@ -172,6 +179,9 @@ void LSPClientConfigPage::reset()
ui->chkIncrementalSync->setChecked(m_plugin->m_incrementalSync);
ui->chkSemanticHighlighting->setChecked(m_plugin->m_semanticHighlighting);
ui->chkMessages->setChecked(m_plugin->m_messages);
ui->comboMessagesSwitch->setCurrentIndex(m_plugin->m_messagesAutoSwitch);
ui->edtConfigPath->setUrl(m_plugin->m_configPath);
readUserConfig(m_plugin->configPath().toLocalFile());
......
......@@ -49,6 +49,8 @@ static const QString CONFIG_INCREMENTAL_SYNC {QStringLiteral("IncrementalSync")}
static const QString CONFIG_DIAGNOSTICS {QStringLiteral("Diagnostics")};
static const QString CONFIG_DIAGNOSTICS_HIGHLIGHT {QStringLiteral("DiagnosticsHighlight")};
static const QString CONFIG_DIAGNOSTICS_MARK {QStringLiteral("DiagnosticsMark")};
static const QString CONFIG_MESSAGES {QStringLiteral("Messages")};
static const QString CONFIG_MESSAGES_AUTO_SWITCH {QStringLiteral("MessagesAutoSwitch")};
static const QString CONFIG_SERVER_CONFIG {QStringLiteral("ServerConfiguration")};
static const QString CONFIG_SEMANTIC_HIGHLIGHTING {QStringLiteral("SemanticHighlighting")};
......@@ -114,6 +116,8 @@ void LSPClientPlugin::readConfig()
m_diagnostics = config.readEntry(CONFIG_DIAGNOSTICS, true);
m_diagnosticsHighlight = config.readEntry(CONFIG_DIAGNOSTICS_HIGHLIGHT, true);
m_diagnosticsMark = config.readEntry(CONFIG_DIAGNOSTICS_MARK, true);
m_messages = config.readEntry(CONFIG_MESSAGES, true);
m_messagesAutoSwitch = config.readEntry(CONFIG_MESSAGES_AUTO_SWITCH, 1);
m_configPath = config.readEntry(CONFIG_SERVER_CONFIG, QUrl());
m_semanticHighlighting = config.readEntry(CONFIG_SEMANTIC_HIGHLIGHTING, false);
......@@ -135,6 +139,8 @@ void LSPClientPlugin::writeConfig() const
config.writeEntry(CONFIG_DIAGNOSTICS, m_diagnostics);
config.writeEntry(CONFIG_DIAGNOSTICS_HIGHLIGHT, m_diagnosticsHighlight);
config.writeEntry(CONFIG_DIAGNOSTICS_MARK, m_diagnosticsMark);
config.writeEntry(CONFIG_MESSAGES, m_messages);
config.writeEntry(CONFIG_MESSAGES_AUTO_SWITCH, m_messagesAutoSwitch);
config.writeEntry(CONFIG_SERVER_CONFIG, m_configPath);
config.writeEntry(CONFIG_SEMANTIC_HIGHLIGHTING, m_semanticHighlighting);
......
......@@ -63,6 +63,8 @@ public:
bool m_diagnostics;
bool m_diagnosticsHighlight;
bool m_diagnosticsMark;
bool m_messages;
int m_messagesAutoSwitch;
bool m_autoHover;
bool m_onTypeFormatting;
bool m_incrementalSync;
......
......@@ -54,6 +54,7 @@
#include <QAction>
#include <QApplication>
#include <QDateTime>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QHeaderView>
......@@ -61,6 +62,7 @@
#include <QJsonObject>
#include <QKeyEvent>
#include <QMenu>
#include <QPlainTextEdit>
#include <QSet>
#include <QStandardItem>
#include <QTextCodec>
......@@ -236,6 +238,9 @@ class LSPClientActionView : public QObject
QPointer<QAction> m_diagnosticsMark;
QPointer<QAction> m_diagnosticsSwitch;
QPointer<QAction> m_diagnosticsCloseNon;
QPointer<QAction> m_messages;
QPointer<KSelectAction> m_messagesAutoSwitch;
QPointer<QAction> m_messagesSwitch;
QPointer<QAction> m_restartServer;
QPointer<QAction> m_restartAll;
......@@ -270,6 +275,12 @@ class LSPClientActionView : public QObject
// and marks
DocumentCollection m_diagnosticsMarks;
using MessagesWidget = QPlainTextEdit;
// messages tab
QPointer<MessagesWidget> m_messagesView;
// widget is either owned here or by tab
QScopedPointer<MessagesWidget> m_messagesViewOwn;
// views on which completions have been registered
QSet<KTextEditor::View *> m_completionViews;
......@@ -305,6 +316,7 @@ public:
connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::updateState);
connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &self_type::handleEsc);
connect(m_serverManager.data(), &LSPClientServerManager::serverChanged, this, &self_type::updateState);
connect(m_serverManager.data(), &LSPClientServerManager::showMessage, this, &self_type::onShowMessage);
m_findDef = actionCollection()->addAction(QStringLiteral("lspclient_find_definition"), this, &self_type::goToDefinition);
m_findDef->setText(i18n("Go to Definition"));
......@@ -355,6 +367,17 @@ public:
m_diagnosticsCloseNon = actionCollection()->addAction(QStringLiteral("lspclient_diagnostic_close_non"), this, &self_type::closeNonDiagnostics);
m_diagnosticsCloseNon->setText(i18n("Close all non-diagnostics tabs"));
// messages
m_messages = actionCollection()->addAction(QStringLiteral("lspclient_messages"), this, &self_type::displayOptionChanged);
m_messages->setText(i18n("Show messages"));
m_messages->setCheckable(true);
m_messagesAutoSwitch = new KSelectAction(i18n("Switch to messages tab upon message level"), this);
actionCollection()->addAction(QStringLiteral("lspclient_messages_auto_switch"), m_messagesAutoSwitch);
const QStringList list {i18nc("@info", "Never"), i18nc("@info", "Error"), i18nc("@info", "Warning"), i18nc("@info", "Information"), i18nc("@info", "Log")};
m_messagesAutoSwitch->setItems(list);
m_messagesSwitch = actionCollection()->addAction(QStringLiteral("lspclient_messages_switch"), this, &self_type::switchToMessages);
m_messagesSwitch->setText(i18n("Switch to messages tab"));
// server control
m_restartServer = actionCollection()->addAction(QStringLiteral("lspclient_restart_server"), this, &self_type::restartCurrent);
m_restartServer->setText(i18n("Restart LSP Server"));
......@@ -384,6 +407,10 @@ public:
menu->addAction(m_diagnosticsSwitch);
menu->addAction(m_diagnosticsCloseNon);
menu->addSeparator();
menu->addAction(m_messages);
menu->addAction(m_messagesAutoSwitch);
menu->addAction(m_messagesSwitch);
menu->addSeparator();
menu->addAction(m_restartServer);
menu->addAction(m_restartAll);
......@@ -398,6 +425,7 @@ public:
m_tabWidget->setTabsClosable(true);
KAcceleratorManager::setNoAccel(m_tabWidget);
connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &self_type::tabCloseRequested);
connect(m_tabWidget, &QTabWidget::currentChanged, this, &self_type::tabChanged);
// diagnostics tab
m_diagnosticsTree = new QTreeView();
......@@ -410,6 +438,12 @@ public:
connect(m_diagnosticsTree, &QTreeView::clicked, this, &self_type::goToItemLocation);
connect(m_diagnosticsTree, &QTreeView::doubleClicked, this, &self_type::triggerCodeAction);
// messages tab
m_messagesView = new QPlainTextEdit();
m_messagesView->setMaximumBlockCount(100);
m_messagesView->setReadOnly(true);
m_messagesViewOwn.reset(m_messagesView);
// track position in view to sync diagnostics list
m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 0, 500));
connect(m_viewTracker.data(), &LSPClientViewTracker::newState, this, &self_type::onViewState);
......@@ -455,14 +489,26 @@ public:
{
m_diagnosticsHighlight->setEnabled(m_diagnostics->isChecked());
m_diagnosticsMark->setEnabled(m_diagnostics->isChecked());
auto index = m_tabWidget->indexOf(m_diagnosticsTree);
// messages tab should go first
int messagesIndex = m_tabWidget->indexOf(m_messagesView);
if (m_messages->isChecked() && m_messagesViewOwn) {
m_tabWidget->insertTab(0, m_messagesView, i18nc("@title:tab", "Messages"));
messagesIndex = 0;
m_messagesViewOwn.take();
} else if (!m_messages->isChecked() && !m_messagesViewOwn) {
m_messagesViewOwn.reset(m_messagesView);
m_tabWidget->removeTab(messagesIndex);
messagesIndex = -1;
}
// diagnstics tab next
int diagnosticsIndex = m_tabWidget->indexOf(m_diagnosticsTree);
// setTabEnabled may still show it ... so let's be more forceful
if (m_diagnostics->isChecked() && m_diagnosticsTreeOwn) {
m_diagnosticsTreeOwn.take();
m_tabWidget->insertTab(0, m_diagnosticsTree, i18nc("@title:tab", "Diagnostics"));
m_tabWidget->insertTab(messagesIndex + 1, m_diagnosticsTree, i18nc("@title:tab", "Diagnostics"));
} else if (!m_diagnostics->isChecked() && !m_diagnosticsTreeOwn) {
m_diagnosticsTreeOwn.reset(m_diagnosticsTree);
m_tabWidget->removeTab(index);
m_tabWidget->removeTab(diagnosticsIndex);
}
m_diagnosticsSwitch->setEnabled(m_diagnostics->isChecked());
m_serverManager->setIncrementalSync(m_incrementalSync->isChecked());
......@@ -487,6 +533,10 @@ public:
m_diagnosticsHighlight->setChecked(m_plugin->m_diagnosticsHighlight);
if (m_diagnosticsMark)
m_diagnosticsMark->setChecked(m_plugin->m_diagnosticsMark);
if (m_messages)
m_messages->setChecked(m_plugin->m_messages);
if (m_messagesAutoSwitch)
m_messagesAutoSwitch->setCurrentItem(m_plugin->m_messagesAutoSwitch);
displayOptionChanged();
}
......@@ -833,7 +883,7 @@ public:
void tabCloseRequested(int index)
{
auto widget = m_tabWidget->widget(index);
if (widget != m_diagnosticsTree) {
if (widget != m_diagnosticsTree && widget != m_messagesView) {
if (m_markModel && widget == m_markModel->parent()) {
clearAllLocationMarks();
}
......@@ -841,12 +891,24 @@ public:
}
}
void tabChanged(int index)
{
// reset to regular foreground
m_tabWidget->tabBar()->setTabTextColor(index, QColor());
}
void switchToDiagnostics()
{
m_tabWidget->setCurrentWidget(m_diagnosticsTree);
m_mainWindow->showToolView(m_toolView.data());
}
void switchToMessages()
{
m_tabWidget->setCurrentWidget(m_messagesView);
m_mainWindow->showToolView(m_toolView.data());
}
void closeNonDiagnostics()
{
for (int i = 0; i < m_tabWidget->count();) {
......@@ -1450,6 +1512,76 @@ public:
return nullptr;
}
void addMessage(LSPMessageType level, const QString &header, const QString &msg)
{
if (!m_messagesView)
return;
QString lvl = i18nc("@info", "Unknown");
switch (level) {
case LSPMessageType::Error:
lvl = i18nc("@info", "Error");
break;
case LSPMessageType::Warning:
lvl = i18nc("@info", "Warning");
break;
case LSPMessageType::Info:
lvl = i18nc("@info", "Information");
break;
case LSPMessageType::Log:
lvl = i18nc("@info", "Log");
break;
}
// let's consider this expert info and use ISO date
auto now = QDateTime::currentDateTime().toString(Qt::ISODate);
auto text = QStringLiteral("[%1] [%2] [%3]\n%4\n").arg(now).arg(lvl).arg(header).arg(msg.trimmed());
m_messagesView->appendPlainText(text);
if (static_cast<int>(level) <= m_messagesAutoSwitch->currentItem()) {
switchToMessages();
} else {
// show arrival of new message
auto index = m_tabWidget->indexOf(m_messagesView);
if (m_tabWidget->currentIndex() != index)
m_tabWidget->tabBar()->setTabTextColor(index, Qt::gray);
}
}
// params type is same for show or log and is treated the same way
void onMessage(const LSPLogMessageParams &params)
{
// determine server description
auto server = dynamic_cast<LSPClientServer *>(sender());
auto desc = i18nc("@info", "LSP Server");
if (server)
desc += QStringLiteral(": %1").arg(LSPClientServerManager::serverDescription(server));
addMessage(params.type, desc, params.message);
}
void onShowMessage(KTextEditor::Message::MessageType level, const QString &msg)
{
// translate level
LSPMessageType lvl = LSPMessageType::Log;
using KMessage = KTextEditor::Message;
switch (level) {
case KMessage::Error:
lvl = LSPMessageType::Error;
break;
case KMessage::Warning:
lvl = LSPMessageType::Warning;
break;
case KMessage::Information:
lvl = LSPMessageType::Info;
break;
case KMessage::Positive:
lvl = LSPMessageType::Log;
break;
}
addMessage(lvl, i18nc("@info", "LSP Client"), msg);
}
Q_SLOT void clearSemanticHighlighting(KTextEditor::Document *document)
{
auto &documentRanges = m_semanticHighlightRanges[document];
......@@ -1680,6 +1812,8 @@ public:
renameEnabled = caps.renameProvider;
connect(server.data(), &LSPClientServer::publishDiagnostics, this, &self_type::onDiagnostics, Qt::UniqueConnection);
connect(server.data(), &LSPClientServer::showMessage, this, &self_type::onMessage, Qt::UniqueConnection);
connect(server.data(), &LSPClientServer::logMessage, this, &self_type::onMessage, Qt::UniqueConnection);
connect(server.data(), &LSPClientServer::semanticHighlighting, this, &self_type::onSemanticHighlighting, Qt::UniqueConnection);
connect(server.data(), &LSPClientServer::applyEdit, this, &self_type::onApplyEdit, Qt::UniqueConnection);
......
......@@ -395,16 +395,8 @@ public:
private:
void showMessage(const QString &msg, KTextEditor::Message::MessageType level)
{
KTextEditor::View *view = m_mainWindow->activeView();
if (!view || !view->document())
return;
auto kmsg = new KTextEditor::Message(xi18nc("@info", "<b>LSP Client:</b> %1", msg), level);
kmsg->setPosition(KTextEditor::Message::AboveView);
kmsg->setAutoHide(5000);
kmsg->setAutoHideMode(KTextEditor::Message::Immediate);
kmsg->setView(view);
view->document()->postMessage(kmsg);
// inform interested view(er) which will decide how/where to show
emit LSPClientServerManager::showMessage(level, msg);
}
// caller ensures that servers are no longer present in m_servers
......@@ -601,6 +593,8 @@ private:
connect(server.data(), &LSPClientServer::stateChanged, this, &self_type::onStateChanged, Qt::UniqueConnection);
if (!server->start(m_plugin)) {
showMessage(i18n("Failed to start server: %1", cmdline.join(QLatin1Char(' '))), KTextEditor::Message::Error);
} else {
showMessage(i18n("Started server %2: %1", cmdline.join(QLatin1Char(' ')), serverDescription(server.data())), KTextEditor::Message::Positive);
}
serverinfo.settings = serverConfig.value(QStringLiteral("settings"));
serverinfo.started = QTime::currentTime();
......
......@@ -28,6 +28,8 @@
#include "lspclientplugin.h"
#include "lspclientserver.h"
#include <KTextEditor/Message>
#include <QSharedPointer>
namespace KTextEditor
......@@ -74,9 +76,21 @@ public:
// locks are released when returned snapshot is delete'd
virtual LSPClientRevisionSnapshot *snapshot(LSPClientServer *server) = 0;
// helper method providing descriptive label for a server
static QString serverDescription(LSPClientServer *server)
{
if (server) {
auto root = server->root().toLocalFile();
return QStringLiteral("%1@%2").arg(server->langId()).arg(root);
} else {
return {};
}
}
public:
Q_SIGNALS:
void serverChanged();
void showMessage(KTextEditor::Message::MessageType level, const QString &msg);
};
class LSPClientRevisionSnapshot : public QObject
......
......@@ -108,6 +108,53 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="chkMessages">
<property name="text">
<string>Show messages</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_1">
<property name="text">
<string>Switch to messages tab upon level</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboMessagesSwitch">
<item>
<property name="text">
<string>Never</string>
</property>
</item>
<item>
<property name="text">
<string>Error</string>
</property>
</item>
<item>
<property name="text">
<string>Warning</string>
</property>
</item>
<item>
<property name="text">
<string>Information</string>
</property>
</item>
<item>
<property name="text">
<string>Log</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE kpartgui>
<gui name="lspclient" library="lspclient" version="4" translationDomain="lspclient">
<gui name="lspclient" library="lspclient" version="5" translationDomain="lspclient">
<MenuBar>
<Menu name="LSPClient Menubar">
<text>LSP Client</text>
......@@ -24,6 +24,10 @@
<Action name="lspclient_diagnostic_switch"/>
<Action name="lspclient_diagnostic_close_non"/>
<Separator/>
<Action name="lspclient_messages"/>
<Action name="lspclient_messages_auto_switch"/>
<Action name="lspclient_messages_switch"/>
<Separator/>
<Action name="lspclient_restart_server"/>
<Action name="lspclient_restart_all"/>
</Menu>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment