あどけない話

インターネットに関する技術的な話など

Electric な Ruby の end (4)

という訳で、最新版です。関数呼び出しの括弧の中に if があっても大丈夫なはずです。

(defvar ruby-elect-keyword
  '("def" "if" "class" "module" "unless" "case"
    "while" "do" "until" "for" "begin" "end"))

(defvar ruby-elect-regex (mapconcat (lambda (x) (format "\\<%s\\>" x)) ruby-elect-keyword "\\|"))

(defun ruby-elect-end ()
  (interactive)
  (insert "d")
  (when (and (char-equal (char-before (1- (point))) ?n)
	     (char-equal (char-before (- (point) 2)) ?e))
    (ruby-indent-command)
    (let ((orig (point)) open)
      (forward-char -3)
      (when (looking-at "\\<end\\>")
	(setq open (ruby-elect-begin))
	(when open
	  (goto-char open)
	  (sit-for 0.3)))
      (goto-char orig))))

(defun ruby-elect-begin ()
  (let ((level 0) pos)
    (catch 'loop
      (while (re-search-backward ruby-elect-regex nil t)
	(setq pos (match-beginning 0))
	(cond
	 ((string= (match-string 0) "end")
	  (setq level (1+ level)))
	 ((member (match-string 0) '("if" "unless" "while" "until"))
	  (when (ruby-elect-if)
	    (if (= level 0) (throw 'loop pos))
	    (setq level (1- level))))
	 (t
	  (if (= level 0) (throw 'loop pos))
	  (setq level (1- level))))
	(if (< level 0) (throw 'loop nil))))))

(defun ruby-elect-if ()
  (save-excursion
    (catch 'loop
      (while (/= (point) (point-min))
	(forward-char -1)
	(let ((c (char-after)))
	  (cond
	   ((or (char-equal c ?\n) (char-equal c ?\()) (throw 'loop t))
	   ((or (char-equal c 32) (char-equal c ?\t)) ())
	   (t (throw 'loop nil))))))))

(add-hook 'ruby-mode-hook
	  (lambda ()
	    (define-key ruby-mode-map "d" 'ruby-elect-end)))