12 pontos por GN⁺ 2025-08-13 | 1 comentários | Compartilhar no WhatsApp
  • Introdução de como implementar, no Bash e no Zsh, um recurso de autocompletação por Tab que exibe descrições até mesmo para palavras já completas
  • Bash e Zsh usam APIs de autocompletação diferentes, e apenas o Zsh oferece nativamente a função de mostrar descrições na autocompletação
  • A estrutura implementa a geração de candidatos com _generate_foo_completions e o retorno via COMPREPLY no Bash e compadd no Zsh
  • Para implementar no Bash o recurso de descrições do Zsh, o texto da descrição é incluído na string candidata, e removido apenas quando há um único candidato
  • Mesmo no caso de um único candidato, a solução melhora a experiência ao adicionar ambiguidade intencionalmente para que a descrição apareça ao pressionar <TAB>
  • O script final oferece a mesma experiência de uso nos dois shells, com suporte a completação parcial, completação total e exibição de descrições dos candidatos

Contexto do problema

  • A autocompletação por Tab (tab-completion) é útil para explorar comandos e flags, especialmente ao usar uma API ou ferramenta de CLI pela primeira vez
  • O Zsh, por padrão, mostra descrições apenas quando há vários candidatos, enquanto o Bash só permite isso com configuração adicional
  • Porém, para palavras já completas, os dois shells não exibem descrições
  • Isso obriga o usuário a passar por um processo incômodo como este para ver a descrição
    • apagar alguns caracteres para que vários candidatos passem a corresponder
    • pressionar a tecla <TAB> para ver a lista de candidatos
    • localizar visualmente a descrição desejada
    • digitar novamente os caracteres apagados para executar o comando

Visão geral da solução

  • Mesmo quando há apenas um candidato, adiciona-se um candidato fictício (dummy completion) para tornar a lista ambígua
  • Assim, tanto o Bash quanto o Zsh passam a mostrar a lista de candidatos junto com as descrições
  • É preciso, no entanto, tomar cuidado para que o texto da descrição não seja inserido no comando real

Conceitos básicos

  • A autocompletação por Tab funciona recebendo a palavra atual e a posição do cursor ao pressionar <TAB>, e retornando uma lista de candidatos possíveis
  • Bash: atribui os candidatos ao array COMPREPLY
  • Zsh: registra os candidatos com o comando compadd
  • A função _generate_foo_completions imprime as strings candidatas e, em uso real, pode gerá-las dinamicamente com base no estado da CLI

Suporte simultâneo a Bash e Zsh

  • Implementação específica para cada shell com as funções _complete_foo_bash e _complete_foo_zsh
  • Distinção com if [ -n "${ZSH_VERSION:-}" ]; then ... elif [ -n "${BASH_VERSION:-}" ]; then ... fi
  • O usuário registra o script no .bashrc ou .zshrc e então o aplica

Exibição de descrições no Zsh

  • Usa-se o formato nome: descrição nas strings candidatas
  • Zsh: compadd -d raw -- $trimmed para passar nome e descrição em arrays paralelos
  • Bash: apenas os candidatos com a parte da descrição removida são passados para COMPREPLY (sem suporte nativo a descrições)

Implementando descrições no Bash

  • Quando há vários candidatos, expõem-se diretamente as strings com descrição incluída
  • Quando há apenas um candidato, a descrição é removida
  • Isso aproveita o comportamento de autocompletação do Bash, que insere apenas o prefixo comum, evitando que a descrição seja incluída na entrada real

Exibir descrição mesmo com um único candidato

  • Para mostrar a descrição mesmo ao pressionar <TAB> sobre uma palavra completa, adiciona-se um candidato fictício para induzir ambiguidade
  • Zsh: adiciona-se o candidato fictício em ambos os arrays paralelos (raw, trimmed)
  • Bash: quando há apenas um candidato, adiciona-se apenas o nome em trimmed

Resultado final

  • Com vários candidatos, nome + descrição são exibidos
  • Mesmo com um único candidato, é possível ver a descrição com <TAB>
  • Bash e Zsh oferecem a mesma experiência
  • Exemplo de uso:
    $ foo <TAB>  
    apple: a common fruit banana: starchy and high in potassium  
    apricot: sour fruit... cherry: small and sweet...  
    

1 comentários

 
GN⁺ 2025-08-13
Comentário no Hacker News
  • No fish, se um programa de interesse oferece uma página man, basta executar fish_update_completions
    • Esse comando faz o parse de todas as páginas man do sistema e gera arquivos de autocompletar em ~/.cache/fish/generated_completions/
    • Se a página man for fraca ou não existir, dá para escrever uma manualmente e contribuir upstream
    • O formato do fish é simples, então só a documentação oficial já basta
    • Ex.: curl com -L → 'Follow redirects', -O → 'Write output to file named as remote file'
    • Quando compartilho a tela, as pessoas acham que uso zsh com plugins, mas na verdade estou usando um fish bonito só com a configuração padrão
    • Mas seria bom melhorarem o fato de que car TAB expande para blkdiscard por causa do autocompletar sem prefixo, então o cargo pode falhar mesmo sem estar no PATH
    • No caso de programas que não têm página man e oferecem só --help, fico curioso se o fish tem algo como o _gnu_generic do zsh ou o complete -F _longopt do bash
    • Também existe um script que gera autocompletar com base em páginas man no zsh → zsh-manpage-completion-generator
    • No OpenSUSE, ao digitar zypper search fish-completion, aparecem mais de 200 pacotes, então senti que havia algo estranho
  • É incômodo que o bash, à medida que vai ficando mais “inteligente”, impeça a conclusão de arquivos/diretórios quando decide que um nome de arquivo não é apropriado na posição atual do cursor
    • Eu preferiria que sempre houvesse fallback para completar nomes de arquivo
    • Por causa disso, já pensei em desligar todos os scripts de completion
    • O bash tem funções como complete-filename, ligada a M-/, que completa só nomes de arquivo ignorando o contexto
    • A conclusão de arquivos para certos comandos fica totalmente quebrada, então contorno completando primeiro com ls e depois trocando o comando
    • Se o arquivo existe mas não é executável, seria menos confuso mostrar uma mensagem como “file foo.exe exists but it isn't executable”
    • Como tudo funciona do jeito que eu quero depois de executar complete -r, parece que o problema está nos scripts do bash-completion
    • Parece uma UX frustrante parecida com a validação no frontend em formulários web que mostra “Invalid email!” depois de digitar só a primeira letra
  • O texto é meu, e espero que outras pessoas também achem a leitura interessante
    • Em vez de carregar o script de completion do zsh toda vez, instalá-lo em $fpath faz com que ele seja armazenado em cache e melhora a velocidade de inicialização
    • Ao distribuir com Homebrew, dá para instalar completions automaticamente
    • As completions do zsh são grandes e difíceis de aprender, mas no trabalho venho refinando aos poucos um wrapper de ansible da empresa, adicionando autocompletar de opções por playbook, tags etc.
  • Recentemente comecei a usar a biblioteca usage no desenvolvimento de CLI → integra com clap e consegue gerar completions, argparse e páginas man
    • Ainda estou pensando se vale a pena substituir os blocos argparse dos scripts fish, mas é muito melhor que optparse
    • Eu também estou criando uma ferramenta parecida para definir especificações de CLI, e acabei de adicionar suporte a fish
  • Foi apresentado o fx.wtf, que suporta autocompletar de campos JSON em bash/zsh
    • É mais leve que o ijq, mas pode ser útil dependendo do caso
  • Tutorial para criar um completer com funções embutidas do zsh → zsh-completions-howto
  • Fico curioso se existe alguma flag padrão que permita a um programa evitar ter de escrever scripts bash
    • No zsh, o _gnu_generic permite uma completion simples baseada em --help
    • Há exemplos como o clap_complete do Rust, a opção --generate do ripgrep e a geração de completion em tempo de execução do PHP Symfony
    • Seria bom existir um padrão --completion que bibliotecas de parser comuns implementassem automaticamente
  • No ksh, dá para implementar completions simples só com definição de arrays
    • Ex.: se você colocar nomes de sinais no array complete_kill_1, o comando kill passa a oferecer completion para o primeiro argumento
    • Fico curioso se isso é sintaxe do ksh93 ou se foi backportado para o oksh
  • Exemplo de um snippet simples de completion para zsh que eu fiz: na função set-java-home, uso _describe para completar as versões a partir da lista ~/apps/java/*
    • A estrutura é simples, quase um one-liner
  • Como a principal função dos LLMs é o autocompletar de texto, o GitHub Copilot faz um trabalho surpreendentemente bom com completion de bash e zsh no terminal do vscode