;;; ruby-mode.el --- Major mode for editing Ruby files
-;; Copyright (C) 1994-2014 Free Software Foundation, Inc.
+;; Copyright (C) 1994-2015 Free Software Foundation, Inc.
;; Authors: Yukihiro Matsumoto
;; Nobuyoshi Nakada
"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.
(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.")
["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
:group 'ruby
:safe 'integerp)
-(defcustom ruby-align-to-stmt-keywords nil
+(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
Only has effect when `ruby-use-smie' is t.
"
- :type '(choice
+ :type `(choice
(const :tag "None" nil)
(const :tag "All" t)
(repeat :tag "User defined"
- (choice (const if)
- (const while)
- (const unless)
- (const until)
- (const begin)
- (const case)
- (const for))))
+ (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'.
;; 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")
(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"))
(nonassoc ">" ">=" "<" "<=")
(nonassoc "==" "===" "!=")
(nonassoc "=~" "!~")
- (left "<<" ">>"))))))
+ (left "<<" ">>")
+ (right "."))))))
(defun ruby-smie--bosp ()
(save-excursion (skip-chars-backward " \t")
(save-excursion
(skip-chars-backward " \t")
(not (or (bolp)
+ (memq (char-before) '(?\[ ?\())
(and (memq (char-before)
- '(?\; ?- ?+ ?* ?/ ?: ?. ?, ?\[ ?\( ?\\ ?& ?> ?< ?%
- ?~ ?^))
+ '(?\; ?- ?+ ?* ?/ ?: ?. ?, ?\\ ?& ?> ?< ?% ?~ ?^))
+ ;; 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) ?|)
;; 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 ".") (smie-rule-parent (- ruby-indent-level)))
+ ((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
;; 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))
- (`(:before . ".") ruby-indent-level)
- (`(:after . "=>") 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")
(smie-indent--hanging-p)
ruby-indent-level))
(`(:after . ,(or "?" ":")) ruby-indent-level)
- (`(:before . ,(or "if" "while" "unless" "until" "begin" "case" "for"))
+ (`(: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)
: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)
(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:]]\\)"
;; $' $" $` .... 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) '(?@ ?$))))
"yield")
'symbols))
(1 font-lock-keyword-face))
- ;; Some core methods.
+ ;; Core methods that have required arguments.
(,(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"
"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
"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"
- "refine"
- "using")
+ "raise"
+ "rand"
+ "readline"
+ "readlines"
+ "sleep"
+ "srand"
+ "throw")
'symbols))
(1 font-lock-builtin-face))
;; Here-doc beginnings.
1 font-lock-variable-name-face)
;; Keywords that evaluate to certain values.
("\\_<__\\(?:LINE\\|ENCODING\\|FILE\\)__\\_>"
- (0 font-lock-variable-name-face))
+ (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)
+ ;; 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.
(ruby-match-expression-expansion
2 font-lock-variable-name-face t)
;; Negation char.
- ("[^[:alnum:]_]\\(!\\)[^=]"
+ ("\\(?:^\\|[^[:alnum:]_]\\)\\(!+\\)[^=~]"
1 font-lock-negation-char-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.")
(add-to-list 'auto-mode-alist
(cons (purecopy (concat "\\(?:\\."
"rb\\|ru\\|rake\\|thor"
- "\\|jbuilder\\|gemspec\\|podspec"
+ "\\|jbuilder\\|rabl\\|gemspec\\|podspec"
"\\|/"
"\\(?:Gem\\|Rake\\|Cap\\|Thor"
- "Vagrant\\|Guard\\|Pod\\)file"
+ "\\|Puppet\\|Berks"
+ "\\|Vagrant\\|Guard\\|Pod\\)file"
"\\)\\'")) 'ruby-mode))
;;;###autoload