ctx — il contesto del plugin¶
register(ctx) riceve un solo argomento: un PluginContext. Espone i servizi
qui sotto (tutta la superficie di IPluginContext compatibile con il modello
fuori processo). Ciascuno è una piccola classe Python che il worker costruisce
sopra il canale RPC; i sorgenti sono 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
Due parti del contratto nativo non hanno ancora un equivalente Python: i fornitori di hover (quindi le fasce di priorità dell'hover non si applicano ai plugin Python) e i fornitori del menu contestuale dell'editor (quindi nemmeno l'area testo / margine del menu contestuale). Entrambi rispondono mentre l'utente aspetta, il che richiede lo stesso trattamento con scadenza che hanno i fornitori di completamento prima che un andata e ritorno verso il worker sia sicuro.
Non conservare ctx in una variabile globale contando di usarlo più tardi:
anche se gli attributi sono stabili per tutta la vita del plugin, tratta ctx
come legato alla singola registrazione. Conserva i riferimenti ai singoli servizi
se ti servono (per esempio log = ctx.log).
commands¶
Decoratore che registra un comando e vi lega come gestore il callable avvolto.
id(str, obbligatorio): identificatore univoco in stile DNS inverso (per esempio"wordcount.count"). Stesse regole che inplugin.json: deve essere univoco a livello globale.title(str, obbligatorio): etichetta leggibile mostrata ovunque il comando affiori (menu, tavolozza dei comandi).shortcut(str, facoltativo): stringa della scorciatoia predefinita portabile, per esempio"Ctrl+Alt+W".Ctrlsi mappa automaticamente sul modificatore primario della piattaforma (Cmdsu macOS). Lascia vuoto per nessuna scorciatoia. L'utente può riassegnarla o annullarla in Preferenze ▸ Scorciatoie; per i comandi legati a un menu è l'host ad applicare la riassegnazione per te.category(str, facoltativo): etichetta di raggruppamento a forma libera, mostrata come categoria del comando nella Mappatura delle scorciatoie.scope(str, facoltativo):""/"application"(predefinito — l'host associa la scorciatoia all'azione di menu del comando) oppure"pluginWindow"per gli accordi che vivono in un'interfaccia posseduta dal plugin: l'host si limita a elencare il comando nella mappatura; leggictx.commands.effective_shortcut(id)mentre costruisci i tuoi widget e riapplicala sull'evento"command.shortcutChanged".
La scorciatoia attualmente in vigore dopo la riassegnazione da parte dell'utente
e la risoluzione dei conflitti ("" = non associata o sconosciuta). È sicuro
chiamarla dai gestori.
La funzione avvolta non prende argomenti e non restituisce nulla. Le eccezioni al suo interno vengono catturate dal worker e registrate su stderr; non fanno andare in crash l'editor.
@ctx.commands.command(id="wordcount.count",
title="Count Words",
shortcut="Ctrl+Alt+W",
category="Text")
def _count():
ctx.status.show("counted")
Sotto il cofano: il lato Python alloca localmente un handlerId, registra la
callback sulla connessione RPC e lancia commands.registerCommand verso l'host.
Quando l'utente attiva il comando (clic nel menu, scorciatoia, tavolozza), l'host
invia una RPC callback e la tua funzione viene eseguita.
Gli id dei comandi valgono per l'intero processo, non per singolo plugin.
Anteponi ai tuoi id il tuo spazio dei nomi in DNS inverso (wordcount.…) per
evitare collisioni.
menus¶
I percorsi di menu usano / come separatore. Il segmento più a sinistra è un
menu di primo livello (Plugins, Tools, …); i segmenti intermedi creano
sottomenu su richiesta; il segmento finale è l'etichetta della voce.
ctx.menus.add_item(path, command_id)¶
path(str):"Top/Sub/…/Label". I sottomenu intermedi vengono creati se non esistono.command_id(str): id di un comando registrato in precedenza. Cliccare la voce chiama il comando.
Restituisce una stringa handlerId opaca. Conservala se in seguito ti servirà
fare unsubscribe della voce (raro nella fase 1).
Il segmento più a sinistra ("Plugins" qui sopra) deve corrispondere al title
di una delle tue voci menus[] in plugin.json, così che la collocazione nella
barra dei menu venga raccolta.
ctx.menus.add_separator(path)¶
Inserisce un separatore in un sottomenu esistente.
ctx.menus.set_item_checked(handler_id, checked)¶
Trasforma la voce aggiunta in precedenza in una voce di menu spuntabile e ne imposta lo stato: è l'idioma standard per un interruttore acceso/spento persistente che mostra un segno di spunta visibile.
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)
La prima chiamata promuove la voce a spuntabile; le chiamate successive si
limitano a ribaltare il segno. Passare un handler_id che l'host non conosce è
un'operazione nulla e silenziosa (sicura da chiamare dopo che il tuo token è
stato lasciato cadere, per esempio da una callback ritardata che scatta durante lo
spegnimento).
ctx.menus.set_item_enabled(handler_id, enabled)¶
Abilita o disattiva una voce aggiunta in precedenza. Usala quando il comando ha bisogno di una precondizione — qualcosa da chiudere, qualcosa da ripristinare — così che il menu mostri che non è disponibile invece di offrire una voce che non fa nulla in silenzio.
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)
Una voce disabilitata resta visibile ma non può essere attivata, né con un clic
né con la sua scorciatoia. La chiamata è idempotente, quindi non devi mai
ricordare lo stato precedente, e un handler_id sconosciuto è un'operazione nulla
e silenziosa come sopra. Le voci nascono abilitate: una sola chiamata subito dopo
add_item predispone una voce non disponibile all'avvio.
events¶
Sottoscrivi gli eventi dell'editor. Due API equivalenti: i decoratori zuccherini
sono consigliati; il subscribe() grezzo è disponibile se vuoi calcolare
l'argomento a tempo di esecuzione.
Decoratori zuccherini¶
@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}")
La funzione avvolta prende un argomento: una dataclass tipizzata corrispondente all'argomento dell'evento. Vedi la Documentazione degli eventi per la tabella completa argomento ↔ dataclass.
API grezza¶
handler_id = ctx.events.subscribe("document.saved", _saved)
# later:
ctx.events.unsubscribe(handler_id)
Proprietà zuccherine disponibili (tutte decoratori):
| Proprietà | Scatta su |
|---|---|
on_document_opened |
Documento aperto in una scheda |
on_document_closed |
Documento chiuso |
on_document_saved |
Documento scritto su disco |
on_selection_changed |
Cursore / selezione spostati nel documento attivo |
on_active_document_changed |
Scheda in primo piano cambiata |
on_document_modified_changed |
Il contrassegno pulito/modificato si è ribaltato (modified: bool) |
on_text_changed |
Ogni modifica (position, added_bytes, removed_bytes) — mantienilo banale |
on_settings_changed |
Preferenze applicate (scope: "" tutto l'editor, "plugins/<id>") |
on_theme_changed |
Tema cambiato o modificato (variant) — rileggi i colori di ctx.theme |
on_workspace_root_changed |
Cartella dell'area di lavoro aperta / chiusa (root) |
on_workspace_files_changed |
Insieme dei file dell'area di lavoro cambiato (files) |
on_host_window_shown |
Una volta per esecuzione, dopo il primo fotogramma — pianifica qui il lavoro lento di avvio |
on_app_about_to_quit |
L'editor si sta chiudendo — ultima occasione per agire |
on_command_shortcut_changed |
La scorciatoia effettiva di un comando è cambiata (riassegnazione dell'utente / importazione di una mappa di tasti / risoluzione di un conflitto); contenuto: command_id, sequence ("" = non associata) |
I nomi di argomento sconosciuti sollevano ValueError al momento della
sottoscrizione. I sottoscrittori possono essere eseguiti dopo il gestore
nativo corrispondente; tratta la consegna degli eventi come informativa, non come
autorevole.
Pubblicazione¶
ctx.events.publish(topic, payload) trasmette allo stesso modo ai sottoscrittori
nativi e a quelli Python — è accettata solo per i due argomenti dell'area di
lavoro (workspace.rootChanged, workspace.filesChanged); qualsiasi altro
solleva ValueError. Vedi la
Documentazione degli eventi.
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")
Ogni livello si mappa sull'ILogger dell'host. Le righe sono precedute da
[python:<plugin.id>], così il tuo output è rintracciabile nel log diagnostico
dell'editor:
Non usare mai print() da un plugin: lo stdout del worker è il canale RPC, e
scriverci corrompe il protocollo. ctx.log.* è l'unico modo ammesso di emettere
testo diagnostico.
status¶
Invia una notifica temporanea alla barra di stato.
message(str): che cosa mostrare. Solo stringhe brevi: la barra di stato ha una larghezza fissa.timeout_ms(int, predefinito 0): per quanto tenerla sullo schermo;0lascia che l'host scelga un valore sensato (qualche secondo).level(str, predefinito"info"):"info"/"warning"/"error". I livelli più alti possono trattenere un po' più a lungo il minimo di permanenza sullo schermo, così che un errore non venga cancellato all'istante da un messaggio informativo di routine.
ctx.status.show è mordi-e-fuggi. Se l'host non ha una barra di stato visibile
(build headless / minimale), la chiamata viene scartata in silenzio.
ctx.status.clear() cancella qualunque notifica temporanea sia sullo schermo.
Segmenti persistenti¶
Per lo stato dal vivo (un conteggio di parole, lo stato di un server) usa un segmento che possiedi e aggiorni: non calpesta mai una notifica, e viceversa:
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 ordina i segmenti dei plugin da sinistra a destra (più basso = più a
sinistra; a pari merito si mantiene l'ordine di registrazione). Lo spegnimento
del plugin rimuove tutto ciò che è ancora trattenuto.
editor¶
Accesso sincrono completo ai documenti attivi. Ogni metodo blocca il thread
chiamante finché l'host non risponde (di norma microsecondi, quando il worker e
l'editor condividono una macchina). Lecito da qualsiasi gestore di comando o di
evento; mai da una callback async def (vedi la nota sui thread in cima a
questa pagina).
Enumerazione dei documenti¶
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
Accesso al buffer¶
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 evita di trascinare l'intero buffer attraverso l'RPC quando ti serve
solo una fetta: preferiscilo per ordinamenti e trasformazioni su file grandi.
Selezione¶
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?
Cursore¶
pos = ctx.editor.caret(doc) # mte.EditorPosition(line, column)
ctx.editor.set_caret(doc, mte.EditorPosition(line=7, column=3))
Operazioni sui file¶
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 è l'unica chiamata per «portare l'utente lì» (un errore di
compilazione, un frame dello stack, un risultato di ricerca): apre il file o
ne attiva la scheda, posiziona il cursore senza selezione, espande una
piegatura che nasconde la riga, scorre la riga fino a renderla visibile —
centrata se era fuori dallo schermo — e dà il focus all'editor. Riga e
colonna partono da 0 (la colonna in byte UTF-8) e vengono limitate, mai
rifiutate; restituisce l'id del documento, o 0 se il file non si è potuto
aprire, e in quel caso non si è mosso nulla. reveal_line mostra soltanto una
riga: non sposta il cursore, non cambia scheda e non prende il focus.
Segnalibri, segni, evidenziazione degli URL¶
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
Ricerca¶
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". Vedi
mte.SearchOptions per l'elenco completo dei campi.
Ricerca incrementale (il modello cresce mentre l'utente digita, a partire da un'ancora fissata):
ctx.editor.set_incremental_search_anchor(doc)
hit = ctx.editor.incremental_search(doc, "TOD", opts) # MatchRange
Rientro e sintassi¶
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
Vista divisa / gruppi di schede¶
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
Un documento mostrato in due riquadri è un unico documento con un solo id,
quindi group_of() può nominare solo una delle sue sedi. groups_of() le
riporta tutte, e show_document_in_group() crea una seconda vista condivisa —
la stessa cosa che fa Visualizza > Dividi il documento a destra — quindi non
viene sollevato alcun evento document_opened.
ctx.editor rispecchia ormai uno a uno l'IEditorService nativo.
languages¶
La colorazione della sintassi è una funzionalità del nucleo: l'host rileva il
linguaggio di un documento e lo colora senza alcun plugin. ctx.languages ti
permette di partecipare: elencare ciò che l'host conosce, leggere o scavalcare il
linguaggio di un documento, rieseguire il rilevamento e contribuire intere
definizioni di linguaggio.
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 accetta la stessa forma di un file colorizers/*.json distribuito (o
un intero dizionario definition=): lexer nomina un lexer di backend esistente
(l'host non acquisisce nuovi lexer a tempo di esecuzione), styles mappa i nomi
di stile di quel lexer su categorie semantiche, e keywords indirizza i suoi
elenchi di parole con la descrizione di slot propria del lexer — l'host risolve
entrambi esattamente come fa per un file distribuito, quindi non devi mai
scrivere per esteso gli id del motore. Registrare un id già esistente lo
sostituisce per tutto il tempo in cui trattieni l'id del gestore; lo spegnimento
del plugin ritira tutto ciò che è ancora trattenuto.
document_settings¶
Impostazioni in sola lettura, con ambito di documento, che stratificano
l'.editorconfig di un progetto sopra il tuo ctx.settings. Per la chiave k e
il documento doc, una lettura risolve mte.<plugin.id>.<k> dall'.editorconfig
che si applica a quel documento, poi ripiega su ctx.settings e infine sul tuo
valore predefinito:
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?
Le scritture passano da ctx.settings: mai di ritorno in .editorconfig.
diagnostics¶
Spingi sottolineature ondulate e glifi nel margine per un documento — da un
analizzatore statico, da un language server, da un log di compilazione.
L'host li disegna; tu non tocchi mai la vista. Pubblicare sostituisce tutto ciò
che avevi pubblicato in precedenza per quella coppia (documento, sorgente),
quindi invia ogni volta l'insieme completo:
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 è obbligatorio: è ciò che tiene il tuo insieme separato da quello degli
altri produttori (il plugin LSP, il correttore ortografico) sullo stesso
documento. Gravità: SEVERITY_ERROR / SEVERITY_WARNING /
SEVERITY_INFORMATION / SEVERITY_HINT. Gli scostamenti sono intervalli di byte
semiaperti [start, end).
theme¶
Registra i colori che la tua interfaccia disegna come categorie con nome e tematizzabili; l'Editor dei temi le elenca automaticamente e le sostituzioni dell'utente fanno andata e ritorno nel file del tema:
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)
Rileggi resolve_color su ctx.events.on_theme_changed.
compare¶
Apre (o porta in primo piano) la vista di confronto affiancata dell'editor su due
sorgenti. Ogni lato è un file su disco (path=), testo letterale (content=) o
entrambi (viene mostrato il contenuto, mentre il percorso dà alla persistenza di
sessione un'identità ripristinabile):
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
Restituisce False quando un lato non può essere letto.
settings¶
Archivio chiave/valore tipizzato e persistente, circoscritto a
plugins/<your.id>/ sull'host. Tutti i metodi sono sincroni; get e set deducono
il tipo da quello Python del valore predefinito.
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
Tipi:
Tipo Python di default |
Tipo sul filo | Tipo restituito da get() |
|---|---|---|
bool |
bool |
bool |
int (non booleano) |
int |
int |
float |
double |
float |
str (o None) |
string |
str |
Inoltre:
ctx.settings.contains("wrap") # bool
ctx.settings.remove("stale_key")
ctx.settings.sync() # optional; host also syncs on shutdown
Reagisci ai cambiamenti fatti nella finestra delle Preferenze sottoscrivendo
settings.changed (ctx.events.on_settings_changed; vedi la
Documentazione degli eventi).
app_settings¶
Vista in sola lettura della configurazione globale dell'editor. I nomi di chiave
noti sono esportati come costanti in mte.keys (rispecchiando l'intestazione C++
PluginApi/AppSettingsKeys.h, che resta l'elenco autorevole):
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"
I setter, remove() e sync() su ctx.app_settings sollevano RuntimeError:
la vista è intenzionalmente in sola lettura.
completions¶
Registra un fornitore di completamenti che partecipa alla finestra a comparsa dell'editor:
@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)]
Il callable avvolto viene invocato a ogni battuta (o su Ctrl+Space) il cui
documento corrisponda al filtro di linguaggio. Campi di req:
| Campo | Tipo | Significato |
|---|---|---|
document |
int |
Documento in corso di modifica |
prefix |
str |
Frammento di parola immediatamente prima del cursore |
position |
int |
Scostamento in byte del cursore |
language_id |
str |
Id del colorizzatore ("python", "cpp", …) |
manual |
bool |
True quando l'utente ha premuto Ctrl+Space; False per l'attivazione automatica |
Restituisci un elenco di mte.CompletionItem (oppure semplici stringhe, oppure
dizionari con {text, detail, kind}). Tipi: mte.KIND_KEYWORD / KIND_WORD /
KIND_SYMBOL / KIND_SNIPPET / KIND_UNKNOWN.
Scadenza
L'host invia al tuo fornitore una richiesta legata alla battuta con una scadenza di ~30 ms. Se la tua callback la manca, la finestra a comparsa si mostra senza i tuoi candidati per quella battuta. Costruisci in background qualsiasi indice costoso e rispondi dalla cache: non fare mai I/O nella callback. Il completamento in rete in stile LSP appartiene a una futura superficie asincrona, non a questa.
languages=[] (o l'assenza del parametro) significa «ogni documento». Sono
ammessi più fornitori per plugin; ogni decoratore registra il proprio.
data_dir¶
Restituisce la cartella scrivibile per plugin che l'host ha già creato per te.
Usala per cache, indici e qualsiasi dato che ti appartenga.
ctx.diagnostics_dir() restituisce analogamente la cartella condivisa dei crash
e dei log (per te in sola lettura: vi scrive il segnalatore di arresti anomali).
docks¶
Pannelli ancorabili dichiarativi: un'interfaccia laterale costruita da una
specifica di modulo, non da widget Qt. Vedi mte.ui per il
vocabolario dei costruttori.
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¶
Registra un pannello ancorabile. Restituisce l'id del pannello (allocato
automaticamente quando id è None).
id(str, facoltativo) — identificatore stabile usato dactx.ui.get / ctx.ui.setper indirizzare i controlli dentro questo pannello. Se non ne fornisci uno, l'host alloca un idpy-dock/Ne lo restituisce.title(str) — mostrato nell'intestazione del pannello.area(str) —"left"/"right"/"top"/"bottom". I valori sconosciuti ripiegano su"right".form(dict) — una specifica costruita con i costruttori dimte.ui.initially_visible(bool) — se il pannello viene mostrato al caricamento del plugin. PredefinitoTrue. PassaFalseper un pannello che non deve comparire all'avvio e mostralo più tardi conset_panel_visible.proportional_size(float) — la dimensione predefinita del pannello come quota della finestra principale: della sua larghezza nelle aree"left"/"right", della sua altezza in"top"/"bottom".0.0(il valore predefinito) conserva il comportamento in pixel che vuole un pannello strumento: il pannello mantiene la sua dimensione e ridimensionare la finestra non lo tocca. Una frazione come0.4è ciò che vuole un'anteprima: ingrandire la finestra allarga il pannello con essa. Solo il valore predefinito è tuo; appena l'utente trascina il separatore del pannello, l'host adotta e ricorda la sua quota. I valori fuori intervallo vengono limitati, e l'host conserva un'estensione minima sia per il pannello sia per l'editor. Le regole complete sono sottoproportionalSize.tab_group(str) — i pannelli con lo stesso id di gruppo non vuoto nella stessaareacondividono un unico pannello ancorato, a schede, invece di stare uno accanto all'altro. Dagli uno spazio dei nomi come a un id di pannello ("renamer/panels"). È solo la disposizione predefinita: un layout salvato dall'utente ha la precedenza. VeditabGroup.
ctx.docks.is_panel_visible(handler_id) -> bool¶
Se un pannello aggiunto con add_panel è aperto: un pannello in una
scheda dietro un altro conta come aperto; uno registrato con
initially_visible=False e mai mostrato, uno chiuso e un id sconosciuto no.
Abbinalo a set_panel_visible per mostrare i tuoi pannelli durante
un'attività e poi rimettere ciò che l'utente aveva:
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)¶
Mostra o nasconde un pannello aggiunto con add_panel. Mostrarlo lo porta anche
davanti ai pannelli con cui condivide un'area, così che un comando che «apre» il
proprio pannello lo metta davvero davanti all'utente. È idempotente — ripeti pure
lo stesso valore, non c'è stato da tracciare — e viene ignorato in silenzio per un
id sconosciuto.
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¶
Alloca un id di gestore per collegare il campo on_change di un controllo a un
callable Python. La callback riceve il nuovo valore come stringa; i controlli
booleani consegnano "true" / "false", quelli numerici consegnano l'intero come
stringa.
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)])
Gli id dei gestori di on_change sono preceduti da py-dock-cb/N e non
collidono mai con gli id dei gestori di comandi / eventi / completamenti.
La callback viene invocata sul ciclo degli eventi del worker, NON sul pool di
thread: mantienila veloce. Usa ctx.log, modifica un piccolo stato in memoria o
lancia un comando successivo; non fare qui lavoro pesante.
line_markers¶
Glifi nel margine e tinte di riga intera agganciati alle righe del documento, che seguono le modifiche dell'utente — punti di interruzione, una freccia dell'istruzione corrente, strisce delle modifiche — e la rivendicazione del clic nella colonna dei segnalibri. Il servizio nativo e tutte le sue regole sono descritti in Marcatori di riga e clic sul margine.
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=)—glyphè uno tramte.GLYPH_NONE(solo tinta),GLYPH_CIRCLE,GLYPH_CIRCLE_HOLLOW,GLYPH_CIRCLE_BAR,GLYPH_ARROW,GLYPH_STRIP; i colori sono id di categorie che hai registrato conctx.theme. L'editor ha solo pochi posti per i marcatori per tutti i plugin insieme, quindi questa chiamata può fallire: in quel caso sollevaRpcError. Registra i tuoi tipi una volta sola, inregister().add_marker(kind, doc, line)— riga a partire da 0, non limitata: una riga fuori dal documento restituisce 0 e non posiziona nulla.- I marcatori si spostano con le loro righe, sopravvivono a un ricaricamento
sullo stesso numero di riga e spariscono con il loro documento: aggiungi di
nuovo i tuoi in
ctx.events.on_document_opened.
Il clic sul margine¶
@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()
Ogni clic sinistro nella colonna dei segnalibri viene pubblicato come
margin.clicked (document, line, margin = "symbol", modifiers —
bit mte.MODIFIER_SHIFT / MODIFIER_CONTROL / MODIFIER_ALT /
MODIFIER_META). Per impostazione predefinita l'editor a quel clic attiva o
disattiva anche un segnalibro; finché un plugin detiene
claim_margin_click(), non lo fa e lascia il clic ai plugin.
ctx.line_markers.release(claim) restituisce il clic.
settings_page¶
Aggiunge una pagina sotto la finestra delle Preferenze dell'editor. Stesso
vocabolario di specifiche di modulo dei pannelli ancorabili; stessa persistenza
con binds="settings:<key>".
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, obbligatorio) — percorso separato da barre nell'albero delle Preferenze, per esempio"Plugins/My Plugin". Le categorie annidate arrivano come nodi dell'albero.title(str, obbligatorio) — intestazione mostrata sopra la pagina.form(dict, obbligatorio) — una specifica costruita con i costruttori dimte.ui.scope(str, facoltativo) — ambito dell'evento emesso suSettingsChangeddopo che l'utente ha premuto OK / Applica.
Restituisce l'id del gestore.
Annulla NON ripristina le modifiche
I controlli con binds="settings:<key>" si rendono persistenti con
impazienza mentre l'utente modifica, quindi premere Annulla nella finestra
delle Preferenze non le annulla. È un limite noto della fase 3; un
perfezionamento futuro avvolgerà le impostazioni sottostanti in un proxy con
buffer.
ui¶
Legge e scrive i valori dei controlli dentro i pannelli che hai già registrato. Sul filo i valori sono sempre stringhe.
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]¶
Legge il valore corrente di un controllo. Restituisce None quando il pannello o
il controllo sono sconosciuti (non è un errore: tratta sia None sia "" come
«vuoto»).
- Label / Button / LineEdit / ComboBox — restituisce il testo.
- Checkbox — restituisce
"true"o"false". - NumberSpin — restituisce l'intero come stringa decimale.
- List — restituisce il testo della voce attualmente selezionata (vuoto quando non è selezionato nulla).
ctx.ui.set(panel_id, control_id, value) -> bool¶
Scrive un valore in un controllo in modo programmatico. Restituisce True quando
la scrittura è riuscita, False quando il pannello o il controllo sono
sconosciuti oppure il valore non può essere convertito (per esempio un valore non
intero inviato a un NumberSpin).
I booleani Python True / False vengono convertiti in "true" / "false"
prima dell'invio; tutto il resto passa da str().
Un set pilotato dall'host NON fa scattare di nuovo la callback on_change del
controllo, così puoi aggiornare i tuoi widget da un gestore di comando senza
cicli infiniti.
Riepilogo dei collegamenti a tempo di esecuzione (fase 3)¶
| Campo della specifica di modulo | Comportamento a tempo di esecuzione |
|---|---|
id="…" |
Indirizza il controllo da ctx.ui.get / ctx.ui.set. |
binds="settings:<key>" |
Legge il valore iniziale da ctx.settings; riscrive a ogni modifica dell'utente. |
on_click="<cmd>" |
I pulsanti invocano al clic l'id di comando registrato. |
on_change=handler |
Fa scattare alla modifica dell'utente la callback registrata tramite ctx.docks.on_change(...). |
Le stesse regole valgono per i pannelli ancorabili e per le pagine delle Preferenze.