オススメのEmacsの標準関数と自作関数のBindings

2023年3月28日
thumbnail
emacsはサードパティ製のパッケージを使わなくても、標準の関数を利用してコーディングが可能です。便利なのにデフォルトではバインドされていない関数があったりするので、オススメの関数ちょっと紹介します。それに加えて、関数を作成して編集が楽になるバインディングも紹介します。
全体のコードはページの最後にまとめてあります。

kill-this-buffer

;; 現在開いているバッファを閉じる
(global-set-key (kbd "C-c k") 'kill-this-buffer)
kill-this-bufferは現在開いているバッファを閉じます。

toggle-truncate-lines

;; 行の画面外の折り返しを切り替えるショートカット
(global-set-key (kbd "M-l") 'toggle-truncate-lines)
toggle-truncate-linesはテキストがウィンドウの端まで到達し、画面外に文字を伸ばすのか、折り返すのかを切り分ける関数です。コーディングしているときは外部にはみ出したほうが読みやすかったり、はたまた文章を書くときは折り返されたほうが読みやすかったりします。
はみ出した行を切り取る。
article image
はみ出した行を折り返す。
article image
表示の切り替えをすぐにできたほうが便利なのでM-lにバインドします。M-lはデフォルトではdowncase-wordが割り当てられています。これはカーソルの位置から見て前に大文字の単語があれば、それを全て小文字にするという関数です。コーディングでも文章を書くときでも利用するシーンは少ないので上書きします。

comment-or-uncomment-region

選択している範囲のコードをコメントイン、コメントアウトを切り替える関数です。 コードを書いているときに素早くコメントアウトできるのは重要です。
;; 選択範囲を全てコメントアウト、コメントインする
(global-set-key (kbd "C-c /") 'comment-or-uncomment-region)
C-SPCで選択モードにしてコメントアウトしたい範囲を選び、そのままC-c /でコメントアウトします。コメントインもC-SPCでコメントアウトされた範囲を選び、C-c /でコメントインします。

revert-buffer-no-confirm

ここからはemacsの標準の関数を使うのではなく、いろんなサイトを参考にして作った関数を使います。revert-buffer-no-confirmはバッファのリロード機能です。バッファはファイルを直接編集しているわけではないので、たまにファイルとバッファで差分が生まれます。
特にgitを利用していて、ブランチを移動したり、リベースしたりすると、ファイルとバッファが異なる状態になります。バッファ側の内容を優先させるのではなく、gitで移動した先のファイル情報をバッファに適応したい場合にrevert-buffer-no-confirmを実行してバッファをリロードします。
;; バッファの再読み込みをC-M-rで実行する
(defun revert-buffer-no-confirm (&optional force-reverting)
  (interactive "P")
  (if (or force-reverting (not (buffer-modified-p)))
      (revert-buffer :ignore-auto :noconfirm)
    (error "The buffer has been modified")))
(global-set-key (kbd "C-M-r") 'revert-buffer-no-confirm)
上書きするC-M-rはデフォルトでisearch-backward-regexpです。これは正規表現を利用してカーソルより前の文字を検索するバインドです。一般的にC-risearch-backwardを使うので上書きしても問題ありません。

kill-dired-buffers

emacsはバッファでフォルダを開くことできます。C-x C-fでファイルじゃなくフォルダを選んでエンターを押すと、フォルダ情報がバッファに展開されます。
article image
バッファに開かれたディレクトリバッファ内でフォルダを移動すると、このように、大量にディレクトリバッファが開かれます。
article image
1つ1つ削除するのは大変なので、kill-dired-buffersで一括削除します。
;; ディレクトリバッファを全て削除
(defun kill-dired-buffers ()
  (interactive)
  (mapc (lambda (buffer)
          (when (eq 'dired-mode (buffer-local-value 'major-mode buffer))
            (kill-buffer buffer)))
        (buffer-list)))
(global-set-key (kbd "C-c d d") 'kill-dired-buffers)
この関数はそこまで使わないので、空いているC-c d dに割り振ります。

mark-whole-word

mark-whole-wordはカーソルの下にある英単語全体を選択します。プログラムを書いていて、目的の変数にカーソルを移動して、その変数をコピーまたは切り取りしたいとします。 単語の先頭までカーソルを移動し、C-SPCで選択モードにして、変数の終わりまでカーソルを移動して、M-wでコピー、C-wで切り取りをするのは正直面倒です。
mark-whole-wordを使えば一瞬で選択されます。
;; カーソルの下にある単語を全体を選択
(defun mark-whole-word (&optional arg allow-extend)
  "Like `mark-word', but selects whole words and skips over whitespace.
If you use a negative prefix arg then select words backward.
Otherwise select them forward.

If cursor starts in the middle of word then select that whole word.

If there is whitespace between the initial cursor position and the
first word (in the selection direction), it is skipped (not selected).

If the command is repeated or the mark is active, select the next NUM
words, where NUM is the numeric prefix argument.  (Negative NUM
selects backward.)"
  (interactive "P\\np")
  (let ((num  (prefix-numeric-value arg)))
    (unless (eq last-command this-command)
      (if (natnump num)
          (skip-syntax-forward "\\\\s-")
        (skip-syntax-backward "\\\\s-")))
    (unless (or (eq last-command this-command)
                (if (natnump num)
                    (looking-at "\\\\b")
                  (looking-back "\\\\b")))
      (if (natnump num)
          (left-word)
        (right-word)))
    (mark-word arg allow-extend)))
(global-set-key (kbd "C-c @") 'mark-whole-word)
変数にカーソルを合わせます。
article image
C-c @を実行すると、userAge変数が全て選択されます。
article image

copy-word

このコマンドはカーソルの下にある単語をコピーします。1つ前のmark-whole-wordは選択でしたが、こちらはそれのコピーバージョンです。
;; カーソルを置いてある単語をコピーする
(defun copy-word (&optional arg)
  "Copy words at point into kill-ring"
  (interactive "P")
  (defun get-point (symbol &optional arg)
    "get the point"
    (funcall symbol arg)
    (point))
  (defun copy-thing (begin-of-thing end-of-thing &optional arg)
    "Copy thing between beg & end into kill ring."
    (save-excursion
      (let ((beg (get-point begin-of-thing 1))
            (end (get-point end-of-thing arg)))
        (copy-region-as-kill beg end))))
  (copy-thing 'backward-word 'forward-word arg))
(global-set-key (kbd "C-c w") 'copy-word)
使い方はmark-whole-wordと同じで、コピーしたい英単語の上でC-c wを実行するだけでkill ringに単語がコピーされます。

copy-whole-line

copy-whole-line はカーソルがある行を丸ごとコピーするコマンドです。
 ;; 一行を丸ごとコピー
 (defun copy-whole-line (&optional arg)
   "Copy current line."
   (interactive "p")
   (or arg (setq arg 1))
   (if (and (> arg 0) (eobp) (save-excursion (forward-visible-line 0) (eobp)))
       (signal 'end-of-buffer nil))
   (if (and (< arg 0) (bobp) (save-excursion (end-of-visible-line) (bobp)))
       (signal 'beginning-of-buffer nil))
   (unless (eq last-command 'copy-region-as-kill)
     (kill-new "")
     (setq last-command 'copy-region-as-kill))
   (cond ((zerop arg)
          (save-excursion
            (copy-region-as-kill (point) (progn (forward-visible-line 0) (point)))
            (copy-region-as-kill (point) (progn (end-of-visible-line) (point)))))
         ((< arg 0)
          (save-excursion
            (copy-region-as-kill (point) (progn (end-of-visible-line) (point)))
            (copy-region-as-kill (point)
                                 (progn (forward-visible-line (1+ arg))
                                        (unless (bobp) (backward-char))
                                        (point)))))
         (t
          (save-excursion
            (copy-region-as-kill (point) (progn (forward-visible-line 0) (point)))
            (copy-region-as-kill (point)
                                 (progn (forward-visible-line arg) (point))))))
   (message (substring (car kill-ring-yank-pointer) 0 -1)))
 (global-set-key (kbd "C-c l") 'copy-whole-line)
バインドキーはC-c lです。

まとめ

バインドした関数とショートカットをまとめておきます。
  • kill-this-buffer C-c k
  • toggle-truncate-lines M-l
  • comment-or-uncomment-region C-c /
  • revert-buffer-no-confirm C-M-r
  • kill-dired-buffers C-c d d
  • mark-whole-word C-c @
  • copy-word C-c w
  • copy-whole-line C-c l
ここで挙げたもの以外にもEmacsは標準で搭載されていて便利な関数があるので、ぜひ自分で探して、バインドしてみてください。
;; 現在開いているバッファを閉じる
(global-set-key (kbd "C-c k") 'kill-this-buffer)

;; 行の画面外の折り返しを切り替えるショートカット
(global-set-key (kbd "M-l") 'toggle-truncate-lines)

;; 選択範囲を全てコメントアウト、コメントインする
(global-set-key (kbd "C-c /") 'comment-or-uncomment-region)

;; バッファの再読み込みをC-M-rで実行する
(defun revert-buffer-no-confirm (&optional force-reverting)
  (interactive "P")
  (if (or force-reverting (not (buffer-modified-p)))
      (revert-buffer :ignore-auto :noconfirm)
    (error "The buffer has been modified")))
(global-set-key (kbd "C-M-r") 'revert-buffer-no-confirm)

;; ディレクトリバッファを全て削除
(defun kill-dired-buffers ()
  (interactive)
  (mapc (lambda (buffer)
          (when (eq 'dired-mode (buffer-local-value 'major-mode buffer))
            (kill-buffer buffer)))
        (buffer-list)))
(global-set-key (kbd "C-c d d") 'kill-dired-buffers)

;; カーソルの下にある単語を全体を選択
(defun mark-whole-word (&optional arg allow-extend)
  "Like `mark-word', but selects whole words and skips over whitespace.
If you use a negative prefix arg then select words backward.
Otherwise select them forward.

If cursor starts in the middle of word then select that whole word.

If there is whitespace between the initial cursor position and the
first word (in the selection direction), it is skipped (not selected).

If the command is repeated or the mark is active, select the next NUM
words, where NUM is the numeric prefix argument.  (Negative NUM
selects backward.)"
  (interactive "P\\np")
  (let ((num  (prefix-numeric-value arg)))
    (unless (eq last-command this-command)
      (if (natnump num)
          (skip-syntax-forward "\\\\s-")
        (skip-syntax-backward "\\\\s-")))
    (unless (or (eq last-command this-command)
                (if (natnump num)
                    (looking-at "\\\\b")
                  (looking-back "\\\\b")))
      (if (natnump num)
          (left-word)
        (right-word)))
    (mark-word arg allow-extend)))
(global-set-key (kbd "C-c @") 'mark-whole-word)

;; カーソルを置いてある単語をコピーする
(defun copy-word (&optional arg)
  "Copy words at point into kill-ring"
  (interactive "P")
  (defun get-point (symbol &optional arg)
    "get the point"
    (funcall symbol arg)
    (point))
  (defun copy-thing (begin-of-thing end-of-thing &optional arg)
    "Copy thing between beg & end into kill ring."
    (save-excursion
      (let ((beg (get-point begin-of-thing 1))
            (end (get-point end-of-thing arg)))
        (copy-region-as-kill beg end))))
  (copy-thing 'backward-word 'forward-word arg))
(global-set-key (kbd "C-c w") 'copy-word)

;; 一行を丸ごとコピー
(defun copy-whole-line (&optional arg)
  "Copy current line."
  (interactive "p")
  (or arg (setq arg 1))
  (if (and (> arg 0) (eobp) (save-excursion (forward-visible-line 0) (eobp)))
      (signal 'end-of-buffer nil))
  (if (and (< arg 0) (bobp) (save-excursion (end-of-visible-line) (bobp)))
      (signal 'beginning-of-buffer nil))
  (unless (eq last-command 'copy-region-as-kill)
    (kill-new "")
    (setq last-command 'copy-region-as-kill))
  (cond ((zerop arg)
         (save-excursion
           (copy-region-as-kill (point) (progn (forward-visible-line 0) (point)))
           (copy-region-as-kill (point) (progn (end-of-visible-line) (point)))))
        ((< arg 0)
         (save-excursion
           (copy-region-as-kill (point) (progn (end-of-visible-line) (point)))
           (copy-region-as-kill (point)
                                (progn (forward-visible-line (1+ arg))
                                       (unless (bobp) (backward-char))
                                       (point)))))
        (t
         (save-excursion
           (copy-region-as-kill (point) (progn (forward-visible-line 0) (point)))
           (copy-region-as-kill (point)
                                (progn (forward-visible-line arg) (point))))))
  (message (substring (car kill-ring-yank-pointer) 0 -1)))
(global-set-key (kbd "C-c l") 'copy-whole-line)
profile image
Ted
大学でコンセンサスアルゴリズムを研究。卒業後ベンチャー企業に入社してフルスタックでWebサービスを開発。現在は大手IT企業に転職し、プログラミングを行っている。AIにプログラマーの仕事を奪って欲しいと願っている。