Pular para conteúdo

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

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

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 do plugin.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". O Ctrl é mapeado automaticamente para o modificador principal da plataforma (Cmd no 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; leia ctx.commands.effective_shortcut(id) ao construir seus widgets e reaplique no evento "command.shortcutChanged".
ctx.commands.effective_shortcut(command_id) -> str

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.


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)

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

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

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:

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

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

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

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

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

path = ctx.data_dir()                    # pathlib.Path

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 por ctx.ui.get / ctx.ui.set para se dirigir aos controles dentro deste painel. Se você não fornecer um, o host aloca um id py-dock/N e 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 de mte.ui.
  • initially_visible (bool) — se o painel é exibido quando o plugin carrega. True por padrão. Passe False para um painel que não deve aparecer na inicialização e revele-o depois com set_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 como 0.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 em proportionalSize.
  • tab_group (str) — painéis com o mesmo id de grupo não vazio na mesma area compartilham 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. Veja tabGroup.

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 de mte.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 com ctx.theme. O editor tem só alguns espaços de marcas para todos os plugins juntos, então isso pode falhar — e aí levanta RpcError. Registre seus tipos uma única vez, em register().
  • 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 de mte.ui.
  • scope (str, opcional) — escopo de evento emitido em SettingsChanged depois 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.