ctx — der Plugin-Kontext¶
register(ctx) erhält ein Argument: einen PluginContext. Er stellt die
folgenden Dienste bereit (die gesamte IPluginContext-Oberfläche, die in
das Out-of-Process-Modell passt). 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 / files / search / bookmarks / groups
ctx.languages # list languages, read / switch a document's language, contribute one
ctx.settings # per-plugin key/value store
ctx.app_settings # editor-wide read-only settings view (keys: mte.keys)
ctx.document_settings # .editorconfig-layered per-document reads
ctx.theme # themeable colour categories for your own UI
ctx.diagnostics # publish squiggles + margin glyphs
ctx.compare # open the side-by-side compare view
ctx.completions # register auto-completion providers
ctx.docks # declarative dock panels (+ set_panel_visible, is_panel_visible)
ctx.line_markers # margin glyphs / line tints that follow edits; the margin-click claim
ctx.settings_page # declarative Preferences pages
ctx.ui # runtime get/set for panel controls
Zwei Teile des nativen Vertrags haben noch kein Python-Gegenstück: Hover-Provider (die Hover-Prioritätsbänder gelten daher nicht für Python-Plugins) und Provider für das Editor-Kontextmenü (daher auch nicht die Unterscheidung Text / Rand im Kontextmenü). Beide werden beantwortet, während der Benutzer wartet; das braucht dieselbe Deadline-Behandlung wie bei Completion-Providern, bevor ein Umweg über den Worker sicher ist.
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).
ctx.menus.set_item_enabled(handler_id, enabled)¶
Aktiviert einen zuvor hinzugefügten Eintrag oder graut ihn aus. Nutzen Sie das, wenn das Command eine Voraussetzung braucht — etwas zu Schließendes, etwas Wiederherstellbares — damit das Menü die Nichtverfügbarkeit zeigt, statt einen Eintrag anzubieten, der still nichts tut.
item = ctx.menus.add_item("File/Close Thing", "myplugin.closeThing")
# Nothing to close yet:
ctx.menus.set_item_enabled(item, False)
# ... and from every place that changes the state the command needs:
ctx.menus.set_item_enabled(item, thing_is_open)
Ein deaktivierter Eintrag bleibt sichtbar, lässt sich aber nicht auslösen —
weder per Klick noch über sein Tastenkürzel. Der Aufruf ist idempotent, Sie
müssen sich den vorherigen Zustand also nie merken, und eine unbekannte
handler_id ist ein stiller No-op wie oben. Einträge starten aktiviert: ein
Aufruf direkt nach add_item setzt einen beim Start nicht verfügbaren
Eintrag.
events¶
Editor-Ereignisse abonnieren. Zwei gleichwertige APIs — die
Zucker-Dekoratoren sind 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_document_modified_changed |
Das Flag sauber/geändert kippte (modified: bool) |
on_text_changed |
Jede Änderung (position, added_bytes, removed_bytes) — trivial halten |
on_settings_changed |
Einstellungen übernommen (scope: "" editorweit, "plugins/<id>") |
on_theme_changed |
Theme gewechselt oder bearbeitet (variant) — ctx.theme-Farben neu lesen |
on_workspace_root_changed |
Workspace-Ordner geöffnet / geschlossen (root) |
on_workspace_files_changed |
Dateimenge des Workspace geändert (files) |
on_host_window_shown |
Einmal pro Lauf, nach dem ersten Frame — langsame Startarbeit hier einplanen |
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
können nach dem entsprechenden nativen Handler laufen; behandeln Sie
die Ereigniszustellung als informativ, nicht autoritativ.
Veröffentlichen¶
ctx.events.publish(topic, payload) sendet an native und Python-Abonnenten
gleichermaßen — akzeptiert nur für die zwei Workspace-Topics
(workspace.rootChanged, workspace.filesChanged); alles andere wirft
ValueError. Siehe die Ereignis-Referenz.
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.
ctx.status.clear() löscht die gerade angezeigte flüchtige Meldung.
Persistente Segmente¶
Für Live-Zustand (eine Wortzahl, ein Serverstatus) nutzen Sie ein Segment, das Sie besitzen und auffrischen — es überschreibt nie eine Meldung und umgekehrt:
seg = ctx.status.add_segment("wordcount.total", priority=100)
seg.set_text("2,431 words")
seg.set_visible(False) # hide without giving it up
seg.remove() # take it off the bar (idempotent)
priority ordnet Plugin-Segmente von links nach rechts (niedriger = weiter
links; Gleichstand behält die Registrierungsreihenfolge). Das Herunterfahren
des Plugins entfernt alles noch Gehaltene.
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))
ranges = ctx.editor.selection_ranges(doc) # list[MatchRange]: one per caret
block = ctx.editor.is_rectangular_selection(doc) # Alt+drag column block?
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
here = ctx.editor.open_file_at("/path/to/file", mte.EditorPosition(41, 0))
ctx.editor.reveal_line(doc, 120) # scroll there; caret and focus stay
ok = ctx.editor.save_document(doc)
closed = ctx.editor.close_document(doc) # prompts if modified; False = user cancelled
dirty = ctx.editor.is_modified(doc)
ctx.editor.set_modified(doc, True) # force the tab asterisk
ro = ctx.editor.is_read_only(doc) # e.g. Monitoring (tail -f) mode
enc = ctx.editor.document_encoding(doc) # mte.DocumentEncoding, e.g. UTF8_BOM
ctx.editor.set_document_encoding(doc, mte.DocumentEncoding.UTF8) # takes effect on the next save
eol = ctx.editor.line_ending(doc) # mte.LineEnding, e.g. CRLF
ctx.editor.set_line_ending(doc, mte.LineEnding.LF) # Enter inserts it; the next save writes it
scratch = ctx.editor.create_untitled("text", "Untitled-3") # new buffer, doc-id
sid = ctx.editor.stable_id(doc) # survives Save As; compare with ==
clip = ctx.editor.clipboard_text() # system clipboard, "" if no text
# Declare that you snapshot unsaved buffers (the host then stops asking
# "save changes?" when the window closes):
ctx.editor.set_unsaved_changes_preserved(True)
ctx.editor.unsaved_changes_preserved() # bool, global flag
open_file_at ist der eine Aufruf für „bring den Benutzer dorthin“ (ein
Build-Fehler, ein Stack-Frame, ein Suchtreffer): Er öffnet die Datei oder
aktiviert ihren Tab, setzt die Einfügemarke ohne Auswahl, klappt eine
Faltung auf, die die Zeile verbirgt, scrollt die Zeile in den sichtbaren
Bereich — zentriert, wenn sie außerhalb des Bildschirms lag — und fokussiert
den Editor. Zeile und Spalte sind 0-basiert (die Spalte in UTF-8-Bytes) und
werden begrenzt, nie abgelehnt; er liefert die Dokument-Id oder 0, wenn die
Datei nicht geöffnet werden konnte, und dann hat sich nichts bewegt.
reveal_line zeigt nur eine Zeile: Es bewegt die Einfügemarke nicht,
wechselt keine Tabs und nimmt keinen Fokus.
Lesezeichen, Markierungen, URL-Hervorhebung¶
ctx.editor.toggle_bookmark(doc, line) # 0-based line
lines = ctx.editor.bookmarked_lines(doc) # list[int]
nxt = ctx.editor.next_bookmark(doc, from_line, wrap=True) # -1 = none
prv = ctx.editor.previous_bookmark(doc, from_line, wrap=True)
ctx.editor.clear_bookmarks(doc)
ctx.editor.mark_range(doc, mte.MatchRange(4, 9)) # "mark" indicator
ctx.editor.clear_marks(doc)
ctx.editor.set_url_highlight_enabled(doc, True)
ctx.editor.is_url_highlight_enabled(doc)
url = ctx.editor.url_at_caret(doc) # "" when none
ctx.editor.open_url_at_caret(doc) # bool
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.
Inkrementelle Suche (das Muster wächst beim Tippen, ab einem gesetzten Anker):
ctx.editor.set_incremental_search_anchor(doc)
hit = ctx.editor.incremental_search(doc, "TOD", opts) # MatchRange
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))
# Where word-wrapped continuation rows start:
# "left" (window edge) / "same" (align with the line; default) / "indent"
ctx.editor.set_wrap_indent(doc, "same")
mode = ctx.editor.wrap_indent(doc) # same strings back
lang = ctx.editor.colorizer_id(doc) # e.g. "python", "" if none
# Semantic classification from the active colorizer (cheap, no re-lex):
# list[mte.CategorySpan(start, length, category)] with category names
# "comment" / "string" / "keyword" / ... -- a spell checker keeps to
# comments and strings this way.
spans = ctx.editor.text_categories(doc, 0, ctx.editor.length(doc))
ctx.editor.trigger_completion(doc) # pop the list as if Ctrl+Space
Split-View / Tab-Gruppen¶
n = ctx.editor.group_count() # >= 1
g = ctx.editor.group_of(doc) # -1 if not open
w = ctx.editor.group_weights() # relative widths
ctx.editor.move_document_to_group(doc, 1) # creates trailing groups on demand
ctx.editor.set_group_weights([2, 1])
ctx.editor.set_document_tab_index(doc, 0) # reorder within its pane
gs = ctx.editor.groups_of(doc) # EVERY pane showing it
ctx.editor.show_document_in_group(doc, 1) # split it into pane 1
Ein in zwei Bereichen gezeigtes Dokument ist ein Dokument mit einer id,
sodass group_of() nur einen seiner Plätze nennen kann. groups_of()
liefert alle, und show_document_in_group() legt eine zweite, geteilte
Ansicht an — genau das, was Ansicht ▸ Dokument rechts teilen tut,
sodass kein document_opened-Ereignis ausgelöst wird.
ctx.editor spiegelt das native IEditorService jetzt eins zu eins.
languages¶
Syntaxfärbung ist ein Kern-Feature — der Host erkennt die Sprache eines
Dokuments und färbt es ohne jedes Plugin. ctx.languages lässt Sie
mitmachen: auflisten, was der Host kennt, die Sprache eines Dokuments lesen
oder überschreiben, die Erkennung neu anstoßen und ganze Sprachdefinitionen
beisteuern.
for lang in ctx.languages.available(): # list[mte.LanguageInfo(id, name)]
...
lang_id = ctx.languages.language_for(doc) # "" when uncoloured
ok = ctx.languages.set_language_for(doc, "json") # "" clears; False = unknown
ctx.languages.auto_detect_for(doc) # undo a manual override
handler = ctx.languages.register(
id="mylang", name="My Language", lexer="cpp",
extensions=["mylang", "ml2"],
keywords=[{"group": "Primary keywords and identifiers",
"words": "if else while"}],
styles={"SCE_C_COMMENT": "comment", "SCE_C_STRING": "string"})
...
ctx.languages.unregister(handler) # restores what it displaced
register nimmt dieselbe Form wie eine mitgelieferte colorizers/*.json
(oder ein ganzes definition=-Dict): lexer nennt einen vorhandenen
Backend-Lexer (der Host gewinnt zur Laufzeit keine neuen), styles bildet
die Stil-Namen dieses Lexers auf semantische Kategorien ab, und keywords
adressiert seine Wortlisten über die Slot-Beschreibung des Lexers — der Host
löst beides genau wie bei einer mitgelieferten Datei auf, Engine-Ids
schreiben Sie nie. Das Registrieren einer vorhandenen Id ersetzt sie,
solange Sie die Handler-Id halten; das Herunterfahren des Plugins zieht
alles noch Gehaltene zurück.
document_settings¶
Schreibgeschützte, dokumentbezogene Einstellungen, die die .editorconfig
eines Projekts über Ihre ctx.settings legen. Für Schlüssel k und
Dokument doc löst ein Lesezugriff mte.<plugin.id>.<k> aus der für dieses
Dokument geltenden .editorconfig auf, dann ctx.settings, dann Ihren
Standard:
wrap = ctx.document_settings.get_bool(doc, "wrap", False)
width = ctx.document_settings.get_int(doc, "width", 80)
ratio = ctx.document_settings.get_double(doc, "ratio", 1.0)
name = ctx.document_settings.get_string(doc, "profile", "")
ctx.document_settings.contains(doc, "wrap") # set in either layer?
Schreibzugriffe laufen über ctx.settings — nie zurück in die
.editorconfig.
diagnostics¶
Wellenlinien und Rand-Glyphen für ein Dokument pushen — aus einem Linter,
einem Sprachserver, einem Build-Log. Der Host rendert sie; Sie berühren die
Ansicht nie. Veröffentlichen ersetzt alles, was Sie zuvor für das Paar
(Dokument, source) veröffentlicht haben — senden Sie also jedes Mal die
vollständige Menge:
ctx.diagnostics.publish(doc, "mylinter", [
mte.Diagnostic(start=12, end=18, message="unused variable",
severity=mte.SEVERITY_WARNING, code="W0612"),
])
ctx.diagnostics.publish(doc, "mylinter", []) # retract only your own
ctx.diagnostics.clear(doc) # clears EVERY producer's set on doc
source ist Pflicht — es hält Ihre Menge von denen anderer Erzeuger
(LSP-Plugin, Rechtschreibprüfung) auf demselben Dokument getrennt.
Schweregrade: SEVERITY_ERROR / SEVERITY_WARNING / SEVERITY_INFORMATION
/ SEVERITY_HINT. Offsets sind halboffene Byte-Bereiche [start, end).
theme¶
Registrieren Sie die Farben, mit denen Ihre eigene UI malt, als benannte, theme-fähige Kategorien; der Theme-Editor listet sie automatisch, und die Überschreibungen des Nutzers wandern in die Theme-Datei:
h = ctx.theme.register_category("com.example.my/match", "Match highlight",
default_light=0xFFF3B0, default_dark=0x8A6D00)
rgb = ctx.theme.resolve_color("com.example.my/match") # 0xRRGGBB int
themed = ctx.theme.has_override("com.example.my/match")
variant = ctx.theme.active_variant() # "light" / "dark" / ""
cats = ctx.theme.categories() # list[mte.ThemeCategory]
ctx.theme.unregister_category(h)
Lesen Sie resolve_color bei ctx.events.on_theme_changed neu.
compare¶
Öffnet (oder fokussiert) die Vergleichsansicht des Editors über zwei
Quellen. Jede Seite ist eine Datei auf der Platte (path=), wörtlicher Text
(content=) oder beides (gezeigt wird der Inhalt, der Pfad gibt der
Sitzungspersistenz eine wiederherstellbare Identität):
shown = ctx.compare.open(
mte.CompareSource("before", path="/tmp/a.txt"),
mte.CompareSource("after", content=new_text),
ignore_whitespace=True) # ignore_case / ignore_eol too
Liefert False, wenn eine Seite nicht gelesen werden kann.
settings¶
Persistenter typisierter Schlüssel/Wert-Speicher, hostseitig auf
plugins/<your.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
settings.changed-Abo (ctx.events.on_settings_changed; siehe
Ereignis-Referenz).
app_settings¶
Nur-Lese-Sicht auf die editorweite Konfiguration. Die bekannten
Schlüsselnamen sind als Konstanten in mte.keys exportiert (Spiegel des
C++-Headers PluginApi/AppSettingsKeys.h, der die autoritative Liste
bleibt):
import mte
size = ctx.app_settings.get(mte.keys.EDITOR_FONT_SIZE, mte.keys.DEFAULT_EDITOR_FONT_SIZE)
family = ctx.app_settings.get(mte.keys.EDITOR_FONT_FAMILY, "") # "" = host default
theme = ctx.app_settings.get(mte.keys.UI_THEME, mte.keys.DEFAULT_UI_THEME) # "system"/"light"/"dark"
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, proportional_size=0.0, tab_group="") -> 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. Übergeben SieFalsefür ein Panel, das beim Start nicht aufspringen soll, und zeigen Sie es später mitset_panel_visible.proportional_size(float) — die Standardgröße des Panels als Anteil am Hauptfenster: seiner Breite in den Bereichen"left"/"right", seiner Höhe in"top"/"bottom".0.0(der Standard) behält das Pixelverhalten, das ein Werkzeug-Panel will — das Panel hält seine Größe, und eine Fenstergrößenänderung lässt es in Ruhe. Ein Bruch wie0.4ist, was eine Vorschau will: das Maximieren des Fensters macht das Panel mit ihm breiter. Ihnen gehört nur der Standard; sobald der Nutzer den Trenner des Panels zieht, übernimmt und merkt der Host seinen Anteil. Werte außerhalb des Bereichs werden geklemmt, und der Host behält für Panel und Editor eine Mindestausdehnung. Die vollständigen Regeln stehen unterproportionalSize.tab_group(str) — Panels mit derselben nicht-leeren Gruppen-ID in derselbenareateilen sich ein Dock als Tabs, statt nebeneinander zu liegen. Geben Sie ihr einen Namensraum wie einer Panel-ID ("renamer/panels"). Nur die Standardanordnung: Ein vom Benutzer gespeichertes Layout hat Vorrang. SiehetabGroup.
ctx.docks.is_panel_visible(handler_id) -> bool¶
Ob ein mit add_panel hinzugefügtes Panel geöffnet ist — ein Panel, das
als Tab hinter einem anderen liegt, zählt als geöffnet; eines, das mit
initially_visible=False registriert und nie gezeigt wurde, ein geschlossenes
und eine unbekannte ID nicht. Kombinieren Sie es mit set_panel_visible, um
Ihre Panels für eine Aufgabe zu zeigen und danach wiederherzustellen, was der
Benutzer hatte:
was_open = ctx.docks.is_panel_visible(panel)
ctx.docks.set_panel_visible(panel, True)
# ... when the task ends:
ctx.docks.set_panel_visible(panel, was_open)
ctx.docks.set_panel_visible(handler_id, visible)¶
Zeigt oder verbirgt ein mit add_panel hinzugefügtes Panel. Anzeigen hebt
das Panel auch über die Docks, mit denen es sich einen Bereich teilt, sodass
ein Befehl, der sein Panel „öffnet", es wirklich vor den Nutzer bringt.
Idempotent — denselben Wert beliebig wiederholen, kein Zustand zu
verfolgen; unbekannte Ids werden still ignoriert.
panel = ctx.docks.add_panel(id="mine.panel", title="Mine", form=form,
initially_visible=False)
@ctx.commands.command("mine.show", title="Show My Panel")
def _show():
ctx.docks.set_panel_visible(panel, True)
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.
line_markers¶
Randsymbole und Tönungen ganzer Zeilen, die an Dokumentzeilen hängen und den Bearbeitungen des Benutzers folgen — Haltepunkte, ein Pfeil für die aktuelle Anweisung, Änderungsstreifen — sowie der Anspruch auf den Klick in die Lesezeichenspalte. Der native Dienst und alle seine Regeln sind unter Zeilenmarkierungen & Randklick beschrieben.
bp_colour = ctx.theme.register_category("myplugin.breakpoint", "Breakpoint",
default_light=0xCC0000,
default_dark=0xFF5555)
bp = ctx.line_markers.register_kind(glyph=mte.GLYPH_CIRCLE,
glyph_color="myplugin.breakpoint",
line_tint="", z_order=50)
marker = ctx.line_markers.add_marker(bp, doc, line) # 0 = refused
ctx.line_markers.line_of(marker) # where it is NOW; -1 = gone
ctx.line_markers.markers(bp, doc) # [mte.LineMarkerPlacement(id, line)]
ctx.line_markers.remove_marker(marker)
ctx.line_markers.clear_markers(bp, doc) # doc 0 = every document
ctx.line_markers.release(bp) # removes the kind and its markers
register_kind(glyph=, glyph_color=, line_tint=, z_order=)—glyphist einer vonmte.GLYPH_NONE(nur Tönung),GLYPH_CIRCLE,GLYPH_CIRCLE_HOLLOW,GLYPH_CIRCLE_BAR,GLYPH_ARROW,GLYPH_STRIP; die Farben sind IDs von Kategorien, die Sie mitctx.themeregistriert haben. Der Editor hat für alle Plugins zusammen nur wenige Markierungs-Slots, daher kann dies fehlschlagen — dann wirdRpcErrorausgelöst. Registrieren Sie Ihre Arten einmal, inregister().add_marker(kind, doc, line)— 0-basierte Zeile, nicht begrenzt: Eine Zeile außerhalb des Dokuments liefert 0 und setzt nichts.- Markierungen wandern mit ihren Zeilen, überstehen ein Neuladen auf
derselben Zeilennummer und verschwinden mit ihrem Dokument — fügen Sie Ihre
in
ctx.events.on_document_openederneut hinzu.
Der Randklick¶
@ctx.events.on_margin_clicked
def _clicked(ev): # mte.MarginClicked
if ev.modifiers & mte.MODIFIER_CONTROL:
edit_condition(ev.document, ev.line)
else:
toggle_breakpoint(ev.document, ev.line)
claim = ctx.line_markers.claim_margin_click()
Jeder Linksklick in die Lesezeichenspalte wird als margin.clicked
veröffentlicht (document, line, margin = "symbol", modifiers — Bits
mte.MODIFIER_SHIFT / MODIFIER_CONTROL / MODIFIER_ALT /
MODIFIER_META). Standardmäßig schaltet der Editor dabei auch ein
Lesezeichen um; solange irgendein Plugin claim_margin_click() hält, tut er
das nicht und überlässt den Klick den Plugins.
ctx.line_markers.release(claim) gibt den Klick zurück.
settings_page¶
Fügt eine Seite im Einstellungen-Dialog des Editors hinzu. Dasselbe
Formular-Vokabular wie bei Dock-Panels; dieselbe
binds="settings:<key>"-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:<key>" 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:<key>" |
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.