Limpando e organizando texto e código com o Vim

Quando trabalhamos com texto ou código, às vezes nos vemos na necessidade de executar uma tarefa repetitiva e ficamos pensando se é melhor fazer tudo à mão ou procurar um jeito de automatizar o trabalho.

Um bom nerd da computação preferiria um jeito mais automatizado. Mas antes de começar a criar um programa que execute a tarefa (e correr o risco de gastar mais tempo programando do que se gastaria fazendo o trabalho à mão), podemos usar o editor de texto Vim (http://www.vim.org).

Vamos ver um caso que me aconteceu recentemente. Suponha que você tenha usado um gerador de consultas SQL para economizar tempo de digitação. Mas aí ele gerou algo do tipo:

select
    "TABELA_A"."COLUNA_A" as "COLUNA_A",
    "TABELA_A"."COLUNA_B" as "COLUNA_B",
    "TABELA_A"."COLUNA_C" as "COLUNA_C",
    -- muitas outras colunas seguindo o mesmo modelo
from "TABELA_A" "TABELA_A"

Tecnicamente, a consulta não está errada, e o gerador até que poupou um trabalho que era muito propenso a erros de digitação. O problema é que depois você ou outros vão precisar dar manutenção nessa consulta, e nesse caso é melhor editar diretamente o texto do que ficar dependendo do gerador; mas se ao menos a query SQL não estivesse tão poluída de elementos desnecessários… Aquelas aspas só precisam ser usadas se um nome de coluna contiver espaços, os aliases após cada coluna e após o nome da tabela só são necessários se você realmente quiser dar um nome mais amigável para um dos itens. Repetir cada nome não tem nenhuma utilidade. Como eu faria a limpeza nesse código?

A primeira coisa que eu faria seria trocar todos os TABs (eles não são perceptíveis aqui no blog, mas vamos supor que eles estejam ali) usados na indentação por espaços. Para código eu realmente prefiro espaços.

Com o Vim configurado com

:set tabstop=4 expandtab

(pode testar à vontade porque estas configurações não serão salvas; se quiser salvá-las, edite o seu arquivo de configuração .vimrc)

Podemos usar o comando :retab em todo o arquivo para trocar todos os TABs pela indentação equivalente a 4 espaços:

:retab

Se antes do comando você estava com o modo 'list' (veja o help com :help 'list') ativado, você verá a mudança instantaneamente de TABs para espaços. Eu tenho o meu configurado assim:

:set list
:set listchars=tab:»\ ,trail:·,nbsp:¬

(note o espaço antes da primeira vírgula).

Agora vamos limpar os nomes das colunas. Dentro do modo normal (modo de comandos — se já não estiver nele, use a tecla Esc uma ou duas vezes para sair de todos os outros modos), posicione o cursor no primeiro abre-aspas (para não estragar a indentação) e digite o seguinte comando:

d4f"

que significa “delete 4 find aspas”, ou seja deletar até a quarta ocorrência das aspas, inclusive a última (mas não incluindo na contagem a que está debaixo do cursor). Se não quiséssemos incluir a última, usaríamos o comando t (till) no lugar do f. Se errar na contagem e apagar demais ou demenos, use o comando u (undo [desfazer]).

Então, o que era

"TABELA_A"."COLUNA_A" as "COLUNA_A",

se torna

COLUNA_A",

Nesse momento, eu até gosto de fazer o trabalho de maneira um pouco manual, e aproveito para mostrar o comando . (ponto), que serve para repetir o último comando. Depois do d4f", basta executar repetidamente:

j.j.j.j.

para descer uma linha (j, equivalente à flecha para baixo do teclado) e repetir o último comando (ponto). Repare cada linha sendo simplificada com precisão, ao mesmo tempo que evita erros manuais e observa o resultado se formando (podendo parar ou desfazer caso alguma linha não esteja no padrão). Sim, seria possível automatizar este passo ainda mais, só que nesse caso eu preferi usar o j. repetidas vezes para não cair no problema de perder mais tempo bolando um script perfeito do que realmente editando o texto.

Mas enquanto escrevia isto, ficou parecendo que eu estou enrolando pra não dizer que não saberia como fazer essa tarefa de forma mais automática. Então bolei o seguinte comando:

:global/^ \{4}"/normal 4ld4f"

(o comando :global é na prática abreviado com :g)

que significa: para todas as linhas do arquivo que começam (^) com 4 espaços seguidos de aspas ( \{4}" — note o espaço antes da barra invertida), execute o comando do modo normal

4ld4f"

que por sua vez significa: mova-se 4 letras para a direita (posicionando-se no primeiro abre-aspas) e delete até encontrar a 4ª ocorrência de aspas. No meu caso, percebi que o gerador de SQL tinha na verdade usado uma combinação de TAB+espaço, por isso tive que fazer 5ld4f".

Claro que com um comando desses é bem mais arriscado você acabar aplicando-o a um conjunto de linhas maior do que o desejado, e deletando coisas que não queria. Por isso, antes de digitar o comando, você pode selecionar o trecho a que ele se aplica usando o modo visual (comando v ou V), que insere automaticamente a faixa '<,'> (significado: da marca de início '< do texto selecionado até a marca de fim '> de texto selecionado) antes do seu comando, logo após os dois-pontos.

Mas, você lembra onde estávamos? Temos agora um monte de linhas terminando com ", (aspas, vírgula) e precisamos tirar essas aspas. Nesse caso eu usaria um comando de substituição:

:%substitute/",$/,

(o comando :substitute é na prática abreviado com :s e o arquivo inteiro é simbolizado por um %, se você não usar o %, o comando se aplica apenas à linha atual — bom para fazer testes)

que significa: substituir a seqüência aspas+vírgula+fim de linha ($) por uma simples vírgula. Normalmente usamos o comando :s no formato :%s/busca/substituição/g, porque o g final indica que a substituição deve ocorrer todas as vezes que forem necessárias em cada linha (em vez de fazer só na primeira); mas neste caso não é necessário porque sabemos que o item buscado só pode ocorrer uma vez por linha (no final de cada uma delas).

Claro que no lugar de % (todo o arquivo), você pode selecionar um trecho com o modo visual, ou especificar manualmente o trecho pretendido (por exemplo o .,$ como no exemplo :.,$s/aaa/bbb/g significa que o comando se aplica do ponto atual até o fim do texto).

Com isso falta acertar última coluna (que termina só com aspas, e não com aspas+vírgula) e a cláusula FROM. Esses dois passos podem ser feitos manualmente mesmo.

Agora você tem uma query bem simplificada como esta:

select
    COLUNA_A,
    COLUNA_B,
    COLUNA_C,
    -- muitas outras colunas
from TABELA_A

E se você quiser transformá-la em:

select
    TAB_A.COLUNA_A,
    TAB_A.COLUNA_B,
    TAB_A.COLUNA_C,
    -- muitas outras colunas
from TABELA_A TAB_A

como fazer para colocar “TAB_A.” no início de cada linha? Vamos usar o modo visual em bloco. No modo normal (isto é, fora do modo de inserção), posicione o cursor no C de COLUNA_A e pressione Ctrl+V (se Ctrl+V estiver configurado como “Colar”, que é o que eu prefiro, use o sinônimo Ctrl+Q). Use a tecla j para descer e selecionar verticalmente todas as linhas onde você quer inserir “TAB_A.” e então dê o comando I (“i” maiúsculo, isto é, Shift+i se o CapsLock estiver desligado), que significa “inserir no começo da linha”. Mas como temos um bloco de seleção, ele se torna “inserir no começo do bloco em cada linha”. Depois do I, digite simplesmente “TAB_A.” e aperte Esc para ver o mesmo texto ser replicado em todas as linhas. O mesmo pode ser feito para adicionar no fim de várias linhas, mas como o fim das linhas normalmente é serrilhado, é necessário apertar $ para extender toda a seleção em bloco para a direita e então inserir o texto com o comando A (I insere no início e A adiciona no fim, ambos maiúsculos neste caso; e $ funciona como a tecla End).

Para inserir coisas no fim das linhas, também é possível usar a técnica “substituir com busca pelo fim da linha ($)”:

:%s/$/seu texto/

Claro que esse comando não substitui os fins de linha, apenas adiciona o texto, por isso chamei ele de “substituir com busca pelo fim da linha ($)”, e não “substituir fim de linha”. O caracter correspondente para o início da linha é o circunflexo (^).

Como ajuste final, suponha que a sua convenção no SQL é usar as palavras-chave em maiúsculas e os nomes de colunas em minúsculas, justo o contrário do que temos. Corrija com os seguintes comandos:

gg (ir ao começo do arquivo, pode usar Ctrl+Home também)

g~ (este é o comando para inverter maiúsculas e minúsculas, veja também gu e gU)

G (mover até o fim, provocando assim o efeito do operador g~ por todo o arquivo, pode usar Ctrl+End também)

É interessante que o operador g~ opera sobre um “movimento” (igual ao operador d, que já usamos). Se achar estranho, pode usar de novo o modo visual (v, V ou Ctrl+V/Ctrl+Q) e usar os comandos ~ (inverte maiúsculas/minúsculas), u (tudo em minúsculas) ou U (tudo em maiúsculas).

Mais uma dica sobre o modo visual: depois de executar uma operação, o texto é desmarcado. Se quiser resselecioná-lo (para executar outra operação, por exemplo), use o comando gv.

Anúncios