【Emacs】
SmartParensのよく使う操作
(SlurpとBarf)

  • EmacsのSmartParensは括弧の構造を保ちながら編集できるパッケージで、MELPAからインストールしてinit.elに設定を追加するだけで使い始められます。
  • SlurpとBarfは括弧の範囲を後から広げたり縮めたりする操作で、特にForward Slurp(C-))は後から関数の引数を追加したいときに頻繁に使います。
  • S式単位でカーソルを移動するコマンド群により、ネストした括弧構造をたどりながら編集できます。

関連記事

1. SmartParensのセットアップ

SmartParensは、Emacsで括弧などのペアを一括して扱うマイナーモードを提供するのパッケージです。
括弧の入力補完や構造的な編集の幅広い機能を持ちますが、まずは頻繁に使う操作を押さえれば十分に恩恵を受けられます。

MELPAからインストールし、設定ファイル(init.el)に以下を追加します。

(use-package smartparens
  :hook (prog-mode text-mode markdown-mode)
  :config
  (require 'smartparens-config))Code language: JavaScript (javascript)

smartparens-config を読み込むと、各言語向けのデフォルト設定が自動適用されます。

今回は、よく使われる paredit互換のキーバインドを前提にしています。
有効にするには設定に (sp-use-paredit-bindings) を追加してください。

また、smartparens-strict-mode を有効にして「厳格モード」にすると、kill-line などの操作でもペアの対称性が崩れないように制御されます。

1.1. 自動ペア挿入

( を入力すると () が挿入されてカーソルが中に置かれます。

入力前    : |
入力 (    : (|)
入力 foo) : (foo)|

ちなみに、文字を入力してから ) を打つと、重複した閉じ括弧が追加されずに、補完された ) をスキップします。
文字列中でダブルクォートを入力したときも自動でエスケープされます。

また、テキストを範囲選択してから ( を入力すると、選択範囲全体が括弧で囲まれます。

これが括弧で包む基本動作です。

2. Slurp と Barf で括弧への出し入れ

SmartParensらしい編集機能が、Slurp(ズルズル)とBarf(ゲロゲロ)です。

Slurp と Barf — 括弧の境界を動かす sp-forward-slurp-sexp C-) 開き括弧を広げて飲み込む sp-forward-barf-sexp C-} 開き括弧を縮めて吐き出す (print (car x) ) (print ) (car x) (print) (car x) (print (car x)) (print (car x)) (print) (car x)

後から括弧の範囲を広げたり縮めたりする操作で、「書いてから気づく」場面で活きます。
基本的に、Slurpで括弧に追加するのがメインです。

2.1. Forward Slurp C-) で関数を前につなげる

Slurpは、後ろの式を関数の引数として追加するときに便利です。

; (car x) を書いたあと その結果を print したいと気づいた
(print|) (car x)
↓ C-)
(print| (car x))Code language: PHP (php)

後から式を関数に与えることができるので、結果に処理を追加するのが書きやすくなります。
Forward / Backward x Slurp / Barf の4種類ややこしかったら、まずはFoward Slurpだけでも使ってみてください。

2.2. Forward Barf C-}

barfはslurpの反対で、末尾を括弧から外すのに使います。

(read| x (print x))
↓ C-}
(read| x) (print x)Code language: PHP (php)

これは、どちらかいうと、Slurpで間違えて次の処理を入れたときに使っています。

2.3. Backward Slurp と let

Backward SlurpとBarfは、左側の括弧を動かします。

  • Backward Slurp C-( — 開き括弧を左に広げる
  • Backward Barf C-{ — 開き括弧を右に縮める

letの変数定義やcondの条件式は、二重の括弧で包むのですが、よく間違えます。
そんなときは、後ろに括弧を追加してから、Backward Slurpで包みます。

; (x 1) を書いたあと () で包みたいと気づいた
(let (x 1)(|))
↓ C-( ↑  C-{
(let ((x 1)|)Code language: JavaScript (javascript)

右の() の開き括弧が左にずれて、左の (x 1) を包みます。

3. S式単位の移動コマンド

SmartParemsでは、S式単位でカーソルを動かして編集できます。

S式単位の移動 (outer (middle (inner) ) ) 内側へ入る C-M-d 外側へ出る C-M-u 次のS式へ C-M-f 前のS式へ C-M-b S式を削除 C-M-k (print |(car x) y) → C-M-k → (print | y) カーソル直後のS式をまるごとkill-ringへ
キーコマンド動作
C-M-fsp-forward-sexp次のS式の末尾へ
C-M-bsp-backward-sexp前のS式の先頭へ
C-M-dsp-down-sexp一段内側のリストに入る
C-M-usp-backward-up-sexp一段外側のリストに出る
C-M-nsp-up-sexp現在のリストの末尾の外へ

S式とはLispで言う括弧に囲まれた一かたまりの式のことですが、SmartParensではLisp以外の言語でもこの概念を利用できます。

C-M-d で内側へ潜り、C-M-u で外側へ出るパターンが、構造をたどる基本的な動き方です。

3.1. Kill C-M-k— S式をまるごと削除する

C-M-ksp-kill-sexp を呼ぶと、カーソル位置の次のS式を削除してkill-ringに積みます。
変数名や関数呼び出しひとかたまりをまとめて消せます。

(print |(car x) y)
↓ C-M-k
(print | y)Code language: PHP (php)

sp-copy-sexp はバッファを変更せずにkill-ringにコピーします。

4. Wrap — 既存の式を括弧で囲む

カーソルを式の上に置いて M-( を押すと、その式全体が () で囲まれてカーソルが開き括弧の直後に移動します。
カーソルが ( の直後に来るので、そのまま関数名を続けて打てます。

|(car x)
↓ M-(
(|(car x))   
(print (car x)) ← "print " と入力Code language: PHP (php)

backward slurpと似た結果になりますが、wrapは包む括弧の関数名を入力する流れに向いていて、backward slurpはすでにある括弧の範囲を広げる流れに向いています。

4.1. Unwrap M-] — 括弧だけを外す

括弧が不要になったとき、中身はそのままで括弧だけ取り除きます。

; when ブロックが不要になった
(when debug
  |(print x))
↓ M-] (sp-unwrap-sexp)
|(print x)Code language: PHP (php)

M-s で呼ぶ sp-splice-sexp でも同じ結果になる場面が多いですが、unwrapはカーソルが括弧の上にあれば即座に外せる点がシンプルです。

4.2. Splice M-s — 括弧を外す(killing バリアント付き)

M-ssp-splice-sexp を呼ぶと、カーソルが入っているリストの括弧だけが消えて中身がそのまま残ります。

(when condition
  |(do-something)
  (do-another))
↓ M-s
|(do-something)
(do-another)Code language: JavaScript (javascript)

バリアントとして、カーソルより前を削除しながら括弧を外す M-<up> と、カーソルより後を削除する M-<down> があります。
それぞれ sp-splice-sexp-killing-backwardsp-splice-sexp-killing-forward に対応します。

; M-<up> の例:when と condition を削除して中身だけ残す
(when condition
  |(do-something)
  (do-another))
↓ M-<up>
|(do-something)
(do-another)Code language: PHP (php)

4.3. Raise — 一つの式だけを残す

M-rsp-raise-sexp を呼ぶと、カーソル位置の直後にある式だけを残し、囲んでいるリストごと他の要素を消します。

; 計算式を単純化したいとき
(+ |(stuff) 10 14)
↓ M-r
|(stuff)

5. よく使う操作のまとめ

目的コマンドキー(paredit互換)
前方slurpsp-forward-slurp-sexpC-)
前方barfsp-forward-barf-sexpC-}
後方slurpsp-backward-slurp-sexpC-(
後方barfsp-backward-barf-sexpC-{
S式を削除sp-kill-sexpC-M-k
括弧で包むsp-wrap-roundM-(
括弧を外すsp-unwrap-sexpM-]
次のS式へsp-forward-sexpC-M-f
前のS式へsp-backward-sexpC-M-b
内側へ入るsp-down-sexpC-M-d
外側へ出るsp-backward-up-sexpC-M-u
括弧を外す(splice)sp-splice-sexpM-s
式だけ残すsp-raise-sexpM-r

5.1. ナビゲーションの粒度を2段階に分ける

ちなみに、標準のparedit互換配列では C-M-fC-M-bsp-forward-sexpsp-backward-sexp を置きます。
これらは括弧一かたまりをまるごとスキップしますが、日常的な編集では変数名や関数名といったシンボル単位で止まりたい場面の方が多いです。

そこで C-M-fC-M-bsp-forward-symbolsp-backward-symbol に差し替え、S式単位の移動は別のキーに退ける設計があります。
シンボル単位の移動は標準の forward-word に当たる M-f に近い感覚で使えるため、止まりたい場所により正確に止まれるようになります。