Repository navigation
plugins
O Codis possui um sistema de plugins completo com sandbox estática, API rica e instalação segura.
Um plugin é um módulo Python (.py) ou um arquivo ZIP contendo plugin.py. A estrutura mínima:
# meu_plugin.py
from src.plugins.api import PluginAPI, PluginManifest
MANIFEST = PluginManifest(
name="meu-plugin",
version="1.0.0",
author="Seu Nome",
description="O que o plugin faz.",
icon="🔧",
api_version="1.0",
)
def activate(api: PluginAPI) -> None:
"""Chamado quando o plugin é ativado."""
api.show_message("Plugin ativado!")
def deactivate() -> None:
"""Chamado quando o plugin é desativado. Opcional."""
passCopie o arquivo .py (ou .zip) para ~/.codis/plugins/. O Codis carrega os plugins nessa pasta automaticamente na inicialização.
- Menu Plugins → Instalar Plugin
- Selecione o arquivo
.pyou.zipno diálogo. - O Codis executa a análise estática de segurança antes de instalar.
Na sidebar → ícone 🔌 (Extensões), clique em Instalar e selecione o arquivo.
Antes de carregar qualquer plugin, o PluginLoader realiza uma análise estática via AST que verifica:
- Imports de módulos perigosos (
os,subprocess,sys,socket, etc.) - classificados comoblockedouwarned - Uso de
eval,exec,__import__ - Acesso a atributos internos do Python
O resultado da análise é classificado em:
| Classificação | Comportamento |
|---|---|
clean |
Plugin carregado automaticamente |
warned |
Diálogo de confirmação exibido ao usuário |
blocked |
Plugin bloqueado por padrão; usuário deve confirmar explicitamente |
Uma vez que o usuário aceita um plugin, ele é adicionado a ~/.codis/trusted_plugins.json e carregado automaticamente nas próximas inicializações.
O diálogo de confirmação só aparece ao instalar pela interface (
Plugins → Instalar Pluginou arrastar o arquivo). Um arquivo colocado diretamente em~/.codis/plugins/sem nunca ter passado pela instalação é verificado na inicialização automática do Codis - se contiver importsblocked, é recusado silenciosamente (sem diálogo), até que seja instalado uma vez pela interface e marcado como confiável.
O objeto PluginAPI é passado para a função activate() e fornece acesso controlado ao Codis.
api.show_message("Mensagem") # Notificação na status bar (3s)
api.show_message("Mensagem", level="warning") # Níveis: "info", "warning", "error"
api.show_message("Permanente", duration=0) # 0 = não desaparece sozinha
api.show_info("Título", "Texto") # Diálogo modal informativo
api.show_warning("Título", "Texto") # Diálogo modal de aviso
api.show_error("Título", "Texto") # Diálogo modal de erroA versão do Codis está disponível como
APP_VERSIONemsrc.constants(não há um métodoapi.get_version()).
editor = api.get_active_editor() # EditorPane ativo (ou None)
content = api.get_text() # Texto completo do editor ativo
selection = api.get_selection() # Texto selecionado
api.insert_at_cursor("texto") # Insere no cursor
api.replace_selection("novo") # Substitui a seleção
api.set_text("novo conteúdo") # Substitui todo o conteúdo
api.get_cursor_position() # (linha, coluna)
api.set_cursor_position(42, 0) # Move o cursor para linha 42, coluna 0
api.scroll_to_line(42) # Rola a view até a linha 42
api.get_active_file() # Path do arquivo ativo (ou None)
api.get_active_lang() # Linguagem do arquivo (ex: "python")# Registrar um novo tema (veja todos os campos obrigatórios em temas.md)
from src.theme.base import Theme
api.register_theme(Theme(name="meu-tema", dark=True, bg="#1a1a2e", fg="#e0e0e0", **outros_campos))
# Destacar uma região no editor (coordenadas linha/coluna, não strings tipo "1.0")
api.add_highlight(line_start=1, col_start=0, line_end=1, col_end=10,
tag="erro", bg="#ff000033")
# Adicionar hint inline (texto fantasma após a posição)
api.add_inlay_hint(line=10, col=5, text="→ string")
# Adicionar ícone no gutter (margem esquerda)
api.add_gutter_icon(line=10, icon="⚡", tooltip="Linha quente")# Registrar um comando (disponível na Paleta de Comandos)
api.register_command("meu.comando", "Descrição do Comando", minha_funcao)
# Adicionar item de menu
api.add_menu_item("Plugins/Meu Plugin", "Fazer Algo", minha_funcao)
# Registrar atalho de teclado
api.add_keybinding("<Control-Alt-m>", "meu.comando")# Adicionar um painel na sidebar com botão na Activity Bar
def criar_painel(parent):
import tkinter as tk
frame = tk.Frame(parent, bg="black")
tk.Label(frame, text="Meu Painel", fg="white", bg="black").pack()
return frame
api.add_sidebar_panel("meu-painel", "🔧", criar_painel, "Meu Painel", position="bottom")O nome do evento é uma string; os eventos abaixo formam o conjunto CORE_EVENTS (src/plugins/api.py). Qualquer outro nome continua válido para eventos entre plugins (api.emit).
# Registrar handler para um evento
def ao_salvar(path):
print(f"Arquivo salvo: {path}")
api.on("file_save", ao_salvar)
api.on("file_open", lambda path: ...)
api.on("text_change", lambda editor: ...)
api.on("tab_change", lambda idx: ...)
api.on("lint_complete", lambda diags: ...)
api.on("cursor_move", lambda pos: ...)
api.on("theme_change", lambda theme: ...)
api.on("lang_change", lambda lang: ...)
api.on("before_format", lambda editor: ...)
api.on("after_format", lambda editor: ...)
api.once("app_ready", lambda: ...) # dispara uma única vez
api.off("file_save", ao_salvar) # remove o handler
api.emit("meu_plugin.evento", dados) # evento customizado entre pluginsEventos do núcleo: file_open, file_save, file_close, text_change, cursor_move, theme_change, lang_change, app_ready, app_exit, before_save, after_save, before_format, after_format, lint_complete, terminal_line, git_commit, tab_change, workspace_set, workspace_close, plugin_loaded, plugin_unloaded, selection_change, fold_change.
O barramento interno
EventKind(93 eventos emsrc/events/kinds.py, usado por pipelines, correlator e streams) é uma camada separada, voltada ao código do Codis; plugins usam os nomes de hook acima.
# Canal de saída próprio do plugin
canal = api.create_output_channel("Meu Plugin")
canal.append("Linha de log")
canal.appendLine("Outra linha")
canal.clear()# Armazenamento por plugin (persistido em ~/.codis/plugins/<id>/storage.json)
storage = api.get_storage()
storage.set("chave", "valor")
valor = storage.get("chave", "valor_padrao") # 2º argumento é o valor padrão (posicional)
storage.delete("chave")
storage.keys() # Lista todas as chaves armazenadas
storage.clear() # Apaga todo o armazenamento do plugin
storage.flush() # Força gravação síncrona imediata em discovalor = api.get_config("font_size") # Lê config do Codis
api.set_config("font_size", 14) # Altera config do Codisworkspace = api.get_workspace_folder() # Path da raiz do workspace (ou None)
api.open_file(Path("arquivo.py")) # Abre arquivo no editor
files = api.get_workspace_files("**/*.py", exclude=["**/.git/**"]) # Busca por padrão glob# Registrar formatador para uma linguagem
def formatar_python(content: str) -> str:
return content.upper() # exemplo trivial
api.register_formatter("python", formatar_python)
# Registrar provedor de símbolos
def obter_simbolos(content: str, lang: str) -> list[str]:
return ["MinhaClasse", "minha_funcao"]
api.register_symbol_provider("python", obter_simbolos)
# Registrar provedor de quick-fix / refatoração para uma linguagem
def provedor_quick_fix(texto, linha, col):
return [{"title": "Adicionar docstring", "command": "meu.add_docstring", "arguments": [linha]}]
api.register_code_action_provider("python", provedor_quick_fix)# Executar após delay (ms) - note: delay_ms vem antes da função
handle = api.set_timeout(1000, minha_funcao)
api.clear_timer(handle) # clear_timer cancela tanto timeout quanto interval
# Executar periodicamente
handle = api.set_interval(5000, minha_funcao)
api.clear_timer(handle) # Para automaticamente também na desativação do plugin
# Debounce: executa apenas após inatividade (delay_ms antes da função)
debounced = api.debounce(300, minha_funcao)
# Throttle: executa no máximo uma vez por intervalo (interval_ms antes da função)
throttled = api.throttle(1000, minha_funcao)# Gerenciador de contexto que exibe progresso (opcionalmente cancelável)
with api.with_progress("Processando...", cancelavel=True) as prog:
for i in range(100):
if prog.is_cancelled:
break
prog.report(i, f"Item {i}/100")# Obter instância de outro plugin pelo ID
outro = api.get_plugin("outro-plugin-id")
if outro:
resultado = api.call_plugin("outro-plugin-id", "minha_funcao", arg1, arg2)logger = api.get_logger() # Logger Python com prefixo do plugin
logger.info("Mensagem informativa")
logger.warning("Aviso")
logger.error("Erro crítico")Métodos públicos de PluginAPI que complementam as seções anteriores (assinaturas conforme src/plugins/api.py).
| Método | Descrição |
|---|---|
register_language(lang_id, patterns=None, keywords=None, label=None, extensions=None, comment=None, comment_block=None, *, indent_rules=None, bracket_pairs=None, lsp_command=None) |
Registra uma nova linguagem (highlight, extensões, comentários, indentação, LSP) |
register_decorator(name, factory) |
Registra um decorador visual do editor (anotações inline) |
remove_highlight(tag="plugin_highlight") |
Remove o highlight da tag no editor ativo |
add_status_item(item_id, text_fn, click_fn=None, side="right", *, tooltip="", priority=0) |
Adiciona item na barra de status |
update_status_item(item_id) / remove_status_item(item_id)
|
Atualiza / remove um item da barra de status |
add_context_menu_item(context, label, handler, *, when=None) |
Adiciona item a um menu de contexto |
add_settings_section(category, settings) |
Adiciona uma seção do plugin na janela de Configurações |
once(event, handler) / off(event, handler)
|
Assina um evento uma única vez / remove o handler |
emit(event, *args, **kwargs) |
Dispara um evento entre plugins (use nomes com namespace, ex.: "meu_plugin.dados_prontos") |
get_all_editors() |
Lista todos os painéis de editor abertos |
get_line(line) / get_line_count() / get_visible_range()
|
Texto de uma linha (base 1), total de linhas e faixa de linhas visíveis no editor ativo |
save_file() |
Salva o arquivo ativo |
find_files(pattern, *, root=None, max_results=1000) |
Busca arquivos por glob sob root (padrão: workspace) |
ask_input(prompt, title="Input", *, initial="") |
Diálogo de texto; retorna str ou None
|
ask_yesno(question, title="Confirmar") |
Diálogo sim/não; retorna bool
|
ask_choice(title, prompt, choices) |
Diálogo de escolha; retorna o item ou None
|
open_file_dialog(**kw) / open_files_dialog(**kw) / save_file_dialog(**kw) / ask_directory(**kw)
|
Diálogos de arquivo/pasta; retornam Path, lista de Path ou None
|
t(key, **kwargs) |
Traduz uma chave no idioma atual |
watch_config(key, callback) / migrate_config(migrations)
|
Observa mudanças de uma chave / renomeia chaves de configuração |
read_clipboard() / write_clipboard(text)
|
Lê / grava a área de transferência |
@dataclass
class PluginManifest:
name: str # Obrigatório. ID único (slug)
version: str = "1.0.0"
author: str = ""
description: str = ""
homepage: str = ""
repository: str = "" # URL do repositório fonte
icon: str = "🔌"
api_version: str = "1.0"
tags: list[str] = []
license: str = ""
category: str = "Geral"
min_codis: str = "1.0.0" # Versão mínima do Codis requerida
trusted: bool = False # Marca como confiável por padrão
readme: str = "" # Markdown de documentação inline
keywords: list[str] = []
capabilities: list[str] = [] # Ex: ["editor", "git", "lsp"]
dependencies: list[str] = [] # IDs de outros plugins requeridos
engines: dict = {} # Ex: {"python": ">=3.12"}
changelog: str = ""
funding: str = ""
preview: bool = False # Marca como experimentalPlugins podem ser distribuídos como .zip contendo:
meu-plugin.zip
├── plugin.py ← Código principal
├── README.md ← Documentação (opcional)
├── assets/ ← Recursos (opcional)
└── requirements.txt ← Dependências Python (opcional)
O painel de Extensões (sidebar → 🔌) exibe:
- Lista de plugins instalados com versão, autor e status (ativo/inativo)
- Botão para ativar/desativar cada plugin
- Botão para abrir a pasta de plugins no explorador do sistema
- Botão para instalar novo plugin
- Erros de carregamento de plugins com traceback
Quando um plugin é desativado, a API chama automaticamente _teardown(), que:
- Cancela todos os temporizadores registrados
- Fecha todos os canais de saída
- Remove todos os hooks de evento
- Limpa o armazenamento em memória
O plugin não precisa implementar limpeza manualmente - apenas declare os recursos via API e eles serão limpos automaticamente.
Plugins que realizam operações pesadas, chamadas de rede ou que acessam recursos externos podem declarar isolated: True no MANIFEST. O Codis os executará em um processo Python separado, garantindo que um travamento ou crash no plugin não afete o editor.
MANIFEST = PluginManifest(
name="meu-plugin-pesado",
version="1.0.0",
author="Autor",
description="Faz algo intensivo.",
isolated=True, # ← executa em processo separado
)- O
PluginLoaderdetectaMANIFEST.isolated = Truena instalação. - O
IsolatedPluginManagerlança um processo filho viamultiprocessing. - Toda comunicação entre o plugin e o editor é feita por filas JSON - sem acesso direto ao Tkinter ou ao estado interno do
CodisApp. - Se o processo travar, o monitor detecta ausência de heartbeat e reinicia automaticamente (até o limite
plugin_max_restarts).
| Chave | Padrão | Descrição |
|---|---|---|
plugin_isolation |
True |
Habilita/desabilita o isolamento por processo |
plugin_timeout_s |
10 |
Timeout para chamadas síncronas ao worker (s) |
plugin_heartbeat_s |
30 |
Tempo sem resposta antes de reiniciar (s) |
plugin_max_restarts |
3 |
Reinícios automáticos antes de desistir |
plugin_sandbox_network |
False |
Bloqueia rede nos workers (experimental) |
Acesse em Configurações → Plugins (Isolamento).
- Sem acesso ao Tkinter (não importar
tkinterno plugin isolado) - Sem acesso direto ao editor, abas ou arquivos abertos
- Toda interação com o editor é via mensagens da
PluginAPI - Imports de
multiprocessing,threadingsão permitidos dentro do worker
Começando
Editando
- Editor
- Inteligência do Editor
- KDocs
- Linting & LSP
- Debug
- Git
- Terminal
- Workspace & Produtividade
- Utilitários de Desenvolvimento
Assistente e conectividade
Ferramentas e extras
- Recursos Avançados
- Recursos Exclusivos
- K-UI
- Sistema & Performance
- Backups, Lixeira & Arquivos
- Painel de Saída
- Acessibilidade
- Editor de Imagens (Paint)
- Utilitários Pessoais
- Incorporar Programas e Preview Web
- DOOM Embutido
Para quem desenvolve
Projeto