emacs のテキストモードで Shift+Tab を実現する

秀丸のような Shift+Tab のバックタブ(逆インデント)を emacs で実現してみた。

タブ位置の調整

emacs ではインデントの考え方が特殊で、テキストモードで (setq tab-width 4) としても効果がない。tab-width に従うのは C 言語モードの Tab キーに割り当てられている c-indent-line-or-region で、テキストモードの Tab キーに割り当てられている tab-to-tab-stop は tab-stop-list というインデント位置のリストに従う。例として幅 4 のソフトタブの設定を以下に示す。

(add-hook 'text-mode-hook 
          '(lambda()
             (define-key text-mode-map "\C-i" 'tab-to-tab-stop)
             (define-key text-mode-map [backtab] 'backtab)
             (setq tab-stop-list '(4 8 12 16 20 24 28 32 36 40 44 48 52 56 60 64 68 72 76 80 84 88 92 96 100 104 108 112 116 120 124 128))
             (setq indent-tabs-mode nil)))

バックタブ

超汚くて恐縮だが、以下のように (backtab) を実装した。

(defun backtab()
  "Do reverse indentation"
  (interactive)
  (back-to-indentation)
  (delete-backward-char
   (if (< (current-column) (car tab-stop-list)) 0
     (- (current-column)
        (car (let ((value (list 0)))
               (dolist (element tab-stop-list value) 
                 (setq value (if (< element (current-column)) (cons element value) value)))))))))

追記(リージョンの対応)

やっつけ実装ですが、複数行まとめてタブを入れたり、タブを抜いたりできます。

(defun backtab-line-or-region ()
  (interactive)
  (if mark-active (save-excursion
                    (setq count (count-lines (region-beginning) (region-end)))
                    (goto-char (region-beginning))
                    (while (> count 0)
                      (backtab)
                      (forward-line)
                      (setq count (1- count)))
                    (setq deactivate-mark nil))
    (backtab)))

(defun tab-to-tab-stop-line-or-region ()
  (interactive)
  (if mark-active (save-excursion
                    (setq count (count-lines (region-beginning) (region-end)))
                    (goto-char (region-beginning))
                    (while (> count 0)
                      (tab-to-tab-stop)
                      (forward-line)
                      (setq count (1- count)))
                    (setq deactivate-mark nil))
    (tab-to-tab-stop)))