Przejdź do treści

API wtyczek — przegląd

Wszystkie typy PluginApi żyją w namespace MTE::plugin. Publiczne nagłówki nie mogą dołączać żadnego nagłówka Qt, Scintilli ani wewnętrznego nagłówka MTE — tylko bibliotekę standardową. Jedynym celowym wyjątkiem jest opcjonalny folder PluginApi/Qt/ (PluginQtGlue.h, ISettingsPage.h, ThemeFormat.h) dla wtyczek, które dostarczają własne widżety Qt.

Ten sam kontrakt realizują dwa backendy: natywny loader Qt (biblioteki współdzielone) oraz działający poza procesem backend Pythona (Wtyczki w Pythonie). Ta sekcja dokumentuje powierzchnię C++; każda opisana tu usługa niezależna od backendu ma swój odpowiednik w Pythonie na ctx.

Cykl życia wtyczki

Wtyczka natywna implementuje jedną klasę:

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() musi zwrócić te same id i apiVersion co plugin.json; host weryfikuje zgodność i przy rozbieżności odmawia załadowania.
  • initialize() uruchamiane jest raz przy starcie (po wykryciu wszystkich wtyczek). Tutaj rejestruj polecenia, menu, providery i panele; zwróć false tylko przy faktycznym błędzie (host wyładuje wtyczkę i to zaloguje).
  • shutdown() uruchamiane jest przy wyjściu (lub gdy wtyczka jest wyładowywana). Nie może rzucać wyjątków; wykonaj reset() na każdym tokenie i wyzeruj przechowywane wskaźniki do usług.

Tokeny RAII

Każda rejestracja zwraca token (CommandToken, MenuToken, DockToken, SettingsPageToken, Subscription, …) — przenoszalny (move-only) BasicToken, który jest właścicielem rejestracji: zniszczenie go lub wywołanie reset() cofa rejestrację polecenia / pozycji menu / panelu / subskrypcji. Trzymaj każdy token jako pole składowe i zwalniaj go w shutdown(). Istotne są dwa akcesory:

  • id() — nieniszczący widok TokenId, dla API, które adresują element bez przejmowania go (np. IMenuRegistry::setItemChecked).
  • release() — odłącza token i zwraca id; token nie cofa już żadnej rejestracji. Rzadko tego chcesz.

Lokator usług

initialize() otrzymuje IPluginContext — lokator usług, którego właścicielem jest host. Nie przechowuj kontekstu po initialize(); przechowuj referencje do poszczególnych potrzebnych usług (każda usługa żyje dłużej niż każda wtyczka).

Akcesor Interfejs Zastosowanie
ctx.editor() IEditorService odczyt/edycja bufora, zaznaczenie, karetka, pliki, wyszukiwanie, zakładki, wcięcia, grupy widoku podzielonego
ctx.commands() ICommandRegistry rejestrowanie / wywoływanie akcji użytkownika (ze skrótami)
ctx.menus() IMenuRegistry pozycje menu, separatory, przełączniki z zaznaczeniem
ctx.docks() IDockRegistry dokowane panele boczne
ctx.documents() IDocumentTypeRegistry pełnoprawne typy kart MainWindow (diff, widok logów, …)
ctx.compare() ICompareService otwieranie dwupanelowego widoku porównania
ctx.diagnostics() IDiagnosticsSink publikowanie podkreśleń + glifów na marginesie
ctx.events() IEventBus subskrypcja / publikacja zdarzeń edytora
ctx.completions() ICompletionRegistry dostarczanie kandydatów auto-uzupełniania
ctx.contextMenu() IContextMenuRegistry dynamiczne pozycje w menu prawego przycisku edytora
ctx.snippetSessions() ISnippetSessionService dostarczanie snippetów rozwijanych Tabem (host prowadzi sesję pól)
ctx.hover() IHoverRegistry dymki podpowiedzi (hover)
ctx.signatureHelp() ISignatureHelpRegistry podpowiedzi wywołań przy ( / ,
ctx.symbols() ISymbolRegistry symbole dokumentu / przestrzeni roboczej (go-to-symbol)
ctx.statusBar() IStatusBar komunikaty tymczasowe + trwałe segmenty
ctx.settings() ISettings trwała konfiguracja klucz/wartość per wtyczka
ctx.appSettings() const ISettings& odczyt ustawień globalnych aplikacji (AppSettingsKeys.h)
ctx.documentSettings() IDocumentSettings warstwowa konfiguracja per dokument z .editorconfig
ctx.settingsRegistry() ISettingsRegistry dodanie strony do okna Preferencji
ctx.themePalette() IThemePalette nazwane kolory motywu, integracja z edytorem motywów
ctx.log() ILogger trace / debug / info / warning / error
ctx.pluginDataDir() std::filesystem::path Twój prywatny katalog z prawem zapisu
ctx.diagnosticsDir() std::filesystem::path wspólny katalog artefaktów awarii + logów (dla wtyczek tylko do odczytu)

Późniejsze rozszerzanie API oznacza dodanie nowego interfejsu usługi i akcesora; istniejące wtyczki pozostają zgodne źródłowo i na poziomie ABI.

IEditorService — model dokumentu

Edytor widziany oczami wtyczek — bez Scintilli, bez Qt. Dokumenty adresowane są nieprzezroczystym DocumentId (std::uint64_t, 0 = nieprawidłowy). Offsety to offsety bajtowe w tekście UTF-8; MatchRange to przedział półotwarty {start, end}, gdzie noMatch() oznacza „nie znaleziono"; EditorPosition to liczony od 0 {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;

Udostępnia też układ grup kart widoku podzielonego w terminach niezależnych od backendu (groupCount(), groupOf(), groupWeights(), moveDocumentToGroup(), setGroupWeights()), więc wtyczka może zapisać i odtworzyć rozmieszczenie paneli, nie widząc splittera Qt. Pełna lista w PluginApi/IEditorService.h — nagłówek jest referencją rozstrzygającą.

Wątki

Wszystkie wywołania kontekstu i usług odbywają się w wątku UI hosta. Nie wywołuj usług z uruchomionego przez siebie wątku w tle; najpierw wróć do wątku UI.

Wersjonowanie

MTE_PLUGIN_API_VERSION (obecnie 1) zdefiniowane jest w PluginApi/PluginInfo.h. Wtyczka musi deklarować tę samą wartość zarówno w plugin.json, jak i w IPlugin::info().apiVersion, inaczej host odmówi jej załadowania.

Polityka przedpremierowa

Przed pierwszym publicznym wydaniem kontrakt może się jeszcze zmieniać bez podbijania MTE_PLUGIN_API_VERSION. Po wydaniu każda niekompatybilna wstecz zmiana istniejącego interfejsu podbija wersję.