ruby-modeが使いにくいので改造(その3)

なんだかRubyの勉強をしていたつもりがすっかりelispにハマってしまっているな。
前回の改造に続き今回は、class, def, do に対して、end を適切にインデントして補完する機能を書いてみた。
class, def, do の直後でスペースバーを叩くと次の行にend が現れるというもの。
do end と書く作法と、{ } を使う作法が有るようだが、まだRubyを始めたばかりなので、どちらがポピュラーなのかまだ良くわかっていないのだが、とりあえず最初に覚えたdo end で補完ができるようにしてみた。
追加した機能をまとめてみると、

  • end の補完機能
  • oneliner の直後か、endブロックの直後でC-x C-eとする事で式をirbプロセスへ送って評価する機能
  • irbが下に伸びて行くのに併せて、スクロールアウトしてしまわないように、最下行へカーソルを移動する機能

こんな所か。
これでRubyの勉強が加速する予定だ。

使い方は、以下を.emacsに追加するだけ。

;; ruby-mode customize
(defun ruby-send-exp ()
  "Send an expression to the inferior Ruby process.
Cursol must be after the 'end' symbol or at the end of a oneliner"
  (interactive)
  (let* ((last-three-chars (buffer-substring (- (point) 3) (point)))
	 (last-char (buffer-substring (- (point) 1) (point)))
	 (beginning
	  (save-excursion
	    (if (or (string= last-three-chars "end")
		    (string= last-char "}"))
		(ruby-beginning-of-block)
	      (beginning-of-line))
	    (point))))
    (ruby-send-region beginning (point))
    (scroll-down-irb)))

(defun scroll-down-irb ()
  (interactive)
  (setq curbuf (current-buffer))
  (if (get-buffer ruby-buffer)
      (pop-to-buffer ruby-buffer)
      (error "No current process buffer. See variable ruby-buffer."))
  (end-of-buffer)
  (pop-to-buffer curbuf))

(defun get-indent-spaces ()
  (interactive)
  (let* ((beginning
	 (save-excursion
	   (beginning-of-line 1)
	   (point)))
	(beginning-of-word-in-the-line
	 (save-excursion
	   (beginning-of-line 1)
	   (re-search-forward "\\w" (point-max) t)
	   (point)))
	(number-of-spaces (- beginning-of-word-in-the-line beginning 1)))
    (if (>= number-of-spaces 0)
	(make-string number-of-spaces ? ))))

(defun ruby-insert-end-ex ()
  (interactive)
  (let* ((last-word-info (search-last-word))
	 (last-word (if last-word-info
			(buffer-substring (car last-word-info) (cdr last-word-info))
		      ""))
	 (spaces (get-indent-spaces)))
    (if (and
	 last-word-info
	 (= (cdr last-word-info) (point))
	 (or (string= last-word "do")
	     (string= last-word "class")
	     (string= last-word "def")))
	(progn (insert " ")
	       (save-excursion
		 (insert "\n")
		 (insert spaces)
		 (insert "end")))
      (insert " "))))

(defun search-last-word ()
  (save-excursion
    (let ((current-point (point))
	  (beginning
	   (save-excursion
	     (beginning-of-line 1)
	     (point)))
	  (beginning-of-last-word
	   (progn (forward-word -1)
		  (point)))
	  (end-of-last-word
	   (progn (forward-word 1)
		  (point))))
      (if (or (< beginning-of-last-word beginning)
	      (> end-of-last-word current-point))
	  nil
	(cons beginning-of-last-word end-of-last-word)))))

(add-hook 'ruby-mode-hook
	  '(lambda ()
	     (define-key ruby-mode-map "\C-x\C-e" 'ruby-send-exp)
	     (define-key ruby-mode-map " " 'ruby-insert-end-ex)))

自動補完て、スペース押したときじゃなくてタブの方が使いやすいかなぁ。
まぁ、多分こういうのは既にどこかにあるんだろうけど、elispの勉強がてらに作ってみたのだ。良かったら使ってやってください。
後、もっと補完しろとか有ったらコメントください。