Skip to content

Plugin API — overview

All PluginApi types live in namespace MTE::plugin. The public headers may not include any Qt, Scintilla, or MTE-internal header — only the standard library. The one deliberate exception is the opt-in PluginApi/Qt/ folder (PluginQtGlue.h, ISettingsPage.h, ThemeFormat.h) for plugins that ship their own Qt widgets.

The same contract is fulfilled by two backends: the native Qt loader (shared libraries) and the out-of-process Python backend (Python plugins). This section documents the C++ surface; every service here that is backend-neutral has a Python counterpart on ctx.

Plugin lifecycle

A native plugin implements one class:

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() must return the same id and apiVersion as plugin.json; the host cross-checks and refuses to load on mismatch.
  • initialize() runs once at startup (after every plugin is discovered). Register commands, menus, providers, and panels here; return false only on a real failure (the host unloads the plugin and logs it).
  • shutdown() runs at exit (or when the plugin is being unloaded). It must not throw; reset() every token and null your stored service pointers.

RAII tokens

Every registration returns a token (CommandToken, MenuToken, DockToken, SettingsPageToken, Subscription, …) — a move-only BasicToken that owns the registration: destroying or reset()-ing it revokes the command / menu item / panel / subscription. Keep each token as a member and release it in shutdown(). Two accessors matter:

  • id() — non-destructive TokenId view, for APIs that address the item without taking it (e.g. IMenuRegistry::setItemChecked).
  • release() — detaches and returns the id; the token no longer revokes anything. Rarely what you want.

The service locator

initialize() receives an IPluginContext, a service locator owned by the host. Do not store the context past initialize(); store the individual service references you need (each service outlives every plugin).

Accessor Interface Use for
ctx.editor() IEditorService read/edit buffer, selection, caret, files, search, bookmarks, indentation, split-view groups
ctx.commands() ICommandRegistry register / invoke user actions (with shortcuts)
ctx.menus() IMenuRegistry menu items, separators, checkable toggles
ctx.docks() IDockRegistry dockable side panels
ctx.documents() IDocumentTypeRegistry first-class MainWindow tab types (diff, log view, …)
ctx.compare() ICompareService open the two-pane compare view
ctx.diagnostics() IDiagnosticsSink publish squiggles + margin glyphs
ctx.events() IEventBus subscribe to / publish editor events
ctx.completions() ICompletionRegistry contribute auto-completion candidates
ctx.contextMenu() IContextMenuRegistry dynamic entries in the editor's right-click menu
ctx.snippetSessions() ISnippetSessionService provide Tab-expandable snippets (host runs the field session)
ctx.hover() IHoverRegistry hover tooltips
ctx.signatureHelp() ISignatureHelpRegistry call tips on ( / ,
ctx.symbols() ISymbolRegistry document / workspace symbols (go-to-symbol)
ctx.statusBar() IStatusBar transient messages + persistent segments
ctx.settings() ISettings persistent per-plugin key/value config
ctx.appSettings() const ISettings& read app-wide settings (AppSettingsKeys.h)
ctx.documentSettings() IDocumentSettings .editorconfig-layered per-document config
ctx.settingsRegistry() ISettingsRegistry add a page to the Preferences dialog
ctx.themePalette() IThemePalette named theme colours, Theme Editor integration
ctx.log() ILogger trace / debug / info / warning / error
ctx.pluginDataDir() std::filesystem::path your private writable directory
ctx.diagnosticsDir() std::filesystem::path shared crash-artifacts + log dir (read-only for plugins)

Extending the API later means adding a new service interface and accessor; existing plugins stay source- and ABI-compatible.

IEditorService — the document model

The editor as seen by plugins — no Scintilla, no Qt. Documents are referenced by an opaque DocumentId (std::uint64_t, 0 = invalid). Offsets are byte offsets into UTF-8 text; MatchRange is half-open {start, end} with noMatch() meaning "not found"; EditorPosition is 0-based {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;

It also exposes the split-view tab-group layout in backend-neutral terms (groupCount(), groupOf(), groupWeights(), moveDocumentToGroup(), setGroupWeights()), so a plugin can capture and restore pane arrangements without seeing Qt's splitter. See PluginApi/IEditorService.h for the full list — the header is the authoritative reference.

Threading

All context and service calls happen on the host's UI thread. Do not call services from a background thread you spawned; marshal back first.

Versioning

MTE_PLUGIN_API_VERSION (currently 1) is defined in PluginApi/PluginInfo.h. A plugin must advertise the same value in both plugin.json and IPlugin::info().apiVersion, or the host refuses to load it.

Pre-release policy

Before the first public release the contract may still change without bumping MTE_PLUGIN_API_VERSION. After release, any backwards-incompatible change to an existing interface bumps the version.