Przejdź do treści

Pisanie wtyczki natywnej

Minimalna wtyczka natywna to jedna klasa, jedno makro fabryki, jeden plik JSON i krótki CMakeLists.txt. Możesz ją napisać w całości poza drzewem źródeł, w oparciu o Plugin SDK — bez checkoutu źródeł MTE.

Natywna czy Python?

Ta strona opisuje wtyczki natywne C++ (kompilowane do .dll/.so/.dylib i ładowane przez loader Qt). Jeśli wolisz dostarczyć folder plików .py, które edytor uruchamia jako podproces, zobacz Wtyczki w Pythonie — ta sama powierzchnia PluginApi, inny backend, kompilator niepotrzebny.

Wersja robocza

Ten przewodnik opiera się na PLUGIN_ARCHITECTURE.md; przed poleganiem na nich zweryfikuj dokładną nazwę pakietu SDK oraz argumenty mte_add_plugin() względem dostarczonego SDK.

1. Zaimplementuj IPlugin

#include <PluginApi/IPlugin.h>
#include <PluginApi/IPluginContext.h>

class HelloWorldPlugin : public MTE::plugin::IPlugin {
public:
    MTE::plugin::PluginInfo info() const override {
        return { "org.mte.helloworld", "Hello World", "1.0.0",
                 "Example Vendor", "Says hi.", MTE_PLUGIN_API_VERSION };
    }

    bool initialize(MTE::plugin::IPluginContext& ctx) override {
        m_cmd = ctx.commands().registerCommand(
            { "helloworld.sayHi", "Say Hi", "Ctrl+Alt+H", "Hello" },
            [&ctx] { ctx.log().log(MTE::plugin::LogLevel::Info, "Hi!"); });
        m_menu = ctx.menus().addItem("Plugins/Say Hi", "helloworld.sayHi");
        return true;
    }

    void shutdown() noexcept override {}   // tokens tear down automatically

private:
    MTE::plugin::CommandToken m_cmd;
    MTE::plugin::MenuToken    m_menu;
};

2. Wyeksportuj fabrykę (natywny backend Qt)

#include <PluginApi/Qt/PluginQtGlue.h>

class HelloWorldFactory : public QObject, public MTE::plugin::IPluginFactory {
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.mte.IPluginFactory/1" FILE "plugin.json")
    Q_INTERFACES(MTE::plugin::IPluginFactory)
public:
    std::unique_ptr<MTE::plugin::IPlugin> create() override {
        return std::make_unique<HelloWorldPlugin>();
    }
};

3. Opisz ją w plugin.json

{
  "id":         "org.mte.helloworld",
  "name":       "Hello World",
  "version":    "1.0.0",
  "apiVersion": 1,
  "entry":      "native",
  "requires":   [],
  "order":      600,
  "menus": [
    { "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
  ]
}

Zarówno tutejszy apiVersion, jak i ten zwracany przez info() muszą być równe MTE_PLUGIN_API_VERSION, inaczej host odmówi załadowania wtyczki.

Towarzyszą mu dwa opcjonalne klucze:

  • apiMinorVersion (domyślnie 0) — najniższa wersja minor API hosta, jakiej potrzebuje Twoja wtyczka. Host, którego MTE_PLUGIN_API_MINOR_VERSION jest niższe, wyświetla wtyczkę z komunikatem „Wymaga nowszej wersji OctoEdit" i jej nie ładuje. Host sprawdza tę wartość z manifestu, zanim cokolwiek załaduje, a potem jeszcze raz PluginInfo::apiMinorVersion z info() — ta domyślnie równa się minor nagłówków, z którymi kompilowałeś, więc nie zmieniaj jej, chyba że wiesz, że nie używasz niczego nowszego. Zobacz Wersjonowanie.
  • requires (domyślnie []) — identyfikatory wtyczek, bez których Twoja wtyczka nie może działać, na przykład ["org.mte.lsp"]. Tylko identyfikatory: nie ma zakresu wersji ani formy opcjonalnej — wtyczka, z którą po prostu działasz lepiej, tu nie trafia; rozmawiaj z nią w czasie działania przez zdarzenia i rejestry providerów. Edytor inicjalizuje Twoje zależności przed Tobą, niezależnie od order, i zamyka je po Tobie. Gdy któraś z nich jest nieobecna, wyłączona, wymaga nieaktywnej licencji albo się nie załadowała, Twoja wtyczka w ogóle nie jest ładowana, a Preferencje ▸ Wtyczki mówią, której wtyczki potrzebuje i dlaczego („Wymaga wtyczki LSP Provider, która jest wyłączona"). Cykl blokuje każdą wtyczkę, która do niego należy. Ten sam klucz znaczy to samo w plugin.json wtyczki w Pythonie.

Uznanie dołączonych bibliotek — thirdParty

Jeśli Twoja wtyczka dołącza lub linkuje komponent zewnętrzny, zadeklaruj go w opcjonalnej tablicy thirdParty, aby edytor mógł wypełnić obowiązki atrybucyjne:

"thirdParty": [
  { "name": "Qt WebEngine", "license": "LGPL-3.0",
    "url": "https://doc.qt.io/qt-6/qtwebengine-index.html",
    "file": "QtWebEngine-LGPLv3.txt" }
]

Każdy wpis wymaga pola name (wpisy bez niego są pomijane); license, url i file są opcjonalne. Teksty licencji należą do Twojej wtyczki — dostarcz je obok wtyczki w Plugins/Licenses/<your-plugin-id>/, gdzie file to nazwa pliku. Okno O programie wyświetla komponenty thirdParty każdej zainstalowanej wtyczki i kieruje użytkowników do tego folderu; rdzeń edytora nigdy nie przechowuje tekstów licencji zewnętrznych za wtyczkę. (Dołączony HtmlPreviewPlugin, który osadza QtWebEngine + Chromium, jest przykładem referencyjnym.)

4. Zbuduj ją

Użyj helpera CMake z SDK:

find_package(MTEPluginSDK REQUIRED)
mte_add_plugin(NAME HelloWorldPlugin
    SOURCES Src/HelloWorldPlugin.cpp
    # TRANSLATIONS Translations/HelloWorld_en.ts ...
)

Wymagania do kompilacji

Wtyczka natywna jest ładowana do procesu edytora, więc musi być zbudowana toolchainem zgodnym binarnie z buildem edytora, dla którego jest przeznaczona. Poniżej wymagania dla bieżącego wydania (zbudowanego z Qt 6.11.2):

Windows macOS
Kompilator Visual Studio 2022, toolset MSVC 14.44 lub starszy 14.x (VS 2015–2022 dzielą jedno ABI C++); działa też clang-cl. Wtyczek z MinGW / GCC nie da się załadować. Apple clang z Xcode (Xcode 27, Apple clang 21.0.0); tylko arm64
Konfiguracja Release, dynamiczny runtime (/MD, domyślne ustawienie CMake). Wtyczkę Debug loader odrzuca; wtyczka ze statycznym runtime (/MT) powoduje awarię, gdy pamięć przekracza granicę wtyczki Release; deployment target 14.4 (jak edytor)
Qt Qt 6.11, zestaw msvc2022_64. Wtyczka zbudowana ze starszym wydaniem 6.x ładuje się; zbudowana z nowszym może się nie załadować Qt 6.11, ta sama architektura
Standard C++ C++20 (nagłówki API używają std::span i konceptów) C++20
CMake 3.16+ 3.16+
API wtyczek MTE_PLUGIN_API_VERSION z SDK dostarczonego z docelową wersją edytora jak obok

Dwie konsekwencje na Windows, o których warto wiedzieć:

  • Visual C++ Redistributable na maszynie użytkownika musi być co najmniej tak nowy jak najnowszy toolset MSVC użyty przez edytor lub dowolną załadowaną wtyczkę. Budowanie wtyczki toolsetem nowszym niż edytor podnosi to wymaganie dla Twoich użytkowników; budowanie toolsetem edytora lub starszym nie.
  • Wersja Windows SDK użyta do kompilacji nie wpływa na zgodność z edytorem; decyduje tylko o tym, które API Windows wtyczka może wywołać, a edytor obsługuje Windows 10 1809 i nowsze.

A na macOS:

  • Edytor jest dostarczany wyłącznie jako arm64; wtyczka tylko x86_64 nie załaduje się (binarka uniwersalna jest w porządku). Zostaw deployment target 14.4: wtyczkę zbudowaną dla nowszego minimalnego macOS systemowy loader odrzuci na starszych Macach, a runtime'em jest systemowa libc++, więc kwestia redistributable nie występuje.
  • Odwołuj się do frameworków Qt przez @rpath, żeby wtyczka użyła kopii dołączonych w OctoEdit.app. Zestawy Qt, których frameworki mają bezwzględne nazwy instalacyjne (np. z Homebrew), wymagają install_name_tool na zbudowanej wtyczce przed wysyłką; inaczej wtyczka szuka Qt pod ścieżką z maszyny budującej.

Nic z tego nie dotyczy wtyczek w Pythonie: działają w osobnym procesie i nie dzielą z edytorem żadnego interfejsu binarnego.

5. Zainstaluj i uruchom

Spakuj zbudowaną bibliotekę + plugin.json do archiwum <name>.mteplugin i zainstaluj przez Preferencje ▸ Wtyczki ▸ Zainstaluj wtyczkę... (pakiet jest walidowany — manifest, bezpieczeństwo archiwum oraz metadane osadzone w binarium — zanim cokolwiek zostanie zmienione), albo wrzuć pliki ręcznie do jednego z wykrywanych katalogów wtyczek (zobacz Przegląd architektury). Uruchom edytor ponownie; w razie potrzeby włącz wtyczkę w Preferencje ▸ Wtyczki.

Dalsze kroki