ctx — el contexto del plugin¶
register(ctx) recibe un argumento: un PluginContext. Expone los servicios de
abajo (toda la superficie de IPluginContext que encaja en el modelo fuera de
proceso). Cada uno es una pequeña clase de Python que el trabajador construye
sobre el canal RPC; el código fuente está en 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
Dos partes del contrato nativo aún no tienen equivalente en Python: los proveedores de hover (así que las bandas de prioridad del hover no se aplican a los plugins Python) y los proveedores del menú contextual del editor (así que tampoco la zona de texto / margen del menú contextual). Ambos se responden mientras el usuario espera, lo que requiere el mismo tratamiento de plazos que tienen los proveedores de autocompletado antes de que un viaje de ida y vuelta al trabajador sea seguro.
No guardes ctx en una variable global pensando en usarlo después: aunque los
atributos son estables durante la vida del plugin, trata ctx como algo propio
del registro. Guarda referencias a los servicios individuales si los necesitas
(log = ctx.log, por ejemplo).
commands¶
Decorador que registra un comando y enlaza el invocable envuelto como su manejador.
id(str, obligatorio): identificador único al estilo DNS inverso (p. ej."wordcount.count"). Las mismas reglas que enplugin.json: debe ser único a nivel global.title(str, obligatorio): etiqueta legible que se muestra allí donde aparezca el comando (menús, paleta de comandos).shortcut(str, opcional): cadena de atajo predeterminado portátil, p. ej."Ctrl+Alt+W".Ctrlse asigna automáticamente al modificador principal de la plataforma (Cmden macOS). Déjalo vacío para no poner ninguno. El usuario puede reasignarlo o desasignarlo en Preferencias ▸ Atajos de teclado; para los comandos ligados a menús, el anfitrión aplica la reasignación por ti.category(str, opcional): etiqueta de agrupación libre; se muestra como la categoría del comando en la Asignación de atajos.scope(str, opcional):""/"application"(lo predeterminado: el anfitrión asigna el atajo en la acción de menú del comando) o"pluginWindow"para combinaciones que viven en interfaces propiedad del plugin, en cuyo caso el anfitrión solo lista el comando en la herramienta de asignación; leectx.commands.effective_shortcut(id)al construir tus widgets y vuelve a aplicarlo con el evento"command.shortcutChanged".
El atajo vigente en este momento tras la reasignación del usuario y la resolución
de conflictos ("" = sin asignar o desconocido). Se puede llamar sin problema
desde los manejadores.
La función envuelta no toma argumentos y no devuelve nada. Las excepciones que se produzcan dentro las captura el trabajador y las anota en stderr; no hacen fallar el editor.
@ctx.commands.command(id="wordcount.count",
title="Count Words",
shortcut="Ctrl+Alt+W",
category="Text")
def _count():
ctx.status.show("counted")
Bajo el capó: el lado de Python reserva un handlerId localmente, registra la
retrollamada en la conexión RPC y dispara commands.registerCommand hacia el
anfitrión. Cuando el usuario activa el comando (clic de menú, atajo, paleta), el
anfitrión envía un RPC callback y tu función se ejecuta.
Los identificadores de comando son de todo el proceso, no por plugin. Ponles
el prefijo de tu espacio de nombres DNS inverso (wordcount.…) para evitar
colisiones.
menus¶
Las rutas de menú usan / como separador. El segmento más a la izquierda es un
menú de primer nivel (Plugins, Tools, …); los segmentos intermedios crean
submenús bajo demanda; el segmento final es la etiqueta del elemento.
ctx.menus.add_item(path, command_id)¶
path(str):"Superior/Sub/…/Etiqueta". Los submenús intermedios se crean si no existen.command_id(str): identificador de un comando ya registrado. Hacer clic en el elemento llama al comando.
Devuelve una cadena handlerId opaca. Consérvala si más adelante necesitas hacer
unsubscribe del elemento (algo raro en la fase 1).
El segmento más a la izquierda ("Plugins" arriba) debe coincidir con el title
de una de tus entradas de menus[] en plugin.json, para que se recoja la
colocación en la barra de menús.
ctx.menus.add_separator(path)¶
Inserta un separador en un submenú existente.
ctx.menus.set_item_checked(handler_id, checked)¶
Convierte el elemento añadido previamente en una entrada de menú marcable y fija su estado de marcado; es el idioma estándar para un conmutador persistente de encendido/apagado que muestra una marca de verificación visible.
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 primera llamada promueve el elemento a marcable; las llamadas posteriores solo
cambian la marca. Pasar un handler_id que el anfitrión no conoce no hace nada en
silencio (es seguro llamarlo después de haber soltado el testigo, por ejemplo
desde una retrollamada retrasada que se dispare durante el cierre).
ctx.menus.set_item_enabled(handler_id, enabled)¶
Activa o atenúa un elemento añadido previamente. Úsalo cuando el comando necesite una condición previa —algo que cerrar, algo que restaurar—, para que el menú muestre que no está disponible en lugar de ofrecer una entrada que no hace nada en silencio.
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)
Un elemento desactivado sigue visible pero no se puede activar, ni con un clic ni
con su atajo. La llamada es idempotente, así que nunca tienes que recordar el
estado anterior, y un handler_id desconocido no hace nada en silencio, como
arriba. Los elementos empiezan activados: una llamada justo después de add_item
prepara un elemento que no está disponible al arrancar.
events¶
Suscríbete a los eventos del editor. Dos API equivalentes: se recomiendan los
decoradores azucarados; el subscribe() en bruto está disponible si quieres
calcular el tema en tiempo de ejecución.
Decoradores azucarados¶
@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 función envuelta toma un argumento: una dataclass tipada correspondiente al tema. Consulta la referencia de eventos para ver la tabla completa de tema ↔ dataclass.
API en bruto¶
handler_id = ctx.events.subscribe("document.saved", _saved)
# later:
ctx.events.unsubscribe(handler_id)
Propiedades azucaradas disponibles (todas son decoradores):
| Propiedad | Se dispara con |
|---|---|
on_document_opened |
Documento abierto en una pestaña |
on_document_closed |
Documento cerrado |
on_document_saved |
Documento escrito en el disco |
on_selection_changed |
El cursor o la selección se han movido en el documento activo |
on_active_document_changed |
Ha cambiado la pestaña en primer plano |
on_document_modified_changed |
El indicador de limpio/sucio ha cambiado (modified: bool) |
on_text_changed |
Cada edición (position, added_bytes, removed_bytes); mantenlo trivial |
on_settings_changed |
Se han aplicado las Preferencias (scope: "" en todo el editor, "plugins/<id>") |
on_theme_changed |
Se ha cambiado o editado el tema (variant); vuelve a leer los colores de ctx.theme |
on_workspace_root_changed |
Se ha abierto o cerrado la carpeta del espacio de trabajo (root) |
on_workspace_files_changed |
Ha cambiado el conjunto de archivos del espacio de trabajo (files) |
on_host_window_shown |
Una vez por ejecución, después del primer fotograma; programa aquí el trabajo lento de arranque |
on_app_about_to_quit |
El editor se está cerrando: última oportunidad de ejecutarse |
on_command_shortcut_changed |
Ha cambiado el atajo efectivo de un comando (reasignación del usuario, importación de mapa de teclado, resolución de conflictos); carga útil: command_id, sequence ("" = sin asignar) |
Los nombres de tema desconocidos lanzan ValueError en el momento de la
suscripción. Los suscriptores pueden ejecutarse después del manejador nativo
correspondiente; trata la entrega de eventos como informativa, no como
autoritativa.
Publicar¶
ctx.events.publish(topic, payload) difunde por igual a los suscriptores nativos
y a los de Python, y solo se acepta para los dos temas del espacio de trabajo
(workspace.rootChanged, workspace.filesChanged); cualquier otro lanza
ValueError. Consulta la referencia de eventos.
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")
Cada nivel se corresponde con el ILogger del anfitrión. Las líneas llevan el
prefijo [python:<plugin.id>] para que tu salida sea localizable en el registro
de diagnóstico del editor:
Nunca uses print() desde un plugin: la salida estándar del trabajador es el
canal RPC, y escribir en ella corrompe el protocolo. ctx.log.* es la única forma
autorizada de emitir texto de diagnóstico.
status¶
Publica una notificación pasajera en la barra de estado.
message(str): qué mostrar. Solo cadenas cortas: la barra de estado tiene un ancho fijo.timeout_ms(int, 0 de forma predeterminada): cuánto tiempo mantenerla en pantalla;0deja que el anfitrión elija un valor sensato (unos segundos).level(str,"info"de forma predeterminada):"info"/"warning"/"error". Los niveles más altos pueden mantener el mínimo en pantalla un poco más, para que un error no lo borre al momento un mensaje informativo rutinario.
ctx.status.show se lanza y se olvida. Si el anfitrión no tiene una barra de
estado visible (compilación mínima o sin interfaz), la llamada se descarta en
silencio. ctx.status.clear() borra la notificación pasajera que haya en
pantalla.
Segmentos persistentes¶
Para estado en vivo (un recuento de palabras, el estado de un servidor) usa un segmento propio que refresques: nunca pisa una notificación, ni al revés.
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 ordena los segmentos de los plugins de izquierda a derecha (menor = más
a la izquierda; los empates conservan el orden de registro). El cierre del plugin
elimina todo lo que siga retenido.
editor¶
Acceso síncrono completo a los documentos activos. Todos los métodos bloquean el
hilo que llama hasta que el anfitrión responde (normalmente microsegundos cuando
el trabajador y el editor comparten máquina). Es legal desde cualquier manejador
de comando o de evento; nunca desde una retrollamada async def (consulta la
nota sobre hilos al principio de esta página).
Enumeración de documentos¶
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
Acceso al búfer¶
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 traer todo el búfer por el RPC cuando solo necesitas un trozo;
prefiérelo para ordenar o transformar archivos grandes.
Selección¶
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))
Operaciones con archivos¶
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 es la única llamada para «llevar al usuario allí» (un error de
compilación, un marco de pila, un resultado de búsqueda): abre el archivo o
activa su pestaña, coloca el cursor sin selección, despliega un plegado que
oculte la línea, desplaza la línea hasta que se vea —centrada si estaba fuera
de la pantalla— y da el foco al editor. Línea y columna empiezan en 0 (la
columna en bytes UTF-8) y se ajustan, nunca se rechazan; devuelve el
identificador del documento, o 0 si no se pudo abrir el archivo, y en ese caso
no se ha movido nada. reveal_line solo muestra una línea: no mueve el
cursor, no cambia de pestaña y no toma el foco.
Marcadores, marcas y resaltado de 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
Búsqueda¶
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". Consulta
mte.SearchOptions para la lista completa de campos.
Búsqueda incremental (hacer crecer el patrón mientras el usuario escribe, desde un ancla fijada):
ctx.editor.set_incremental_search_anchor(doc)
hit = ctx.editor.incremental_search(doc, "TOD", opts) # MatchRange
Sangría y sintaxis¶
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 dividida y grupos de pestañas¶
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 mostrado en dos paneles es un documento con un solo
identificador, así que group_of() solo puede nombrar uno de sus asientos.
groups_of() los informa todos, y show_document_in_group() crea una segunda
vista compartida: lo mismo que hace Ver > Dividir documento a la derecha,
así que no se emite ningún evento document_opened.
ctx.editor refleja ahora el IEditorService nativo uno a uno.
languages¶
El coloreado de sintaxis es una función del núcleo: el anfitrión detecta el
lenguaje de un documento y lo colorea sin ningún plugin. ctx.languages te
permite participar: listar lo que el anfitrión conoce, leer o anular el lenguaje
de un documento, volver a ejecutar la detección y aportar definiciones de lenguaje
completas.
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 toma la misma forma que un archivo colorizers/*.json distribuido (o un
diccionario definition= completo): lexer nombra un analizador léxico del
backend que ya existe (el anfitrión no gana analizadores nuevos en tiempo de
ejecución), styles asocia los nombres de estilo de ese analizador con
categorías semánticas, y keywords se dirige a sus listas de palabras mediante la
propia descripción de ranura del analizador; el anfitrión resuelve ambas cosas
exactamente igual que para un archivo distribuido, así que nunca deletreas
identificadores del motor. Registrar un identificador que ya existe lo sustituye
mientras conserves el identificador de manejador; el cierre del plugin retira todo
lo que siga retenido.
document_settings¶
Ajustes de solo lectura, con ámbito de documento, que superponen el
.editorconfig de un proyecto a tus propios ctx.settings. Para la clave k y el
documento doc, una lectura resuelve mte.<plugin.id>.<k> a partir del
.editorconfig que se aplica a ese documento, recurre después a ctx.settings y
luego a tu valor predeterminado:
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?
Las escrituras van a través de ctx.settings, nunca de vuelta al
.editorconfig.
diagnostics¶
Empuja líneas onduladas y glifos de margen para un documento, desde un analizador
estático, un servidor de lenguaje o un registro de compilación. El anfitrión los
dibuja; tú nunca tocas la vista. Publicar sustituye todo lo que hubieras publicado
antes para esa pareja (documento, fuente), así que envía el conjunto completo
cada vez:
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 es obligatorio: es lo que mantiene tu conjunto separado de los de otros
productores (el plugin LSP, el corrector ortográfico) en el mismo documento.
Gravedades: SEVERITY_ERROR / SEVERITY_WARNING / SEVERITY_INFORMATION /
SEVERITY_HINT. Los desplazamientos son intervalos de bytes semiabiertos
[start, end).
theme¶
Registra los colores que pinta tu propia interfaz como categorías con nombre y adaptables al tema; el Editor de temas las lista automáticamente y las anulaciones del usuario van y vuelven en el archivo de 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)
Vuelve a leer resolve_color con ctx.events.on_theme_changed.
compare¶
Abre (o enfoca) la vista de comparación en paralelo del editor sobre dos fuentes.
Cada lado es un archivo del disco (path=), texto literal (content=) o ambos
(se muestra el contenido, y la ruta le da a la persistencia de sesión una
identidad restaurable):
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
Devuelve False cuando un lado no se puede leer.
settings¶
Almacén persistente y tipado de clave/valor, acotado a plugins/<your.id>/ en el
anfitrión. Todos los métodos son síncronos; get y set se infieren del tipo de
Python del valor predeterminado.
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
Tipos:
Tipo de Python de default |
Tipo en el canal | Tipo devuelto por get() |
|---|---|---|
bool |
bool |
bool |
int (que no sea bool) |
int |
int |
float |
double |
float |
str (o None) |
string |
str |
Además:
ctx.settings.contains("wrap") # bool
ctx.settings.remove("stale_key")
ctx.settings.sync() # optional; host also syncs on shutdown
Reacciona a los cambios hechos en el diálogo de Preferencias suscribiéndote a
settings.changed (ctx.events.on_settings_changed; consulta la
referencia de eventos).
app_settings¶
Vista de solo lectura de la configuración de toda la aplicación del editor. Los
nombres de clave conocidos se exportan como constantes en mte.keys (reflejando
la cabecera de C++ PluginApi/AppSettingsKeys.h, que sigue siendo la lista que
manda):
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"
Los asignadores, remove() y sync() en ctx.app_settings lanzan
RuntimeError: la vista es de solo lectura a propósito.
completions¶
Registra un proveedor de autocompletado que participe en la ventana emergente del 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)]
El invocable envuelto se llama en cada pulsación (o con Ctrl+Space) cuyo documento
coincida con el filtro de lenguaje. Campos de req:
| Campo | Tipo | Significado |
|---|---|---|
document |
int |
Documento que se está editando |
prefix |
str |
Fragmento de palabra inmediatamente anterior al cursor |
position |
int |
Desplazamiento en bytes del cursor |
language_id |
str |
Identificador de colorizador ("python", "cpp", …) |
manual |
bool |
True cuando el usuario pulsó Ctrl+Space; False en la activación automática |
Devuelve una lista de mte.CompletionItem (o cadenas simples, o diccionarios con
{text, detail, kind}). Tipos: mte.KIND_KEYWORD / KIND_WORD / KIND_SYMBOL /
KIND_SNIPPET / KIND_UNKNOWN.
Plazo
El anfitrión envía a tu proveedor una petición por pulsación con un plazo de ~30 ms. Si tu retrollamada no llega, la ventana emergente se muestra sin tus candidatos para esa pulsación. Construye cualquier índice costoso en segundo plano y responde desde caché; nunca hagas entrada/salida en la retrollamada. El autocompletado por red al estilo LSP corresponde a una futura superficie asíncrona, no a esta.
languages=[] (u omitirlo) significa «todos los documentos». Se permiten varios
proveedores por plugin; cada decorador registra el suyo.
data_dir¶
Devuelve el directorio por plugin con permiso de escritura que el anfitrión ya ha
creado para ti. Úsalo para cachés, índices y cualquier dato que sea tuyo.
ctx.diagnostics_dir() devuelve de forma análoga el directorio compartido de
fallos y registros (de solo lectura para ti: ahí escribe el informador de fallos).
docks¶
Paneles acoplables declarativos: una interfaz de panel lateral construida a partir
de una especificación de formulario, no de widgets de Qt. Consulta
mte.ui para el vocabulario del constructor.
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 panel acoplable. Devuelve el identificador del panel (asignado
automáticamente cuando id es None).
id(str, opcional) — identificador estable que usanctx.ui.get/ctx.ui.setpara dirigirse a los controles de dentro de este panel. Si no proporcionas uno, el anfitrión asigna un identificadorpy-dock/Ny lo devuelve.title(str) — se muestra en el encabezado del panel.area(str) —"left"/"right"/"top"/"bottom". Los valores desconocidos recurren a"right".form(dict) — una especificación construida con los constructores demte.ui.initially_visible(bool) — si el panel se muestra cuando se carga el plugin.Truede forma predeterminada. PasaFalsepara un panel que no debería aparecer al arrancar y muéstralo después conset_panel_visible.proportional_size(float) — el tamaño predeterminado del panel como porción de la ventana principal: de su ancho en las áreas"left"/"right", de su alto en"top"/"bottom".0.0(lo predeterminado) conserva el comportamiento en píxeles que quiere un panel de herramienta: el panel mantiene su tamaño y redimensionar la ventana no lo toca. Una fracción como0.4es lo que quiere una vista previa: maximizar la ventana ensancha el panel con ella. Solo el valor predeterminado es tuyo; en cuanto el usuario arrastra el separador del panel, el anfitrión adopta y recuerda su porción. Los valores fuera de rango se acotan, y el anfitrión conserva una extensión mínima tanto para el panel como para el editor. Las reglas completas están enproportionalSize.tab_group(str): los paneles con el mismo identificador de grupo no vacío en la mismaareacomparten un único panel acoplable como pestañas en lugar de colocarse uno al lado del otro. Ponle un espacio de nombres como a un identificador de panel ("renamer/panels"). Es solo la disposición predeterminada: una disposición guardada por el usuario tiene prioridad. ConsultatabGroup.
ctx.docks.is_panel_visible(handler_id) -> bool¶
Si un panel añadido con add_panel está abierto: un panel en una pestaña
detrás de otro cuenta como abierto; uno registrado con
initially_visible=False y nunca mostrado, uno cerrado y un identificador
desconocido, no. Combínalo con set_panel_visible para mostrar tus paneles
durante una tarea y después devolver lo que tenía el usuario:
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)¶
Muestra u oculta un panel añadido con add_panel. Mostrarlo lo levanta además por
encima de los paneles acoplables con los que comparte área, de modo que un comando
que «abre» su panel lo ponga realmente delante del usuario. Es idempotente —repite
el mismo valor cuantas veces quieras, no hay estado que rastrear— y se ignora en
silencio para un identificador desconocido.
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¶
Asigna un identificador de manejador para conectar el campo on_change de un
control a un invocable de Python. La retrollamada recibe el valor nuevo como
cadena; los controles booleanos entregan "true" / "false" y los numéricos
entregan el entero como cadena.
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)])
Los identificadores de manejador de on_change llevan el prefijo py-dock-cb/N y
nunca chocan con los identificadores de manejador de comandos, eventos o
autocompletado.
La retrollamada se invoca en el bucle de eventos del trabajador, NO en el grupo de
hilos: mantenla rápida. Usa ctx.log, cambia un pequeño estado en memoria o
dispara un comando posterior; no hagas trabajo pesado aquí.
line_markers¶
Glifos en el margen y tintes de línea completa unidos a líneas del documento que siguen las ediciones del usuario —puntos de interrupción, una flecha de instrucción actual, franjas de cambios—, además de la reclamación del clic en la columna de marcadores. El servicio nativo y todas sus reglas se describen en Marcas de línea y clic en el margen.
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=):glyphes uno demte.GLYPH_NONE(solo tinte),GLYPH_CIRCLE,GLYPH_CIRCLE_HOLLOW,GLYPH_CIRCLE_BAR,GLYPH_ARROW,GLYPH_STRIP; los colores son identificadores de categorías que registraste conctx.theme. El editor solo tiene unas pocas ranuras de marcas para todos los plugins juntos, así que esto puede fallar: entonces lanzaRpcError. Registra tus tipos una sola vez, enregister().add_marker(kind, doc, line): línea que empieza en 0, sin ajustar: una línea fuera del documento devuelve 0 y no coloca nada.- Las marcas se mueven con sus líneas, sobreviven a una recarga en el mismo
número de línea y desaparecen con su documento: vuelve a añadir las tuyas en
ctx.events.on_document_opened.
El clic en el margen¶
@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()
Cada clic izquierdo en la columna de marcadores se publica como
margin.clicked (document, line, margin = "symbol", modifiers:
bits mte.MODIFIER_SHIFT / MODIFIER_CONTROL / MODIFIER_ALT /
MODIFIER_META). De forma predeterminada el editor también alterna un
marcador con él; mientras algún plugin mantenga claim_margin_click(), no lo
hace y deja el clic a los plugins. ctx.line_markers.release(claim) devuelve
el clic.
settings_page¶
Añade una página bajo el diálogo de Preferencias del editor. El mismo
vocabulario de especificación de formulario que los paneles acoplables; la misma
persistencia 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, obligatorio) — ruta separada por barras en el árbol de Preferencias, p. ej."Plugins/My Plugin". Las categorías anidadas llegan como nodos del árbol.title(str, obligatorio) — encabezado que se muestra sobre la página.form(dict, obligatorio) — una especificación construida con los constructores demte.ui.scope(str, opcional) — ámbito de evento que se emite enSettingsChangeddespués de que el usuario pulse Aceptar o Aplicar.
Devuelve el identificador de manejador.
Cancelar NO revierte los cambios
Los controles con binds="settings:<key>" se guardan con avidez a medida que
el usuario edita, así que pulsar Cancelar en el diálogo de Preferencias no
los deshace. Es una limitación conocida de la fase 3; un refinamiento futuro
envolverá los ajustes subyacentes en un proxy con búfer.
ui¶
Lee y escribe valores de los controles dentro de paneles que ya has registrado. Los valores van siempre como cadenas por el canal.
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]¶
Lee el valor actual de un control. Devuelve None cuando el panel o el control son
desconocidos (no es un error: trata tanto None como "" como «vacío»).
- Label / Button / LineEdit / ComboBox — devuelve el texto.
- Checkbox — devuelve
"true"o"false". - NumberSpin — devuelve el entero como cadena decimal.
- List — devuelve el texto del elemento seleccionado en ese momento (vacío cuando no hay nada seleccionado).
ctx.ui.set(panel_id, control_id, value) -> bool¶
Escribe un valor en un control mediante programación. Devuelve True cuando la
escritura tuvo éxito y False cuando el panel o el control son desconocidos o el
valor no se puede convertir (p. ej. un valor no entero enviado a un
NumberSpin).
Los True / False de Python se convierten a "true" / "false" antes de
enviarse; todo lo demás pasa por str().
Un set iniciado por el anfitrión NO vuelve a disparar la retrollamada
on_change del control, así que puedes actualizar tus propios widgets desde un
manejador de comando sin bucles infinitos.
Resumen de enlaces en tiempo de ejecución (fase 3)¶
| Campo de la especificación de formulario | Comportamiento en tiempo de ejecución |
|---|---|
id="…" |
Dirigirse al control desde ctx.ui.get / ctx.ui.set. |
binds="settings:<key>" |
Lee el valor inicial de ctx.settings; lo vuelve a escribir en cada edición del usuario. |
on_click="<cmd>" |
Los botones invocan al hacer clic el identificador de comando registrado. |
on_change=handler |
Dispara al editar el usuario la retrollamada registrada mediante ctx.docks.on_change(...). |
Las mismas reglas se aplican en los paneles acoplables y en las páginas de Preferencias.