貴重なC-a,C-eといったキーが行頭/行末移動しかしないのはもったいない、ということで、 sequential-command.elなどのように、多少空気を読んで動作を変えるようなコマンドを定義します。
(defmacro as-this-command (cmd &rest args)
`(progn
(setq this-command ',cmd)
(call-interactively ',cmd ,@args)))
(defun buffer-empty? ()
(= (point-min) (point-max)))
(defun initialize-buffer ()
(interactive)
(call-interactively 'auto-insert))
(defun at-line-start? ()
(= (point) (line-beginning-position)))
(defun at-line-end? ()
(= (point) (line-end-position)))
(defun at-word? ()
(case (char-after (point))
((9 10 13 32 59
?( ?) ?[ ?] ?{ ?}) nil)
((nil) nil)
(t t)))
(defun at-paren-start? ()
(find (char-after (point))
"([{"))
(defun at-paren-end? ()
(when (> (point) 1)
(find (char-after (point))
")]}")))
(defun at-end-of-symbol? ()
(when (> (point) 1)
(save-excursion
(unless (at-word?)
(backward-char)
(at-word?)))))
(defun forward-at-paren-end ()
(interactive)
(let ((pos (point)))
(cond
((= pos (point-max)) (call-interactively 'newline-and-indent))
((= pos (line-end-position))
(call-interactively 'forward-char)
(call-interactively 'indent-for-tab-command))
(t (call-interactively 'forward-char)))))
(defun forward-at-line-end ()
(interactive)
(let ((pos (point)))
(cond
((= pos (point-max)) (call-interactively 'newline-and-indent))
(t
(call-interactively 'forward-char)
(call-interactively 'indent-for-tab-command)))))
(defun my-ctrl-o ()
(interactive)
(cond
;; markが有効な場合、インデントする
(mark-active (as-this-command indent-region))
;; バッファが空の場合、初期化する(auto-insert)
((buffer-empty?) (as-this-command initialize-buffer))
;; ポイントが単語上にある場合、次の単語に移動する
((at-word?) (as-this-command forward-word))
;; ポイントが開き括弧上にある場合、対応する括弧の終わりに移動する
((at-paren-start?) (as-this-command forward-sexp))
;; ポイントが単語の終わりにある場合、hippie-expandを呼び出す
((at-end-of-symbol?) (as-this-command hippie-expand))
;; ポイントが閉じ括弧の次にある場合、次の文字に進む
((at-paren-end?) (as-this-command forward-at-paren-end))
;; ポイントが行頭にある場合、インデントする
((at-line-start?) (as-this-command indent-for-tab-command))
;; ポイントが行末にある場合、次の行に移動する。
;; バッファの終端でもある場合、改行する。
((at-line-end?) (as-this-command forward-at-line-end))
(t t)))
(defun my-ctrl-a ()
(interactive)
(when (and (eq last-command 'my-ctrl-a)
(= (point) (line-beginning-position)))
(call-interactively 'scroll-down))
(call-interactively 'move-beginning-of-line))
(defun my-ctrl-e ()
(interactive)
(when (and (eq last-command 'my-ctrl-e)
(= (point) (line-end-position)))
(call-interactively 'scroll-up))
(call-interactively 'move-end-of-line))
(global-set-key (kbd "C-o") 'my-ctrl-o)
(global-set-key (kbd "C-a") 'my-ctrl-a)
(global-set-key (kbd "C-e") 'my-ctrl-e)
my-ctrl-aコマンドはC-aに割り当てるつもりで定義したものです。普段は通常のC-aの動作をしますが、すでにポイントが行頭にあり、 1つ前に実行されたコマンドもmy-ctrl-aの場合には、M-v(scroll-down)の動作を行います。
my-ctrl-eコマンドはmy-ctrl-aのC-eバージョンです。 1つ前のコマンドがmy-ctrl-eの場合にはC-v(scroll-up)の動作を行います。
これでscroll-downが押しやすくなった上、C-vを他のコマンドに割り当てる余裕ができました。
また、普段はC-oをhippie-expandコマンドにしているので、展開が必要なさそうな箇所では別の動作をするmy-ctrl-oも定義しました。こちらはC-a/C-eに比べて残念な感じがします。