Skip to content
cccskills
FreePublished on GitHub

kate-plugin

Use when developing C++ plugins for Kate - KTextEditor Framework, CMake/ECM, Qt threading, KDE plugin architecture

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md17.5 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Kate Plugin Development

C++ plugins for Kate using KTextEditor Framework, KDE Frameworks, and Qt.

Overview

Kate plugins extend functionality through C++ using KTextEditor framework.

/usr/share/kate/plugins/    # System
~/.local/share/kate/plugins/ # User

Plugin Architecture

#include <KPluginFactory>
#include <KTextEditor/Plugin>

class MyPlugin : public KTextEditor::Plugin {
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.kde.KTextEditor.Plugin" FILE "metadata.json")
public:
    explicit MyPlugin(QObject *parent, const QVariantList &)
        : KTextEditor::Plugin(parent) {}
    ~MyPlugin() override = default;

    QWidget *createConfigWidget(QWidget *p) override { return new ConfigWidget(p); }
    void readConfig(KConfigGroup &g) override { m_enabled = g.readEntry("Enabled", true); }
    void writeConfig(KConfigGroup &g) override { g.writeEntry("Enabled", m_enabled); }
    void init() override {}
    void addView(KTextEditor::MainWindow *mw) override {
        auto *v = mw->activeView();
        if (v) connect(v->document(), &KTextEditor::Document::textChanged, this, &MyPlugin::onTextChanged);
    }
    void removeView(KTextEditor::MainWindow *) override {}
private slots:
    void onTextChanged(KTextEditor::Document *doc) { qDebug() << doc->url(); }
private:
    bool m_enabled = true;
};

K_PLUGIN_FACTORY_WITH_JSON(MyPluginFactory, "metadata.json", registerPlugin<MyPlugin>();)
{
    "KPlugin": {
        "Name": "My Plugin",
        "Description": "A C++ plugin for Kate",
        "Icon": "kate",
        "Authors": [{"Name": "Dev", "Email": "dev@example.com"}],
        "Version": "1.0.0",
        "License": "GPL-2.0-or-later",
        "ServiceTypes": ["KTextEditor/Plugin"],
        "Category": "Language"
    },
    "X-KTextEditor": {"APIVersion": "5"}
}
cmake_minimum_required(VERSION 3.16)
project(myplugin VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 17)
find_package(ECM 5.90 REQUIRED NO_MODULE)
find_package(KF5 REQUIRED COMPONENTS CoreAddons Config KTextEditor WidgetsAddons)
include(KDEInstallDirs)
include(KDECMakeSettings)

kcoreaddons_add_plugin(myplugin INSTALL_NAMESPACE "kate"
    SOURCES myplugin.cpp configwidget.cpp
    LIBRARIES KF5::CoreAddons KF5::ConfigGui KF5::KTextEditor)
install(FILES metadata.json DESTINATION ${KDE_INSTALL_KTPLUGINDIR}/myplugin)

KTextEditor API

#include <KTextEditor/Document>
#include <KTextEditor/View>
#include <KTextEditor/Range>
#include <KTextEditor/Cursor>

class DocumentHandler {
public:
    explicit DocumentHandler(KTextEditor::Document *doc) : m_doc(doc) {}
    void insertText(const KTextEditor::Cursor &cur, const QString &text) { m_doc->insertText(cur, text); }
    void replaceText(const KTextEditor::Range &range, const QString &text) { m_doc->replaceText(range, text); }
    QString getText() const { return m_doc->text(); }
    QString getLine(int n) const { return m_doc->line(n); }
    int lineCount() const { return m_doc->lines(); }
    KTextEditor::Range search(const QString &pat, const KTextEditor::Cursor &start) { return m_doc->searchText(pat, start); }
private:
    KTextEditor::Document *m_doc;
};

class ViewHandler {
public:
    explicit ViewHandler(KTextEditor::View *view) : m_view(view) {}
    KTextEditor::Cursor getCursorPosition() const { return m_view->cursorPosition(); }
    void setCursorPosition(int line, int col) { m_view->setCursorPosition(line, col); }
    QString getSelection() const { return m_view->selectionText(); }
    void setSelection(const KTextEditor::Range &range) { m_view->setSelection(range); }
    void clearSelection() { m_view->clearSelection(); }
    void toggleFolding(const KTextEditor::Range &range) { m_view->toggleFolding(range); }
private:
    KTextEditor::View *m_view;
};

void positions(KTextEditor::Document *doc) {
    KTextEditor::Cursor cursor(10, 5);
    KTextEditor::Range range(10, 0, 10, 20);
    if (range.isValid()) { QString text = range.text(doc); bool contains = range.contains(cursor); }
}

CMake Build System

cmake_minimum_required(VERSION 3.16)
project(myplugin VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
find_package(ECM 5.90 REQUIRED NO_MODULE)
find_package(KF5 REQUIRED COMPONENTS CoreAddons Config KTextEditor WidgetsAddons)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings)

kcoreaddons_add_plugin(myplugin INSTALL_NAMESPACE "kate"
    SOURCES main.cpp myplugin.cpp configwidget.cpp
    LIBRARIES KF5::CoreAddons KF5::ConfigGui KF5::KTextEditor)

enable_testing()
find_package(Qt5 REQUIRED COMPONENTS Test)
qt5_add_tests(test_myplugin SOURCES test_myplugin.cpp LIBRARIES Qt5::Test KF5::KTextEditor)

install(TARGETS myplugin LIBRARY DESTINATION ${KDE_INSTALL_KTPLUGINDIR}/myplugin)
install(FILES metadata.json DESTINATION ${KDE_INSTALL_KTPLUGINDIR}/myplugin)
#include <QJsonObject>
#include <QJsonDocument>
#include <QFile>

QJsonObject loadConfig(const QString &path) {
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly)) return {};
    QJsonParseError error;
    QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
    return doc.object();
}

void saveConfig(const QString &path, const QJsonObject &config) {
    QFile file(path);
    if (file.open(QIODevice::WriteOnly)) file.write(QJsonDocument(config).toJson(QJsonDocument::Indented));
}

Threaded Agent Architecture

#include <QThread>
#include <QQueue>
#include <QMutex>
#include <QWaitCondition>

class AgentThread : public QThread {
    Q_OBJECT
public:
    explicit AgentThread(QObject *parent = nullptr) : QThread(parent), m_running(true) {}
    ~AgentThread() override { m_running = false; m_condition.wakeOne(); wait(); }
    void enqueueTask(const QString &task) { QMutexLocker locker(&m_mutex); m_tasks.enqueue(task); m_condition.wakeOne(); }
protected:
    void run() override {
        while (m_running) {
            QMutexLocker locker(&m_mutex);
            while (m_tasks.isEmpty() && m_running) m_condition.wait(&m_mutex);
            if (!m_running) break;
            QString task = m_tasks.dequeue();
            locker.unlock();
            processTask(task);
        }
    }
private:
    void processTask(const QString &task) { emit taskCompleted(task, "Result: " + task); }
signals:
    void taskCompleted(const QString &task, const QString &result);
private:
    QQueue<QString> m_tasks; QMutex m_mutex; QWaitCondition m_condition; bool m_running;
};

class AgentManager : public QObject {
    Q_OBJECT
public:
    explicit AgentManager(QObject *parent = nullptr) : QObject(parent), m_agentThread(new AgentThread(this)) {
        connect(m_agentThread.data(), &AgentThread::taskCompleted, this, &AgentManager::onTaskCompleted, Qt::QueuedConnection);
        m_agentThread->start();
    }
    void enqueueAgentTask(const QString &task) { m_agentThread->enqueueTask(task); }
private slots:
    void onTaskCompleted(const QString &task, const QString &result) { qDebug() << "Task:" << task; emit resultReady(task, result); }
signals:
    void resultReady(const QString &task, const QString &result);
private:
    QScopedPointer<AgentThread> m_agentThread;
};

struct AgentTask { QString id; QString data; };
struct AgentResult { QString id; QString output; };

class AgentLoop : public QObject {
    Q_OBJECT
public:
    explicit AgentLoop(QObject *parent = nullptr) : QObject(parent), m_thread(new QThread(this)) {
        m_processor = new AgentProcessor();
        m_processor->moveToThread(m_thread);
        connect(this, &AgentLoop::taskEnqueued, m_processor, &AgentProcessor::processTask, Qt::QueuedConnection);
        connect(m_processor, &AgentProcessor::taskFinished, this, &AgentLoop::onTaskFinished, Qt::QueuedConnection);
        m_thread->start();
    }
    ~AgentLoop() override { m_thread->quit(); m_thread->wait(); }
    void enqueue(const AgentTask &task) { QMutexLocker locker(&m_mutex); m_queue.enqueue(task); emit taskEnqueued(task); }
private slots:
    void onTaskFinished(const AgentResult &result) {
        emit taskCompleted(result);
        QMutexLocker locker(&m_mutex);
        if (!m_queue.isEmpty()) emit taskEnqueued(m_queue.dequeue());
    }
signals:
    void taskEnqueued(const AgentTask &task);
    void taskCompleted(const AgentResult &result);
private:
    QThread *m_thread; AgentProcessor *m_processor; QQueue<AgentTask> m_queue; QMutex m_mutex;
};

Interface-Based Design

#include <memory>
#include <QString>
#include <QVariant>

class IThreadStorage {
public:
    virtual ~IThreadStorage() = default;
    virtual void store(const QString &key, const QVariant &value) = 0;
    virtual QVariant retrieve(const QString &key) = 0;
    virtual bool has(const QString &key) = 0;
    virtual void remove(const QString &key) = 0;
    virtual void clear() = 0;
};

class JsonThreadStorage : public IThreadStorage {
public:
    explicit JsonThreadStorage(const QString &filePath) : m_filePath(filePath) { load(); }
    void store(const QString &key, const QVariant &value) override { m_data[key] = QJsonValue::fromVariant(value); save(); }
    QVariant retrieve(const QString &key) override { return m_data.contains(key) ? m_data[key].toVariant() : QVariant(); }
    bool has(const QString &key) override { return m_data.contains(key); }
    void remove(const QString &key) override { m_data.remove(key); save(); }
    void clear() override { m_data = QJsonObject(); save(); }
private:
    void load() { QFile f(m_filePath); if (f.open(QIODevice::ReadOnly)) m_data = QJsonDocument::fromJson(f.readAll()).object(); }
    void save() { QFile f(m_filePath); if (f.open(QIODevice::WriteOnly)) f.write(QJsonDocument(m_data).toJson()); }
    QString m_filePath; QJsonObject m_data;
};

class MemoryThreadStorage : public IThreadStorage {
public:
    void store(const QString &key, const QVariant &value) override { m_data[key] = value; }
    QVariant retrieve(const QString &key) override { return m_data.value(key); }
    bool has(const QString &key) override { return m_data.contains(key); }
    void remove(const QString &key) override { m_data.remove(key); }
    void clear() override { m_data.clear(); }
private:
    QMap<QString, QVariant> m_data;
};

class AgentService {
public:
    explicit AgentService(std::unique_ptr<IThreadStorage> storage) : m_storage(std::move(storage)) {}
    void runAgent(const QString &taskId) { m_storage->store("lastTask", taskId); m_storage->store("lastResult", processTask(taskId)); }
private:
    QString processTask(const QString &id) { return "Processed: " + id; }
    std::unique_ptr<IThreadStorage> m_storage;
};

Configuration with KConfig/QSettings

#include <KSharedConfig>
#include <KConfigGroup>

class PluginConfig {
public:
    explicit PluginConfig() : m_config(KSharedConfig::openConfig()), m_group(m_config->group("Plugin")) {}
    void load() { m_enabled = m_group.readEntry("Enabled", true); m_maxThreads = m_group.readEntry("MaxThreads", 4); }
    void save() { m_group.writeEntry("Enabled", m_enabled); m_group.writeEntry("MaxThreads", m_maxThreads); m_config->sync(); }
    bool isEnabled() const { return m_enabled; }
    void setEnabled(bool v) { m_enabled = v; }
private:
    KSharedConfigPtr m_config; KConfigGroup m_group;
    bool m_enabled = true; int m_maxThreads = 4;
};

class StructuredConfig {
public:
    QJsonObject toJsonObject() const { QJsonObject obj; obj["enabled"] = m_enabled; QJsonObject t; t["max"] = m_maxThreads; obj["threads"] = t; return obj; }
    void fromJsonObject(const QJsonObject &obj) { m_enabled = obj["enabled"].toBool(true); if (obj.contains("threads")) m_maxThreads = obj["threads"].toObject()["max"].toInt(4); }
private:
    bool m_enabled = true; int m_maxThreads = 4;
};

class ConfigMigrator {
public:
    static void migrate(KConfigGroup &group) {
        int version = group.readEntry("ConfigVersion", 0);
        if (version < 2) { group.writeEntry("Plugin.Enabled", group.readEntry("Enabled", true)); group.writeEntry("ConfigVersion", 2); }
    }
};

Permission System

#include <QSet>

enum class Permission { ReadDocument, WriteDocument, AccessNetwork };

class PermissionManager {
public:
    enum class Decision { Granted, Denied, Pending };
    Decision requestPermission(Permission perm, const QString &reason) {
        if (m_granted.contains(perm)) return Decision::Granted;
        emit permissionRequested(perm, reason);
        return Decision::Pending;
    }
    void grantPermission(Permission perm) { m_granted.insert(perm); emit permissionGranted(perm); }
    void denyPermission(Permission perm) { m_denied.insert(perm); emit permissionDenied(perm); }
    bool hasPermission(Permission perm) const { return m_granted.contains(perm); }
signals:
    void permissionRequested(Permission perm, const QString &reason);
    void permissionGranted(Permission perm);
    void permissionDenied(Permission perm);
private:
    QSet<Permission> m_granted, m_denied;
};

class DocumentAccessController {
public:
    explicit DocumentAccessController(PermissionManager *pm) : m_permManager(pm) {}
    void requestWriteAccess(const QString &reason) {
        auto decision = m_permManager->requestPermission(Permission::WriteDocument, reason);
        if (decision == PermissionManager::Decision::Granted) performWrite();
    }
private:
    void performWrite() {}
    PermissionManager *m_permManager;
};

class PluginActions : public QObject {
    Q_OBJECT
public:
    explicit PluginActions(KTextEditor::MainWindow *mainWindow, PermissionManager *pm)
        : QObject(mainWindow), m_mainWindow(mainWindow), m_permManager(pm) {
        auto *action = m_mainWindow->actionCollection()->addAction("write");
        connect(action, &KAction::triggered, this, &PluginActions::onWriteRequested);
    }
private slots:
    void onWriteRequested() {
        if (!m_permManager->hasPermission(Permission::WriteDocument)) {
            m_permManager->requestPermission(Permission::WriteDocument, "Needs write access"); return;
        }
        performWrite();
    }
    void performWrite() {}
private:
    KTextEditor::MainWindow *m_mainWindow; PermissionManager *m_permManager;
};

Testing C++ Plugins

#include <QTest>
#include <QObject>

class DocumentHandlerTest : public QObject {
    Q_OBJECT
private slots:
    void testInsertText() {
        KTextEditor::Document *doc = new KTextEditor::Document(nullptr);
        DocumentHandler handler(doc);
        handler.insertText(KTextEditor::Cursor(0, 0), "Hello");
        QCOMPARE(doc->text(), QString("Hello"));
        delete doc;
    }
    void testReplaceText() {
        KTextEditor::Document *doc = new KTextEditor::Document(nullptr);
        doc->setText("Hello World");
        DocumentHandler handler(doc);
        handler.replaceText(KTextEditor::Range(0, 0, 0, 5), "Hi");
        QCOMPARE(doc->text(), QString("Hi World"));
        delete doc;
    }
};
QTEST_APPLESS_MAIN(DocumentHandlerTest)
#include "test_document_handler.moc"

class MockDocument : public QObject {
    Q_OBJECT
public:
    QString text() const { return m_text; }
    void setText(const QString &t) { m_text = t; }
    void insertText(const KTextEditor::Cursor &, const QString &text) { m_text += text; emit textChanged(this); }
signals:
    void textChanged(KTextEditor::Document *doc);
private:
    QString m_text;
};

class AgentThreadTest : public QObject {
    Q_OBJECT
private slots:
    void testTaskProcessing() {
        AgentThread thread; thread.start();
        bool completed = false;
        connect(&thread, &AgentThread::taskCompleted, [&completed](const QString &, const QString &) { completed = true; });
        thread.enqueueTask("test-task");
        QTest::qWait(1000);
        QVERIFY(completed);
        thread.quit(); thread.wait();
    }
};
QTEST_MAIN(AgentThreadTest)
#include "test_agent_thread.moc"

enable_testing()
find_package(Qt5 REQUIRED COMPONENTS Test)
add_executable(test_myplugin test_main.cpp test_document_handler.cpp test_agent_thread.cpp)
target_link_libraries(test_myplugin PRIVATE Qt5::Test KF5::KTextEditor myplugin)
add_test(NAME myplugin_tests COMMAND test_myplugin)

Common Issues

IssueSolution
Plugin not loadingVerify metadata.json and KPluginFactory
KTextEditor symbols undefinedLink KF5::KTextEditor
Signal/slot not connectingEnsure Q_OBJECT and moc
Crash on addViewCheck null view/document
Config not persistingCall KConfigGroup::sync()
Thread safety issuesUse Qt::QueuedConnection
AgentLoop tasks stuckVerify task queue and signals
Permission requests hangingImplement async callback

Best Practices

  • Use Q_PLUGIN_METADATA with JSON file for registration
  • Always check for null pointers on view/document
  • Use Qt::QueuedConnection for cross-thread signals
  • Implement cleanup in removeView() matching addView()
  • Store config in KConfig with namespaced groups
  • Use smart pointers for interface-based DI
  • Process long tasks in worker threads
  • Emit results via queued signals for UI updates
  • Use QTest with mock KTextEditor interfaces
  • Follow KDE coding style

References

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when building Python async HTTP services or clients with aiohttp - web server routing, middleware, WebSocket, SSE, streaming, client sessions, pytest-aiohttp testing, or troubleshooting SSL and timeout issues

The English summary is being prepared. Showing the original description.

CodeAtCode/oss-ai-skills24Updated Oct 9, 2026

Use when doing structural code search and rewriting - ast-grep linting, refactoring, multi-language patterns

The English summary is being prepared. Showing the original description.

CodeAtCode/oss-ai-skills24Updated Oct 9, 2026

Use when building GBA games with the BPCore Lua engine - entity, sprite and tilemap functions, SRAM save and load, link cable multiplayer protocol, camera and scrolling, or optimization patterns

The English summary is being prepared. Showing the original description.

CodeAtCode/oss-ai-skills24Updated Oct 9, 2026

celery

Free

Use when running background tasks with Celery - worker and broker configuration (Redis, RabbitMQ), task routing by name vs queue, chains/groups/chords, retry patterns (autoretry_for, retry_backoff), acks_late semantics, failure detection, and monitoring with Flower

The English summary is being prepared. Showing the original description.

CodeAtCode/oss-ai-skills24Updated Oct 9, 2026

django

Free

Use when building Django applications - security hardening, authentication and permissions, ORM optimization, PostgreSQL features, Django 6.0, migrations, testing, and ecosystem libraries

The English summary is being prepared. Showing the original description.

CodeAtCode/oss-ai-skills24Updated Oct 9, 2026

Use when customizing Django Admin - save_formset, get_search_results, formsets, queryset optimization, db_index, custom URLs

The English summary is being prepared. Showing the original description.

CodeAtCode/oss-ai-skills24Updated Oct 9, 2026

See all skills by CodeAtCode

Report a problem with this skill