Skip to content

Events

Plugins react to editor activity through IEventBus. Events are plain structs defined in PluginEvents.h:

Event Payload
DocumentOpened DocumentId, std::filesystem::path
DocumentAboutToClose DocumentId — the user is closing the document, and the close is certain; published just before its last view goes, while it is still fully readable. Not on quit. Native plugins only; see below
DocumentClosed DocumentId
DocumentSaved DocumentId
DocumentPrinted DocumentId, pages — published after a print job reached the device (File ▸ Print, or Print inside the preview); pages counts the document pages printed, fewer than the document has when a selection or page range was chosen. A cancelled dialog publishes nothing. C++ plugins only for now
DocumentAboutToSave DocumentId, path — synchronous, buffer still editable (trim whitespace, add final newline); no dialogs
DocumentEncodingChanged DocumentId, DocumentEncoding — the encoding the host will write the document back in changed (file just identified, or setDocumentEncoding called)
DocumentLineEndingChanged DocumentId, LineEnding — the line terminator the document uses changed (file just read or reloaded, an .editorconfig end_of_line applied on save, or setLineEnding called)
DocumentModifiedChanged DocumentId, modified — the clean/dirty flag toggled (not every keystroke)
TextChanged DocumentId, pos, addedBytes, removedBytes
SelectionChanged DocumentId
ActiveDocumentChanged DocumentId
HostWindowShown (none) — once per run, after the first frame is on screen; schedule slow startup work off this
SettingsChanged scope — "" = editor-wide, "plugins/<id>" = that plugin's page was applied
WorkspaceRootChanged root (empty = folder closed) — published by the Workspace plugin
WorkspaceFilesChanged files[] of {path, displayPath} — published by the Workspace plugin
ThemeChanged variant ("light" / "dark" / "")
AppAboutToQuit (none)
CommandShortcutChanged commandId, sequence (new portable chord; "" = unbound)
MarginClicked DocumentId, line, margin (EditorMargin::Symbol), modifiers — a left click in the bookmark column of an editor, on every such click; see Line markers

Plugins can publish events too (ctx.events().publish(e)) — that is how the Workspace plugin broadcasts WorkspaceRootChanged / WorkspaceFilesChanged for Find in Files and the Command Palette to consume.

One document, one id

A DocumentId names a document, not a pane. Split Document shows one file in two panes, and both panes carry the same DocumentId, so:

  • DocumentOpened is published once per file — splitting publishes nothing;
  • DocumentClosed is published only when the last view of the document closes, so closing one pane never tears down state the other still needs;
  • activeDocument() answers with that one id whichever pane is in front, and openDocuments() lists the document once.

There is deliberately no per-pane event and no way to ask which pane is active: panes are a host concern, and everything a plugin can observe — text, caret, selection, encoding, line ending, modified state — belongs to the document, which the panes share.

DocumentAboutToClose

Published once per document, immediately before its last view goes away, when the close is certain — any save prompt was answered and not cancelled. Inside the handler the document is still whole: text(), filePath(), caret(), bookmarkedLines() and the encoding all answer. The teardown follows, then DocumentClosed. Every way of closing a tab publishes it (Close, Close All, Close Others / to the Right, a middle-click, Close Folder, a plugin's closeDocument()), untitled documents included; closing one pane of a document still shown in another does not.

Quitting does not publish it. The application publishes AppAboutToQuit once, while every document is still open, and no per-document close events: a plugin that keeps per-document state across sessions walks openDocuments() in its AppAboutToQuit handler. DocumentAboutToClose therefore always means "this document is being closed", never "the program is exiting".

Handlers run synchronously, inside the close: be fast, show no dialogs, and treat the set of documents as read-only — do not open, close or activate documents, and do not edit the closing document's text.

It is native-only by design: delivery to Python plugins is asynchronous, so the "still readable" promise could not be kept there, and the event is not offered to them (see Python events).

CommandShortcutChanged

CommandShortcutChanged fires when a command's effective shortcut changes — a user remap in the Shortcut Mapper, a keymap import/reset, or a conflict suppression engaging/lifting. Menu-bound commands need nothing (the host rebinds their action); commands registered with scope = command_scope::kPluginWindow subscribe and re-apply the sequence to their own widgets (see Commands & menus).

Subscribing

subscribe is a template that returns a Subscription (RAII). Keep the subscription alive as a member for as long as you want the callback to fire; destroying it unsubscribes.

m_sub = ctx.events().subscribe<MTE::plugin::DocumentSaved>(
    [this](const MTE::plugin::DocumentSaved& e) {
        // react to e.id ...
    });
class IEventBus {
public:
    template <class Event>
    Subscription subscribe(std::function<void(const Event&)> cb);
    // Forwards to a type-erased subscribeRaw(std::type_index, ...) internally.
};

Tip

Hold every Subscription as a plugin member so all callbacks are torn down automatically in shutdown().