1. 読みやすさの改行と除外したい範囲
ブログ記事を書くとき、読みやすさのために「。」の直後で改行を入れるようにしています。
以前は replace-regexp で正規表現を使い、直後が改行でない「。」だけを「。\n」に置換していました。
。\([^\n]\) -> 。\n\1Code language: CSS (css)
ただし、すべての「。」が改行されてしまうので、問題がありました。
WordPressの脚注ショートコード [ efn_note]...[/ efn_note] では、除外したかったのです。
ショートコードは開始タグと終了タグが対応しているので、その範囲をEmacs Lispで検出して置換から除外するように、init.elに専用のコマンドを書きました。
1.1. Emacs Lispのコード
;; リージョン内の「。」の後ろに改行を入れる。
(defun replace-japanese-period-newline (beg end)
"リージョン内、またはリージョンがなければバッファ全体の「。」の後ろに改行を入れる。
ただし [ efn_note] ... [/ efn_note] 内は処理しない。"
(interactive
(if (use-region-p)
(list (region-beginning) (region-end))
(list (point-min) (point-max))))
(save-excursion
(let ((ranges (jp--collect-efn-note-ranges beg end)))
(goto-char beg)
(while (re-search-forward "。" end t)
(let ((pos (point)))
(unless (or (looking-at "\n")
(jp--pos-in-ranges-p pos ranges))
(insert "\n")
(setq end (1+ end))))))))
(defun jp--collect-efn-note-ranges (beg end)
"リージョン内の [ efn_note] ... [/ efn_note] の範囲をリストで返す。
各要素は (START . END)。"
(save-excursion
(goto-char beg)
(let (ranges)
(while (search-forward "[ efn_note]" end t)
(let ((start (match-beginning 0)))
(when (search-forward "[/ efn_note]" end t)
(push (cons start (point)) ranges))))
ranges)))
(defun jp--pos-in-ranges-p (pos ranges)
"POS が ranges のどれかの範囲内なら t。"
(catch 'found
(dolist (r ranges)
(when (and (>= pos (car r)) (<= pos (cdr r)))
(throw 'found t)))
nil))Code language: Lisp (lisp)
テキストを選択してから M-x replace-japanese-period-newline を実行すると、選択範囲内の「。」の後ろに改行を挿入します。
リージョンを選択していない場合はバッファ全体が対象になります。
私は、ミニバッファでSMEXでIDOを使っているので、repjapなどの短縮形で実行しています。
2. コード解説
replace-japanese-period-newline関数は、内部でjp--pos-in-ranges-p と jp--collect-efn-note-ranges の2つの関数を使っています。
2.1. interactive フォームでリージョン判定
はじめは、(interactive "r") で作ってリージョンをbeg、endの引数に入れていました。
ただ、リージョンを指定しないときには、バッファ全体にした方が便利だと思いました。
そこで次のように書き換えました。
(interactive
(if (use-region-p)
(list (region-beginning) (region-end))
(list (point-min) (point-max))))Code language: Lisp (lisp)
use-region-p でリージョンが有効かどうかを判定し、有効ならその両端、無効ならバッファの先頭と末尾を返します。interactive フォームが返すリストの各要素が、関数の引数 beg、end に順番に束縛されるため、この変更だけで両方のケースに対応できます。
2.2. 段落末の「。」を除外する
(looking-at "\n") で、「。」の直後がすでに改行かどうかを確認しています。
段落の末尾にある「。」の後ろに改行を入れると改行が連続してしまうため、その場合は何もしません。
これは以前 replace-regexp で正規表現を使って同じ条件を表現していた部分を、そのまま引き継いでいます。
2.3. efn_note ブロックを除外する
jp--collect-efn-note-ranges が処理対象範囲内の [ efn_note]...[/ efn_note] を探し、その開始・終了位置のペアをリストで返します。
jp--pos-in-ranges-p は、カーソル位置がそのリスト内のどれかの範囲に含まれるかどうかを判定します。
「。」を見つけるたびにこの判定を行い、ショートコード内であれば挿入をスキップします。
2.4. end をインクリメントする理由
insert でテキストを挿入するとバッファの内容が変わり、end が指す位置がずれます。
1文字挿入するたびに (setq end (1+ end)) で補正しているのはそのためです。
これを忘れると、後半の「。」が処理されないまま終わることがあります。