Emacsの括弧機能を超絶強化するsmartparensとrainbow-delimiters

2023年4月08日
thumbnail
smartparensはその名の通り、emacsの括弧に関するあらゆる機能を強化してくれます。
括弧機能の強化は大きく分けて3つあります。
  1. 括弧を展開、削除するショートカット
  2. 括弧とキーワード間のカーソル移動の強化
  3. 階層別括弧のハイライト
最初に完成済みコードを記述しておきます。
(use-package smartparens
  :ensure t
  :bind (
         :map smartparens-mode-map
              ("C-M-a" . sp-beginning-of-sexp)              ;; カーソルが括弧の中か上にある場合、括弧の先頭に移動
              ("C-M-e" . sp-end-of-sexp)                    ;; カーソルが括弧の中か上にある場合、括弧の最後に移動
              ;; ("C-M-d" . sp-down-sexp)          ;; 現在いる括弧の中にさらにネストした括弧がある場合その括弧に移動
              ;; ("C-M-u" . sp-up-sexp)            ;; 現在いる括弧の外の閉じ括弧に移動
              ;; ("C-M-w" . sp-backward-down-sexp) ;; カーソルから見て後ろにネストした括弧がある場合は、そのネストに入っていく
              ;; ("C-M-q" . sp-backward-up-sexp)   ;; カーソルより後ろに上位の括弧がある場合はその上位の括弧に移動する
              ("C-M-f" . sp-forward-symbol)     ;; 次のシンボルへざっくり移動する。M-f良いような気がする。
              ("C-M-b" . sp-backward-symbol)    ;; 前のシンボルへざっくり移動する。M-bで良いような気がする。
              ;; ("C-M-n" . sp-next-sexp)          ;; 現在のカーソルがある階層で次の括弧へ移動する
              ;; ("C-M-p" . sp-previous-sexp)      ;; 現在のカーソルがある階層で前の括弧へ移動する
              ("C-s-f" . sp-forward-sexp)                   ;; カーソルからの次の括弧へ移動する。
              ("C-s-b" . sp-backward-sexp)                  ;; カーソルからの前の括弧へ移動する。

              ;; カーソルがある位置のワードをその括弧で囲う
              ("C-c ("  . wrap-with-parens)
              ("C-c ["  . wrap-with-brackets)
              ("C-c {"  . wrap-with-braces)
              ("C-c '"  . wrap-with-single-quotes) ;; lisp-modeではシングルクオーはテキストではなく変数のオブジェクト化で使われるので、利用できない
              ("C-c \"" . wrap-with-double-quotes)

              ;;          ;;("M-<" . sp-backward-unwrap-sexp)  ;; input系のM-[プレフィックスにぶつかり、予期せない挙動が出るのでショートカットを変更する。
              ("M-]" . sp-unwrap-sexp) ;; 現在のカーソルがる位置の括弧を解除する
              ;;          ("C-<right>" . sp-forward-slurp-sexp) ;; 括弧が囲む範囲を右に拡張する
              ;;          ("C-<left>" . sp-forward-barf-sexp) ;; 括弧が囲む範囲を左に縮小する。
              ("M-k" . sp-kill-sexp)) ;; ざっくり括弧の範囲を削除する。変数名や関数にも使える

  :init
  (smartparens-global-mode t)   ;; 基本的なsmartparensモードを起動
  (require 'smartparens-config) ;; メージャーモードに合わせて必要な括弧のモードを起動する設定
  :config
  ;; カーソルがある位置の要素を括弧でラップするための関数
  (defmacro def-pairs (pairs)
    "Define functions for pairing. PAIRS is an alist of (NAME . STRING)
     conses, where NAME is the function name that will be created and
     STRING is a single-character string that marks the opening character.
     (def-pairs ((paren . \"(\")
              (bracket . \"[\"))
     defines the functions WRAP-WITH-PAREN and WRAP-WITH-BRACKET,respectively."
    `(progn
       ,@(cl-loop for (key . val) in pairs
                  collect
                  `(defun ,(read (concat
                                  "wrap-with-"
                                  (prin1-to-string key)
                                  "s"))
                       (&optional arg)
                     (interactive "p")
                     (sp-wrap-with-pair ,val)))))

  (def-pairs ((paren . "(")
              (bracket . "[")
              (brace . "{")
              (single-quote . "'")
              (double-quote . "\""))))

(use-package rainbow-delimiters
  :ensure t
  :init
  (add-hook 'prog-mode-hook #'rainbow-delimiters-mode)) ;;プログラミングモード全てでカッコをハイライトする

括弧の展開と解除

smartparensが扱うカッコは''(シングルクォート)""(ダブルクォート)()(小カッコ){}(中カッコ)[](大カッコ)になります。

自動的に閉じ括弧を展開

smartparensは何もしなくても開き括弧を入力すれば、閉じ括弧を展開してくれます。
()

選択範囲を一括で囲う

括弧で囲みたい範囲をC-SPCで選択して、括弧をタイプすると、その範囲が囲われます。 例えば、下のテキストを書いていて、それを丸ごとダブルクォートで囲みたい場合は、テキストを選択して"をタイプすれば囲われます。
"この範囲をC-SPCで選択して、ダブルクォートで囲ってみます"

キーワードを瞬時に囲う

上のコードのdef-pairsはカーソルの下にあるキーワードを括弧で囲う関数です。例えば、プログラムを書いていて、const userName = aliceみたいなものがあったとします。aliceが文字列になっていないので、これをダブルクォートで囲うとしたら、カーソルをaliceに合わせて、
article image
C-c "とタイプするだけで、aliceがダブルクォートで囲われます。
article image

括弧を解除する

括弧で囲われている範囲にカーソルを当てて、M-]を実行すると、括弧を解除することができます。先ほどのconst userName = "alice"の場合は"alice" のダブルクォートを外せます。その他の括弧も同じコマンドで解除できます。

括弧内のコンテンツを削除

M-kは括弧内のコンテンツだけを削除してくれます。削除する単位は大体単語レベルで削除してくれます。よく使うシチュエーションはやっぱり文字列で、ダブルクォートで囲われた文字を全て削除してくれます。
例えば、 const userName = "じゅげむじゅげむごこうのすりきれ"みたいなものでもダブルクォートの中にカーソルを当てて、M-kを実行すればじゅげむじゅげむごこうのすりきれが削除されます。

カーソル移動を強化

smartparensはカッコに関する移動だけでなく、割と良い感じの移動も提供してくれます。ただ、多くの移動機能はlispに向けたものが多いので、上の完成済みコードでも使用頻度が低いものはコメントアウトしています。
その中でも、他の言語でも使えそうな移動コマンドをバインドしています。

括弧の先頭と末尾に移動

カーソルが括弧の中にある時に、"C-M-a" sp-beginning-of-sexpをタイプすると、開き括弧に移動します。逆に"C-M-e" sp-end-of-sexpをタイプすると、閉じカッコに移動します。具体的にはこんな感じです。
←C-M-a                                 C-M-e→
"<https://raw.githubusercontent.com/install.el>"

シンボルレベルで移動

"C-M-f" sp-forward-symbolはざっくりキーワード括りでカーソルを前に移動させます。"C-M-b" sp-backward-symbolはその逆でざっくりキーワードで後ろに戻ります。 これとよく似た動き方でM-f forward-wordがあります。それぞれの動きを注意深く見ると、このような違いがあります。
forward-wordはハイフンで区切られた同一変数でもアンダーバー毎に移動します。
M-fの場合はカーソルはこのように前へ移動します
|  =>|  => |   =>  |  => |   =>   |
event-apply-control-super-modifier
一方で"C-M-f" sp-forward-symbolはこういった連結したシンボルを認知して一っ飛びします。
C-M-fの場合はカーソルはこのように前へ移動します
|                =>               |
event-apply-control-super-modifier

括弧レベルで移動

さらに飛躍的に前に移動させるのが"C-s-f" sp-forward-sexpです。これはそもそもがlispコードでの移動を対象としているので、ruby、python、jsではそこまで目的に叶った動きにはなりませ。
それでも目的の位置がM-fC-M-fではやや遠いなと思ったら使えなくもありません。例えば、このようなpythonコードがある場合C-M-fはこのように移動します
C-M-fの場合はカーソルはこのように前へ移動します
|  =>   |   =>  |  =>|      =>       | => |    =>   | =>|  =>|
response = await loop.run_in_executor(None, requests.get, url)
一方で"C-s-f" sp-forward-sexpは括弧に関わらないところでは同じ挙動をしますが、カッコに差し掛かるとその中は経由せずに飛びます。
C-s-fの場合はカーソルはこのように前へ移動します
|  =>   |   =>  |  =>|      =>       |         =>            |
response = await loop.run_in_executor(None, requests.get, url)

括弧のハイライト

最後に括弧のハイライトを自動的に行なってくれるrainbow-delimitersを紹介します。特にセットアップは必要なく、階層が深くなるにつれて認識しづらくなる括弧を色分けて表示します。
私のテーマでは括弧の色は白 > ピンク > 緑とネストする毎に色が変わり視認性が上がります。
article image

まとめ

smartparensは単純に括弧の入力、削除サポートだけでなく移動も強化してくれました。なかなか使ってみないと利用シーンはイメージしずらいと思います。ただ、コードを書いていると思ったよりもC-c "みたいなキーワードを括弧で囲むことや、間違って入力した括弧を解除するM-]は使います。ぜひ試してみてください。
profile image
Ted
大学でコンセンサスアルゴリズムを研究。卒業後ベンチャー企業に入社してフルスタックでWebサービスを開発。現在は大手IT企業に転職し、プログラミングを行っている。AIにプログラマーの仕事を奪って欲しいと願っている。