API de plugin — vue d'ensemble¶
Tous les types de PluginApi vivent dans namespace MTE::plugin. Les en-têtes
publics ne peuvent inclure aucun en-tête Qt, Scintilla ou interne à MTE —
uniquement la bibliothèque standard. La seule exception délibérée est le
dossier optionnel PluginApi/Qt/ (PluginQtGlue.h, ISettingsPage.h,
ThemeFormat.h) pour les plugins qui livrent leurs propres widgets Qt.
Le même contrat est rempli par deux backends : le chargeur natif Qt
(bibliothèques partagées) et le backend Python hors processus
(plugins Python). Cette section documente la
surface C++ ; chaque service décrit ici qui est neutre vis-à-vis du backend a
un équivalent Python sur ctx.
Cycle de vie du plugin¶
Un plugin natif implémente une seule classe :
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()doit renvoyer les mêmesidetapiVersionqueplugin.json; l'hôte effectue une vérification croisée et refuse le chargement en cas d'incohérence.initialize()s'exécute une fois au démarrage (après la découverte de tous les plugins). Enregistrez-y vos commandes, menus, fournisseurs et panneaux ; ne renvoyezfalsequ'en cas d'échec réel (l'hôte décharge alors le plugin et journalise l'incident).shutdown()s'exécute à la sortie (ou quand le plugin est déchargé). Elle ne doit pas lever d'exception ; faitesreset()sur chaque jeton et mettez à null vos pointeurs de services stockés.
Jetons RAII¶
Chaque enregistrement renvoie un jeton (CommandToken, MenuToken,
DockToken, SettingsPageToken, Subscription, …) — un BasicToken
déplaçable uniquement (move-only) qui possède l'enregistrement : le
détruire ou appeler reset() révoque la commande / l'élément de menu / le
panneau / l'abonnement. Conservez chaque jeton comme membre et libérez-le dans
shutdown(). Deux accesseurs comptent :
id()— vueTokenIdnon destructive, pour les API qui désignent l'élément sans en prendre possession (p. ex.IMenuRegistry::setItemChecked).release()— détache et renvoie l'id ; le jeton ne révoque alors plus rien. Rarement ce que vous voulez.
Le localisateur de services¶
initialize() reçoit un IPluginContext, un localisateur de services possédé
par l'hôte. Ne stockez pas le contexte au-delà d'initialize() ; stockez
les références des services individuels dont vous avez besoin (chaque service
survit à tous les plugins).
| Accesseur | Interface | À utiliser pour |
|---|---|---|
ctx.editor() |
IEditorService |
lire/modifier le tampon, la sélection, le caret, les fichiers, la recherche, les signets, l'indentation, les groupes de vue scindée |
ctx.commands() |
ICommandRegistry |
enregistrer / invoquer des actions utilisateur (avec raccourcis) |
ctx.menus() |
IMenuRegistry |
éléments de menu, séparateurs, bascules cochables |
ctx.docks() |
IDockRegistry |
panneaux latéraux ancrables |
ctx.documents() |
IDocumentTypeRegistry |
types d'onglets de première classe de la MainWindow (diff, vue de log, …) |
ctx.compare() |
ICompareService |
ouvrir la vue de comparaison à deux volets |
ctx.diagnostics() |
IDiagnosticsSink |
publier des soulignements ondulés + glyphes de marge |
ctx.events() |
IEventBus |
s'abonner aux événements de l'éditeur / en publier |
ctx.completions() |
ICompletionRegistry |
contribuer des candidats d'auto-complétion |
ctx.contextMenu() |
IContextMenuRegistry |
entrées dynamiques du menu contextuel de l'éditeur |
ctx.snippetSessions() |
ISnippetSessionService |
fournir des snippets extensibles par Tab (l'hôte gère la session de champs) |
ctx.hover() |
IHoverRegistry |
infobulles de survol |
ctx.signatureHelp() |
ISignatureHelpRegistry |
info-bulles d'appel sur ( / , |
ctx.symbols() |
ISymbolRegistry |
symboles du document / de l'espace de travail (aller au symbole) |
ctx.statusBar() |
IStatusBar |
messages transitoires + segments persistants |
ctx.settings() |
ISettings |
configuration clé/valeur persistante par plugin |
ctx.appSettings() |
const ISettings& |
lire les réglages globaux de l'application (AppSettingsKeys.h) |
ctx.documentSettings() |
IDocumentSettings |
configuration par document superposée par .editorconfig |
ctx.settingsRegistry() |
ISettingsRegistry |
ajouter une page à la boîte de dialogue Préférences |
ctx.themePalette() |
IThemePalette |
couleurs de thème nommées, intégration avec l'éditeur de thèmes |
ctx.log() |
ILogger |
trace / debug / info / warning / error |
ctx.pluginDataDir() |
std::filesystem::path |
votre répertoire privé accessible en écriture |
ctx.diagnosticsDir() |
std::filesystem::path |
répertoire partagé des artefacts de crash + journaux (lecture seule pour les plugins) |
Étendre l'API plus tard consiste à ajouter une nouvelle interface de service et son accesseur ; les plugins existants restent compatibles au niveau source et ABI.
IEditorService — le modèle de document¶
L'éditeur tel que vu par les plugins — sans Scintilla, sans Qt. Les documents
sont référencés par un DocumentId opaque (std::uint64_t, 0 = invalide).
Les offsets sont des offsets en octets dans le texte UTF-8 ; MatchRange
est un intervalle semi-ouvert {start, end} où noMatch() signifie
« non trouvé » ; EditorPosition est un {line, column} en base 0.
// 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;
Il expose aussi la disposition des groupes d'onglets de la vue scindée en
termes neutres vis-à-vis du backend (groupCount(), groupOf(),
groupWeights(), moveDocumentToGroup(), setGroupWeights()), afin qu'un
plugin puisse capturer et restaurer l'agencement des volets sans voir le
splitter de Qt. Consultez PluginApi/IEditorService.h pour la liste
complète — l'en-tête est la référence qui fait foi.
Threads
Tous les appels au contexte et aux services ont lieu sur le thread UI de l'hôte. N'appelez pas les services depuis un thread d'arrière-plan que vous avez créé ; revenez d'abord sur le thread UI.
Gestion des versions¶
MTE_PLUGIN_API_VERSION (actuellement 1) est défini dans
PluginApi/PluginInfo.h. Un plugin doit annoncer la même valeur à la fois
dans plugin.json et dans IPlugin::info().apiVersion, sinon l'hôte refuse
de le charger.
Politique de pré-version
Avant la première version publique, le contrat peut encore changer sans
incrémenter MTE_PLUGIN_API_VERSION. Après la publication, tout
changement incompatible avec l'existant sur une interface existante
incrémente la version.