Zum Inhalt

Plugin-API — Überblick

Alle PluginApi-Typen liegen in namespace MTE::plugin. Die öffentlichen Header dürfen keinen Qt-, Scintilla- oder MTE-internen Header einbinden — nur die Standardbibliothek. Die eine bewusste Ausnahme ist der Opt-in-Ordner PluginApi/Qt/ (PluginQtGlue.h, ISettingsPage.h, ThemeFormat.h) für Plugins, die eigene Qt-Widgets mitbringen.

Derselbe Vertrag wird von zwei Backends erfüllt: dem nativen Qt-Loader (Shared Libraries) und dem Out-of-Process-Python-Backend (Python-Plugins). Dieser Abschnitt dokumentiert die C++-Oberfläche; jeder hier aufgeführte Backend-neutrale Dienst hat ein Python-Gegenstück auf ctx.

Plugin-Lebenszyklus

Ein natives Plugin implementiert eine Klasse:

class IPlugin {
public:
    virtual PluginInfo info() const = 0;                    // id, name, version, apiVersion
    virtual bool initialize(IPluginContext& ctx) = 0;       // register everything here
    virtual void shutdown() noexcept = 0;                   // release every token
};
  • info() muss dieselbe id und apiVersion zurückgeben wie plugin.json; der Host gleicht beide ab und verweigert bei Abweichung das Laden.
  • initialize() läuft einmal beim Start (nachdem alle Plugins erkannt wurden). Registrieren Sie hier Commands, Menüs, Provider und Panels; geben Sie false nur bei einem echten Fehler zurück (der Host entlädt das Plugin und protokolliert es).
  • shutdown() läuft beim Beenden (oder wenn das Plugin entladen wird). Es darf nicht werfen; rufen Sie reset() auf jedem Token auf und setzen Sie Ihre gespeicherten Dienstzeiger auf null.

RAII-Tokens

Jede Registrierung liefert ein Token zurück (CommandToken, MenuToken, DockToken, SettingsPageToken, Subscription, …) — ein Move-only BasicToken, das die Registrierung besitzt: Wird es zerstört oder mit reset() zurückgesetzt, wird das Command / der Menüeintrag / das Panel / das Abonnement widerrufen. Halten Sie jedes Token als Member und geben Sie es in shutdown() frei. Zwei Accessoren sind wichtig:

  • id() — nicht-destruktive TokenId-Sicht, für APIs, die das Element adressieren, ohne es zu übernehmen (z. B. IMenuRegistry::setItemChecked).
  • release() — löst die Bindung und liefert die Id zurück; das Token widerruft danach nichts mehr. Selten das, was Sie wollen.

Der Service-Locator

initialize() erhält einen IPluginContext, einen Service-Locator im Besitz des Hosts. Speichern Sie den Kontext nicht über initialize() hinaus; speichern Sie die einzelnen Dienstreferenzen, die Sie benötigen (jeder Dienst überlebt jedes Plugin).

Accessor Schnittstelle Verwendung für
ctx.editor() IEditorService Puffer lesen/bearbeiten, Auswahl, Caret, Dateien, Suche, Lesezeichen, Einrückung, Split-View-Gruppen
ctx.commands() ICommandRegistry Benutzeraktionen registrieren / aufrufen (mit Shortcuts)
ctx.menus() IMenuRegistry Menüeinträge, Trennlinien, ankreuzbare Umschalter
ctx.docks() IDockRegistry andockbare Seitenpanels
ctx.documents() IDocumentTypeRegistry vollwertige MainWindow-Tab-Typen (Diff, Log-Ansicht, …)
ctx.compare() ICompareService die zweigeteilte Vergleichsansicht öffnen
ctx.diagnostics() IDiagnosticsSink Unterschlängelungen + Randsymbole veröffentlichen
ctx.events() IEventBus Editor-Events abonnieren / veröffentlichen
ctx.completions() ICompletionRegistry Autovervollständigungs-Kandidaten beisteuern
ctx.contextMenu() IContextMenuRegistry dynamische Einträge im Rechtsklick-Menü des Editors
ctx.snippetSessions() ISnippetSessionService per Tab expandierbare Snippets bereitstellen (der Host führt die Feldsession aus)
ctx.hover() IHoverRegistry Hover-Tooltips
ctx.signatureHelp() ISignatureHelpRegistry Call-Tips bei ( / ,
ctx.symbols() ISymbolRegistry Dokument- / Workspace-Symbole (Go-to-Symbol)
ctx.statusBar() IStatusBar flüchtige Meldungen + persistente Segmente
ctx.settings() ISettings persistente Schlüssel/Wert-Konfiguration pro Plugin
ctx.appSettings() const ISettings& app-weite Einstellungen lesen (AppSettingsKeys.h)
ctx.documentSettings() IDocumentSettings .editorconfig-geschichtete Konfiguration pro Dokument
ctx.settingsRegistry() ISettingsRegistry eine Seite zum Einstellungen-Dialog hinzufügen
ctx.themePalette() IThemePalette benannte Theme-Farben, Theme-Editor-Integration
ctx.log() ILogger trace / debug / info / warning / error
ctx.pluginDataDir() std::filesystem::path Ihr privates beschreibbares Verzeichnis
ctx.diagnosticsDir() std::filesystem::path gemeinsames Verzeichnis für Crash-Artefakte + Logs (für Plugins nur lesend)

Die API später zu erweitern bedeutet, eine neue Dienstschnittstelle samt Accessor hinzuzufügen; bestehende Plugins bleiben quell- und ABI-kompatibel.

IEditorService — das Dokumentmodell

Der Editor aus Sicht der Plugins — kein Scintilla, kein Qt. Dokumente werden über eine opake DocumentId referenziert (std::uint64_t, 0 = ungültig). Offsets sind Byte-Offsets in UTF-8-Text; MatchRange ist halboffen {start, end}, wobei noMatch() „nicht gefunden“ bedeutet; EditorPosition ist 0-basiert {line, column}.

// Documents
DocumentId               activeDocument() const;
std::vector<DocumentId>  openDocuments() const;
void                     setActiveDocument(DocumentId);
std::optional<std::filesystem::path> filePath(DocumentId) const;
DocumentId               openFile(std::filesystem::path);
bool                     saveDocument(DocumentId);
bool                     isModified(DocumentId) const;

// Text, selection, caret
std::string   text(DocumentId) const;              // whole buffer
std::string   textRange(DocumentId, start, end) const;
void          setText(DocumentId, std::string_view);
std::int64_t  length(DocumentId) const;
std::string   selectedText(DocumentId) const;
void          replaceSelection(DocumentId, std::string_view);
MatchRange    selectionRange(DocumentId) const;
void          setSelection(DocumentId, MatchRange);
EditorPosition caret(DocumentId) const;
void          setCaret(DocumentId, EditorPosition);

// Search & replace (SearchOptions: case, whole-word, regex, wrap)
MatchRange    findInRange(DocumentId, needle, range, SearchOptions) const;
void          replaceTarget(DocumentId, MatchRange, std::string_view);

// Bookmarks, marks, URL highlighting
void toggleBookmark(DocumentId, line);   std::vector<std::int64_t> bookmarkedLines(DocumentId) const;
void markRange(DocumentId, MatchRange);  void clearMarks(DocumentId);

// Language & indentation
std::string      colorizerId(DocumentId) const;      // "cpp", "" if none
IndentationStyle indentation(DocumentId) const;      // {useTabs, width}
void             setIndentation(DocumentId, bool useTabs, int width);

// Semantic classification (from the active colorizer's style rules;
// cheap -- no re-lex). CategorySpan = {start, length, SyntaxCategory}.
// The spell checker keeps to Comment/Documentation/String spans this way.
std::vector<CategorySpan> textCategories(DocumentId, start, end) const;

// Clipboard
std::string clipboardText() const;

Zusätzlich stellt es das Tab-Gruppen-Layout der geteilten Ansicht in Backend-neutralen Begriffen bereit (groupCount(), groupOf(), groupWeights(), moveDocumentToGroup(), setGroupWeights()), sodass ein Plugin Fensterbereichs-Anordnungen erfassen und wiederherstellen kann, ohne Qt-Splitter zu sehen. Die vollständige Liste finden Sie in PluginApi/IEditorService.h — der Header ist die maßgebliche Referenz.

Threading

Alle Kontext- und Dienstaufrufe erfolgen auf dem UI-Thread des Hosts. Rufen Sie Dienste nicht aus einem selbst gestarteten Hintergrund-Thread auf; wechseln Sie zuerst zurück auf den UI-Thread.

Versionierung

MTE_PLUGIN_API_VERSION (derzeit 1) ist in PluginApi/PluginInfo.h definiert. Ein Plugin muss in plugin.json und in IPlugin::info().apiVersion denselben Wert angeben, sonst verweigert der Host das Laden.

Pre-Release-Richtlinie

Vor der ersten öffentlichen Veröffentlichung kann sich der Vertrag noch ändern, ohne dass MTE_PLUGIN_API_VERSION erhöht wird. Nach der Veröffentlichung erhöht jede rückwärtsinkompatible Änderung an einer bestehenden Schnittstelle die Version.