Vai al contenuto

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

ctx.commands.command(*, id, title, shortcut="", category="", scope="")

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 in plugin.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". Ctrl si mappa automaticamente sul modificatore primario della piattaforma (Cmd su 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; leggi ctx.commands.effective_shortcut(id) mentre costruisci i tuoi widget e riapplicala sull'evento "command.shortcutChanged".
ctx.commands.effective_shortcut(command_id) -> str

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.


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)

ctx.menus.add_item("Plugins/Word Count/Count", "wordcount.count")
  • 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)

ctx.menus.add_separator("Plugins/Word Count")

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:

[python:org.example.wordcount] counted 42 words

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

ctx.status.show(message, timeout_ms=0, level="info")

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; 0 lascia 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("saved", timeout_ms=1500)
ctx.status.show("no results", level="warning")

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

path = ctx.data_dir()                    # pathlib.Path

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 da ctx.ui.get / ctx.ui.set per indirizzare i controlli dentro questo pannello. Se non ne fornisci uno, l'host alloca un id py-dock/N e 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 di mte.ui.
  • initially_visible (bool) — se il pannello viene mostrato al caricamento del plugin. Predefinito True. Passa False per un pannello che non deve comparire all'avvio e mostralo più tardi con set_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 come 0.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 sotto proportionalSize.
  • tab_group (str) — i pannelli con lo stesso id di gruppo non vuoto nella stessa area condividono 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. Vedi tabGroup.

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 tra mte.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 con ctx.theme. L'editor ha solo pochi posti per i marcatori per tutti i plugin insieme, quindi questa chiamata può fallire: in quel caso solleva RpcError. Registra i tuoi tipi una volta sola, in register().
  • 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 di mte.ui.
  • scope (str, facoltativo) — ambito dell'evento emesso su SettingsChanged dopo 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.