Skip to content

Events reference

Every event the host publishes for native plugins (PluginEvents.h) reaches Python too, with one exception noted below. Each topic delivers a small typed dataclass to your handler. The dataclasses are defined in Python/mte/events.py and are importable from the top-level mte package.

Topic ↔ dataclass table

Topic Payload dataclass Fields Decorator
document.opened mte.DocumentOpened document: int, path: str on_document_opened
document.closed mte.DocumentClosed document: int on_document_closed
document.saved mte.DocumentSaved document: int on_document_saved
document.encodingChanged mte.DocumentEncodingChanged document: int, encoding: mte.DocumentEncoding on_document_encoding_changed
document.lineEndingChanged mte.DocumentLineEndingChanged document: int, ending: mte.LineEnding on_document_line_ending_changed
document.modifiedChanged mte.DocumentModifiedChanged document: int, modified: bool on_document_modified_changed
document.activeChanged mte.ActiveDocumentChanged document: int on_active_document_changed
text.changed mte.TextChanged document, position, added_bytes, removed_bytes on_text_changed
selection.changed mte.SelectionChanged document: int on_selection_changed
settings.changed mte.SettingsChanged scope: str ("" = editor-wide, "plugins/<id>") on_settings_changed
theme.changed mte.ThemeChanged variant: str ("light" / "dark" / "") on_theme_changed
margin.clicked mte.MarginClicked document: int, line: int, margin: str ("symbol"), modifiers: int (bits mte.MODIFIER_SHIFT / MODIFIER_CONTROL / MODIFIER_ALT / MODIFIER_META) — a click in the bookmark column, see ctx.line_markers on_margin_clicked
workspace.rootChanged mte.WorkspaceRootChanged root: str ("" = folder closed) on_workspace_root_changed
workspace.filesChanged mte.WorkspaceFilesChanged files: list[mte.WorkspaceFile] (path, display_path) on_workspace_files_changed
command.shortcutChanged mte.CommandShortcutChanged command_id: str, sequence: str on_command_shortcut_changed
host.windowShown mte.HostWindowShown (no fields) — once per run, after the first frame on_host_window_shown
app.aboutToQuit mte.AppAboutToQuit (no fields) on_app_about_to_quit

document is an opaque integer id assigned by the host. Compare ids with ==; do not persist them across editor restarts. text.changed fires on every edit — keep that handler trivial.

Subscribe via sugar decorator

Recommended — the decorator name matches the topic and the payload type is inferred:

import mte

def register(ctx):

    @ctx.events.on_document_saved
    def _saved(ev: mte.DocumentSaved):
        ctx.log.info(f"saved doc {ev.document}")

    @ctx.events.on_document_opened
    def _opened(ev: mte.DocumentOpened):
        ctx.log.info(f"opened {ev.document} at {ev.path}")

    @ctx.events.on_app_about_to_quit
    def _quit(_ev: mte.AppAboutToQuit):
        ctx.log.info("editor is closing")

Subscribe via the raw API

def _saved(ev):
    ctx.log.info(f"saved {ev.document}")

handler_id = ctx.events.subscribe("document.saved", _saved)
  • Returns the locally-allocated handler_id (a "py-ev/N" string).
  • Unknown topics raise ValueError at subscribe time — no silent noop.

Unsubscribe when you no longer care:

ctx.events.unsubscribe(handler_id)

Note: after unsubscribe the host stops delivering the topic to you, but the worker's local dispatcher keeps its entry until the plugin shuts down. Do not rely on unsubscribe for memory cleanup — it's a policy signal, not a garbage-collection primitive.

Delivery semantics

  • Events fire after the corresponding action has completed. On document.saved, the file is already on disk. On document.closed, the document id is already invalid.
  • Multiple subscribers to the same topic all fire, in undefined order.
  • Exceptions raised in your handler are caught by the worker and written to stderr (visible in the host's log as [worker stderr] …). A misbehaving handler does not stop other subscribers from firing.
  • Payload dataclasses are immutable; do not mutate ev fields.

Publishing

ctx.events.publish(topic, payload) puts an event on the shared bus, where native and Python subscribers alike receive it. Only the two topics that are meant to be published by a plugin are accepted — the workspace pair, which the Workspace plugin broadcasts for Find in Files and the Command Palette:

ctx.events.publish("workspace.rootChanged", {"root": "/home/me/project"})
ctx.events.publish("workspace.filesChanged", {"files": [
    {"path": "/home/me/project/a.txt", "displayPath": "a.txt"},
]})

Any other topic raises ValueError: the rest are the host's own statements of fact (a document was saved, the theme did change) that a plugin has no business asserting. For plugin-to-plugin messaging beyond these, register a command and have the other side invoke it.

Not available

Two host events have no Python topic:

  • document.aboutToSave: the host publishes it synchronously with the save blocked until every subscriber returns, and honouring that over the worker RPC needs the same deadline treatment as completion providers before it is safe. Native plugins can use it today.
  • DocumentAboutToClose — native-only by design. Its whole promise is that the document can still be read inside the handler, and delivery to a Python plugin is asynchronous, so the handler would run after the close. Use document.closed instead, and keep whatever you need about a document up to date from the earlier events rather than reading it at close time.