Subject: 24.3; When starting Emacs 24.3.1 in Xubuntu 13.10, "Theme parsing error: gtk-widgets.css" is always output to console

From: Teemu Leisti <[email-masked]>
Date: Wed, 19 Feb 2014 16:38:45 +0200

I have just updated my Xubuntu to version 13.10, and installed all the latest
package upgrades.  To make sure that this problem didn't result from some funky
package conflict issue, I removed all emacs-related packages and files, then
reinstalled emacs24 via the Ubuntu software center (which gets me Emacs
v24.3.1).  The X version is 1.14.5.

The problem: each time I start Emacs from the command line, it outputs a
warning.  For example:

    teemu@teemu-desktop:~ $ emacs &
    [2] 11450
    teemu@teemu-desktop:~ $
    (emacs:11450): Gtk-WARNING **: Theme parsing error:
gtk-widgets.css:3274:48: Expected ',' in color definition

I tried commenting out all color-setting commands and the font-setting command
from my init.el file, but that didn't help.  I also tried starting without
customizations:

    teemu@teemu-desktop:~ $ emacs -Q &
    [4] 11727
    teemu@teemu-desktop:~ $
    (emacs:11727): Gtk-WARNING **: Theme parsing error:
gtk-widgets.css:3274:48: Expected ',' in color definition

but obviously that didn't help.

The number reported at the beginning of the warning line, after "emacs:",
changes from one execution to another; so, far I've got at least these:
11450, 11531, 11548, 11590, 11605, 11618, 11639, and 11649.

What follows is (a) the contents of my init.el file, (b) the contents of
/usr/share/emacs/24.3/site-lisp/debian-startup, (c) the contents of file
/home/teemu/.emacs.d/elpa/ruby-mode-1.1/ruby-mode, (d) the packages
installed on my machines whose
name includes gtk, and (e) the text generated by executing the
function report-emacs-bug.

-- Teemu Leisti 2014-02-19

===========================================================================
== (a) my init.el file ====================================================
===========================================================================

;;;; file: ~/.emacs.d/init.el
;;;; Teemu Leisti 2014-02-19

;; Use UTF-8 for all character encoding.
(set-language-environment 'utf-8)
(set-default-coding-systems 'utf-8)
(set-selection-coding-system 'utf-8)
(set-locale-environment "en_GB.UTF-8")
(prefer-coding-system 'utf-8)
(setq utf-translate-cjk-mode nil) ; disable CJK coding/encoding

;   ;; Select the package archives to use.
;   (require 'package)
;   (package-initialize)
;   (setq package-archives '(("ELPA" . "http://tromey.com/elpa/")
;                            ("gnu" . "http://elpa.gnu.org/packages/")
;                            ("marmalade" .
"http://marmalade-repo.org/packages/")))

;; Add to load-path this directory containing local add-ons.
(add-to-list 'load-path "~/.emacs.d/lisp/")

;; Set the default major mode.
(setq major-mode 'text-mode)

(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(blink-cursor-mode nil)
 '(c-basic-indent 2)
 '(c-basic-offset 4)
 '(js-indent-level 2)
 '(c-offsets-alist (quote ((inline-open . 0) (statement-case-intro .
0) (statement-case-open . 0) (substatement-open . 0) (case-label .
+))))
 '(fci-handle-truncate-lines nil)
 '(fill-column 100)
 '(indent-tabs-mode nil)
 '(indicate-empty-lines t)
 '(inhibit-startup-screen t)
 '(line-move-visual nil)
 '(tab-width 4)
 '(vc-make-backup-files t)
 '(list-colors-sort 'hsv)
 '(search-whitespace-regexp nil))

(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(highlight ((t (:background "khaki"))))
 '(isearch ((t (:background "chocolate" :foreground "white"))))
 '(lazy-highlight ((t (:background "pale green" :foreground "black"))))
 '(mode-line ((t (:background "darkorange4" :foreground "white smoke"
:box (:line-width -1 :style released-button)))))
 '(mode-line-inactive ((t (:background "gray16" :foreground "white
smoke" :box (:line-width -1 :style released-button)))))
 '(region ((t (:background "yellow" :foreground "black"))))
 '(secondary-selection ((t (:background "dark salmon"))))
 '(warning ((t (:foreground "orange red" :weight bold)))))

;; Turn on the highlight-current-line mode, and set its color.
(global-hl-line-mode 1)
(set-face-background 'hl-line "navajo white")

;; Show a vertical line at the fill column, defined as a global minor
mode.  Provided by
;; fill-column-indicator.el.
(require 'fill-column-indicator)
(define-globalized-minor-mode global-fci-mode fci-mode (lambda () (fci-mode 1)))
(global-fci-mode 1)

;; C-S-DEL kills the current buffer without questions, if it's not dirty.
(defun kill-current-buffer-immediately ()
  (interactive)
  (kill-buffer (buffer-name)))
(global-set-key [C-S-delete] 'kill-current-buffer-immediately)

;; Scroll up and down without moving the cursor by pressing the
numeric keypad's "/" and "*" keys.
(defun scroll-down-keep-cursor () (interactive) (scroll-down 1))
(global-set-key [kp-divide] 'scroll-down-keep-cursor)
(defun scroll-up-keep-cursor () (interactive) (scroll-up 1))
(global-set-key [kp-multiply] 'scroll-up-keep-cursor)

;; Enable the narrow-to-region command.
(put 'narrow-to-region 'disabled nil)

;; Remove all trailing whitespace when saving buffer.
(add-hook 'before-save-hook 'delete-trailing-whitespace)

;; Set the transient mark mode.
(transient-mark-mode)

;; Make tab characters visible as a close-guillemet symbol.
(require 'whitespace)
(setq whitespace-style '(tabs tab-mark))
(global-whitespace-mode 1)

;; Set the window title format to show the full path of the file.
(setq frame-title-format
      '(buffer-file-name "%f" (dired-directory dired-directory "%b")))

;; Tweak the mode line format.
(setq default-mode-line-format
      (list "--:" 'mode-line-modified "- %30b %[(" 'mode-name 'minor-mode-alist
            "%n" 'mode-line-process ")%]--(%l,%c)--" '(-3 . "%p") "-%-"))
(setq mode-line-format default-mode-line-format)

;; Make all yes-or-no ask for "y" of "n".
(fset 'yes-or-no-p 'y-or-n-p)

;; Define a command to join all the lines of a paragraph into one
line, which is the opposite of
;; fill-paragraph (M-q).  Bind to key sequence shift-tab.
(defun unfill-paragraph ()
  "Takes a multi-line paragraph and makes it into a single line of text."
  (interactive)
  (let ((fill-column (point-max)))
    (fill-paragraph nil)))
(global-set-key [backtab] 'unfill-paragraph)

;; Pressing C-u C-SPC pops the mark from the mark stack.  The
following setting enables you to keep
;; pressing spce, while holding down Ctrl, to keep unpopping marks.
(setq set-mark-command-repeat-pop t)

;; No need for adding the encoding line to top of Ruby files in case
the file contains non-ASCII
;; characters, since Ruby 2 uses UTF-8 by default.
;; TODO: take this line into use when you take Ruby 2 into use.
;(setq ruby-insert-encoding-magic-comment nil)

;   ;; Turn on the Projectile global mode for all buffers.  Projectile
helps navigation among the files
;   ;; of a project, including those of Rails projects.
;   (add-to-list 'load-path "~/.emacs.d/elpa/projectile-0.9.1/")
;   (require 'projectile)
;   (projectile-global-mode)                ;; turn it on everywhere
;   (setq projectile-enable-caching t)      ;; so native indexing will be faster
;   (global-set-key (kbd "C-,") 'projectile-find-file)
;   (global-set-key (kbd "C-.") 'projectile-grep)

;; Meta-up and meta-down move the active region, or the current line,
up or down.
(require 'move-text)
(global-set-key [M-up] 'move-text-up)
(global-set-key [M-down] 'move-text-down)

;; (C-c c) inserts the <code> </code> tags around the region.
(fset 'insert-mediawiki-code-tags-around-region "\C-x\C-x<code>\C-x\C-x</code>")
(global-set-key (kbd "C-c c") 'insert-mediawiki-code-tags-around-region)

;; Customize the calendar; (C-c r) opens it.
(setq calendar-week-start-day 1) ;; The week starts on Monday.
(copy-face font-lock-constant-face 'calendar-iso-week-face)
(set-face-attribute 'calendar-iso-week-face nil :height 0.8)
(setq calendar-intermonth-text ;; Show ISO week numbers.
      '(propertize (format "%2d"
                           (car
                            (calendar-iso-from-absolute
                             (calendar-absolute-from-gregorian (list
month day year)))))
                   'font-lock-face 'calendar-iso-week-face))
(setq calendar-intermonth-header ;; Also show a "wk" header above the
ISO week numbers.
      (propertize "wk" 'font-lock-face 'calendar-iso-week-face))
(global-set-key (kbd "C-c r") 'calendar)

;; Instead of killing region, (C-w) now kills previous word.  S-delete kills
;; region, anyway.
(global-set-key (kbd "C-w") 'backward-kill-word)

;; (C-c 6) inserts "-- Teemu".
(global-set-key (kbd "C-c 6") '(lambda () (interactive) (insert "-- Teemu")))

;; (C-c 7) inserts "-- Teemu " followed by the current date in ISO format.
(global-set-key (kbd "C-c 7")
                '(lambda ()
                   (interactive)
                   (insert "-- Teemu " (format-time-string "%Y-%m-%d"))))

;; (C-c 8) inserts "-- Teemu Leisti".
(global-set-key (kbd "C-c 8") '(lambda () (interactive) (insert "--
Teemu Leisti")))

;; (C-c 9) inserts "-- Teemu Leisti " followed by the current date in
ISO format.
(global-set-key (kbd "C-c 9")
                '(lambda ()
                   (interactive)
                   (insert "-- Teemu Leisti " (format-time-string "%Y-%m-%d"))))

;; (C-c 0) inserts the current date in ISO format.
(global-set-key (kbd "C-c 0") '(lambda () (interactive) (insert
(format-time-string "%Y-%m-%d"))))

;; (C-c d) opens file ~/misc/todo.txt.
(global-set-key (kbd "C-c d") '(lambda () (interactive) (find-file
"~/misc/todo.txt")))

;; (C-c e) opens the initialization file,  ~/.emacs.d/init.el.
(global-set-key (kbd "C-c e") '(lambda () (interactive) (find-file
"~/.emacs.d/init.el")))

;; (C-c t) opens file ~/tmp/tmp.txt.
(global-set-key (kbd "C-c t") '(lambda () (interactive) (find-file
"~/tmp/tmp.txt")))

;; (C-c j) opens file ~/tmp/tmp.java.
(global-set-key (kbd "C-c j") '(lambda () (interactive) (find-file
"~/tmp/tmp.java")))

;; (C-c m) starts opening a file in directory ~/projects/mui.
(fset 'start-opening-a-mui-file "\C-x\C-f\C-a\C-k~/projects/mui/")
(global-set-key (kbd "C-c m") '(lambda ()
                                 (interactive)
                                 (execute-kbd-macro (symbol-function
'start-opening-a-mui-file))))

;; (C-ö) sets the fill column to 80.
(global-set-key (kbd "C-ö")
                '(lambda ()
                   (interactive)
                   (setq fill-column 80)
                   (message "Fill column set to 80.")))

;; (C-ä) sets the fill column to 100.
(global-set-key (kbd "C-ä")
                '(lambda ()
                   (interactive)
                   (setq fill-column 100)
                   (message "Fill column set to 100.")))

;; (C-') leaves only the active window open.
(global-set-key (kbd "C-'") '(lambda () (interactive) (delete-other-windows)))

;; C-+ enlarges the current window by one row, providing an easier key sequence
;; than (C-x ^), which it's bound to by default.
(global-set-key (kbd "C-+") 'enlarge-window)

;; C-å shrink the current window by one row.
(global-set-key (kbd "C-å") 'shrink-window)

;;;; The following definitions are appropriate for Emacs running on X,
;;;; not in a terminal window.

;; Set up the frames.
(setq default-frame-alist
      '((wait-for-wm . nil)          ; speed up initialization
        (top . 60) (left . 150)      ; pixels; this tells where on the
display to place the frame
        (background-color . "cornsilk")
        (foreground-color . "black")
        (line-spacing . 0)
        (screen-gamma . 1.8)
        (font . "-unknown-Liberation
Mono-normal-normal-normal-*-13-*-*-*-m-0-iso10646-1")
        ; in case the preceding doesn't work, try one of these:
        ; (font . "-unknown-Droid Sans
Mono-normal-normal-normal-*-14-*-*-*-m-0-iso10646-1")
        ; (font . "-b&h-lucidatypewriter-bold-*-*-*-14-*-*-*-*-*-iso8859-*")
))

(if (window-system) (set-frame-size (selected-frame) 130 60)) ; columns, lines

;; C-? toggles the frame between maximum and minimum, with a puzzling
;; almost-vertical-maximum mode in between.
(defun x-maximize-frame ()
  "Maximize the current frame (to full screen)"
  (interactive)
  (x-send-client-message nil 0 nil "_NET_WM_STATE" 32
                         '(2 "_NET_WM_STATE_MAXIMIZED_HORZ" 0))
  (x-send-client-message nil 0 nil "_NET_WM_STATE" 32
                         '(2 "_NET_WM_STATE_MAXIMIZED_VERT" 0)))
(global-set-key (kbd "C-?") 'x-maximize-frame)

;; Redefine the C-z key, because otherwise it runs the command suspend-frame,
;; which annoyingly minimizes the window.
(defun echo-friendly-message () (interactive) (message "Please do not
press that button again."))
(global-set-key (kbd "C-z") 'echo-friendly-message)

;; Show the windows in a tab bar at the top of each window.  Provided
by tabbar.el.
(require 'tabbar)
(tabbar-mode)                     ;; Turn on the tabbar minor mode.
(defun my-tabbar-buffer-groups () ;; Customize to show all normal
files in one group.
  "Returns the name of the tab group names the current buffer belongs to.
There are two groups: Emacs buffers (those whose name starts with “*”, plus
dired buffers), and the rest."
  (list (cond ((string-equal "*" (substring (buffer-name) 0 1)) "emacs")
              ((eq major-mode 'dired-mode) "emacs")
              (t "user")))
)
(setq tabbar-buffer-groups-function 'my-tabbar-buffer-groups)
;; Tweak the text color of the tabs, for easier visibility.
(set-face-attribute 'tabbar-default nil :height 1.1)
(set-face-attribute 'tabbar-unselected nil :background "wheat2")
(set-face-attribute 'tabbar-unselected nil :foreground "black")
(set-face-attribute 'tabbar-selected nil :background "cornsilk")
(set-face-attribute 'tabbar-selected nil :foreground "black")
;; Bind tab navigation to keys.
(global-set-key [C-prior] 'tabbar-backward-tab) ;; Previous tab: Ctrl-PageUp
(global-set-key [C-next]  'tabbar-forward-tab)  ;; Next tab:     Ctrl-PageDown
;; Add this hook to get rid of the slight but annoying extra spacing
between lines Tabbar sometimes
;; introduces when a file is opened.  Adding the hook is equivalent to
always pressing C-L after
;; opening a file.
(add-hook 'find-file-hook 'recenter-top-bottom)

;; Hint: to see which keystrokes are unassigned, command describe-unbound-keys.

;; This is an example of how to hook a function to the after-save-hook,
;; conditional on the major mode being java-mode.
;
; (defun a-test-save-hook()
;   "Test of save hook"
;   (when (eq major-mode 'java-mode)
;     (message "this is the payload function")))
; (add-hook 'after-save-hook 'a-test-save-hook)
;
;; Further examples of adding hooks.
;
; ;; Define functions to be used in individual mode definitions.
; (defun fill-column-to-100() (setq fill-column 100))
; (defun use-spaces-instead-of-tabs() (setq indent-tabs-mode nil))
; ;; When in the Java, Ruby, or HTML modes:
; ;; Set the maximum line length to 100.
; (add-hook 'java-mode-hook 'fill-column-to-100)
; (add-hook 'ruby-mode-hook 'fill-column-to-100)
; (add-hook 'html-mode-hook 'fill-column-to-100)
; ;; Use tabs instead of spaces.
; (add-hook 'java-mode-hook 'use-spaces-instead-of-tabs)
; (add-hook 'ruby-mode-hook 'use-spaces-instead-of-tabs)
; (add-hook 'html-mode-hook 'use-spaces-instead-of-tabs)

===========================================================================
== (b) file /usr/share/emacs/24.3/site-lisp/debian-startup ================
===========================================================================

;;; debian-startup.el --- Debian specific emacsen startup code.

;; Copyright (C) 1998-2012 Rob Browning

;; Maintainer: Rob Browning <[email-masked]>
;; Keywords: debian

;; This file is part of the debian release of GNU Emacs, and will
;; be contributed to the FSF after testing. It is released under the same
;; terms, namely the GPL v2 or later.

;; 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 2, 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; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; This file contains startup code needed by all the various flavors
;; of Emacs for a Debian system.

(defun debian-pkg-add-load-path-item (item)
  "Takes a path item (a string) and adds it to load path in the
correct position for an add-on package, before the emacs system
directories, but after the /usr/local/ directories.  After modifying
load-path, returns the new load-path."
  (let ((pos 0)
        (last-local-pos nil)
        (lp-rest load-path))

    ;; Find the last /usr/local/ element.
    (while (not (null lp-rest))
      (if (and (not (null (car lp-rest)))
               (string-match "^/usr/local" (car lp-rest)))
          (setq last-local-pos pos))
      (setq pos (+ pos 1))
      (setq lp-rest (cdr lp-rest)))

    (if (not last-local-pos)
        (error "No /usr/local/ prefixed paths in load-path"))

    (let ((result '())
          (pos 0)
          (remainder load-path))
      (while (consp remainder)
        (setq result (cons (car remainder) result))
        (setq remainder (cdr remainder))
        (if (= pos last-local-pos)
            (setq result (cons item result)))
        (setq pos (+ pos 1)))
      (setq load-path (nreverse result))
      load-path)))

(defun debian-unique-strings (strings)
  "Takes a list of strings and returns the list with *adjacent*
duplicates removed."
  (let ((result '()))
    (while (consp strings)
      (if (not (string= (car strings) (car (cdr strings))))
          (setq result (cons (car strings) result)))
      (setq strings (cdr strings)))
    (nreverse result)))

(defun debian-run-directories (&rest dirs)
  "Load each file of the form XXfilename.el or XXfilename.elc in any
of the dirs, where XX must be a number.  The files will be run in
alphabetical order.  If a file appears in more than one of the dirs,
then the earlier dir takes precedence, and a .elc file always
supercedes a .el file of the same name."

  (let* ((paths (mapcar 'copy-sequence dirs)) ; Ensure we have unique objects.

         ;; Get a list of all the files in all the specified
         ;; directories that match the pattern.
         (files
          (apply 'append
                 (mapcar
                  (lambda (dir)
                    (directory-files dir nil "^[0-9][0-9].*\\.elc?$" t))
                  paths)))

         ;; Now strip the directory portion, remove any .el or .elc
         ;; extension.
         (stripped-names
          (mapcar (lambda (file)
                    (if (string-match "\\.el$" file)
                        (substring file 0 -3)
                      (if (string-match "\\.elc$" file)
                          (substring file 0 -4)
                        file)))
                  (mapcar
                   (lambda (file) (file-name-nondirectory file))
                   files)))

         ;; Finally sort them, and delete duplicates
         (base-names (debian-unique-strings (sort stripped-names 'string<))))

    (setq load-path (append paths load-path)) ; Prefix paths temporarily.
    ;; Now load the files.  "load" will make sure we get the byte
    ;; compiled one first, if any, and will respect load-path's
    ;; ordering.
    (mapc
     (lambda (file)
       (condition-case err
           (load file nil)
         (error (message "Error while loading %s: %s"
                         file (error-message-string err)))))
     base-names)
    ;; Remove the paths we inserted, and only those paths.
    (dolist (item paths)
      (setq load-path (remq item load-path)))))

(defun debian-startup (flavor)

  ;; Our handling of debian-emacs-flavor here is truly weird, but we
  ;; have to do it like this because some of the emacsen flavors
  ;; didn't DWIM in their startup sequence.  I wasn't as clear as I
  ;; should have been in debian-policy, but they were also
  ;; technically violating policy.

  ;; It's even weirder now.  I've changed policy back to the old way,
  ;; but I'm also doing some sanity checking here and making sure that
  ;; even debian-emacs-flavor gets set no matter what.  I'm in a rush
  ;; right now, but I'll come back later and make all this cleaner and
  ;; better documented.  Sorry.

  (unless (boundp 'debian-emacs-flavor)
    (defconst debian-emacs-flavor flavor
      "A symbol representing the particular debian flavor of emacs that's
running.  Something like 'emacs20, 'xemacs20, etc."))

  (let ((common-dir "/etc/emacs/site-start.d")
        (flavor-dir (concat "/etc/" (symbol-name flavor) "/site-start.d")))
    (debian-run-directories flavor-dir common-dir)))
package tabbar edmacro kmacro move-text whitespace disp-table
fill-column-indicator easy-mmode hl-line time-date tooltip ediff-hook
vc-hooks lisp-float-type mwheel x-win x-dnd tool-bar dnd fontset image
regexp-opt fringe tabulated-list newcomment lisp-mode register page
menu-bar rfn-eshadow timer select scroll-bar mouse jit-lock font-lock
syntax facemenu font-core frame cham georgian utf-8-lang misc-lang
vietnamese tibetan thai tai-viet lao korean japanese hebrew greek
romanian slovak czech european ethiopic indian cyrillic chinese
case-table epa-hook jka-cmpr-hook help simple abbrev minibuffer loaddefs
button faces cus-face macroexp files text-properties overlay sha1 md5
base64 format env code-pages mule custom widget hashtable-print-readable
backquote make-network-process dbusbind dynamic-setting
system-font-setting font-render-setting move-toolbar gtk x-toolkit x
multi-tty emacs)

===========================================================================
== (c) /home/teemu/.emacs.d/elpa/ruby-mode-1.1/ruby-mode.el ===============
===========================================================================

;;; ruby-mode.el --- Major mode for editing Ruby files

;; Copyright (C) 1994-2008 Yukihiro Matsumoto, Nobuyoshi Nakada

;; 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.1

;; This file is not yet part of GNU Emacs.

;;; 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-variable-buffer-local 'comment-start) "# ")
  (set (make-variable-buffer-local 'comment-end) "")
  (set (make-variable-buffer-local 'comment-column) ruby-comment-column)
  (set (make-variable-buffer-local '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")))
              )))))

;;;###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)))

(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 pnt)
  "Return t if the character before PNT (default `point') is a special
character.

Special characters are `?', `$', `:' when preceded by whitespace,
and `\\' when preceded by `?'."
  (setq pnt (or pnt (point)))
  (let ((c (char-before pnt)) (b (and (< (point-min) pnt) (char-before
(1- pnt)))))
    (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))
       ((looking-at ruby-here-doc-beg-re)
        (if (re-search-forward (ruby-here-doc-end-match)
                               indent-point t)
            (forward-line 1)
          (setq in-string (match-end 0))
          (goto-char 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 ((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 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) 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 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 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))

(eval-when-compile
  (defmacro defun-region-command (func args &rest body)
    "TODO: document."
    ;; If we drop xemacs support, we can replace this macro with a simple defun
    (let ((intr (car body)))
      (when (featurep 'xemacs)
        (if (stringp intr) (setq intr (cadr body)))
        (and (eq (car intr) 'interactive)
             (setq intr (cdr intr))
             (setcar intr (concat "_" (car intr)))))
      (cons 'defun (cons func (cons args body))))))

;; TODO: Why isn't one ruby-*-of-defun written in terms of the other?
(defun-region-command 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-region-command 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-region-command 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-region-command 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-region-command 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-region-command 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)
                                 (oddp (skip-chars-backward "\\")))))
                   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.")

;; Invoke ruby-mode when appropriate

;;;###autoload
(add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))

;;;###autoload
(progn
  (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

===========================================================================
== (e) the text generated by executing the function report-emacs-bug ======
===========================================================================

This bug report will be sent to the Bug-GNU-Emacs mailing list
and the GNU bug tracker at debbugs.gnu.org.  Please check that
the From: line contains a valid email address.  After a delay of up
to one day, you should receive an acknowledgment at that address.

If Emacs crashed, and you have the Emacs process in the gdb debugger,
please include the output from the following gdb commands:
    `bt full' and `xbacktrace'.
For information about debugging Emacs, please read the file
/usr/share/emacs/24.3/etc/DEBUG.

In GNU Emacs 24.3.1 (x86_64-pc-linux-gnu, GTK+ Version 3.8.2)
 of 2013-07-27 on roseapple, modified by Debian
Windowing system distributor `The X.Org Foundation', version 11.0.11405000
System Description:    Ubuntu 13.10

Configured using:
 `configure '--build' 'x86_64-linux-gnu' '--build' 'x86_64-linux-gnu'
 '--prefix=/usr' '--sharedstatedir=/var/lib' '--libexecdir=/usr/lib'
 '--localstatedir=/var/lib' '--infodir=/usr/share/info'
 '--mandir=/usr/share/man' '--with-pop=yes'
 '--enable-locallisppath=/etc/emacs24:/etc/emacs:/usr/local/share/emacs/24.3/site-lisp:/usr/local/share/emacs/site-lisp:/usr/share/emacs/24.3/site-lisp:/usr/share/emacs/site-lisp'
 '--with-crt-dir=/usr/lib/x86_64-linux-gnu' '--with-x=yes'
 '--with-x-toolkit=gtk3' '--with-toolkit-scroll-bars'
 'build_alias=x86_64-linux-gnu' 'CFLAGS=-g -O2 -fstack-protector
 --param=ssp-buffer-size=4 -Wformat -Werror=format-security -Wall'
 'LDFLAGS=-Wl,-Bsymbolic-functions -Wl,-z,relro'
 'CPPFLAGS=-D_FORTIFY_SOURCE=2''

Important settings:
  value of $LC_MONETARY: en_GB.UTF-8
  value of $LC_NUMERIC: en_GB.UTF-8
  value of $LC_TIME: en_GB.UTF-8
  value of $LANG: en_US.UTF-8
  locale-coding-system: utf-8
  default enable-multibyte-characters: t

Major mode: Text

Minor modes in effect:
  tabbar-mwheel-mode: t
  tabbar-mode: t
  global-whitespace-mode: t
  global-fci-mode: t
  fci-mode: t
  global-hl-line-mode: t
  tooltip-mode: t
  mouse-wheel-mode: t
  tool-bar-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t
  line-number-mode: t
  transient-mark-mode: t

Recent input:
<C-left> <backspace> <backspace> i t SPC o u t p u
t s SPC M-d <delete> <end> : <return> <return> <down-mouse-2>
<mouse-2> <up> <up> <delete> SPC SPC SPC SPC <down>
<down> <return> I S-SPC d o n ' t SPC k n o w SPC i
f SPC i t SPC h a s SPC a n y t h i n g SPC t o SPC
d o SPC w i t h SPC t h i s , SPC b u t SPC t h e SPC
X SPC v e r s i o n SPC i s SPC 1 . 1 4 . 5 <up> <up>
<C-left> <C-left> <C-left> <C-left> <C-left> <C-left>
<C-left> <C-left> <C-left> <C-left> <C-left> <C-left>
<C-left> <C-left> <C-left> <C-right> SPC / w <backspace>
<backspace> ( w h i c h SPC g e t s SPC m e SPC E m
a c s SPC v 2 4 . 3 . 1 ) <right> <delete> <return>
<return> M-d <delete> <delete> M-c <delete> SPC <home>
T h e SPC p r o b l e m SPC <backspace> : SPC M-l C-x
C-s <C-next> <C-end> <down> <down> <backspace> . <return>
C-x C-s C-h h C-x k <return> C-h C-h C-x o <backspace>
C-h C-h SPC SPC SPC b u g <backspace> <backspace> C-x
o C-s b u g C-s C-s C-h d b u g <return> C-g C-g M-x
r e p o <tab> r <tab> <return> C-x o <C-S-delete> <C-S-delete>
<C-S-delete> C-x 0 <down-mouse-1> <mouse-movement>
<mouse-movement> <drag-mouse-1> <help-echo> M-c <home>
C-k C-x o C-y <return> C-x C-s C-h f r e p o r t -
e m a c s - b u g <return> q <backspace> C-x C-s C-x
o q C-x 0 M-x r e p o r <tab> <return>

Recent messages:
Type C-x 1 to delete the help window, C-M-v to scroll help.
Mark saved where search started
Quit [2 times]
Making completion list...
byte-code: Selecting deleted buffer
Saving file /home/teemu/tmp/tmp.txt...
Wrote /home/teemu/tmp/tmp.txt
Type "q" in help window to restore its previous buffer.
Saving file /home/teemu/tmp/tmp.txt...
Wrote /home/teemu/tmp/tmp.txt

Load-path shadows:
/usr/share/emacs/24.3/site-lisp/debian-startup hides
/usr/share/emacs/site-lisp/debian-startup
/home/teemu/.emacs.d/elpa/ruby-mode-1.1/ruby-mode hides
/usr/share/emacs/24.3/lisp/progmodes/ruby-mode

Features:
(shadow sort gnus-util mail-extr help-fns emacsbug message format-spec
rfc822 mml mml-sec mm-decode mm-bodies mm-encode mail-parse rfc2231
mailabbrev gmm-utils mailheader sendmail rfc2047 rfc2045 ietf-drums
mm-util mail-prsvr mail-utils apropos misearch multi-isearch two-column
iso-transl help-mode easymenu thai-util thai-word mule-util lao-util
view findr-autoloads inflections-autoloads projectile-autoloads
dash-autoloads ruby-compilation-autoloads inf-ruby-autoloads inf-ruby
ruby-mode compile comint ansi-color ring ruby-mode-autoloads s-autoloads

===========================================================================
== (d) the packages installed on my machines whose name includes gtk ======
===========================================================================

apport-gtk                    install
gir1.2-dbusmenu-gtk-0.4                install
gir1.2-gtk-2.0                    install
gir1.2-gtk-3.0                    install
gir1.2-javascriptcoregtk-3.0            install
gtk-theme-config                install
gtk2-engines:amd64                install
gtk2-engines-murrine:amd64            install
gtk2-engines-pixbuf:amd64            install
gtk3-engines-unico:amd64            install
ibus-gtk:amd64                    install
ibus-gtk3:amd64                    install
indicator-application-gtk2            install
indicator-sound-gtk2                install
jockey-gtk                    install
libcanberra-gtk3-0:amd64            install
libcanberra-gtk3-module:amd64            install
libchamplain-gtk-0.12-0:amd64            install
libcheese-gtk23:amd64                install
libclutter-gtk-1.0-0:amd64            install
libdbusmenu-gtk3-4:amd64            install
libdbusmenu-gtk4:amd64                install
libgtk-3-0:amd64                install
libgtk-3-bin                    install
libgtk-3-common                    install
libgtk2-notify-perl                install
libgtk2-perl                    install
libgtk2-trayicon-perl                install
libgtk2.0-0:amd64                install
libgtk2.0-0:i386                install
libgtk2.0-bin                    install
libgtk2.0-common                install
libgtkmathview0c2a                install
libgtkmm-3.0-1:amd64                install
libgtksourceview-3.0-1:amd64            install
libgtksourceview-3.0-common            install
libgtksourceview2.0-0                install
libgtksourceview2.0-common            install
libgtkspell0                    install
libindicate-gtk3                install
libjavascriptcoregtk-1.0-0            install
libjavascriptcoregtk-3.0-0            install
libnm-gtk-common                install
libnm-gtk0                    install
libreoffice-gtk                    install
libwebkitgtk-1.0-0                install
libwebkitgtk-1.0-common                install
libwebkitgtk-3.0-0                install
libwebkitgtk-3.0-common                install
lightdm-gtk-greeter                install
python-aptdaemon.gtk3widgets            install
python-gtk2                    install
python-gtksourceview2                install
python3-aptdaemon.gtk3widgets            install
software-properties-gtk                install
transmission-gtk                install
ubuntu-release-upgrader-gtk            install
usb-creator-gtk                    install
xdg-user-dirs-gtk                install

===========================================================================
== (e) the text generated by executing the function report-emacs-bug ======
===========================================================================

This bug report will be sent to the Bug-GNU-Emacs mailing list
and the GNU bug tracker at debbugs.gnu.org.  Please check that
the From: line contains a valid email address.  After a delay of up
to one day, you should receive an acknowledgment at that address.

If Emacs crashed, and you have the Emacs process in the gdb debugger,
please include the output from the following gdb commands:
    `bt full' and `xbacktrace'.
For information about debugging Emacs, please read the file
/usr/share/emacs/24.3/etc/DEBUG.

In GNU Emacs 24.3.1 (x86_64-pc-linux-gnu, GTK+ Version 3.8.2)
 of 2013-07-27 on roseapple, modified by Debian
Windowing system distributor `The X.Org Foundation', version 11.0.11405000
System Description:    Ubuntu 13.10

Configured using:
 `configure '--build' 'x86_64-linux-gnu' '--build' 'x86_64-linux-gnu'
 '--prefix=/usr' '--sharedstatedir=/var/lib' '--libexecdir=/usr/lib'
 '--localstatedir=/var/lib' '--infodir=/usr/share/info'
 '--mandir=/usr/share/man' '--with-pop=yes'
 '--enable-locallisppath=/etc/emacs24:/etc/emacs:/usr/local/share/emacs/24.3/site-lisp:/usr/local/share/emacs/site-lisp:/usr/share/emacs/24.3/site-lisp:/usr/share/emacs/site-lisp'
 '--with-crt-dir=/usr/lib/x86_64-linux-gnu' '--with-x=yes'
 '--with-x-toolkit=gtk3' '--with-toolkit-scroll-bars'
 'build_alias=x86_64-linux-gnu' 'CFLAGS=-g -O2 -fstack-protector
 --param=ssp-buffer-size=4 -Wformat -Werror=format-security -Wall'
 'LDFLAGS=-Wl,-Bsymbolic-functions -Wl,-z,relro'
 'CPPFLAGS=-D_FORTIFY_SOURCE=2''

Important settings:
  value of $LC_MONETARY: en_GB.UTF-8
  value of $LC_NUMERIC: en_GB.UTF-8
  value of $LC_TIME: en_GB.UTF-8
  value of $LANG: en_US.UTF-8
  locale-coding-system: utf-8
  default enable-multibyte-characters: t

Major mode: Text

Minor modes in effect:
  tabbar-mwheel-mode: t
  tabbar-mode: t
  global-whitespace-mode: t
  global-fci-mode: t
  fci-mode: t
  global-hl-line-mode: t
  tooltip-mode: t
  mouse-wheel-mode: t
  tool-bar-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t
  line-number-mode: t
  transient-mark-mode: t

Recent input:
<C-left> <backspace> <backspace> i t SPC o u t p u
t s SPC M-d <delete> <end> : <return> <return> <down-mouse-2>
<mouse-2> <up> <up> <delete> SPC SPC SPC SPC <down>
<down> <return> I S-SPC d o n ' t SPC k n o w SPC i
f SPC i t SPC h a s SPC a n y t h i n g SPC t o SPC
d o SPC w i t h SPC t h i s , SPC b u t SPC t h e SPC
X SPC v e r s i o n SPC i s SPC 1 . 1 4 . 5 <up> <up>
<C-left> <C-left> <C-left> <C-left> <C-left> <C-left>
<C-left> <C-left> <C-left> <C-left> <C-left> <C-left>
<C-left> <C-left> <C-left> <C-right> SPC / w <backspace>
<backspace> ( w h i c h SPC g e t s SPC m e SPC E m
a c s SPC v 2 4 . 3 . 1 ) <right> <delete> <return>
<return> M-d <delete> <delete> M-c <delete> SPC <home>
T h e SPC p r o b l e m SPC <backspace> : SPC M-l C-x
C-s <C-next> <C-end> <down> <down> <backspace> . <return>
C-x C-s C-h h C-x k <return> C-h C-h C-x o <backspace>
C-h C-h SPC SPC SPC b u g <backspace> <backspace> C-x
o C-s b u g C-s C-s C-h d b u g <return> C-g C-g M-x
r e p o <tab> r <tab> <return> C-x o <C-S-delete> <C-S-delete>
<C-S-delete> C-x 0 <down-mouse-1> <mouse-movement>
<mouse-movement> <drag-mouse-1> <help-echo> M-c <home>
C-k C-x o C-y <return> C-x C-s C-h f r e p o r t -
e m a c s - b u g <return> q <backspace> C-x C-s C-x
o q C-x 0 M-x r e p o r <tab> <return>

Recent messages:
Type C-x 1 to delete the help window, C-M-v to scroll help.
Mark saved where search started
Quit [2 times]
Making completion list...
byte-code: Selecting deleted buffer
Saving file /home/teemu/tmp/tmp.txt...
Wrote /home/teemu/tmp/tmp.txt
Type "q" in help window to restore its previous buffer.
Saving file /home/teemu/tmp/tmp.txt...
Wrote /home/teemu/tmp/tmp.txt

Load-path shadows:
/usr/share/emacs/24.3/site-lisp/debian-startup hides
/usr/share/emacs/site-lisp/debian-startup
/home/teemu/.emacs.d/elpa/ruby-mode-1.1/ruby-mode hides
/usr/share/emacs/24.3/lisp/progmodes/ruby-mode

Features:
(shadow sort gnus-util mail-extr help-fns emacsbug message format-spec
rfc822 mml mml-sec mm-decode mm-bodies mm-encode mail-parse rfc2231
mailabbrev gmm-utils mailheader sendmail rfc2047 rfc2045 ietf-drums
mm-util mail-prsvr mail-utils apropos misearch multi-isearch two-column
iso-transl help-mode easymenu thai-util thai-word mule-util lao-util
view findr-autoloads inflections-autoloads projectile-autoloads
dash-autoloads ruby-compilation-autoloads inf-ruby-autoloads inf-ruby
ruby-mode compile comint ansi-color ring ruby-mode-autoloads s-autoloads

===========================================================================