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 sameidandapiVersionasplugin.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; returnfalseonly 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-destructiveTokenIdview, 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.