続・Emacsで連番を挿入する方法

久々のEmacsネタ。

歴史は繰り返すものです。 今はブラウザでなんでもできてしまう時代、かつてのEmacsを彷彿とさせます。 だが、ブラウザがEmacsに決定的に劣っているのは、『速さが足りない』ことだと思ってます。

フレーム、フラッシュなどマウス前提のIFなので、素早い動作が難しいです。 対して、Emacsはテキスト入力のスペシャリスト。 テキスト入力が関わる場面があれば、そこに必ずEmacsの活躍があります。 dabbrevyasnippetなど、入力に不可欠なツールが無数に存在するからです。

さてさて…

2年前に書いた記事 では、外部コマンドやloopを使ってEmacsで連番を入力する方法を紹介しました。 しかし、めんどいですね。 なので、Emacs Lispで書きました。

M-x duplicate-this-line-forward 直前の行をコピーする

直前の行と同じ行を作成することはよくあります。 C-a C-k C-k C-y C-y ...と操作すると思いますが、キルリングが置き換わってしまう問題があります。 そこで、このコード。

(defun duplicate-this-line-forward (n)
  "Duplicates the line point is on.  The point is next line.
 With prefix arg, duplicate current line this many times."
  (interactive "p")
  (when (eq (point-at-eol)(point-max))
    (save-excursion (end-of-line) (insert "\n")))
  (save-excursion
    (beginning-of-line)
    (dotimes (i n)
      (insert-buffer-substring (current-buffer) (point-at-bol)(1+ (point-at-eol))))))

僕は F5 に割り当てています。 連打することでたくさんコピーできます。 数引数を指定すると、その回数だけコピーします。

M-x seq 数字のみが違う同じ行を作成する

「第1回」から「第10回」までの行を作成したいとします。 そのとき、「第1回」という行をM-x duplicate-this-line-forwardでコピーして数字だけを書き換えるのは面倒です。 そこで、このコード。

(defun count-string-matches (regexp string)
  (with-temp-buffer
    (insert string)
    (count-matches regexp (point-min) (point-max))))
(defun seq (format-string from to)
  "Insert sequences with FORMAT-STRING.
FORMAT-STRING is like `format', but it can have multiple %-sequences."
  (interactive
   (list (read-string "Input sequence Format: ")
         (read-number "From: " 1)
         (read-number "To: ")))
  (save-excursion
    (loop for i from from to to do
          (insert (apply 'format format-string
                         (make-list (count-string-matches "%[^%]" format-string) i))
                  "\n")))
  (end-of-line))

M-x seqでは、数字部分をformat関数の書式指定にしておくことでそこが数字に置き換わります。 このコマンドは、書式指定→開始番号→終了番号の順で聞いてきます。 この例では、「M-x seq RET 第%d回 RET RET 10 RET」と操作します。

%dは複数個持つことができます。 「M-x seq RET [%02d]第%d回 RET RET 3 RET」で以下の行が挿入されます。


[01]第1回
[02]第2回
[03]第3回

M-x number-rectangle 連番の長方形を作成する

最後に、連番の長方形を作成するコマンドを作りました。

(eval-when-compile (require 'cl))
(defun number-rectangle (start end format-string from)
  "Delete (don't save) text in the region-rectangle, then number it."
  (interactive
   (list (region-beginning) (region-end)
         (read-string "Number rectangle: " (if (looking-back "^ *") "%d. " "%d"))
         (read-number "From: " 1)))
  (save-excursion
    (goto-char start)
    (setq start (point-marker))
    (goto-char end)
    (setq end (point-marker))
    (delete-rectangle start end)
    (goto-char start)
    (loop with column = (current-column)
          while (<= (point) end)
          for i from from   do
          (insert (format format-string i))
          (forward-line 1)
          (move-to-column column)))
  (goto-char start))

(global-set-key "\C-xrN" 'number-rectangle)

C-x r t (string-rectangle)は、同じ文字列を複数行に書く(行頭の「*」や引用符「> 」など)のに便利です。 その連番バージョンです。

使い方は連番を入れたい行の行頭をリージョンとして指定し、C-x r Nと操作します。 すると、連番の書式、開始番号を聞いてきます。 書式は「%d. 」ならば「1. 」、「2. 」〜という感じです。

あるいは、「第1回」の行をM-x duplicate-this-line-forwardでコピーして、数字部分を長方形リージョンに指定する使い方もあります。 そのときは書式は「%d」と指定します。

なお、このコマンドと同じような働きをするコマンドM-x rectangle-number-linesが開発版Emacsに存在し、C-x r Nに割り当ててあります。

両者の違いは、M-x number-rectangleの方が賢くて使いやすいことです。

  • カーソル位置によってデフォルトの書式が異なる
    • 行頭(インデントがあってもよい)のときは「%d. 」
    • 行頭以外では「%d」
  • M-x number-rectangleはC-uをつけないと細かな指定ができない

ただのリサーチ不足ではありますが、この程度のコマンドはすぐ書けるので。 結果的には好みのコマンドが出来上がりました。

まとめ

今回はM-x seq、M-x number-rectangleの2種類の連番コマンドを定義しました。 それに付随してM-x duplicate-this-line-forwardも定義しました。

では、よりよいEmacsライフを! お役に立てれば幸いだ。