]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/ruby-mode.el
Update copyright year to 2015
[gnu-emacs] / lisp / progmodes / ruby-mode.el
index 7b40bf536f86f2c5260874621e5818d432e0b69f..d1e42ca3443725c91e2f004d47fd380a8ef2aa9d 100644 (file)
@@ -1,6 +1,6 @@
 ;;; ruby-mode.el --- Major mode for editing Ruby files
 
-;; Copyright (C) 1994-2013 Free Software Foundation, Inc.
+;; Copyright (C) 1994-2015 Free Software Foundation, Inc.
 
 ;; Authors: Yukihiro Matsumoto
 ;;     Nobuyoshi Nakada
@@ -39,8 +39,6 @@
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))
-
 (defgroup ruby nil
   "Major mode for editing Ruby code."
   :prefix "ruby-"
   "Regexp to match the beginning of a heredoc.")
 
   (defconst ruby-expression-expansion-re
-    "\\(?:[^\\]\\|\\=\\)\\(\\\\\\\\\\)*\\(#\\({[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\|\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+\\)\\)"))
+    "\\(?:[^\\]\\|\\=\\)\\(\\\\\\\\\\)*\\(#\\({[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\|\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+\\|\\$[^a-zA-Z \n]\\)\\)"))
 
 (defun ruby-here-doc-end-match ()
   "Return a regexp to find the end of a heredoc.
@@ -154,6 +152,7 @@ This should only be called after matching against `ruby-here-doc-beg-re'."
     (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 "C-c {") 'ruby-toggle-block)
+    (define-key map (kbd "C-c '") 'ruby-toggle-string-quotes)
     map)
   "Keymap used in Ruby mode.")
 
@@ -166,6 +165,8 @@ This should only be called after matching against `ruby-here-doc-beg-re'."
     ["End of Block" ruby-end-of-block t]
     ["Toggle Block" ruby-toggle-block t]
     "--"
+    ["Toggle String Quotes" ruby-toggle-string-quotes t]
+    "--"
     ["Backward Sexp" ruby-backward-sexp
      :visible (not ruby-use-smie)]
     ["Backward Sexp" backward-sexp
@@ -188,7 +189,6 @@ This should only be called after matching against `ruby-here-doc-beg-re'."
     (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)
@@ -229,22 +229,84 @@ This should only be called after matching against `ruby-here-doc-beg-re'."
   :group 'ruby
   :safe 'integerp)
 
+(defconst ruby-alignable-keywords '(if while unless until begin case for def)
+  "Keywords that can be used in `ruby-align-to-stmt-keywords'.")
+
+(defcustom ruby-align-to-stmt-keywords '(def)
+  "Keywords after which we align the expression body to statement.
+
+When nil, an expression that begins with one these keywords is
+indented to the column of the keyword.  Example:
+
+  tee = if foo
+          bar
+        else
+          qux
+        end
+
+If this value is t or contains a symbol with the name of given
+keyword, the expression is indented to align to the beginning of
+the statement:
+
+  tee = if foo
+    bar
+  else
+    qux
+  end
+
+Only has effect when `ruby-use-smie' is t.
+"
+  :type `(choice
+          (const :tag "None" nil)
+          (const :tag "All" t)
+          (repeat :tag "User defined"
+                  (choice ,@(mapcar
+                             (lambda (kw) (list 'const kw))
+                             ruby-alignable-keywords))))
+  :group 'ruby
+  :safe 'listp
+  :version "24.4")
+
+(defcustom ruby-align-chained-calls nil
+  "If non-nil, align chained method calls.
+
+Each method call on a separate line will be aligned to the column
+of its parent.
+
+Only has effect when `ruby-use-smie' is t."
+  :type 'boolean
+  :group 'ruby
+  :safe 'booleanp
+  :version "24.4")
+
 (defcustom ruby-deep-arglist t
   "Deep indent lists in parenthesis when non-nil.
-Also ignores spaces after parenthesis when 'space."
+Also ignores spaces after parenthesis when `space'.
+Only has effect when `ruby-use-smie' is nil."
   :type 'boolean
   :group 'ruby
   :safe 'booleanp)
 
+;; FIXME Woefully under documented.  What is the point of the last `t'?.
 (defcustom ruby-deep-indent-paren '(?\( ?\[ ?\] t)
   "Deep indent lists in parenthesis when non-nil.
 The value t means continuous line.
-Also ignores spaces after parenthesis when 'space."
+Also ignores spaces after parenthesis when `space'.
+Only has effect when `ruby-use-smie' is nil."
+  :type '(choice (const nil)
+                 character
+                 (repeat (choice character
+                                 (cons character (choice (const nil)
+                                                         (const t)))
+                                 (const t) ; why?
+                                 )))
   :group 'ruby)
 
 (defcustom ruby-deep-indent-paren-style 'space
-  "Default deep indent style."
-  :options '(t nil space) :group 'ruby)
+  "Default deep indent style.
+Only has effect when `ruby-use-smie' is nil."
+  :type '(choice (const t) (const nil) (const space))
+  :group 'ruby)
 
 (defcustom ruby-encoding-map
   '((us-ascii       . nil)       ;; Do not put coding: us-ascii
@@ -258,7 +320,12 @@ explicitly declared in magic comment."
   :group 'ruby)
 
 (defcustom ruby-insert-encoding-magic-comment t
-  "Insert a magic Emacs 'coding' comment upon save if this is non-nil."
+  "Insert a magic Ruby encoding comment upon save if this is non-nil.
+The encoding will be auto-detected.  The format of the encoding comment
+is customizable via `ruby-encoding-magic-comment-style'.
+
+When set to `always-utf8' an utf-8 comment will always be added,
+even if it's not required."
   :type 'boolean :group 'ruby)
 
 (defcustom ruby-encoding-magic-comment-style 'ruby
@@ -267,13 +334,15 @@ explicitly declared in magic comment."
           (const :tag "Emacs Style" emacs)
           (const :tag "Ruby Style" ruby)
           (const :tag "Custom Style" custom))
-  :group 'ruby)
+  :group 'ruby
+  :version "24.4")
 
-(defcustom ruby-custom-encoding-magic-comment-template "# coding: %s"
-  "The encoding comment template to be used when
-`ruby-encoding-magic-comment-style' is set to `custom'."
+(defcustom ruby-custom-encoding-magic-comment-template "# encoding: %s"
+  "A custom encoding comment template.
+It is used when `ruby-encoding-magic-comment-style' is set to `custom'."
   :type 'string
-  :group 'ruby)
+  :group 'ruby
+  :version "24.4")
 
 (defcustom ruby-use-encoding-map t
   "Use `ruby-encoding-map' to set encoding magic comment if this is non-nil."
@@ -296,10 +365,10 @@ explicitly declared in magic comment."
              ;; but avoids lots of conflicts:
              (exp "and" exp) (exp "or" exp))
        (exp  (exp1) (exp "," exp) (exp "=" exp)
-             (id " @ " exp)
-             (exp "." id))
+             (id " @ " exp))
        (exp1 (exp2) (exp2 "?" exp1 ":" exp1))
-       (exp2 ("def" insts "end")
+       (exp2 (exp3) (exp3 "." exp2))
+       (exp3 ("def" insts "end")
              ("begin" insts-rescue-insts "end")
              ("do" insts "end")
              ("class" insts "end") ("module" insts "end")
@@ -312,10 +381,10 @@ explicitly declared in magic comment."
              ("unless" insts "end")
              ("if" if-body "end")
              ("case"  cases "end"))
-       (formal-params ("opening-|" exp "|"))
+       (formal-params ("opening-|" exp "closing-|"))
        (for-body (for-head ";" insts))
        (for-head (id "in" exp))
-       (cases (exp "then" insts) ;; FIXME: Ruby also allows (exp ":" insts).
+       (cases (exp "then" insts)
               (cases "when" cases) (insts "else" insts))
        (expseq (exp) );;(expseq "," expseq)
        (hashvals (id "=>" exp1) (hashvals "," hashvals))
@@ -326,7 +395,7 @@ explicitly declared in magic comment."
        (ielsei (itheni) (itheni "else" insts))
        (if-body (ielsei) (if-body "elsif" if-body)))
      '((nonassoc "in") (assoc ";") (right " @ ")
-       (assoc ",") (right "=") (assoc "."))
+       (assoc ",") (right "="))
      '((assoc "when"))
      '((assoc "elsif"))
      '((assoc "rescue" "ensure"))
@@ -339,14 +408,14 @@ explicitly declared in magic comment."
        (left ".." "...")
        (left "+" "-")
        (left "*" "/" "%" "**")
-       ;; (left "|") ; FIXME: Conflicts with | after block parameters.
        (left "&&" "||")
-       (left "^" "&")
+       (left "^" "&" "|")
        (nonassoc "<=>")
        (nonassoc ">" ">=" "<" "<=")
        (nonassoc "==" "===" "!=")
        (nonassoc "=~" "!~")
-       (left "<<" ">>"))))))
+       (left "<<" ">>")
+       (right "."))))))
 
 (defun ruby-smie--bosp ()
   (save-excursion (skip-chars-backward " \t")
@@ -356,15 +425,22 @@ explicitly declared in magic comment."
   (save-excursion
     (skip-chars-backward " \t")
     (not (or (bolp)
+             (memq (char-before) '(?\[ ?\())
              (and (memq (char-before)
-                        '(?\; ?- ?+ ?* ?/ ?: ?. ?, ?\[ ?\( ?\{ ?\\ ?& ?> ?< ?% ?~ ?^))
-                  ;; Make sure it's not the end of a regexp.
-                  (not (eq (car (syntax-after (1- (point)))) 7)))
+                        '(?\; ?- ?+ ?* ?/ ?: ?. ?, ?\\ ?& ?> ?< ?% ?~ ?^))
+                  ;; Not a binary operator symbol.
+                  (not (eq (char-before (1- (point))) ?:))
+                  ;; Not the end of a regexp or a percent literal.
+                  (not (memq (car (syntax-after (1- (point)))) '(7 15))))
              (and (eq (char-before) ?\?)
                   (equal (save-excursion (ruby-smie--backward-token)) "?"))
              (and (eq (char-before) ?=)
+                  ;; Not a symbol :==, :!=, or a foo= method.
                   (string-match "\\`\\s." (save-excursion
                                             (ruby-smie--backward-token))))
+             (and (eq (char-before) ?|)
+                  (member (save-excursion (ruby-smie--backward-token))
+                          '("|" "||")))
              (and (eq (car (syntax-after (1- (point)))) 2)
                   (member (save-excursion (ruby-smie--backward-token))
                           '("iuwu-mod" "and" "or")))
@@ -384,6 +460,12 @@ explicitly declared in magic comment."
     (or (eq ?\{ (char-before))
         (looking-back "\\_<do" (- (point) 2)))))
 
+(defun ruby-smie--closing-pipe-p ()
+  (save-excursion
+    (if (eq ?| (char-before)) (forward-char -1))
+    (and (re-search-backward "|" (line-beginning-position) t)
+         (ruby-smie--opening-pipe-p))))
+
 (defun ruby-smie--args-separator-p (pos)
   (and
    (< pos (line-end-position))
@@ -395,11 +477,11 @@ explicitly declared in magic comment."
    (save-excursion
      (goto-char pos)
      (or (and (eq (char-syntax (char-after)) ?w)
-              (not (looking-at (regexp-opt '("unless" "if" "while" "until"
-                                             "else" "elsif" "do" "end" "and" "or")
+              (not (looking-at (regexp-opt '("unless" "if" "while" "until" "or"
+                                             "else" "elsif" "do" "end" "and")
                                            'symbols))))
-         (memq (syntax-after pos) '(7 15))
-         (looking-at "[([]\\|[-+!~:]\\sw")))))
+         (memq (car (syntax-after pos)) '(7 15))
+         (looking-at "[([]\\|[-+!~]\\sw\\|:\\(?:\\sw\\|\\s.\\)")))))
 
 (defun ruby-smie--at-dot-call ()
   (and (eq ?w (char-syntax (following-char)))
@@ -410,12 +492,10 @@ explicitly declared in magic comment."
   (let ((pos (point)))
     (skip-chars-forward " \t")
     (cond
-     ((looking-at "\\s\"") ;A heredoc or a string.
-      (if (not (looking-at "\n"))
-          ""
-        ;; Tokenize the whole heredoc as semicolon.
-        (goto-char (scan-sexps (point) 1))
-        ";"))
+     ((and (looking-at "\n") (looking-at "\\s\""))  ;A heredoc.
+      ;; Tokenize the whole heredoc as semicolon.
+      (goto-char (scan-sexps (point) 1))
+      ";")
      ((and (looking-at "[\n#]")
            (ruby-smie--implicit-semi-p)) ;Only add implicit ; when needed.
       (if (eolp) (forward-char 1) (forward-comment 1))
@@ -423,12 +503,13 @@ explicitly declared in magic comment."
      (t
       (forward-comment (point-max))
       (cond
-       ((looking-at ":\\s.+")
-        (goto-char (match-end 0)) (match-string 0)) ;; bug#15208.
        ((and (< pos (point))
              (save-excursion
                (ruby-smie--args-separator-p (prog1 (point) (goto-char pos)))))
         " @ ")
+       ((looking-at ":\\s.+")
+        (goto-char (match-end 0)) (match-string 0)) ;bug#15208.
+       ((looking-at "\\s\"") "")                    ;A string.
        (t
         (let ((dot (ruby-smie--at-dot-call))
               (tok (smie-default-forward-token)))
@@ -441,7 +522,10 @@ explicitly declared in magic comment."
            ((string-match-p "\\`|[*&]?\\'" tok)
             (forward-char (- 1 (length tok)))
             (setq tok "|")
-            (if (ruby-smie--opening-pipe-p) "opening-|" tok))
+            (cond
+             ((ruby-smie--opening-pipe-p) "opening-|")
+             ((ruby-smie--closing-pipe-p) "closing-|")
+             (t tok)))
            ((and (equal tok "") (looking-at "\\\\\n"))
             (goto-char (match-end 0)) (ruby-smie--forward-token))
            ((equal tok "do")
@@ -481,7 +565,10 @@ explicitly declared in magic comment."
           (if (ruby-smie--bosp)
               tok "iuwu-mod"))
          ((equal tok "|")
-          (if (ruby-smie--opening-pipe-p) "opening-|" tok))
+          (cond
+           ((ruby-smie--opening-pipe-p) "opening-|")
+           ((ruby-smie--closing-pipe-p) "closing-|")
+           (t tok)))
          ((string-match-p "\\`|[*&]\\'" tok)
           (forward-char 1)
           (substring tok 1))
@@ -502,6 +589,10 @@ explicitly declared in magic comment."
     (smie-backward-sexp ";")
     (cons 'column (smie-indent-virtual))))
 
+(defun ruby-smie--indent-to-stmt-p (keyword)
+  (or (eq t ruby-align-to-stmt-keywords)
+      (memq (intern keyword) ruby-align-to-stmt-keywords)))
+
 (defun ruby-smie-rules (kind token)
   (pcase (cons kind token)
     (`(:elem . basic) ruby-indent-level)
@@ -530,25 +621,75 @@ explicitly declared in magic comment."
        (ruby-smie--indent-to-stmt))
       ((smie-rule-hanging-p)
        ;; Treat purely syntactic block-constructs as being part of their parent,
-       ;; when the opening statement is hanging.
-       (let ((state (smie-backward-sexp 'halfsexp)))
-         (when (eq t (car state)) (goto-char (cadr state))))
-       (cons 'column  (smie-indent-virtual)))))
-    (`(:after . " @ ") (smie-rule-parent))
+       ;; when the opening token is hanging and the parent is not an
+       ;; open-paren.
+       (cond
+        ((eq (car (smie-indent--parent)) t) nil)
+        ;; When after `.', let's always de-indent,
+        ;; because when `.' is inside the line, the
+        ;; additional indentation from it looks out of place.
+        ((smie-rule-parent-p ".")
+         (let (smie--parent)
+           (save-excursion
+             ;; Traverse up the parents until the parent is "." at
+             ;; indentation, or any other token.
+             (while (and (let ((parent (smie-indent--parent)))
+                           (goto-char (cadr parent))
+                           (save-excursion
+                             (unless (integerp (car parent)) (forward-char -1))
+                             (not (ruby-smie--bosp))))
+                         (progn
+                           (setq smie--parent nil)
+                           (smie-rule-parent-p "."))))
+             (smie-rule-parent))))
+        (t (smie-rule-parent))))))
+    (`(:after . ,(or `"(" "[" "{"))
+     ;; FIXME: Shouldn't this be the default behavior of
+     ;; `smie-indent-after-keyword'?
+     (save-excursion
+       (forward-char 1)
+       (skip-chars-forward " \t")
+       ;; `smie-rule-hanging-p' is not good enough here,
+       ;; because we want to reject hanging tokens at bol, too.
+       (unless (or (eolp) (forward-comment 1))
+         (cons 'column (current-column)))))
+    (`(:before . " @ ")
+     (save-excursion
+       (skip-chars-forward " \t")
+       (cons 'column (current-column))))
     (`(:before . "do") (ruby-smie--indent-to-stmt))
-    (`(,(or :before :after) . ".")
-     (unless (smie-rule-parent-p ".")
-       (smie-rule-parent ruby-indent-level)))
-    (`(:before . ,(or `"else" `"then" `"elsif" `"rescue" `"ensure")) 0)
-    (`(:before . ,(or `"when"))
-     (if (not (smie-rule-sibling-p)) 0)) ;; ruby-indent-level
+    (`(:before . ".")
+     (if (smie-rule-sibling-p)
+         (and ruby-align-chained-calls 0)
+       ruby-indent-level))
+    (`(:before . ,(or `"else" `"then" `"elsif" `"rescue" `"ensure"))
+     (smie-rule-parent))
+    (`(:before . "when")
+     ;; Align to the previous `when', but look up the virtual
+     ;; indentation of `case'.
+     (if (smie-rule-sibling-p) 0 (smie-rule-parent)))
     (`(:after . ,(or "=" "iuwu-mod" "+" "-" "*" "/" "&&" "||" "%" "**" "^" "&"
                      "<=>" ">" "<" ">=" "<=" "==" "===" "!=" "<<" ">>"
-                     "+=" "-=" "*=" "/=" "%=" "**=" "&=" "|=" "^="
+                     "+=" "-=" "*=" "/=" "%=" "**=" "&=" "|=" "^=" "|"
                      "<<=" ">>=" "&&=" "||=" "and" "or"))
-     (if (smie-rule-parent-p ";" nil) ruby-indent-level))
+     (and (smie-rule-parent-p ";" nil)
+          (smie-indent--hanging-p)
+          ruby-indent-level))
+    (`(:after . ,(or "?" ":")) ruby-indent-level)
+    (`(:before . ,(guard (memq (intern-soft token) ruby-alignable-keywords)))
+     (when (not (ruby--at-indentation-p))
+       (if (ruby-smie--indent-to-stmt-p token)
+           (ruby-smie--indent-to-stmt)
+         (cons 'column (current-column)))))
     ))
 
+(defun ruby--at-indentation-p (&optional point)
+  (save-excursion
+    (unless point (setq point (point)))
+    (forward-line 0)
+    (skip-chars-forward " \t")
+    (eq (point) point)))
+
 (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)
@@ -592,11 +733,16 @@ explicitly declared in magic comment."
   (nreverse (ruby-imenu-create-index-in-block nil (point-min) nil)))
 
 (defun ruby-accurate-end-of-block (&optional end)
-  "TODO: document."
+  "Jump to the end of the current block or END, whichever is closer."
   (let (state
         (end (or end (point-max))))
-    (while (and (setq state (apply 'ruby-parse-partial end state))
-                (>= (nth 2 state) 0) (< (point) end)))))
+    (if ruby-use-smie
+        (save-restriction
+          (back-to-indentation)
+          (narrow-to-region (point) end)
+          (smie-forward-sexp))
+      (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."
@@ -606,7 +752,6 @@ explicitly declared in magic comment."
                   :forward-token  #'ruby-smie--forward-token
                   :backward-token #'ruby-smie--backward-token)
     (setq-local indent-line-function 'ruby-indent-line))
-  (setq-local require-final-newline t)
   (setq-local comment-start "# ")
   (setq-local comment-end "")
   (setq-local comment-column ruby-comment-column)
@@ -617,53 +762,86 @@ explicitly declared in magic comment."
   (setq-local paragraph-separate paragraph-start)
   (setq-local paragraph-ignore-fill-prefix t))
 
+(defun ruby--insert-coding-comment (encoding)
+  "Insert a magic coding comment for ENCODING.
+The style of the comment is controlled by `ruby-encoding-magic-comment-style'."
+  (let ((encoding-magic-comment-template
+         (pcase ruby-encoding-magic-comment-style
+           (`ruby "# coding: %s")
+           (`emacs "# -*- coding: %s -*-")
+           (`custom
+            ruby-custom-encoding-magic-comment-template))))
+    (insert
+     (format encoding-magic-comment-template encoding)
+     "\n")))
+
+(defun ruby--detect-encoding ()
+  (if (eq ruby-insert-encoding-magic-comment 'always-utf8)
+      "utf-8"
+    (let ((coding-system
+           (or save-buffer-coding-system
+               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))))
+      (if coding-system
+          (symbol-name
+           (if ruby-use-encoding-map
+               (let ((elt (assq coding-system ruby-encoding-map)))
+                 (if elt (cdr elt) coding-system))
+             coding-system))
+        "ascii-8bit"))))
+
+(defun ruby--encoding-comment-required-p ()
+  (or (eq ruby-insert-encoding-magic-comment 'always-utf8)
+      (re-search-forward "[^\0-\177]" nil 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)
+    (when (ruby--encoding-comment-required-p)
       (goto-char (point-min))
-      (let ((coding-system
-             (or save-buffer-coding-system
-                 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
-                   (if ruby-use-encoding-map
-                       (let ((elt (assq coding-system ruby-encoding-map)))
-                         (if elt (cdr elt) coding-system))
-                     coding-system))
-                "ascii-8bit"))
+      (let ((coding-system (ruby--detect-encoding)))
         (when coding-system
           (if (looking-at "^#!") (beginning-of-line 2))
-          (cond ((looking-at "\\s *#.*-\*-\\s *\\(en\\)?coding\\s *:\\s *\\([-a-z0-9_]*\\)\\s *\\(;\\|-\*-\\)")
+          (cond ((looking-at "\\s *#\\s *.*\\(en\\)?coding\\s *:\\s *\\([-a-z0-9_]*\\)")
+                 ;; update existing encoding comment if necessary
                  (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
-                     (let ((encoding-magic-comment-template
-                            (case ruby-encoding-magic-comment-style
-                              ('ruby "# coding: %s")
-                              ('emacs "# -*- coding: %s -*-")
-                              ('custom ruby-custom-encoding-magic-comment-template))))
-                      (insert
-                       (format encoding-magic-comment-template coding-system)
-                       "\n")))))
+                     (ruby--insert-coding-comment coding-system))))
           (when (buffer-modified-p)
             (basic-save-buffer-1)))))))
 
+(defvar ruby--electric-indent-chars '(?. ?\) ?} ?\]))
+
+(defun ruby--electric-indent-p (char)
+  (cond
+   ((memq char ruby--electric-indent-chars)
+    ;; Reindent after typing a char affecting indentation.
+    (ruby--at-indentation-p (1- (point))))
+   ((memq (char-after) ruby--electric-indent-chars)
+    ;; Reindent after inserting something in front of the above.
+    (ruby--at-indentation-p (1- (point))))
+   ((or (and (>= char ?a) (<= char ?z)) (memq char '(?_ ?? ?! ?:)))
+    (let ((pt (point)))
+      (save-excursion
+        (skip-chars-backward "[:alpha:]:_?!")
+        (and (ruby--at-indentation-p)
+             (looking-at (regexp-opt (cons "end" ruby-block-mid-keywords)))
+             ;; Outdent after typing a keyword.
+             (or (eq (match-end 0) pt)
+                 ;; Reindent if it wasn't a keyword after all.
+                 (eq (match-end 0) (1- pt)))))))))
+
+;; FIXME: Remove this?  It's unused here, but some redefinitions of
+;; `ruby-calculate-indent' in user init files still call it.
 (defun ruby-current-indentation ()
   "Return the indentation level of current line."
   (save-excursion
@@ -750,7 +928,7 @@ Can be one of `heredoc', `modifier', `expr-qstr', `expr-re'."
                                         ruby-block-mid-keywords)
                                 'words))
                    (goto-char (match-end 0))
-                   (not (looking-at "\\s_\\|!")))
+                   (not (looking-at "\\s_")))
                   ((eq option 'expr-qstr)
                    (looking-at "[a-zA-Z][a-zA-z0-9_]* +%[^ \t]"))
                   ((eq option 'expr-re)
@@ -758,11 +936,28 @@ Can be one of `heredoc', `modifier', `expr-qstr', `expr-re'."
                   (t nil)))))))))
 
 (defun ruby-forward-string (term &optional end no-error expand)
-  "TODO: document."
+  "Move forward across one balanced pair of string delimiters.
+Skips escaped delimiters. If EXPAND is non-nil, also ignores
+delimiters in interpolated strings.
+
+TERM should be a string containing either a single, self-matching
+delimiter (e.g. \"/\"), or a pair of matching delimiters with the
+close delimiter first (e.g. \"][\").
+
+When non-nil, search is bounded by position END.
+
+Throws an error if a balanced match is not found, unless NO-ERROR
+is non-nil, in which case nil will be returned.
+
+This command assumes the character after point is an opening
+delimiter."
   (let ((n 1) (c (string-to-char term))
-        (re (if expand
-                (concat "[^\\]\\(\\\\\\\\\\)*\\([" term "]\\|\\(#{\\)\\)")
-              (concat "[^\\]\\(\\\\\\\\\\)*[" term "]"))))
+        (re (concat "[^\\]\\(\\\\\\\\\\)*\\("
+                    (if (string= term "^") ;[^] is not a valid regexp
+                        "\\^"
+                      (concat "[" term "]"))
+                    (when expand "\\|\\(#{\\)")
+                    "\\)")))
     (while (and (re-search-forward re end no-error)
                 (if (match-beginning 3)
                     (ruby-forward-string "}{" end no-error nil)
@@ -797,7 +992,8 @@ Can be one of `heredoc', `modifier', `expr-qstr', `expr-re'."
        ((looking-at "[\"`]")            ;skip string
         (cond
          ((and (not (eobp))
-               (ruby-forward-string (buffer-substring (point) (1+ (point))) end t t))
+               (ruby-forward-string (buffer-substring (point) (1+ (point)))
+                                    end t t))
           nil)
          (t
           (setq in-string (point))
@@ -1104,7 +1300,8 @@ Can be one of `heredoc', `modifier', `expr-qstr', `expr-re'."
             (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))
+                            (and (setq state (ruby-parse-region
+                                              parse-start end))
                                  (nth 0 state))))
               (setq end nil))
             (goto-char (or end pos))
@@ -1115,7 +1312,8 @@ Can be one of `heredoc', `modifier', `expr-qstr', `expr-re'."
           (and
            (or (and (looking-at ruby-symbol-re)
                     (skip-chars-backward ruby-symbol-chars)
-                    (looking-at (concat "\\<\\(" ruby-block-hanging-re "\\)\\>"))
+                    (looking-at (concat "\\<\\(" ruby-block-hanging-re
+                                        "\\)\\>"))
                     (not (eq (point) (nth 3 state)))
                     (save-excursion
                       (goto-char (match-end 0))
@@ -1159,7 +1357,8 @@ Can be one of `heredoc', `modifier', `expr-qstr', `expr-re'."
                  (cond
                   ((and
                     (null op-end)
-                    (not (looking-at (concat "\\<\\(" ruby-block-hanging-re "\\)\\>")))
+                    (not (looking-at (concat "\\<\\(" ruby-block-hanging-re
+                                             "\\)\\>")))
                     (eq (ruby-deep-indent-paren-p t) 'space)
                     (not (bobp)))
                    (widen)
@@ -1296,7 +1495,8 @@ With ARG, do it many times.  Negative ARG means move backward."
                      (skip-chars-forward ",.:;|&^~=!?\\+\\-\\*")
                      (looking-at "\\s("))
                    (goto-char (scan-sexps (point) 1)))
-                  ((and (looking-at (concat "\\<\\(" ruby-block-beg-re "\\)\\>"))
+                  ((and (looking-at (concat "\\<\\(" ruby-block-beg-re
+                                            "\\)\\>"))
                         (not (eq (char-before (point)) ?.))
                         (not (eq (char-before (point)) ?:)))
                    (ruby-end-of-block)
@@ -1313,7 +1513,8 @@ With ARG, do it many times.  Negative ARG means move backward."
                          (progn
                            (setq expr (or expr (ruby-expr-beg)
                                           (looking-at "%\\sw?\\Sw\\|[\"'`/]")))
-                           (nth 1 (setq state (apply 'ruby-parse-partial nil state))))
+                           (nth 1 (setq state (apply #'ruby-parse-partial
+                                                     nil state))))
                        (setq expr t)
                        (skip-chars-forward "<"))
                      (not expr))))
@@ -1337,10 +1538,11 @@ With ARG, do it many times.  Negative ARG means move forward."
             (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))))
+                   (pcase (char-before)
+                     (`?% (forward-char -1))
+                     ((or `?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)))))
@@ -1354,13 +1556,14 @@ With ARG, do it many times.  Negative ARG means move forward."
                   (t
                    (forward-char 1)
                    (while (progn (forward-word -1)
-                                 (case (char-before)
-                                   (?_ t)
-                                   (?. (forward-char -1) t)
-                                   ((?$ ?@)
+                                 (pcase (char-before)
+                                   (`?_ t)
+                                   (`?. (forward-char -1) t)
+                                   ((or `?$ `?@)
                                     (forward-char -1)
-                                    (and (eq (char-before) (char-after)) (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)
@@ -1551,8 +1754,9 @@ If the result is do-end block, it will always be multiline."
   (let ((start (point)) beg end)
     (end-of-line)
     (unless
-        (if (and (re-search-backward "\\({\\)\\|\\_<do\\(\\s \\|$\\||\\)")
+        (if (and (re-search-backward "\\(?:[^#]\\)\\({\\)\\|\\(\\_<do\\_>\\)")
                  (progn
+                   (goto-char (or (match-beginning 1) (match-beginning 2)))
                    (setq beg (point))
                    (save-match-data (ruby-forward-sexp))
                    (setq end (point))
@@ -1562,6 +1766,43 @@ If the result is do-end block, it will always be multiline."
               (ruby-do-end-to-brace beg end)))
       (goto-char start))))
 
+(defun ruby--string-region ()
+  "Return region for string at point."
+  (let ((state (syntax-ppss)))
+    (when (memq (nth 3 state) '(?' ?\"))
+      (save-excursion
+        (goto-char (nth 8 state))
+        (forward-sexp)
+        (list (nth 8 state) (point))))))
+
+(defun ruby-string-at-point-p ()
+  "Check if cursor is at a string or not."
+  (ruby--string-region))
+
+(defun ruby--inverse-string-quote (string-quote)
+  "Get the inverse string quoting for STRING-QUOTE."
+  (if (equal string-quote "\"") "'" "\""))
+
+(defun ruby-toggle-string-quotes ()
+  "Toggle string literal quoting between single and double."
+  (interactive)
+  (when (ruby-string-at-point-p)
+    (let* ((region (ruby--string-region))
+           (min (nth 0 region))
+           (max (nth 1 region))
+           (string-quote (ruby--inverse-string-quote (buffer-substring-no-properties min (1+ min))))
+           (content
+            (buffer-substring-no-properties (1+ min) (1- max))))
+      (setq content
+            (if (equal string-quote "\"")
+                (replace-regexp-in-string "\\\\\"" "\"" (replace-regexp-in-string "\\([^\\\\]\\)'" "\\1\\\\'" content))
+              (replace-regexp-in-string "\\\\\'" "'" (replace-regexp-in-string "\\([^\\\\]\\)\"" "\\1\\\\\"" content))))
+      (let ((orig-point (point)))
+        (delete-region min max)
+        (insert
+         (format "%s%s%s" string-quote content string-quote))
+        (goto-char orig-point)))))
+
 (eval-and-compile
   (defconst ruby-percent-literal-beg-re
     "\\(%\\)[qQrswWxIi]?\\([[:punct:]]\\)"
@@ -1602,10 +1843,20 @@ It will be properly highlighted even when the call omits parens.")
       ;; $' $" $` .... are variables.
       ;; ?' ?" ?` are character literals (one-char strings in 1.9+).
       ("\\([?$]\\)[#\"'`]"
-       (1 (unless (save-excursion
-                    ;; Not within a string.
-                    (nth 3 (syntax-ppss (match-beginning 0))))
+       (1 (if (save-excursion
+                (nth 3 (syntax-ppss (match-beginning 0))))
+              ;; Within a string, skip.
+              (goto-char (match-end 1))
             (string-to-syntax "\\"))))
+      ;; Part of symbol when at the end of a method name.
+      ("[!?]"
+       (0 (unless (save-excursion
+                    (or (nth 8 (syntax-ppss (match-beginning 0)))
+                        (eq (char-before) ?:)
+                        (let (parse-sexp-lookup-properties)
+                          (zerop (skip-syntax-backward "w_")))
+                        (memq (preceding-char) '(?@ ?$))))
+            (string-to-syntax "_"))))
       ;; Regular expressions.  Start with matching unescaped slash.
       ("\\(?:\\=\\|[^\\]\\)\\(?:\\\\\\\\\\)*\\(/\\)"
        (1 (let ((state (save-excursion (syntax-ppss (match-beginning 1)))))
@@ -1770,153 +2021,192 @@ See `font-lock-syntax-table'.")
 (defconst ruby-font-lock-keyword-beg-re "\\(?:^\\|[^.@$]\\|\\.\\.\\)")
 
 (defconst ruby-font-lock-keywords
-  (list
-   ;; functions
-   '("^\\s *def\\s +\\(?:[^( \t\n.]*\\.\\)?\\([^( \t\n]+\\)"
+  `(;; Functions.
+    ("^\\s *def\\s +\\(?:[^( \t\n.]*\\.\\)?\\([^( \t\n]+\\)"
      1 font-lock-function-name-face)
-   ;; keywords
-   (list (concat
-          ruby-font-lock-keyword-beg-re
-          (regexp-opt
-           '("alias"
-             "and"
-             "begin"
-             "break"
-             "case"
-             "class"
-             "def"
-             "defined?"
-             "do"
-             "elsif"
-             "else"
-             "fail"
-             "ensure"
-             "for"
-             "end"
-             "if"
-             "in"
-             "module"
-             "next"
-             "not"
-             "or"
-             "redo"
-             "rescue"
-             "retry"
-             "return"
-             "then"
-             "super"
-             "unless"
-             "undef"
-             "until"
-             "when"
-             "while"
-             "yield")
-           'symbols))
-         1 'font-lock-keyword-face)
-   ;; some core methods
-   (list (concat
-          ruby-font-lock-keyword-beg-re
-          (regexp-opt
-           '(;; built-in methods on Kernel
-             "__callee__"
-             "__dir__"
-             "__method__"
-             "abort"
-             "at_exit"
-             "autoload"
-             "autoload?"
-             "binding"
-             "block_given?"
-             "caller"
-             "catch"
-             "eval"
-             "exec"
-             "exit"
-             "exit!"
-             "fail"
-             "fork"
-             "format"
-             "lambda"
-             "load"
-             "loop"
-             "open"
-             "p"
-             "print"
-             "printf"
-             "proc"
-             "putc"
-             "puts"
-             "raise"
-             "rand"
-             "readline"
-             "readlines"
-             "require"
-             "require_relative"
-             "sleep"
-             "spawn"
-             "sprintf"
-             "srand"
-             "syscall"
-             "system"
-             "throw"
-             "trap"
-             "warn"
-             ;; keyword-like private methods on Module
-             "alias_method"
-             "attr"
-             "attr_accessor"
-             "attr_reader"
-             "attr_writer"
-             "define_method"
-             "extend"
-             "include"
-             "module_function"
-             "prepend"
-             "private"
-             "protected"
-             "public"
-             "refine"
-             "using")
-           'symbols))
-         1 'font-lock-builtin-face)
-   ;; here-doc beginnings
-   `(,ruby-here-doc-beg-re 0 (unless (ruby-singleton-class-p (match-beginning 0))
-                               'font-lock-string-face))
-   ;; Perl-ish keywords
-   "\\_<\\(?:BEGIN\\|END\\)\\_>\\|^__END__$"
-   ;; variables
-   `(,(concat ruby-font-lock-keyword-beg-re
-              "\\_<\\(nil\\|self\\|true\\|false\\)\\>")
+    ;; Keywords.
+    (,(concat
+       ruby-font-lock-keyword-beg-re
+       (regexp-opt
+        '("alias"
+          "and"
+          "begin"
+          "break"
+          "case"
+          "class"
+          "def"
+          "defined?"
+          "do"
+          "elsif"
+          "else"
+          "fail"
+          "ensure"
+          "for"
+          "end"
+          "if"
+          "in"
+          "module"
+          "next"
+          "not"
+          "or"
+          "redo"
+          "rescue"
+          "retry"
+          "return"
+          "then"
+          "super"
+          "unless"
+          "undef"
+          "until"
+          "when"
+          "while"
+          "yield")
+        'symbols))
+     (1 font-lock-keyword-face))
+    ;; Core methods that have required arguments.
+    (,(concat
+       ruby-font-lock-keyword-beg-re
+       (regexp-opt
+        '( ;; built-in methods on Kernel
+          "at_exit"
+          "autoload"
+          "autoload?"
+          "catch"
+          "eval"
+          "exec"
+          "fork"
+          "format"
+          "lambda"
+          "load"
+          "loop"
+          "open"
+          "p"
+          "print"
+          "printf"
+          "proc"
+          "putc"
+          "puts"
+          "require"
+          "require_relative"
+          "spawn"
+          "sprintf"
+          "syscall"
+          "system"
+          "trap"
+          "warn"
+          ;; keyword-like private methods on Module
+          "alias_method"
+          "attr"
+          "attr_accessor"
+          "attr_reader"
+          "attr_writer"
+          "define_method"
+          "extend"
+          "include"
+          "module_function"
+          "prepend"
+          "private_class_method"
+          "private_constant"
+          "public_class_method"
+          "public_constant"
+          "refine"
+          "using")
+        'symbols))
+     (1 (unless (looking-at " *\\(?:[]|,.)}=]\\|$\\)")
+          font-lock-builtin-face)))
+    ;; Kernel methods that have no required arguments.
+    (,(concat
+       ruby-font-lock-keyword-beg-re
+       (regexp-opt
+        '("__callee__"
+          "__dir__"
+          "__method__"
+          "abort"
+          "at_exit"
+          "binding"
+          "block_given?"
+          "caller"
+          "exit"
+          "exit!"
+          "fail"
+          "private"
+          "protected"
+          "public"
+          "raise"
+          "rand"
+          "readline"
+          "readlines"
+          "sleep"
+          "srand"
+          "throw")
+        'symbols))
+     (1 font-lock-builtin-face))
+    ;; Here-doc beginnings.
+    (,ruby-here-doc-beg-re
+     (0 (unless (ruby-singleton-class-p (match-beginning 0))
+          'font-lock-string-face)))
+    ;; Perl-ish keywords.
+    "\\_<\\(?:BEGIN\\|END\\)\\_>\\|^__END__$"
+    ;; Variables.
+    (,(concat ruby-font-lock-keyword-beg-re
+              "\\_<\\(nil\\|self\\|true\\|false\\)\\_>")
      1 font-lock-variable-name-face)
-   ;; keywords that evaluate to certain values
-   '("\\_<__\\(?:LINE\\|ENCODING\\|FILE\\)__\\_>" 0 font-lock-variable-name-face)
-   ;; symbols
-   '("\\(^\\|[^:]\\)\\(:\\([-+~]@?\\|[/%&|^`]\\|\\*\\*?\\|<\\(<\\|=>?\\)?\\|>[>=]?\\|===?\\|=~\\|![~=]?\\|\\[\\]=?\\|@?\\(\\w\\|_\\)+\\([!?=]\\|\\b_*\\)\\|#{[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\)\\)"
+    ;; Keywords that evaluate to certain values.
+    ("\\_<__\\(?:LINE\\|ENCODING\\|FILE\\)__\\_>"
+     (0 font-lock-builtin-face))
+    ;; Symbols.
+    ("\\(^\\|[^:]\\)\\(:\\([-+~]@?\\|[/%&|^`]\\|\\*\\*?\\|<\\(<\\|=>?\\)?\\|>[>=]?\\|===?\\|=~\\|![~=]?\\|\\[\\]=?\\|@?\\(\\w\\|_\\)+\\([!?=]\\|\\b_*\\)\\|#{[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\)\\)"
      2 font-lock-constant-face)
-   ;; variables
-   '("\\(\\$\\([^a-zA-Z0-9 \n]\\|[0-9]\\)\\)\\W"
-     1 font-lock-variable-name-face)
-   '("\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+"
+    ;; Special globals.
+    (,(concat "\\$\\(?:[:\"!@;,/\\._><\\$?~=*&`'+0-9]\\|-[0adFiIlpvw]\\|"
+              (regexp-opt '("LOAD_PATH" "LOADED_FEATURES" "PROGRAM_NAME"
+                            "ERROR_INFO" "ERROR_POSITION"
+                            "FS" "FIELD_SEPARATOR"
+                            "OFS" "OUTPUT_FIELD_SEPARATOR"
+                            "RS" "INPUT_RECORD_SEPARATOR"
+                            "ORS" "OUTPUT_RECORD_SEPARATOR"
+                            "NR" "INPUT_LINE_NUMBER"
+                            "LAST_READ_LINE" "DEFAULT_OUTPUT" "DEFAULT_INPUT"
+                            "PID" "PROCESS_ID" "CHILD_STATUS"
+                            "LAST_MATCH_INFO" "IGNORECASE"
+                            "ARGV" "MATCH" "PREMATCH" "POSTMATCH"
+                            "LAST_PAREN_MATCH" "stdin" "stdout" "stderr"
+                            "DEBUG" "FILENAME" "VERBOSE" "SAFE" "CLASSPATH"
+                            "JRUBY_VERSION" "JRUBY_REVISION" "ENV_JAVA"))
+              "\\_>\\)")
+     0 font-lock-builtin-face)
+    ("\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+"
      0 font-lock-variable-name-face)
-   ;; constants
-   '("\\(?:\\_<\\|::\\)\\([A-Z]+\\(\\w\\|_\\)*\\)"
+    ;; Constants.
+    ("\\(?:\\_<\\|::\\)\\([A-Z]+\\(\\w\\|_\\)*\\)"
      1 (unless (eq ?\( (char-after)) font-lock-type-face))
-   '("\\(^\\s *\\|[\[\{\(,]\\s *\\|\\sw\\s +\\)\\(\\(\\sw\\|_\\)+\\):[^:]" 2 font-lock-constant-face)
-   ;; conversion methods on Kernel
-   (list (concat ruby-font-lock-keyword-beg-re
-                 (regexp-opt '("Array" "Complex" "Float" "Hash"
-                               "Integer" "Rational" "String") 'symbols))
-         1 font-lock-builtin-face)
-   ;; expression expansion
-   '(ruby-match-expression-expansion
+    ("\\(^\\s *\\|[\[\{\(,]\\s *\\|\\sw\\s +\\)\\(\\(\\sw\\|_\\)+\\):[^:]"
+     (2 font-lock-constant-face))
+    ;; Conversion methods on Kernel.
+    (,(concat ruby-font-lock-keyword-beg-re
+              (regexp-opt '("Array" "Complex" "Float" "Hash"
+                            "Integer" "Rational" "String") 'symbols))
+     (1 font-lock-builtin-face))
+    ;; Expression expansion.
+    (ruby-match-expression-expansion
      2 font-lock-variable-name-face t)
-   ;; negation char
-   '("[^[:alnum:]_]\\(!\\)[^=]"
+    ;; Negation char.
+    ("\\(?:^\\|[^[:alnum:]_]\\)\\(!+\\)[^=~]"
      1 font-lock-negation-char-face)
-   ;; character literals
-   ;; FIXME: Support longer escape sequences.
-   '("\\_<\\?\\\\?\\S " 0 font-lock-string-face)
-   )
+    ;; Character literals.
+    ;; FIXME: Support longer escape sequences.
+    ("\\_<\\?\\\\?\\S " 0 font-lock-string-face)
+    ;; Regexp options.
+    ("\\(?:\\s|\\|/\\)\\([imxo]+\\)"
+     1 (when (save-excursion
+               (let ((state (syntax-ppss (match-beginning 0))))
+                 (and (nth 3 state)
+                      (or (eq (char-after) ?/)
+                          (progn
+                            (goto-char (nth 8 state))
+                            (looking-at "%r"))))))
+         font-lock-preprocessor-face))
+    )
   "Additional expressions to highlight in Ruby mode.")
 
 (defun ruby-match-expression-expansion (limit)
@@ -1942,8 +2232,7 @@ See `font-lock-syntax-table'.")
   (setq-local end-of-defun-function 'ruby-end-of-defun)
 
   (add-hook 'after-save-hook 'ruby-mode-set-encoding nil 'local)
-
-  (setq-local electric-indent-chars (append '(?\{ ?\}) electric-indent-chars))
+  (add-hook 'electric-indent-functions 'ruby--electric-indent-p nil 'local)
 
   (setq-local font-lock-defaults '((ruby-font-lock-keywords) nil nil))
   (setq-local font-lock-keywords ruby-font-lock-keywords)
@@ -1957,10 +2246,11 @@ See `font-lock-syntax-table'.")
 (add-to-list 'auto-mode-alist
              (cons (purecopy (concat "\\(?:\\."
                                      "rb\\|ru\\|rake\\|thor"
-                                     "\\|jbuilder\\|gemspec"
+                                     "\\|jbuilder\\|rabl\\|gemspec\\|podspec"
                                      "\\|/"
                                      "\\(?:Gem\\|Rake\\|Cap\\|Thor"
-                                     "Vagrant\\|Guard\\)file"
+                                     "\\|Puppet\\|Berks"
+                                     "\\|Vagrant\\|Guard\\|Pod\\)file"
                                      "\\)\\'")) 'ruby-mode))
 
 ;;;###autoload