Abrir .xlsx no terminal em 2026 ainda quebra silenciosamente.
O problema não era o excel-tui. Nem o yazi. Era um campo de configuração que a documentação informal dizia existir, mas o código-fonte não aceitava. E o yazi, fiel ao seu estilo, não disse nada. Simplesmente não fez nada.
Este artigo documenta essa integração: como conectar o excel-tui ao yazi para abrir planilhas com Enter e ver um preview no painel lateral antes de abrir. Se você construiu o excel-tui a partir do zero em um dia e já configurou o yazi com outro TUI antes, o terreno é familiar, mas o bug específico desta integração vale documentar.
Parte 1: configurando o opener
A integração básica, abrir planilhas com Enter, pareceu simples. Adicionei o opener no yazi.toml:
[opener]
excel = [
{ run = 'excel-tui "$@"', block = true, desc = "SpreadSheet" }
]
[open]
rules = [
{ mime = "text/csv", use = "excel" },
{ mime = "application/vnd.ms-excel", use = "excel" },
{ mime = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", use = "excel" },
{ name = "*.xls", use = "excel" },
{ name = "*.xlsx", use = "excel" },
]
block = true é o mesmo padrão do helix: faz o yazi pausar e aguardar o excel-tui fechar antes de voltar ao painel. Sem isso, o yazi tentaria renderizar por cima do TUI enquanto ele ainda está ativo.
Resultado: CSV abria com o excel-tui, mas .xlsx e .xls não abriam nada.
Parte 2: debugando o problema
Primeiro passo: verificar se era detecção de MIME. O yazi usa file -bL --mime-type internamente, confirmado pelo yazi --debug:
Routine
`file -bL --mime-type`: text/plain
Rodei o mesmo comando nos arquivos:
$ file -bL --mime-type planilha.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet $ file -bL --mime-type planilha.xls application/vnd.ms-excel
MIMEs corretos. O problema estava em outro lugar.
Parte 3: lendo o código-fonte do yazi
Fui direto ao código-fonte do yazi no GitHub. A struct que processa as regras de abertura é a Selector em yazi-config/src/selector.rs:
pub struct Selector {
pub url: Option<Pattern>,
pub mime: Option<Pattern>,
}
Só dois campos: url e mime. Não existe campo name.
A validação é explícita:
impl Selector {
pub fn new(url: Option<Pattern>, mime: Option<Pattern>) -> Result<Self> {
ensure!(url.is_some() || mime.is_some(),
"at least one of `url` or `mime` must be specified");
Ok(Self { url, mime })
}
}
Quando o serde tenta deserializar { name = "*.xlsx", use = "excel" }, o campo name é ignorado como campo desconhecido, resultando em url = None, mime = None. O ensure! lança um erro que descarta o array de rules inteiro silenciosamente, e o yazi cai no comportamento padrão, que tem handlers para text/* (CSV funciona) mas não para binários como .xlsx e .xls.
O yazi não grita. Ele simplesmente não faz nada. O campo correto é url.
A correção
Trocar name por url em todas as regras:
[open]
rules = [
{ mime = "text/csv", use = "excel" },
{ mime = "application/vnd.ms-excel", use = "excel" },
{ mime = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", use = "excel" },
{ url = "*.xls", use = "excel" }, # ← era name
{ url = "*.xlsx", use = "excel" }, # ← era name
]
Com isso, xlsx e xls passaram a abrir normalmente.
Parte 4: preview no painel lateral
Ter o opener funcionando é ótimo, mas o yazi mostra um preview vazio para .xlsx e .xls ao navegar. O ideal
é ver as primeiras linhas da planilha antes de abrir, o mesmo princípio do preview de imagens, PDF, ou texto que o yazi já faz para outros formatos.
é ver as primeiras linhas da planilha antes de abrir, o mesmo princípio do preview de imagens, PDF, ou texto que o yazi já faz para outros formatos.
Adicionando --preview ao excel-tui
Como o excel-tui já lê os três formatos e os representa como TableData com Sheet e Cell, bastou adicionar um modo não-interativo que imprime uma tabela formatada na stdout. Sem inicializar o terminal em raw mode, sem ratatui, sem loop de eventos. Só leitura e impressão.
O novo fluxo de argumentos:
- excel-tui arquivo.xlsx → modo TUI (comportamento original)
- excel-tui --preview arquivo.xlsx → imprime as primeiras 30 linhas
- excel-tui --preview N arquivo.xlsx → imprime as primeiras N linhas
Trecho da função de renderização:
fn print_sheet(sheet: &Sheet, max_rows: usize) {
let display_rows = sheet.rows.len().min(max_rows);
let widths: Vec<usize> = (0..sheet.col_count())
.map(|c| {
(0..display_rows)
.map(|r| sheet.get(r, c).to_string().chars().count())
.max()
.unwrap_or(0)
.clamp(1, 20)
})
.collect();
for r in 0..display_rows {
let line: Vec<String> = (0..sheet.col_count())
.map(|c| pad_cell(&sheet.get(r, c).to_string(), widths[c]))
.collect();
println!(" {}", line.join(" "));
if r == 0 {
let sep = widths.iter().map(|&w| "─".repeat(w)).collect::<Vec<_>>().join(" ");
println!(" {sep}");
}
}
}
A largura de cada coluna usa .clamp(1, 20), o mesmo princípio de amostragem que o modo TUI usa para colunas grandes: não vale varrer o arquivo inteiro para alinhar perfeitamente uma coluna que ninguém vai ver completa de qualquer forma. Equilíbrio entre precisão e custo.
O resultado para um xlsx real:
Nome Valor Data ──────────── ────── ────────── Alice 1234 2024-01-01 Bob 567 2023-06-15 Carol 8901 2024-03-22
Plugin Lua para o yazi
Criei ~/.config/yazi/plugins/excel.yazi/main.lua:
local M = {}
function M:spot(job)
local child, err = Command("excel-tui")
:args({ "--preview", tostring(job.area.h), tostring(job.file.url) })
:stdout(Command.PIPED)
:stderr(Command.PIPED)
:spawn()
if not child then
ya.err("excel-preview: " .. tostring(err))
return
end
local output, err = child:wait_with_output()
if not output then
ya.err("excel-preview: " .. tostring(err))
return
end
ya.preview_widgets(job, {
ui.Text(output.stdout):area(job.area),
})
end
return M
O plugin passa job.area.h, a altura do painel de preview em linhas, como o N do --preview. O preview sempre preenche o espaço disponível, independente de como o yazi está dividido na tela. Sem hardcode, sem truncamento desnecessário.
Registrando o spotter no yazi.toml
[plugin]
prepend_previewers = [
{ mime = "text/csv", run = "excel" },
{ mime = "application/vnd.ms-excel", run = "excel" },
{ mime = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", run = "excel" },
{ url = "*.xls", run = "excel" },
{ url = "*.xlsx", run = "excel" },
]
Edit: aqui deveria ser prepend_previewers e não prepend_spotters
prepend_spotters garante que o plugin excel tem prioridade sobre o spotter padrão de texto, sem remover os outros spotters embutidos do yazi. A mesma distinção url vs name se aplica aqui.
Resultado final
Navegar por arquivos no yazi agora exibe uma tabela formatada no painel lateral para qualquer planilha. Pressionar Enter abre o arquivo no excel-tui para edição interativa. O fluxo que quebrava, sair do terminal, abrir uma GUI pesada, corrigir uma célula, voltar, deixou de existir.
O que esse processo revelou, além da configuração em si:
O yazi falha silenciosamente. Campos desconhecidos são ignorados. Rules inválidas são descartadas sem aviso. yazi --debug é a única janela para o que está acontecendo de verdade. Se algo não funciona, a primeira pergunta não é "o que está errado na minha config?" mas "o que o código-fonte diz que o yazi realmente aceita?". Documentação informal mente; o compilador não.
A interface entre ferramentas é sempre onde as suposições explodem. O excel-tui lia xlsx corretamente. O yazi abria outros TUIs corretamente. A integração quebrou num campo de configuração que parecia válido e não era. Esse é o tipo de problema que não aparece em teste unitário nenhum, só aparece quando você coloca as duas coisas juntas e vê o que acontece.
Tooling moderno vive de configuração declarativa, plugins e integração entre processos. Isso significa que cada campo mal documentado vira uma fronteira invisível entre "funciona perfeitamente" e "não faz absolutamente nada". E quase sempre o terminal escolhe silêncio ao invés de erro. Saber disso não resolve o bug, mas te faz procurar no lugar certo na primeira vez.
O projeto está em: github.com/demostenex/kikito-spreadsheet