Quando os dedos apertam Esc: keybindings do Vim no composer do CLI do Hermes (e o nome do branch na sua status bar)


Você digitou um prompt longo no Hermes e, pouco antes de apertar Enter, percebe uma palavra escrita errado no meio. Seus dedos apertam Esc e depois dd — e o composer não faz absolutamente nada. Nas bindings emacs padrão, Esc não significa “sair do insert mode”; é uma tecla de cancelar/limpar, e dd virou apenas dois caracteres de texto. Se você passou uma década no vim, esse meio segundo de atrito é difícil de explicar para quem não passou.

Agora existe resposta oficial. O composer clássico do CLI pode usar keybindings vi/vim — e a configuração é menor do que você imagina: uma chave de config, e nenhum slash command novo.

Como ligar: display.vim_mode

Adicione uma linha na seção display do ~/.hermes/config.yaml:

display:
  # Só no CLI: keybindings vi/vim no composer de entrada (Esc → NORMAL, i → INSERT)
  # O modo NORMAL / INSERT / REPLACE ao vivo aparece à direita da status bar.
  # Somente config, lido na inicialização.
  vim_mode: true

Depois abra a sua config com hermes config edit (ou o seu próprio editor), salve e reinicie o Hermes.

Uma armadilha para tirar do caminho logo de cara: não existe slash command /vim. Uma iteração inicial desse recurso trouxe um, com subcomandos on|off|status, mas ele foi removido antes do merge — por um motivo sensato. Os vizinhos dele (/statusbar, /battery, /timestamps) alternam itens de exibição no momento, enquanto keybindings são um hábito de edição, e prendê-los à configuração é mais consistente do que trocá-los sessão a sessão. Então, se você vir /vim on escrito por aí, esse é o estado intermediário descartado — digitá-lo não faz absolutamente nada.

Como é na prática

O comportamento bate com o que os seus dedos esperam:

  • Esc entra no modo NORMAL, onde as teclas são comandos e não texto;
  • i (ou a, o e companhia) volta ao modo INSERT para digitação comum;
  • a status bar mostra o modo atual na borda direita — um entre NORMAL, INSERT ou REPLACE. Isso é genuinamente útil, não enfeite: em um prompt de várias linhas, esquecer em que modo você está é o incômodo clássico, e um rótulo persistente elimina a maior parte dos momentos de “por que nada acontece quando eu digito”.

Esse rótulo é ao vivo, acompanhando os seus toques em Esc e i em vez de ficar fixo na inicialização. Por baixo, a status bar o monta dinamicamente a partir do modo de edição atual (_vim_mode_label em cli_status_bar_mixin.py).

E se você nunca usou vim? O padrão é desligado, o que mantém as bindings emacs padrão do prompt_toolkit — não mude nada, não note nada. Essa postura opt-in é exatamente o motivo de o recurso ter entrado como uma chave de config.

Um companheiro que vale conhecer: o branch git atual na status bar

Já que estamos na status bar, há um segundo campo “desligado por padrão, ligue de propósito”: o branch git atual.

Em uma máquina com vários repositórios e worktrees, “em que branch esta sessão está de fato” é uma pergunta frequente e consequente — especialmente depois que um agente editou arquivos para você e você quer ter certeza de qual branch levou a alteração. Fixe isso na status bar e fica sempre a um olhar de distância:

display:
  status_bar:
    # Lista não vazia = só estes campos aparecem (ordem nativa mantida; a config
    # controla visibilidade, não a ordenação). Lista vazia = conjunto padrão.
    fields: ["model", "git_branch", "duration"]

Os campos disponíveis incluem model, context_pct, cache_hit, latency, tps, compressions, bg_tasks, bg_subagents, goal, git_branch (renderizado como ⎇ branch), duration, focus, yolo, battery, title e total_tokens. Duas regras importam:

  1. Uma lista não vazia é uma whitelist: só os campos que você nomear aparecem; uma lista vazia restaura o conjunto padrão. A ordenação continua nativa — a configuração decide apenas a visibilidade.
  2. Terminais estreitos descartam campos automaticamente. Quando a janela aperta, campos largos como context_detail, prompt_elapsed e idle_since são removidos em vez de comprimidos. Isso é comportamento intencional, não config quebrada.

Se você quer apenas alternar a própria status bar, o /statusbar faz isso; a lista de campos é configuração.

Ressalvas

  • Reinicie depois de editar. O vim_mode é lido na inicialização, então uma edição a quente da config não troca o modo em uma sessão que já está rodando.
  • Isso mexe só no composer. A mudança vale para a caixa de entrada onde você digita prompts, não para toda outra superfície de interação, e muito menos para como o agente trabalha.
  • É específico do CLI. Os keybindings pertencem ao composer clássico do CLI; o app desktop e outras superfícies TUI têm a própria lógica de entrada. Não espere que um único switch mude todas.
  • Não é o vim inteiro. Este é o modo de edição vi do prompt_toolkit: movimento, remoção, entrada em insert mode — edição do dia a dia, não uma sessão Neovim com plugins e registradores elaborados.
  • O recurso tem precedente. O pedido original é a issue #4254, e mais ou menos na mesma época o GitHub Copilot CLI levou o vim mode para disponibilidade geral (as notas da versão v1.0.84-2 citam tanto um comando /vim quanto uma configuração editorMode). Os dois projetos concordam no ponto de fundo: hábitos de edição no terminal merecem respeito.

Se você está ajustando a sua configuração de CLI, três conteúdos relacionados aqui valem a leitura: o guia da command palette com Ctrl+P, o mergulho nas métricas da status bar e como remover o teto de max-turns para sessões longas. Montar uma máquina do zero começa no guia de instalação.

A versão curta

Em uma frase: quer teclas do vim? Escreva display.vim_mode: true e reinicie — e não saia procurando um comando /vim, porque não existe.

Três coisas para levar:

  1. Desligado por padrão, então nada muda a menos que você peça; uma vez ligado, a status bar mostra NORMAL / INSERT / REPLACE permanentemente.
  2. Considere adicionar git_branch a display.status_bar.fields — em um ambiente com vários branches, isso evita toda uma classe de erros bobos.
  3. Esta mudança foi mergeada depois da tag v0.21.2, então atualmente ela vive na main em vez de um release tagueado; o hermes update pega a main, ou espere o próximo release.

Para quem traz memória muscular de vim para o terminal, é uma mudança pequena que você vai usar várias vezes por dia.