Skip to content

Python plugins

Modular Text Editor loads Python plugins alongside native C++ ones through the same plugin management surface. A Python plugin is a folder containing one plugin.json and at least one .py file with a top-level register(ctx) function.

The editor spawns a small worker subprocess per plugin, runs register(ctx) inside it, and marshals commands / menus / events across an RPC channel. Your plugin never touches Qt or Scintilla — the ctx object is a plain Python service locator.

What works today (Phases 1–5: feature-complete)

Phase 1: commands, menu items, event subscriptions (open / close / save / active document changed / selection changed / app quit), the log, the status bar.

Phase 2: editor buffer / selection / caret / search / indentation / file ops via ctx.editor.*, persistent settings via ctx.settings.* and read-only app settings via ctx.app_settings.*, completion providers (with a small keystroke deadline) via @ctx.completions.provider(...), and ctx.data_dir() for the per-plugin writable directory.

Phase 3: declarative dock panels via ctx.docks.add_panel(...), Preferences pages via ctx.settings_page.register(...), both built from mte.ui form builders, and runtime control access via ctx.ui.get / ctx.ui.set.

Phase 4: .mteplugin package installer — zip your plugin folder, install it from Preferences → Plugins (see Packaging).

Phase 5: per-plugin virtual environments — declare pip dependencies in pyRequires and the editor provisions a venv for your plugin on first launch (see Dependencies); a permissions declaration shown in the Plugins page; and a streaming/cancellation RPC substrate (no plugin-facing API yet — it underpins future AI services).

Minimal plugin

Create <any-folder>/hello/ with these two files.

hello/plugin.json
{
  "id":         "org.example.hello",
  "name":       "Hello (Python)",
  "version":    "1.0.0",
  "apiVersion": 1,
  "entry":      "python",
  "module":     "main",
  "menus": [
    { "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
  ]
}
hello/main.py
import mte  # noqa: F401


def register(ctx):

    @ctx.commands.command(id="hello.sayHi",
                          title="Say Hi",
                          shortcut="Ctrl+Alt+H")
    def _say_hi():
        ctx.status.show("Hello from Python!", timeout_ms=3000)
        ctx.log.info("hello: Say Hi invoked")

    ctx.menus.add_item("Plugins/Hello/Say Hi", "hello.sayHi")

Zip the folder as hello.mteplugin and install it via Preferences → Plugins → Install… — or drop the folder into your per-user plugin directory by hand (see Packaging). Restart the editor and Plugins → Hello → Say Hi appears in the menu bar.

What you need on the machine

  • A Python 3.9+ interpreter reachable one of two ways:
    • On PATH (py.exe, python.exe, or python3.exe on Windows; python3 on macOS/Linux), or
    • Pointed at by the MTE_PYTHON environment variable (either an absolute path to a python.exe, or a bare name to look up on PATH).
  • No pip packages: the Phase 1 client library mte ships with the editor and is added to sys.path for you.

The editor logs which interpreter it selected on startup:

PluginHost: MTE_PYTHON is not set; falling back to PATH.
PluginHost: located Python on PATH: py.exe -> C:/Windows/py.exe
PluginHost: Python plugin backend enabled; interpreter=..., worker=.../mte_python_host.py

If you see Python plugins disabled, jump to Troubleshooting.

What your plugin can do

Each item below links to its API section:

Capability Section
Register commands (with optional keyboard shortcut) ctx.commands
Contribute menu items and separators ctx.menus
Subscribe to editor events (documents, selection, app quit) ctx.events and Events reference
Log to the editor's diagnostics stream ctx.log
Post transient status-bar messages ctx.status
Read/modify the active buffer, selection, caret, indentation ctx.editor
Persist per-plugin config, read app-wide settings ctx.settings / ctx.app_settings
Register a completion provider (with a keystroke deadline) ctx.completions
Locate your writable per-plugin directory ctx.data_dir()
Add a declarative dock panel ctx.docks and mte.ui
Add a page in Preferences ctx.settings_page
Read / write panel controls at runtime ctx.ui
Toggle a persistent check mark on a menu item ctx.menus.set_item_checked

What's next