Commit 724a9d0a authored by Mark Nauwelaerts's avatar Mark Nauwelaerts
Browse files

lspclient: add as optionally enabled plugin

parent 621ada0d
......@@ -55,6 +55,12 @@ ecm_optional_add_subdirectory (rustcompletion)
# D completion plugin
ecm_optional_add_subdirectory (lumen)
# LSP Client plugin
option(ENABLE_LSPCLIENT "Enable LSPCLient plugin" OFF)
if (ENABLE_LSPCLIENT)
ecm_optional_add_subdirectory (lspclient)
endif()
# build plugin
ecm_optional_add_subdirectory (katebuild-plugin)
......
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
include(ECMQtDeclareLoggingCategory)
add_definitions(-DTRANSLATION_DOMAIN=\"lspclient\")
set(lspclientplugin_SRCS
lspclientcompletion.cpp
lspclientconfigpage.cpp
lspclientplugin.cpp
lspclientpluginview.cpp
lspclientserver.cpp
lspclientservermanager.cpp
lspclientsymbolview.cpp
)
ecm_qt_declare_logging_category(lspclientplugin_debug_SRCS
HEADER lspclient_debug.h
IDENTIFIER LSPCLIENT
CATEGORY_NAME "katelspclientplugin"
)
# resource for ui file and stuff
qt5_add_resources(lspclientplugin_SRCS plugin.qrc)
add_library(lspclientplugin MODULE ${lspclientplugin_SRCS} ${lspclientplugin_debug_SRCS})
kcoreaddons_desktop_to_json(lspclientplugin lspclientplugin.desktop)
target_link_libraries(lspclientplugin
KF5::TextEditor
KF5::XmlGui
)
install(TARGETS lspclientplugin DESTINATION ${PLUGIN_INSTALL_DIR}/ktexteditor)
############# unit tests ################
if (BUILD_TESTING)
add_subdirectory(tests)
endif()
#! /bin/sh
$EXTRACTRC *.rc >> rc.cpp
$XGETTEXT *.cpp -o $podir/lspclient.pot
/***************************************************************************
* Copyright (C) 2012 Christoph Cullmann <cullmann@kde.org> *
* Copyright (C) 2003 Anders Lund <anders.lund@lund.tdcadsl.dk> *
* Copyright (C) 2019 by Mark Nauwelaerts <mark.nauwelaerts@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "lspclientcompletion.h"
#include "lspclientplugin.h"
#include "lspclient_debug.h"
#include <KTextEditor/Cursor>
#include <KTextEditor/Document>
#include <KTextEditor/View>
#include <QIcon>
#include <QUrl>
#define RETURN_CACHED_ICON(name) \
{ \
static QIcon icon(QIcon::fromTheme(QStringLiteral(name))); \
return icon; \
}
static QIcon
kind_icon(LSPCompletionItemKind kind)
{
switch (kind)
{
case LSPCompletionItemKind::Method:
case LSPCompletionItemKind::Function:
case LSPCompletionItemKind::Constructor:
RETURN_CACHED_ICON("code-function")
case LSPCompletionItemKind::Variable:
RETURN_CACHED_ICON("code-variable")
case LSPCompletionItemKind::Class:
case LSPCompletionItemKind::Interface:
case LSPCompletionItemKind::Struct:
RETURN_CACHED_ICON("code-class");
case LSPCompletionItemKind::Module:
RETURN_CACHED_ICON("code-block");
case LSPCompletionItemKind::Field:
case LSPCompletionItemKind::Property:
RETURN_CACHED_ICON("field");
case LSPCompletionItemKind::Enum:
case LSPCompletionItemKind::EnumMember:
RETURN_CACHED_ICON("enum");
default:
break;
}
return QIcon();
}
static KTextEditor::CodeCompletionModel::CompletionProperty
kind_property(LSPCompletionItemKind kind)
{
using CompletionProperty = KTextEditor::CodeCompletionModel::CompletionProperty;
auto p = CompletionProperty::NoProperty;
switch (kind)
{
case LSPCompletionItemKind::Method:
case LSPCompletionItemKind::Function:
case LSPCompletionItemKind::Constructor:
p = CompletionProperty::Function;
break;
case LSPCompletionItemKind::Variable:
p = CompletionProperty::Variable;
break;
case LSPCompletionItemKind::Class:
case LSPCompletionItemKind::Interface:
p = CompletionProperty::Class;
break;
case LSPCompletionItemKind::Struct:
p = CompletionProperty::Class;
break;
case LSPCompletionItemKind::Module:
p =CompletionProperty::Namespace;
break;
case LSPCompletionItemKind::Enum:
case LSPCompletionItemKind::EnumMember:
p = CompletionProperty::Enum;
break;
default:
break;
}
return p;
}
static bool compare_match (const LSPCompletionItem & a, const LSPCompletionItem b)
{ return a.sortText < b.sortText; }
class LSPClientCompletionImpl : public LSPClientCompletion
{
Q_OBJECT
typedef LSPClientCompletionImpl self_type;
QSharedPointer<LSPClientServerManager> m_manager;
QSharedPointer<LSPClientServer> m_server;
QList<LSPCompletionItem> m_matches;
LSPClientServer::RequestHandle m_handle;
public:
LSPClientCompletionImpl(QSharedPointer<LSPClientServerManager> manager)
: LSPClientCompletion(nullptr), m_manager(manager), m_server(nullptr)
{
}
void setServer(QSharedPointer<LSPClientServer> server) override
{ m_server = server; }
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid() || index.row() >= m_matches.size()) {
return QVariant();
}
const auto &match = m_matches.at(index.row());
if (index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole) {
// sigh, remove (leading) whitespace (looking at clangd here)
// TODO better way, but it seems handy this way ??
return QString(match.label.simplified() + QStringLiteral(" [") + match.detail + QStringLiteral("]"));;
} else if (index.column() == KTextEditor::CodeCompletionModel::Icon && role == Qt::DecorationRole) {
return kind_icon(match.kind);
} else if (role == KTextEditor::CodeCompletionModel::CompletionRole) {
return kind_property(match.kind);
} else if (role == KTextEditor::CodeCompletionModel::ArgumentHintDepth) {
// TODO use when also handling signatureHelp
return 0; // match.depth;
} else if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) {
// (ab)use depth to indicate sort order
return index.row();
}
return QVariant();
}
bool shouldStartCompletion(KTextEditor::View *view, const QString &insertedText,
bool userInsertion, const KTextEditor::Cursor &position) override
{
qCInfo(LSPCLIENT) << "should start " << userInsertion;
if (!userInsertion) {
return false;
}
bool complete = CodeCompletionModelControllerInterface::shouldStartCompletion(view,
insertedText, userInsertion, position);
complete = complete || insertedText.endsWith(QStringLiteral("("));
complete = complete || insertedText.endsWith(QStringLiteral("::"));
qCInfo(LSPCLIENT) << "should start " << complete;
return complete;
}
void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType it) override
{
Q_UNUSED(it)
qCInfo(LSPCLIENT) << "completion invoked" << m_server;
auto handler = [this] (const QList<LSPCompletionItem> compl) {
beginResetModel();
m_matches = compl;
// sort as requested
qSort(m_matches.begin(), m_matches.end(), compare_match);
setRowCount(m_matches.size());
qCInfo(LSPCLIENT) << "adding completions " << m_matches.size();
endResetModel();
};
beginResetModel();
m_matches.clear();
auto document = view->document();
if (m_server && document) {
// the default range is determined based on a reasonable identifier (word)
// which is generally fine and nice, but let's pass actual cursor position
// (which may be within this typical range)
auto position = view->cursorPosition();
auto cursor = qMax(range.start(), qMin(range.end(), position));
m_manager->update(document);
m_handle = m_server->documentCompletion(document->url(),
{cursor.line(), cursor.column()}, handler);
}
setRowCount(m_matches.size());
endResetModel();
}
void executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const override
{
if (index.row() < m_matches.size())
view->document()->replaceText(word, m_matches.at(index.row()).insertText);
}
void aborted(KTextEditor::View *view) override
{
Q_UNUSED(view);
beginResetModel();
m_matches.clear();
m_handle.cancel();
endResetModel();
}
};
LSPClientCompletion*
LSPClientCompletion::new_(QSharedPointer<LSPClientServerManager> manager)
{
return new LSPClientCompletionImpl(manager);
}
#include "lspclientcompletion.moc"
/***************************************************************************
* Copyright (C) 2019 by Mark Nauwelaerts <mark.nauwelaerts@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#ifndef LSPCLIENTCOMPLETION_H
#define LSPCLIENTCOMPLETION_H
#include "lspclientserver.h"
#include "lspclientservermanager.h"
#include <KTextEditor/CodeCompletionModel>
#include <KTextEditor/CodeCompletionModelControllerInterface>
class LSPClientCompletion : public KTextEditor::CodeCompletionModel, public KTextEditor::CodeCompletionModelControllerInterface
{
Q_OBJECT
Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface)
public:
// implementation factory method
static LSPClientCompletion* new_(QSharedPointer<LSPClientServerManager> manager);
LSPClientCompletion(QObject * parent)
: KTextEditor::CodeCompletionModel(parent)
{}
virtual void setServer(QSharedPointer<LSPClientServer> server) = 0;
};
#endif
/***************************************************************************
* Copyright (C) 2015 by Eike Hein <hein@kde.org> *
* Copyright (C) 2019 by Mark Nauwelaerts <mark.nauwelaerts@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "lspclientconfigpage.h"
#include "lspclientplugin.h"
#include <QGroupBox>
#include <QLineEdit>
#include <QCheckBox>
#include <QVBoxLayout>
#include <KLocalizedString>
#include <KUrlRequester>
LSPClientConfigPage::LSPClientConfigPage(QWidget *parent, LSPClientPlugin *plugin)
: KTextEditor::ConfigPage(parent), m_plugin(plugin)
{
QVBoxLayout *layout = new QVBoxLayout(this);
QGroupBox *outlineBox = new QGroupBox(i18n("Symbol Outline Options"), this);
QVBoxLayout *top = new QVBoxLayout(outlineBox);
m_symbolDetails = new QCheckBox(i18n("Display symbol details"));
m_symbolTree = new QCheckBox(i18n("Tree mode outline"));
m_symbolExpand = new QCheckBox(i18n("Automatically expand nodes in tree mode"));
m_symbolSort = new QCheckBox(i18n("Sort symbols alphabetically"));
top->addWidget(m_symbolDetails);
top->addWidget(m_symbolTree);
top->addWidget(m_symbolExpand);
top->addWidget(m_symbolSort);
layout->addWidget(outlineBox);
layout->addStretch(1);
reset();
for (const auto & cb : {m_symbolDetails, m_symbolExpand, m_symbolSort, m_symbolTree})
connect(cb, &QCheckBox::toggled, this, &LSPClientConfigPage::changed);
}
QString LSPClientConfigPage::name() const
{
return QString(i18n("LSP Client"));
}
QString LSPClientConfigPage::fullName() const
{
return QString(i18n("LSP Client"));
}
QIcon LSPClientConfigPage::icon() const
{
return QIcon::fromTheme(QLatin1String("code-context"));
}
void LSPClientConfigPage::apply()
{
m_plugin->m_symbolDetails = m_symbolDetails->isChecked();
m_plugin->m_symbolTree = m_symbolTree->isChecked();
m_plugin->m_symbolExpand = m_symbolExpand->isChecked();
m_plugin->m_symbolSort = m_symbolSort->isChecked();
m_plugin->writeConfig();
}
void LSPClientConfigPage::reset()
{
m_symbolDetails->setChecked(m_plugin->m_symbolDetails);
m_symbolTree->setChecked(m_plugin->m_symbolTree);
m_symbolExpand->setChecked(m_plugin->m_symbolExpand);
m_symbolSort->setChecked(m_plugin->m_symbolSort);
}
void LSPClientConfigPage::defaults()
{
reset();
}
/***************************************************************************
* Copyright (C) 2015 by Eike Hein <hein@kde.org> *
* Copyright (C) 2019 by Mark Nauwelaerts <mark.nauwelaerts@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#ifndef LSPCLIENTCONFIGPAGE_H
#define LSPCLIENTCONFIGPAGE_H
#include <KTextEditor/ConfigPage>
class LSPClientPlugin;
class QCheckBox;
class QLineEdit;
class KUrlRequester;
class LSPClientConfigPage : public KTextEditor::ConfigPage
{
Q_OBJECT
public:
explicit LSPClientConfigPage(QWidget *parent = nullptr, LSPClientPlugin *plugin = nullptr);
~LSPClientConfigPage() override {};
QString name() const override;
QString fullName() const override;
QIcon icon() const override;
public Q_SLOTS:
void apply() override;
void defaults() override;
void reset() override;
private:
QCheckBox* m_symbolDetails;
QCheckBox* m_symbolExpand;
QCheckBox* m_symbolTree;
QCheckBox* m_symbolSort;
LSPClientPlugin *m_plugin;
};
#endif
/***************************************************************************
* Copyright (C) 2015 by Eike Hein <hein@kde.org> *
* Copyright (C) 2019 by Mark Nauwelaerts <mark.nauwelaerts@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "lspclientplugin.h"
#include "lspclientpluginview.h"
#include "lspclientconfigpage.h"
#include <KConfigGroup>
#include <KDirWatch>
#include <KPluginFactory>
#include <KSharedConfig>
#include <QDir>
K_PLUGIN_FACTORY_WITH_JSON(LSPClientPluginFactory, "lspclientplugin.json", registerPlugin<LSPClientPlugin>();)
LSPClientPlugin::LSPClientPlugin(QObject *parent, const QList<QVariant> &)
: KTextEditor::Plugin(parent)
{
readConfig();
}
LSPClientPlugin::~LSPClientPlugin()
{
}
QObject *LSPClientPlugin::createView(KTextEditor::MainWindow *mainWindow)
{
return LSPClientPluginView::new_(this, mainWindow);
}
int LSPClientPlugin::configPages() const
{
return 1;
}
KTextEditor::ConfigPage *LSPClientPlugin::configPage(int number, QWidget *parent)
{
if (number != 0) {
return nullptr;
}
return new LSPClientConfigPage(parent, this);
}
void LSPClientPlugin::readConfig()
{
KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("lspclient"));
m_symbolDetails = config.readEntry(QStringLiteral("SymbolDetails"), false);
m_symbolTree = config.readEntry(QStringLiteral("SymbolTree"), true);
m_symbolExpand = config.readEntry(QStringLiteral("SymbolExpand"), true);
m_symbolSort = config.readEntry(QStringLiteral("SymbolSort"), false);
// TODO properly read/write from/to config
m_serverCmds = QMap<QString, QString> {
{ QStringLiteral("Python"),
QStringLiteral("python3 -m pyls --check-parent-process") },
{ QStringLiteral("C"), QStringLiteral("clangd -log=verbose --background-index") },
{ QStringLiteral("C++"), QStringLiteral("clangd -log=verbose --background-index") }
};
emit update();
}
void LSPClientPlugin::writeConfig() const
{
KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("lspclient"));
config.writeEntry(QStringLiteral("SymbolDetails"), m_symbolDetails);
config.writeEntry(QStringLiteral("SymbolTree"), m_symbolTree);
config.writeEntry(QStringLiteral("SymbolExpand"), m_symbolExpand);
config.writeEntry(QStringLiteral("SymbolSort"), m_symbolSort);
emit update();
}
#include "lspclientplugin.moc"
[Desktop Entry]
Type=Service
ServiceTypes=KTextEditor/Plugin
X-KDE-Library=lspclientplugin
Name=LSP Client
Name[en_GB]=LSP Client
Comment=Language Server Protocol Client
Comment[en_GB]=Language Server Protocol Client
/***************************************************************************
* Copyright (C) 2015 by Eike Hein <hein@kde.org> *
* Copyright (C) 2019 by Mark Nauwelaerts <mark.nauwelaerts@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *