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 dieselbeidundapiVersionzurückgeben wieplugin.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 Siefalsenur 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 Siereset()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-destruktiveTokenId-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.