TTY
Eventos de entrada de terminal, saída estilizada, surfaces de apresentação e viewports virtuais locais.
process.lua em um process.host comum é dono de um terminal virtual quando é criado com uma concessão de viewport. Sem nenhuma dessas anexações o módulo retorna "no terminal context".
Carregamento
local tty = require("tty")
Modelo
Uma Surface é o lease exclusivo de apresentação de um processo sobre sua porta de terminal. Ela publica snapshots completos de linhas; o backend cuida do diffing e da recuperação do terminal. Apenas uma surface pode estar aberta em uma porta por vez.
Um Canvas é um buffer de composição de células estilizadas dentro do processo. Ele recorta em limites de célula e nunca emite comandos de controle de terminal próprios.
Um Viewport é um limite de terminal local e estruturado que permite a um processo hospedar a surface de outro processo sem compartilhar streams de bytes. O shell decide onde o conteúdo do viewport aparece e traduz a entrada para as coordenadas do filho; o filho vê uma porta de terminal comum e não sabe se está em tela cheia, lado a lado, em abas ou oculto.
Viewports são locais a um nó de runtime. Concessões e handles são capacidades locais opacas, não referências de rede serializáveis.
Loop de Entrada
Inicia a entrega de entrada, inscreve-se nos eventos e os processa em loop:
local tty = require("tty")
local io = require("io")
local function handler()
local events = tty.events()
tty.start()
while true do
local ev, open = events:receive()
if not open then break end
if ev.type == "key" then
if ev.key == "q" or (ev.ctrl and ev.key == "c") then
break
end
local _, print_err = io.print("Key: " .. ev.key)
if print_err then loop_err = print_err; break end
elseif ev.type == "resize" then
local _, print_err = io.print("Size: " .. ev.width .. "x" .. ev.height)
if print_err then loop_err = print_err; break end
end
end
local _, stop_err = tty.stop()
if loop_err then return nil, loop_err end
if stop_err then return nil, stop_err end
return started
end
Chame events() antes de start() para que um consumidor esteja pronto quando os primeiros eventos chegarem. Em uma porta virtual, start() abre a entrega de eventos do viewer para o produtor e stop() a fecha: um Viewport:send() fora desse intervalo falha em vez de descartar a entrada silenciosamente. A entrega de resize é independente do estado da entrada.
Controle de Entrada
tty.start()
Inicia a entrega de entrada para a porta atual. Um terminal físico alterna para o modo raw.
local ok, err = tty.start()
Retorna: boolean, error
tty.stop()
Interrompe a entrega de entrada e restaura o terminal ao modo normal.
local ok, err = tty.stop()
Retorna: boolean, error
tty.events()
Inscreve-se nos eventos de terminal da porta e retorna um channel. Eventos são entregues como tabelas com um campo type. Inscreva-se uma vez e reutilize o channel.
local events, err = tty.events()
Retorna: EventChannel, error
EventChannel tem receive() e case_receive(), portanto compõe com channel.select.
tty.screen_size()
Consulta as dimensões atuais do terminal.
local width, height, err = tty.screen_size()
Retorna: number, number, error
tty.mouse(enable)
Habilita ou desabilita o rastreamento de eventos de mouse.
local ok, err = tty.mouse(true)
| Parâmetro | Tipo | Descrição |
|---|---|---|
enable |
boolean | true para habilitar, false para desabilitar |
Retorna: boolean, error
Surface
Uma surface é o lease de apresentação da porta. Adquira uma, publique frames completos, e feche-a ao terminar.
tty.surface(options?)
local surface, err = tty.surface({
alternate_screen = true,
hide_cursor = true,
synchronized_output = true,
})
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
alternate_screen |
boolean | false | Apresenta no buffer de tela alternativa do terminal |
hide_cursor |
boolean | false | Oculta o cursor do terminal enquanto a surface estiver aberta |
synchronized_output |
boolean | false | Envolve cada frame em marcadores de saída sincronizada |
Retorna: Surface, error
Abrir uma segunda surface em uma porta que já tem uma falha. Uma porta virtual mantém as opções como metadados da surface; uma porta física as traduz em modos de terminal e os restaura no fechamento.
surface:present(rows, options?)
Publica um array completo de strings de linha. A linha 1 é a linha do topo.
local stats, err = surface:present(rows, {
cursor = {x = 12, y = 3, visible = true},
images = {
{placement_id = "logo", image = logo, x = 2, y = 2, cols = 20, rows = 8, alt = "Logo"},
},
})
| Parâmetro | Tipo | Descrição |
|---|---|---|
rows |
string[] | Frame completo, no máximo 16384 linhas |
options.cursor |
table | {x, y, visible} em coordenadas de surface baseadas em 1 |
options.images |
table[] | Conjunto completo de posicionamentos de imagens retidas do frame |
Omitir cursor preserva o último estado explícito do cursor. Os três campos do cursor são obrigatórios quando cursor está presente.
Retorna: stats, error — um registro imutável com rows, changed_rows e bytes_written. Um frame físico idêntico ao anterior não escreve nada.
surface:invalidate()
Esquece o estado de apresentação do backend sem apagar o frame lógico. O próximo present é comitado mesmo com linhas inalteradas. Use-o após um resize do terminal externo ou quando outro dono possa ter perturbado o estado físico.
Retorna: boolean
surface:close()
Libera o lease. Idempotente: chamadas posteriores retornam o resultado do primeiro fechamento. Um backend físico restaura os modos do terminal.
Retorna: boolean, error
surface:capabilities()
Retorna {images = "native" | "kitty" | "pending" | "none"}. Inicie a entrada do
terminal antes da consulta. Um backend físico pode retornar brevemente pending enquanto
consulta o terminal; surfaces virtuais retêm imagens sem sondagem.
Retorna: table, error
surface:clipboard(text)
Escreve uma solicitação de clipboard OSC 52 em uma surface física. O texto deve ser UTF-8 válido e ter no máximo 65.536 bytes. Sucesso significa que a saída do terminal aceitou a solicitação; a política do terminal ainda pode ignorá-la. Surfaces virtuais retornam erro de não suportado, e a API não oferece leitura ou confirmação do clipboard.
Retorna: boolean, error
Imagens retidas
Importe um PNG para o armazenamento limitado do runtime e coloque seu handle em um frame completo:
local image = assert(tty.image(png_bytes))
local info = image:info() -- id, format, width, height, bytes
assert(surface:present(rows, {images = {{
placement_id = "preview", image = image,
x = 1, y = 1, cols = 40, rows = 12,
src = {x = 0, y = 0, width = info.width, height = info.height},
z = 1, alt = "Preview",
}}}))
tty.image() valida bytes PNG de forma assíncrona. image:read() exporta os bytes
codificados explicitamente e image:close() libera a referência. Coordenadas de pixels
de origem começam em zero e coordenadas de células de destino em um. Omitir images em
um present posterior limpa os posicionamentos anteriores; terminais físicos incompatíveis
mostram o texto alt, enquanto surfaces virtuais mantêm o recurso para viewers.
Canvas
Um canvas é um buffer limitado de células estilizadas usado para compor um frame antes de apresentá-lo.
tty.canvas(width, height)
local canvas = tty.canvas(width, height)
A largura é limitada a 16384 colunas, a altura a 16384 linhas, e a área a 262.144 células. Argumentos fora do intervalo geram um erro de argumento.
Retorna: Canvas
O desenho aceita texto estilizado, não comandos de terminal. Cores SGR e links OSC 8 são preservados; saída de apagamento, movimentação de cursor e outros controles não é emitida. Cada posicionamento é recortado independentemente em limites de célula com ciência da largura de grafemas, de modo que uma sequência de escape recortada não pode vazar para o conteúdo vizinho.
canvas:clear(fill?)
Limpa todas as células. Uma string estilizada fill opcional é repetida em cada linha.
canvas:clear()
canvas:clear(tty.style():background("#1a1a1a"):render(" "))
Retorna: boolean
canvas:put(x, y, text, width?)
Posiciona uma linha estilizada em x, y baseados em 1 e a recorta em width células (padrão: a largura do canvas). As coordenadas podem ser negativas ou além da borda; o posicionamento é recortado em vez de rejeitado. Uma quebra de linha encerra a linha, então use put_rows para conteúdo de múltiplas linhas.
canvas:put(3, 1, tty.style():bold():render("Title"), 40)
Retorna: boolean
canvas:put_rows(x, y, rows, width?)
Posiciona um array de linhas estilizadas começando em x, y, uma linha por vez para baixo. Cada entrada é validada antes de qualquer desenho.
canvas:put_rows(2, 2, child_rows, inner_width)
Retorna: boolean
canvas:rows()
Renderiza o array completo de linhas, pronto para surface:present.
Retorna: string[]
Viewport
Um viewport é uma porta de terminal virtual. O processo criador é seu primeiro viewer; o processo admitido com sua concessão é seu produtor.
tty.viewport(options?)
local view, err = tty.viewport({width = 80, height = 24})
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
width |
number | 80 | Colunas, 1 a 65535 |
height |
number | 24 | Linhas, 1 a 65535 |
page |
table | nenhuma | Padrões opacos de primeiro e segundo plano em #RRGGBB |
A área é limitada a 262.144 células.
Retorna: Viewport, error
tty.attach(handle)
Adiciona outro viewer local a um viewport existente. Um handle concede visualização, nunca a posse da apresentação, e não é válido em outro nó.
local view, err = tty.attach(handle)
Retorna: Viewport, error
viewport:grant()
Retorna a capacidade de produtor de uso único. Passe-a como a opção de spawn terminal:
local grant = assert(view:grant())
local child = assert(process.with_options({terminal = grant})
:spawn_monitored("app:child", "app:workers"))
A admissão consome a concessão transacionalmente: um start rejeitado restaura uma concessão não resolvida, enquanto um processo que resolveu a porta a consome permanentemente. Um host que não suporta anexações de terminal rejeita o spawn em vez de descartar a opção. Veja Processos.
Retorna: string, error
viewport:handle()
Retorna o handle de viewer local para tty.attach.
Retorna: string
viewport:snapshot(after_revision?)
Lê as dimensões, linhas, cursor e revisão atuais. Com after_revision, retorna nil quando a revisão não mudou.
local frame = view:snapshot(revision)
if frame then
revision = frame.revision
canvas:put_rows(2, 2, frame.rows, inner_width)
end
Retorna: snapshot ou nil
| Campo | Tipo | Descrição |
|---|---|---|
revision |
number | Revisão monotônica deste frame |
width |
number | Colunas do viewport |
height |
number | Linhas do viewport |
rows |
string[] | Linhas publicadas por último pelo produtor |
cursor |
table | {x, y, visible} em coordenadas baseadas em 1, ausente até o produtor publicar um estado de cursor explícito |
images |
table[] | Metadados dos posicionamentos de imagens retidas |
layers |
table[] | Camadas de apresentação ordenadas |
images_omitted |
boolean | Recursos de imagem existem, mas este snapshot simples não os retém |
Uma página resolve células padrão do terminal e linhas omitidas em cores explícitas. O
criador pode alterá-la com viewport:set_page(page); passar nil restaura as linhas
originais do produtor. Alterações de página avançam a revisão sem exigir nova pintura.
viewport:updates()
Retorna um channel de marcas d'água de revisão coalescidas. receive() entrega o número da revisão; case_receive() compõe com channel.select.
local updates = assert(view:updates())
As atualizações são dicas limitadas, não um log de eventos. Um viewer lento recebe apenas a marca d'água mais recente e deve chamar snapshot() para obter o estado. Apresentação e resize nunca bloqueiam por causa de um viewer lento.
Retorna: ViewportUpdateChannel, error
viewport:send(event)
Encaminha um registro de evento validado ao produtor. O produtor deve ter chamado tty.start(); caso contrário a chamada falha em vez de descartar o evento.
assert(view:send(event))
assert(view:send({type = "close"}))
Retorna: boolean, error
viewport:resize(width, height)
Atualiza a geometria do viewport. Quando o tamanho muda, os viewers recebem uma nova revisão e o produtor recebe um evento resize.
Retorna: boolean, error
viewport:close()
Desanexa apenas este viewer. Fechar o último viewer não mata um produtor ativo, e fechar a porta do produtor não destrói o estado enquanto restarem viewers.
Retorna: boolean, error
viewport:mount(recipient_pid, rights)
Emite uma referência vinculada ao processo para um viewer local ou remoto. Os direitos são
independentes e têm false como padrão:
local observation = assert(view:mount(agent_pid, {observe = true}))
local control = assert(view:mount(agent_pid, {input = true, resize = true}))
-- No processo destinatário exato, neste nó ou em um peer mesh autenticado:
local observer = assert(tty.attach(observation))
local controller = assert(tty.attach(control))
O mount é vinculado à PID completa do destinatário e só pode ser resgatado uma vez. Viewers
montados não podem criar grants de produtor ou delegar outros mounts. Use
viewport:revoke(reference) para revogar uma referência; fechar o viewport proprietário ou
encerrar seu processo revoga os mounts. Mounts remotos usam uma lease renovável; após
reconectar, exigem um novo mount e não permitem reproduzir a entrada do terminal.
viewport:capture()
Fixa atomicamente uma revisão do viewport e seus recursos de imagem retidos:
local capture = assert(view:capture())
local snapshot = capture:snapshot()
local image = assert(capture:image(snapshot.images[1].image_id))
assert(capture:close())
Um snapshot() comum não retém bytes de imagem. Um capture os mantém até ser fechado;
handles obtidos dele permanecem com propriedade independente.
Retorna: Capture, error
Tipos de Evento
Eventos são tabelas com um campo type que determina quais outros campos estão presentes. As coordenadas são baseadas em 1. Os mesmos registros são aceitos por viewport:send().
Evento de Tecla
{
type = "key",
key = "a", -- printable character or key name
key_type = "runes", -- "runes" for printable, or special key name
action = "press", -- "press" or "release"
alt = false,
ctrl = false,
shift = false
}
Evento de Mouse
Requer tty.mouse(true).
{
type = "mouse",
action = "press", -- "press", "release", "motion", "wheel"
button = "left", -- button name
x = 10,
y = 5,
alt = false,
ctrl = false,
shift = false
}
Evento de Redimensionamento
{type = "resize", width = 120, height = 40}
Evento de Início
Emitido uma vez após tty.start() com as dimensões iniciais.
{type = "start", width = 120, height = 40}
Evento de Foco
Reporta a posse do teclado.
{type = "focus", focused = true}
Evento de Visibilidade
Reporta se repintar é útil. Ele não prescreve o ciclo de vida da aplicação nem computação em segundo plano.
{type = "visibility", visible = true}
Evento de Colar
{type = "paste", text = "pasted content"}
Evento de Fechamento
Pede ao produtor que encerre. Um shell o envia por viewport:send para solicitar a saída graciosa de um filho.
{type = "close"}
Vinculações de Teclas
Crie vinculações de teclas reutilizáveis que correspondem a eventos de tecla:
local quit = tty.bind({
keys = {"q", "ctrl+c"},
help = {key = "q/ctrl+c", desc = "quit"}
})
-- In event loop
if quit:matches(ev) then
break
end
tty.bind(config)
| Campo | Tipo | Descrição |
|---|---|---|
keys |
string[] | Obrigatório. Padrões de tecla a corresponder (por exemplo, "a", "ctrl+c", "enter") |
help |
table | Opcional. {key = "...", desc = "..."} para texto de ajuda |
Retorna: KeyBinding
O schema de tipo exige keys. Em runtime, a ausência de keys ou uma tabela vazia cria uma vinculação que nunca corresponde.
Métodos de KeyBinding
| Método | Retorna | Descrição |
|---|---|---|
matches(event) |
boolean | Testa se um evento de tecla corresponde a esta vinculação |
set_enabled(bool) |
self | Habilita ou desabilita a vinculação |
is_enabled() |
boolean | Verifica se a vinculação está habilitada |
help() |
table | Retorna informações de ajuda {key, desc} |
Estilos
Crie saída estilizada para o terminal. Os valores de estilo são imutáveis, portanto cada método de estilo retorna um novo valor.
local tty = require("tty")
local io = require("io")
local title = tty.style()
:bold()
:foreground("#FF0000")
:padding(0, 1)
local box = tty.style()
:border(tty.borders.ROUNDED)
:border_foreground("#00FF00")
:width(40)
:padding(1, 2)
local _, print_err = io.print(box:render(title:render("Hello"), "World"))
if print_err then return nil, print_err end
tty.style()
Cria um novo estilo vazio.
Retorna: Style
Métodos de Estilo
Todos os métodos retornam um novo Style e podem ser encadeados.
Decoração de Texto
| Método | Parâmetro | Descrição |
|---|---|---|
foreground(color) |
string | Cor do texto (hex "#FF0000", ANSI "9", ou nome) |
background(color) |
string | Cor de fundo |
bold(enable?) |
boolean | Texto em negrito (padrão: true) |
italic(enable?) |
boolean | Texto em itálico |
underline(enable?) |
boolean | Texto sublinhado |
strikethrough(enable?) |
boolean | Texto tachado |
faint(enable?) |
boolean | Texto esmaecido |
blink(enable?) |
boolean | Texto piscante |
reverse(enable?) |
boolean | Inverte primeiro plano/fundo |
Layout
| Método | Parâmetro | Descrição |
|---|---|---|
width(n) |
number | Largura fixa |
height(n) |
number | Altura fixa |
max_width(n) |
number | Largura máxima |
max_height(n) |
number | Altura máxima |
padding(...) |
numbers | Padding (estilo CSS: top, right, bottom, left) |
margin(...) |
numbers | Margin (estilo CSS) |
align(pos) |
number | Alinhamento horizontal |
align_vertical(pos) |
number | Alinhamento vertical |
inline(enable?) |
boolean | Modo de renderização inline |
Bordas
| Método | Parâmetro | Descrição |
|---|---|---|
border(name, ...) |
string, booleans | Estilo de borda, alternâncias opcionais por lado |
border_foreground(...) |
strings | Cor(es) de borda |
border_background(...) |
strings | Cor(es) de fundo da borda |
Outros
| Método | Descrição |
|---|---|
render(...) |
Renderiza strings com este estilo aplicado |
copy() |
Cria uma cópia deste estilo |
Constantes de Borda
tty.borders.NORMAL
tty.borders.ROUNDED
tty.borders.THICK
tty.borders.DOUBLE
tty.borders.HIDDEN
Constantes de Alinhamento
tty.align.LEFT -- 0
tty.align.CENTER -- 0.5
tty.align.RIGHT -- 1
Utilitários de Texto
Funções de layout e medição para texto estilizado. Disponíveis sob tty.text.
Medição
local w = tty.text.width("hello") -- printable width (ANSI-aware)
local h = tty.text.height("a\nb\nc") -- line count
local w, h = tty.text.size("hello\nworld") -- both
Recorte
-- Trunca para uma largura imprimível, com um sufixo opcional
local head = tty.text.truncate(line, 40)
local head = tty.text.truncate(line, 40, "…")
-- Pega o intervalo de células imprimíveis [left, right)
local middle = tty.text.cut(line, 10, 30)
Ambos preservam o estado ANSI e os limites de grafema, de modo que texto estilizado pode ser recortado e emendado sem quebrar sequências de escape. truncate retorna uma string vazia para uma largura zero ou menor; cut retorna uma string vazia quando right não é maior que left.
Junção
-- Join side by side, aligned at top
local row = tty.text.join_horizontal(tty.text.position.TOP, left, right)
-- Stack vertically, centered
local col = tty.text.join_vertical(tty.text.position.CENTER, top, bottom)
Dimensões Máximas
local w = tty.text.max_width({"short", "a longer string"}) -- widest
local h = tty.text.max_height({"one\ntwo", "single"}) -- tallest
Posicionamento
Posiciona uma string dentro de uma caixa de dimensões dadas:
-- Center in a 80x24 box
local out = tty.text.place(80, 24, tty.text.position.CENTER, tty.text.position.CENTER, content)
-- Horizontal only
local out = tty.text.place_horizontal(80, tty.text.position.RIGHT, content)
-- Vertical only
local out = tty.text.place_vertical(24, tty.text.position.BOTTOM, content)
Constantes de Posição
tty.text.position.TOP -- 0
tty.text.position.LEFT -- 0
tty.text.position.CENTER -- 0.5
tty.text.position.BOTTOM -- 1
tty.text.position.RIGHT -- 1
Permissões
O terminal físico vem do frame do processo. Anexar um produtor por meio de
process.with_options({terminal = grant}) requer process.context no spawn.
Viewports delegados também verificam tty.mount, tty.observe, tty.input e
tty.resize contra o handle do viewport proprietário.
Veja Também
- UI de Terminal — construa um shell que hospeda um filho em um viewport
- I/O do Terminal — operações de stdin/stdout/stderr
- Terminal Host — Configuração do host de terminal
- Execução de Comandos — processos PTY e sessões de terminal
- Processos — opções de spawn, monitoramento, eventos de ciclo de vida