リージョンコマンド

リージョンに関するコマンドは色々ある。それらはばらばら、ってわけでもないが、色々なキーにちりばめてバインドされている。バインドされてないものもある。ここでもキーが足りない! と思うわけで、リージョン操作を一手に引き受けるコマンドを作ろう。


このコマンドは、呼び出されたらさらに入力を要求して、入力されたキーに応じて処理を行う。レジスタと異なり、操作に従って内容が変化するわけではないので、キーマップに直接書けば良いと思うかも知れない。しかし、その方法とは異なる部分がある。

もし、コマンドを別のキーにバインドしたいとき、直接キーマップに書いた場合は全て書き直さなければならないが、こちらの場合は1つで済む。また、最後のキーを入力しなかった、もしくは、対応するコマンドが存在しなかったときに実行する動作を作れる。欠点もあって、define-keyが使えなかったり、入力が完了するまで他の動作を受け付けなかったりする。

(let ((table (make-hash-table :size 7)))
  (defun region-op (&optional chr)
    (interactive)
    (if (interactive-p)
        (let ((ime (get-ime-mode)))
          (message "cregion-op (h is help command)")
          (toggle-ime nil)
          (setq chr (read-char *keyboard*))))
    (let ((func (gethash chr table)))
      (if (or (null chr) func)
          (call-interactively func)
       (progn (call-interactively (gethash 'default table))
         (unread-char chr)))))

  (defun region-op-table () table)

  (defun define-region-op (chr command)
    (setf (gethash chr table) command)))

(defun region-op-command-help (&optional fp)
  (interactive)
  (let ((str (with-output-to-string (fpi)
               (format fpi "(region-op key commands)~%")
               (maphash (lambda (x y) (format fpi "~%~A ~A" x y))
                        (region-op-table)))))
    (if fp (format fp str)
      (popup-string str (point)))))

(defun enclose-region (le re &optional from to)
  (interactive "*sbefore enclosure: \nsafter enclosure: \nr")
  (let ((pos (set-marker(make-marker))))
    (goto-char (max from to))
    (insert re)
    (goto-char (min from to))
    (insert le)
    (goto-marker pos)))

(defmacro enclose-region-with (le re)
  `(defun ,(intern (concat "enclose-by-" le re "-region")) (from to)
     (interactive "*r")
     (enclose-region ,le ,re from to)))

(enclose-region-with "\"" "\"")
(enclose-region-with "(" ")")
(enclose-region-with "$" "$")
(enclose-region-with "[" "]")
(enclose-region-with "{" "}")
(enclose-region-with "<" ">")

(mapcar (lambda (x) (define-region-op (car x) (cadr x)))
        '((default kill-region)
          (nil quit)
          (#\C-g quit)
          (#\w copy-region-as-kill)
          (#\i indent-region)
          (#\n narrow-to-region)
          (#\c copy-region-to-clipboard)
          (#\/ minibuffer-convert-backslash-to-slash-region)
          (#\\ minibuffer-convert-slash-to-backslash-region)
          (#\h region-op-command-help)
          (#\1 enclose-region)
          (#\2 enclose-by-\"\"-region)
          (#\4 enclose-by-$$-region)
          (#\8 enclose-by-\(\)-region)
          (#\[ enclose-by-[]-region)
          (#\{ enclose-by-{}-region)
          (#\, enclose-by-<>-region)
          (#\q quote-region)
          (#\e execute-region)
          (#\| filter-region)
          (#\f fill-region-as-paragraph)
          (#\a append-rectangle)
          (#\z clear-rectangle)
          (#\o open-rectangle)
          (#\r copy-rectangle)
          (#\d delete-rectangle)
          (#\k kill-rectangle)
          (#\v overwrite-rectangle)
          (#\y yank-rectangle)
          (#\s string-rectangle)))

(#\C-w global-set-key 'region-op)

今回は、打ちやすいC-wを上書きすることにして、defaultもkill-regionにしてみた。


ところで、こうしたクロージャを用いた関数は、内部で使っている変数がスペシャル変数として定義されてしまうと機能しなくなる。それを防ぎたければ、マクロで変数を (gensym) によるシンボルに置き換えると良いだろう。