changeset 100329:267bf24aac5e

New file.
author Chong Yidong <cyd@stupidchicken.com>
date Wed, 10 Dec 2008 18:59:26 +0000
parents 3cfd649195fe
children 3f13e82befbc
files lisp/progmodes/ruby-mode.el
diffstat 1 files changed, 1430 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/progmodes/ruby-mode.el	Wed Dec 10 18:59:26 2008 +0000
@@ -0,0 +1,1430 @@
+;;; ruby-mode.el --- Major mode for editing Ruby files
+
+;; Copyright (C) 1994, 1995, 1996 1997, 1998, 1999, 2000, 2001,
+;;   2002,2003, 2004, 2005, 2006, 2007, 2008
+;;   Free Software Foundation, Inc.
+
+;; Authors: Yukihiro Matsumoto, Nobuyoshi Nakada
+;; URL: http://www.emacswiki.org/cgi-bin/wiki/RubyMode
+;; Created: Fri Feb  4 14:49:13 JST 1994
+;; Keywords: languages ruby
+;; Version: 1.0
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Provides font-locking, indentation support, and navigation for Ruby code.
+;;
+;; If you're installing manually, you should add this to your .emacs
+;; file after putting it on your load path:
+;;
+;;    (autoload 'ruby-mode "ruby-mode" "Major mode for ruby files" t)
+;;    (add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))
+;;    (add-to-list 'interpreter-mode-alist '("ruby" . ruby-mode))
+;;
+;; Still needs more docstrings; search below for TODO.
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+
+(defconst ruby-keyword-end-re
+  (if (string-match "\\_>" "ruby")
+      "\\_>"
+    "\\>"))
+
+(defconst ruby-block-beg-keywords
+  '("class" "module" "def" "if" "unless" "case" "while" "until" "for" "begin" "do")
+  "Keywords at the beginning of blocks.")
+
+(defconst ruby-block-beg-re
+  (regexp-opt ruby-block-beg-keywords)
+  "Regexp to match the beginning of blocks.")
+
+(defconst ruby-non-block-do-re
+  (concat (regexp-opt '("while" "until" "for" "rescue") t) ruby-keyword-end-re)
+  "Regexp to match keywords that nest without blocks.")
+
+(defconst ruby-indent-beg-re
+  (concat "\\(\\s *" (regexp-opt '("class" "module" "def") t) "\\)\\|"
+          (regexp-opt '("if" "unless" "case" "while" "until" "for" "begin")))
+  "Regexp to match where the indentation gets deeper.")
+
+(defconst ruby-modifier-beg-keywords
+  '("if" "unless" "while" "until")
+  "Modifiers that are the same as the beginning of blocks.")
+
+(defconst ruby-modifier-beg-re
+  (regexp-opt ruby-modifier-beg-keywords)
+  "Regexp to match modifiers same as the beginning of blocks.")
+
+(defconst ruby-modifier-re
+  (regexp-opt (cons "rescue" ruby-modifier-beg-keywords))
+  "Regexp to match modifiers.")
+
+(defconst ruby-block-mid-keywords
+  '("then" "else" "elsif" "when" "rescue" "ensure")
+  "Keywords where the indentation gets shallower in middle of block statements.")
+
+(defconst ruby-block-mid-re
+  (regexp-opt ruby-block-mid-keywords)
+  "Regexp to match where the indentation gets shallower in middle of block statements.")
+
+(defconst ruby-block-op-keywords
+  '("and" "or" "not")
+  "Regexp to match boolean keywords.")
+
+(defconst ruby-block-hanging-re
+  (regexp-opt (append ruby-modifier-beg-keywords ruby-block-op-keywords))
+  "Regexp to match hanging block modifiers.")
+
+(defconst ruby-block-end-re "\\<end\\>")
+
+(defconst ruby-here-doc-beg-re
+  "\\(<\\)<\\(-\\)?\\(\\([a-zA-Z0-9_]+\\)\\|[\"]\\([^\"]+\\)[\"]\\|[']\\([^']+\\)[']\\)"
+  "Regexp to match the beginning of a heredoc.")
+
+(defconst ruby-here-doc-end-re
+  "^\\([ \t]+\\)?\\(.*\\)\\(.\\)$"
+  "Regexp to match the end of heredocs.
+
+This will actually match any line with one or more characters.
+It's useful in that it divides up the match string so that
+`ruby-here-doc-beg-match' can search for the beginning of the heredoc.")
+
+(defun ruby-here-doc-end-match ()
+  "Return a regexp to find the end of a heredoc.
+
+This should only be called after matching against `ruby-here-doc-beg-re'."
+  (concat "^"
+          (if (match-string 2) "[ \t]*" nil)
+          (regexp-quote
+           (or (match-string 4)
+               (match-string 5)
+               (match-string 6)))))
+
+(defun ruby-here-doc-beg-match ()
+  "Return a regexp to find the beginning of a heredoc.
+
+This should only be called after matching against `ruby-here-doc-end-re'."
+  (let ((contents (regexp-quote (concat (match-string 2) (match-string 3)))))
+    (concat "<<"
+            (let ((match (match-string 1)))
+              (if (and match (> (length match) 0))
+                  (concat "\\(?:-\\([\"']?\\)\\|\\([\"']\\)" (match-string 1) "\\)"
+                          contents "\\b\\(\\1\\|\\2\\)")
+                (concat "-?\\([\"']\\|\\)" contents "\\b\\1"))))))
+
+(defconst ruby-delimiter
+  (concat "[?$/%(){}#\"'`.:]\\|<<\\|\\[\\|\\]\\|\\<\\("
+          ruby-block-beg-re
+          "\\)\\>\\|" ruby-block-end-re
+          "\\|^=begin\\|" ruby-here-doc-beg-re))
+
+(defconst ruby-negative
+  (concat "^[ \t]*\\(\\(" ruby-block-mid-re "\\)\\>\\|"
+          ruby-block-end-re "\\|}\\|\\]\\)")
+  "Regexp to match where the indentation gets shallower.")
+
+(defconst ruby-operator-re "[-,.+*/%&|^~=<>:]"
+  "Regexp to match operators.")
+
+(defconst ruby-symbol-chars "a-zA-Z0-9_"
+  "List of characters that symbol names may contain.")
+(defconst ruby-symbol-re (concat "[" ruby-symbol-chars "]")
+  "Regexp to match symbols.")
+
+(defvar ruby-mode-abbrev-table nil
+  "Abbrev table in use in ruby-mode buffers.")
+
+(define-abbrev-table 'ruby-mode-abbrev-table ())
+
+(defvar ruby-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "{" 'ruby-electric-brace)
+    (define-key map "}" 'ruby-electric-brace)
+    (define-key map (kbd "M-C-a") 'ruby-beginning-of-defun)
+    (define-key map (kbd "M-C-e") 'ruby-end-of-defun)
+    (define-key map (kbd "M-C-b") 'ruby-backward-sexp)
+    (define-key map (kbd "M-C-f") 'ruby-forward-sexp)
+    (define-key map (kbd "M-C-p") 'ruby-beginning-of-block)
+    (define-key map (kbd "M-C-n") 'ruby-end-of-block)
+    (define-key map (kbd "M-C-h") 'ruby-mark-defun)
+    (define-key map (kbd "M-C-q") 'ruby-indent-exp)
+    (define-key map (kbd "TAB")   'ruby-indent-line)
+    (define-key map (kbd "C-M-h") 'backward-kill-word)
+    (define-key map (kbd "C-j")   'reindent-then-newline-and-indent)
+    (define-key map (kbd "C-m")   'newline)
+    map)
+  "Keymap used in ruby-mode.")
+
+(defvar ruby-mode-syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?\' "\"" table)
+    (modify-syntax-entry ?\" "\"" table)
+    (modify-syntax-entry ?\` "\"" table)
+    (modify-syntax-entry ?# "<" table)
+    (modify-syntax-entry ?\n ">" table)
+    (modify-syntax-entry ?\\ "\\" table)
+    (modify-syntax-entry ?$ "." table)
+    (modify-syntax-entry ?? "_" table)
+    (modify-syntax-entry ?_ "_" table)
+    (modify-syntax-entry ?< "." table)
+    (modify-syntax-entry ?> "." table)
+    (modify-syntax-entry ?& "." table)
+    (modify-syntax-entry ?| "." table)
+    (modify-syntax-entry ?% "." table)
+    (modify-syntax-entry ?= "." table)
+    (modify-syntax-entry ?/ "." table)
+    (modify-syntax-entry ?+ "." table)
+    (modify-syntax-entry ?* "." table)
+    (modify-syntax-entry ?- "." table)
+    (modify-syntax-entry ?\; "." table)
+    (modify-syntax-entry ?\( "()" table)
+    (modify-syntax-entry ?\) ")(" table)
+    (modify-syntax-entry ?\{ "(}" table)
+    (modify-syntax-entry ?\} "){" table)
+    (modify-syntax-entry ?\[ "(]" table)
+    (modify-syntax-entry ?\] ")[" table)
+    table)
+  "Syntax table to use in ruby-mode.")
+
+(defcustom ruby-indent-tabs-mode nil
+  "Indentation can insert tabs in ruby mode if this is non-nil."
+  :type 'boolean :group 'ruby)
+
+(defcustom ruby-indent-level 2
+  "Indentation of ruby statements."
+  :type 'integer :group 'ruby)
+
+(defcustom ruby-comment-column 32
+  "Indentation column of comments."
+  :type 'integer :group 'ruby)
+
+(defcustom ruby-deep-arglist t
+  "Deep indent lists in parenthesis when non-nil.
+Also ignores spaces after parenthesis when 'space."
+  :group 'ruby)
+
+(defcustom ruby-deep-indent-paren '(?\( ?\[ ?\] t)
+  "Deep indent lists in parenthesis when non-nil. t means continuous line.
+Also ignores spaces after parenthesis when 'space."
+  :group 'ruby)
+
+(defcustom ruby-deep-indent-paren-style 'space
+  "Default deep indent style."
+  :options '(t nil space) :group 'ruby)
+
+(defcustom ruby-encoding-map '((shift_jis . cp932) (shift-jis . cp932))
+  "Alist to map encoding name from Emacs to Ruby."
+  :group 'ruby)
+
+(defcustom ruby-insert-encoding-magic-comment t
+  "*Insert a magic emacs 'coding' comment upon save if this is non-nil."
+  :type 'boolean :group 'ruby)
+
+(defcustom ruby-use-encoding-map t
+  "Use `ruby-encoding-map' to set encoding magic comment if this is non-nil."
+  :type 'boolean :group 'ruby)
+
+;; Safe file variables
+(put 'ruby-indent-tabs-mode 'safe-local-variable 'booleanp)
+(put 'ruby-indent-level 'safe-local-variable 'integerp)
+(put 'ruby-comment-column 'safe-local-variable 'integerp)
+(put 'ruby-deep-arglist 'safe-local-variable 'booleanp)
+
+(defun ruby-imenu-create-index-in-block (prefix beg end)
+  "Create an imenu index of methods inside a block."
+  (let ((index-alist '()) (case-fold-search nil)
+        name next pos decl sing)
+    (goto-char beg)
+    (while (re-search-forward "^\\s *\\(\\(class\\s +\\|\\(class\\s *<<\\s *\\)\\|module\\s +\\)\\([^\(<\n ]+\\)\\|\\(def\\|alias\\)\\s +\\([^\(\n ]+\\)\\)" end t)
+      (setq sing (match-beginning 3))
+      (setq decl (match-string 5))
+      (setq next (match-end 0))
+      (setq name (or (match-string 4) (match-string 6)))
+      (setq pos (match-beginning 0))
+      (cond
+       ((string= "alias" decl)
+        (if prefix (setq name (concat prefix name)))
+        (push (cons name pos) index-alist))
+       ((string= "def" decl)
+        (if prefix
+            (setq name
+                  (cond
+                   ((string-match "^self\." name)
+                    (concat (substring prefix 0 -1) (substring name 4)))
+                  (t (concat prefix name)))))
+        (push (cons name pos) index-alist)
+        (ruby-accurate-end-of-block end))
+       (t
+        (if (string= "self" name)
+            (if prefix (setq name (substring prefix 0 -1)))
+          (if prefix (setq name (concat (substring prefix 0 -1) "::" name)))
+          (push (cons name pos) index-alist))
+        (ruby-accurate-end-of-block end)
+        (setq beg (point))
+        (setq index-alist
+              (nconc (ruby-imenu-create-index-in-block
+                      (concat name (if sing "." "#"))
+                      next beg) index-alist))
+        (goto-char beg))))
+    index-alist))
+
+(defun ruby-imenu-create-index ()
+  "Create an imenu index of all methods in the buffer."
+  (nreverse (ruby-imenu-create-index-in-block nil (point-min) nil)))
+
+(defun ruby-accurate-end-of-block (&optional end)
+  "TODO: document."
+  (let (state
+        (end (or end (point-max))))
+    (while (and (setq state (apply 'ruby-parse-partial end state))
+                (>= (nth 2 state) 0) (< (point) end)))))
+
+(defun ruby-mode-variables ()
+  "Set up initial buffer-local variables for ruby-mode."
+  (set-syntax-table ruby-mode-syntax-table)
+  (setq local-abbrev-table ruby-mode-abbrev-table)
+  (setq indent-tabs-mode ruby-indent-tabs-mode)
+  (set (make-local-variable 'indent-line-function) 'ruby-indent-line)
+  (set (make-local-variable 'require-final-newline) t)
+  (set (make-local-variable 'comment-start) "# ")
+  (set (make-local-variable 'comment-end) "")
+  (set (make-local-variable 'comment-column) ruby-comment-column)
+  (set (make-local-variable 'comment-start-skip) "#+ *")
+  (set (make-local-variable 'parse-sexp-ignore-comments) t)
+  (set (make-local-variable 'parse-sexp-lookup-properties) t)
+  (set (make-local-variable 'paragraph-start) (concat "$\\|" page-delimiter))
+  (set (make-local-variable 'paragraph-separate) paragraph-start)
+  (set (make-local-variable 'paragraph-ignore-fill-prefix) t))
+
+(defun ruby-mode-set-encoding ()
+  "Insert a magic comment header with the proper encoding if necessary."
+  (save-excursion
+    (widen)
+    (goto-char (point-min))
+    (when (re-search-forward "[^\0-\177]" nil t)
+      (goto-char (point-min))
+      (let ((coding-system
+             (or coding-system-for-write
+                 buffer-file-coding-system)))
+        (if coding-system
+            (setq coding-system
+                  (or (coding-system-get coding-system 'mime-charset)
+                      (coding-system-change-eol-conversion coding-system nil))))
+        (setq coding-system
+              (if coding-system
+                  (symbol-name
+                   (or (and ruby-use-encoding-map
+                            (cdr (assq coding-system ruby-encoding-map)))
+                       coding-system))
+                "ascii-8bit"))
+        (if (looking-at "^#![^\n]*ruby") (beginning-of-line 2))
+        (cond ((looking-at "\\s *#.*-\*-\\s *\\(en\\)?coding\\s *:\\s *\\([-a-z0-9_]*\\)\\s *\\(;\\|-\*-\\)")
+               (unless (string= (match-string 2) coding-system)
+                 (goto-char (match-beginning 2))
+                 (delete-region (point) (match-end 2))
+                 (and (looking-at "-\*-")
+                      (let ((n (skip-chars-backward " ")))
+                        (cond ((= n 0) (insert "  ") (backward-char))
+                              ((= n -1) (insert " "))
+                              ((forward-char)))))
+                 (insert coding-system)))
+              ((looking-at "\\s *#.*coding\\s *[:=]"))
+              (t (when ruby-insert-encoding-magic-comment
+                   (insert "# -*- coding: " coding-system " -*-\n"))))))))
+
+(defun ruby-current-indentation ()
+  "Return the indentation level of current line."
+  (save-excursion
+    (beginning-of-line)
+    (back-to-indentation)
+    (current-column)))
+
+(defun ruby-indent-line (&optional flag)
+  "Correct the indentation of the current ruby line."
+  (interactive)
+  (ruby-indent-to (ruby-calculate-indent)))
+
+(defun ruby-indent-to (column)
+  "Indent the current line to COLUMN."
+  (when column
+    (let (shift top beg)
+      (and (< column 0) (error "invalid nest"))
+      (setq shift (current-column))
+      (beginning-of-line)
+      (setq beg (point))
+      (back-to-indentation)
+      (setq top (current-column))
+      (skip-chars-backward " \t")
+      (if (>= shift top) (setq shift (- shift top))
+        (setq shift 0))
+      (if (and (bolp)
+               (= column top))
+          (move-to-column (+ column shift))
+        (move-to-column top)
+        (delete-region beg (point))
+        (beginning-of-line)
+        (indent-to column)
+        (move-to-column (+ column shift))))))
+
+(defun ruby-special-char-p (&optional pos)
+  "Return t if the character before POS is a special character.
+If omitted, POS defaults to the current point.
+Special characters are `?', `$', `:' when preceded by whitespace,
+and `\\' when preceded by `?'."
+  (setq pos (or pos (point)))
+  (let ((c (char-before pos)) (b (and (< (point-min) pos)
+				      (char-before (1- pos)))))
+    (cond ((or (eq c ??) (eq c ?$)))
+          ((and (eq c ?:) (or (not b) (eq (char-syntax b) ? ))))
+          ((eq c ?\\) (eq b ??)))))
+
+(defun ruby-expr-beg (&optional option)
+  "TODO: document."
+  (save-excursion
+    (store-match-data nil)
+    (let ((space (skip-chars-backward " \t"))
+          (start (point)))
+      (cond
+       ((bolp) t)
+       ((progn
+          (forward-char -1)
+          (and (looking-at "\\?")
+               (or (eq (char-syntax (char-before (point))) ?w)
+                   (ruby-special-char-p))))
+        nil)
+       ((and (eq option 'heredoc) (< space 0)) t)
+       ((or (looking-at ruby-operator-re)
+            (looking-at "[\\[({,;]")
+            (and (looking-at "[!?]")
+                 (or (not (eq option 'modifier))
+                     (bolp)
+                     (save-excursion (forward-char -1) (looking-at "\\Sw$"))))
+            (and (looking-at ruby-symbol-re)
+                 (skip-chars-backward ruby-symbol-chars)
+                 (cond
+                  ((looking-at (regexp-opt
+                                (append ruby-block-beg-keywords
+                                        ruby-block-op-keywords
+                                        ruby-block-mid-keywords)
+                                'words))
+                   (goto-char (match-end 0))
+                   (not (looking-at "\\s_")))
+                  ((eq option 'expr-qstr)
+                   (looking-at "[a-zA-Z][a-zA-z0-9_]* +%[^ \t]"))
+                  ((eq option 'expr-re)
+                   (looking-at "[a-zA-Z][a-zA-z0-9_]* +/[^ \t]"))
+                  (t nil)))))))))
+
+(defun ruby-forward-string (term &optional end no-error expand)
+  "TODO: document."
+  (let ((n 1) (c (string-to-char term))
+        (re (if expand
+                (concat "[^\\]\\(\\\\\\\\\\)*\\([" term "]\\|\\(#{\\)\\)")
+              (concat "[^\\]\\(\\\\\\\\\\)*[" term "]"))))
+    (while (and (re-search-forward re end no-error)
+                (if (match-beginning 3)
+                    (ruby-forward-string "}{" end no-error nil)
+                  (> (setq n (if (eq (char-before (point)) c)
+                                     (1- n) (1+ n))) 0)))
+      (forward-char -1))
+    (cond ((zerop n))
+          (no-error nil)
+          ((error "unterminated string")))))
+
+(defun ruby-deep-indent-paren-p (c)
+    "TODO: document."
+  (cond ((listp ruby-deep-indent-paren)
+         (let ((deep (assoc c ruby-deep-indent-paren)))
+           (cond (deep
+                  (or (cdr deep) ruby-deep-indent-paren-style))
+                 ((memq c ruby-deep-indent-paren)
+                  ruby-deep-indent-paren-style))))
+        ((eq c ruby-deep-indent-paren) ruby-deep-indent-paren-style)
+        ((eq c ?\( ) ruby-deep-arglist)))
+
+(defun ruby-parse-partial (&optional end in-string nest depth pcol indent)
+    "TODO: document throughout function body."
+  (or depth (setq depth 0))
+  (or indent (setq indent 0))
+  (when (re-search-forward ruby-delimiter end 'move)
+    (let ((pnt (point)) w re expand)
+      (goto-char (match-beginning 0))
+      (cond
+       ((and (memq (char-before) '(?@ ?$)) (looking-at "\\sw"))
+        (goto-char pnt))
+       ((looking-at "[\"`]")            ;skip string
+        (cond
+         ((and (not (eobp))
+               (ruby-forward-string (buffer-substring (point) (1+ (point))) end t t))
+          nil)
+         (t
+          (setq in-string (point))
+          (goto-char end))))
+       ((looking-at "'")
+        (cond
+         ((and (not (eobp))
+               (re-search-forward "[^\\]\\(\\\\\\\\\\)*'" end t))
+          nil)
+         (t
+          (setq in-string (point))
+          (goto-char end))))
+       ((looking-at "/=")
+        (goto-char pnt))
+       ((looking-at "/")
+        (cond
+         ((and (not (eobp)) (ruby-expr-beg 'expr-re))
+          (if (ruby-forward-string "/" end t t)
+              nil
+            (setq in-string (point))
+            (goto-char end)))
+         (t
+          (goto-char pnt))))
+       ((looking-at "%")
+        (cond
+         ((and (not (eobp))
+               (ruby-expr-beg 'expr-qstr)
+               (not (looking-at "%="))
+               (looking-at "%[QqrxWw]?\\([^a-zA-Z0-9 \t\n]\\)"))
+          (goto-char (match-beginning 1))
+          (setq expand (not (memq (char-before) '(?q ?w))))
+          (setq w (match-string 1))
+          (cond
+           ((string= w "[") (setq re "]["))
+           ((string= w "{") (setq re "}{"))
+           ((string= w "(") (setq re ")("))
+           ((string= w "<") (setq re "><"))
+           ((and expand (string= w "\\"))
+            (setq w (concat "\\" w))))
+          (unless (cond (re (ruby-forward-string re end t expand))
+                        (expand (ruby-forward-string w end t t))
+                        (t (re-search-forward
+                            (if (string= w "\\")
+                                "\\\\[^\\]*\\\\"
+                              (concat "[^\\]\\(\\\\\\\\\\)*" w))
+                            end t)))
+            (setq in-string (point))
+            (goto-char end)))
+         (t
+          (goto-char pnt))))
+       ((looking-at "\\?")              ;skip ?char
+        (cond
+         ((and (ruby-expr-beg)
+               (looking-at "?\\(\\\\C-\\|\\\\M-\\)*\\\\?."))
+          (goto-char (match-end 0)))
+         (t
+          (goto-char pnt))))
+       ((looking-at "\\$")              ;skip $char
+        (goto-char pnt)
+        (forward-char 1))
+       ((looking-at "#")                ;skip comment
+        (forward-line 1)
+        (goto-char (point))
+        )
+       ((looking-at "[\\[{(]")
+        (let ((deep (ruby-deep-indent-paren-p (char-after))))
+          (if (and deep (or (not (eq (char-after) ?\{)) (ruby-expr-beg)))
+              (progn
+                (and (eq deep 'space) (looking-at ".\\s +[^# \t\n]")
+                     (setq pnt (1- (match-end 0))))
+                (setq nest (cons (cons (char-after (point)) pnt) nest))
+                (setq pcol (cons (cons pnt depth) pcol))
+                (setq depth 0))
+            (setq nest (cons (cons (char-after (point)) pnt) nest))
+            (setq depth (1+ depth))))
+        (goto-char pnt)
+        )
+       ((looking-at "[])}]")
+        (if (ruby-deep-indent-paren-p (matching-paren (char-after)))
+            (setq depth (cdr (car pcol)) pcol (cdr pcol))
+          (setq depth (1- depth)))
+        (setq nest (cdr nest))
+        (goto-char pnt))
+       ((looking-at ruby-block-end-re)
+        (if (or (and (not (bolp))
+                     (progn
+                       (forward-char -1)
+                       (setq w (char-after (point)))
+                       (or (eq ?_ w)
+                           (eq ?. w))))
+                (progn
+                  (goto-char pnt)
+                  (setq w (char-after (point)))
+                  (or (eq ?_ w)
+                      (eq ?! w)
+                      (eq ?? w))))
+            nil
+          (setq nest (cdr nest))
+          (setq depth (1- depth)))
+        (goto-char pnt))
+       ((looking-at "def\\s +[^(\n;]*")
+        (if (or (bolp)
+                (progn
+                  (forward-char -1)
+                  (not (eq ?_ (char-after (point))))))
+            (progn
+              (setq nest (cons (cons nil pnt) nest))
+              (setq depth (1+ depth))))
+        (goto-char (match-end 0)))
+       ((looking-at (concat "\\<\\(" ruby-block-beg-re "\\)\\>"))
+        (and
+         (save-match-data
+           (or (not (looking-at (concat "do" ruby-keyword-end-re)))
+               (save-excursion
+                 (back-to-indentation)
+                 (not (looking-at ruby-non-block-do-re)))))
+         (or (bolp)
+             (progn
+               (forward-char -1)
+               (setq w (char-after (point)))
+               (not (or (eq ?_ w)
+                        (eq ?. w)))))
+         (goto-char pnt)
+         (setq w (char-after (point)))
+         (not (eq ?_ w))
+         (not (eq ?! w))
+         (not (eq ?? w))
+         (skip-chars-forward " \t")
+         (goto-char (match-beginning 0))
+         (or (not (looking-at ruby-modifier-re))
+             (ruby-expr-beg 'modifier))
+         (goto-char pnt)
+         (setq nest (cons (cons nil pnt) nest))
+         (setq depth (1+ depth)))
+        (goto-char pnt))
+       ((looking-at ":\\(['\"]\\)")
+        (goto-char (match-beginning 1))
+        (ruby-forward-string (buffer-substring (match-beginning 1) (match-end 1)) end))
+       ((looking-at ":\\([-,.+*/%&|^~<>]=?\\|===?\\|<=>\\)")
+        (goto-char (match-end 0)))
+       ((looking-at ":\\([a-zA-Z_][a-zA-Z_0-9]*[!?=]?\\)?")
+        (goto-char (match-end 0)))
+       ((or (looking-at "\\.\\.\\.?")
+            (looking-at "\\.[0-9]+")
+            (looking-at "\\.[a-zA-Z_0-9]+")
+            (looking-at "\\."))
+        (goto-char (match-end 0)))
+       ((looking-at "^=begin")
+        (if (re-search-forward "^=end" end t)
+            (forward-line 1)
+          (setq in-string (match-end 0))
+          (goto-char end)))
+       ((looking-at "<<")
+        (cond
+         ((and (ruby-expr-beg 'heredoc)
+               (looking-at "<<\\(-\\)?\\(\\([\"'`]\\)\\([^\n]+?\\)\\3\\|\\(?:\\sw\\|\\s_\\)+\\)"))
+          (setq re (regexp-quote (or (match-string 4) (match-string 2))))
+          (if (match-beginning 1) (setq re (concat "\\s *" re)))
+          (let* ((id-end (goto-char (match-end 0)))
+                 (line-end-position (save-excursion (end-of-line) (point)))
+                 (state (list in-string nest depth pcol indent)))
+            ;; parse the rest of the line
+            (while (and (> line-end-position (point))
+                        (setq state (apply 'ruby-parse-partial
+                                           line-end-position state))))
+            (setq in-string (car state)
+                  nest (nth 1 state)
+                  depth (nth 2 state)
+                  pcol (nth 3 state)
+                  indent (nth 4 state))
+            ;; skip heredoc section
+            (if (re-search-forward (concat "^" re "$") end 'move)
+                (forward-line 1)
+              (setq in-string id-end)
+              (goto-char end))))
+         (t
+          (goto-char pnt))))
+       ((looking-at "^__END__$")
+        (goto-char pnt))
+       ((and (looking-at ruby-here-doc-beg-re)
+	     (boundp 'ruby-indent-point))
+        (if (re-search-forward (ruby-here-doc-end-match)
+                               ruby-indent-point t)
+            (forward-line 1)
+          (setq in-string (match-end 0))
+          (goto-char ruby-indent-point)))
+       (t
+        (error (format "bad string %s"
+                       (buffer-substring (point) pnt)
+                       ))))))
+  (list in-string nest depth pcol))
+
+(defun ruby-parse-region (start end)
+  "TODO: document."
+  (let (state)
+    (save-excursion
+      (if start
+          (goto-char start)
+        (ruby-beginning-of-indent))
+      (save-restriction
+        (narrow-to-region (point) end)
+        (while (and (> end (point))
+                    (setq state (apply 'ruby-parse-partial end state))))))
+    (list (nth 0 state)                 ; in-string
+          (car (nth 1 state))           ; nest
+          (nth 2 state)                 ; depth
+          (car (car (nth 3 state)))     ; pcol
+          ;(car (nth 5 state))          ; indent
+          )))
+
+(defun ruby-indent-size (pos nest)
+  "Returns the indentation level in spaces NEST levels deeper than POS."
+  (+ pos (* (or nest 1) ruby-indent-level)))
+
+(defun ruby-calculate-indent (&optional parse-start)
+  "Returns the proper indentation level of the current line."
+  ;; TODO: Document body
+  (save-excursion
+    (beginning-of-line)
+    (let ((ruby-indent-point (point))
+          (case-fold-search nil)
+          state bol eol begin op-end
+          (paren (progn (skip-syntax-forward " ")
+                        (and (char-after) (matching-paren (char-after)))))
+          (indent 0))
+      (if parse-start
+          (goto-char parse-start)
+        (ruby-beginning-of-indent)
+        (setq parse-start (point)))
+      (back-to-indentation)
+      (setq indent (current-column))
+      (setq state (ruby-parse-region parse-start ruby-indent-point))
+      (cond
+       ((nth 0 state)                   ; within string
+        (setq indent nil))              ;  do nothing
+       ((car (nth 1 state))             ; in paren
+        (goto-char (setq begin (cdr (nth 1 state))))
+        (let ((deep (ruby-deep-indent-paren-p (car (nth 1 state)))))
+          (if deep
+              (cond ((and (eq deep t) (eq (car (nth 1 state)) paren))
+                     (skip-syntax-backward " ")
+                     (setq indent (1- (current-column))))
+                    ((let ((s (ruby-parse-region (point) ruby-indent-point)))
+                       (and (nth 2 s) (> (nth 2 s) 0)
+                            (or (goto-char (cdr (nth 1 s))) t)))
+                     (forward-word -1)
+                     (setq indent (ruby-indent-size (current-column)
+						    (nth 2 state))))
+                    (t
+                     (setq indent (current-column))
+                     (cond ((eq deep 'space))
+                           (paren (setq indent (1- indent)))
+                           (t (setq indent (ruby-indent-size (1- indent) 1))))))
+            (if (nth 3 state) (goto-char (nth 3 state))
+              (goto-char parse-start) (back-to-indentation))
+            (setq indent (ruby-indent-size (current-column) (nth 2 state))))
+          (and (eq (car (nth 1 state)) paren)
+               (ruby-deep-indent-paren-p (matching-paren paren))
+               (search-backward (char-to-string paren))
+               (setq indent (current-column)))))
+       ((and (nth 2 state) (> (nth 2 state) 0)) ; in nest
+        (if (null (cdr (nth 1 state)))
+            (error "invalid nest"))
+        (goto-char (cdr (nth 1 state)))
+        (forward-word -1)               ; skip back a keyword
+        (setq begin (point))
+        (cond
+         ((looking-at "do\\>[^_]")      ; iter block is a special case
+          (if (nth 3 state) (goto-char (nth 3 state))
+            (goto-char parse-start) (back-to-indentation))
+          (setq indent (ruby-indent-size (current-column) (nth 2 state))))
+         (t
+          (setq indent (+ (current-column) ruby-indent-level)))))
+       
+       ((and (nth 2 state) (< (nth 2 state) 0)) ; in negative nest
+        (setq indent (ruby-indent-size (current-column) (nth 2 state)))))
+      (when indent
+        (goto-char ruby-indent-point)
+        (end-of-line)
+        (setq eol (point))
+        (beginning-of-line)
+        (cond
+         ((and (not (ruby-deep-indent-paren-p paren))
+               (re-search-forward ruby-negative eol t))
+          (and (not (eq ?_ (char-after (match-end 0))))
+               (setq indent (- indent ruby-indent-level))))
+         ((and
+           (save-excursion
+             (beginning-of-line)
+             (not (bobp)))
+           (or (ruby-deep-indent-paren-p t)
+               (null (car (nth 1 state)))))
+          ;; goto beginning of non-empty no-comment line
+          (let (end done)
+            (while (not done)
+              (skip-chars-backward " \t\n")
+              (setq end (point))
+              (beginning-of-line)
+              (if (re-search-forward "^\\s *#" end t)
+                  (beginning-of-line)
+                (setq done t))))
+          (setq bol (point))
+          (end-of-line)
+          ;; skip the comment at the end
+          (skip-chars-backward " \t")
+          (let (end (pos (point)))
+            (beginning-of-line)
+            (while (and (re-search-forward "#" pos t)
+                        (setq end (1- (point)))
+                        (or (ruby-special-char-p end)
+                            (and (setq state (ruby-parse-region parse-start end))
+                                 (nth 0 state))))
+              (setq end nil))
+            (goto-char (or end pos))
+            (skip-chars-backward " \t")
+            (setq begin (if (and end (nth 0 state)) pos (cdr (nth 1 state))))
+            (setq state (ruby-parse-region parse-start (point))))
+          (or (bobp) (forward-char -1))
+          (and
+           (or (and (looking-at ruby-symbol-re)
+                    (skip-chars-backward ruby-symbol-chars)
+                    (looking-at (concat "\\<\\(" ruby-block-hanging-re "\\)\\>"))
+                    (not (eq (point) (nth 3 state)))
+                    (save-excursion
+                      (goto-char (match-end 0))
+                      (not (looking-at "[a-z_]"))))
+               (and (looking-at ruby-operator-re)
+                    (not (ruby-special-char-p))
+                    ;; operator at the end of line
+                    (let ((c (char-after (point))))
+                      (and
+;;                     (or (null begin)
+;;                         (save-excursion
+;;                           (goto-char begin)
+;;                           (skip-chars-forward " \t")
+;;                           (not (or (eolp) (looking-at "#")
+;;                                    (and (eq (car (nth 1 state)) ?{)
+;;                                         (looking-at "|"))))))
+                       (or (not (eq ?/ c))
+                           (null (nth 0 (ruby-parse-region (or begin parse-start) (point)))))
+                       (or (not (eq ?| (char-after (point))))
+                           (save-excursion
+                             (or (eolp) (forward-char -1))
+                             (cond
+                              ((search-backward "|" nil t)
+                               (skip-chars-backward " \t\n")
+                               (and (not (eolp))
+                                    (progn
+                                      (forward-char -1)
+                                      (not (looking-at "{")))
+                                    (progn
+                                      (forward-word -1)
+                                      (not (looking-at "do\\>[^_]")))))
+                              (t t))))
+                       (not (eq ?, c))
+                       (setq op-end t)))))
+           (setq indent
+                 (cond
+                  ((and
+                    (null op-end)
+                    (not (looking-at (concat "\\<\\(" ruby-block-hanging-re "\\)\\>")))
+                    (eq (ruby-deep-indent-paren-p t) 'space)
+                    (not (bobp)))
+                   (widen)
+                   (goto-char (or begin parse-start))
+                   (skip-syntax-forward " ")
+                   (current-column))
+                  ((car (nth 1 state)) indent)
+                  (t
+                   (+ indent ruby-indent-level))))))))
+      (goto-char ruby-indent-point)
+      (beginning-of-line)
+      (skip-syntax-forward " ")
+      (if (looking-at "\\.[^.]")
+          (+ indent ruby-indent-level)
+        indent))))
+
+(defun ruby-electric-brace (arg)
+  "Inserts a brace and re-indents the current line."
+  (interactive "P")
+  (self-insert-command (prefix-numeric-value arg))
+  (ruby-indent-line t))
+
+;; TODO: Why isn't one ruby-*-of-defun written in terms of the other?
+(defun ruby-beginning-of-defun (&optional arg)
+  "Move backward to the beginning of the current top-level defun.
+With ARG, move backward multiple defuns. Negative ARG means
+move forward."
+  (interactive "p")
+  (and (re-search-backward (concat "^\\(" ruby-block-beg-re "\\)\\b")
+                           nil 'move (or arg 1))
+       (beginning-of-line)))
+
+(defun ruby-end-of-defun (&optional arg)
+  "Move forward to the end of the current top-level defun.
+With ARG, move forward multiple defuns. Negative ARG means
+move backward."
+  (interactive "p")
+  (and (re-search-forward (concat "^\\(" ruby-block-end-re "\\)\\($\\|\\b[^_]\\)")
+                          nil 'move (or arg 1))
+       (beginning-of-line))
+  (forward-line 1))
+
+(defun ruby-beginning-of-indent ()
+  "TODO: document"
+  ;; I don't understand this function.
+  ;; It seems like it should move to the line where indentation should deepen,
+  ;; but ruby-indent-beg-re only accounts for whitespace before class, module and def,
+  ;; so this will only match other block beginners at the beginning of the line.
+  (and (re-search-backward (concat "^\\(" ruby-indent-beg-re "\\)\\b") nil 'move)
+       (beginning-of-line)))
+
+(defun ruby-move-to-block (n)
+  "Moves to the beginning (N < 0) or the end (N > 0) of the current block
+or blocks containing the current block."
+  ;; TODO: Make this work for n > 1,
+  ;; make it not loop for n = 0,
+  ;; document body
+  (let (start pos done down)
+    (setq start (ruby-calculate-indent))
+    (setq down (looking-at (if (< n 0) ruby-block-end-re
+                             (concat "\\<\\(" ruby-block-beg-re "\\)\\>"))))
+    (while (and (not done) (not (if (< n 0) (bobp) (eobp))))
+      (forward-line n)
+      (cond
+       ((looking-at "^\\s *$"))
+       ((looking-at "^\\s *#"))
+       ((and (> n 0) (looking-at "^=begin\\>"))
+        (re-search-forward "^=end\\>"))
+       ((and (< n 0) (looking-at "^=end\\>"))
+        (re-search-backward "^=begin\\>"))
+       (t
+        (setq pos (current-indentation))
+        (cond
+         ((< start pos)
+          (setq down t))
+         ((and down (= pos start))
+          (setq done t))
+         ((> start pos)
+          (setq done t)))))
+      (if done
+          (save-excursion
+            (back-to-indentation)
+            (if (looking-at (concat "\\<\\(" ruby-block-mid-re "\\)\\>"))
+                (setq done nil))))))
+  (back-to-indentation))
+
+(defun ruby-beginning-of-block (&optional arg)
+  "Move backward to the beginning of the current block.
+With ARG, move up multiple blocks."
+  (interactive "p")
+  (ruby-move-to-block (- (or arg 1))))
+
+(defun ruby-end-of-block (&optional arg)
+  "Move forward to the end of the current block.
+With ARG, move out of multiple blocks."
+  ;; Passing a value > 1 to ruby-move-to-block currently doesn't work.
+  (interactive)
+  (ruby-move-to-block (or arg 1)))
+
+(defun ruby-forward-sexp (&optional arg)
+  "Move forward across one balanced expression (sexp).
+With ARG, do it many times. Negative ARG means move backward."
+  ;; TODO: Document body
+  (interactive "p")
+  (if (and (numberp arg) (< arg 0))
+      (ruby-backward-sexp (- arg))
+    (let ((i (or arg 1)))
+      (condition-case nil
+          (while (> i 0)
+            (skip-syntax-forward " ")
+            (cond ((looking-at "\\?\\(\\\\[CM]-\\)*\\\\?\\S ")
+                   (goto-char (match-end 0)))
+                  ((progn
+                     (skip-chars-forward ",.:;|&^~=!?\\+\\-\\*")
+                     (looking-at "\\s("))
+                   (goto-char (scan-sexps (point) 1)))
+                  ((and (looking-at (concat "\\<\\(" ruby-block-beg-re "\\)\\>"))
+                        (not (eq (char-before (point)) ?.))
+                        (not (eq (char-before (point)) ?:)))
+                   (ruby-end-of-block)
+                   (forward-word 1))
+                  ((looking-at "\\(\\$\\|@@?\\)?\\sw")
+                   (while (progn
+                            (while (progn (forward-word 1) (looking-at "_")))
+                            (cond ((looking-at "::") (forward-char 2) t)
+                                  ((> (skip-chars-forward ".") 0))
+                                  ((looking-at "\\?\\|!\\(=[~=>]\\|[^~=]\\)")
+                                   (forward-char 1) nil)))))
+                  ((let (state expr)
+                     (while
+                         (progn
+                           (setq expr (or expr (ruby-expr-beg)
+                                          (looking-at "%\\sw?\\Sw\\|[\"'`/]")))
+                           (nth 1 (setq state (apply 'ruby-parse-partial nil state))))
+                       (setq expr t)
+                       (skip-chars-forward "<"))
+                     (not expr))))
+            (setq i (1- i)))
+        ((error) (forward-word 1)))
+      i)))
+
+(defun ruby-backward-sexp (&optional arg)
+  "Move backward across one balanced expression (sexp).
+With ARG, do it many times. Negative ARG means move forward."
+  ;; TODO: Document body
+  (interactive "p")
+  (if (and (numberp arg) (< arg 0))
+      (ruby-forward-sexp (- arg))
+    (let ((i (or arg 1)))
+      (condition-case nil
+          (while (> i 0)
+            (skip-chars-backward " \t\n,.:;|&^~=!?\\+\\-\\*")
+            (forward-char -1)
+            (cond ((looking-at "\\s)")
+                   (goto-char (scan-sexps (1+ (point)) -1))
+                   (case (char-before)
+                     (?% (forward-char -1))
+                     ('(?q ?Q ?w ?W ?r ?x)
+                      (if (eq (char-before (1- (point))) ?%) (forward-char -2))))
+                   nil)
+                  ((looking-at "\\s\"\\|\\\\\\S_")
+                   (let ((c (char-to-string (char-before (match-end 0)))))
+                     (while (and (search-backward c)
+				 (eq (logand (skip-chars-backward "\\") 1)
+				     1))))
+                   nil)
+                  ((looking-at "\\s.\\|\\s\\")
+                   (if (ruby-special-char-p) (forward-char -1)))
+                  ((looking-at "\\s(") nil)
+                  (t
+                   (forward-char 1)
+                   (while (progn (forward-word -1)
+                                 (case (char-before)
+                                   (?_ t)
+                                   (?. (forward-char -1) t)
+                                   ((?$ ?@)
+                                    (forward-char -1)
+                                    (and (eq (char-before) (char-after)) (forward-char -1)))
+                                   (?:
+                                    (forward-char -1)
+                                    (eq (char-before) :)))))
+                   (if (looking-at ruby-block-end-re)
+                       (ruby-beginning-of-block))
+                   nil))
+            (setq i (1- i)))
+        ((error)))
+      i)))
+
+(defun ruby-mark-defun ()
+  "Put mark at end of this Ruby function, point at beginning."
+  (interactive)
+  (push-mark (point))
+  (ruby-end-of-defun)
+  (push-mark (point) nil t)
+  (ruby-beginning-of-defun)
+  (re-search-backward "^\n" (- (point) 1) t))
+
+(defun ruby-indent-exp (&optional shutup-p)
+  "Indent each line in the balanced expression following the point.
+If a prefix arg is given or SHUTUP-P is non-nil, no errors
+are signalled if a balanced expression isn't found."
+  (interactive "*P")
+  (let ((here (point-marker)) start top column (nest t))
+    (set-marker-insertion-type here t)
+    (unwind-protect
+        (progn
+          (beginning-of-line)
+          (setq start (point) top (current-indentation))
+          (while (and (not (eobp))
+                      (progn
+                        (setq column (ruby-calculate-indent start))
+                        (cond ((> column top)
+                               (setq nest t))
+                              ((and (= column top) nest)
+                               (setq nest nil) t))))
+            (ruby-indent-to column)
+            (beginning-of-line 2)))
+      (goto-char here)
+      (set-marker here nil))))
+
+(defun ruby-add-log-current-method ()
+  "Returns the current method name as a string.
+This string includes all namespaces.
+
+For example:
+
+  #exit
+  String#gsub
+  Net::HTTP#active?
+  File::open.
+
+See `add-log-current-defun-function'."
+  ;; TODO: Document body
+  ;; Why does this append a period to class methods?
+  (condition-case nil
+      (save-excursion
+        (let (mname mlist (indent 0))
+          ;; get current method (or class/module)
+          (if (re-search-backward
+               (concat "^[ \t]*\\(def\\|class\\|module\\)[ \t]+"
+                       "\\("
+                       ;; \\. and :: for class method
+                        "\\([A-Za-z_]" ruby-symbol-re "*\\|\\.\\|::" "\\)"
+                        "+\\)")
+               nil t)
+              (progn
+                (setq mname (match-string 2))
+                (unless (string-equal "def" (match-string 1))
+                  (setq mlist (list mname) mname nil))
+                (goto-char (match-beginning 1))
+                (setq indent (current-column))
+                (beginning-of-line)))
+          ;; nest class/module
+          (while (and (> indent 0)
+                      (re-search-backward
+                       (concat
+                        "^[ \t]*\\(class\\|module\\)[ \t]+"
+                        "\\([A-Z]" ruby-symbol-re "*\\)")
+                       nil t))
+            (goto-char (match-beginning 1))
+            (if (< (current-column) indent)
+                (progn
+                  (setq mlist (cons (match-string 2) mlist))
+                  (setq indent (current-column))
+                  (beginning-of-line))))
+          (when mname
+            (let ((mn (split-string mname "\\.\\|::")))
+              (if (cdr mn)
+                  (progn
+                    (cond
+                     ((string-equal "" (car mn))
+                      (setq mn (cdr mn) mlist nil))
+                     ((string-equal "self" (car mn))
+                      (setq mn (cdr mn)))
+                     ((let ((ml (nreverse mlist)))
+                        (while ml
+                          (if (string-equal (car ml) (car mn))
+                              (setq mlist (nreverse (cdr ml)) ml nil))
+                          (or (setq ml (cdr ml)) (nreverse mlist))))))
+                    (if mlist
+                        (setcdr (last mlist) mn)
+                      (setq mlist mn))
+                    (setq mn (last mn 2))
+                    (setq mname (concat "." (cadr mn)))
+                    (setcdr mn nil))
+                (setq mname (concat "#" mname)))))
+          ;; generate string
+          (if (consp mlist)
+              (setq mlist (mapconcat (function identity) mlist "::")))
+          (if mname
+              (if mlist (concat mlist mname) mname)
+            mlist)))))
+
+(defconst ruby-font-lock-syntactic-keywords
+  `(;; #{ }, #$hoge, #@foo are not comments
+    ("\\(#\\)[{$@]" 1 (1 . nil))
+    ;; the last $', $", $` in the respective string is not variable
+    ;; the last ?', ?", ?` in the respective string is not ascii code
+    ("\\(^\\|[\[ \t\n<+\(,=]\\)\\(['\"`]\\)\\(\\\\.\\|\\2\\|[^'\"`\n\\\\]\\)*?\\\\?[?$]\\(\\2\\)"
+     (2 (7 . nil))
+     (4 (7 . nil)))
+    ;; $' $" $` .... are variables
+    ;; ?' ?" ?` are ascii codes
+    ("\\(^\\|[^\\\\]\\)\\(\\\\\\\\\\)*[?$]\\([#\"'`]\\)" 3 (1 . nil))
+    ;; regexps
+    ("\\(^\\|[=(,~?:;<>]\\|\\(^\\|\\s \\)\\(if\\|elsif\\|unless\\|while\\|until\\|when\\|and\\|or\\|&&\\|||\\)\\|g?sub!?\\|scan\\|split!?\\)\\s *\\(/\\)[^/\n\\\\]*\\(\\\\.[^/\n\\\\]*\\)*\\(/\\)"
+     (4 (7 . ?/))
+     (6 (7 . ?/)))
+    ("^=en\\(d\\)\\_>" 1 "!")
+    ("^\\(=\\)begin\\_>" 1 (ruby-comment-beg-syntax))
+    ;; Currently, the following case is highlighted incorrectly:
+    ;;
+    ;;   <<FOO
+    ;;   FOO
+    ;;   <<BAR
+    ;;   <<BAZ
+    ;;   BAZ
+    ;;   BAR
+    ;;
+    ;; This is because all here-doc beginnings are highlighted before any endings,
+    ;; so although <<BAR is properly marked as a beginning, when we get to <<BAZ
+    ;; it thinks <<BAR is part of a string so it's marked as well.
+    ;;
+    ;; This may be fixable by modifying ruby-in-here-doc-p to use
+    ;; ruby-in-non-here-doc-string-p rather than syntax-ppss-context,
+    ;; but I don't want to try that until we've got unit tests set up
+    ;; to make sure I don't break anything else.
+    (,(concat ruby-here-doc-beg-re ".*\\(\n\\)")
+     ,(+ 1 (regexp-opt-depth ruby-here-doc-beg-re))
+     (ruby-here-doc-beg-syntax))
+    (,ruby-here-doc-end-re 3 (ruby-here-doc-end-syntax)))
+  "Syntactic keywords for ruby-mode. See `font-lock-syntactic-keywords'.")
+
+(defun ruby-comment-beg-syntax ()
+  "Returns the syntax cell for a the first character of a =begin.
+See the definition of `ruby-font-lock-syntactic-keywords'.
+
+This returns a comment-delimiter cell as long as the =begin
+isn't in a string or another comment."
+  (when (not (nth 3 (syntax-ppss)))
+    (string-to-syntax "!")))
+
+(unless (functionp 'syntax-ppss)
+  (defun syntax-ppss (&optional pos)
+    (parse-partial-sexp (point-min) (or pos (point)))))
+
+(defun ruby-in-ppss-context-p (context &optional ppss)
+  (let ((ppss (or ppss (syntax-ppss (point)))))
+    (if (cond
+         ((eq context 'anything)
+          (or (nth 3 ppss)
+              (nth 4 ppss)))
+         ((eq context 'string)
+          (nth 3 ppss))
+         ((eq context 'heredoc)
+          (and (nth 3 ppss)
+               ;; If it's generic string, it's a heredoc and we don't care
+               ;; See `parse-partial-sexp'
+               (not (numberp (nth 3 ppss)))))
+         ((eq context 'non-heredoc)
+          (and (ruby-in-ppss-context-p 'anything)
+               (not (ruby-in-ppss-context-p 'heredoc))))
+         ((eq context 'comment)
+          (nth 4 ppss))
+         (t
+          (error (concat
+                  "Internal error on `ruby-in-ppss-context-p': "
+                  "context name `" (symbol-name context) "' is unknown"))))
+        t)))
+
+(defun ruby-in-here-doc-p ()
+  "Returns whether or not the point is in a heredoc."
+  (save-excursion
+    (let ((old-point (point)) (case-fold-search nil))
+      (beginning-of-line)
+      (catch 'found-beg
+        (while (re-search-backward ruby-here-doc-beg-re nil t)
+          (if (not (or (ruby-in-ppss-context-p 'anything)
+                       (ruby-here-doc-find-end old-point)))
+              (throw 'found-beg t)))))))
+
+(defun ruby-here-doc-find-end (&optional limit)
+  "Expects the point to be on a line with one or more heredoc
+openers. Returns the buffer position at which all heredocs on the
+line are terminated, or nil if they aren't terminated before the
+buffer position `limit' or the end of the buffer."
+  (save-excursion
+    (beginning-of-line)
+    (catch 'done
+      (let ((eol (save-excursion (end-of-line) (point)))
+            (case-fold-search nil)
+            ;; Fake match data such that (match-end 0) is at eol
+            (end-match-data (progn (looking-at ".*$") (match-data)))
+            beg-match-data end-re)
+        (while (re-search-forward ruby-here-doc-beg-re eol t)
+          (setq beg-match-data (match-data))
+          (setq end-re (ruby-here-doc-end-match))
+
+          (set-match-data end-match-data)
+          (goto-char (match-end 0))
+          (unless (re-search-forward end-re limit t) (throw 'done nil))
+          (setq end-match-data (match-data))
+
+          (set-match-data beg-match-data)
+          (goto-char (match-end 0)))
+        (set-match-data end-match-data)
+        (goto-char (match-end 0))
+        (point)))))
+
+(defun ruby-here-doc-beg-syntax ()
+  "Returns the syntax cell for a line that may begin a heredoc.
+See the definition of `ruby-font-lock-syntactic-keywords'.
+
+This sets the syntax cell for the newline ending the line
+containing the heredoc beginning so that cases where multiple
+heredocs are started on one line are handled correctly."
+  (save-excursion
+    (goto-char (match-beginning 0))
+    (unless (or (ruby-in-ppss-context-p 'non-heredoc)
+                (ruby-in-here-doc-p))
+      (string-to-syntax "|"))))
+
+(defun ruby-here-doc-end-syntax ()
+  "Returns the syntax cell for a line that may end a heredoc.
+See the definition of `ruby-font-lock-syntactic-keywords'."
+  (let ((pss (syntax-ppss)) (case-fold-search nil))
+    ;; If we aren't in a string, we definitely aren't ending a heredoc,
+    ;; so we can just give up.
+    ;; This means we aren't doing a full-document search
+    ;; every time we enter a character.
+    (when (ruby-in-ppss-context-p 'heredoc pss)
+      (save-excursion
+        (goto-char (nth 8 pss))  ; Go to the beginning of heredoc.
+        (let ((eol (point)))
+          (beginning-of-line)
+          (if (and (re-search-forward (ruby-here-doc-beg-match) eol t) ; If there is a heredoc that matches this line...
+                   (not (ruby-in-ppss-context-p 'anything)) ; And that's not inside a heredoc/string/comment...
+                   (progn (goto-char (match-end 0)) ; And it's the last heredoc on its line...
+                          (not (re-search-forward ruby-here-doc-beg-re eol t))))
+              (string-to-syntax "|")))))))
+
+(if (featurep 'xemacs)
+    (put 'ruby-mode 'font-lock-defaults
+         '((ruby-font-lock-keywords)
+           nil nil nil
+           beginning-of-line
+           (font-lock-syntactic-keywords
+            . ruby-font-lock-syntactic-keywords))))
+
+(defvar ruby-font-lock-syntax-table
+  (let ((tbl (copy-syntax-table ruby-mode-syntax-table)))
+    (modify-syntax-entry ?_ "w" tbl)
+    tbl)
+  "The syntax table to use for fontifying ruby-mode buffers.
+See `font-lock-syntax-table'.")
+
+(defconst ruby-font-lock-keywords
+  (list
+   ;; functions
+   '("^\\s *def\\s +\\([^( \t\n]+\\)"
+     1 font-lock-function-name-face)
+   ;; keywords
+   (cons (concat
+          "\\(^\\|[^_:.@$]\\|\\.\\.\\)\\b\\(defined\\?\\|"
+          (regexp-opt
+           '("alias_method"
+             "alias"
+             "and"
+             "begin"
+             "break"
+             "case"
+             "catch"
+             "class"
+             "def"
+             "do"
+             "elsif"
+             "else"
+             "fail"
+             "ensure"
+             "for"
+             "end"
+             "if"
+             "in"
+             "module_function"
+             "module"
+             "next"
+             "not"
+             "or"
+             "public"
+             "private"
+             "protected"
+             "raise"
+             "redo"
+             "rescue"
+             "retry"
+             "return"
+             "then"
+             "throw"
+             "super"
+             "unless"
+             "undef"
+             "until"
+             "when"
+             "while"
+             "yield")
+           t)
+          "\\)"
+          ruby-keyword-end-re)
+         2)
+   ;; here-doc beginnings
+   (list ruby-here-doc-beg-re 0 'font-lock-string-face)
+   ;; variables
+   '("\\(^\\|[^_:.@$]\\|\\.\\.\\)\\b\\(nil\\|self\\|true\\|false\\)\\>"
+     2 font-lock-variable-name-face)
+   ;; variables
+   '("\\(\\$\\([^a-zA-Z0-9 \n]\\|[0-9]\\)\\)\\W"
+     1 font-lock-variable-name-face)
+   '("\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+"
+     0 font-lock-variable-name-face)
+   ;; general delimited string
+   '("\\(^\\|[[ \t\n<+(,=]\\)\\(%[xrqQwW]?\\([^<[{(a-zA-Z0-9 \n]\\)[^\n\\\\]*\\(\\\\.[^\n\\\\]*\\)*\\(\\3\\)\\)"
+     (2 font-lock-string-face))
+   ;; constants
+   '("\\(^\\|[^_]\\)\\b\\([A-Z]+\\(\\w\\|_\\)*\\)"
+     2 font-lock-type-face)
+   ;; symbols
+   '("\\(^\\|[^:]\\)\\(:\\([-+~]@?\\|[/%&|^`]\\|\\*\\*?\\|<\\(<\\|=>?\\)?\\|>[>=]?\\|===?\\|=~\\|\\[\\]=?\\|\\(\\w\\|_\\)+\\([!?=]\\|\\b_*\\)\\|#{[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\)\\)"
+     2 font-lock-reference-face)
+   ;; expression expansion
+   '("#\\({[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\|\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+\\)"
+     0 font-lock-variable-name-face t)
+   ;; warn lower camel case
+                                        ;'("\\<[a-z]+[a-z0-9]*[A-Z][A-Za-z0-9]*\\([!?]?\\|\\>\\)"
+                                        ;  0 font-lock-warning-face)
+   )
+  "Additional expressions to highlight in ruby mode.")
+
+;;;###autoload
+(defun ruby-mode ()
+  "Major mode for editing Ruby scripts.
+\\[ruby-indent-line] properly indents subexpressions of multi-line
+class, module, def, if, while, for, do, and case statements, taking
+nesting into account.
+
+The variable ruby-indent-level controls the amount of indentation.
+\\{ruby-mode-map}"
+  (interactive)
+  (kill-all-local-variables)
+  (use-local-map ruby-mode-map)
+  (setq mode-name "Ruby")
+  (setq major-mode 'ruby-mode)
+  (ruby-mode-variables)
+
+  (set (make-local-variable 'imenu-create-index-function)
+       'ruby-imenu-create-index)
+  (set (make-local-variable 'add-log-current-defun-function)
+       'ruby-add-log-current-method)
+
+  (add-hook
+   (cond ((boundp 'before-save-hook)
+          (make-local-variable 'before-save-hook)
+          'before-save-hook)
+         ((boundp 'write-contents-functions) 'write-contents-functions)
+         ((boundp 'write-contents-hooks) 'write-contents-hooks))
+   'ruby-mode-set-encoding)
+
+  (set (make-local-variable 'font-lock-defaults)
+       '((ruby-font-lock-keywords) nil nil))
+  (set (make-local-variable 'font-lock-keywords)
+       ruby-font-lock-keywords)
+  (set (make-local-variable 'font-lock-syntax-table)
+       ruby-font-lock-syntax-table)
+  (set (make-local-variable 'font-lock-syntactic-keywords)
+       ruby-font-lock-syntactic-keywords)
+
+  (if (fboundp 'run-mode-hooks)
+      (run-mode-hooks 'ruby-mode-hook)
+    (run-hooks 'ruby-mode-hook)))
+
+;;; Invoke ruby-mode when appropriate
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))
+
+;;;###autoload
+(add-to-list 'interpreter-mode-alist '("ruby" . ruby-mode))
+(add-to-list 'interpreter-mode-alist '("rbx" . ruby-mode))
+(add-to-list 'interpreter-mode-alist '("jruby" . ruby-mode))
+(add-to-list 'interpreter-mode-alist '("ruby1.9" . ruby-mode))
+(add-to-list 'interpreter-mode-alist '("ruby1.8" . ruby-mode))
+
+(provide 'ruby-mode)
+
+;;; ruby-mode.el ends here