Bom, numa postarem anterior eu mostrei meu workflow com Clojure e Atom. Nesse post, farei um passo a passo bem mais detalhado.
A primeira coisa a se fazer é instalar, no sistema operacional, o Java SDK e o Leiningen. Isso torna possível rodar Clojure e ClojureScript no sistema operacional. Agora, vamos ao Atom.
As novas alterações do meu plug-in clojure plus trazem um suporte preliminar a ClojureScript também, usando o piggieback. Na verdade, qualquer biblioteca é possível, já que o plug-in permite que você defina um comando que abriria um console ClojureScript. Mas mais sobre isso mais tarde.
Atom e Profiles
Dentro do Atom, instale o proto-repl, clojure-plus, lisp-paredit e clojure-language. O primeiro plug-in faz a ponte entre o clojure e o editor, o segundo traz funcionalidades interessantes, o terceiro faz edição estrutural (se você quiser, claro), mas principalmente corrige a indentação de código Clojure quando se digita enter (o Atom tem uma regra genérica que não funciona em LISPs).
Enquanto esses plug-ins instalam, é hora de configurar seu profile. Em Clojure usando Leiningen (ou lein para os íntimos – demora muito digitar o nome completo) há um arquivo de profiles em seu diretório home. Esse arquivo define bibliotecas e plug-ins que sempre ficarão ativos em qualquer circunstância e em qualquer código que se esteja digitando. Desnecessário dizer quão poderoso é isso, certo? Basicamente, bibliotecas ficam disponíveis para todos os projetos, mesmo os que não a usam, em qualquer circunstância. Aqui vale um pequeno desvio:
Em Clojure, há muitas bibliotecas que não servem exatamente para serem usadas no código – basicamente, o uso delas é refatorar código (como o refactor nrepl), debug (como o sayid), autocomplete (como o compliment), etc. O que vamos fazer é adicionar o refactor-nrepl e o proto-repl no projeto. O proto-repl, na verdade, é só o agrupamento do compliment e do clojure.tools.nrepl, então se você quiser pode adicionar essas bibliotecas individualmente (bom caso algum bug numa delas esteja corrigido numa versão mais recente).
O seu arquivo de profiles vai ficar dentro do diretório home, subdiretório .lein
, no arquivo profiles.clj
. Se nem o arquivo nem o diretório existirem, crie-os. Logo, seu arquivo /home/seu-usuario/.lein/profiles.clj
ficaria assim:
{:user {:plugins [[refactor-nrepl "2.2.0"]] :dependencies [[slamhound "1.3.1"] [proto-repl "0.3.1"] [com.billpiel/sayid "0.0.10"]]}}
As dependências do slamhound e do sayid não tem uso ainda, mas estou pensando em integrá-las num futuro próximo ao clojure-plus
, logo é bom mantê-las.
Nesse ponto, seu editor está pronto para ser usado. Você pode instalar também o plug-in parinfer, que infere parênteses a partir da indentação – muito útil, na minha opinião, mas devido a algumas semanticas provavelmente você vai querer usar o parinfer em conjunto com o paredit. Eu uso os dois juntos quando trabalho com Clojure.
Configuração dos plug-ins
Eu não gosto dos plug-ins que definem atalhos para mim, logo eu não defini nenhum atalho para o clojure-plus. O proto-repl, em compensação, define uma centena de atalhos, bem como o lisp-paredit. Eu costumo entrar em “View Installed Packages”, e dentro do proto-repl e do lisp-paredit eu removo os keybindings (de-selecionando o check Enable da área Keybindings de ambos os plugins). Agora, você provavelmente vai querer um atalho para mudar o modo “strict” do paredit, e atalhos para clojure. Então, abra seu arquivo de keymap, e vamos adicionar alguns. Nesse caso, eu vou adicionar keybindings compostos – “ctrl+espaço” vai ser o principal, e podemos usar outra tecla pra fazer o que queremos (ou seja, se você quiser se conectar no REPL, basta apertar “ctrl+espaço” e logo depois digitar “c”):
'atom-workspace atom-text-editor[data-grammar="source clojure"]': 'ctrl-space c': 'proto-repl:remote-nrepl-connection' 'ctrl-space l': 'clojure-plus:evaluate-last-code' 'ctrl-space s': 'lisp-paredit:toggle-strict' 'ctrl-space f': 'clojure-plus:display-full-symbol-name' 'ctrl-space w': 'clojure-plus:add-watch-in-selection' 'ctrl-space q': 'clojure-plus:remove-all-watches' 'ctrl-space r': 'clojure-plus:refresh-namespaces' 'ctrl-space space': 'proto-repl:clear-saved-values' 'enter': 'lisp-paredit:newline' 'alt-left': 'lisp-paredit:barf-forwards' 'alt-right': 'lisp-paredit:slurp-forwards' 'alt-up': 'lisp-paredit:up-sexp' 'alt-down': 'lisp-paredit:down-sexp' 'ctrl-c': 'proto-repl:interrupt' 'shift-enter': 'clojure-plus:evaluate-block' 'ctrl-enter': 'clojure-plus:evaluate-top-block' 'ctrl-shift-enter': 'clojure-plus:evaluate-full-file' 'F12': 'clojure-plus:goto-var-definition' 'ctrl-d': 'proto-repl:print-var-documentation'
Ufa, muita coisa. Vamos por partes. As primeiras partes são para facilitar nossa vida – conectar a um REPL, rodar o último código que estávamos rodando, mudar o paredit para strict ou não (em modo strict, toda vez que abrimos um parênteses ele é fechado imediatamente, e não podemos manualmente remover parenteses). Normalmente você vai usar esse modo, ele é mais seguro. Para adicionar ou remover elementos dentro dos parênteses, usamos o segundo bloco – o primeiro muda o enter para adicionar uma nova linha, mas já calculando a correta indentação. Os dois próximos são para adicionar o próximo elemento à direita do cursor dentro dos parenteses, e para remover o último elemento dos parêntese. Uma imagem vale mais do que muitas palavras:
Os dois últimos são para navegar para os próximos parenteses dentro desses, ou para fora. De novo, uma imagem vale mais:
Por fim, o último bloco server para definir o ctrl-c para parar o que quer que estejamos rodando, ctrl-enter vai rodar o bloco de código inteiro que estamos, shift-enter vai rodar apenas o bloco que estamos dentro, ctrl-shift-enter vai rodar o arquivo inteiro, bloco a bloco, e os dois últimos são auto-explicativos. Embora não pareça, ctrl-d é mais útil do que parece – por definição, todas as funções de Clojure tem uma pequena documentação, mesmo que seja apenas mostrar os parâmetros que ela recebe.
Agora, algumas edições cosméticas (literalmente – mudar a cor de algumas coisas) talvez sejam úteis:
Mudanças de estilo
O plug-in lisp-paredit
é educado o suficiente para nos mostrar quando há problemas com os parênteses, mas infelizmente ele é bem invasivo nisso – ele deixa o texto num vermelho berrante absurdo. Para tal, eu mudo na folha de estilos do Atom para deixar apenas um contorno: aperte ctrl-shift-p
para abrir a paleta de comandos, e busque por Open your stylesheet. No fim do arquivo, coloque algo como:
atom-text-editor.is-focused[data-grammar="source clojure"]::shadow { .lisp-syntax-error .region { background-color: rgba(255, 150, 150, 0) !important; border: 1px solid rgba(204, 0, 0, 0.8) !important; border-radius: 5px; } }
Ou qualquer outro estilo que você queira – é um arquivo less, que basicamente é um CSS com mais poder. Se você, assim como eu, for daltônico, talvez seja difícil de ver na barra de baixo do Atom se o paredit está em format strict ou não, então eu configuei o meu para ficar azul quando estiver no strict:
.lisp-paredit-status.strict.enabled > .strict-status { color: #0000FF; font-weight: bold; }
Desenvolvimento
Desenvolver com Clojure, da forma como eu costumo fazer, costuma ser bem diferente do que o normal. Primeiro porque todas as coisas – autocomplete, rodar código, etc – normalmente são feitas pelo REPL. O que fazemos no Atom é que iniciamos um REPL na linha de comando usando lein repl
, e depois conectamos ele ao Atom usando Remote Nrepl Connection. Aqui as coisas começam a ficar interessantes:
Idealmente, em cada projeto é bom ter um diretório reservado apenas para pequenos fragmentos de código úteis. Por exemplo, no arquivo project.clj
, idealmente teríamos um pedaço assim:
(defproject my-project "0.0.1-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.8.0"]] :profiles {:dev {:source-paths ["dev"]}}) ;
Basicamente, a linha em destaque define um novo diretório aonde os códigos-fonte serão avaliados, mas apenas em desenvolvimento – isso não fará parte do código final. Isso é muito útil especialmente em casos aonde se desenvolve um código que sobe componentes – por exemplo, um servidor web. O código abaixo, colocado no arquivo dev/user.clj
, define duas funções – uma que sobre o servidor numa porta de desenvolvimento (3000, nesse caso) e outra que derruba o servidor se ele existir.
(ns user (:require [my.handler :as handler] [ring.adapter.jetty :as jetty])) (def server (atom nil)) (defn start-system [] (reset! server (jetty/run-jetty #'handler/app {:port 3000 :join? false}))) (defn stop-system [] (when-let [s @server] (.stop @server) (reset! server nil)))
Dessa forma, eu configuro o plug-in clojure-plus
para possuir os seguintes comandos:
- Before Refresh Cmd:
(alter-var-root #'clojure.test/*load-tests* (constantly true)) (user/start-system)
- After Refresh Cmd:
(alter-var-root #'clojure.test/*load-tests* (constantly true)) (user/stop-system)
Eu também configuro o plug-in para, ao salvar, tentar fazer o refresh dos namespaces. Dessa forma eu sei que o código que eu estou digitando sempre está correto – mas, ao mesmo tempo, esse tipo de abordagem é mais perigosa. Quando o código não consegue fazer refresh (por qualquer motivo – var não definida, etc) sequer o autocomplete funciona. Logo, no meu workflow, eu digito código, aperto ctrl-enter para rodar o código atual, e se eu não tiver exceptions, me sinto confiante em salvar o arquivo e deixar o refresh me falar se eu cometi algum erro.
Outra coisa importante pra mencionar, e que eu mencionei no meu post anterior – a primeira parte do Before e do After refresh, (alter-var-root #'clojure.test/*load-tests* (constantly true))
, servem para evitar rodar testes do Midje. Se for usado com clojure.test
, provavelmente os testes em Clojure não serão definidos – então, rodar um (run-tests)
ou (run-all-tests)
vai simplesmente ignorar todos os testes depois de um refresh. Provavelmente esse não é o comportamento desejado, então é melhor remover essa parte do código (ou fazer algo MUITO clever: detectar se há um Midje antes no projeto, e se houver, desativar o *load-tests*
:
(try (require '[midje.sweet :as midje]) (alter-var-root #'clojure.test/*load-tests* (constantly false)) (catch Exception e nil)) (user/stop-system)
E colocar isso no Before Refresh Cmd. Basicamente, isso vai resolver os problemas de refresh e deixar o código genérico para usar midje ou clojure.test.
Um pouco do workflow
Resolvi também gravar um vídeo com o meu workflow. Nesse vídeo, eu mostro como estou tentando integrar o Sayid (biblioteca de debug para Clojure) no clojure-plus, e alguns testes automatizados que comecei a fazer. É bem interessante porque mostra uma situação real, inclusive com problemas como “por que essa coisa não funcionou?” (eu achava que estava fazendo corretamente o parsing da função no meu plug-in. Não estava, e foi um bug que eu corrigi no vídeo).
Segue o vídeo, e bom divertimento!