Fix bug#17097
[bpt/emacs.git] / lisp / progmodes / ruby-mode.el
CommitLineData
c7fbbc3c
CY
1;;; ruby-mode.el --- Major mode for editing Ruby files
2
ba318903 3;; Copyright (C) 1994-2014 Free Software Foundation, Inc.
c7fbbc3c 4
c5220417
GM
5;; Authors: Yukihiro Matsumoto
6;; Nobuyoshi Nakada
c7fbbc3c
CY
7;; URL: http://www.emacswiki.org/cgi-bin/wiki/RubyMode
8;; Created: Fri Feb 4 14:49:13 JST 1994
9;; Keywords: languages ruby
ea0857a1 10;; Version: 1.2
c7fbbc3c
CY
11
12;; This file is part of GNU Emacs.
13
14;; GNU Emacs is free software: you can redistribute it and/or modify
15;; it under the terms of the GNU General Public License as published by
16;; the Free Software Foundation, either version 3 of the License, or
17;; (at your option) any later version.
18
19;; GNU Emacs is distributed in the hope that it will be useful,
20;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22;; GNU General Public License for more details.
23
24;; You should have received a copy of the GNU General Public License
25;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
26
27;;; Commentary:
28
29;; Provides font-locking, indentation support, and navigation for Ruby code.
30;;
31;; If you're installing manually, you should add this to your .emacs
32;; file after putting it on your load path:
33;;
34;; (autoload 'ruby-mode "ruby-mode" "Major mode for ruby files" t)
35;; (add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))
36;; (add-to-list 'interpreter-mode-alist '("ruby" . ruby-mode))
37;;
38;; Still needs more docstrings; search below for TODO.
39
40;;; Code:
41
44a41a47
KR
42(defgroup ruby nil
43 "Major mode for editing Ruby code."
44 :prefix "ruby-"
45 :group 'languages)
46
c7fbbc3c
CY
47(defconst ruby-block-beg-keywords
48 '("class" "module" "def" "if" "unless" "case" "while" "until" "for" "begin" "do")
49 "Keywords at the beginning of blocks.")
50
51(defconst ruby-block-beg-re
52 (regexp-opt ruby-block-beg-keywords)
53 "Regexp to match the beginning of blocks.")
54
55(defconst ruby-non-block-do-re
ac72c08d 56 (regexp-opt '("while" "until" "for" "rescue") 'symbols)
c7fbbc3c
CY
57 "Regexp to match keywords that nest without blocks.")
58
59(defconst ruby-indent-beg-re
db590ef6
DG
60 (concat "^\\(\\s *" (regexp-opt '("class" "module" "def")) "\\|"
61 (regexp-opt '("if" "unless" "case" "while" "until" "for" "begin"))
62 "\\)\\_>")
c7fbbc3c
CY
63 "Regexp to match where the indentation gets deeper.")
64
65(defconst ruby-modifier-beg-keywords
66 '("if" "unless" "while" "until")
67 "Modifiers that are the same as the beginning of blocks.")
68
69(defconst ruby-modifier-beg-re
70 (regexp-opt ruby-modifier-beg-keywords)
71 "Regexp to match modifiers same as the beginning of blocks.")
72
73(defconst ruby-modifier-re
74 (regexp-opt (cons "rescue" ruby-modifier-beg-keywords))
75 "Regexp to match modifiers.")
76
77(defconst ruby-block-mid-keywords
78 '("then" "else" "elsif" "when" "rescue" "ensure")
79 "Keywords where the indentation gets shallower in middle of block statements.")
80
81(defconst ruby-block-mid-re
82 (regexp-opt ruby-block-mid-keywords)
83 "Regexp to match where the indentation gets shallower in middle of block statements.")
84
85(defconst ruby-block-op-keywords
86 '("and" "or" "not")
87 "Regexp to match boolean keywords.")
88
89(defconst ruby-block-hanging-re
90 (regexp-opt (append ruby-modifier-beg-keywords ruby-block-op-keywords))
91 "Regexp to match hanging block modifiers.")
92
39675016 93(defconst ruby-block-end-re "\\_<end\\_>")
c7fbbc3c 94
8f06acce
DG
95(defconst ruby-defun-beg-re
96 '"\\(def\\|class\\|module\\)"
97 "Regexp to match the beginning of a defun, in the general sense.")
98
bb808526
DG
99(defconst ruby-singleton-class-re
100 "class\\s *<<"
101 "Regexp to match the beginning of a singleton class context.")
102
cf38dd42
SM
103(eval-and-compile
104 (defconst ruby-here-doc-beg-re
c7fbbc3c 105 "\\(<\\)<\\(-\\)?\\(\\([a-zA-Z0-9_]+\\)\\|[\"]\\([^\"]+\\)[\"]\\|[']\\([^']+\\)[']\\)"
c62792e7
DG
106 "Regexp to match the beginning of a heredoc.")
107
108 (defconst ruby-expression-expansion-re
c2d6c639 109 "\\(?:[^\\]\\|\\=\\)\\(\\\\\\\\\\)*\\(#\\({[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\|\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+\\|\\$[^a-zA-Z \n]\\)\\)"))
c7fbbc3c
CY
110
111(defun ruby-here-doc-end-match ()
112 "Return a regexp to find the end of a heredoc.
113
114This should only be called after matching against `ruby-here-doc-beg-re'."
115 (concat "^"
116 (if (match-string 2) "[ \t]*" nil)
117 (regexp-quote
118 (or (match-string 4)
119 (match-string 5)
120 (match-string 6)))))
121
c7fbbc3c 122(defconst ruby-delimiter
39675016 123 (concat "[?$/%(){}#\"'`.:]\\|<<\\|\\[\\|\\]\\|\\_<\\("
c7fbbc3c 124 ruby-block-beg-re
39675016 125 "\\)\\_>\\|" ruby-block-end-re
c7fbbc3c
CY
126 "\\|^=begin\\|" ruby-here-doc-beg-re))
127
128(defconst ruby-negative
129 (concat "^[ \t]*\\(\\(" ruby-block-mid-re "\\)\\>\\|"
130 ruby-block-end-re "\\|}\\|\\]\\)")
131 "Regexp to match where the indentation gets shallower.")
132
578c21bc 133(defconst ruby-operator-re "[-,.+*/%&|^~=<>:]\\|\\\\$"
c7fbbc3c
CY
134 "Regexp to match operators.")
135
136(defconst ruby-symbol-chars "a-zA-Z0-9_"
137 "List of characters that symbol names may contain.")
88527bc0 138
c7fbbc3c
CY
139(defconst ruby-symbol-re (concat "[" ruby-symbol-chars "]")
140 "Regexp to match symbols.")
141
34d1a133 142(defvar ruby-use-smie t)
a9e4425b 143
c7fbbc3c
CY
144(defvar ruby-mode-map
145 (let ((map (make-sparse-keymap)))
a9e4425b
SM
146 (unless ruby-use-smie
147 (define-key map (kbd "M-C-b") 'ruby-backward-sexp)
148 (define-key map (kbd "M-C-f") 'ruby-forward-sexp)
149 (define-key map (kbd "M-C-q") 'ruby-indent-exp))
8c1ae481
DG
150 (when ruby-use-smie
151 (define-key map (kbd "M-C-d") 'smie-down-list))
c7fbbc3c
CY
152 (define-key map (kbd "M-C-p") 'ruby-beginning-of-block)
153 (define-key map (kbd "M-C-n") 'ruby-end-of-block)
0d9e2599 154 (define-key map (kbd "C-c {") 'ruby-toggle-block)
c7fbbc3c 155 map)
ee61fe97 156 "Keymap used in Ruby mode.")
c7fbbc3c 157
f73754c9
JA
158(easy-menu-define
159 ruby-mode-menu
160 ruby-mode-map
161 "Ruby Mode Menu"
162 '("Ruby"
963ce636
DG
163 ["Beginning of Block" ruby-beginning-of-block t]
164 ["End of Block" ruby-end-of-block t]
f73754c9
JA
165 ["Toggle Block" ruby-toggle-block t]
166 "--"
167 ["Backward Sexp" ruby-backward-sexp
963ce636
DG
168 :visible (not ruby-use-smie)]
169 ["Backward Sexp" backward-sexp
170 :visible ruby-use-smie]
f73754c9 171 ["Forward Sexp" ruby-forward-sexp
963ce636
DG
172 :visible (not ruby-use-smie)]
173 ["Forward Sexp" forward-sexp
174 :visible ruby-use-smie]
175 ["Indent Sexp" ruby-indent-exp
176 :visible (not ruby-use-smie)]
177 ["Indent Sexp" prog-indent-sexp
178 :visible ruby-use-smie]))
f73754c9 179
c7fbbc3c
CY
180(defvar ruby-mode-syntax-table
181 (let ((table (make-syntax-table)))
182 (modify-syntax-entry ?\' "\"" table)
183 (modify-syntax-entry ?\" "\"" table)
184 (modify-syntax-entry ?\` "\"" table)
185 (modify-syntax-entry ?# "<" table)
186 (modify-syntax-entry ?\n ">" table)
187 (modify-syntax-entry ?\\ "\\" table)
188 (modify-syntax-entry ?$ "." table)
c7fbbc3c 189 (modify-syntax-entry ?_ "_" table)
39675016 190 (modify-syntax-entry ?: "_" table)
c7fbbc3c
CY
191 (modify-syntax-entry ?< "." table)
192 (modify-syntax-entry ?> "." table)
193 (modify-syntax-entry ?& "." table)
194 (modify-syntax-entry ?| "." table)
195 (modify-syntax-entry ?% "." table)
196 (modify-syntax-entry ?= "." table)
197 (modify-syntax-entry ?/ "." table)
198 (modify-syntax-entry ?+ "." table)
199 (modify-syntax-entry ?* "." table)
200 (modify-syntax-entry ?- "." table)
201 (modify-syntax-entry ?\; "." table)
202 (modify-syntax-entry ?\( "()" table)
203 (modify-syntax-entry ?\) ")(" table)
204 (modify-syntax-entry ?\{ "(}" table)
205 (modify-syntax-entry ?\} "){" table)
206 (modify-syntax-entry ?\[ "(]" table)
207 (modify-syntax-entry ?\] ")[" table)
208 table)
ee61fe97 209 "Syntax table to use in Ruby mode.")
c7fbbc3c
CY
210
211(defcustom ruby-indent-tabs-mode nil
ee61fe97 212 "Indentation can insert tabs in Ruby mode if this is non-nil."
983d0df5
BB
213 :type 'boolean
214 :group 'ruby
215 :safe 'booleanp)
c7fbbc3c
CY
216
217(defcustom ruby-indent-level 2
ee61fe97 218 "Indentation of Ruby statements."
983d0df5
BB
219 :type 'integer
220 :group 'ruby
221 :safe 'integerp)
c7fbbc3c 222
34d1a133 223(defcustom ruby-comment-column (default-value 'comment-column)
c7fbbc3c 224 "Indentation column of comments."
983d0df5
BB
225 :type 'integer
226 :group 'ruby
227 :safe 'integerp)
c7fbbc3c 228
fb3d479c
DG
229(defconst ruby-alignable-keywords '(if while unless until begin case for def)
230 "Keywords that can be used in `ruby-align-to-stmt-keywords'.")
231
232(defcustom ruby-align-to-stmt-keywords '(def)
65a1da00
DG
233 "Keywords after which we align the expression body to statement.
234
b520f210
DG
235When nil, an expression that begins with one these keywords is
236indented to the column of the keyword. Example:
237
238 tee = if foo
239 bar
240 else
241 qux
242 end
243
244If this value is t or contains a symbol with the name of given
245keyword, the expression is indented to align to the beginning of
246the statement:
247
248 tee = if foo
249 bar
250 else
251 qux
252 end
253
254Only has effect when `ruby-use-smie' is t.
255"
fb3d479c 256 :type `(choice
b520f210
DG
257 (const :tag "None" nil)
258 (const :tag "All" t)
259 (repeat :tag "User defined"
fb3d479c
DG
260 (choice ,@(mapcar
261 (lambda (kw) (list 'const kw))
262 ruby-alignable-keywords))))
b520f210
DG
263 :group 'ruby
264 :safe 'listp
265 :version "24.4")
266
e2a67bd0 267(defcustom ruby-align-chained-calls nil
2f313daf
DG
268 "If non-nil, align chained method calls.
269
270Each method call on a separate line will be aligned to the column
271of its parent.
e2a67bd0
DG
272
273Only has effect when `ruby-use-smie' is t."
274 :type 'boolean
275 :group 'ruby
2f313daf
DG
276 :safe 'booleanp
277 :version "24.4")
e2a67bd0 278
c7fbbc3c
CY
279(defcustom ruby-deep-arglist t
280 "Deep indent lists in parenthesis when non-nil.
b520f210
DG
281Also ignores spaces after parenthesis when `space'.
282Only has effect when `ruby-use-smie' is nil."
983d0df5
BB
283 :type 'boolean
284 :group 'ruby
285 :safe 'booleanp)
c7fbbc3c 286
9c5a5c77 287;; FIXME Woefully under documented. What is the point of the last `t'?.
c7fbbc3c 288(defcustom ruby-deep-indent-paren '(?\( ?\[ ?\] t)
ee61fe97
JB
289 "Deep indent lists in parenthesis when non-nil.
290The value t means continuous line.
b520f210
DG
291Also ignores spaces after parenthesis when `space'.
292Only has effect when `ruby-use-smie' is nil."
9c5a5c77
GM
293 :type '(choice (const nil)
294 character
295 (repeat (choice character
296 (cons character (choice (const nil)
297 (const t)))
298 (const t) ; why?
299 )))
c7fbbc3c
CY
300 :group 'ruby)
301
302(defcustom ruby-deep-indent-paren-style 'space
b520f210
DG
303 "Default deep indent style.
304Only has effect when `ruby-use-smie' is nil."
9c5a5c77
GM
305 :type '(choice (const t) (const nil) (const space))
306 :group 'ruby)
c7fbbc3c 307
e70181b8
AM
308(defcustom ruby-encoding-map
309 '((us-ascii . nil) ;; Do not put coding: us-ascii
310 (shift-jis . cp932) ;; Emacs charset name of Shift_JIS
311 (shift_jis . cp932) ;; MIME charset name of Shift_JIS
312 (japanese-cp932 . cp932)) ;; Emacs charset name of CP932
313 "Alist to map encoding name from Emacs to Ruby.
314Associating an encoding name with nil means it needs not be
315explicitly declared in magic comment."
316 :type '(repeat (cons (symbol :tag "From") (symbol :tag "To")))
c7fbbc3c
CY
317 :group 'ruby)
318
319(defcustom ruby-insert-encoding-magic-comment t
6c1bf086
BB
320 "Insert a magic Ruby encoding comment upon save if this is non-nil.
321The encoding will be auto-detected. The format of the encoding comment
322is customizable via `ruby-encoding-magic-comment-style'.
323
324When set to `always-utf8' an utf-8 comment will always be added,
325even if it's not required."
c7fbbc3c
CY
326 :type 'boolean :group 'ruby)
327
71731c03
BB
328(defcustom ruby-encoding-magic-comment-style 'ruby
329 "The style of the magic encoding comment to use."
330 :type '(choice
331 (const :tag "Emacs Style" emacs)
332 (const :tag "Ruby Style" ruby)
333 (const :tag "Custom Style" custom))
15ba2182
BB
334 :group 'ruby
335 :version "24.4")
71731c03 336
638af3a1 337(defcustom ruby-custom-encoding-magic-comment-template "# encoding: %s"
d19ffd64
BB
338 "A custom encoding comment template.
339It is used when `ruby-encoding-magic-comment-style' is set to `custom'."
71731c03 340 :type 'string
15ba2182
BB
341 :group 'ruby
342 :version "24.4")
71731c03 343
c7fbbc3c
CY
344(defcustom ruby-use-encoding-map t
345 "Use `ruby-encoding-map' to set encoding magic comment if this is non-nil."
346 :type 'boolean :group 'ruby)
347
a9e4425b
SM
348;;; SMIE support
349
350(require 'smie)
351
cfef16c0
DG
352;; Here's a simplified BNF grammar, for reference:
353;; http://www.cse.buffalo.edu/~regan/cse305/RubyBNF.pdf
a9e4425b 354(defconst ruby-smie-grammar
a9e4425b 355 (smie-prec2->grammar
cfef16c0
DG
356 (smie-merge-prec2s
357 (smie-bnf->prec2
358 '((id)
359 (insts (inst) (insts ";" insts))
1f923923
DG
360 (inst (exp) (inst "iuwu-mod" exp)
361 ;; Somewhat incorrect (both can be used multiple times),
362 ;; but avoids lots of conflicts:
363 (exp "and" exp) (exp "or" exp))
cfef16c0 364 (exp (exp1) (exp "," exp) (exp "=" exp)
e2a67bd0 365 (id " @ " exp))
cfef16c0 366 (exp1 (exp2) (exp2 "?" exp1 ":" exp1))
e2a67bd0
DG
367 (exp2 (exp3) (exp3 "." exp2))
368 (exp3 ("def" insts "end")
cfef16c0
DG
369 ("begin" insts-rescue-insts "end")
370 ("do" insts "end")
371 ("class" insts "end") ("module" insts "end")
372 ("for" for-body "end")
373 ("[" expseq "]")
374 ("{" hashvals "}")
375 ("{" insts "}")
376 ("while" insts "end")
377 ("until" insts "end")
378 ("unless" insts "end")
379 ("if" if-body "end")
380 ("case" cases "end"))
7b08f97e 381 (formal-params ("opening-|" exp "closing-|"))
cfef16c0
DG
382 (for-body (for-head ";" insts))
383 (for-head (id "in" exp))
7b08f97e 384 (cases (exp "then" insts)
cfef16c0
DG
385 (cases "when" cases) (insts "else" insts))
386 (expseq (exp) );;(expseq "," expseq)
387 (hashvals (id "=>" exp1) (hashvals "," hashvals))
388 (insts-rescue-insts (insts)
389 (insts-rescue-insts "rescue" insts-rescue-insts)
390 (insts-rescue-insts "ensure" insts-rescue-insts))
391 (itheni (insts) (exp "then" insts))
392 (ielsei (itheni) (itheni "else" insts))
393 (if-body (ielsei) (if-body "elsif" if-body)))
394 '((nonassoc "in") (assoc ";") (right " @ ")
e2a67bd0 395 (assoc ",") (right "="))
cfef16c0
DG
396 '((assoc "when"))
397 '((assoc "elsif"))
398 '((assoc "rescue" "ensure"))
399 '((assoc ",")))
400
401 (smie-precs->prec2
402 '((right "=")
403 (right "+=" "-=" "*=" "/=" "%=" "**=" "&=" "|=" "^="
404 "<<=" ">>=" "&&=" "||=")
405 (left ".." "...")
406 (left "+" "-")
407 (left "*" "/" "%" "**")
1f923923 408 (left "&&" "||")
7b08f97e 409 (left "^" "&" "|")
cfef16c0
DG
410 (nonassoc "<=>")
411 (nonassoc ">" ">=" "<" "<=")
412 (nonassoc "==" "===" "!=")
413 (nonassoc "=~" "!~")
e2a67bd0
DG
414 (left "<<" ">>")
415 (right "."))))))
a9e4425b
SM
416
417(defun ruby-smie--bosp ()
418 (save-excursion (skip-chars-backward " \t")
c8c605ac 419 (or (bolp) (memq (char-before) '(?\; ?=)))))
a9e4425b
SM
420
421(defun ruby-smie--implicit-semi-p ()
422 (save-excursion
423 (skip-chars-backward " \t")
424 (not (or (bolp)
a09beb3d 425 (memq (char-before) '(?\[ ?\())
34d1a133 426 (and (memq (char-before)
a09beb3d
DG
427 '(?\; ?- ?+ ?* ?/ ?: ?. ?, ?\\ ?& ?> ?< ?% ?~ ?^))
428 ;; Not a binary operator symbol.
429 (not (eq (char-before (1- (point))) ?:))
8212d9c0
DG
430 ;; Not the end of a regexp or a percent literal.
431 (not (memq (car (syntax-after (1- (point)))) '(7 15))))
b68e2926
DG
432 (and (eq (char-before) ?\?)
433 (equal (save-excursion (ruby-smie--backward-token)) "?"))
434 (and (eq (char-before) ?=)
a09beb3d 435 ;; Not a symbol :==, :!=, or a foo= method.
b68e2926
DG
436 (string-match "\\`\\s." (save-excursion
437 (ruby-smie--backward-token))))
1629a329 438 (and (eq (char-before) ?|)
7b08f97e
DG
439 (member (save-excursion (ruby-smie--backward-token))
440 '("|" "||")))
238150c8 441 (and (eq (car (syntax-after (1- (point)))) 2)
1d1c86da
DG
442 (member (save-excursion (ruby-smie--backward-token))
443 '("iuwu-mod" "and" "or")))
0ea1599d
DG
444 (save-excursion
445 (forward-comment 1)
446 (eq (char-after) ?.))))))
f069bba8 447
7ccae3b1
SM
448(defun ruby-smie--redundant-do-p (&optional skip)
449 (save-excursion
450 (if skip (backward-word 1))
b68e2926 451 (member (nth 2 (smie-backward-sexp ";")) '("while" "until" "for"))))
7ccae3b1 452
f069bba8
SM
453(defun ruby-smie--opening-pipe-p ()
454 (save-excursion
455 (if (eq ?| (char-before)) (forward-char -1))
456 (skip-chars-backward " \t\n")
457 (or (eq ?\{ (char-before))
458 (looking-back "\\_<do" (- (point) 2)))))
a9e4425b 459
7b08f97e
DG
460(defun ruby-smie--closing-pipe-p ()
461 (save-excursion
462 (if (eq ?| (char-before)) (forward-char -1))
463 (and (re-search-backward "|" (line-beginning-position) t)
464 (ruby-smie--opening-pipe-p))))
465
650fa7bf
SM
466(defun ruby-smie--args-separator-p (pos)
467 (and
8c1ae481 468 (< pos (line-end-position))
1eda1d8d 469 (or (eq (char-syntax (preceding-char)) '?w)
bae91342
DG
470 ;; FIXME: Check that the preceding token is not a keyword.
471 ;; This isn't very important most of the time, though.
1eda1d8d
DG
472 (and (memq (preceding-char) '(?! ??))
473 (eq (char-syntax (char-before (1- (point)))) '?w)))
1d1c86da
DG
474 (save-excursion
475 (goto-char pos)
476 (or (and (eq (char-syntax (char-after)) ?w)
ad16897c
SM
477 (not (looking-at (regexp-opt '("unless" "if" "while" "until" "or"
478 "else" "elsif" "do" "end" "and")
1d1c86da 479 'symbols))))
7e1549c9
DG
480 (memq (car (syntax-after pos)) '(7 15))
481 (looking-at "[([]\\|[-+!~]\\sw\\|:\\(?:\\sw\\|\\s.\\)")))))
650fa7bf 482
18cacc39 483(defun ruby-smie--at-dot-call ()
ee4282cd 484 (and (eq ?w (char-syntax (following-char)))
18cacc39
DG
485 (eq (char-before) ?.)
486 (not (eq (char-before (1- (point))) ?.))))
7ccae3b1 487
a9e4425b 488(defun ruby-smie--forward-token ()
650fa7bf
SM
489 (let ((pos (point)))
490 (skip-chars-forward " \t")
491 (cond
7e1549c9
DG
492 ((and (looking-at "\n") (looking-at "\\s\"")) ;A heredoc.
493 ;; Tokenize the whole heredoc as semicolon.
494 (goto-char (scan-sexps (point) 1))
495 ";")
650fa7bf
SM
496 ((and (looking-at "[\n#]")
497 (ruby-smie--implicit-semi-p)) ;Only add implicit ; when needed.
498 (if (eolp) (forward-char 1) (forward-comment 1))
499 ";")
500 (t
501 (forward-comment (point-max))
502 (cond
650fa7bf
SM
503 ((and (< pos (point))
504 (save-excursion
505 (ruby-smie--args-separator-p (prog1 (point) (goto-char pos)))))
506 " @ ")
7e1549c9
DG
507 ((looking-at ":\\s.+")
508 (goto-char (match-end 0)) (match-string 0)) ;bug#15208.
509 ((looking-at "\\s\"") "") ;A string.
650fa7bf 510 (t
18cacc39
DG
511 (let ((dot (ruby-smie--at-dot-call))
512 (tok (smie-default-forward-token)))
513 (when dot
514 (setq tok (concat "." tok)))
7ccae3b1 515 (cond
650fa7bf
SM
516 ((member tok '("unless" "if" "while" "until"))
517 (if (save-excursion (forward-word -1) (ruby-smie--bosp))
518 tok "iuwu-mod"))
ffa2df72 519 ((string-match-p "\\`|[*&]?\\'" tok)
1f923923
DG
520 (forward-char (- 1 (length tok)))
521 (setq tok "|")
7b08f97e
DG
522 (cond
523 ((ruby-smie--opening-pipe-p) "opening-|")
524 ((ruby-smie--closing-pipe-p) "closing-|")
525 (t tok)))
650fa7bf
SM
526 ((and (equal tok "") (looking-at "\\\\\n"))
527 (goto-char (match-end 0)) (ruby-smie--forward-token))
528 ((equal tok "do")
529 (cond
530 ((not (ruby-smie--redundant-do-p 'skip)) tok)
531 ((> (save-excursion (forward-comment (point-max)) (point))
532 (line-end-position))
533 (ruby-smie--forward-token)) ;Fully redundant.
534 (t ";")))
650fa7bf 535 (t tok)))))))))
a9e4425b
SM
536
537(defun ruby-smie--backward-token ()
538 (let ((pos (point)))
539 (forward-comment (- (point)))
34d1a133
SM
540 (cond
541 ((and (> pos (line-end-position)) (ruby-smie--implicit-semi-p))
542 (skip-chars-forward " \t") ";")
dca01b09
DG
543 ((and (bolp) (not (bobp))) ;Presumably a heredoc.
544 ;; Tokenize the whole heredoc as semicolon.
545 (goto-char (scan-sexps (point) -1))
546 ";")
650fa7bf
SM
547 ((and (> pos (point)) (not (bolp))
548 (ruby-smie--args-separator-p pos))
549 ;; We have "ID SPC ID", which is a method call, but it binds less tightly
550 ;; than commas, since a method call can also be "ID ARG1, ARG2, ARG3".
551 ;; In some textbooks, "e1 @ e2" is used to mean "call e1 with arg e2".
552 " @ ")
34d1a133 553 (t
18cacc39
DG
554 (let ((tok (smie-default-backward-token))
555 (dot (ruby-smie--at-dot-call)))
556 (when dot
238150c8 557 (setq tok (concat "." tok)))
f069bba8
SM
558 (when (and (eq ?: (char-before)) (string-match "\\`\\s." tok))
559 (forward-char -1) (setq tok (concat ":" tok))) ;; bug#15208.
a9e4425b
SM
560 (cond
561 ((member tok '("unless" "if" "while" "until"))
562 (if (ruby-smie--bosp)
563 tok "iuwu-mod"))
f069bba8 564 ((equal tok "|")
7b08f97e
DG
565 (cond
566 ((ruby-smie--opening-pipe-p) "opening-|")
567 ((ruby-smie--closing-pipe-p) "closing-|")
568 (t tok)))
ffa2df72 569 ((string-match-p "\\`|[*&]\\'" tok)
1f923923
DG
570 (forward-char 1)
571 (substring tok 1))
34d1a133
SM
572 ((and (equal tok "") (eq ?\\ (char-before)) (looking-at "\n"))
573 (forward-char -1) (ruby-smie--backward-token))
7ccae3b1
SM
574 ((equal tok "do")
575 (cond
576 ((not (ruby-smie--redundant-do-p)) tok)
577 ((> (save-excursion (forward-word 1)
578 (forward-comment (point-max)) (point))
579 (line-end-position))
580 (ruby-smie--backward-token)) ;Fully redundant.
581 (t ";")))
34d1a133 582 (t tok)))))))
a9e4425b 583
1f923923
DG
584(defun ruby-smie--indent-to-stmt ()
585 (save-excursion
da3b328d
DG
586 (smie-backward-sexp ";")
587 (cons 'column (smie-indent-virtual))))
1f923923 588
b520f210
DG
589(defun ruby-smie--indent-to-stmt-p (keyword)
590 (or (eq t ruby-align-to-stmt-keywords)
591 (memq (intern keyword) ruby-align-to-stmt-keywords)))
592
a9e4425b
SM
593(defun ruby-smie-rules (kind token)
594 (pcase (cons kind token)
595 (`(:elem . basic) ruby-indent-level)
34d1a133
SM
596 ;; "foo" "bar" is the concatenation of the two strings, so the second
597 ;; should be aligned with the first.
598 (`(:elem . args) (if (looking-at "\\s\"") 0))
599 ;; (`(:after . ",") (smie-rule-separator kind))
7790a270 600 (`(:before . ";")
8c1ae481
DG
601 (cond
602 ((smie-rule-parent-p "def" "begin" "do" "class" "module" "for"
603 "while" "until" "unless"
604 "if" "then" "elsif" "else" "when"
369bbf71 605 "rescue" "ensure" "{")
8c1ae481 606 (smie-rule-parent ruby-indent-level))
8c1ae481
DG
607 ;; For (invalid) code between switch and case.
608 ;; (if (smie-parent-p "switch") 4)
bc4aaa31 609 ))
34d1a133 610 (`(:before . ,(or `"(" `"[" `"{"))
8c1ae481
DG
611 (cond
612 ((and (equal token "{")
1f923923
DG
613 (not (smie-rule-prev-p "(" "{" "[" "," "=>" "=" "return" ";"))
614 (save-excursion
615 (forward-comment -1)
616 (not (eq (preceding-char) ?:))))
8c1ae481 617 ;; Curly block opener.
1f923923 618 (ruby-smie--indent-to-stmt))
8c1ae481
DG
619 ((smie-rule-hanging-p)
620 ;; Treat purely syntactic block-constructs as being part of their parent,
7e1549c9
DG
621 ;; when the opening token is hanging and the parent is not an
622 ;; open-paren.
623 (cond
624 ((eq (car (smie-indent--parent)) t) nil)
625 ;; When after `.', let's always de-indent,
626 ;; because when `.' is inside the line, the
627 ;; additional indentation from it looks out of place.
e2a67bd0
DG
628 ((smie-rule-parent-p ".")
629 (let (smie--parent)
630 (save-excursion
631 ;; Traverse up the parents until the parent is "." at
632 ;; indentation, or any other token.
dfdb365c
DG
633 (while (and (let ((parent (smie-indent--parent)))
634 (goto-char (cadr parent))
635 (save-excursion
636 (unless (integerp (car parent)) (forward-char -1))
637 (not (ruby-smie--bosp))))
e2a67bd0
DG
638 (progn
639 (setq smie--parent nil)
640 (smie-rule-parent-p "."))))
641 (smie-rule-parent))))
7e1549c9 642 (t (smie-rule-parent))))))
276bc333
DG
643 (`(:after . ,(or `"(" "[" "{"))
644 ;; FIXME: Shouldn't this be the default behavior of
645 ;; `smie-indent-after-keyword'?
646 (save-excursion
647 (forward-char 1)
648 (skip-chars-forward " \t")
649 ;; `smie-rule-hanging-p' is not good enough here,
660efa1a 650 ;; because we want to reject hanging tokens at bol, too.
276bc333
DG
651 (unless (or (eolp) (forward-comment 1))
652 (cons 'column (current-column)))))
1fc8f655
DG
653 (`(:before . " @ ")
654 (save-excursion
655 (skip-chars-forward " \t")
656 (cons 'column (current-column))))
1f923923 657 (`(:before . "do") (ruby-smie--indent-to-stmt))
e2a67bd0
DG
658 (`(:before . ".")
659 (if (smie-rule-sibling-p)
660 (and ruby-align-chained-calls 0)
661 ruby-indent-level))
ce41edb4
DG
662 (`(:before . ,(or `"else" `"then" `"elsif" `"rescue" `"ensure"))
663 (smie-rule-parent))
664 (`(:before . "when")
b520f210
DG
665 ;; Align to the previous `when', but look up the virtual
666 ;; indentation of `case'.
667 (if (smie-rule-sibling-p) 0 (smie-rule-parent)))
1d1c86da
DG
668 (`(:after . ,(or "=" "iuwu-mod" "+" "-" "*" "/" "&&" "||" "%" "**" "^" "&"
669 "<=>" ">" "<" ">=" "<=" "==" "===" "!=" "<<" ">>"
7b08f97e 670 "+=" "-=" "*=" "/=" "%=" "**=" "&=" "|=" "^=" "|"
1d1c86da 671 "<<=" ">>=" "&&=" "||=" "and" "or"))
7e1549c9
DG
672 (and (smie-rule-parent-p ";" nil)
673 (smie-indent--hanging-p)
674 ruby-indent-level))
35b249a6 675 (`(:after . ,(or "?" ":")) ruby-indent-level)
fb3d479c 676 (`(:before . ,(guard (memq (intern-soft token) ruby-alignable-keywords)))
6cddebc1 677 (when (not (ruby--at-indentation-p))
b520f210
DG
678 (if (ruby-smie--indent-to-stmt-p token)
679 (ruby-smie--indent-to-stmt)
680 (cons 'column (current-column)))))
7ccae3b1 681 ))
a9e4425b 682
6cddebc1
DG
683(defun ruby--at-indentation-p (&optional point)
684 (save-excursion
685 (unless point (setq point (point)))
686 (forward-line 0)
687 (skip-chars-forward " \t")
688 (eq (point) point)))
689
c7fbbc3c
CY
690(defun ruby-imenu-create-index-in-block (prefix beg end)
691 "Create an imenu index of methods inside a block."
692 (let ((index-alist '()) (case-fold-search nil)
693 name next pos decl sing)
694 (goto-char beg)
695 (while (re-search-forward "^\\s *\\(\\(class\\s +\\|\\(class\\s *<<\\s *\\)\\|module\\s +\\)\\([^\(<\n ]+\\)\\|\\(def\\|alias\\)\\s +\\([^\(\n ]+\\)\\)" end t)
696 (setq sing (match-beginning 3))
697 (setq decl (match-string 5))
698 (setq next (match-end 0))
699 (setq name (or (match-string 4) (match-string 6)))
700 (setq pos (match-beginning 0))
701 (cond
702 ((string= "alias" decl)
703 (if prefix (setq name (concat prefix name)))
704 (push (cons name pos) index-alist))
705 ((string= "def" decl)
706 (if prefix
707 (setq name
708 (cond
709 ((string-match "^self\." name)
710 (concat (substring prefix 0 -1) (substring name 4)))
711 (t (concat prefix name)))))
712 (push (cons name pos) index-alist)
713 (ruby-accurate-end-of-block end))
714 (t
715 (if (string= "self" name)
716 (if prefix (setq name (substring prefix 0 -1)))
717 (if prefix (setq name (concat (substring prefix 0 -1) "::" name)))
718 (push (cons name pos) index-alist))
719 (ruby-accurate-end-of-block end)
720 (setq beg (point))
721 (setq index-alist
722 (nconc (ruby-imenu-create-index-in-block
723 (concat name (if sing "." "#"))
724 next beg) index-alist))
725 (goto-char beg))))
726 index-alist))
727
728(defun ruby-imenu-create-index ()
729 "Create an imenu index of all methods in the buffer."
730 (nreverse (ruby-imenu-create-index-in-block nil (point-min) nil)))
731
732(defun ruby-accurate-end-of-block (&optional end)
ff8c9764 733 "Jump to the end of the current block or END, whichever is closer."
c7fbbc3c
CY
734 (let (state
735 (end (or end (point-max))))
ff8c9764
DG
736 (if ruby-use-smie
737 (save-restriction
738 (back-to-indentation)
739 (narrow-to-region (point) end)
740 (smie-forward-sexp))
741 (while (and (setq state (apply 'ruby-parse-partial end state))
742 (>= (nth 2 state) 0) (< (point) end))))))
c7fbbc3c
CY
743
744(defun ruby-mode-variables ()
ee61fe97 745 "Set up initial buffer-local variables for Ruby mode."
c7fbbc3c 746 (setq indent-tabs-mode ruby-indent-tabs-mode)
a9e4425b
SM
747 (if ruby-use-smie
748 (smie-setup ruby-smie-grammar #'ruby-smie-rules
749 :forward-token #'ruby-smie--forward-token
750 :backward-token #'ruby-smie--backward-token)
a3996a2e
BB
751 (setq-local indent-line-function 'ruby-indent-line))
752 (setq-local require-final-newline t)
753 (setq-local comment-start "# ")
754 (setq-local comment-end "")
755 (setq-local comment-column ruby-comment-column)
756 (setq-local comment-start-skip "#+ *")
757 (setq-local parse-sexp-ignore-comments t)
758 (setq-local parse-sexp-lookup-properties t)
759 (setq-local paragraph-start (concat "$\\|" page-delimiter))
760 (setq-local paragraph-separate paragraph-start)
761 (setq-local paragraph-ignore-fill-prefix t))
c7fbbc3c 762
6c1bf086
BB
763(defun ruby--insert-coding-comment (encoding)
764 "Insert a magic coding comment for ENCODING.
765The style of the comment is controlled by `ruby-encoding-magic-comment-style'."
766 (let ((encoding-magic-comment-template
767 (pcase ruby-encoding-magic-comment-style
768 (`ruby "# coding: %s")
769 (`emacs "# -*- coding: %s -*-")
770 (`custom
771 ruby-custom-encoding-magic-comment-template))))
772 (insert
773 (format encoding-magic-comment-template encoding)
774 "\n")))
775
776(defun ruby--detect-encoding ()
777 (if (eq ruby-insert-encoding-magic-comment 'always-utf8)
778 "utf-8"
779 (let ((coding-system
780 (or save-buffer-coding-system
781 buffer-file-coding-system)))
782 (if coding-system
783 (setq coding-system
784 (or (coding-system-get coding-system 'mime-charset)
785 (coding-system-change-eol-conversion coding-system nil))))
786 (if coding-system
787 (symbol-name
788 (if ruby-use-encoding-map
789 (let ((elt (assq coding-system ruby-encoding-map)))
790 (if elt (cdr elt) coding-system))
791 coding-system))
792 "ascii-8bit"))))
793
794(defun ruby--encoding-comment-required-p ()
795 (or (eq ruby-insert-encoding-magic-comment 'always-utf8)
796 (re-search-forward "[^\0-\177]" nil t)))
797
c7fbbc3c
CY
798(defun ruby-mode-set-encoding ()
799 "Insert a magic comment header with the proper encoding if necessary."
800 (save-excursion
801 (widen)
802 (goto-char (point-min))
6c1bf086 803 (when (ruby--encoding-comment-required-p)
c7fbbc3c 804 (goto-char (point-min))
6c1bf086 805 (let ((coding-system (ruby--detect-encoding)))
e70181b8
AM
806 (when coding-system
807 (if (looking-at "^#!") (beginning-of-line 2))
99f5d074
BB
808 (cond ((looking-at "\\s *#\\s *.*\\(en\\)?coding\\s *:\\s *\\([-a-z0-9_]*\\)")
809 ;; update existing encoding comment if necessary
e70181b8
AM
810 (unless (string= (match-string 2) coding-system)
811 (goto-char (match-beginning 2))
812 (delete-region (point) (match-end 2))
e70181b8
AM
813 (insert coding-system)))
814 ((looking-at "\\s *#.*coding\\s *[:=]"))
815 (t (when ruby-insert-encoding-magic-comment
6c1bf086 816 (ruby--insert-coding-comment coding-system))))
e70181b8
AM
817 (when (buffer-modified-p)
818 (basic-save-buffer-1)))))))
c7fbbc3c 819
6cddebc1
DG
820(defvar ruby--electric-indent-chars '(?. ?\) ?} ?\]))
821
822(defun ruby--electric-indent-p (char)
823 (cond
824 ((memq char ruby--electric-indent-chars)
1f87a56c 825 ;; Reindent after typing a char affecting indentation.
6cddebc1
DG
826 (ruby--at-indentation-p (1- (point))))
827 ((memq (char-after) ruby--electric-indent-chars)
1f87a56c 828 ;; Reindent after inserting something in front of the above.
6cddebc1 829 (ruby--at-indentation-p (1- (point))))
1f87a56c 830 ((or (and (>= char ?a) (<= char ?z)) (memq char '(?_ ?? ?! ?:)))
6cddebc1
DG
831 (let ((pt (point)))
832 (save-excursion
1f87a56c 833 (skip-chars-backward "[:alpha:]:_?!")
6cddebc1
DG
834 (and (ruby--at-indentation-p)
835 (looking-at (regexp-opt (cons "end" ruby-block-mid-keywords)))
836 ;; Outdent after typing a keyword.
837 (or (eq (match-end 0) pt)
838 ;; Reindent if it wasn't a keyword after all.
839 (eq (match-end 0) (1- pt)))))))))
840
841;; FIXME: Remove this? It's unused here, but some redefinitions of
842;; `ruby-calculate-indent' in user init files still call it.
c7fbbc3c
CY
843(defun ruby-current-indentation ()
844 "Return the indentation level of current line."
845 (save-excursion
846 (beginning-of-line)
847 (back-to-indentation)
848 (current-column)))
849
cf38dd42 850(defun ruby-indent-line (&optional ignored)
ee61fe97 851 "Correct the indentation of the current Ruby line."
c7fbbc3c
CY
852 (interactive)
853 (ruby-indent-to (ruby-calculate-indent)))
854
855(defun ruby-indent-to (column)
856 "Indent the current line to COLUMN."
857 (when column
858 (let (shift top beg)
03177f98 859 (and (< column 0) (error "Invalid nesting"))
c7fbbc3c
CY
860 (setq shift (current-column))
861 (beginning-of-line)
862 (setq beg (point))
863 (back-to-indentation)
864 (setq top (current-column))
865 (skip-chars-backward " \t")
866 (if (>= shift top) (setq shift (- shift top))
867 (setq shift 0))
868 (if (and (bolp)
869 (= column top))
870 (move-to-column (+ column shift))
871 (move-to-column top)
872 (delete-region beg (point))
873 (beginning-of-line)
874 (indent-to column)
875 (move-to-column (+ column shift))))))
876
877(defun ruby-special-char-p (&optional pos)
878 "Return t if the character before POS is a special character.
879If omitted, POS defaults to the current point.
880Special characters are `?', `$', `:' when preceded by whitespace,
881and `\\' when preceded by `?'."
882 (setq pos (or pos (point)))
883 (let ((c (char-before pos)) (b (and (< (point-min) pos)
884 (char-before (1- pos)))))
885 (cond ((or (eq c ??) (eq c ?$)))
886 ((and (eq c ?:) (or (not b) (eq (char-syntax b) ? ))))
887 ((eq c ?\\) (eq b ??)))))
888
9cd80478
DG
889(defun ruby-singleton-class-p (&optional pos)
890 (save-excursion
891 (when pos (goto-char pos))
892 (forward-word -1)
893 (and (or (bolp) (not (eq (char-before (point)) ?_)))
bb808526 894 (looking-at ruby-singleton-class-re))))
9cd80478 895
c7fbbc3c 896(defun ruby-expr-beg (&optional option)
8619323f
DG
897 "Check if point is possibly at the beginning of an expression.
898OPTION specifies the type of the expression.
899Can be one of `heredoc', `modifier', `expr-qstr', `expr-re'."
c7fbbc3c
CY
900 (save-excursion
901 (store-match-data nil)
9cd80478
DG
902 (let ((space (skip-chars-backward " \t"))
903 (start (point)))
c7fbbc3c
CY
904 (cond
905 ((bolp) t)
906 ((progn
907 (forward-char -1)
908 (and (looking-at "\\?")
909 (or (eq (char-syntax (char-before (point))) ?w)
910 (ruby-special-char-p))))
911 nil)
8619323f
DG
912 ((looking-at ruby-operator-re))
913 ((eq option 'heredoc)
914 (and (< space 0) (not (ruby-singleton-class-p start))))
915 ((or (looking-at "[\\[({,;]")
c7fbbc3c
CY
916 (and (looking-at "[!?]")
917 (or (not (eq option 'modifier))
918 (bolp)
919 (save-excursion (forward-char -1) (looking-at "\\Sw$"))))
920 (and (looking-at ruby-symbol-re)
921 (skip-chars-backward ruby-symbol-chars)
922 (cond
923 ((looking-at (regexp-opt
924 (append ruby-block-beg-keywords
925 ruby-block-op-keywords
926 ruby-block-mid-keywords)
927 'words))
928 (goto-char (match-end 0))
af67e79a 929 (not (looking-at "\\s_")))
c7fbbc3c
CY
930 ((eq option 'expr-qstr)
931 (looking-at "[a-zA-Z][a-zA-z0-9_]* +%[^ \t]"))
932 ((eq option 'expr-re)
933 (looking-at "[a-zA-Z][a-zA-z0-9_]* +/[^ \t]"))
934 (t nil)))))))))
935
936(defun ruby-forward-string (term &optional end no-error expand)
8f48d131
CD
937 "Move forward across one balanced pair of string delimiters.
938Skips escaped delimiters. If EXPAND is non-nil, also ignores
939delimiters in interpolated strings.
940
941TERM should be a string containing either a single, self-matching
942delimiter (e.g. \"/\"), or a pair of matching delimiters with the
943close delimiter first (e.g. \"][\").
944
945When non-nil, search is bounded by position END.
946
947Throws an error if a balanced match is not found, unless NO-ERROR
948is non-nil, in which case nil will be returned.
949
950This command assumes the character after point is an opening
951delimiter."
c7fbbc3c 952 (let ((n 1) (c (string-to-char term))
8f48d131
CD
953 (re (concat "[^\\]\\(\\\\\\\\\\)*\\("
954 (if (string= term "^") ;[^] is not a valid regexp
955 "\\^"
956 (concat "[" term "]"))
957 (when expand "\\|\\(#{\\)")
958 "\\)")))
c7fbbc3c
CY
959 (while (and (re-search-forward re end no-error)
960 (if (match-beginning 3)
961 (ruby-forward-string "}{" end no-error nil)
962 (> (setq n (if (eq (char-before (point)) c)
963 (1- n) (1+ n))) 0)))
964 (forward-char -1))
965 (cond ((zerop n))
966 (no-error nil)
03177f98 967 ((error "Unterminated string")))))
c7fbbc3c
CY
968
969(defun ruby-deep-indent-paren-p (c)
ee61fe97 970 "TODO: document."
c7fbbc3c
CY
971 (cond ((listp ruby-deep-indent-paren)
972 (let ((deep (assoc c ruby-deep-indent-paren)))
973 (cond (deep
974 (or (cdr deep) ruby-deep-indent-paren-style))
975 ((memq c ruby-deep-indent-paren)
976 ruby-deep-indent-paren-style))))
977 ((eq c ruby-deep-indent-paren) ruby-deep-indent-paren-style)
978 ((eq c ?\( ) ruby-deep-arglist)))
979
980(defun ruby-parse-partial (&optional end in-string nest depth pcol indent)
ee61fe97 981 "TODO: document throughout function body."
c7fbbc3c
CY
982 (or depth (setq depth 0))
983 (or indent (setq indent 0))
984 (when (re-search-forward ruby-delimiter end 'move)
985 (let ((pnt (point)) w re expand)
986 (goto-char (match-beginning 0))
987 (cond
988 ((and (memq (char-before) '(?@ ?$)) (looking-at "\\sw"))
989 (goto-char pnt))
990 ((looking-at "[\"`]") ;skip string
991 (cond
992 ((and (not (eobp))
ad16897c
SM
993 (ruby-forward-string (buffer-substring (point) (1+ (point)))
994 end t t))
c7fbbc3c
CY
995 nil)
996 (t
997 (setq in-string (point))
998 (goto-char end))))
999 ((looking-at "'")
1000 (cond
1001 ((and (not (eobp))
1002 (re-search-forward "[^\\]\\(\\\\\\\\\\)*'" end t))
1003 nil)
1004 (t
1005 (setq in-string (point))
1006 (goto-char end))))
1007 ((looking-at "/=")
1008 (goto-char pnt))
1009 ((looking-at "/")
1010 (cond
1011 ((and (not (eobp)) (ruby-expr-beg 'expr-re))
1012 (if (ruby-forward-string "/" end t t)
1013 nil
1014 (setq in-string (point))
1015 (goto-char end)))
1016 (t
1017 (goto-char pnt))))
1018 ((looking-at "%")
1019 (cond
1020 ((and (not (eobp))
1021 (ruby-expr-beg 'expr-qstr)
1022 (not (looking-at "%="))
1023 (looking-at "%[QqrxWw]?\\([^a-zA-Z0-9 \t\n]\\)"))
1024 (goto-char (match-beginning 1))
1025 (setq expand (not (memq (char-before) '(?q ?w))))
1026 (setq w (match-string 1))
1027 (cond
1028 ((string= w "[") (setq re "]["))
1029 ((string= w "{") (setq re "}{"))
1030 ((string= w "(") (setq re ")("))
1031 ((string= w "<") (setq re "><"))
1032 ((and expand (string= w "\\"))
1033 (setq w (concat "\\" w))))
1034 (unless (cond (re (ruby-forward-string re end t expand))
1035 (expand (ruby-forward-string w end t t))
1036 (t (re-search-forward
1037 (if (string= w "\\")
1038 "\\\\[^\\]*\\\\"
1039 (concat "[^\\]\\(\\\\\\\\\\)*" w))
1040 end t)))
1041 (setq in-string (point))
1042 (goto-char end)))
1043 (t
1044 (goto-char pnt))))
1045 ((looking-at "\\?") ;skip ?char
1046 (cond
1047 ((and (ruby-expr-beg)
1048 (looking-at "?\\(\\\\C-\\|\\\\M-\\)*\\\\?."))
1049 (goto-char (match-end 0)))
1050 (t
1051 (goto-char pnt))))
1052 ((looking-at "\\$") ;skip $char
1053 (goto-char pnt)
1054 (forward-char 1))
1055 ((looking-at "#") ;skip comment
1056 (forward-line 1)
1057 (goto-char (point))
1058 )
1059 ((looking-at "[\\[{(]")
1060 (let ((deep (ruby-deep-indent-paren-p (char-after))))
1061 (if (and deep (or (not (eq (char-after) ?\{)) (ruby-expr-beg)))
1062 (progn
1063 (and (eq deep 'space) (looking-at ".\\s +[^# \t\n]")
1064 (setq pnt (1- (match-end 0))))
1065 (setq nest (cons (cons (char-after (point)) pnt) nest))
1066 (setq pcol (cons (cons pnt depth) pcol))
1067 (setq depth 0))
1068 (setq nest (cons (cons (char-after (point)) pnt) nest))
1069 (setq depth (1+ depth))))
1070 (goto-char pnt)
1071 )
1072 ((looking-at "[])}]")
1073 (if (ruby-deep-indent-paren-p (matching-paren (char-after)))
1074 (setq depth (cdr (car pcol)) pcol (cdr pcol))
1075 (setq depth (1- depth)))
1076 (setq nest (cdr nest))
1077 (goto-char pnt))
1078 ((looking-at ruby-block-end-re)
1079 (if (or (and (not (bolp))
1080 (progn
1081 (forward-char -1)
1082 (setq w (char-after (point)))
1083 (or (eq ?_ w)
1084 (eq ?. w))))
1085 (progn
1086 (goto-char pnt)
1087 (setq w (char-after (point)))
1088 (or (eq ?_ w)
1089 (eq ?! w)
1090 (eq ?? w))))
1091 nil
1092 (setq nest (cdr nest))
1093 (setq depth (1- depth)))
1094 (goto-char pnt))
1095 ((looking-at "def\\s +[^(\n;]*")
1096 (if (or (bolp)
1097 (progn
1098 (forward-char -1)
1099 (not (eq ?_ (char-after (point))))))
1100 (progn
1101 (setq nest (cons (cons nil pnt) nest))
1102 (setq depth (1+ depth))))
1103 (goto-char (match-end 0)))
39675016 1104 ((looking-at (concat "\\_<\\(" ruby-block-beg-re "\\)\\_>"))
c7fbbc3c
CY
1105 (and
1106 (save-match-data
ac72c08d 1107 (or (not (looking-at "do\\_>"))
c7fbbc3c
CY
1108 (save-excursion
1109 (back-to-indentation)
1110 (not (looking-at ruby-non-block-do-re)))))
1111 (or (bolp)
1112 (progn
1113 (forward-char -1)
1114 (setq w (char-after (point)))
1115 (not (or (eq ?_ w)
1116 (eq ?. w)))))
1117 (goto-char pnt)
7132e457 1118 (not (eq ?! (char-after (point))))
c7fbbc3c
CY
1119 (skip-chars-forward " \t")
1120 (goto-char (match-beginning 0))
1121 (or (not (looking-at ruby-modifier-re))
1122 (ruby-expr-beg 'modifier))
1123 (goto-char pnt)
1124 (setq nest (cons (cons nil pnt) nest))
1125 (setq depth (1+ depth)))
1126 (goto-char pnt))
1127 ((looking-at ":\\(['\"]\\)")
1128 (goto-char (match-beginning 1))
c28662a8 1129 (ruby-forward-string (match-string 1) end t))
11473529 1130 ((looking-at ":\\([-,.+*/%&|^~<>]=?\\|===?\\|<=>\\|![~=]?\\)")
c7fbbc3c
CY
1131 (goto-char (match-end 0)))
1132 ((looking-at ":\\([a-zA-Z_][a-zA-Z_0-9]*[!?=]?\\)?")
1133 (goto-char (match-end 0)))
1134 ((or (looking-at "\\.\\.\\.?")
1135 (looking-at "\\.[0-9]+")
1136 (looking-at "\\.[a-zA-Z_0-9]+")
1137 (looking-at "\\."))
1138 (goto-char (match-end 0)))
1139 ((looking-at "^=begin")
1140 (if (re-search-forward "^=end" end t)
1141 (forward-line 1)
1142 (setq in-string (match-end 0))
1143 (goto-char end)))
1144 ((looking-at "<<")
1145 (cond
1146 ((and (ruby-expr-beg 'heredoc)
1147 (looking-at "<<\\(-\\)?\\(\\([\"'`]\\)\\([^\n]+?\\)\\3\\|\\(?:\\sw\\|\\s_\\)+\\)"))
1148 (setq re (regexp-quote (or (match-string 4) (match-string 2))))
1149 (if (match-beginning 1) (setq re (concat "\\s *" re)))
1150 (let* ((id-end (goto-char (match-end 0)))
e180ab9f 1151 (line-end-position (point-at-eol))
c7fbbc3c
CY
1152 (state (list in-string nest depth pcol indent)))
1153 ;; parse the rest of the line
1154 (while (and (> line-end-position (point))
1155 (setq state (apply 'ruby-parse-partial
1156 line-end-position state))))
1157 (setq in-string (car state)
1158 nest (nth 1 state)
1159 depth (nth 2 state)
1160 pcol (nth 3 state)
1161 indent (nth 4 state))
1162 ;; skip heredoc section
1163 (if (re-search-forward (concat "^" re "$") end 'move)
1164 (forward-line 1)
1165 (setq in-string id-end)
1166 (goto-char end))))
1167 (t
1168 (goto-char pnt))))
1169 ((looking-at "^__END__$")
1170 (goto-char pnt))
1171 ((and (looking-at ruby-here-doc-beg-re)
1172 (boundp 'ruby-indent-point))
1173 (if (re-search-forward (ruby-here-doc-end-match)
1174 ruby-indent-point t)
1175 (forward-line 1)
1176 (setq in-string (match-end 0))
1177 (goto-char ruby-indent-point)))
1178 (t
03177f98 1179 (error (format "Bad string %s"
c7fbbc3c
CY
1180 (buffer-substring (point) pnt)
1181 ))))))
1182 (list in-string nest depth pcol))
1183
1184(defun ruby-parse-region (start end)
1185 "TODO: document."
1186 (let (state)
1187 (save-excursion
1188 (if start
1189 (goto-char start)
1190 (ruby-beginning-of-indent))
1191 (save-restriction
1192 (narrow-to-region (point) end)
1193 (while (and (> end (point))
1194 (setq state (apply 'ruby-parse-partial end state))))))
1195 (list (nth 0 state) ; in-string
1196 (car (nth 1 state)) ; nest
1197 (nth 2 state) ; depth
1198 (car (car (nth 3 state))) ; pcol
1199 ;(car (nth 5 state)) ; indent
1200 )))
1201
1202(defun ruby-indent-size (pos nest)
ee61fe97 1203 "Return the indentation level in spaces NEST levels deeper than POS."
c7fbbc3c
CY
1204 (+ pos (* (or nest 1) ruby-indent-level)))
1205
1206(defun ruby-calculate-indent (&optional parse-start)
ee61fe97 1207 "Return the proper indentation level of the current line."
c7fbbc3c
CY
1208 ;; TODO: Document body
1209 (save-excursion
1210 (beginning-of-line)
1211 (let ((ruby-indent-point (point))
1212 (case-fold-search nil)
cf38dd42 1213 state eol begin op-end
c7fbbc3c
CY
1214 (paren (progn (skip-syntax-forward " ")
1215 (and (char-after) (matching-paren (char-after)))))
1216 (indent 0))
1217 (if parse-start
1218 (goto-char parse-start)
1219 (ruby-beginning-of-indent)
1220 (setq parse-start (point)))
1221 (back-to-indentation)
1222 (setq indent (current-column))
1223 (setq state (ruby-parse-region parse-start ruby-indent-point))
1224 (cond
1225 ((nth 0 state) ; within string
1226 (setq indent nil)) ; do nothing
1227 ((car (nth 1 state)) ; in paren
1228 (goto-char (setq begin (cdr (nth 1 state))))
1229 (let ((deep (ruby-deep-indent-paren-p (car (nth 1 state)))))
1230 (if deep
1231 (cond ((and (eq deep t) (eq (car (nth 1 state)) paren))
1232 (skip-syntax-backward " ")
1233 (setq indent (1- (current-column))))
1234 ((let ((s (ruby-parse-region (point) ruby-indent-point)))
1235 (and (nth 2 s) (> (nth 2 s) 0)
1236 (or (goto-char (cdr (nth 1 s))) t)))
1237 (forward-word -1)
1238 (setq indent (ruby-indent-size (current-column)
1239 (nth 2 state))))
1240 (t
1241 (setq indent (current-column))
1242 (cond ((eq deep 'space))
1243 (paren (setq indent (1- indent)))
1244 (t (setq indent (ruby-indent-size (1- indent) 1))))))
1245 (if (nth 3 state) (goto-char (nth 3 state))
1246 (goto-char parse-start) (back-to-indentation))
1247 (setq indent (ruby-indent-size (current-column) (nth 2 state))))
1248 (and (eq (car (nth 1 state)) paren)
1249 (ruby-deep-indent-paren-p (matching-paren paren))
1250 (search-backward (char-to-string paren))
1251 (setq indent (current-column)))))
1252 ((and (nth 2 state) (> (nth 2 state) 0)) ; in nest
1253 (if (null (cdr (nth 1 state)))
03177f98 1254 (error "Invalid nesting"))
c7fbbc3c
CY
1255 (goto-char (cdr (nth 1 state)))
1256 (forward-word -1) ; skip back a keyword
1257 (setq begin (point))
1258 (cond
1259 ((looking-at "do\\>[^_]") ; iter block is a special case
1260 (if (nth 3 state) (goto-char (nth 3 state))
1261 (goto-char parse-start) (back-to-indentation))
1262 (setq indent (ruby-indent-size (current-column) (nth 2 state))))
1263 (t
1264 (setq indent (+ (current-column) ruby-indent-level)))))
ee61fe97 1265
c7fbbc3c
CY
1266 ((and (nth 2 state) (< (nth 2 state) 0)) ; in negative nest
1267 (setq indent (ruby-indent-size (current-column) (nth 2 state)))))
1268 (when indent
1269 (goto-char ruby-indent-point)
1270 (end-of-line)
1271 (setq eol (point))
1272 (beginning-of-line)
1273 (cond
1274 ((and (not (ruby-deep-indent-paren-p paren))
1275 (re-search-forward ruby-negative eol t))
1276 (and (not (eq ?_ (char-after (match-end 0))))
1277 (setq indent (- indent ruby-indent-level))))
1278 ((and
1279 (save-excursion
1280 (beginning-of-line)
1281 (not (bobp)))
1282 (or (ruby-deep-indent-paren-p t)
1283 (null (car (nth 1 state)))))
1284 ;; goto beginning of non-empty no-comment line
1285 (let (end done)
1286 (while (not done)
1287 (skip-chars-backward " \t\n")
1288 (setq end (point))
1289 (beginning-of-line)
1290 (if (re-search-forward "^\\s *#" end t)
1291 (beginning-of-line)
1292 (setq done t))))
c7fbbc3c
CY
1293 (end-of-line)
1294 ;; skip the comment at the end
1295 (skip-chars-backward " \t")
1296 (let (end (pos (point)))
1297 (beginning-of-line)
1298 (while (and (re-search-forward "#" pos t)
1299 (setq end (1- (point)))
1300 (or (ruby-special-char-p end)
ad16897c
SM
1301 (and (setq state (ruby-parse-region
1302 parse-start end))
c7fbbc3c
CY
1303 (nth 0 state))))
1304 (setq end nil))
1305 (goto-char (or end pos))
1306 (skip-chars-backward " \t")
1307 (setq begin (if (and end (nth 0 state)) pos (cdr (nth 1 state))))
1308 (setq state (ruby-parse-region parse-start (point))))
1309 (or (bobp) (forward-char -1))
1310 (and
1311 (or (and (looking-at ruby-symbol-re)
1312 (skip-chars-backward ruby-symbol-chars)
ad16897c
SM
1313 (looking-at (concat "\\<\\(" ruby-block-hanging-re
1314 "\\)\\>"))
c7fbbc3c
CY
1315 (not (eq (point) (nth 3 state)))
1316 (save-excursion
1317 (goto-char (match-end 0))
1318 (not (looking-at "[a-z_]"))))
1319 (and (looking-at ruby-operator-re)
1320 (not (ruby-special-char-p))
88527bc0
DG
1321 (save-excursion
1322 (forward-char -1)
1323 (or (not (looking-at ruby-operator-re))
1324 (not (eq (char-before) ?:))))
dfbd787f 1325 ;; Operator at the end of line.
c7fbbc3c
CY
1326 (let ((c (char-after (point))))
1327 (and
1328;; (or (null begin)
1329;; (save-excursion
1330;; (goto-char begin)
1331;; (skip-chars-forward " \t")
1332;; (not (or (eolp) (looking-at "#")
1333;; (and (eq (car (nth 1 state)) ?{)
1334;; (looking-at "|"))))))
e636fafe 1335 ;; Not a regexp or percent literal.
dfbd787f
SM
1336 (null (nth 0 (ruby-parse-region (or begin parse-start)
1337 (point))))
c7fbbc3c
CY
1338 (or (not (eq ?| (char-after (point))))
1339 (save-excursion
1340 (or (eolp) (forward-char -1))
1341 (cond
1342 ((search-backward "|" nil t)
1343 (skip-chars-backward " \t\n")
1344 (and (not (eolp))
1345 (progn
1346 (forward-char -1)
1347 (not (looking-at "{")))
1348 (progn
1349 (forward-word -1)
1350 (not (looking-at "do\\>[^_]")))))
1351 (t t))))
1352 (not (eq ?, c))
1353 (setq op-end t)))))
1354 (setq indent
1355 (cond
1356 ((and
1357 (null op-end)
ad16897c
SM
1358 (not (looking-at (concat "\\<\\(" ruby-block-hanging-re
1359 "\\)\\>")))
c7fbbc3c
CY
1360 (eq (ruby-deep-indent-paren-p t) 'space)
1361 (not (bobp)))
1362 (widen)
1363 (goto-char (or begin parse-start))
1364 (skip-syntax-forward " ")
1365 (current-column))
1366 ((car (nth 1 state)) indent)
1367 (t
1368 (+ indent ruby-indent-level))))))))
1369 (goto-char ruby-indent-point)
1370 (beginning-of-line)
1371 (skip-syntax-forward " ")
1372 (if (looking-at "\\.[^.]")
1373 (+ indent ruby-indent-level)
1374 indent))))
1375
c7fbbc3c 1376(defun ruby-beginning-of-defun (&optional arg)
fb549d64 1377 "Move backward to the beginning of the current defun.
ee61fe97 1378With ARG, move backward multiple defuns. Negative ARG means
c7fbbc3c
CY
1379move forward."
1380 (interactive "p")
fb549d64
DG
1381 (let (case-fold-search)
1382 (and (re-search-backward (concat "^\\s *" ruby-defun-beg-re "\\_>")
1383 nil t (or arg 1))
1384 (beginning-of-line))))
1385
1386(defun ruby-end-of-defun ()
1387 "Move point to the end of the current defun.
1388The defun begins at or after the point. This function is called
1389by `end-of-defun'."
c7fbbc3c 1390 (interactive "p")
8f06acce 1391 (ruby-forward-sexp)
fb549d64
DG
1392 (let (case-fold-search)
1393 (when (looking-back (concat "^\\s *" ruby-block-end-re))
1394 (forward-line 1))))
c7fbbc3c
CY
1395
1396(defun ruby-beginning-of-indent ()
0ba2d4b6
DG
1397 "Backtrack to a line which can be used as a reference for
1398calculating indentation on the lines after it."
1399 (while (and (re-search-backward ruby-indent-beg-re nil 'move)
1400 (if (ruby-in-ppss-context-p 'anything)
1401 t
1402 ;; We can stop, then.
1403 (beginning-of-line)))))
c7fbbc3c
CY
1404
1405(defun ruby-move-to-block (n)
5e9419e8
DG
1406 "Move to the beginning (N < 0) or the end (N > 0) of the
1407current block, a sibling block, or an outer block. Do that (abs N) times."
3086ca2e 1408 (back-to-indentation)
7132e457 1409 (let ((signum (if (> n 0) 1 -1))
5e9419e8 1410 (backward (< n 0))
3086ca2e 1411 (depth (or (nth 2 (ruby-parse-region (point) (line-end-position))) 0))
fb549d64 1412 case-fold-search
7132e457 1413 down done)
3086ca2e
DG
1414 (when (looking-at ruby-block-mid-re)
1415 (setq depth (+ depth signum)))
7132e457
DG
1416 (when (< (* depth signum) 0)
1417 ;; Moving end -> end or beginning -> beginning.
1418 (setq depth 0))
5e9419e8
DG
1419 (dotimes (_ (abs n))
1420 (setq done nil)
1421 (setq down (save-excursion
1422 (back-to-indentation)
1423 ;; There is a block start or block end keyword on this
1424 ;; line, don't need to look for another block.
1425 (and (re-search-forward
1426 (if backward ruby-block-end-re
1427 (concat "\\_<\\(" ruby-block-beg-re "\\)\\_>"))
1428 (line-end-position) t)
1429 (not (nth 8 (syntax-ppss))))))
1430 (while (and (not done) (not (if backward (bobp) (eobp))))
1431 (forward-line signum)
c7fbbc3c 1432 (cond
5e9419e8
DG
1433 ;; Skip empty and commented out lines.
1434 ((looking-at "^\\s *$"))
1435 ((looking-at "^\\s *#"))
1436 ;; Skip block comments;
1437 ((and (not backward) (looking-at "^=begin\\>"))
1438 (re-search-forward "^=end\\>"))
1439 ((and backward (looking-at "^=end\\>"))
1440 (re-search-backward "^=begin\\>"))
53ca88c4
DG
1441 ;; Jump over a multiline literal.
1442 ((ruby-in-ppss-context-p 'string)
1443 (goto-char (nth 8 (syntax-ppss)))
1444 (unless backward
1445 (forward-sexp)
1446 (when (bolp) (forward-char -1)))) ; After a heredoc.
5e9419e8 1447 (t
53ca88c4
DG
1448 (let ((state (ruby-parse-region (point) (line-end-position))))
1449 (unless (car state) ; Line ends with unfinished string.
1450 (setq depth (+ (nth 2 state) depth))))
5e9419e8 1451 (cond
3086ca2e 1452 ;; Increased depth, we found a block.
7132e457 1453 ((> (* signum depth) 0)
5e9419e8 1454 (setq down t))
3086ca2e
DG
1455 ;; We're at the same depth as when we started, and we've
1456 ;; encountered a block before. Stop.
7132e457 1457 ((and down (zerop depth))
5e9419e8 1458 (setq done t))
3086ca2e 1459 ;; Lower depth, means outer block, can stop now.
7132e457 1460 ((< (* signum depth) 0)
3086ca2e 1461 (setq done t)))))))
d1e1e53d 1462 (back-to-indentation)))
c7fbbc3c
CY
1463
1464(defun ruby-beginning-of-block (&optional arg)
1465 "Move backward to the beginning of the current block.
1466With ARG, move up multiple blocks."
1467 (interactive "p")
1468 (ruby-move-to-block (- (or arg 1))))
1469
1470(defun ruby-end-of-block (&optional arg)
1471 "Move forward to the end of the current block.
1472With ARG, move out of multiple blocks."
5e9419e8 1473 (interactive "p")
c7fbbc3c
CY
1474 (ruby-move-to-block (or arg 1)))
1475
1476(defun ruby-forward-sexp (&optional arg)
1477 "Move forward across one balanced expression (sexp).
ee61fe97 1478With ARG, do it many times. Negative ARG means move backward."
c7fbbc3c
CY
1479 ;; TODO: Document body
1480 (interactive "p")
34d1a133
SM
1481 (cond
1482 (ruby-use-smie (forward-sexp arg))
1483 ((and (numberp arg) (< arg 0)) (ruby-backward-sexp (- arg)))
1484 (t
c7fbbc3c
CY
1485 (let ((i (or arg 1)))
1486 (condition-case nil
1487 (while (> i 0)
1488 (skip-syntax-forward " ")
cc9c9831 1489 (if (looking-at ",\\s *") (goto-char (match-end 0)))
c7fbbc3c
CY
1490 (cond ((looking-at "\\?\\(\\\\[CM]-\\)*\\\\?\\S ")
1491 (goto-char (match-end 0)))
1492 ((progn
1493 (skip-chars-forward ",.:;|&^~=!?\\+\\-\\*")
1494 (looking-at "\\s("))
1495 (goto-char (scan-sexps (point) 1)))
ad16897c
SM
1496 ((and (looking-at (concat "\\<\\(" ruby-block-beg-re
1497 "\\)\\>"))
c7fbbc3c
CY
1498 (not (eq (char-before (point)) ?.))
1499 (not (eq (char-before (point)) ?:)))
1500 (ruby-end-of-block)
1501 (forward-word 1))
1502 ((looking-at "\\(\\$\\|@@?\\)?\\sw")
1503 (while (progn
1504 (while (progn (forward-word 1) (looking-at "_")))
1505 (cond ((looking-at "::") (forward-char 2) t)
1506 ((> (skip-chars-forward ".") 0))
1507 ((looking-at "\\?\\|!\\(=[~=>]\\|[^~=]\\)")
1508 (forward-char 1) nil)))))
1509 ((let (state expr)
1510 (while
1511 (progn
1512 (setq expr (or expr (ruby-expr-beg)
1513 (looking-at "%\\sw?\\Sw\\|[\"'`/]")))
ad16897c
SM
1514 (nth 1 (setq state (apply #'ruby-parse-partial
1515 nil state))))
c7fbbc3c
CY
1516 (setq expr t)
1517 (skip-chars-forward "<"))
1518 (not expr))))
1519 (setq i (1- i)))
1520 ((error) (forward-word 1)))
34d1a133 1521 i))))
c7fbbc3c
CY
1522
1523(defun ruby-backward-sexp (&optional arg)
1524 "Move backward across one balanced expression (sexp).
ee61fe97 1525With ARG, do it many times. Negative ARG means move forward."
c7fbbc3c
CY
1526 ;; TODO: Document body
1527 (interactive "p")
34d1a133
SM
1528 (cond
1529 (ruby-use-smie (backward-sexp arg))
1530 ((and (numberp arg) (< arg 0)) (ruby-forward-sexp (- arg)))
1531 (t
c7fbbc3c
CY
1532 (let ((i (or arg 1)))
1533 (condition-case nil
1534 (while (> i 0)
1535 (skip-chars-backward " \t\n,.:;|&^~=!?\\+\\-\\*")
1536 (forward-char -1)
1537 (cond ((looking-at "\\s)")
1538 (goto-char (scan-sexps (1+ (point)) -1))
ad16897c
SM
1539 (pcase (char-before)
1540 (`?% (forward-char -1))
1541 ((or `?q `?Q `?w `?W `?r `?x)
1542 (if (eq (char-before (1- (point))) ?%)
1543 (forward-char -2))))
c7fbbc3c
CY
1544 nil)
1545 ((looking-at "\\s\"\\|\\\\\\S_")
1546 (let ((c (char-to-string (char-before (match-end 0)))))
1547 (while (and (search-backward c)
1548 (eq (logand (skip-chars-backward "\\") 1)
1549 1))))
1550 nil)
1551 ((looking-at "\\s.\\|\\s\\")
1552 (if (ruby-special-char-p) (forward-char -1)))
1553 ((looking-at "\\s(") nil)
1554 (t
1555 (forward-char 1)
1556 (while (progn (forward-word -1)
ad16897c
SM
1557 (pcase (char-before)
1558 (`?_ t)
1559 (`?. (forward-char -1) t)
1560 ((or `?$ `?@)
c7fbbc3c 1561 (forward-char -1)
ad16897c
SM
1562 (and (eq (char-before) (char-after))
1563 (forward-char -1)))
1564 (`?:
c7fbbc3c
CY
1565 (forward-char -1)
1566 (eq (char-before) :)))))
1567 (if (looking-at ruby-block-end-re)
1568 (ruby-beginning-of-block))
1569 nil))
1570 (setq i (1- i)))
1571 ((error)))
34d1a133 1572 i))))
c7fbbc3c 1573
cf38dd42
SM
1574(defun ruby-indent-exp (&optional ignored)
1575 "Indent each line in the balanced expression following the point."
c7fbbc3c
CY
1576 (interactive "*P")
1577 (let ((here (point-marker)) start top column (nest t))
1578 (set-marker-insertion-type here t)
1579 (unwind-protect
1580 (progn
1581 (beginning-of-line)
1582 (setq start (point) top (current-indentation))
1583 (while (and (not (eobp))
1584 (progn
1585 (setq column (ruby-calculate-indent start))
1586 (cond ((> column top)
1587 (setq nest t))
1588 ((and (= column top) nest)
1589 (setq nest nil) t))))
1590 (ruby-indent-to column)
1591 (beginning-of-line 2)))
1592 (goto-char here)
1593 (set-marker here nil))))
1594
1595(defun ruby-add-log-current-method ()
ee61fe97 1596 "Return the current method name as a string.
c7fbbc3c
CY
1597This string includes all namespaces.
1598
1599For example:
1600
1601 #exit
1602 String#gsub
1603 Net::HTTP#active?
5745cae6 1604 File.open
c7fbbc3c
CY
1605
1606See `add-log-current-defun-function'."
c7fbbc3c
CY
1607 (condition-case nil
1608 (save-excursion
71a048c1
DG
1609 (let* ((indent 0) mname mlist
1610 (start (point))
1611 (make-definition-re
1612 (lambda (re)
1613 (concat "^[ \t]*" re "[ \t]+"
1614 "\\("
1615 ;; \\. and :: for class methods
1616 "\\([A-Za-z_]" ruby-symbol-re "*\\|\\.\\|::" "\\)"
1617 "+\\)")))
1618 (definition-re (funcall make-definition-re ruby-defun-beg-re))
1619 (module-re (funcall make-definition-re "\\(class\\|module\\)")))
5745cae6 1620 ;; Get the current method definition (or class/module).
bb808526
DG
1621 (when (re-search-backward definition-re nil t)
1622 (goto-char (match-beginning 1))
71a048c1
DG
1623 (if (not (string-equal "def" (match-string 1)))
1624 (setq mlist (list (match-string 2)))
1625 ;; We're inside the method. For classes and modules,
1626 ;; this check is skipped for performance.
1627 (when (ruby-block-contains-point start)
1628 (setq mname (match-string 2))))
bb808526
DG
1629 (setq indent (current-column))
1630 (beginning-of-line))
5745cae6 1631 ;; Walk up the class/module nesting.
c7fbbc3c 1632 (while (and (> indent 0)
71a048c1 1633 (re-search-backward module-re nil t))
c7fbbc3c 1634 (goto-char (match-beginning 1))
71a048c1 1635 (when (< (current-column) indent)
bb808526
DG
1636 (setq mlist (cons (match-string 2) mlist))
1637 (setq indent (current-column))
1638 (beginning-of-line)))
5745cae6 1639 ;; Process the method name.
c7fbbc3c
CY
1640 (when mname
1641 (let ((mn (split-string mname "\\.\\|::")))
1642 (if (cdr mn)
1643 (progn
5745cae6
DG
1644 (unless (string-equal "self" (car mn)) ; def self.foo
1645 ;; def C.foo
1646 (let ((ml (nreverse mlist)))
1647 ;; If the method name references one of the
1648 ;; containing modules, drop the more nested ones.
c7fbbc3c
CY
1649 (while ml
1650 (if (string-equal (car ml) (car mn))
1651 (setq mlist (nreverse (cdr ml)) ml nil))
5745cae6
DG
1652 (or (setq ml (cdr ml)) (nreverse mlist))))
1653 (if mlist
1654 (setcdr (last mlist) (butlast mn))
1655 (setq mlist (butlast mn))))
1656 (setq mname (concat "." (car (last mn)))))
bb808526
DG
1657 ;; See if the method is in singleton class context.
1658 (let ((in-singleton-class
1659 (when (re-search-forward ruby-singleton-class-re start t)
1660 (goto-char (match-beginning 0))
71a048c1
DG
1661 ;; FIXME: Optimize it out, too?
1662 ;; This can be slow in a large file, but
1663 ;; unlike class/module declaration
1664 ;; indentations, method definitions can be
1665 ;; intermixed with these, and may or may not
1666 ;; be additionally indented after visibility
1667 ;; keywords.
bb808526
DG
1668 (ruby-block-contains-point start))))
1669 (setq mname (concat
1670 (if in-singleton-class "." "#")
1671 mname))))))
5745cae6 1672 ;; Generate the string.
c7fbbc3c
CY
1673 (if (consp mlist)
1674 (setq mlist (mapconcat (function identity) mlist "::")))
1675 (if mname
1676 (if mlist (concat mlist mname) mname)
1677 mlist)))))
1678
bb808526
DG
1679(defun ruby-block-contains-point (pt)
1680 (save-excursion
1681 (save-match-data
1682 (ruby-forward-sexp)
1683 (> (point) pt))))
1684
c3268831
DG
1685(defun ruby-brace-to-do-end (orig end)
1686 (let (beg-marker end-marker)
1687 (goto-char end)
1688 (when (eq (char-before) ?\})
1689 (delete-char -1)
32fb8162
DG
1690 (when (save-excursion
1691 (skip-chars-backward " \t")
1692 (not (bolp)))
c3268831
DG
1693 (insert "\n"))
1694 (insert "end")
1695 (setq end-marker (point-marker))
1696 (when (and (not (eobp)) (eq (char-syntax (char-after)) ?w))
1697 (insert " "))
1698 (goto-char orig)
1699 (delete-char 1)
1700 (when (eq (char-syntax (char-before)) ?w)
1701 (insert " "))
1702 (insert "do")
1703 (setq beg-marker (point-marker))
1704 (when (looking-at "\\(\\s \\)*|")
1705 (unless (match-beginning 1)
1706 (insert " "))
1707 (goto-char (1+ (match-end 0)))
1708 (search-forward "|"))
1709 (unless (looking-at "\\s *$")
1710 (insert "\n"))
1711 (indent-region beg-marker end-marker)
1712 (goto-char beg-marker)
1713 t)))
1714
1715(defun ruby-do-end-to-brace (orig end)
32fb8162
DG
1716 (let (beg-marker end-marker beg-pos end-pos)
1717 (goto-char (- end 3))
1718 (when (looking-at ruby-block-end-re)
1719 (delete-char 3)
1720 (setq end-marker (point-marker))
1721 (insert "}")
1722 (goto-char orig)
1723 (delete-char 2)
963ce636
DG
1724 ;; Maybe this should be customizable, let's see if anyone asks.
1725 (insert "{ ")
32fb8162
DG
1726 (setq beg-marker (point-marker))
1727 (when (looking-at "\\s +|")
1728 (delete-char (- (match-end 0) (match-beginning 0) 1))
1729 (forward-char)
1730 (re-search-forward "|" (line-end-position) t))
1731 (save-excursion
1732 (skip-chars-forward " \t\n\r")
1733 (setq beg-pos (point))
1734 (goto-char end-marker)
1735 (skip-chars-backward " \t\n\r")
1736 (setq end-pos (point)))
1737 (when (or
1738 (< end-pos beg-pos)
1739 (and (= (line-number-at-pos beg-pos) (line-number-at-pos end-pos))
1740 (< (+ (current-column) (- end-pos beg-pos) 2) fill-column)))
1741 (just-one-space -1)
1742 (goto-char end-marker)
1743 (just-one-space -1))
1744 (goto-char beg-marker)
1745 t)))
0d9e2599
NN
1746
1747(defun ruby-toggle-block ()
c3268831
DG
1748 "Toggle block type from do-end to braces or back.
1749The block must begin on the current line or above it and end after the point.
1750If the result is do-end block, it will always be multiline."
0d9e2599 1751 (interactive)
c3268831
DG
1752 (let ((start (point)) beg end)
1753 (end-of-line)
1754 (unless
56cd894e 1755 (if (and (re-search-backward "\\(?:[^#]\\)\\({\\)\\|\\(\\_<do\\_>\\)")
c3268831 1756 (progn
56cd894e 1757 (goto-char (or (match-beginning 1) (match-beginning 2)))
c3268831
DG
1758 (setq beg (point))
1759 (save-match-data (ruby-forward-sexp))
1760 (setq end (point))
1761 (> end start)))
1762 (if (match-beginning 1)
1763 (ruby-brace-to-do-end beg end)
1764 (ruby-do-end-to-brace beg end)))
1765 (goto-char start))))
0d9e2599 1766
7ffd3721
DG
1767(eval-and-compile
1768 (defconst ruby-percent-literal-beg-re
1769 "\\(%\\)[qQrswWxIi]?\\([[:punct:]]\\)"
1770 "Regexp to match the beginning of percent literal.")
1771
1772 (defconst ruby-syntax-methods-before-regexp
1773 '("gsub" "gsub!" "sub" "sub!" "scan" "split" "split!" "index" "match"
1774 "assert_match" "Given" "Then" "When")
1775 "Methods that can take regexp as the first argument.
1a0a0a8a
DG
1776It will be properly highlighted even when the call omits parens.")
1777
7ffd3721
DG
1778 (defvar ruby-syntax-before-regexp-re
1779 (concat
1780 ;; Special tokens that can't be followed by a division operator.
1781 "\\(^\\|[[=(,~;<>]"
1782 ;; Distinguish ternary operator tokens.
1783 ;; FIXME: They don't really have to be separated with spaces.
1784 "\\|[?:] "
1785 ;; Control flow keywords and operators following bol or whitespace.
1786 "\\|\\(?:^\\|\\s \\)"
1787 (regexp-opt '("if" "elsif" "unless" "while" "until" "when" "and"
1788 "or" "not" "&&" "||"))
1789 ;; Method name from the list.
1790 "\\|\\_<"
1791 (regexp-opt ruby-syntax-methods-before-regexp)
1792 "\\)\\s *")
1793 "Regexp to match text that can be followed by a regular expression."))
1794
1795(defun ruby-syntax-propertize-function (start end)
1796 "Syntactic keywords for Ruby mode. See `syntax-propertize-function'."
1797 (let (case-fold-search)
1798 (goto-char start)
1799 (remove-text-properties start end '(ruby-expansion-match-data))
1800 (ruby-syntax-propertize-heredoc end)
1801 (ruby-syntax-enclosing-percent-literal end)
1802 (funcall
1803 (syntax-propertize-rules
1804 ;; $' $" $` .... are variables.
1805 ;; ?' ?" ?` are character literals (one-char strings in 1.9+).
1806 ("\\([?$]\\)[#\"'`]"
1807 (1 (unless (save-excursion
1808 ;; Not within a string.
1809 (nth 3 (syntax-ppss (match-beginning 0))))
1810 (string-to-syntax "\\"))))
af67e79a
DG
1811 ;; Part of symbol when at the end of a method name.
1812 ("[!?]"
1813 (0 (unless (save-excursion
1814 (or (nth 8 (syntax-ppss (match-beginning 0)))
42ebc34e 1815 (eq (char-before) ?:)
af67e79a 1816 (let (parse-sexp-lookup-properties)
fa834a93
DG
1817 (zerop (skip-syntax-backward "w_")))
1818 (memq (preceding-char) '(?@ ?$))))
af67e79a 1819 (string-to-syntax "_"))))
7ffd3721
DG
1820 ;; Regular expressions. Start with matching unescaped slash.
1821 ("\\(?:\\=\\|[^\\]\\)\\(?:\\\\\\\\\\)*\\(/\\)"
1822 (1 (let ((state (save-excursion (syntax-ppss (match-beginning 1)))))
1823 (when (or
1824 ;; Beginning of a regexp.
1825 (and (null (nth 8 state))
1826 (save-excursion
1827 (forward-char -1)
1828 (looking-back ruby-syntax-before-regexp-re
1829 (point-at-bol))))
1830 ;; End of regexp. We don't match the whole
1831 ;; regexp at once because it can have
1832 ;; string interpolation inside, or span
1833 ;; several lines.
1834 (eq ?/ (nth 3 state)))
1835 (string-to-syntax "\"/")))))
1836 ;; Expression expansions in strings. We're handling them
1837 ;; here, so that the regexp rule never matches inside them.
1838 (ruby-expression-expansion-re
1839 (0 (ignore (ruby-syntax-propertize-expansion))))
1840 ("^=en\\(d\\)\\_>" (1 "!"))
1841 ("^\\(=\\)begin\\_>" (1 "!"))
1842 ;; Handle here documents.
1843 ((concat ruby-here-doc-beg-re ".*\\(\n\\)")
1844 (7 (unless (or (nth 8 (save-excursion
1845 (syntax-ppss (match-beginning 0))))
1846 (ruby-singleton-class-p (match-beginning 0)))
1847 (put-text-property (match-beginning 7) (match-end 7)
1848 'syntax-table (string-to-syntax "\""))
1849 (ruby-syntax-propertize-heredoc end))))
1850 ;; Handle percent literals: %w(), %q{}, etc.
1851 ((concat "\\(?:^\\|[[ \t\n<+(,=]\\)" ruby-percent-literal-beg-re)
1852 (1 (prog1 "|" (ruby-syntax-propertize-percent-literal end)))))
1853 (point) end)))
1854
1855(defun ruby-syntax-propertize-heredoc (limit)
1856 (let ((ppss (syntax-ppss))
1857 (res '()))
1858 (when (eq ?\n (nth 3 ppss))
1859 (save-excursion
1860 (goto-char (nth 8 ppss))
cf38dd42 1861 (beginning-of-line)
7ffd3721
DG
1862 (while (re-search-forward ruby-here-doc-beg-re
1863 (line-end-position) t)
1864 (unless (ruby-singleton-class-p (match-beginning 0))
1865 (push (concat (ruby-here-doc-end-match) "\n") res))))
1866 (save-excursion
1867 ;; With multiple openers on the same line, we don't know in which
1868 ;; part `start' is, so we have to go back to the beginning.
1869 (when (cdr res)
1870 (goto-char (nth 8 ppss))
1871 (setq res (nreverse res)))
1872 (while (and res (re-search-forward (pop res) limit 'move))
1873 (if (null res)
1874 (put-text-property (1- (point)) (point)
1875 'syntax-table (string-to-syntax "\""))))
1876 ;; End up at bol following the heredoc openers.
1877 ;; Propertize expression expansions from this point forward.
1878 ))))
1879
1880(defun ruby-syntax-enclosing-percent-literal (limit)
1881 (let ((state (syntax-ppss))
1882 (start (point)))
1883 ;; When already inside percent literal, re-propertize it.
1884 (when (eq t (nth 3 state))
1885 (goto-char (nth 8 state))
1886 (when (looking-at ruby-percent-literal-beg-re)
1887 (ruby-syntax-propertize-percent-literal limit))
1888 (when (< (point) start) (goto-char start)))))
1889
1890(defun ruby-syntax-propertize-percent-literal (limit)
1891 (goto-char (match-beginning 2))
1892 ;; Not inside a simple string or comment.
1893 (when (eq t (nth 3 (syntax-ppss)))
1894 (let* ((op (char-after))
1895 (ops (char-to-string op))
1896 (cl (or (cdr (aref (syntax-table) op))
1897 (cdr (assoc op '((?< . ?>))))))
1898 parse-sexp-lookup-properties)
1899 (save-excursion
1900 (condition-case nil
1901 (progn
1902 (if cl ; Paired delimiters.
1903 ;; Delimiter pairs of the same kind can be nested
1904 ;; inside the literal, as long as they are balanced.
1905 ;; Create syntax table that ignores other characters.
1906 (with-syntax-table (make-char-table 'syntax-table nil)
1907 (modify-syntax-entry op (concat "(" (char-to-string cl)))
1908 (modify-syntax-entry cl (concat ")" ops))
1909 (modify-syntax-entry ?\\ "\\")
1910 (save-restriction
1911 (narrow-to-region (point) limit)
1912 (forward-list))) ; skip to the paired character
1913 ;; Single character delimiter.
1914 (re-search-forward (concat "[^\\]\\(?:\\\\\\\\\\)*"
1915 (regexp-quote ops)) limit nil))
1916 ;; Found the closing delimiter.
1917 (put-text-property (1- (point)) (point) 'syntax-table
1918 (string-to-syntax "|")))
1919 ;; Unclosed literal, do nothing.
1920 ((scan-error search-failed)))))))
1921
1922(defun ruby-syntax-propertize-expansion ()
1923 ;; Save the match data to a text property, for font-locking later.
1924 ;; Set the syntax of all double quotes and backticks to punctuation.
1925 (let* ((beg (match-beginning 2))
1926 (end (match-end 2))
1927 (state (and beg (save-excursion (syntax-ppss beg)))))
1928 (when (ruby-syntax-expansion-allowed-p state)
1929 (put-text-property beg (1+ beg) 'ruby-expansion-match-data
1930 (match-data))
1931 (goto-char beg)
1932 (while (re-search-forward "[\"`]" end 'move)
1933 (put-text-property (match-beginning 0) (match-end 0)
1934 'syntax-table (string-to-syntax "."))))))
1935
1936(defun ruby-syntax-expansion-allowed-p (parse-state)
1937 "Return non-nil if expression expansion is allowed."
1938 (let ((term (nth 3 parse-state)))
1939 (cond
1940 ((memq term '(?\" ?` ?\n ?/)))
1941 ((eq term t)
1942 (save-match-data
cf38dd42 1943 (save-excursion
7ffd3721
DG
1944 (goto-char (nth 8 parse-state))
1945 (looking-at "%\\(?:[QWrxI]\\|\\W\\)")))))))
c7fbbc3c 1946
7ffd3721
DG
1947(defun ruby-syntax-propertize-expansions (start end)
1948 (save-excursion
1949 (goto-char start)
1950 (while (re-search-forward ruby-expression-expansion-re end 'move)
1951 (ruby-syntax-propertize-expansion))))
c7fbbc3c
CY
1952
1953(defun ruby-in-ppss-context-p (context &optional ppss)
1954 (let ((ppss (or ppss (syntax-ppss (point)))))
1955 (if (cond
1956 ((eq context 'anything)
1957 (or (nth 3 ppss)
1958 (nth 4 ppss)))
1959 ((eq context 'string)
1960 (nth 3 ppss))
1961 ((eq context 'heredoc)
cf38dd42 1962 (eq ?\n (nth 3 ppss)))
c7fbbc3c
CY
1963 ((eq context 'non-heredoc)
1964 (and (ruby-in-ppss-context-p 'anything)
1965 (not (ruby-in-ppss-context-p 'heredoc))))
1966 ((eq context 'comment)
1967 (nth 4 ppss))
1968 (t
1969 (error (concat
1970 "Internal error on `ruby-in-ppss-context-p': "
1971 "context name `" (symbol-name context) "' is unknown"))))
1972 t)))
1973
c7fbbc3c
CY
1974(defvar ruby-font-lock-syntax-table
1975 (let ((tbl (copy-syntax-table ruby-mode-syntax-table)))
1976 (modify-syntax-entry ?_ "w" tbl)
1977 tbl)
ee61fe97 1978 "The syntax table to use for fontifying Ruby mode buffers.
c7fbbc3c
CY
1979See `font-lock-syntax-table'.")
1980
ac72c08d
DG
1981(defconst ruby-font-lock-keyword-beg-re "\\(?:^\\|[^.@$]\\|\\.\\.\\)")
1982
c7fbbc3c 1983(defconst ruby-font-lock-keywords
ad16897c
SM
1984 `(;; Functions.
1985 ("^\\s *def\\s +\\(?:[^( \t\n.]*\\.\\)?\\([^( \t\n]+\\)"
c7fbbc3c 1986 1 font-lock-function-name-face)
ad16897c
SM
1987 ;; Keywords.
1988 (,(concat
1989 ruby-font-lock-keyword-beg-re
1990 (regexp-opt
1991 '("alias"
1992 "and"
1993 "begin"
1994 "break"
1995 "case"
1996 "class"
1997 "def"
1998 "defined?"
1999 "do"
2000 "elsif"
2001 "else"
2002 "fail"
2003 "ensure"
2004 "for"
2005 "end"
2006 "if"
2007 "in"
2008 "module"
2009 "next"
2010 "not"
2011 "or"
2012 "redo"
2013 "rescue"
2014 "retry"
2015 "return"
2016 "then"
2017 "super"
2018 "unless"
2019 "undef"
2020 "until"
2021 "when"
2022 "while"
2023 "yield")
2024 'symbols))
2025 (1 font-lock-keyword-face))
68e004e0 2026 ;; Core methods that have required arguments.
ad16897c
SM
2027 (,(concat
2028 ruby-font-lock-keyword-beg-re
2029 (regexp-opt
2030 '( ;; built-in methods on Kernel
ad16897c
SM
2031 "at_exit"
2032 "autoload"
2033 "autoload?"
ad16897c
SM
2034 "catch"
2035 "eval"
2036 "exec"
ad16897c
SM
2037 "fork"
2038 "format"
2039 "lambda"
2040 "load"
2041 "loop"
2042 "open"
2043 "p"
2044 "print"
2045 "printf"
2046 "proc"
2047 "putc"
2048 "puts"
ad16897c
SM
2049 "require"
2050 "require_relative"
ad16897c
SM
2051 "spawn"
2052 "sprintf"
ad16897c
SM
2053 "syscall"
2054 "system"
ad16897c
SM
2055 "trap"
2056 "warn"
2057 ;; keyword-like private methods on Module
2058 "alias_method"
2059 "attr"
2060 "attr_accessor"
2061 "attr_reader"
2062 "attr_writer"
2063 "define_method"
2064 "extend"
2065 "include"
2066 "module_function"
2067 "prepend"
ad16897c
SM
2068 "refine"
2069 "using")
2070 'symbols))
dfdb365c 2071 (1 (unless (looking-at " *\\(?:[]|,.)}=]\\|$\\)")
68e004e0
DG
2072 font-lock-builtin-face)))
2073 ;; Kernel methods that have no required arguments.
2074 (,(concat
2075 ruby-font-lock-keyword-beg-re
2076 (regexp-opt
2077 '("__callee__"
2078 "__dir__"
2079 "__method__"
2080 "abort"
2081 "at_exit"
2082 "binding"
2083 "block_given?"
2084 "caller"
2085 "exit"
2086 "exit!"
2087 "fail"
6da8227c
DG
2088 "private"
2089 "protected"
2090 "public"
68e004e0
DG
2091 "raise"
2092 "rand"
2093 "readline"
2094 "readlines"
2095 "sleep"
2096 "srand"
2097 "throw")
2098 'symbols))
ad16897c
SM
2099 (1 font-lock-builtin-face))
2100 ;; Here-doc beginnings.
2101 (,ruby-here-doc-beg-re
2102 (0 (unless (ruby-singleton-class-p (match-beginning 0))
2103 'font-lock-string-face)))
2104 ;; Perl-ish keywords.
2105 "\\_<\\(?:BEGIN\\|END\\)\\_>\\|^__END__$"
2106 ;; Variables.
2107 (,(concat ruby-font-lock-keyword-beg-re
43cebc23 2108 "\\_<\\(nil\\|self\\|true\\|false\\)\\_>")
ac72c08d 2109 1 font-lock-variable-name-face)
ad16897c
SM
2110 ;; Keywords that evaluate to certain values.
2111 ("\\_<__\\(?:LINE\\|ENCODING\\|FILE\\)__\\_>"
2112 (0 font-lock-variable-name-face))
2113 ;; Symbols.
2114 ("\\(^\\|[^:]\\)\\(:\\([-+~]@?\\|[/%&|^`]\\|\\*\\*?\\|<\\(<\\|=>?\\)?\\|>[>=]?\\|===?\\|=~\\|![~=]?\\|\\[\\]=?\\|@?\\(\\w\\|_\\)+\\([!?=]\\|\\b_*\\)\\|#{[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\)\\)"
6c27f0f8 2115 2 font-lock-constant-face)
ad16897c 2116 ;; Variables.
c2d6c639
DG
2117 ("\\$[^a-zA-Z \n]"
2118 0 font-lock-variable-name-face)
ad16897c 2119 ("\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+"
c7fbbc3c 2120 0 font-lock-variable-name-face)
ad16897c
SM
2121 ;; Constants.
2122 ("\\(?:\\_<\\|::\\)\\([A-Z]+\\(\\w\\|_\\)*\\)"
48186177 2123 1 (unless (eq ?\( (char-after)) font-lock-type-face))
ad16897c
SM
2124 ("\\(^\\s *\\|[\[\{\(,]\\s *\\|\\sw\\s +\\)\\(\\(\\sw\\|_\\)+\\):[^:]"
2125 (2 font-lock-constant-face))
2126 ;; Conversion methods on Kernel.
2127 (,(concat ruby-font-lock-keyword-beg-re
2128 (regexp-opt '("Array" "Complex" "Float" "Hash"
2129 "Integer" "Rational" "String") 'symbols))
2130 (1 font-lock-builtin-face))
2131 ;; Expression expansion.
2132 (ruby-match-expression-expansion
c62792e7 2133 2 font-lock-variable-name-face t)
ad16897c 2134 ;; Negation char.
1f44df94 2135 ("\\(?:^\\|[^[:alnum:]_]\\)\\(!+\\)[^=]"
70c46b28 2136 1 font-lock-negation-char-face)
ad16897c
SM
2137 ;; Character literals.
2138 ;; FIXME: Support longer escape sequences.
2139 ("\\_<\\?\\\\?\\S " 0 font-lock-string-face)
1850913d
DG
2140 ;; Regexp options.
2141 ("\\(?:\\s|\\|/\\)\\([imxo]+\\)"
2142 1 (when (save-excursion
2143 (let ((state (syntax-ppss (match-beginning 0))))
2144 (and (nth 3 state)
2145 (or (eq (char-after) ?/)
2146 (progn
2147 (goto-char (nth 8 state))
2148 (looking-at "%r"))))))
2149 font-lock-preprocessor-face))
ad16897c 2150 )
ee61fe97 2151 "Additional expressions to highlight in Ruby mode.")
c7fbbc3c 2152
0ba2d4b6 2153(defun ruby-match-expression-expansion (limit)
dbb530d9
DG
2154 (let* ((prop 'ruby-expansion-match-data)
2155 (pos (next-single-char-property-change (point) prop nil limit))
2156 value)
2157 (when (and pos (> pos (point)))
c62792e7
DG
2158 (goto-char pos)
2159 (or (and (setq value (get-text-property pos prop))
2160 (progn (set-match-data value) t))
2161 (ruby-match-expression-expansion limit)))))
0ba2d4b6 2162
c7fbbc3c 2163;;;###autoload
cf38dd42 2164(define-derived-mode ruby-mode prog-mode "Ruby"
2ea53115 2165 "Major mode for editing Ruby code.
c7fbbc3c 2166
ae93bc74 2167\\{ruby-mode-map}"
c7fbbc3c
CY
2168 (ruby-mode-variables)
2169
a3996a2e
BB
2170 (setq-local imenu-create-index-function 'ruby-imenu-create-index)
2171 (setq-local add-log-current-defun-function 'ruby-add-log-current-method)
2172 (setq-local beginning-of-defun-function 'ruby-beginning-of-defun)
2173 (setq-local end-of-defun-function 'ruby-end-of-defun)
c7fbbc3c 2174
a9ba094b 2175 (add-hook 'after-save-hook 'ruby-mode-set-encoding nil 'local)
6cddebc1 2176 (add-hook 'electric-indent-functions 'ruby--electric-indent-p nil 'local)
c7fbbc3c 2177
a3996a2e
BB
2178 (setq-local font-lock-defaults '((ruby-font-lock-keywords) nil nil))
2179 (setq-local font-lock-keywords ruby-font-lock-keywords)
2180 (setq-local font-lock-syntax-table ruby-font-lock-syntax-table)
c7fbbc3c 2181
7ffd3721 2182 (setq-local syntax-propertize-function #'ruby-syntax-propertize-function))
c7fbbc3c
CY
2183
2184;;; Invoke ruby-mode when appropriate
2185
2186;;;###autoload
5cf8176d
DG
2187(add-to-list 'auto-mode-alist
2188 (cons (purecopy (concat "\\(?:\\."
f72e2fdb 2189 "rb\\|ru\\|rake\\|thor"
dd806710 2190 "\\|jbuilder\\|gemspec\\|podspec"
5cf8176d 2191 "\\|/"
f72e2fdb 2192 "\\(?:Gem\\|Rake\\|Cap\\|Thor"
cb8f50a7 2193 "\\|Vagrant\\|Guard\\|Pod\\)file"
5cf8176d 2194 "\\)\\'")) 'ruby-mode))
c7fbbc3c
CY
2195
2196;;;###autoload
2a08047a
GM
2197(dolist (name (list "ruby" "rbx" "jruby" "ruby1.9" "ruby1.8"))
2198 (add-to-list 'interpreter-mode-alist (cons (purecopy name) 'ruby-mode)))
c7fbbc3c
CY
2199
2200(provide 'ruby-mode)
2201
2202;;; ruby-mode.el ends here