--- name: kate-plugin description: "Develop C++ plugins for Kate text editor - KTextEditor interface, CMake, Qt widgets, action system" metadata: author: mte90 version: "1.0.0" tags: - kate - kde - text-editor - plugin - c++ - qt - kde-frameworks --- # Kate Plugin Development Develop C++ plugins for Kate text editor using KTextEditor interface. ## Overview Kate (KDE Advanced Text Editor) is a powerful programmable editor. Plugins extend functionality through C++ using the KTextEditor framework. **Note:** PyKDE6 does not exist. Kate plugins are developed in C++ using the native KTextEditor API. ### Plugin Types - **KTextEditor Plugins** - C++ plugins using KTextEditor interface (primary method) - **LSP Plugins** - Language Server Protocol clients - **Python Scripts** - Limited scripting via console (not full plugins) ### Plugin Locations ``` # System plugins /usr/share/kate/plugins/ # User plugins ~/.local/share/kate/plugins/ ~/.kde/share/kate/plugins/ ``` ## KTextEditor API ### Document Interface ```python from PyKDE6 import ktexteditor from PyQt6.QtCore import QObject class MyPlugin(QObject): def __init__(self, parent=None): super().__init__(parent) self.doc = None def setDocument(self, doc: ktexteditor.Document): self.doc = doc # Document methods def insertText(self, text: str): self.doc.insertText(text) def getText(self) -> str: return self.doc.text() def getLine(self, line: int) -> str: return self.doc.line(line) def getCursor(self) -> ktextEditor.Cursor: return self.doc.cursorPosition() ``` ### View Interface ```python class MyPlugin(QObject): def __init__(self, parent=None): super().__init__(parent) self.view = None def setView(self, view: ktexteditor.View): self.view = view # View methods def getSelection(self) -> str: return self.view.selectionText() def setSelection(self, start: Cursor, end: Cursor): self.view.setSelection(start, end) def gotoCursor(self, line: int, col: int): cursor = ktexteditor.Cursor(line, col) self.view.setCursorPosition(cursor) def getActiveLanguage(self) -> str: return self.view.activeLanguageMode() ``` ### Cursor and Range ```python from PyKDE6 import ktexteditor # Create cursor (line, column) cursor = ktexteditor.Cursor(10, 5) # Create range start = ktexteditor.Cursor(10, 0) end = ktexteditor.Cursor(10, 20) range_ = ktexteditor.Range(start, end) # Cursor methods line = cursor.line() col = cursor.column() # Range methods start = range_.start() end = range_.end() text = range_.text(document) # Get text in range ``` ## Plugin Structure ### Python Plugin Layout ``` my-plugin/ ├── plugin/ │ ├── __init__.py │ └── myplugin.py ├── ui/ │ └── mydialog.ui ├── ../../../metadata.json └── ../../../myplugin.desktop ``` ### metadata.json ```json { "KPlugin": { "Name": "My Plugin", "Comment": "Description of my plugin", "Icon": "kate", "Authors": [ { "Name": "Your Name", "Email": "you@example.com" } ], "Version": "1.0.0", "License": "MIT", "ServiceTypes": [ "Kate/Plugin" ], "Category": "Editor", "ApiVersion": "2.0" } } ``` ### Desktop File ```desktop [Desktop Entry] Name=My Plugin Comment=A brief description Icon=kate Type=Service X-KDE-Library=myplugin X-KDE-PluginKeyword=myplugin X-KDE-ParentApp=kate X-KDE-InitFunction=load_myplugin X-KDE-UnloadFunction=unload_myplugin ``` ### Python Plugin Class ```python from PyKDE6 import ktexteditor from PyQt6.QtCore import QObject, pyqtSlot class MyPlugin(ktexteditor.Plugin): def __init__(self, parent=None): super().__init__(parent) self.widgets = [] def addViewWidget(self, mainwindow): """Called when plugin is activated""" widget = MyPluginWidget(mainwindow) mainwindow.addToolView(widget) self.widgets.append(widget) return widget def removeViewWidget(self, widget): """Called when plugin is deactivated""" widget.mainWindow().removeToolView(widget) self.widgets.remove(widget) def readProperties(self, group, file): """Load settings""" self.enabled = group.readEntry("Enabled", True) def writeProperties(self, group, file): """Save settings""" group.writeEntry("Enabled", self.enabled) ``` ## JavaScript Plugins ### Action Scripting ```javascript // In Kate's console or .katescript files // Basic editing view.insertText("Hello, World!"); var selected = view.selectionText(); document.setText("New document content"); // Cursor navigation view.setCursorPosition(10, 5); var cursor = view.cursorPosition(); // Selection view.select(cursor, new Cursor(10, 20)); // Search and replace view.search("find", Cursor(0, 0)); view.replace("find", "replace"); ``` ### Key Shortcuts ```javascript // Define keyboard shortcuts // Edit > Shortcuts > Custom Shortcuts ``` ## Examples ### Simple Insert Plugin ```python from PyKDE6 import ktexteditor from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton class SimpleInsertPlugin(ktexteditor.Plugin): def __init__(self, parent=None): super().__init__(parent) def addViewWidget(self, mainwindow): widget = QWidget() layout = QVBoxLayout() btn = QPushButton("Insert Timestamp") btn.clicked.connect(lambda: self.insertTimestamp(mainwindow)) layout.addWidget(btn) widget.setLayout(layout) mainwindow.addToolView(widget) return widget def insertTimestamp(self, mainwindow): from datetime import datetime view = mainwindow.activeView() if view: view.insertText(datetime.now().isoformat()) ``` ### Find and Replace Plugin ```python class FindReplacePlugin(ktexteditor.Plugin): def __init__(self, parent=None): super().__init__(parent) self.range = None def findNext(self, view, text): doc = view.document() cursor = view.cursorPosition() # Search from cursor result = doc.searchText(text, cursor) if result.isValid(): view.setSelection(result) view.setCursorPosition(result.end()) return True return False def replace(self, view, find, replace): if view.hasSelection(): selected = view.selectionText() if selected == find: view.removeSelectionText() view.insertText(replace) return True return False ``` ## Configuration ### Settings Integration ```python class MyPlugin(ktexteditor.Plugin): def __init__(self, parent=None): super().__init__(parent) self.config = {} def createConfigPage(self, parent, alias): """Create configuration page""" from PyQt6.QtWidgets import QFormLayout, QLineEdit, QWidget page = QWidget(parent) layout = QFormLayout() self.pathInput = QLineEdit() self.pathInput.setText(self.config.get("path", "")) layout.addRow("Path:", self.pathInput) page.setLayout(layout) return page def applyConfig(self): """Apply configuration changes""" self.config["path"] = self.pathInput.text() ``` ## Best Practices ### 1. Initialize Properly ```python # Good: Check for valid document def do_something(self, view): if view and view.document(): doc = view.document() # Work with document ``` ### 2. Handle Errors ```python # Good: Error handling try: view.insertText(text) except Exception as e: print(f"Error inserting text: {e}") ``` ### 3. Use Signals ```python # Connect to document signals doc.textChanged.connect(self.onTextChanged) doc.cursorPositionChanged.connect(self.onCursorMoved) ``` ## Advanced Topics ### KTextEditor Interface ```cpp // Document interface - text manipulation KTextEditor::Document* doc = view->document(); // Insert text at cursor doc->insertText(cursor, "text"); // Replace range doc->replaceText(start, end, "new text"); // Search and replace doc->setSearchText("pattern", KTextEditor::Search::CaseInsensitive); ``` ### View Interface ```cpp // Cursor management KTextEditor::Cursor cursor = view->cursorPosition(); view->setCursorPosition(cursor); // Selection view->selection(); view->setSelection(range); view->clearSelection(); // Folding view->toggleFolding(idx); ``` ### Advanced Features ```cpp // Variables (user-configurable settings) KTextEditor::VariableInterface* varIface = qobject_cast(doc); QVariant value = varIface->variable("my-var"); // Plugins can register: # Document: new editing features # View: visual enhancements # Tools: integrated utilities # Tolls: additional functionality ``` ### Best Practices ```cpp // ✅ GOOD: Use proper interfaces KTextEditor::Editor* editor = KTextEditor::Editor::self(); // ✅ GOOD: Check for null if (view && view->document()) { // ... } // ✅ GOOD: Use signals for async connect(doc, &KTextEditor::Document::textChanged, this, &MyPlugin::onTextChanged); // ❌ BAD: Direct manipulation without interfaces ``` ### Common Issues | Issue | Solution | |-------|----------| | Plugin not loading | Check JSON metadata, correct category | | Crash on load | Initialize in init() not constructor | | Feature not working | CheckKatePart version requirement | --- ## References - **KDE Documentation**: https://docs.kde.org/ - **Kate API**: https://api.kde.org/ - **KTextEditor**: https://docs.kde.org/stable/en/kate/kate-part/ - **KDE Developer**: https://developer.kde.org/