ruby

概要

Ruby のコードを編集する上での設定をここには書いている。別の箇所で rspec-mode などの設定も書いているのでいつか記述場所を統合した方が良さそうな気もする

rbenv.el

Ruby のバージョンを切り替えられる rbenv を使ってるので Emacs 上でもそれが使えるように rbenv.el を導入している。

インストール

インストールはいつも通り el-get でやっている

(el-get-bundle rbenv)

有効化

そして global に有効化している。というか global じゃない有効化ってあるのかなってのと、あるとしても意味があるのかな的な。

(global-rbenv-mode)

bundler.el

bundler.el は Emacs から Ruby の bundler を操作するためのパッケージ。 bundler-open が便利っぽいので入れている

インストール

(el-get-bundle bundler)

これ以外は特に設定していない。いずれ Ruby 用の Hydra に設定を突っ込む。

yard-mode

メソッドコメントなんかは Yard 形式で書くのでこれも入れている

インストール

(el-get-bundle yard-mode)

ruby-refactor

メソッド切り出しとかの機能を提供してくれるパッケージ。まだちゃんと試してないけど便利かもということでとりあえず入れてみている。

インストール

el-get 本体にレシピは登録済なのでそのまま el-get-bunlde でインストールしている

(el-get-bundle ruby-refactor)

設定

  • メソッドのパラメータを出力する際にカッコを付与するように設定
  • RSpec で同階層の context/describe に let を切り出す

という設定にしている。

(setopt ruby-refactor-add-parens t)
(setopt ruby-refactor-let-position 'closest)

enh-ruby-mode

メジャーモードは enhanced-ruby-mode を利用している。が、最近は ruby-mode の方がやっぱり良いみたいな話もどこかで見た気がするので戻ってみるのも手かもしれないと思っている。

といいながら今は ruby-ts-mode も検証しているので、 enh-ruby-mode 向けの設定に ruby-ts-mode の設定も混ぜている

インストール

いつも通り el-get で入れている。

(el-get-bundle enh-ruby-mode)

カスタム設定

enh-ruby-mode が読み込まれた後に setq で以下のように設定されている

(with-eval-after-load 'enh-ruby-mode
  (setq enh-ruby-add-encoding-comment-on-save nil)
  (setq enh-ruby-deep-indent-paren nil)
  (setq enh-ruby-deep-indent-construct nil)
  (setq enh-ruby-bounce-deep-indent nil))

encoding のマジックコメントが入らないようにする

enh-ruby-add-encoding-comment-on-save を nil にすることで encoding 設定のマジックコメントが入らないようにしている。

これは昔は有効にしておいた方が良かったけど最近の Ruby では設定しなくても UTF-8 が前提になるからむしろ無い方が良いというお話だったはず。

そういう状況に変わったのも大分前なので詳細は忘れた。

ただとりあえず 最新の enhanced-ruby-mode を見るとデフォルトが nil なのでわざわざ設定しなくて良さそう。

インデントの調整

enh-ruby-deep-indent-paren が t の場合

hoge = {
         foo: 1
       }

みたいな深いインデントになるけど

hoge = {
  foo: 1
}

というようにしたいので nil に設定している。

インデントの切替

インデントを深くしたくないといいつつ、全然それができないのも困りそうなので enh-ruby-bounce-deep-indent を t に設定してタブを押すごとに切り替わるようにしている。

ところでデフォルトで深い方になってるような気がするので今度設定の見直しした方が良さそう。

hook

hook 用の関数で補完などの機能を有効にしている

(defun my/ruby-modes-hook ()
  (origami-mode 1)
  (company-mode 1)
  (setq-local company-backends
              '(company-capf (company-keywords company-dabbrev-code) company-yasnippet company-files company-dabbrev))

  (subword-mode 1)
  (copilot-mode 1)
  (ruby-refactor-mode 1)
  (yard-mode 1)
  (eldoc-mode 1)
  (turn-on-smartparens-strict-mode)
  (display-line-numbers-mode 1))
  • コード折り畳み用に origami を有効化
  • 補完用に company-mode を有効化
    • backend も余計なものが入らないようにカスタマイズしている
  • CamelCase の単語区切りを有効にするため subword-mode を有効化
  • Copilot もあると便利なので有効化
  • リファクタリング用に ruby-refactor-mode の有効化
  • yard 形式のコメントも良い感じにハイライトされると便利なので yard-mode を有効化
    • 更に eldoc-mode を有効にしていると yard コメントを書く時に文法を minibuffer に表示してくれるのでこちらも有効化
  • 開きカッコと閉じカッコの組み合わせがズレないように smartparens-strict-mode を有効にしている
  • 行番号も表示されている方が便利なので display-line-numbers-mode を有効にしている
  • lsp-mode に関してはプロジェクト毎にごにゃごにゃしたいので .dir-locals.el に任せる方針にしている
    • BUNDLE_GEMFILE を設定したりとかね

それらを設定する関数を enh-ruby-mode-hook に突っ込んでいる

(add-hook 'enh-ruby-mode-hook 'my/ruby-modes-hook)

そして同じ hook を ruby-ts-mode にも設定してる

(add-hook 'ruby-ts-mode-hook 'my/ruby-modes-hook)

SKK の調整

enh-ruby-modecontext-skk-programming-mode に追加することで Ruby を使ってる時にコメント部分はクォートの外以外では自動的に日本語入力がオフになるようにしている

(add-to-list 'context-skk-programming-mode 'enh-ruby-mode)

同じ設定を ruby-ts-mode にも突っ込んでいる

(add-to-list 'context-skk-programming-mode 'ruby-ts-mode)

キーバインド

キーバインドは覚えられないので major-mode-hydra でキーを定義している

(with-eval-after-load 'major-mode-hydra
  (let ((heads '("Ruby"
                 (("{" enh-ruby-toggle-block               "Toggle block")
                  ("e" enh-ruby-insert-end                 "Insert end")
                  ("p" ruby-refactor-add-parameter         "Add param")
                  ("i" ruby-refactor-convert-post-condition "if/unless block"))

                 "Extract to"
                 (("M" ruby-refactor-extract-to-method      "method")
                  ("V" ruby-refactor-extract-local-variable "local variable")
                  ("C" ruby-refactor-extract-constant       "constant"))

                 "RSpec"
                 (("l" ruby-refactor-extract-to-let "let")
                  ("s" rspec-verify                 "Run associated spec")
                  ("m" rspec-verify-method          "Run method spec")
                  ("r" rspec-rerun                  "Rerun")
                  ("f" rspec-run-last-failed        "Run last failed"))

                 "REPL"
                 (("I" inf-ruby "inf-ruby"))

                 "Other"
                 (("j" dumb-jump-go       "Dumb Jump")
                  ("o" origami-hydra/body "Origami")))))
    (eval `(major-mode-hydra-define enh-ruby-mode (:separator "-" :quit-key "q" :title (concat (nerd-icons-mdicon "nf-md-language_ruby") " Ruby commands"))
             (,@heads)))
    (eval `(major-mode-hydra-define ruby-ts-mode  (:separator "-" :quit-key "q" :title (concat (nerd-icons-mdicon "nf-md-language_ruby") " Ruby commands"))
             (,@heads)))))
Key 効果
{ do 〜 end と { 〜 } を切り替える
e end を挿入する。使ったことない気がする
p メソッドにパラメータを追加
i 後置 if/unless をブロック形式に変更
M メソッド切り出し
V ローカル変数に切り出し
C 定数に切り出し
l let に切り出し
s 関連するテストまたは特定のテストの実行
m カーソル位置のコードのテストを探して実行する
r 最後に実行したテストを再実行
l 最後に失敗したテストの再実行
I REPL バッファで Ruby を実行する
j dumb-jump で関数定義にジャンプ。dumb-jump 用の hydra があるから要らなさそう
o Origami 用の Hydra を起動する

snippets

enh-ruby-mode 用に自前でスニペットも用意している

YARD

YARD の文法をなかなか覚えられないのでとりあえず snippet を用意している

  • @param

    YARD で一番使うけど params なのか param なのか分からなくなるやつ。というわけで真っ先に登録

    # key: #@p
    # name: @param
    # --
    # @param [${1:type}] ${2:name} $0
    
  • @deprecated

    こっちもまあまあ使いたくなるし deprecation なのか deprecated なのか分からなくなるやつ。

    # -*- mode: snippet -*-
    # name: @deprecated
    # key: #@d
    # --
    # @deprecated $0
    
  • @return

    戻り値は関数には必須なのでこれも入れておく

    # key: #@r
    # name: @return
    # --
    # @return [${1:type}] $0
    

その他

  • frozen_string_literal

    frozen_string_literal のマジックコメントも使うことがあるけどよくスペルミスをするので入れておいている

    # -*- mode: snippet -*-
    # name: frozen_string_literal
    # key: frozen
    # --
    # frozen_string_literal: true