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