Commit 44627769 authored by Waqar Ahmed's avatar Waqar Ahmed Committed by Christoph Cullmann
Browse files

CMake plugin: Initial commit

This change introduces a new plugin, the Cmake plugin. For now it
doesn't do anything besides providing completions.
parent b809b7f0
Pipeline #115399 passed with stage
in 6 minutes and 24 seconds
......@@ -32,3 +32,4 @@ ecm_optional_add_subdirectory(tabswitcher) # ALT+Tab like tab switcher.
ecm_optional_add_subdirectory(textfilter) # Pipe text through some external command.
ecm_optional_add_subdirectory(xmlcheck) # XML Validation plugin
ecm_optional_add_subdirectory(xmltools) # XML completion
ecm_optional_add_subdirectory(cmake-tools)
add_library(cmaketoolsplugin MODULE "")
target_compile_definitions(cmaketoolsplugin PRIVATE TRANSLATION_DOMAIN="cmaketoolsplugin")
target_link_libraries(cmaketoolsplugin PRIVATE KF5::I18n KF5::TextEditor)
target_sources(
cmaketoolsplugin
PRIVATE
cmaketoolsplugin.cpp
cmakecompletion.cpp
)
# ensure we are able to load plugins pre-install, too, directories must match!
set_target_properties(cmaketoolsplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/ktexteditor")
install(TARGETS cmaketoolsplugin DESTINATION ${KDE_INSTALL_PLUGINDIR}/ktexteditor)
/*
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "cmakecompletion.h"
#include <KTextEditor/Document>
#include <KTextEditor/View>
#include <QProcess>
struct CMakeComplData {
std::vector<QByteArray> m_commands;
std::vector<QByteArray> m_vars;
std::vector<QByteArray> m_props;
};
static QByteArray runCMake(const QString &arg)
{
QProcess p;
p.start(QStringLiteral("cmake"), {arg});
if (p.waitForStarted() && p.waitForFinished()) {
if (p.exitCode() == 0 && p.exitStatus() == QProcess::NormalExit) {
return p.readAllStandardOutput();
}
}
return {};
}
std::vector<QByteArray> parseList(const QByteArray &ba, int reserve)
{
std::vector<QByteArray> ret;
ret.reserve(reserve);
int start = 0;
int next = ba.indexOf('\n', start);
while (next > 0) {
ret.push_back(ba.mid(start, next - start));
start = next + 1;
next = ba.indexOf('\n', start);
}
return ret;
}
static CMakeComplData fetchData()
{
CMakeComplData ret;
auto cmds = runCMake(QStringLiteral("--help-command-list"));
auto vars = runCMake(QStringLiteral("--help-variable-list"));
auto props = runCMake(QStringLiteral("--help-property-list"));
// The numbers are from counting the number of props/vars/cmds
// from the output of cmake --help-*
ret.m_commands = parseList(cmds, 125);
ret.m_vars = parseList(vars, 627);
ret.m_props = parseList(props, 497);
return ret;
}
bool CMakeCompletion::isCMakeFile(const QUrl &url)
{
auto urlString = url.fileName();
return urlString == QStringLiteral("CMakeLists.txt") || urlString.endsWith(QStringLiteral(".cmake"));
}
static void append(std::vector<CMakeCompletion::Completion> &out, std::vector<QByteArray> &&in, CMakeCompletion::Completion::Kind kind)
{
for (auto &&s : in) {
out.push_back({kind, std::move(s)});
}
}
CMakeCompletion::CMakeCompletion(QObject *parent)
: KTextEditor::CodeCompletionModel(parent)
{
}
void CMakeCompletion::completionInvoked(KTextEditor::View *view, const KTextEditor::Range &, InvocationType)
{
if (!m_hasData && isCMakeFile(view->document()->url())) {
CMakeComplData data = fetchData();
append(m_matches, std::move(data.m_commands), Completion::Compl_COMMAND);
append(m_matches, std::move(data.m_vars), Completion::Compl_VARIABLE);
append(m_matches, std::move(data.m_props), Completion::Compl_PROPERTY);
setRowCount(m_matches.size());
m_hasData = true;
}
}
bool CMakeCompletion::shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position)
{
if (!userInsertion) {
return false;
}
if (insertedText.isEmpty()) {
return false;
}
// Dont invoke for comments, wont handle everything of course
if (view->document()->line(position.line()).startsWith(QLatin1Char('#'))) {
return false;
}
return isCMakeFile(view->document()->url());
}
int CMakeCompletion::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_matches.size();
}
static QIcon getIcon(CMakeCompletion::Completion::Kind type)
{
using Kind = CMakeCompletion::Completion::Kind;
if (type == Kind::Compl_PROPERTY) {
static const QIcon icon(QIcon::fromTheme(QStringLiteral("code-block")));
return icon;
} else if (type == Kind::Compl_COMMAND) {
static const QIcon icon(QIcon::fromTheme(QStringLiteral("code-function")));
return icon;
} else if (type == Kind::Compl_VARIABLE) {
static const QIcon icon(QIcon::fromTheme(QStringLiteral("code-variable")));
return icon;
} else {
Q_UNREACHABLE();
return {};
}
}
QVariant CMakeCompletion::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return {};
if (role != Qt::DisplayRole && role != Qt::DecorationRole)
return {};
if (index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole) {
return m_matches.at(index.row()).text;
} else if (role == Qt::DecorationRole && index.column() == KTextEditor::CodeCompletionModel::Icon) {
return getIcon(m_matches.at(index.row()).kind);
}
return {};
}
/*
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KATE_CMAKE_COMPLETION_H
#define KATE_CMAKE_COMPLETION_H
#include <ktexteditor/codecompletionmodel.h>
#include <ktexteditor/codecompletionmodelcontrollerinterface.h>
#include <ktexteditor/view.h>
#include <QStandardItemModel>
/**
* Project wide completion support.
*/
class CMakeCompletion : public KTextEditor::CodeCompletionModel, public KTextEditor::CodeCompletionModelControllerInterface
{
Q_OBJECT
Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface)
using Controller = KTextEditor::CodeCompletionModelControllerInterface;
public:
/**
* Construct project completion.
* @param plugin our plugin
*/
explicit CMakeCompletion(QObject *parent = nullptr);
/**
* This function is responsible to generating / updating the list of current
* completions. The default implementation does nothing.
*
* When implementing this function, remember to call setRowCount() (or implement
* rowCount()), and to generate the appropriate change notifications (for instance
* by calling QAbstractItemModel::reset()).
* @param view The view to generate completions for
* @param range The range of text to generate completions for
* */
void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType) override;
bool shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) override;
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
static bool isCMakeFile(const QUrl &url);
// KTextEditor::Range completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position) override;
// void allMatches(QStandardItemModel &model, KTextEditor::View *view, const KTextEditor::Range &range) const;
struct Completion {
enum Kind {
Compl_PROPERTY,
Compl_VARIABLE,
Compl_COMMAND,
} kind;
QByteArray text;
};
private:
std::vector<Completion> m_matches;
bool m_hasData = false;
};
#endif
/*
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "cmaketoolsplugin.h"
#include <KPluginFactory>
#include <KTextEditor/MainWindow>
#include <KXMLGUIFactory>
#include <ktexteditor/codecompletioninterface.h>
#include <QDebug>
K_PLUGIN_FACTORY_WITH_JSON(CMakeToolsPluginFactory, "cmaketoolsplugin.json", registerPlugin<CMakeToolsPlugin>();)
CMakeToolsPlugin::CMakeToolsPlugin(QObject *parent, const QList<QVariant> &)
: KTextEditor::Plugin(parent)
{
}
CMakeToolsPlugin::~CMakeToolsPlugin() = default;
QObject *CMakeToolsPlugin::createView(KTextEditor::MainWindow *mainWindow)
{
return new CMakeToolsPluginView(this, mainWindow);
}
CMakeToolsPluginView::CMakeToolsPluginView(CMakeToolsPlugin *plugin, KTextEditor::MainWindow *mainwindow)
: QObject(plugin)
, m_mainWindow(mainwindow)
{
connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &CMakeToolsPluginView::onViewCreated);
/**
* connect for all already existing views
*/
const auto views = m_mainWindow->views();
for (KTextEditor::View *view : views) {
onViewCreated(view);
}
}
CMakeToolsPluginView::~CMakeToolsPluginView()
{
m_mainWindow->guiFactory()->removeClient(this);
}
void CMakeToolsPluginView::onViewCreated(KTextEditor::View *v)
{
if (!CMakeCompletion::isCMakeFile(v->document()->url()))
return;
qWarning() << "Registering code completion model for view <<" << v << v->document()->url();
KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>(v);
if (cci) {
cci->registerCompletionModel(&m_completion);
}
}
#include "cmaketoolsplugin.moc"
/*
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KATE_CMAKE_TOOLS_PLUGIN_H
#define KATE_CMAKE_TOOLS_PLUGIN_H
#include <KTextEditor/Plugin>
#include <KXMLGUIClient>
#include <QVariant>
#include "cmakecompletion.h"
/**
* Plugin
*/
class CMakeToolsPlugin : public KTextEditor::Plugin
{
Q_OBJECT
public:
explicit CMakeToolsPlugin(QObject *parent = nullptr, const QList<QVariant> & = QList<QVariant>());
~CMakeToolsPlugin() override;
QObject *createView(KTextEditor::MainWindow *mainWindow) override;
};
/**
* Plugin view
*/
class CMakeToolsPluginView : public QObject, public KXMLGUIClient
{
Q_OBJECT
public:
CMakeToolsPluginView(CMakeToolsPlugin *plugin, KTextEditor::MainWindow *mainwindow);
~CMakeToolsPluginView() override;
private Q_SLOTS:
void onViewCreated(KTextEditor::View *v);
private:
KTextEditor::MainWindow *m_mainWindow;
CMakeCompletion m_completion;
};
#endif
{
"KPlugin": {
"Description": "CMake plugin for kate",
"Name": "CMake Tools",
"ServiceTypes": [
"KTextEditor/Plugin"
]
}
}
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