ctx — o contexto do plugin¶
register(ctx) recebe um argumento: um PluginContext. Ele expõe os serviços
abaixo (toda a superfície do IPluginContext que cabe no modelo fora de processo).
Cada um é uma pequena classe Python que o trabalhador constrói sobre o canal RPC; o
código-fonte está em 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
Duas partes do contrato nativo ainda não têm equivalente em Python: os provedores de hover (então as faixas de prioridade do hover não se aplicam a plugins Python) e os provedores do menu de contexto do editor (então a área texto / margem do menu de contexto também não). Ambos são respondidos enquanto o usuário espera, o que exige o mesmo tratamento de prazos que os provedores de autocompletar têm antes que uma ida e volta ao trabalhador seja segura.
Não guarde ctx em uma variável global esperando usá-lo depois — embora os
atributos sejam estáveis durante a vida do plugin, trate ctx como algo do
registro. Mantenha referências aos serviços individuais se precisar deles
(log = ctx.log, por exemplo).
commands¶
Decorador que registra um comando e vincula o chamável envolvido como o manipulador dele.
id(str, obrigatório): identificador único no estilo DNS reverso (p. ex."wordcount.count"). As mesmas regras doplugin.json— precisa ser único globalmente.title(str, obrigatório): rótulo legível exibido onde quer que o comando apareça (menus, paleta de comandos).shortcut(str, opcional): cadeia de atalho padrão portátil, p. ex."Ctrl+Alt+W". OCtrlé mapeado automaticamente para o modificador principal da plataforma (Cmdno macOS). Deixe vazio para nenhum. O usuário pode remapeá-lo ou desvinculá-lo em Preferências ▸ Atalhos; para comandos ligados a menus, o host aplica o remapeamento por você.category(str, opcional): rótulo livre de agrupamento — exibido como a categoria do comando no Mapeamento de atalhos.scope(str, opcional):""/"application"(padrão — o host associa o atalho na ação de menu do comando) ou"pluginWindow"para combinações que vivem em interfaces do plugin: o host apenas lista o comando no mapeador; leiactx.commands.effective_shortcut(id)ao construir seus widgets e reaplique no evento"command.shortcutChanged".
O atalho em vigor no momento, depois do remapeamento do usuário e da resolução de
conflitos ("" = sem associação ou desconhecido). Seguro chamar a partir dos
manipuladores.
A função envolvida não recebe argumentos e não devolve nada. Exceções dentro dela são capturadas pelo trabalhador e registradas em stderr; elas não derrubam o editor.
@ctx.commands.command(id="wordcount.count",
title="Count Words",
shortcut="Ctrl+Alt+W",
category="Text")
def _count():
ctx.status.show("counted")
Nos bastidores: o lado Python aloca um handlerId localmente, registra a
retrochamada na conexão RPC e dispara commands.registerCommand no host. Quando o
usuário ativa o comando (clique de menu, atalho, paleta), o host envia um RPC
callback e a sua função roda.
Os ids de comando são de todo o processo, não por plugin. Prefixe seus ids com
o seu espaço de nomes DNS reverso (wordcount.…) para evitar colisões.
menus¶
Os caminhos de menu usam / como separador. O segmento mais à esquerda é um menu
de primeiro nível (Plugins, Tools, …); segmentos intermediários criam submenus
sob demanda; o segmento final é o rótulo do item.
ctx.menus.add_item(path, command_id)¶
path(str):"Topo/Sub/…/Rótulo". Submenus intermediários são criados se não existirem.command_id(str): id de um comando registrado antes. Clicar no item chama o comando.
Devolve uma cadeia handlerId opaca. Guarde-a se precisar fazer unsubscribe do
item depois (raro na fase 1).
O segmento mais à esquerda ("Plugins" acima) precisa coincidir com o title de
uma das suas entradas de menus[] no plugin.json, para que o posicionamento na
barra de menus seja captado.
ctx.menus.add_separator(path)¶
Insere um separador em um submenu existente.
ctx.menus.set_item_checked(handler_id, checked)¶
Transforma o item acrescentado antes em uma entrada de menu marcável e define o estado de marcação dele — o idioma padrão para uma alternância persistente de liga/desliga que exibe uma marca de verificação visível.
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)
A primeira chamada promove o item a marcável; as seguintes apenas invertem a marca.
Passar um handler_id que o host não conhece não faz nada em silêncio (seguro
chamar depois que o seu token foi descartado, por exemplo a partir de uma
retrochamada atrasada disparando durante o encerramento).
ctx.menus.set_item_enabled(handler_id, enabled)¶
Habilita ou esmaece um item acrescentado antes. Use quando o comando precisa de uma condição prévia — algo para fechar, algo para restaurar — para que o menu mostre que ele está indisponível em vez de oferecer uma entrada que silenciosamente não faz nada.
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)
Um item desativado continua visível, mas não pode ser acionado, nem por clique nem
pelo atalho. A chamada é idempotente, então você nunca precisa lembrar o estado
anterior, e um handler_id desconhecido não faz nada em silêncio, como acima. Os
itens começam habilitados: uma chamada logo depois de add_item prepara um item
indisponível na inicialização.
events¶
Assine eventos do editor. Duas APIs equivalentes — os decoradores açucarados são
recomendados; o subscribe() bruto está disponível se você quiser calcular o
tópico em tempo de execução.
Decoradores açucarados¶
@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}")
A função envolvida recebe um argumento: uma dataclass tipada correspondente ao tópico. Veja a referência de eventos para a tabela completa de tópico ↔ dataclass.
API bruta¶
handler_id = ctx.events.subscribe("document.saved", _saved)
# later:
ctx.events.unsubscribe(handler_id)
Propriedades açucaradas disponíveis (todas são decoradores):
| Propriedade | Dispara em |
|---|---|
on_document_opened |
Documento aberto em uma aba |
on_document_closed |
Documento fechado |
on_document_saved |
Documento gravado no disco |
on_selection_changed |
Cursor / seleção se moveu no documento ativo |
on_active_document_changed |
A aba em primeiro plano mudou |
on_document_modified_changed |
O sinalizador de limpo/sujo mudou (modified: bool) |
on_text_changed |
Toda edição (position, added_bytes, removed_bytes) — mantenha trivial |
on_settings_changed |
Preferências aplicadas (scope: "" de todo o editor, "plugins/<id>") |
on_theme_changed |
Tema trocado ou editado (variant) — releia as cores de ctx.theme |
on_workspace_root_changed |
Pasta de espaço de trabalho aberta / fechada (root) |
on_workspace_files_changed |
Conjunto de arquivos do espaço de trabalho mudou (files) |
on_host_window_shown |
Uma vez por execução, depois do primeiro quadro — agende aqui o trabalho lento de inicialização |
on_app_about_to_quit |
O editor está fechando — última chance de rodar |
on_command_shortcut_changed |
O atalho efetivo de um comando mudou (remapeamento do usuário / importação de mapa de teclado / resolução de conflito); carga útil: command_id, sequence ("" = sem associação) |
Nomes de tópico desconhecidos levantam ValueError na hora da assinatura. Os
assinantes podem rodar depois do manipulador nativo correspondente; trate a
entrega de eventos como informativa, não como autoritativa.
Publicando¶
ctx.events.publish(topic, payload) transmite para assinantes nativos e Python
igualmente — aceito apenas para os dois tópicos de espaço de trabalho
(workspace.rootChanged, workspace.filesChanged); qualquer outra coisa levanta
ValueError. Veja a referência 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 nível corresponde ao ILogger do host. As linhas recebem o prefixo
[python:<plugin.id>], para que a sua saída seja localizável no log de
diagnóstico do editor:
Nunca use print() em um plugin — o stdout do trabalhador é o canal RPC, e escrever
nele corrompe o protocolo. ctx.log.* é a única forma autorizada de emitir texto
de diagnóstico.
status¶
Publica uma notificação passageira na barra de status.
message(str): o que exibir. Apenas cadeias curtas — a barra de status tem largura fixa.timeout_ms(int, 0 por padrão): por quanto tempo mantê-la na tela;0deixa o host escolher um padrão sensato (alguns segundos).level(str,"info"por padrão):"info"/"warning"/"error". Níveis mais altos podem segurar o mínimo na tela um pouco mais, para que um erro não seja apagado na hora por uma mensagem informativa rotineira.
ctx.status.show é dispare-e-esqueça. Se o host não tem barra de status visível
(build mínimo / sem interface), a chamada é descartada em silêncio.
ctx.status.clear() limpa a notificação passageira que estiver na tela.
Segmentos persistentes¶
Para estado ao vivo (uma contagem de palavras, o status de um servidor), use um segmento do qual você é dono e que você atualiza — ele nunca atropela uma notificação, nem o contrário:
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 os segmentos dos plugins da esquerda para a direita (menor = mais
à esquerda; empates mantêm a ordem de registro). O encerramento do plugin remove
tudo o que ainda estiver retido.
editor¶
Acesso síncrono completo aos documentos ativos. Todo método bloqueia a thread
chamadora até o host responder (normalmente microssegundos quando o trabalhador e o
editor compartilham a máquina). Legal a partir de qualquer manipulador de comando /
evento; nunca de uma retrochamada async def (veja a observação sobre threads
no topo desta página).
Enumeração 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
Acesso ao 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 puxar o buffer inteiro pelo RPC quando você só precisa de um
pedaço — prefira-o para ordenar/transformar arquivos grandes.
Seleção¶
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))
Operações de arquivo¶
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 é a única chamada para "levar o usuário até lá" (um erro de
compilação, um quadro da pilha, um resultado de busca): ela abre o arquivo ou
ativa a aba dele, posiciona o cursor sem seleção, expande uma dobra que
esconda a linha, rola a linha até ficar visível — centralizada quando estava
fora da tela — e dá o foco ao editor. Linha e coluna começam em 0 (a coluna em
bytes UTF-8) e são limitadas, nunca recusadas; ela devolve o id do documento,
ou 0 quando o arquivo não pôde ser aberto, e então nada se moveu.
reveal_line só mostra uma linha: não move o cursor, não troca de aba e não
toma o foco.
Marcadores, marcas, realce 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
Busca¶
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". Veja mte.SearchOptions para
a lista completa de campos.
Busca incremental (crescer o padrão enquanto o usuário digita, a partir de uma âncora fixada):
ctx.editor.set_incremental_search_anchor(doc)
hit = ctx.editor.incremental_search(doc, "TOD", opts) # MatchRange
Recuo e sintaxe¶
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
Visualização dividida / grupos de abas¶
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
Um documento exibido em dois painéis é um documento com um id, então
group_of() só pode nomear um dos assentos dele. groups_of() reporta todos, e
show_document_in_group() cria uma segunda visualização compartilhada — a mesma
coisa que Exibir > Dividir documento à direita faz, então nenhum evento
document_opened é emitido.
O ctx.editor agora espelha o IEditorService nativo um a um.
languages¶
A coloração de sintaxe é um recurso do núcleo — o host detecta a linguagem de um
documento e o colore sem plugin algum. O ctx.languages permite que você
participe: liste o que o host conhece, leia ou substitua a linguagem de um
documento, reexecute a detecção e contribua definições de linguagem inteiras.
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
O register recebe a mesma forma de um arquivo colorizers/*.json distribuído (ou
um dicionário definition= inteiro): lexer nomeia um analisador léxico de backend
existente (o host não ganha analisadores novos em tempo de execução), styles
mapeia os nomes de estilo daquele analisador em categorias semânticas, e
keywords se dirige às listas de palavras dele pela descrição de vaga do próprio
analisador — o host resolve os dois exatamente como faz para um arquivo
distribuído, então você nunca soletra ids do mecanismo. Registrar um id que já
existe o substitui enquanto você mantiver o id do manipulador; o encerramento do
plugin retira tudo o que ainda estiver retido.
document_settings¶
Configurações somente leitura, com escopo de documento, que sobrepõem o
.editorconfig de um projeto aos seus próprios ctx.settings. Para a chave k e o
documento doc, uma leitura resolve mte.<plugin.id>.<k> a partir do
.editorconfig que se aplica àquele documento, depois recorre a ctx.settings e
então ao seu padrão:
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?
As gravações passam por ctx.settings — nunca de volta ao .editorconfig.
diagnostics¶
Empurre traços ondulados e glifos de margem para um documento — de um analisador
estático, um servidor de linguagem, um log de build. O host os renderiza; você nunca
toca na visualização. Publicar substitui tudo o que você publicou antes para aquele
par (documento, fonte), então envie o conjunto completo toda 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 é obrigatório — é o que mantém o seu conjunto separado dos de outros
produtores (o plugin LSP, o verificador ortográfico) no mesmo documento.
Severidades: SEVERITY_ERROR / SEVERITY_WARNING / SEVERITY_INFORMATION /
SEVERITY_HINT. Os deslocamentos são intervalos de bytes semiabertos
[start, end).
theme¶
Registre as cores que a sua própria interface pinta como categorias nomeadas e adaptáveis ao tema; o Editor de temas as lista automaticamente e as substituições do usuário vão e voltam no arquivo 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)
Releia resolve_color em ctx.events.on_theme_changed.
compare¶
Abra (ou foque) a visualização de comparação lado a lado do editor sobre duas
fontes. Cada lado é um arquivo no disco (path=), texto literal (content=) ou os
dois (o conteúdo é exibido, e o caminho dá à persistência de sessão uma identidade
restaurável):
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
Devolve False quando um lado não pode ser lido.
settings¶
Armazenamento persistente e tipado de chave/valor, restrito a plugins/<your.id>/ no
host. Todos os métodos são síncronos; get/set são inferidos do tipo Python do
padrão.
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 Python do default |
Tipo no canal | Tipo devolvido por get() |
|---|---|---|
bool |
bool |
bool |
int (que não seja bool) |
int |
int |
float |
double |
float |
str (ou None) |
string |
str |
Também:
ctx.settings.contains("wrap") # bool
ctx.settings.remove("stale_key")
ctx.settings.sync() # optional; host also syncs on shutdown
Reaja a alterações feitas na caixa de diálogo Preferências assinando
settings.changed (ctx.events.on_settings_changed; veja a
referência de eventos).
app_settings¶
Visão somente leitura da configuração de todo o aplicativo do editor. Os nomes de
chave conhecidos são exportados como constantes em mte.keys (espelhando o
cabeçalho C++ PluginApi/AppSettingsKeys.h, que continua sendo a 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"
Os setters, remove() e sync() em ctx.app_settings levantam RuntimeError — a
visão é intencionalmente somente leitura.
completions¶
Registre um provedor de autocompletar que participe do popup do 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)]
O chamável envolvido é invocado a cada tecla (ou com Ctrl+Space) cujo documento
corresponde ao filtro de linguagem. Campos de req:
| Campo | Tipo | Significado |
|---|---|---|
document |
int |
Documento sendo editado |
prefix |
str |
Fragmento de palavra imediatamente antes do cursor |
position |
int |
Deslocamento em bytes do cursor |
language_id |
str |
Id do colorizador ("python", "cpp", …) |
manual |
bool |
True quando o usuário pressionou Ctrl+Space; False no acionamento automático |
Devolva uma lista de mte.CompletionItem (ou cadeias simples, ou dicionários com
{text, detail, kind}). Tipos: mte.KIND_KEYWORD / KIND_WORD / KIND_SYMBOL /
KIND_SNIPPET / KIND_UNKNOWN.
Prazo
O host envia ao seu provedor uma requisição por tecla com um prazo de ~30 ms. Se a sua retrochamada não cumprir, o popup aparece sem os seus candidatos para aquela tecla. Construa qualquer índice caro em segundo plano e responda a partir do cache — nunca faça E/S na retrochamada. Autocompletar por rede no estilo LSP pertence a uma futura superfície assíncrona, não a esta.
languages=[] (ou omitido) significa "todo documento". Vários provedores por
plugin são permitidos; cada decorador registra o seu.
data_dir¶
Devolve o diretório gravável por plugin que o host já criou para você. Use para
caches, índices e quaisquer dados que sejam seus. ctx.diagnostics_dir() devolve de
forma análoga o diretório compartilhado de falhas/logs (somente leitura para você —
o relator de falhas grava ali).
docks¶
Painéis acopláveis declarativos — uma interface de painel lateral construída a
partir de uma especificação de formulário, não de widgets do Qt. Veja
mte.ui para o vocabulário do construtor.
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 um painel acoplável. Devolve o id do painel (alocado automaticamente
quando id é None).
id(str, opcional) — identificador estável usado porctx.ui.get/ctx.ui.setpara se dirigir aos controles dentro deste painel. Se você não fornecer um, o host aloca um idpy-dock/Ne o devolve.title(str) — exibido no cabeçalho do painel.area(str) —"left"/"right"/"top"/"bottom". Valores desconhecidos recorrem a"right".form(dict) — uma especificação construída com os construtores demte.ui.initially_visible(bool) — se o painel é exibido quando o plugin carrega.Truepor padrão. PasseFalsepara um painel que não deve aparecer na inicialização e revele-o depois comset_panel_visible.proportional_size(float) — o tamanho padrão do painel como fração da janela principal: da largura dele nas áreas"left"/"right", da altura em"top"/"bottom".0.0(o padrão) mantém o comportamento em pixels que um painel de ferramenta quer — o painel mantém o tamanho e redimensionar a janela não o toca. Uma fração como0.4é o que uma visualização quer: maximizar a janela alarga o painel junto com ela. Só o padrão é seu; assim que o usuário arrasta o separador do painel, o host adota e lembra a fração dele. Valores fora do intervalo são limitados, e o host mantém uma extensão mínima tanto para o painel quanto para o editor. As regras completas estão emproportionalSize.tab_group(str) — painéis com o mesmo id de grupo não vazio na mesmaareacompartilham um único painel acoplável em abas em vez de ficarem lado a lado. Dê a ele um namespace como a um id de painel ("renamer/panels"). É só a disposição padrão: um layout salvo pelo usuário tem prioridade. VejatabGroup.
ctx.docks.is_panel_visible(handler_id) -> bool¶
Se um painel adicionado com add_panel está aberto — um painel em uma aba
atrás de outro conta como aberto; um registrado com initially_visible=False
e nunca mostrado, um fechado e um id desconhecido, não. Combine-o com
set_panel_visible para mostrar seus painéis durante uma tarefa e depois
devolver o que o usuário tinha:
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)¶
Revela ou oculta um painel acrescentado com add_panel. Exibi-lo também eleva o
painel acima dos painéis com que ele compartilha a área, de modo que um comando que
"abre" o painel dele realmente o coloque na frente do usuário. Idempotente — repita
o mesmo valor à vontade, sem estado a rastrear; ignorado em silêncio para um id
desconhecido.
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¶
Aloca um id de manipulador para ligar o campo on_change de um controle a um
chamável Python. A retrochamada recebe o novo valor como cadeia; controles
booleanos entregam "true" / "false", controles numéricos entregam o inteiro como
cadeia.
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)])
Ids de manipulador de on_change levam o prefixo py-dock-cb/N e nunca colidem com
ids de manipulador de comando / evento / autocompletar.
A retrochamada é invocada no laço de eventos do trabalhador, NÃO no pool de
threads — mantenha-a rápida. Use ctx.log, altere um pequeno estado em memória, ou
dispare um comando subsequente; não faça trabalho pesado aqui.
line_markers¶
Glifos na margem e tons de linha inteira presos a linhas do documento, que acompanham as edições do usuário — pontos de interrupção, uma seta da instrução atual, faixas de alterações — e a reivindicação do clique na coluna de marcadores. O serviço nativo e todas as suas regras estão descritos em Marcas de linha e clique na margem.
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é um demte.GLYPH_NONE(só o tom),GLYPH_CIRCLE,GLYPH_CIRCLE_HOLLOW,GLYPH_CIRCLE_BAR,GLYPH_ARROW,GLYPH_STRIP; as cores são ids de categorias que você registrou comctx.theme. O editor tem só alguns espaços de marcas para todos os plugins juntos, então isso pode falhar — e aí levantaRpcError. Registre seus tipos uma única vez, emregister().add_marker(kind, doc, line)— linha a partir de 0, sem limitação: uma linha fora do documento devolve 0 e não posiciona nada.- As marcas andam com as suas linhas, sobrevivem a um recarregamento no mesmo
número de linha e somem com o documento — acrescente as suas de novo em
ctx.events.on_document_opened.
O clique na margem¶
@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()
Todo clique esquerdo na coluna de marcadores é publicado como
margin.clicked (document, line, margin = "symbol", modifiers —
bits mte.MODIFIER_SHIFT / MODIFIER_CONTROL / MODIFIER_ALT /
MODIFIER_META). Por padrão o editor também alterna um marcador nesse
clique; enquanto algum plugin mantiver claim_margin_click(), ele não faz
isso e deixa o clique para os plugins. ctx.line_markers.release(claim)
devolve o clique.
settings_page¶
Acrescenta uma página sob a caixa de diálogo Preferências do editor. Mesmo
vocabulário de especificação de formulário dos painéis acopláveis; mesma
persistência por 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, obrigatório) — caminho separado por barras na árvore de Preferências, p. ex."Plugins/My Plugin". Categorias aninhadas chegam como nós da árvore.title(str, obrigatório) — título exibido acima da página.form(dict, obrigatório) — uma especificação construída com os construtores demte.ui.scope(str, opcional) — escopo de evento emitido emSettingsChangeddepois que o usuário pressiona OK / Aplicar.
Devolve o id do manipulador.
Cancelar NÃO reverte as alterações
Controles com binds="settings:<key>" persistem avidamente enquanto o
usuário edita, então pressionar Cancelar na caixa de diálogo Preferências
não as desfaz. Esta é uma limitação conhecida da fase 3; um refinamento futuro
envolverá as configurações subjacentes em um proxy com buffer.
ui¶
Lê e escreve valores de controles dentro de painéis que você já registrou. Os valores são sempre cadeias no 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]¶
Lê o valor atual de um controle. Devolve None quando o painel ou o controle são
desconhecidos (não é um erro — trate tanto None quanto "" como "vazio").
- Label / Button / LineEdit / ComboBox — devolve o texto.
- Checkbox — devolve
"true"ou"false". - NumberSpin — devolve o inteiro como cadeia decimal.
- List — devolve o texto do item selecionado no momento (vazio quando nada está selecionado).
ctx.ui.set(panel_id, control_id, value) -> bool¶
Escreve um valor em um controle programaticamente. Devolve True quando a gravação
teve êxito, False quando o painel/controle é desconhecido ou o valor não pode ser
convertido (por exemplo, um valor não inteiro enviado a um NumberSpin).
Os True / False do Python são convertidos em "true" / "false" antes de serem
enviados; todo o resto passa por str().
Um set iniciado pelo host NÃO dispara de novo a retrochamada on_change do
controle, então você pode atualizar os seus próprios widgets a partir de um
manipulador de comando sem laços infinitos.
Resumo das ligações em tempo de execução (fase 3)¶
| Campo da especificação de formulário | Comportamento em tempo de execução |
|---|---|
id="…" |
Dirija-se ao controle por ctx.ui.get / ctx.ui.set. |
binds="settings:<key>" |
Lê o valor inicial de ctx.settings; grava de volta a cada edição do usuário. |
on_click="<cmd>" |
Botões invocam o id de comando registrado ao serem clicados. |
on_change=handler |
Dispara a retrochamada registrada via ctx.docks.on_change(...) na edição do usuário. |
As mesmas regras valem em painéis acopláveis e em páginas de Preferências.