pular para o conteúdo
nvim · ~/athaydelab/artigos/lf-substituto-nautilus/index.md ws 2 · monocle

Tecnologia

lf e Yazi no lugar do Nautilus: onde o preview gráfico quebra

O terminal mostra imagem, mas o preview gráfico do lf tem limites reais. A tentativa com Ghostty, chafa e Hyprland, o que ela ensinou e por que o Yazi entrou no meu uso.

≈ 10 min74a3a6a

neste texto
  1. O lf não sabe fazer nada (e isso é a força dele)
  2. A tentativa: imagem de verdade no terminal
  3. O bug que me travou (e o que ele ensina)
  4. O ecossistema que faz o “nível Nautilus”
  5. Quanto pesa o lf: ~20 MB de RAM
  6. Você não baixou um app: montou o seu
  7. Onde o Yazi entra no meu uso

Abrir o Nautilus para mover três arquivos sempre me pareceu um exagero de cerimônia: tirar a mão do teclado, mirar o mouse, clicar, arrastar. Eu queria um gerenciador de arquivos que vivesse onde eu já vivo: no terminal. O obstáculo de sempre, aquele que todo mundo repete: “mas no terminal você não vê as imagens”.

Acontece que essa frase está errada desde, pelo menos, 2017: o terminal mostra imagem. Mas a descoberta não transforma automaticamente o lf, um binário único de 6 MB, sem dependências de runtime, num substituto integral do Nautilus. O preview gráfico expõe uma fronteira real entre uma TUI de texto e um gerenciador de arquivos que controla uma interface gráfica inteira.

O lf não sabe fazer nada (e isso é a força dele)

lf (“list files”) é um gerenciador de arquivos de terminal escrito em Go, herdeiro do ranger.O ranger é escrito em Python e arrasta o interpretador inteiro a cada abertura. O lf é um executável estático único: abre instantâneo, e o lfrc de configuração é, literalmente, um shell script. O que mais me chamou atenção não foi a velocidade, mas a filosofia: o lf não sabe abrir um PDF, tocar um vídeo nem mostrar uma imagem. Ele delega tudo a programas externos.

Isso parece limitação. É o oposto. Significa que você não recebe um app fechado com as decisões de outra pessoa. Você costura o gerenciador dos seus sonhos a partir de ferramentas que já existem no sistema. O lf é só a cola e a navegação. O modelo mental correto: ele não compete com o Nautilus, ele compete com cd + ls + mv. O que o eleva ao nível do Nautilus é o ecossistema que você pendura ao redor.

A tentativa: imagem de verdade no terminal

Terminais foram feitos para texto. Para mostrar um JPEG num grid de caracteres, o terminal precisa implementar um protocolo gráfico. Existem três grandes:

ProtocoloQuem usaComo funciona
Sixelxterm, foot, terminais DEC antigosbitmap codificado em texto
kitty graphicskitty, Ghostty, WezTermimagem enviada via escape sequences
iTerm2 inlineiTerm2 (macOS)base64 embutido na linha

Eu uso o Ghostty, que implementa o kitty graphics protocol nativamente. É por isso que dá para ver a miniatura real de uma foto, não uma aproximação em ASCII art, mas o pixel.

A ponte entre o arquivo e o protocolo é o chafa. Ele lê a imagem e emite os escape codes que o Ghostty entende:

emitir kitty graphics bash
1
2
3
chafa -f kitty -s 80x25 foto.png
# -f kitty  → fala o protocolo do kitty (que o Ghostty entende)
# -s 80x25  → desenha em no máximo 80 colunas por 25 linhas
NORMAL emitir kitty graphics bash · utf-8 · LF · 3 L

A saída desse comando começa com ESC _ G a=T,f=32,...: a sintaxe do protocolo. O Ghostty recebe esses bytes e pinta a foto. Sem intermediário, sem servidor gráfico.

O bug que me travou (e o que ele ensina)

Aqui entra a parte que vale o post inteiro, porque é o tipo de armadilha que nenhum tutorial conta.

A primeira coisa que tentei foi a receita mais copiada da internet: usar o kitten icat no previewer. O previewer é o script que o lf chama toda vez que você move o cursor para um arquivo. Resultado: nada aparecia. O motivo é simples e ninguém avisa: o kitten é o utilitário do terminal kitty, e eu não uso o kitty. No Ghostty esse binário não existe. A receita popular pressupõe um terminal que eu não tenho.

A solução foi trocar kitten icat por chafa -f kitty, que fala o mesmo protocolo sem depender do kitty. Instalei o chafa, reabri o lf, e… apareceu uma sopa de letrinhas no painel de preview:

_Ga=T,f=32,s=434,v=256,c=62,r=16,m=1,q=2\_Gm=1;EBAQ/xAQEP8...
lf aberto na pasta Downloads com o painel de preview mostrando códigos de escape como texto
O previewer mandou os códigos do protocolo gráfico para o stdout, e o lf, que captura o stdout, os imprimiu como texto em vez de desenhar a imagem. Listagem desfocada nesta publicação.

Os códigos do protocolo, impressos como texto em vez de virarem imagem. Foi aqui que a ficha caiu sobre como o lf funciona por dentro:

⚠ A raiz do problema
O lf captura o stdout do previewer e imprime aquilo como texto dentro do painel. Se o chafa manda os escape codes para o stdout, o lf os trata como texto comum, e você vê a sopa de letrinhas, não a foto.

A tentativa de correção foi escrever a imagem direto no terminal (/dev/tty), driblando a captura do lf, e posicionar o cursor na coordenada que o próprio lf informa. O lf passa ao previewer cinco argumentos: caminho, largura, altura, coluna (x) e linha (y). Usei x e y para mirar o canto do painel:

preview.sh: desenhar no /dev/tty bash
1
2
3
4
5
6
7
8
# lf passa: $1=arquivo $2=largura $3=altura $4=coluna(x) $5=linha(y)
draw_image() {
    # posiciona o cursor no canto do painel...
    printf '\033[%d;%dH' "$y" "$x" > /dev/tty
    # ...e escreve a imagem DIRETO no terminal, não no stdout do lf
    # chafa se comporta mal dentro do lf quando herda LF_LEVEL.
    env -u LF_LEVEL chafa -f kitty -s "${w}x${h}" --animate off "$1" > /dev/tty 2>/dev/null
}
NORMAL preview.sh: desenhar no /dev/tty bash · utf-8 · LF · 8 L

Reabri o lf, parei o cursor sobre um JPEG, e a imagem renderizou no painel, idêntica ao que eu via rodando o chafa solto. Dois detalhes finos pareciam fechar o conjunto: o previewer terminava com exit 1 (código ≠ 0 diz ao lf para nunca cachear preview de imagem, forçando o redesenho); e um cleaner mandava a sequência “apague todas as imagens” (ESC _ G a=d ESC \) quando você saía do arquivo.

Correção posterior: LF_LEVEL, Wayland e o limite do painel

Depois de usar a configuração por mais tempo, apareceu um defeito intermitente: ao descer pela lista, a miniatura podia parar de atualizar; redimensionar a janela do Hyprland fazia o preview reaparecer. O lf exporta LF_LEVEL para marcar instâncias aninhadas, e o chafa altera seu comportamento quando encontra essa variável; env -u LF_LEVEL corrige uma parte do problema, como confirmado pelo mantenedor do lf na issue #2574.

Mas havia outra falha, mais fundamental: imagens transmitidas diretamente ao terminal não participam do repaint da TUI do lf. Ao navegar, o lf redesenha células de texto enquanto o Ghostty mantém a imagem gráfica em outra camada; o resultado são resíduos e caracteres aparentes na tela. Redimensionar força um repaint e mascara o defeito, mas não o corrige. Portanto, essa receita não é confiável no lf 41 com Ghostty e Hyprland.

O caminho que costuma ser indicado para esse cenário é o ueberzugpp, sucessor Wayland do overlay que o Ranger usava: ele desenha a imagem numa janela sobreposta ao painel, fora da grade de texto do lf. Não cheguei a adotá-lo; registro como a direção a testar, não como resultado.

E mesmo um preview que funcione não resolve tudo. Uma página de PDF cheia de texto, reduzida ao espaço de um painel, continua ilegível: aumentar a resolução da rasterização não cria espaço físico para as letras. O preview serve para identificar o arquivo, a página e o tipo de conteúdo; não para ler o documento. Foi nesse ponto que o Yazi entrou no meu uso, como conto no fim. O lf permanece excelente para navegação, seleção e operações de arquivo, mas não recebe aqui a promessa de substituir um navegador gráfico em todos os casos.

lf aberto na pasta Downloads com a imagem 2B renderizada de verdade no painel de preview à direita
O experimento com /dev/tty chegou a renderizar a miniatura real, mas repare nos restos de texto sobrepostos à lista: são os resíduos de repaint descritos acima. Listagem desfocada nesta publicação.

A lição de método é a que mais me interessa: a receita mais popular pressupunha um terminal que eu não usava, e o sintoma, letrinhas em vez de imagem, só fazia sentido depois de entender que o lf captura o stdout. Copiar config dos outros funciona até o dia em que não funciona; aí você precisa saber o que cada linha faz.

O ecossistema que faz o “nível Nautilus”

Imagem à parte, o resto é pendurar ferramentas. Cada uma assume um pedaço do que o Nautilus faz num pacote só:

FerramentaPapel
chafaimagem pelo protocolo kitty, com as ressalvas acima, ou em caracteres como fallback
trash-clilixeira no padrão freedesktop, a mesma do GNOME
batpreview de código com cor e número de linha
ezalistagem de pasta com ícones
glowpreview de Markdown renderizado
popplerrasteriza a 1ª página do PDF / extrai texto
ffmpegthumbnailerframe de miniatura de vídeo
fd + fzfbusca fuzzy recursiva de arquivos e pastas
zoxide“teletransporte” para as pastas mais visitadas

A lixeira merece nota. Em vez de um mv para uma pasta qualquer, o trash-cli escreve os metadados .trashinfo corretos, então o que você apaga no lf aparece na lixeira do sistema e pode ser restaurado de lá. Integração de verdade, não um arremedo.

PDF e vídeo seguem a mesma lógica: para PDF, o poppler rasteriza a primeira página num PNG temporário; para vídeo, o ffmpegthumbnailer extrai um frame. O preview vira uma função, e cada tipo de arquivo só precisa produzir uma imagem para ela. Com isso, herdam também o mesmo limite de repaint da imagem.

Quanto pesa o lf: ~20 MB de RAM

Um gerenciador de arquivos completo deveria pesar, certo? Medi na minha máquina, com o Nautilus e o lf abertos e ociosos, uma instância de cada. A medição é só do processo do lf; cada previewer chamado soma a sua memória enquanto roda:

RSSPSS, o footprint realRSS conta toda a memória mapeada pelo processo, incluindo bibliotecas compartilhadas com o resto do desktop. PSS divide cada biblioteca compartilhada pelo número de processos que a usam: é a medida mais justa de quanto um programa de fato “custa” a mais.
Nautilus419 MB296 MB
lf21 MB15 MB

O lf usa cerca de 1/20 da memória do Nautilus: uns 5% do footprint, para navegar e operar arquivos. E a diferença não está no binário (são 6 MB de Go contra 1,5 MB do Nautilus); está em tudo que o Nautilus arrasta atrás de si: GTK, thumbnailers, indexadores, daemons. O lf carrega o que você mandou carregar, e nada além.

btop ordenado por memória mostrando o Nautilus em 416 MB entre processos do Brave e do Chromium
btop ordenado por memória. O Nautilus (416 MB, linha destacada) senta-se entre o Brave e o Chromium: um gerenciador de arquivos custando tanto quanto um navegador inteiro. O lf, em seus ~20 MB, está dezenas de linhas abaixo: não alcança nem a lista dos pesos-pesados.

Dá para conferir no seu próprio sistema, com os dois abertos:

medir o footprint real (PSS) bash
1
2
3
4
5
for p in nautilus lf; do
  pid=$(pgrep -x "$p" | head -1)
  pss=$(awk '/^Pss:/{s+=$2} END{printf "%.0f", s/1024}' /proc/$pid/smaps_rollup)
  echo "$p: ${pss} MB (PSS)"
done
NORMAL medir o footprint real (PSS) bash · utf-8 · LF · 5 L

Você não baixou um app: montou o seu

É essa a diferença que eu queria registrar. O Nautilus é uma decisão fechada que você aceita inteira. O lf é um esqueleto de 6 MB sobre o qual você empilha exatamente as peças que usa, cada uma fazendo bem uma coisa só: a velha filosofia Unix, aplicada ao gesto mais banal do dia: andar entre arquivos.

E a frase “no terminal você não vê imagens” era só uma frase que ninguém tinha conferido. O que existe é outro limite, mais interessante: o de uma TUI de texto tentando conviver com uma camada gráfica.

Onde o Yazi entra no meu uso

Depois dessa experiência passei a usar também o Yazi, outro gerenciador de arquivos de terminal, escrito em Rust. A diferença que me levou a ele é justamente a deste texto: o Yazi trata o preview de imagem como parte do programa, e não como um script externo tentando desenhar por cima da interface. Ele detecta o protocolo gráfico do terminal e redesenha a imagem junto com o resto da tela, que é exatamente o ponto em que a minha receita com o lf quebrava.

Não troquei um pelo outro. O lf continua sendo a ferramenta mínima, configurável por shell script, muito próxima do modelo cd + ls + mv descrito aqui. O Yazi entra quando quero navegar entre imagens, PDFs e vídeos vendo o que estou escolhendo. São duas respostas para o mesmo gesto: sair do gerenciador gráfico sem abrir mão de navegar, pré-visualizar e operar arquivos com o teclado. Uma comparação dos dois, com medições reproduzíveis, fica para uma nota própria.

Aparato

O que sustenta este texto: fontes citadas, textos que o mencionam, vizinhança temática e histórico de revisão.

Cronologia

A ordem em que os textos entraram no acervo.

Revisão

Uma nota muda com o tempo; o histórico fica visível.

publicado
última revisão
commit
74a3a6a
arquivo
artigos/lf-substituto-nautilus/index.md

Assuntos

Cada assunto abre o próprio índice.

lfyaziterminalghosttykitty-graphicsfile-managerchafatui

← voltar ao índice do acervo