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