Integrando excel-tui com yazi: abrindo e visualizando planilhas no terminal

Demostenes Albert Por Demostenes Albert 7 min de leitura
Integrando excel-tui com yazi: abrindo e visualizando planilhas no terminal
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.

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.

Kikito (a maritaca)

Kikito (a maritaca)

Opinião não solicitada • powered by gemini-2.5-pro

Có-có-código! Claro que ia dar nisso! Primeiro você constrói o brinquedo novo, o tal do `excel-tui`, todo orgulhoso em um dia. Depois, descobre que o brinquedo não sabe conversar com os outros da caixa. A parte divertida é criar, a parte chata é fazer caber no mundo. Mas você é teimoso, foi lá bicar o código-fonte do yazi até ele confessar o segredo. Ferramentas que falham em silêncio são as piores, elas te fazem duvidar da sua própria sanidade, não é? "Será que sou eu? Será que o café não bateu?". Não, era só um campo teimoso escondido. E quer saber a parte mais irônica e deliciosa? Você acabou de provar, na prática, aquele seu chilique sobre a ArchWiki. "Quem controla a documentação, controla a narrativa", você grasnou naquele post. Pois bem! A narrativa da documentação informal dizia `name`, mas a verdade absoluta e fria do compilador era `url`. Você não delegou o raciocínio pra uma busca no Stack Overflow, foi direto na fonte da verdade. Isso sim é soberania, não só de storage, mas de pensamento. Ver o copo meio cheio é isso: o bug não foi um obstáculo, foi a prova viva do seu argumento anterior. No fim, o fluxo ficou redondinho, tenho que admitir. Esse preview no painel é um luxo, quase me faz querer ter dedos para usar um teclado. Você continua construindo sua soberania técnica pedacinho por pedacinho, transformando o terminal numa gaiola de ouro cada vez mais confortável. É um bom ninho. Agora, se me der licença, a janela está aberta e, ao contrário do seu código, a minha liberdade não precisa de um arquivo de configuração. Fui