You can read the english version of this post here.
Um dos aspectos legais do editor Emacs é a possibilidade de usar código Lisp para empoderar a expansão de snippets. O propósito desse post é mostrar a potência dessa técnica para que você possa implementá-la no seu próprio editor (Neovim é um bom candidato) se necessário.
Expansão de snippets via código
Eu tenho dois snippets para gerar declarações de código Ruby para class e module através do pacote yasnippet. Se eu abrir o arquivo lib/job_tracker/job.rb no meu projeto e digitar o seguinte gatilho seguido de TAB:
cclas
Isso gera o seguinte código:
module JobTracker
  class Job
    $
  end
end
$ representa a posição final do cursor após expandir o snippet. Eu também tenho um snippet similar, o cmod, que gera um module ao invés de class. Se você é um programador, provavelmente já está imaginando como implementar esse snippet:
- Obtenha o caminho do arquivo, lib/job_tracker/job.rb;
- Retire o prefixo lib/, resultando emjob_tracker/job.rb;
- Retire a extensão .rb, resultando emjob_tracker/job;
- Quebre a string anterior pelo delimitador /para obter uma lista de duas strings:['job_tracker', 'job'];
- Aplique camel case nas strings usando a função map:['JobTracker', 'Job']. usar uma biblioteca de camel case ou uma regex avançada;
- Transforme o primeiro elemento na string module JobTracker\n;
- O segundo elemento se transformaria em module Job\n(com dois espaços de indentação). Se estivermos lidando com a última string do array (que é o caso), seria uma classe ao invés demodule:class Job\n;
- Feche as declarações de classoumodulecomend, no nível certo de indentação.
Claro, esse algoritmo deve ser genérico.
Meu snippet para isso é o seguinte:
`(ruby-code-for-fully-qualified-name-top "class")`
`(ruby-code-for-fully-qualified-name-middle)`$0
`(ruby-code-for-fully-qualified-name-bottom)`
$0 indica a posição do cursor depois de expandir o snippet.
Ele é composto por três chamadas de função que geram as partes do topo, meio, e rodapé do meu código Ruby (existe um motivo para usar três funções ao invés de uma, que se traduz em uma limitação do yasnippets). yasnippets resolve todo o código Lisp envolvido em backticks (acento grave) e insere o valor de retorno no resultado da expansão do snippet. Nós certamente poderíamos ter, também, strings literais dentro do snippet! Esse é o caso mais comum.
Note que chamamos uma função no Lisp com
(funcao), e nãofuncao()como é comum em linguagens derivadas do C.
Se você estiver curioso para ver a minha implementação em Lisp, ela inicia aqui.
O problema
Eu devo mencionar que o meu snippet para cclas era o seguinte:
# frozen_string_literal: true
`(ruby-code-for-fully-qualified-name-top "class")`
`(ruby-code-for-fully-qualified-name-middle)`$0
`(ruby-code-for-fully-qualified-name-bottom)`
Ele tem o comentário mágico frozen_string_literal, para garantir que todas as literais de string dentro do arquivo serão congeladas pelo runtime do Ruby (para evitar mutações nas strings).
Recentemente, eu entrei em um projeto onde frozen_string_literal é desnecessário. Como modificar a expansão do meu snippet apenas para esse projeto? O que eu quero é que frozen_string_literal seja expandido dependendo de uma configuração específica. Novamente, o Emacs brilha nesse quesito.
Como tornar o meu snippet configurável?
Para isso, posso codificar uma função em Lisp que lê o valor global de uma variável booleana; se o valor for verdadeiro (t em Lisp) quero que retorne a saída # frozen_string_literal: true dentro do meu snippet. Seria isso aqui:
(defun ruby-code-for-frozen-string-literal ()
  (when ruby-code-for-frozen-string-literal
      "# frozen_string_literal: true\n\n"))
E, claro, é necessário definir uma variável (configuração) com a diretiva defvar:
(defvar ruby-code-for-frozen-string-literal t)
O valor t significa que frozen_string_literal será expandido por padrão dentro do meu snippet.
E agora, como modificar essa variável dentro do escopo de um projeto específico? O Emacs tem um recurso chamado Per-Directory Local Variables (variáveis locais por diretório). Isso significa que eu posso sobrescrever qualquer defvar dentro do escopo de qualquer projeto. Legal né? Então eu abri o meu projeto e acionei esse comando:
M-x add-dir-local-variable
Com ele, eu especifiquei que, para o modo (major mode) enh-ruby-mode, eu quero que o valor de ruby-code-for-frozen-string-literal seja nulo (nil). Então o Emacs criou um arquivo .dir-locals na raiz do meu projeto com isso:
((enh-ruby-mode . ((ruby-code-for-frozen-string-literal . nil))))
Como um arquivo .dir-locals já existia, o Emacs apenas incluiu a minha nova configuração nele.
O último passo foi fazer uma pequena mudança no meu snippet:
`(ruby-code-for-frozen-string-literal)``(ruby-code-for-fully-qualified-name-top "class")`
`(ruby-code-for-fully-qualified-name-middle)`$0
`(ruby-code-for-fully-qualified-name-bottom)`
Eu realmente gostaria que cada chamada de função ficasse em sua própria linha, mas isso tornaria as coisas mais difíceis por motivos que não vou explicar aqui.
E essa é a história de como eu gastei menos de 5 minutos tornando o meu snippet configurável.
Conclusão
Eu tenho outra configuração em mente para esse snippet em específico, que é gerar declarações de módulo compactas (compact) ou aninhadas (nested) - mas não preciso disso, então não vou implementar agora.
Editores com linguagens de programação embutidas são realmente potentes e customizáveis. Você tem flexibilidade quase infinita para mudar qualquer coisa que quiser! Espero que essa leitura tenha sido interessante.
