ctx — der Plugin-Kontext¶
register(ctx) erhält ein Argument: einen PluginContext. In Phase 1
stellt er fünf Dienste bereit. Jeder ist eine kleine Python-Klasse, die der
Worker über dem RPC-Kanal aufbaut; die Quelle liegt in Python/mte/.
def register(ctx):
ctx.commands # register/invoke commands
ctx.menus # add menu items & separators
ctx.events # subscribe to editor events
ctx.log # write to the host's diagnostics stream
ctx.status # post transient status-bar messages
ctx.editor # buffer / caret / selection access (Phase 2)
ctx.settings # per-plugin key/value store (Phase 2)
ctx.app_settings # editor-wide read-only settings view (Phase 2)
ctx.completions # register auto-completion providers (Phase 2)
ctx.docks # declarative dock panels (Phase 3)
ctx.settings_page # declarative Preferences pages (Phase 3)
ctx.ui # runtime get/set for panel controls (Phase 3)
Speichern Sie ctx nicht in einer Globalen, um es später zu verwenden —
die Attribute sind zwar über die Lebenszeit des Plugins stabil, behandeln
Sie ctx aber als pro-Registrierung. Halten Sie bei Bedarf Referenzen auf
die einzelnen Dienste (z. B. log = ctx.log).
commands¶
Dekorator, der einen Befehl registriert und die umschlossene Funktion als seinen Handler bindet.
id(str, erforderlich): eindeutiger Bezeichner im Reverse-DNS-Stil (z. B."wordcount.count"). Dieselben Regeln wie inplugin.json— muss global eindeutig sein.title(str, erforderlich): menschenlesbare Beschriftung, gezeigt wo immer der Befehl auftaucht (Menüs, Befehlspalette).shortcut(str, optional): portable Standard-Kürzelzeichenkette, z. B."Ctrl+Alt+W".Ctrlwird automatisch auf den primären Modifikator der Plattform abgebildet (Cmdauf macOS). Leer = keins. Der Nutzer kann es unter Einstellungen ▸ Tastenkürzel umbelegen oder lösen; bei menügebundenen Befehlen wendet der Host die Umbelegung für Sie an.category(str, optional): freies Gruppierungsetikett — als Kategorie des Befehls im Tastenkürzel-Zuordner gezeigt.scope(str, optional):""/"application"(Standard — der Host bindet das Kürzel an die Menüaktion des Befehls) oder"pluginWindow"für Kürzel in plugin-eigener UI: Der Host listet den Befehl nur im Zuordner; lesen Sie beim Aufbau Ihrer Widgetsctx.commands.effective_shortcut(id)und wenden Sie es beim Ereignis"command.shortcutChanged"neu an.
Das nach Nutzer-Umbelegung und Konfliktauflösung aktuell wirksame Kürzel
("" = ungebunden oder unbekannt). Aus Handlern sicher aufrufbar.
Die umschlossene Funktion nimmt keine Argumente und gibt nichts zurück. Ausnahmen darin fängt der Worker und protokolliert sie auf stderr; sie bringen den Editor nicht zum Absturz.
@ctx.commands.command(id="wordcount.count",
title="Count Words",
shortcut="Ctrl+Alt+W",
category="Text")
def _count():
ctx.status.show("counted")
Unter der Haube: Die Python-Seite vergibt lokal eine handlerId,
registriert den Callback auf der RPC-Verbindung und sendet
commands.registerCommand an den Host. Aktiviert der Nutzer den Befehl
(Menüklick, Kürzel, Palette), schickt der Host einen callback-RPC und
Ihre Funktion läuft.
Befehls-Ids sind prozessweit, nicht pro Plugin. Präfixen Sie Ihre Ids
mit Ihrem Reverse-DNS-Namensraum (wordcount.…), um Kollisionen zu
vermeiden.
menus¶
Menüpfade nutzen / als Trenner. Das linkeste Segment ist ein Menü der
obersten Ebene (Plugins, Tools, …); Zwischensegmente erzeugen Untermenüs
bei Bedarf; das letzte Segment ist die Beschriftung des Eintrags.
ctx.menus.add_item(path, command_id)¶
path(str):"Top/Sub/…/Beschriftung". Zwischen-Untermenüs werden angelegt, wenn sie fehlen.command_id(str): Id eines zuvor registrierten Befehls. Ein Klick auf den Eintrag ruft den Befehl auf.
Gibt eine opake handlerId-Zeichenkette zurück. Behalten Sie sie, falls
Sie den Eintrag später unsubscribe müssen (in Phase 1 selten).
Das linkeste Segment ("Plugins" oben) muss dem title eines Ihrer
menus[]-Einträge in plugin.json entsprechen, damit die Platzierung in
der Menüleiste übernommen wird.
ctx.menus.add_separator(path)¶
Fügt einen Trenner in ein bestehendes Untermenü ein.
ctx.menus.set_item_checked(handler_id, checked)¶
Macht den zuvor hinzugefügten Eintrag zu einem abhakbaren Menüpunkt und setzt seinen Zustand — das Standard-Idiom für einen dauerhaften Ein/Aus-Schalter mit sichtbarem Häkchen.
item = ctx.menus.add_item("View/Auto-open Preview",
"myplugin.toggleAutoOpen")
# Seed the check mark from the persisted setting so it matches
# reality before the user opens the menu.
ctx.menus.set_item_checked(item, ctx.settings.get("autoOpen", True))
@ctx.commands.command(id="myplugin.toggleAutoOpen",
title="Toggle Auto-Open")
def _toggle():
on = not ctx.settings.get("autoOpen", True)
ctx.settings.set("autoOpen", on)
ctx.menus.set_item_checked(item, on)
Der erste Aufruf macht den Eintrag abhakbar; weitere kippen nur das
Häkchen. Eine dem Host unbekannte handler_id ist ein stiller No-op
(sicher aufrufbar, nachdem Ihr Token verworfen wurde, z. B. aus einem
verzögerten Callback während des Herunterfahrens).
events¶
Editor-Ereignisse abonnieren. Zwei gleichwertige APIs — die
Zucker-Dekoratoren sind für den festen Themensatz der Phase 1 empfohlen;
das rohe subscribe() steht bereit, wenn Sie das Thema zur Laufzeit
berechnen wollen.
Zucker-Dekoratoren¶
@ctx.events.on_document_saved
def _saved(ev):
ctx.log.info(f"saved doc {ev.document}")
@ctx.events.on_document_opened
def _opened(ev):
ctx.log.info(f"opened {ev.path}")
Die umschlossene Funktion nimmt ein Argument: eine typisierte Dataclass passend zum Thema. Die vollständige Tabelle Thema ↔ Dataclass: Ereignis-Referenz.
Rohes API¶
handler_id = ctx.events.subscribe("document.saved", _saved)
# later:
ctx.events.unsubscribe(handler_id)
Verfügbare Zucker-Eigenschaften (alle Dekoratoren):
| Eigenschaft | Feuert bei |
|---|---|
on_document_opened |
Dokument in einem Tab geöffnet |
on_document_closed |
Dokument geschlossen |
on_document_saved |
Dokument auf die Festplatte geschrieben |
on_selection_changed |
Cursor / Auswahl im aktiven Dokument bewegt |
on_active_document_changed |
Vordergrund-Tab gewechselt |
on_app_about_to_quit |
Der Editor schließt — letzte Gelegenheit |
on_command_shortcut_changed |
Das wirksame Kürzel eines Befehls hat sich geändert (Nutzer-Umbelegung / Keymap-Import / Konfliktauflösung); Payload: command_id, sequence ("" = ungebunden) |
Unbekannte Themennamen werfen ValueError beim Abonnieren. Abonnenten der
Phase 1 können nach dem entsprechenden nativen Handler laufen;
behandeln Sie die Ereigniszustellung als informativ, nicht autoritativ.
log¶
ctx.log.trace ("very-fine-grained")
ctx.log.debug ("dev-only detail")
ctx.log.info ("routine event")
ctx.log.warning("recoverable weirdness")
ctx.log.error ("something is wrong")
Jede Stufe bildet auf den ILogger des Hosts ab. Zeilen tragen das Präfix
[python:<plugin.id>], Ihre Ausgabe ist im Diagnoseprotokoll des Editors
also auffindbar:
Niemals print() aus einem Plugin — der stdout des Workers ist der
RPC-Kanal, und Schreiben dorthin korrumpiert das Protokoll. ctx.log.* ist
der einzige sanktionierte Weg, Diagnosetext auszugeben.
status¶
Sendet eine flüchtige Benachrichtigung an die Statusleiste.
message(str): was gezeigt wird. Nur kurze Zeichenketten — die Statusleiste hat feste Breite.timeout_ms(int, Standard 0): wie lange sie sichtbar bleibt;0lässt den Host einen sinnvollen Standard wählen (einige Sekunden).level(str, Standard"info"):"info"/"warning"/"error". Höhere Stufen halten das Bildschirm-Minimum etwas länger, damit ein Fehler nicht sofort von einer Routinemeldung weggewischt wird.
ctx.status.show ist fire-and-forget. Hat der Host keine sichtbare
Statusleiste (headless / Minimal-Build), wird der Aufruf still verworfen.
editor¶
Voller synchroner Zugriff auf die aktiven Dokumente. Jede Methode
blockiert den aufrufenden Thread bis zur Antwort des Hosts
(typischerweise Mikrosekunden, wenn Worker und Editor eine Maschine
teilen). Legal aus jedem Befehls-/Ereignishandler; nie aus einem
async def-Callback (siehe die Threading-Notiz am Seitenanfang).
Dokument-Enumeration¶
doc = ctx.editor.active_document() # 0 when no document is open
docs = ctx.editor.open_documents() # list[int]
ctx.editor.set_active_document(doc)
path = ctx.editor.file_path(doc) # str or None for untitled
Pufferzugriff¶
text = ctx.editor.text(doc) # whole buffer
slice = ctx.editor.text_range(doc, start, end) # [start, end) bytes
n = ctx.editor.length(doc) # int64
ctx.editor.set_text(doc, "...")
text_range vermeidet es, den ganzen Puffer über den RPC zu ziehen, wenn
Sie nur einen Ausschnitt brauchen — bevorzugen Sie es für
Sortier-/Transformationsläufe auf großen Dateien.
Auswahl¶
sel = ctx.editor.selection_range(doc) # mte.MatchRange(start, end)
s = ctx.editor.selected_text(doc)
ctx.editor.replace_selection(doc, "new text")
ctx.editor.set_selection(doc, mte.MatchRange(4, 9))
Cursor¶
pos = ctx.editor.caret(doc) # mte.EditorPosition(line, column)
ctx.editor.set_caret(doc, mte.EditorPosition(line=7, column=3))
Dateioperationen¶
new_doc = ctx.editor.open_file("/path/to/file") # doc-id or 0 on failure
ok = ctx.editor.save_document(doc)
dirty = ctx.editor.is_modified(doc)
Suche¶
opts = mte.SearchOptions(match_case=True, mode="regex")
hit = ctx.editor.find_in_range(doc, r"TODO\(.*\)", 0,
ctx.editor.length(doc), opts)
if hit.valid:
new_end = ctx.editor.replace_target(doc, hit, "DONE()", opts)
SearchOptions.mode: "normal" / "extended" / "regex".
SearchOptions.direction: "forward" / "backward". Die vollständige
Feldliste: mte.SearchOptions.
Einrückung und Syntax¶
style = ctx.editor.indentation(doc) # IndentationStyle(use_tabs, width)
ctx.editor.set_indentation(doc,
mte.IndentationStyle(use_tabs=False, width=2))
lang = ctx.editor.colorizer_id(doc) # e.g. "python", "" if none
settings¶
Persistenter typisierter Schlüssel/Wert-Speicher, hostseitig auf
plugins/<ihre.id>/ begrenzt. Alle Methoden synchron; der get/set-Typ wird
aus dem Python-Typ des Standardwerts abgeleitet.
ctx.settings.set("wrap", True)
ctx.settings.set("count", 42)
ctx.settings.set("theme", "dark")
wrap = ctx.settings.get("wrap", False) # returns bool
count = ctx.settings.get("count", 0) # returns int
theme = ctx.settings.get("theme", "light") # returns str
Typen:
Python-Typ von default |
Wire-Typ | Rückgabetyp von get() |
|---|---|---|
bool |
bool |
bool |
int (nicht-bool) |
int |
int |
float |
double |
float |
str (oder None) |
string |
str |
Außerdem:
ctx.settings.contains("wrap") # bool
ctx.settings.remove("stale_key")
ctx.settings.sync() # optional; host also syncs on shutdown
Auf Änderungen aus dem Einstellungsdialog reagieren Sie per
SettingsChanged-Abo (siehe Ereignis-Referenz; die volle
Ereignisfläche kommt mit Phase 3).
app_settings¶
Nur-Lese-Sicht auf die editorweite Konfiguration. Die Schlüsselnamen leben
im C++-Header PluginApi/AppSettingsKeys.h; die autoritative Liste steht
dort.
tab_width = ctx.app_settings.get("editor.tabWidth", 4)
theme = ctx.app_settings.get("editor.theme.variant", "light")
Setter, remove() und sync() auf ctx.app_settings werfen
RuntimeError — die Sicht ist absichtlich schreibgeschützt.
completions¶
Registriert einen Vervollständigungsanbieter, der am Popup des Editors teilnimmt:
@ctx.completions.provider(languages=["python"])
def _complete(req: mte.CompletionRequest):
if not req.prefix:
return []
return [mte.CompletionItem(text=w, kind=mte.KIND_KEYWORD)
for w in ("hello", "hello_world", "helper")
if w.startswith(req.prefix)]
Die umschlossene Funktion wird bei jedem Tastendruck (oder Ctrl+Space)
aufgerufen, dessen Dokument zum Sprachfilter passt. Felder von req:
| Feld | Typ | Bedeutung |
|---|---|---|
document |
int |
Bearbeitetes Dokument |
prefix |
str |
Wortfragment unmittelbar vor dem Cursor |
position |
int |
Byte-Offset des Cursors |
language_id |
str |
Colorizer-Id ("python", "cpp", …) |
manual |
bool |
True, wenn der Nutzer Ctrl+Space drückte; False bei Auto-Auslösung |
Geben Sie eine Liste von mte.CompletionItem zurück (oder einfache
Zeichenketten oder Dicts mit {text, detail, kind}). Arten:
mte.KIND_KEYWORD / KIND_WORD / KIND_SYMBOL / KIND_SNIPPET /
KIND_UNKNOWN.
Deadline
Der Host sendet Ihrem Anbieter eine Tastendruck-Anfrage mit einer ~30-ms-Deadline. Verpasst Ihr Callback sie, erscheint das Popup für diesen Tastendruck ohne Ihre Kandidaten. Bauen Sie teure Indizes im Hintergrund und antworten Sie aus dem Cache — nie I/O im Callback. LSP-artige Netzwerk-Vervollständigung gehört in eine künftige Async-Fläche, nicht hierher.
languages=[] (oder weggelassen) heißt „jedes Dokument“. Mehrere Anbieter
pro Plugin sind erlaubt; jeder Dekorator registriert seinen eigenen.
data_dir¶
Gibt das beschreibbare Pro-Plugin-Verzeichnis zurück, das der Host bereits
für Sie angelegt hat. Für Caches, Indizes und alle Daten, die Ihnen
gehören. ctx.diagnostics_dir() liefert analog das gemeinsame
Absturz-/Protokollverzeichnis (für Sie nur lesbar — dort schreibt der
Absturzberichter).
docks¶
Deklarative Dock-Panels — eine Seitenpanel-UI aus einer
Formular-Spezifikation, nicht aus Qt-Widgets. Das Builder-Vokabular:
mte.ui.
from mte.ui import Column, Row, Label, LineEdit, Checkbox, Button
def register(ctx):
def _on_needle_changed(value: str) -> None:
ctx.log.info(f"needle -> {value!r}")
needle_handler = ctx.docks.on_change(_on_needle_changed)
panel_id = ctx.docks.add_panel(
id="renamer.panel",
title="Renamer",
area="right", # left / right / top / bottom
form=Column([
Label(text="Search:"),
LineEdit(id="needle", placeholder="text...",
binds="settings:lastNeedle",
on_change=needle_handler),
Checkbox(id="cs", label="Case sensitive",
binds="settings:caseSensitive"),
Row([Button(text="Run", on_click="renamer.run")]),
]),
)
ctx.docks.add_panel(*, id=None, title="", area="right", form, initially_visible=True) -> str¶
Registriert ein Dock-Panel. Gibt die Panel-Id zurück (automatisch
vergeben, wenn id None ist).
id(str, optional) — stabiler Bezeichner, mit demctx.ui.get / ctx.ui.setSteuerelemente in diesem Panel adressiert. Ohne Angabe vergibt der Host einepy-dock/N-Id und gibt sie zurück.title(str) — auf dem Dock-Kopf gezeigt.area(str) —"left"/"right"/"top"/"bottom". Unbekannte Werte fallen auf"right"zurück.form(dict) — eine mit denmte.ui-Buildern gebaute Spezifikation.initially_visible(bool) — ob das Dock beim Laden des Plugins angezeigt wird. StandardTrue. Der Nutzer kann es danach verbergen; Sie können es nicht wieder erzwingen.
ctx.docks.on_change(callback) -> str¶
Vergibt eine Handler-Id, um das on_change-Feld eines Steuerelements mit
einem Python-Callable zu verdrahten. Der Callback erhält den neuen Wert
als Zeichenkette; boolesche Steuerelemente liefern "true" / "false",
numerische die Ganzzahl als Zeichenkette.
def _on_volume(value: str) -> None:
ctx.log.info(f"volume={int(value)}")
handler = ctx.docks.on_change(_on_volume)
form = Column([NumberSpin(id="volume", default=5, on_change=handler)])
on_change-Handler-Ids tragen das Präfix py-dock-cb/N und kollidieren
nie mit Befehls-/Ereignis-/Vervollständigungs-Handler-Ids.
Der Callback läuft auf der Ereignisschleife des Workers, NICHT im
Thread-Pool — halten Sie ihn schnell. Nutzen Sie ctx.log, ändern Sie
kleinen In-Memory-Zustand oder feuern Sie einen Folgebefehl; keine schwere
Arbeit hier.
settings_page¶
Fügt eine Seite im Einstellungen-Dialog des Editors hinzu. Dasselbe
Formular-Vokabular wie bei Dock-Panels; dieselbe
binds="settings:<schlüssel>"-Persistenz.
from mte.ui import Column, Checkbox, NumberSpin
def register(ctx):
ctx.settings_page.register(
category="Plugins/Renamer", # slash-separated tree path
title="Renamer",
form=Column([
Checkbox(id="cs", label="Case sensitive by default",
binds="settings:caseSensitive"),
NumberSpin(id="results", default=100, min=1, max=10_000,
binds="settings:resultCap"),
]),
)
ctx.settings_page.register(*, category, title, form, scope="") -> str¶
category(str, erforderlich) — Slash-getrennter Pfad im Einstellungsbaum, z. B."Plugins/My Plugin". Verschachtelte Kategorien werden Baumknoten.title(str, erforderlich) — Überschrift über der Seite.form(dict, erforderlich) — eine mit denmte.ui-Buildern gebaute Spezifikation.scope(str, optional) — Ereignis-Scope, der beiSettingsChangedgesendet wird, nachdem der Nutzer OK / Übernehmen drückt.
Gibt die Handler-Id zurück.
Abbrechen macht Änderungen NICHT rückgängig
Steuerelemente mit binds="settings:<schlüssel>" persistieren eifrig,
während der Nutzer editiert — Abbrechen im Einstellungsdialog
nimmt sie nicht zurück. Das ist eine bekannte Einschränkung der
Phase 3; eine spätere Verfeinerung wird die zugrunde liegenden
Einstellungen in einen puffernden Proxy hüllen.
ui¶
Liest und schreibt Steuerelementwerte in bereits registrierten Panels. Werte sind auf dem Draht immer Zeichenketten.
def register(ctx):
panel_id = ctx.docks.add_panel(id="renamer.panel", ...)
@ctx.commands.command(id="renamer.echo", title="Echo needle")
def _echo():
needle = ctx.ui.get(panel_id, "needle") or ""
cs = (ctx.ui.get(panel_id, "cs") or "false") == "true"
ctx.status.show(f"needle={needle!r} case={cs}")
@ctx.commands.command(id="renamer.clear", title="Clear needle")
def _clear():
ctx.ui.set(panel_id, "needle", "")
ctx.ui.get(panel_id, control_id) -> Optional[str]¶
Liest den aktuellen Wert eines Steuerelements. Gibt None zurück, wenn
Panel oder Steuerelement unbekannt sind (kein Fehler — behandeln Sie
None und "" gleichermaßen als „leer“).
- Label / Button / LineEdit / ComboBox — liefert den Text.
- Checkbox — liefert
"true"oder"false". - NumberSpin — liefert die Ganzzahl als Dezimal-Zeichenkette.
- List — liefert den Text des aktuell gewählten Eintrags (leer, wenn nichts gewählt ist).
ctx.ui.set(panel_id, control_id, value) -> bool¶
Schreibt programmatisch einen Wert in ein Steuerelement. Gibt True
zurück, wenn das Schreiben gelang, False, wenn Panel/Steuerelement
unbekannt sind oder der Wert nicht wandelbar ist (z. B. eine
Nicht-Ganzzahl an eine NumberSpin).
Python-True / False werden vor dem Senden zu "true" / "false";
alles andere geht durch str().
Ein hostgetriebenes set feuert den on_change-Callback des
Steuerelements NICHT erneut — Sie können Ihre eigenen Widgets aus einem
Befehlshandler aktualisieren, ohne Endlosschleifen.
Zusammenfassung der Laufzeit-Bindungen (Phase 3)¶
| Formular-Feld | Laufzeitverhalten |
|---|---|
id="…" |
Steuerelement über ctx.ui.get / ctx.ui.set adressieren. |
binds="settings:<schlüssel>" |
Liest den Anfangswert aus ctx.settings; schreibt bei jeder Nutzeränderung zurück. |
on_click="<cmd>" |
Buttons rufen beim Klick die registrierte Befehls-Id auf. |
on_change=handler |
Feuert den über ctx.docks.on_change(...) registrierten Callback bei Nutzeränderung. |
Dieselben Regeln gelten für Dock-Panels und Einstellungsseiten.