Vim/Neovim 向けに Clojure の開発環境を提供するプラグイン vim-iced を使って Clojure 開発環境の構築をしてみましょう。 なお vim-iced は個人的に開発している拙作の Vim プラグインで、実際に私が業務で(勝手に)利用しているという実績があります。
前提
- Vim もしくは Neovim がインストールされていること
- Vim は
8.1.0614
以降、Neovim は0.4.0
以降が必要であることにご注意ください
- Vim は
- Java がインストールされていること
- macOS もしくは Linux 環境(本記事では Ubuntu を想定)であること
- Windows 環境は vim-iced 自体がサポートしていないので、VM などで Linux 環境を用意してください
ゴール
本記事の内容を実践したことで以下の状態になっていることをゴールとします。
- Vim を使った Clojure の開発環境が構築でき、REPL 駆動開発ならびにテスト駆動開発が体験できていること
やること
- Clojure 実行環境のインストール
- vim-iced のセットアップ
- サンプルプロジェクトの作成
- REPL 駆動開発のお試し
Clojure 実行環境のインストール
Clojure の実行環境は複数ありますが、今回は多くのプロジェクトで使われている Leiningen (ライニンゲン)を使った手順を紹介します。
なにはともあれインストールから始めましょう。
# macOS の場合 brew install leiningen # Linux の場合 curl -fLo lein https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein chmod +x lein sudo mv lein /usr/local/bin # パスが通っているディレクトリであればどこでも可
インストールができたら簡単に Clojure のコードを実行して試してみましょう。
lein repl
コマンドを実行して REPL(Read Eval Print Loop) を起動します。- 初回は起動に必要なライブラリ類をダウンロードするので時間がかかります。
user=>
が出てくるまでしばしお待ちください。
- 初回は起動に必要なライブラリ類をダウンロードするので時間がかかります。
user=>
が出てきたら Read の状態なので、何かコードを書いて評価(Eval)してみましょう。結果が表示(Print)されるはずです。- E.g.
(+ 1 2 3 4 5)
を評価すると15
が表示されます。
- E.g.
- 確認ができたら
Ctrl + d
を押すか、(exit)
を評価して REPL を閉じましょう。
vim-iced のセットアップ
vim-iced は Vim プラグインとして提供しています。そのためプラグインマネージャーを利用していない場合はまずその導入から始めましょう。 すでにプラグインマネージャーを利用している場合は次のセクションは飛ばして構いません。 なお本記事では vim-plug を使った例を紹介します。
vim-plug のインストール
Vim/Neovim で設定ファイルのパスが異なるので vim-plug の配置先も異なることにご注意ください。
# Vim の場合 curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim # Neovim の場合 curl -fLo ~/.local/share/nvim/site/autoload/plug.vim --create-dirs \ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
各種プラグインのインストール
Vim/Neovim の設定ファイルを用意します。
Vim の場合は ~/.vimrc
、Neovim の場合は ~/.config/nvim/init.vim
です。
すでに設定ファイルがある場合は「追加する設定」の部分だけの追加で問題ありません。
# Neovim の場合は先にディレクトリを作成しておく # mkdir -p ~/.config/nvim cat <<EOT > ~/.vimrc set nocompatible set encoding=utf-8 scriptencoding utf-8 filetype plugin indent on " ==== 追加する設定 ここから ==== " プラグインのインストール先ディレクトリは必要に応じて変更してください call plug#begin('~/.vim/plugged') " 次のうちいずれかが必要 Plug 'ctrlpvim/ctrlp.vim' "Plug 'junegunn/fzf' "Plug 'liuchengxu/vim-clap' " 必須 Plug 'guns/vim-sexp', {'for': 'clojure'} Plug 'liquidz/vim-iced', {'for': 'clojure'} call plug#end() " vim-iced でのデフォルトキーマップを有効化 let g:iced_enable_default_key_mappings = v:true " 任意) 見やすさのためスクリーンキャプチャ内では有効にしています set splitright let g:iced#buffer#stdout#mods = 'vertical' let g:iced#buffer#error#height = 5 " ==== 追加する設定 ここまで ==== EOT
上記内容の設定ファイルが作成できたら、Vim/Neovim を起動している場合、一度再起動して :PlugInstall
コマンドを実行してください。
もしくはターミナル上から vim -c PlugInstall -c qa
を実行するのでも可です。
上記の例だと ~/.vim/plugged
配下に各種プラグインが配置されたことが確認できます。
ls ~/.vim/plugged
iced コマンドにパスを通す
最後に vim-iced が提供している iced
コマンドへのパスを通す必要があります。
vim-iced のインストール先が ~/.vim/plugged
である場合は以下のコマンドでパスを通してください。
export PATH=$PATH:~/.vim/plugged/vim-iced/bin
なお iced
コマンドはインストールディレクトリ配下のファイルを参照するので、すでにパスが通っているところへ iced
コマンドをコピーしても正しく動作しませんのでご注意ください。
以下を実行して何かしらバージョン番号が表示されれば正しく動作しています。
iced version
サンプルプロジェクトの作成
ここまでで Vim で Clojure 開発を始められる準備は整いました。 Leiningen を使ってサンプルプロジェクトを作って実際に動かしてみましょう。
lein new hello-iced
cd hello-iced
サンプルプロジェクトのディレクトリ構成を簡単に説明すると以下の通りですが、今回使うのはソースコードのみです。
Dir/File | Description |
---|---|
src | ソースコードはこちら |
test | テストコードはこちら |
project.clj | プロジェクト名、バージョン番号、依存ライブラリなどを記述する設定ファイル |
REPL 駆動開発への入り口
まずはソースコードを開きます。Leiningen で作成されたプロジェクトでは プロジェクト名.core
という名前空間が用意され、名前空間のドット区切りがそのままディレクトリ/ファイル名に反映されます。
唯一の例外がハイフン(-
)で、これはディレクトリ/ファイル名上ではアンダーバー(_
)に変換されます。
vim src/hello_iced/core.clj
開いたファイルは以下のようになっているかと思います。
(ns hello-iced.core) (defn foo "I don't do a whole lot." [x] (println x "Hello, World!"))
REPL への接続
ソースコードを開いたらおもむろに :IcedJackIn コマンドを実行してみてください。
iced
コマンドにパスが通っていれば、ステータスラインに OK: Leiningen project is detected
のようなメッセージが表示され、しばらくすると Connected.
が表示されます。
この Connected
が出た状態が Vim の裏側で起動している REPL に vim-iced が接続している状態です。
※ 2022/05/27追記 ここから
:IcedJackIn
は Vim 内で REPL のプロセスを起動しますが、別途 REPL を起動しておいてそちらに接続する方法もあります。
プロジェクト配下で iced repl
コマンドを実行してみてください。
すると lein repl
同様に REPL が起動するので、Vim 上で :IcedConnect コマンドを実行すると別途起動した REPL に接続できます。
こちらの方法だと Vim を終了しても REPL は起動したままなので、REPL を再度立ち上げる必要がないというメリットもあります。
※ 2022/05/27追記 ここまで
試しに :IcedEval (+ 1 2 3)
コマンドを実行してみてください。Vim 上で 6
という結果が表示されることが確認できるはずです。
コードを評価してみる
次にソースの末尾に (comment (foo "iced"))
を追記し、その括弧の中にカーソルを移動して、ノーマルモードで <Leader>et
(:IcedEvalOuterTopList)とタイプしてみてください。
これはカーソル配下のトップレベルのフォームを対象に評価することを意味しています。
なお <Leader>
はデフォルトでバックスラッシュ(\
) なので \et
となります。
なおここで comment
フォームを使っているのは、名前空間 hello-iced.core
が読み込まれたときに無駄に評価されないようにするためです。
vim-iced の <Leader>et
ではデフォルトで comment
フォーム内のコードを評価するようになっているので、既存のコードを評価して試してみたいときには comment
フォームを使うと便利です。
(ns hello-iced.core) (defn foo "I don't do a whole lot." [x] (println x "Hello, World!")) (comment (foo #_カーソルはここ "iced"))
結果はどうでしょうか?恐らく期待とは違い nil
が表示されたかと思います。
vim-iced では関数の戻り値を Popup ならびにステータスラインに表示して、標準出力は別の場所に表示するようにしています。
ノーマルモードで <Leader>ss
(:IcedStdoutBufferOpen) とタイプすると標準出力の表示先(以下、StdoutBuffer)が別ウインドウで表示され、期待した結果が出力されていることが確認できると思います。
変更内容を即座に確認する
ではサンプルプロジェクトの foo
関数で println
の引数を以下のように変更してみましょう。
この変更で StdoutBuffer に出力される内容が期待したものに変更されるかを確認したいと思います。
(defn foo "I don't do a whole lot." [x] (println x "is awesome!!!")) ;; 出力内容を修正
変更できましたか?
できたら foo
関数の中にカーソルを移動して <Leader>et
で foo
関数を再評価してください。
REPL 駆動開発では起動している REPL 内に評価された結果が保持されて、それを使って開発を進めていきます。
なので単にコードを変更しただけでは REPL 内の foo
は変更前のままなので再度評価してあげる必要があります。
再評価できたら末尾の comment
フォームを改めて <Leader>et
で評価してみてください。
StdoutBuffer に変更後の内容が出力されることが確認できるかと思います。
このようにフォーム単位で簡単・迅速に挙動が確認できるのがREPL駆動開発の強みです。
上記の例では関数単位ですが、さらに言うと例えば (def x "bar")
を評価しておいて、 println
フォーム内で <Leader>ee
(参考: Evaluation ranges)で評価すると関数内のフォーム単位でも動作確認可能です。
テスト駆動開発への入り口
次にテスト駆動開発に触れてみましょう。 ここでは整数のリストを渡して、その合計値を返すという簡単な関数を題材としてみます。
まずは core.clj に以下の関数を書いて評価してください。
この時点では合計を算出する処理はまだ書かずにとりあえず 0
を返すだけです。
(defn sum [ls] 0)
テストの作成
では hello-iced.core/sum
のテストを書いてみましょう。
テストコードはサンプルプロジェクト直下の test
ディレクトリ配下にあります。
ファイルを探して開いても良いのですが、ここでは :IcedCycleSrcAndTest コマンドを実行してみましょう。
これによりソースファイルに対応するテストファイルとして test/hello_iced/core_test.clj
を自動的に開くことができます。(ファイルが実在しなくても名前空間からパスを推測して新規作成も可能です)
開いたファイルは以下のようになっているかと思います。
(ns hello-iced.core-test (:require [clojure.test :refer :all] [hello-iced.core :refer :all])) (deftest a-test (testing "FIXME, I fail." (is (= 0 1))))
サンプルプロジェクトでは必ず失敗するテストしか書かれていないので、この a-test
は以下のように書き換えてください。
(deftest sum-test (is (= 6 (sum [1 2 3]))) (is (= 10 (sum [1 2 3 4]))))
ここでは hello-iced.core/sum
に [1 2 3]
と、[1 2 3 4]
というリストを渡して、
それぞれの結果が期待したものになるかをテストしています。
このフォーム上にカーソルを移動させてノーマルモードで <Leader>tt
(:IcedTestUnderCursor)とタイプしてみてください。
これはカーソル配下のトップレベルのフォームを対象にテストを実行することを意味しています。
ステータスラインにテスト結果の概要が表示されると共に、別ウインドウで期待した結果と実際の戻り値が異なるエラーが2つ表示されることが確認できるかと思います。
ソースの修正
ではテストが通るようにソースを修正していきましょう。
改めて :IcedCycleSrcAndTest
コマンドを実行してソース・ファイルに戻りましょう。
まずはすこしズルをして以下のように sum
を修正してみてください。
(defn sum [ls] 6)
修正できたら <Leader>et
で再度 sum
を評価し、 テストも再実行します。
もう1度 :IcedCycleSrcAndTest
で移動、 <Leader>tt
をタイプとしても良いのですが、ちょっと面倒なので今度はソースコードを開いたままノーマルモードで <Leader>tr
(:IcedTestRedo)とタイプしてみてください。
これは直前に失敗したテストを再実行することを意味しています。 すると今まで2つ出ていたエラーが1つになったかと思います。 あと1つのエラーが残っていますが、今度はズルをせずにきちんと書きましょう。
(defn sum [ls] (apply + ls))
再度評価してもう1度 <Leader>tr
でテスト実行してみてください。
今まで別ウインドウで表示されていたエラーはウインドウと共に消えて、ステータスラインに成功した旨が表示されたかと思います。
ここまででテストの書き方、実行方法、そしてテスト駆動による修正方法がわかったかと思います。
リンター/フォーマッター の設定
※ 2022/06/01追記
REPL駆動以外にも開発時にあって欲しいのはリンターとフォーマッターのサポートです。 より具体的には以下の2点は開発において必須と言っても過言ではないかと思います。
clj-kondo による静的解析の設定
Vim においてリンターの結果を表示するためのプラグインはいくつかあり、デファクトスタンダードと言えるものはありません。
主要なプラグインに関しては clj-kondo の以下のドキュメントにまとまっているので、こちらを参考に設定すると良いでしょう。
なお筆者はこのドキュメントにおける coc.nvim の設定を採用しています。
cljstyle によるフォーマットの設定
フォーマッターについては vim-iced 自体がデフォルトで cljfmt を使ったフォーマットに対応しています。 しかしチーム内でコーディングスタイルを統一したい目的で cljfmt 以外を使いたいニーズもあり cljstyle を使ったフォーマットも勿論サポートしています。
https://liquidz.github.io/vim-iced/#formatting_customize
vim-iced でのフォーマッターの設定
vim-iced にてフォーマッターとして cljstyle を使いたい場合の設定は簡単で ~/.vimrc
などに以下の1行を追加するのみです。
let g:iced_formatter = 'cljstyle'
もし cljstyle がインストールされていない場合は :IcedFormat のようなフォーマットに関するコマンドを実行することで 「iced コマンドにパスを通す」でパスを通したディレクトリ配下にインストールするかという案内が始まるので、それに従うと簡単にインストールすることが可能です。
保存時にフォーマットする設定
vim-iced が提供するフォーマット関連のコマンドは基本的に非同期でバッファ上のテキストを更新しますが、保存時は非同期にしてしまうとフォーマット結果が正しくファイルに保存されなくなってしまいます。 そのため vim-iced では同期的に動くフォーマットコマンドも用意しています。
これらのコマンドを BufWritePre に対する autocmd で使うことで保存時のフォーマットを実現できます。
以下ドキュメントからの抜粋ですが設定例です。
https://liquidz.github.io/vim-iced/#format_on_writing_files
aug VimIcedAutoFormatOnWriting au! au BufWritePre *.clj,*.cljs,*.cljc,*.edn execute ':IcedFormatSyncAll' aug END
なお環境やフォーマット対象のコードによってはバッファ全体のフォーマットに時間がかかり Vim がフリーズする時間ができてしまうこともなきにしもあらずです。
その場合は :IcedFormatSyncAll
の代わりに :IcedFormatSync
コマンドを使うとカーソル配下のフォームだけを対象にフォーマット可能です。
フォームを編集 → 保存をこまめに行っている場合だとこちらの設定でフリーズはほぼ回避できますが、保存時のフォーマット対象がカーソル配下のフォームに限定されるのでフォーマット漏れが出る可能性があることに注意してください。
最後に
最初に掲げたゴールを振り返ってみましょう。
Vim を使った Clojure の開発環境が構築でき、REPL 駆動開発ならびにテスト駆動開発が体験できていること
簡単ではありましたがゴールの通りの体験ができたでしょうか? これをきっかけとして Clojure での開発に興味をもっていただけたなら幸いです。
なお vim-iced 自体にも興味を持っていただけたなら、 ここで紹介していない多くの便利な機能がまだあるので、ぜひドキュメントを読んでみていただければと思います。
Spacemacsを利用している方はこちらを参考にしてください。
トヨクモでは一緒に働いてくれるエンジニアを募集しております。