Ein Python-Plugin für den Modular Text Editor mit Claude schreiben¶
Zielgruppe: ein KI-Assistent (Claude / ChatGPT / usw.). Diese Seite ist ein in sich geschlossener Anweisungssatz: Geben Sie sie dem Assistenten zusammen mit der Aufgabe („schreib mir ein Plugin, das X tut“), und der Assistent sollte einen vollständigen, korrekten, funktionierenden Plugin-Ordner produzieren, ohne den Rest der Doku zu lesen.
Wenn Sie ein Mensch sind, der von Hand ein Plugin schreibt, beginnen Sie hier — diese Seite ist für KI-Konsum optimiert und lässt die freundlichen Beispiele aus.
1. Was Sie produzieren¶
Einen Ordner mit genau zwei Dateien (plus optionale Extras):
Geben Sie den Ordner als Codeblock pro Datei zurück. Der Nutzer legt den
Ordner entweder in das Plugin-Verzeichnis des Editors, oder er zippt den
Ordner, benennt das Archiv in <name>.mteplugin um und installiert es über
Einstellungen → Plugins → Installieren…. Beide Wege brauchen einen
Neustart zum Laden.
Die zwei Dateien sind unten erschöpfend beschrieben. Befolgen Sie die Regeln wörtlich — der Host führt eine statische Prüfung durch und lehnt Plugins ab, die sie brechen.
2. plugin.json — kanonische Vorlage¶
{
"id": "org.example.<short-name>",
"name": "<Human Display Name>",
"version": "1.0.0",
"vendor": "<Author or Team>",
"description": "<One sentence about what it does.>",
"apiVersion": 1,
"entry": "python",
"module": "main",
"order": 900,
"menus": [
{ "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
]
}
Harte Regeln¶
"apiVersion"muss1sein (die aktuelle Host-Version)."entry"muss"python"sein — nichts anderes führt zu diesem Backend."module"muss"main"sein, sofern es keinen ausdrücklichen Grund für einen anderen Modulnamen gibt — und nie ein gepunkteter Pfad (Phase 1 unterstützt nur flache Modulnamen)."id"muss Reverse-DNS-Stil haben (org.<vendor>.<name>) und global eindeutig unter den installierten Plugins des Nutzers sein. Hat der Nutzer keinen Vendor genannt, verwenden Sieorg.example..menus[]muss mindestens einen Eintrag enthalten, dessentitledem linkesten Segment jedes inmain.pyverwendeten Menüpfads entspricht. Bevorzugen Sie"Plugins"für alles Nutzerseitige, das nicht offensichtlich in ein Kernmenü gehört.
Optionale Felder¶
"pyRequires": []— ein Array von pip-Requirement-Zeichenketten (z. B.["requests>=2.31"]). Wenn nicht leer, provisioniert der Editor beim ersten Start eine Pro-Plugin-venv (pip install— braucht dieses eine Mal Netz) und führt das Plugin darunter aus. Bevorzugen Sie die Standardbibliothek, wenn sie genügt (keine Provisionierungsverzögerung, funktioniert offline); deklarieren SiepyRequires, wenn die Aufgabe wirklich ein Drittpaket braucht, und sagen Sie dem Nutzer, dass der erste Start zum Installieren pausiert."permissions": []— grobe Fähigkeitsdeklarationen (z. B.["network"]), schreibgeschützt auf der Plugins-Seite gezeigt. Deklarieren Sie"network", wenn das Plugin ausgehende Verbindungen aufbaut; das ist ehrliche Kennzeichnung für den Nutzer, keine Durchsetzung.
Werte, die Sie NICHT erfinden sollten¶
- Jedes oben nicht gelistete Feld. Der Host ignoriert unbekannte Schlüssel; sie hinzuzufügen ist ein Wartungsrisiko.
3. main.py — kanonisches Skelett¶
"""<Short description of what this plugin does.>"""
import mte # noqa: F401 -- pulls in dataclasses for event payloads
def register(ctx):
# Register commands, menu items, and event subscriptions here.
# Everything below is illustrative -- delete what you don't need.
@ctx.commands.command(id="<id>.<verb>",
title="<Human Label>",
shortcut="Ctrl+Alt+<letter>", # optional
category="<Group>") # optional
def _handler():
ctx.status.show("hello", timeout_ms=2000)
ctx.log.info("<id>: handler ran")
ctx.menus.add_item("Plugins/<Human Label>/<Item>", "<id>.<verb>")
Harte Regeln¶
register(ctx)MUSS eine Funktion oberster Ebene sein. Die statische Prüfung verlangt eine mitdef register(in Spalte 0 beginnende Zeile. Verschachteln Sie sie in eine Klasse, dekorieren sie auf Modulebene oder machen sie zuasync def, wird das Plugin bei der Erkennung stillschweigend abgelehnt.- Importieren Sie aus
mte, wenn Sie Ereignisse abonnieren — die typisierten Dataclasses (mte.DocumentSavedusw.) kommen aus diesem Paket.import mteam Anfang ist auch ungenutzt sicher; das# noqa: F401oben verhindert Lint-Lärm. - Niemals
print(). Der stdout des Workers ist der RPC-Kanal; Schreiben dorthin korrumpiert das Protokoll. Verwenden Siectx.log.info / warning / error. - Importieren Sie nie etwas Undeklariertes. Jenseits der
Standardbibliothek und
mtemuss jeder Drittimport inpyRequiresderplugin.jsonstehen (der Editor pip-installiert ihn beim ersten Start in die venv des Plugins) oder im Plugin-Ordner vendored sein. Ein undeklarierter Import wirft auf der Maschine des NutzersModuleNotFoundError. - Befehls-/Ereignishandler dürfen kurz blockieren — sie laufen im
Thread-Pool des Workers, synchrone Aufrufe in
ctx.editor/ctx.settings/ctx.data_dirsind also legal. Aber Vervollständigungs-Callbacks (@ctx.completions.provider(...)) DÜRFEN NICHT blockieren: Sie laufen auf der Ereignisschleife mit einer ~30-ms-Deadline. Kein I/O, keinectx.editor.*-Aufrufe, nur Cache. Indizes anderswo bauen (z. B. beidocument.opened) und aus einem Dict antworten. - Kein Import von Qt, PySide, PyQt oder Scintilla. Sie sind im Worker nicht verfügbar.
- Befehls-Ids müssen mit dem Namensraum Ihres Plugins geprefixt sein,
um Kollisionen mit dem Kern / anderen Plugins zu vermeiden.
<id>.<verb>oben; z. B.wordcount.count,hello.sayHi. - Menüpfade müssen mit einem Menü oberster Ebene beginnen, das Sie
auch in
menus[]derplugin.jsondeklariert haben. Konvention:"Plugins/<Plugin-Name>/<Verb>".
4. Vollständige API — alles auf ctx¶
Sie haben zwölf Dienste (Phasen 1 + 2 + 3). Was hier nicht gelistet
ist, existiert nicht — erfinden Sie keine Dienste. (Pro-Plugin-venvs werden
über pyRequires deklariert, nicht über einen ctx-Dienst; das
Streaming-/Cancel-Protokollsubstrat hat noch keine Plugin-API.)
ctx.commands¶
Dekorator. Registriert einen Befehl und bindet _handler (eine nullstellige
Funktion, die None zurückgibt). Ausnahmen in _handler fängt der Worker
und schreibt sie auf stderr; sie bringen den Editor nicht zum Absturz.
ctx.menus¶
ctx.menus.add_item(path, command_id) # -> handler_id (str)
ctx.menus.add_separator(path) # -> handler_id (str)
ctx.menus.set_item_checked(handler_id, checked) # -> None
path ist "Top/Sub/…/Beschriftung". Zwischen-Untermenüs entstehen bei
Bedarf.
set_item_checked macht den Eintrag abhakbar und setzt sein Häkchen. Für
dauerhafte Ein/Aus-Schalter. Erster Aufruf befördert; weitere kippen nur.
Stiller No-op bei unbekannten Ids.
ctx.events¶
Sechs Themen, jedes mit Zucker-Dekorator und roher Form.
| Zucker | Rohes Thema | Payload |
|---|---|---|
@ctx.events.on_document_opened |
document.opened |
mte.DocumentOpened(document: int, path: str) |
@ctx.events.on_document_closed |
document.closed |
mte.DocumentClosed(document: int) |
@ctx.events.on_document_saved |
document.saved |
mte.DocumentSaved(document: int) |
@ctx.events.on_selection_changed |
selection.changed |
mte.SelectionChanged(document: int) |
@ctx.events.on_active_document_changed |
document.activeChanged |
mte.ActiveDocumentChanged(document: int) |
@ctx.events.on_app_about_to_quit |
app.aboutToQuit |
mte.AppAboutToQuit() |
Rohe Form:
handler_id = ctx.events.subscribe("<topic>", callback)
ctx.events.unsubscribe(handler_id) # to unbind
Erfinden Sie keine neuen Themen — die rohe Form validiert sie und wirft
ValueError bei allem außerhalb der Tabelle.
ctx.log¶
Fire-and-forget. Zeilen tragen im Diagnoseprotokoll des Hosts das Präfix
[python:<plugin.id>].
ctx.status¶
level muss "info" / "warning" / "error" sein. timeout_ms=0
heißt, der Host wählt einen Standard.
ctx.editor (Phase 2)¶
Jede Methode ist synchron: Sie blockiert den aufrufenden Thread. In
Ordnung aus einem def-Befehls-/Ereignis-Callback (der Worker verteilt
Sync-Handler auf einen Thread-Pool); illegal aus async def-Handlern
und aus ctx.completions-Anbietern (die direkt auf der Ereignisschleife
laufen).
Dokumente werden über opake int-Ids referenziert (0 = kein Dokument).
doc = ctx.editor.active_document()
docs = ctx.editor.open_documents()
ctx.editor.set_active_document(doc)
path = ctx.editor.file_path(doc) # str or None
text = ctx.editor.text(doc)
slice = ctx.editor.text_range(doc, start, end) # [start, end) bytes
ctx.editor.set_text(doc, "new text")
n = ctx.editor.length(doc)
sel = ctx.editor.selection_range(doc) # mte.MatchRange
s = ctx.editor.selected_text(doc)
ctx.editor.replace_selection(doc, "hi")
ctx.editor.set_selection(doc, mte.MatchRange(4, 9))
pos = ctx.editor.caret(doc) # mte.EditorPosition
ctx.editor.set_caret(doc, mte.EditorPosition(line=5, column=0))
new_doc = ctx.editor.open_file("/path/to/file") # 0 on failure
ok = ctx.editor.save_document(doc)
dirty = ctx.editor.is_modified(doc)
hit = ctx.editor.find_in_range(doc, "TODO", 0,
ctx.editor.length(doc),
mte.SearchOptions(match_case=True))
new_end = ctx.editor.replace_target(doc, hit, "DONE",
mte.SearchOptions())
style = ctx.editor.indentation(doc) # IndentationStyle
ctx.editor.set_indentation(doc,
mte.IndentationStyle(use_tabs=False, width=2))
lang = ctx.editor.colorizer_id(doc) # "python", "cpp", ...
mte.SearchOptions.mode: "normal" / "extended" / "regex".
mte.SearchOptions.direction: "forward" / "backward".
ctx.settings / ctx.app_settings (Phase 2)¶
Typisierter Schlüssel/Wert-Speicher pro Plugin (ctx.settings) plus eine
Nur-Lese-Sicht der appweiten Einstellungen (ctx.app_settings). Der 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) # bool
count = ctx.settings.get("count", 0) # int
theme = ctx.settings.get("theme", "light") # str
ctx.settings.contains("wrap")
ctx.settings.remove("stale")
# App-wide (read-only)
tw = ctx.app_settings.get("editor.tabWidth", 4)
ctx.app_settings.set/remove/sync werfen RuntimeError. Prüfen Sie in
eigener Leselogik bool vor int (Python: isinstance(True, int) == True).
ctx.completions (Phase 2)¶
@ctx.completions.provider(languages=["python"])
def _complete(req: mte.CompletionRequest):
return [mte.CompletionItem(text=w, kind=mte.KIND_KEYWORD)
for w in ("hello", "hello_world")
if w.startswith(req.prefix)]
Felder von req: document, prefix, position, language_id,
manual. Arten: KIND_KEYWORD / KIND_WORD / KIND_SYMBOL /
KIND_SNIPPET / KIND_UNKNOWN. Zurückgeben: list[CompletionItem],
einfache Zeichenketten oder Dicts. Der Callback MUSS in ~30 ms
zurückkehren, sonst verwirft der Host Ihre Ergebnisse für diesen
Tastendruck — kein I/O, kein ctx.editor.text(...), nur Cache. Indizes
anderswo bauen (z. B. bei document.opened).
ctx.data_dir() / ctx.diagnostics_dir() (Phase 2)¶
data_dir() für Caches und Indizes. diagnostics_dir() ist nur lesbar
(dort schreibt der Absturzberichter).
ctx.docks — deklarative Dock-Panels (Phase 3)¶
from mte.ui import Column, Row, Label, LineEdit, Checkbox, Button
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="myplugin.panel", # returned; use it for ctx.ui.get / ctx.ui.set
title="My Plugin",
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="myplugin.run")]),
]),
)
Bauen Sie die Formular-Spezifikation mit den mte.ui-Helfern.
Das Steuerelement-Vokabular ist fest: Label / Button / Checkbox /
LineEdit / NumberSpin / ComboBox / List in Column / Row / Group.
binds="settings:<schlüssel>"— der Wert des Steuerelements persistiert automatisch überctx.settings(derselbe Speicher wiectx.settings.set).on_click="<cmd.id>"— nur Button; ruft einen registrierten Befehl auf.on_change=<handler>— überctx.docks.on_change(callback)vergeben; feuert bei Nutzeränderungen.
on_change-Callbacks laufen auf der Ereignisschleife des Workers (NICHT
im Thread-Pool). Halten Sie sie billig — keine Editor-Aufrufe, keine
lange Arbeit. Kleinen In-Memory-Zustand ändern, loggen oder einen Befehl
auslösen; schwere Arbeit im Befehl erledigen.
ctx.settings_page — deklarative Einstellungsseiten (Phase 3)¶
from mte.ui import Column, Checkbox, NumberSpin
ctx.settings_page.register(
category="Plugins/My Plugin", # slash-separated tree path
title="My Plugin",
form=Column([
Checkbox(id="verbose", label="Verbose logging",
binds="settings:verbose"),
NumberSpin(id="cap", default=100, min=1, max=10_000,
binds="settings:cap"),
]),
)
Dieselben Builder, dieselbe binds-Semantik. Zwei Steuerelemente mit
demselben binds="settings:<schlüssel>" spiegeln sich in Echtzeit — das
Dock-Panel und die Einstellungsseite können beide ein Kästchen für
dieselbe Einstellung halten, und das Umschalten des einen kippt das
andere.
Phase-3-Vorbehalt
Werte persistieren eifrig bei jeder Nutzeränderung — Abbrechen rollt nicht zurück, was der Nutzer geändert hat. Das wird in einer späteren Verfeinerung besser.
ctx.ui.get / ctx.ui.set — Laufzeitzugriff auf Panel-Steuerelemente (Phase 3)¶
# Read the current value; returns str or None.
current = ctx.ui.get(panel_id, "needle")
# Write. Returns bool (False when panel/control unknown, or when the
# value can't be coerced to the target type).
ctx.ui.set(panel_id, "needle", "hello")
ctx.ui.set(panel_id, "cs", True) # bool -> "true"/"false"
get liefert den Primärwert als Zeichenkette:
- Label / Button / LineEdit / ComboBox → Text
- Checkbox →
"true"/"false" - NumberSpin → Ganzzahl dezimal
- List → Text des aktuell gewählten Eintrags (leer, wenn keiner)
Hostgetriebenes set ist still: Es feuert das on_change des
Steuerelements NICHT erneut — eigene Widgets lassen sich also aus einem
Befehlshandler ohne Endlosschleifen aktualisieren.
5. Vollständiges Beispiel — Plugin wordcount¶
Kopieren Sie es wörtlich und passen Sie id, name und die title an.
Es übt jeden Dienst der Phase 1.
{
"id": "org.example.wordcount",
"name": "Word Count",
"version": "1.0.0",
"vendor": "Example, Inc.",
"description": "Counts words the user has typed since launch and logs saves.",
"apiVersion": 1,
"entry": "python",
"module": "main",
"order": 900,
"menus": [
{ "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
]
}
"""Reports a session word-count total via a menu command and logs saves."""
import mte # noqa: F401
# Module-level state is fine -- one worker per plugin, one register() call.
_state = {"saves": 0}
def register(ctx):
ctx.log.info("wordcount: register() ran")
@ctx.commands.command(id="wordcount.report",
title="Report save count",
shortcut="Ctrl+Alt+W",
category="Text")
def _report():
n = _state["saves"]
ctx.status.show(f"{n} save(s) this session", timeout_ms=2500)
ctx.log.info(f"wordcount: user asked for count; {n} saves so far")
ctx.menus.add_item("Plugins/Word Count/Report save count",
"wordcount.report")
@ctx.events.on_document_saved
def _saved(ev: mte.DocumentSaved):
_state["saves"] += 1
ctx.log.info(f"wordcount: doc {ev.document} saved -- total now {_state['saves']}")
@ctx.events.on_app_about_to_quit
def _quit(_ev):
ctx.log.info(f"wordcount: shutting down after {_state['saves']} saves")
Erwarteter Ablauf: Editor öffnen, eine Datei dreimal speichern, dann
Plugins → Word Count → Report save count klicken. Die Statusleiste
zeigt 3 save(s) this session. Das Protokoll hält jeden Schritt fest.
6. Häufige Aufgaben — Copy-Paste-Rezepte¶
Jedes Rezept ist ein Nur-Körper-Snippet: in def register(ctx): einfügen.
Einen Menübefehl hinzufügen¶
@ctx.commands.command(id="mine.hello", title="Say Hi", shortcut="Ctrl+Alt+Y")
def _hi():
ctx.status.show("hi")
ctx.menus.add_item("Plugins/Mine/Say Hi", "mine.hello")
Auf Speichern reagieren¶
Beim Beenden warnen¶
@ctx.events.on_app_about_to_quit
def _on_quit(_ev):
ctx.log.warning("mine: last chance to persist state")
Auf einer bestimmten Stufe loggen¶
ctx.log.trace ("very fine")
ctx.log.debug ("dev")
ctx.log.info ("routine")
ctx.log.warning("odd")
ctx.log.error ("wrong")
Menü-Trenner¶
Mehrere verwandte Befehle¶
@ctx.commands.command(id="mine.a", title="Do A")
def _a(): ...
@ctx.commands.command(id="mine.b", title="Do B")
def _b(): ...
ctx.menus.add_item("Plugins/Mine/Do A", "mine.a")
ctx.menus.add_item("Plugins/Mine/Do B", "mine.b")
Dauerhafter Ein/Aus-Schalter mit sichtbarem Häkchen¶
KEY = "verbose"
item = ctx.menus.add_item("View/Verbose Logging", "mine.toggleVerbose")
# Seed the mark from the persisted state so it matches on launch.
ctx.menus.set_item_checked(item, ctx.settings.get(KEY, False))
@ctx.commands.command(id="mine.toggleVerbose", title="Verbose Logging")
def _toggle():
on = not ctx.settings.get(KEY, False)
ctx.settings.set(KEY, on)
ctx.menus.set_item_checked(item, on)
Seitenpanel mit Texteingabe, Kästchen und zwei Buttons¶
from mte.ui import Column, Row, Label, LineEdit, Checkbox, Button
def _on_needle(value): ctx.log.info(f"needle={value!r}")
needle_h = ctx.docks.on_change(_on_needle)
panel = ctx.docks.add_panel(
id="mine.panel", title="Mine", area="right",
form=Column([
Label(text="Search:"),
LineEdit(id="needle", placeholder="text...",
binds="settings:needle", on_change=needle_h),
Checkbox(id="cs", label="Case", binds="settings:cs"),
Row([Button(text="Run", on_click="mine.run"),
Button(text="Clear", on_click="mine.clear")]),
]),
)
@ctx.commands.command(id="mine.run", title="Run")
def _run():
needle = ctx.ui.get(panel, "needle") or ""
cs = ctx.ui.get(panel, "cs") == "true"
ctx.status.show(f"searching for {needle!r} (case={cs})")
@ctx.commands.command(id="mine.clear", title="Clear")
def _clear():
ctx.ui.set(panel, "needle", "")
Einstellungsseite, die die Einstellungen eines Docks spiegelt¶
from mte.ui import Column, Checkbox
# Both controls bind to the SAME settings key -- toggling one
# flips the other in real time.
ctx.docks.add_panel(id="mine.panel", title="Mine", area="right",
form=Column([Checkbox(id="cs", label="Case",
binds="settings:cs")]))
ctx.settings_page.register(category="Plugins/Mine", title="Mine",
form=Column([Checkbox(id="cs", label="Case sensitive",
binds="settings:cs")]))
7. Anti-Muster — tun Sie das NICHT¶
print() zur Diagnose¶
Falsch:
Richtig:
register in einer Klasse¶
Falsch:
Richtig:
async def register¶
Falsch:
Richtig:
Drittimporte ohne mitgeliefertes Wheel¶
Falsch:
Richtig (Phase 1): bei der Standardbibliothek bleiben. Brauchen Sie
wirklich etwas Drittes, liefern Sie das Wheel im Plugin-Ordner mit und
erweitern Sie sys.path in den ersten Zeilen von main.py; siehe
Paketierung.
Qt-Importe¶
Falsch:
Richtig: nirgends Qt. Alle UI, die Sie beitragen können, läuft über
ctx.menus, ctx.status und (ab Phase 3) deklarative
Dock-/Einstellungspanels. Nie ein rohes Widget.
Blockieren in einem Handler¶
Falsch:
def _handler():
time.sleep(30) # <-- stalls the worker for 30s
result = requests.get("https://slow.example")
Richtig (Phase 1): in einen Thread auslagern, über ctx.log
kommunizieren:
def _handler():
import threading
def _work():
time.sleep(30)
ctx.log.info("done")
threading.Thread(target=_work, daemon=True).start()
ctx.status.show("started")
Menüpfad, dessen oberstes Segment nicht in plugin.json steht¶
Falsch:
Richtig: das oberste Pfadsegment an menus[] ausrichten. Oben
"Plugins/Mine/Do" verwenden.
ctx für später aufheben¶
Falsch:
_ctx = None
def register(ctx):
global _ctx
_ctx = ctx # <-- fine for the lifetime of this worker,
# but confusing and unnecessary
def some_other_function():
_ctx.log.info("...") # <-- called from where?
Richtig: die benötigten Dienste in der Closure einfangen, die sie
verwendet. register ist der Ort der Verdrahtung; alles andere ist ein
Handler, der über die Closure bereits Zugriff hat.
8. Debugging-Checkliste für den Nutzer¶
Fügen Sie dies ans Ende Ihrer Antwort an, wenn Sie ein Plugin produzieren, damit der Nutzer weiß, was bei Problemen zu prüfen ist:
- Editor aus einer Shell starten, in der ein Python-3.9+-Interpreter im
PATHliegt (oderMTE_PYTHONauf einen zeigt). - Im Protokoll nach
PluginHost: Python plugin backend enabled; interpreter=…suchen. Fehlt es, weiter zu Fehlerbehebung. - Nach
[python:<ihre.id>] register() ransuchen (demwordcount-Beispiel folgend) — beweist, dassregisterlief. - Fehlt der Menüpunkt, das Protokoll auf
PythonPluginBackend: skipping '<id>': …-Erklärungen prüfen. - Tut ein Menüklick nichts, das Protokoll auf
[worker stderr] …-Zeilen prüfen — dort landen Python-Tracebacks.
9. Am Ende jeder Antwort zu wiederholende Auflagen¶
Wenn Sie ein Plugin produzieren, sagen Sie dem Nutzer immer:
- Installation auf einem der beiden Wege:
- den Ordnerinhalt als
<name>.mtepluginzippen und Einstellungen → Plugins → Installieren… verwenden (validiert das Paket, bevor irgendwas angefasst wird), oder - den Ordner in das Benutzer-Plugin-Verzeichnis des Editors legen
(
%LOCALAPPDATA%/MTE/Plugins/unter Windows;~/Library/Application Support/MTE/Plugins/unter macOS;~/.local/share/MTE/Plugins/unter Linux).
- den Ordnerinhalt als
- Editor neu starten.
MTE_PYTHONsetzen, wenn das Protokoll „no Python interpreter found“ meldet.- Die Protokollzeile
[python:<id>] …zurückmelden, damit der Lauf bestätigt werden kann.
10. Quelle der Wahrheit¶
Widerspricht irgendeine Aussage dieser Seite dem, was das
Python-Client-Paket tatsächlich implementiert, gewinnt das Paket. Es liegt
neben dem Editor unter <exe>/Python/mte/ — lesen Sie diese Quelle, bevor
Sie ein Verhalten entscheiden. Die C++-Seite des Protokolls steht in
Components/PluginHost/Src/PyServiceBridge.cpp, die Wire-Spezifikation in
Python/PROTOCOL.md.