Aller au contenu

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êmes id et apiVersion que plugin.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 renvoyez false qu'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 ; faites reset() 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() — vue TokenId non 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}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.