Merge from emacs-24; up to 2012-12-22T19:09:52Z!rgm@gnu.org
[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-keyword-end-re
50 (if (string-match "\\_>" "ruby")
51 "\\_>"
52 "\\>"))
53
54(defconst ruby-block-beg-keywords
55 '("class" "module" "def" "if" "unless" "case" "while" "until" "for" "begin" "do")
56 "Keywords at the beginning of blocks.")
57
58(defconst ruby-block-beg-re
59 (regexp-opt ruby-block-beg-keywords)
60 "Regexp to match the beginning of blocks.")
61
62(defconst ruby-non-block-do-re
63 (concat (regexp-opt '("while" "until" "for" "rescue") t) ruby-keyword-end-re)
64 "Regexp to match keywords that nest without blocks.")
65
66(defconst ruby-indent-beg-re
db590ef6
DG
67 (concat "^\\(\\s *" (regexp-opt '("class" "module" "def")) "\\|"
68 (regexp-opt '("if" "unless" "case" "while" "until" "for" "begin"))
69 "\\)\\_>")
c7fbbc3c
CY
70 "Regexp to match where the indentation gets deeper.")
71
72(defconst ruby-modifier-beg-keywords
73 '("if" "unless" "while" "until")
74 "Modifiers that are the same as the beginning of blocks.")
75
76(defconst ruby-modifier-beg-re
77 (regexp-opt ruby-modifier-beg-keywords)
78 "Regexp to match modifiers same as the beginning of blocks.")
79
80(defconst ruby-modifier-re
81 (regexp-opt (cons "rescue" ruby-modifier-beg-keywords))
82 "Regexp to match modifiers.")
83
84(defconst ruby-block-mid-keywords
85 '("then" "else" "elsif" "when" "rescue" "ensure")
86 "Keywords where the indentation gets shallower in middle of block statements.")
87
88(defconst ruby-block-mid-re
89 (regexp-opt ruby-block-mid-keywords)
90 "Regexp to match where the indentation gets shallower in middle of block statements.")
91
92(defconst ruby-block-op-keywords
93 '("and" "or" "not")
94 "Regexp to match boolean keywords.")
95
96(defconst ruby-block-hanging-re
97 (regexp-opt (append ruby-modifier-beg-keywords ruby-block-op-keywords))
98 "Regexp to match hanging block modifiers.")
99
39675016 100(defconst ruby-block-end-re "\\_<end\\_>")
c7fbbc3c 101
8f06acce
DG
102(defconst ruby-defun-beg-re
103 '"\\(def\\|class\\|module\\)"
104 "Regexp to match the beginning of a defun, in the general sense.")
105
bb808526
DG
106(defconst ruby-singleton-class-re
107 "class\\s *<<"
108 "Regexp to match the beginning of a singleton class context.")
109
cf38dd42
SM
110(eval-and-compile
111 (defconst ruby-here-doc-beg-re
c7fbbc3c 112 "\\(<\\)<\\(-\\)?\\(\\([a-zA-Z0-9_]+\\)\\|[\"]\\([^\"]+\\)[\"]\\|[']\\([^']+\\)[']\\)"
c62792e7
DG
113 "Regexp to match the beginning of a heredoc.")
114
115 (defconst ruby-expression-expansion-re
116 "[^\\]\\(\\\\\\\\\\)*\\(#\\({[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\|\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+\\)\\)"))
c7fbbc3c
CY
117
118(defun ruby-here-doc-end-match ()
119 "Return a regexp to find the end of a heredoc.
120
121This should only be called after matching against `ruby-here-doc-beg-re'."
122 (concat "^"
123 (if (match-string 2) "[ \t]*" nil)
124 (regexp-quote
125 (or (match-string 4)
126 (match-string 5)
127 (match-string 6)))))
128
c7fbbc3c 129(defconst ruby-delimiter
39675016 130 (concat "[?$/%(){}#\"'`.:]\\|<<\\|\\[\\|\\]\\|\\_<\\("
c7fbbc3c 131 ruby-block-beg-re
39675016 132 "\\)\\_>\\|" ruby-block-end-re
c7fbbc3c
CY
133 "\\|^=begin\\|" ruby-here-doc-beg-re))
134
135(defconst ruby-negative
136 (concat "^[ \t]*\\(\\(" ruby-block-mid-re "\\)\\>\\|"
137 ruby-block-end-re "\\|}\\|\\]\\)")
138 "Regexp to match where the indentation gets shallower.")
139
140(defconst ruby-operator-re "[-,.+*/%&|^~=<>:]"
141 "Regexp to match operators.")
142
143(defconst ruby-symbol-chars "a-zA-Z0-9_"
144 "List of characters that symbol names may contain.")
145(defconst ruby-symbol-re (concat "[" ruby-symbol-chars "]")
146 "Regexp to match symbols.")
147
2122161f 148(define-abbrev-table 'ruby-mode-abbrev-table ()
ee61fe97 149 "Abbrev table in use in Ruby mode buffers.")
c7fbbc3c 150
c7fbbc3c
CY
151(defvar ruby-mode-map
152 (let ((map (make-sparse-keymap)))
c7fbbc3c
CY
153 (define-key map (kbd "M-C-b") 'ruby-backward-sexp)
154 (define-key map (kbd "M-C-f") 'ruby-forward-sexp)
155 (define-key map (kbd "M-C-p") 'ruby-beginning-of-block)
156 (define-key map (kbd "M-C-n") 'ruby-end-of-block)
c7fbbc3c 157 (define-key map (kbd "M-C-q") 'ruby-indent-exp)
0d9e2599 158 (define-key map (kbd "C-c {") 'ruby-toggle-block)
c7fbbc3c 159 map)
ee61fe97 160 "Keymap used in Ruby mode.")
c7fbbc3c
CY
161
162(defvar ruby-mode-syntax-table
163 (let ((table (make-syntax-table)))
164 (modify-syntax-entry ?\' "\"" table)
165 (modify-syntax-entry ?\" "\"" table)
166 (modify-syntax-entry ?\` "\"" table)
167 (modify-syntax-entry ?# "<" table)
168 (modify-syntax-entry ?\n ">" table)
169 (modify-syntax-entry ?\\ "\\" table)
170 (modify-syntax-entry ?$ "." table)
171 (modify-syntax-entry ?? "_" table)
172 (modify-syntax-entry ?_ "_" table)
39675016 173 (modify-syntax-entry ?: "_" table)
c7fbbc3c
CY
174 (modify-syntax-entry ?< "." table)
175 (modify-syntax-entry ?> "." table)
176 (modify-syntax-entry ?& "." table)
177 (modify-syntax-entry ?| "." table)
178 (modify-syntax-entry ?% "." table)
179 (modify-syntax-entry ?= "." table)
180 (modify-syntax-entry ?/ "." table)
181 (modify-syntax-entry ?+ "." table)
182 (modify-syntax-entry ?* "." table)
183 (modify-syntax-entry ?- "." table)
184 (modify-syntax-entry ?\; "." table)
185 (modify-syntax-entry ?\( "()" table)
186 (modify-syntax-entry ?\) ")(" table)
187 (modify-syntax-entry ?\{ "(}" table)
188 (modify-syntax-entry ?\} "){" table)
189 (modify-syntax-entry ?\[ "(]" table)
190 (modify-syntax-entry ?\] ")[" table)
191 table)
ee61fe97 192 "Syntax table to use in Ruby mode.")
c7fbbc3c
CY
193
194(defcustom ruby-indent-tabs-mode nil
ee61fe97 195 "Indentation can insert tabs in Ruby mode if this is non-nil."
c7fbbc3c
CY
196 :type 'boolean :group 'ruby)
197
198(defcustom ruby-indent-level 2
ee61fe97 199 "Indentation of Ruby statements."
c7fbbc3c
CY
200 :type 'integer :group 'ruby)
201
202(defcustom ruby-comment-column 32
203 "Indentation column of comments."
204 :type 'integer :group 'ruby)
205
206(defcustom ruby-deep-arglist t
207 "Deep indent lists in parenthesis when non-nil.
208Also ignores spaces after parenthesis when 'space."
209 :group 'ruby)
210
211(defcustom ruby-deep-indent-paren '(?\( ?\[ ?\] t)
ee61fe97
JB
212 "Deep indent lists in parenthesis when non-nil.
213The value t means continuous line.
c7fbbc3c
CY
214Also ignores spaces after parenthesis when 'space."
215 :group 'ruby)
216
217(defcustom ruby-deep-indent-paren-style 'space
218 "Default deep indent style."
219 :options '(t nil space) :group 'ruby)
220
221(defcustom ruby-encoding-map '((shift_jis . cp932) (shift-jis . cp932))
222 "Alist to map encoding name from Emacs to Ruby."
223 :group 'ruby)
224
225(defcustom ruby-insert-encoding-magic-comment t
ee61fe97 226 "Insert a magic Emacs 'coding' comment upon save if this is non-nil."
c7fbbc3c
CY
227 :type 'boolean :group 'ruby)
228
229(defcustom ruby-use-encoding-map t
230 "Use `ruby-encoding-map' to set encoding magic comment if this is non-nil."
231 :type 'boolean :group 'ruby)
232
233;; Safe file variables
234(put 'ruby-indent-tabs-mode 'safe-local-variable 'booleanp)
235(put 'ruby-indent-level 'safe-local-variable 'integerp)
236(put 'ruby-comment-column 'safe-local-variable 'integerp)
237(put 'ruby-deep-arglist 'safe-local-variable 'booleanp)
238
239(defun ruby-imenu-create-index-in-block (prefix beg end)
240 "Create an imenu index of methods inside a block."
241 (let ((index-alist '()) (case-fold-search nil)
242 name next pos decl sing)
243 (goto-char beg)
244 (while (re-search-forward "^\\s *\\(\\(class\\s +\\|\\(class\\s *<<\\s *\\)\\|module\\s +\\)\\([^\(<\n ]+\\)\\|\\(def\\|alias\\)\\s +\\([^\(\n ]+\\)\\)" end t)
245 (setq sing (match-beginning 3))
246 (setq decl (match-string 5))
247 (setq next (match-end 0))
248 (setq name (or (match-string 4) (match-string 6)))
249 (setq pos (match-beginning 0))
250 (cond
251 ((string= "alias" decl)
252 (if prefix (setq name (concat prefix name)))
253 (push (cons name pos) index-alist))
254 ((string= "def" decl)
255 (if prefix
256 (setq name
257 (cond
258 ((string-match "^self\." name)
259 (concat (substring prefix 0 -1) (substring name 4)))
260 (t (concat prefix name)))))
261 (push (cons name pos) index-alist)
262 (ruby-accurate-end-of-block end))
263 (t
264 (if (string= "self" name)
265 (if prefix (setq name (substring prefix 0 -1)))
266 (if prefix (setq name (concat (substring prefix 0 -1) "::" name)))
267 (push (cons name pos) index-alist))
268 (ruby-accurate-end-of-block end)
269 (setq beg (point))
270 (setq index-alist
271 (nconc (ruby-imenu-create-index-in-block
272 (concat name (if sing "." "#"))
273 next beg) index-alist))
274 (goto-char beg))))
275 index-alist))
276
277(defun ruby-imenu-create-index ()
278 "Create an imenu index of all methods in the buffer."
279 (nreverse (ruby-imenu-create-index-in-block nil (point-min) nil)))
280
281(defun ruby-accurate-end-of-block (&optional end)
282 "TODO: document."
283 (let (state
284 (end (or end (point-max))))
285 (while (and (setq state (apply 'ruby-parse-partial end state))
286 (>= (nth 2 state) 0) (< (point) end)))))
287
288(defun ruby-mode-variables ()
ee61fe97 289 "Set up initial buffer-local variables for Ruby mode."
c7fbbc3c
CY
290 (set-syntax-table ruby-mode-syntax-table)
291 (setq local-abbrev-table ruby-mode-abbrev-table)
292 (setq indent-tabs-mode ruby-indent-tabs-mode)
293 (set (make-local-variable 'indent-line-function) 'ruby-indent-line)
294 (set (make-local-variable 'require-final-newline) t)
295 (set (make-local-variable 'comment-start) "# ")
296 (set (make-local-variable 'comment-end) "")
297 (set (make-local-variable 'comment-column) ruby-comment-column)
298 (set (make-local-variable 'comment-start-skip) "#+ *")
299 (set (make-local-variable 'parse-sexp-ignore-comments) t)
300 (set (make-local-variable 'parse-sexp-lookup-properties) t)
301 (set (make-local-variable 'paragraph-start) (concat "$\\|" page-delimiter))
302 (set (make-local-variable 'paragraph-separate) paragraph-start)
303 (set (make-local-variable 'paragraph-ignore-fill-prefix) t))
304
305(defun ruby-mode-set-encoding ()
306 "Insert a magic comment header with the proper encoding if necessary."
307 (save-excursion
308 (widen)
309 (goto-char (point-min))
310 (when (re-search-forward "[^\0-\177]" nil t)
311 (goto-char (point-min))
312 (let ((coding-system
313 (or coding-system-for-write
314 buffer-file-coding-system)))
315 (if coding-system
316 (setq coding-system
317 (or (coding-system-get coding-system 'mime-charset)
318 (coding-system-change-eol-conversion coding-system nil))))
319 (setq coding-system
320 (if coding-system
321 (symbol-name
322 (or (and ruby-use-encoding-map
323 (cdr (assq coding-system ruby-encoding-map)))
324 coding-system))
325 "ascii-8bit"))
cc9c9831 326 (if (looking-at "^#!") (beginning-of-line 2))
c7fbbc3c
CY
327 (cond ((looking-at "\\s *#.*-\*-\\s *\\(en\\)?coding\\s *:\\s *\\([-a-z0-9_]*\\)\\s *\\(;\\|-\*-\\)")
328 (unless (string= (match-string 2) coding-system)
329 (goto-char (match-beginning 2))
330 (delete-region (point) (match-end 2))
331 (and (looking-at "-\*-")
332 (let ((n (skip-chars-backward " ")))
333 (cond ((= n 0) (insert " ") (backward-char))
334 ((= n -1) (insert " "))
335 ((forward-char)))))
336 (insert coding-system)))
337 ((looking-at "\\s *#.*coding\\s *[:=]"))
338 (t (when ruby-insert-encoding-magic-comment
339 (insert "# -*- coding: " coding-system " -*-\n"))))))))
340
341(defun ruby-current-indentation ()
342 "Return the indentation level of current line."
343 (save-excursion
344 (beginning-of-line)
345 (back-to-indentation)
346 (current-column)))
347
cf38dd42 348(defun ruby-indent-line (&optional ignored)
ee61fe97 349 "Correct the indentation of the current Ruby line."
c7fbbc3c
CY
350 (interactive)
351 (ruby-indent-to (ruby-calculate-indent)))
352
353(defun ruby-indent-to (column)
354 "Indent the current line to COLUMN."
355 (when column
356 (let (shift top beg)
357 (and (< column 0) (error "invalid nest"))
358 (setq shift (current-column))
359 (beginning-of-line)
360 (setq beg (point))
361 (back-to-indentation)
362 (setq top (current-column))
363 (skip-chars-backward " \t")
364 (if (>= shift top) (setq shift (- shift top))
365 (setq shift 0))
366 (if (and (bolp)
367 (= column top))
368 (move-to-column (+ column shift))
369 (move-to-column top)
370 (delete-region beg (point))
371 (beginning-of-line)
372 (indent-to column)
373 (move-to-column (+ column shift))))))
374
375(defun ruby-special-char-p (&optional pos)
376 "Return t if the character before POS is a special character.
377If omitted, POS defaults to the current point.
378Special characters are `?', `$', `:' when preceded by whitespace,
379and `\\' when preceded by `?'."
380 (setq pos (or pos (point)))
381 (let ((c (char-before pos)) (b (and (< (point-min) pos)
382 (char-before (1- pos)))))
383 (cond ((or (eq c ??) (eq c ?$)))
384 ((and (eq c ?:) (or (not b) (eq (char-syntax b) ? ))))
385 ((eq c ?\\) (eq b ??)))))
386
9cd80478
DG
387(defun ruby-singleton-class-p (&optional pos)
388 (save-excursion
389 (when pos (goto-char pos))
390 (forward-word -1)
391 (and (or (bolp) (not (eq (char-before (point)) ?_)))
bb808526 392 (looking-at ruby-singleton-class-re))))
9cd80478 393
c7fbbc3c 394(defun ruby-expr-beg (&optional option)
8619323f
DG
395 "Check if point is possibly at the beginning of an expression.
396OPTION specifies the type of the expression.
397Can be one of `heredoc', `modifier', `expr-qstr', `expr-re'."
c7fbbc3c
CY
398 (save-excursion
399 (store-match-data nil)
9cd80478
DG
400 (let ((space (skip-chars-backward " \t"))
401 (start (point)))
c7fbbc3c
CY
402 (cond
403 ((bolp) t)
404 ((progn
405 (forward-char -1)
406 (and (looking-at "\\?")
407 (or (eq (char-syntax (char-before (point))) ?w)
408 (ruby-special-char-p))))
409 nil)
8619323f
DG
410 ((looking-at ruby-operator-re))
411 ((eq option 'heredoc)
412 (and (< space 0) (not (ruby-singleton-class-p start))))
413 ((or (looking-at "[\\[({,;]")
c7fbbc3c
CY
414 (and (looking-at "[!?]")
415 (or (not (eq option 'modifier))
416 (bolp)
417 (save-excursion (forward-char -1) (looking-at "\\Sw$"))))
418 (and (looking-at ruby-symbol-re)
419 (skip-chars-backward ruby-symbol-chars)
420 (cond
421 ((looking-at (regexp-opt
422 (append ruby-block-beg-keywords
423 ruby-block-op-keywords
424 ruby-block-mid-keywords)
425 'words))
426 (goto-char (match-end 0))
9cd80478 427 (not (looking-at "\\s_\\|!")))
c7fbbc3c
CY
428 ((eq option 'expr-qstr)
429 (looking-at "[a-zA-Z][a-zA-z0-9_]* +%[^ \t]"))
430 ((eq option 'expr-re)
431 (looking-at "[a-zA-Z][a-zA-z0-9_]* +/[^ \t]"))
432 (t nil)))))))))
433
434(defun ruby-forward-string (term &optional end no-error expand)
435 "TODO: document."
436 (let ((n 1) (c (string-to-char term))
437 (re (if expand
438 (concat "[^\\]\\(\\\\\\\\\\)*\\([" term "]\\|\\(#{\\)\\)")
439 (concat "[^\\]\\(\\\\\\\\\\)*[" term "]"))))
440 (while (and (re-search-forward re end no-error)
441 (if (match-beginning 3)
442 (ruby-forward-string "}{" end no-error nil)
443 (> (setq n (if (eq (char-before (point)) c)
444 (1- n) (1+ n))) 0)))
445 (forward-char -1))
446 (cond ((zerop n))
447 (no-error nil)
448 ((error "unterminated string")))))
449
450(defun ruby-deep-indent-paren-p (c)
ee61fe97 451 "TODO: document."
c7fbbc3c
CY
452 (cond ((listp ruby-deep-indent-paren)
453 (let ((deep (assoc c ruby-deep-indent-paren)))
454 (cond (deep
455 (or (cdr deep) ruby-deep-indent-paren-style))
456 ((memq c ruby-deep-indent-paren)
457 ruby-deep-indent-paren-style))))
458 ((eq c ruby-deep-indent-paren) ruby-deep-indent-paren-style)
459 ((eq c ?\( ) ruby-deep-arglist)))
460
461(defun ruby-parse-partial (&optional end in-string nest depth pcol indent)
ee61fe97 462 "TODO: document throughout function body."
c7fbbc3c
CY
463 (or depth (setq depth 0))
464 (or indent (setq indent 0))
465 (when (re-search-forward ruby-delimiter end 'move)
466 (let ((pnt (point)) w re expand)
467 (goto-char (match-beginning 0))
468 (cond
469 ((and (memq (char-before) '(?@ ?$)) (looking-at "\\sw"))
470 (goto-char pnt))
471 ((looking-at "[\"`]") ;skip string
472 (cond
473 ((and (not (eobp))
474 (ruby-forward-string (buffer-substring (point) (1+ (point))) end t t))
475 nil)
476 (t
477 (setq in-string (point))
478 (goto-char end))))
479 ((looking-at "'")
480 (cond
481 ((and (not (eobp))
482 (re-search-forward "[^\\]\\(\\\\\\\\\\)*'" end t))
483 nil)
484 (t
485 (setq in-string (point))
486 (goto-char end))))
487 ((looking-at "/=")
488 (goto-char pnt))
489 ((looking-at "/")
490 (cond
491 ((and (not (eobp)) (ruby-expr-beg 'expr-re))
492 (if (ruby-forward-string "/" end t t)
493 nil
494 (setq in-string (point))
495 (goto-char end)))
496 (t
497 (goto-char pnt))))
498 ((looking-at "%")
499 (cond
500 ((and (not (eobp))
501 (ruby-expr-beg 'expr-qstr)
502 (not (looking-at "%="))
503 (looking-at "%[QqrxWw]?\\([^a-zA-Z0-9 \t\n]\\)"))
504 (goto-char (match-beginning 1))
505 (setq expand (not (memq (char-before) '(?q ?w))))
506 (setq w (match-string 1))
507 (cond
508 ((string= w "[") (setq re "]["))
509 ((string= w "{") (setq re "}{"))
510 ((string= w "(") (setq re ")("))
511 ((string= w "<") (setq re "><"))
512 ((and expand (string= w "\\"))
513 (setq w (concat "\\" w))))
514 (unless (cond (re (ruby-forward-string re end t expand))
515 (expand (ruby-forward-string w end t t))
516 (t (re-search-forward
517 (if (string= w "\\")
518 "\\\\[^\\]*\\\\"
519 (concat "[^\\]\\(\\\\\\\\\\)*" w))
520 end t)))
521 (setq in-string (point))
522 (goto-char end)))
523 (t
524 (goto-char pnt))))
525 ((looking-at "\\?") ;skip ?char
526 (cond
527 ((and (ruby-expr-beg)
528 (looking-at "?\\(\\\\C-\\|\\\\M-\\)*\\\\?."))
529 (goto-char (match-end 0)))
530 (t
531 (goto-char pnt))))
532 ((looking-at "\\$") ;skip $char
533 (goto-char pnt)
534 (forward-char 1))
535 ((looking-at "#") ;skip comment
536 (forward-line 1)
537 (goto-char (point))
538 )
539 ((looking-at "[\\[{(]")
540 (let ((deep (ruby-deep-indent-paren-p (char-after))))
541 (if (and deep (or (not (eq (char-after) ?\{)) (ruby-expr-beg)))
542 (progn
543 (and (eq deep 'space) (looking-at ".\\s +[^# \t\n]")
544 (setq pnt (1- (match-end 0))))
545 (setq nest (cons (cons (char-after (point)) pnt) nest))
546 (setq pcol (cons (cons pnt depth) pcol))
547 (setq depth 0))
548 (setq nest (cons (cons (char-after (point)) pnt) nest))
549 (setq depth (1+ depth))))
550 (goto-char pnt)
551 )
552 ((looking-at "[])}]")
553 (if (ruby-deep-indent-paren-p (matching-paren (char-after)))
554 (setq depth (cdr (car pcol)) pcol (cdr pcol))
555 (setq depth (1- depth)))
556 (setq nest (cdr nest))
557 (goto-char pnt))
558 ((looking-at ruby-block-end-re)
559 (if (or (and (not (bolp))
560 (progn
561 (forward-char -1)
562 (setq w (char-after (point)))
563 (or (eq ?_ w)
564 (eq ?. w))))
565 (progn
566 (goto-char pnt)
567 (setq w (char-after (point)))
568 (or (eq ?_ w)
569 (eq ?! w)
570 (eq ?? w))))
571 nil
572 (setq nest (cdr nest))
573 (setq depth (1- depth)))
574 (goto-char pnt))
575 ((looking-at "def\\s +[^(\n;]*")
576 (if (or (bolp)
577 (progn
578 (forward-char -1)
579 (not (eq ?_ (char-after (point))))))
580 (progn
581 (setq nest (cons (cons nil pnt) nest))
582 (setq depth (1+ depth))))
583 (goto-char (match-end 0)))
39675016 584 ((looking-at (concat "\\_<\\(" ruby-block-beg-re "\\)\\_>"))
c7fbbc3c
CY
585 (and
586 (save-match-data
587 (or (not (looking-at (concat "do" ruby-keyword-end-re)))
588 (save-excursion
589 (back-to-indentation)
590 (not (looking-at ruby-non-block-do-re)))))
591 (or (bolp)
592 (progn
593 (forward-char -1)
594 (setq w (char-after (point)))
595 (not (or (eq ?_ w)
596 (eq ?. w)))))
597 (goto-char pnt)
7132e457 598 (not (eq ?! (char-after (point))))
c7fbbc3c
CY
599 (skip-chars-forward " \t")
600 (goto-char (match-beginning 0))
601 (or (not (looking-at ruby-modifier-re))
602 (ruby-expr-beg 'modifier))
603 (goto-char pnt)
604 (setq nest (cons (cons nil pnt) nest))
605 (setq depth (1+ depth)))
606 (goto-char pnt))
607 ((looking-at ":\\(['\"]\\)")
608 (goto-char (match-beginning 1))
c28662a8 609 (ruby-forward-string (match-string 1) end t))
11473529 610 ((looking-at ":\\([-,.+*/%&|^~<>]=?\\|===?\\|<=>\\|![~=]?\\)")
c7fbbc3c
CY
611 (goto-char (match-end 0)))
612 ((looking-at ":\\([a-zA-Z_][a-zA-Z_0-9]*[!?=]?\\)?")
613 (goto-char (match-end 0)))
614 ((or (looking-at "\\.\\.\\.?")
615 (looking-at "\\.[0-9]+")
616 (looking-at "\\.[a-zA-Z_0-9]+")
617 (looking-at "\\."))
618 (goto-char (match-end 0)))
619 ((looking-at "^=begin")
620 (if (re-search-forward "^=end" end t)
621 (forward-line 1)
622 (setq in-string (match-end 0))
623 (goto-char end)))
624 ((looking-at "<<")
625 (cond
626 ((and (ruby-expr-beg 'heredoc)
627 (looking-at "<<\\(-\\)?\\(\\([\"'`]\\)\\([^\n]+?\\)\\3\\|\\(?:\\sw\\|\\s_\\)+\\)"))
628 (setq re (regexp-quote (or (match-string 4) (match-string 2))))
629 (if (match-beginning 1) (setq re (concat "\\s *" re)))
630 (let* ((id-end (goto-char (match-end 0)))
e180ab9f 631 (line-end-position (point-at-eol))
c7fbbc3c
CY
632 (state (list in-string nest depth pcol indent)))
633 ;; parse the rest of the line
634 (while (and (> line-end-position (point))
635 (setq state (apply 'ruby-parse-partial
636 line-end-position state))))
637 (setq in-string (car state)
638 nest (nth 1 state)
639 depth (nth 2 state)
640 pcol (nth 3 state)
641 indent (nth 4 state))
642 ;; skip heredoc section
643 (if (re-search-forward (concat "^" re "$") end 'move)
644 (forward-line 1)
645 (setq in-string id-end)
646 (goto-char end))))
647 (t
648 (goto-char pnt))))
649 ((looking-at "^__END__$")
650 (goto-char pnt))
651 ((and (looking-at ruby-here-doc-beg-re)
652 (boundp 'ruby-indent-point))
653 (if (re-search-forward (ruby-here-doc-end-match)
654 ruby-indent-point t)
655 (forward-line 1)
656 (setq in-string (match-end 0))
657 (goto-char ruby-indent-point)))
658 (t
659 (error (format "bad string %s"
660 (buffer-substring (point) pnt)
661 ))))))
662 (list in-string nest depth pcol))
663
664(defun ruby-parse-region (start end)
665 "TODO: document."
666 (let (state)
667 (save-excursion
668 (if start
669 (goto-char start)
670 (ruby-beginning-of-indent))
671 (save-restriction
672 (narrow-to-region (point) end)
673 (while (and (> end (point))
674 (setq state (apply 'ruby-parse-partial end state))))))
675 (list (nth 0 state) ; in-string
676 (car (nth 1 state)) ; nest
677 (nth 2 state) ; depth
678 (car (car (nth 3 state))) ; pcol
679 ;(car (nth 5 state)) ; indent
680 )))
681
682(defun ruby-indent-size (pos nest)
ee61fe97 683 "Return the indentation level in spaces NEST levels deeper than POS."
c7fbbc3c
CY
684 (+ pos (* (or nest 1) ruby-indent-level)))
685
686(defun ruby-calculate-indent (&optional parse-start)
ee61fe97 687 "Return the proper indentation level of the current line."
c7fbbc3c
CY
688 ;; TODO: Document body
689 (save-excursion
690 (beginning-of-line)
691 (let ((ruby-indent-point (point))
692 (case-fold-search nil)
cf38dd42 693 state eol begin op-end
c7fbbc3c
CY
694 (paren (progn (skip-syntax-forward " ")
695 (and (char-after) (matching-paren (char-after)))))
696 (indent 0))
697 (if parse-start
698 (goto-char parse-start)
699 (ruby-beginning-of-indent)
700 (setq parse-start (point)))
701 (back-to-indentation)
702 (setq indent (current-column))
703 (setq state (ruby-parse-region parse-start ruby-indent-point))
704 (cond
705 ((nth 0 state) ; within string
706 (setq indent nil)) ; do nothing
707 ((car (nth 1 state)) ; in paren
708 (goto-char (setq begin (cdr (nth 1 state))))
709 (let ((deep (ruby-deep-indent-paren-p (car (nth 1 state)))))
710 (if deep
711 (cond ((and (eq deep t) (eq (car (nth 1 state)) paren))
712 (skip-syntax-backward " ")
713 (setq indent (1- (current-column))))
714 ((let ((s (ruby-parse-region (point) ruby-indent-point)))
715 (and (nth 2 s) (> (nth 2 s) 0)
716 (or (goto-char (cdr (nth 1 s))) t)))
717 (forward-word -1)
718 (setq indent (ruby-indent-size (current-column)
719 (nth 2 state))))
720 (t
721 (setq indent (current-column))
722 (cond ((eq deep 'space))
723 (paren (setq indent (1- indent)))
724 (t (setq indent (ruby-indent-size (1- indent) 1))))))
725 (if (nth 3 state) (goto-char (nth 3 state))
726 (goto-char parse-start) (back-to-indentation))
727 (setq indent (ruby-indent-size (current-column) (nth 2 state))))
728 (and (eq (car (nth 1 state)) paren)
729 (ruby-deep-indent-paren-p (matching-paren paren))
730 (search-backward (char-to-string paren))
731 (setq indent (current-column)))))
732 ((and (nth 2 state) (> (nth 2 state) 0)) ; in nest
733 (if (null (cdr (nth 1 state)))
734 (error "invalid nest"))
735 (goto-char (cdr (nth 1 state)))
736 (forward-word -1) ; skip back a keyword
737 (setq begin (point))
738 (cond
739 ((looking-at "do\\>[^_]") ; iter block is a special case
740 (if (nth 3 state) (goto-char (nth 3 state))
741 (goto-char parse-start) (back-to-indentation))
742 (setq indent (ruby-indent-size (current-column) (nth 2 state))))
743 (t
744 (setq indent (+ (current-column) ruby-indent-level)))))
ee61fe97 745
c7fbbc3c
CY
746 ((and (nth 2 state) (< (nth 2 state) 0)) ; in negative nest
747 (setq indent (ruby-indent-size (current-column) (nth 2 state)))))
748 (when indent
749 (goto-char ruby-indent-point)
750 (end-of-line)
751 (setq eol (point))
752 (beginning-of-line)
753 (cond
754 ((and (not (ruby-deep-indent-paren-p paren))
755 (re-search-forward ruby-negative eol t))
756 (and (not (eq ?_ (char-after (match-end 0))))
757 (setq indent (- indent ruby-indent-level))))
758 ((and
759 (save-excursion
760 (beginning-of-line)
761 (not (bobp)))
762 (or (ruby-deep-indent-paren-p t)
763 (null (car (nth 1 state)))))
764 ;; goto beginning of non-empty no-comment line
765 (let (end done)
766 (while (not done)
767 (skip-chars-backward " \t\n")
768 (setq end (point))
769 (beginning-of-line)
770 (if (re-search-forward "^\\s *#" end t)
771 (beginning-of-line)
772 (setq done t))))
c7fbbc3c
CY
773 (end-of-line)
774 ;; skip the comment at the end
775 (skip-chars-backward " \t")
776 (let (end (pos (point)))
777 (beginning-of-line)
778 (while (and (re-search-forward "#" pos t)
779 (setq end (1- (point)))
780 (or (ruby-special-char-p end)
781 (and (setq state (ruby-parse-region parse-start end))
782 (nth 0 state))))
783 (setq end nil))
784 (goto-char (or end pos))
785 (skip-chars-backward " \t")
786 (setq begin (if (and end (nth 0 state)) pos (cdr (nth 1 state))))
787 (setq state (ruby-parse-region parse-start (point))))
788 (or (bobp) (forward-char -1))
789 (and
790 (or (and (looking-at ruby-symbol-re)
791 (skip-chars-backward ruby-symbol-chars)
792 (looking-at (concat "\\<\\(" ruby-block-hanging-re "\\)\\>"))
793 (not (eq (point) (nth 3 state)))
794 (save-excursion
795 (goto-char (match-end 0))
796 (not (looking-at "[a-z_]"))))
797 (and (looking-at ruby-operator-re)
798 (not (ruby-special-char-p))
dfbd787f 799 ;; Operator at the end of line.
c7fbbc3c
CY
800 (let ((c (char-after (point))))
801 (and
802;; (or (null begin)
803;; (save-excursion
804;; (goto-char begin)
805;; (skip-chars-forward " \t")
806;; (not (or (eolp) (looking-at "#")
807;; (and (eq (car (nth 1 state)) ?{)
808;; (looking-at "|"))))))
e636fafe 809 ;; Not a regexp or percent literal.
dfbd787f
SM
810 (null (nth 0 (ruby-parse-region (or begin parse-start)
811 (point))))
c7fbbc3c
CY
812 (or (not (eq ?| (char-after (point))))
813 (save-excursion
814 (or (eolp) (forward-char -1))
815 (cond
816 ((search-backward "|" nil t)
817 (skip-chars-backward " \t\n")
818 (and (not (eolp))
819 (progn
820 (forward-char -1)
821 (not (looking-at "{")))
822 (progn
823 (forward-word -1)
824 (not (looking-at "do\\>[^_]")))))
825 (t t))))
826 (not (eq ?, c))
827 (setq op-end t)))))
828 (setq indent
829 (cond
830 ((and
831 (null op-end)
832 (not (looking-at (concat "\\<\\(" ruby-block-hanging-re "\\)\\>")))
833 (eq (ruby-deep-indent-paren-p t) 'space)
834 (not (bobp)))
835 (widen)
836 (goto-char (or begin parse-start))
837 (skip-syntax-forward " ")
838 (current-column))
839 ((car (nth 1 state)) indent)
840 (t
841 (+ indent ruby-indent-level))))))))
842 (goto-char ruby-indent-point)
843 (beginning-of-line)
844 (skip-syntax-forward " ")
845 (if (looking-at "\\.[^.]")
846 (+ indent ruby-indent-level)
847 indent))))
848
c7fbbc3c
CY
849(defun ruby-beginning-of-defun (&optional arg)
850 "Move backward to the beginning of the current top-level defun.
ee61fe97 851With ARG, move backward multiple defuns. Negative ARG means
c7fbbc3c
CY
852move forward."
853 (interactive "p")
8f06acce 854 (and (re-search-backward (concat "^\\s *" ruby-defun-beg-re "\\_>")
d81ceaaf 855 nil t (or arg 1))
c7fbbc3c
CY
856 (beginning-of-line)))
857
858(defun ruby-end-of-defun (&optional arg)
859 "Move forward to the end of the current top-level defun.
ee61fe97 860With ARG, move forward multiple defuns. Negative ARG means
c7fbbc3c
CY
861move backward."
862 (interactive "p")
8f06acce
DG
863 (ruby-forward-sexp)
864 (when (looking-back (concat "^\\s *" ruby-block-end-re))
865 (forward-line 1)))
c7fbbc3c
CY
866
867(defun ruby-beginning-of-indent ()
0ba2d4b6
DG
868 "Backtrack to a line which can be used as a reference for
869calculating indentation on the lines after it."
870 (while (and (re-search-backward ruby-indent-beg-re nil 'move)
871 (if (ruby-in-ppss-context-p 'anything)
872 t
873 ;; We can stop, then.
874 (beginning-of-line)))))
c7fbbc3c
CY
875
876(defun ruby-move-to-block (n)
5e9419e8
DG
877 "Move to the beginning (N < 0) or the end (N > 0) of the
878current block, a sibling block, or an outer block. Do that (abs N) times."
7132e457 879 (let ((signum (if (> n 0) 1 -1))
5e9419e8 880 (backward (< n 0))
7132e457
DG
881 (depth (or (nth 2 (ruby-parse-region (line-beginning-position)
882 (line-end-position)))
883 0))
884 down done)
885 (when (< (* depth signum) 0)
886 ;; Moving end -> end or beginning -> beginning.
887 (setq depth 0))
5e9419e8
DG
888 (dotimes (_ (abs n))
889 (setq done nil)
890 (setq down (save-excursion
891 (back-to-indentation)
892 ;; There is a block start or block end keyword on this
893 ;; line, don't need to look for another block.
894 (and (re-search-forward
895 (if backward ruby-block-end-re
896 (concat "\\_<\\(" ruby-block-beg-re "\\)\\_>"))
897 (line-end-position) t)
898 (not (nth 8 (syntax-ppss))))))
899 (while (and (not done) (not (if backward (bobp) (eobp))))
900 (forward-line signum)
c7fbbc3c 901 (cond
5e9419e8
DG
902 ;; Skip empty and commented out lines.
903 ((looking-at "^\\s *$"))
904 ((looking-at "^\\s *#"))
905 ;; Skip block comments;
906 ((and (not backward) (looking-at "^=begin\\>"))
907 (re-search-forward "^=end\\>"))
908 ((and backward (looking-at "^=end\\>"))
909 (re-search-backward "^=begin\\>"))
53ca88c4
DG
910 ;; Jump over a multiline literal.
911 ((ruby-in-ppss-context-p 'string)
912 (goto-char (nth 8 (syntax-ppss)))
913 (unless backward
914 (forward-sexp)
915 (when (bolp) (forward-char -1)))) ; After a heredoc.
5e9419e8 916 (t
53ca88c4
DG
917 (let ((state (ruby-parse-region (point) (line-end-position))))
918 (unless (car state) ; Line ends with unfinished string.
919 (setq depth (+ (nth 2 state) depth))))
5e9419e8 920 (cond
7831fb1b 921 ;; Deeper indentation, we found a block.
5e9419e8 922 ;; FIXME: We can't recognize empty blocks this way.
7132e457 923 ((> (* signum depth) 0)
5e9419e8
DG
924 (setq down t))
925 ;; Block found, and same indentation as when started, stop.
7132e457 926 ((and down (zerop depth))
5e9419e8
DG
927 (setq done t))
928 ;; Shallower indentation, means outer block, can stop now.
7132e457 929 ((< (* signum depth) 0)
5e9419e8
DG
930 (setq done t)))))
931 (if done
932 (save-excursion
933 (back-to-indentation)
934 ;; Not really at the first or last line of the block, move on.
935 (if (looking-at (concat "\\<\\(" ruby-block-mid-re "\\)\\>"))
936 (setq done nil))))))
d1e1e53d 937 (back-to-indentation)))
c7fbbc3c
CY
938
939(defun ruby-beginning-of-block (&optional arg)
940 "Move backward to the beginning of the current block.
941With ARG, move up multiple blocks."
942 (interactive "p")
943 (ruby-move-to-block (- (or arg 1))))
944
945(defun ruby-end-of-block (&optional arg)
946 "Move forward to the end of the current block.
947With ARG, move out of multiple blocks."
5e9419e8 948 (interactive "p")
c7fbbc3c
CY
949 (ruby-move-to-block (or arg 1)))
950
951(defun ruby-forward-sexp (&optional arg)
952 "Move forward across one balanced expression (sexp).
ee61fe97 953With ARG, do it many times. Negative ARG means move backward."
c7fbbc3c
CY
954 ;; TODO: Document body
955 (interactive "p")
956 (if (and (numberp arg) (< arg 0))
957 (ruby-backward-sexp (- arg))
958 (let ((i (or arg 1)))
959 (condition-case nil
960 (while (> i 0)
961 (skip-syntax-forward " ")
cc9c9831 962 (if (looking-at ",\\s *") (goto-char (match-end 0)))
c7fbbc3c
CY
963 (cond ((looking-at "\\?\\(\\\\[CM]-\\)*\\\\?\\S ")
964 (goto-char (match-end 0)))
965 ((progn
966 (skip-chars-forward ",.:;|&^~=!?\\+\\-\\*")
967 (looking-at "\\s("))
968 (goto-char (scan-sexps (point) 1)))
969 ((and (looking-at (concat "\\<\\(" ruby-block-beg-re "\\)\\>"))
970 (not (eq (char-before (point)) ?.))
971 (not (eq (char-before (point)) ?:)))
972 (ruby-end-of-block)
973 (forward-word 1))
974 ((looking-at "\\(\\$\\|@@?\\)?\\sw")
975 (while (progn
976 (while (progn (forward-word 1) (looking-at "_")))
977 (cond ((looking-at "::") (forward-char 2) t)
978 ((> (skip-chars-forward ".") 0))
979 ((looking-at "\\?\\|!\\(=[~=>]\\|[^~=]\\)")
980 (forward-char 1) nil)))))
981 ((let (state expr)
982 (while
983 (progn
984 (setq expr (or expr (ruby-expr-beg)
985 (looking-at "%\\sw?\\Sw\\|[\"'`/]")))
986 (nth 1 (setq state (apply 'ruby-parse-partial nil state))))
987 (setq expr t)
988 (skip-chars-forward "<"))
989 (not expr))))
990 (setq i (1- i)))
991 ((error) (forward-word 1)))
992 i)))
993
994(defun ruby-backward-sexp (&optional arg)
995 "Move backward across one balanced expression (sexp).
ee61fe97 996With ARG, do it many times. Negative ARG means move forward."
c7fbbc3c
CY
997 ;; TODO: Document body
998 (interactive "p")
999 (if (and (numberp arg) (< arg 0))
1000 (ruby-forward-sexp (- arg))
1001 (let ((i (or arg 1)))
1002 (condition-case nil
1003 (while (> i 0)
1004 (skip-chars-backward " \t\n,.:;|&^~=!?\\+\\-\\*")
1005 (forward-char -1)
1006 (cond ((looking-at "\\s)")
1007 (goto-char (scan-sexps (1+ (point)) -1))
1008 (case (char-before)
1009 (?% (forward-char -1))
0adf5618 1010 ((?q ?Q ?w ?W ?r ?x)
c7fbbc3c
CY
1011 (if (eq (char-before (1- (point))) ?%) (forward-char -2))))
1012 nil)
1013 ((looking-at "\\s\"\\|\\\\\\S_")
1014 (let ((c (char-to-string (char-before (match-end 0)))))
1015 (while (and (search-backward c)
1016 (eq (logand (skip-chars-backward "\\") 1)
1017 1))))
1018 nil)
1019 ((looking-at "\\s.\\|\\s\\")
1020 (if (ruby-special-char-p) (forward-char -1)))
1021 ((looking-at "\\s(") nil)
1022 (t
1023 (forward-char 1)
1024 (while (progn (forward-word -1)
1025 (case (char-before)
1026 (?_ t)
1027 (?. (forward-char -1) t)
1028 ((?$ ?@)
1029 (forward-char -1)
1030 (and (eq (char-before) (char-after)) (forward-char -1)))
1031 (?:
1032 (forward-char -1)
1033 (eq (char-before) :)))))
1034 (if (looking-at ruby-block-end-re)
1035 (ruby-beginning-of-block))
1036 nil))
1037 (setq i (1- i)))
1038 ((error)))
1039 i)))
1040
cf38dd42
SM
1041(defun ruby-indent-exp (&optional ignored)
1042 "Indent each line in the balanced expression following the point."
c7fbbc3c
CY
1043 (interactive "*P")
1044 (let ((here (point-marker)) start top column (nest t))
1045 (set-marker-insertion-type here t)
1046 (unwind-protect
1047 (progn
1048 (beginning-of-line)
1049 (setq start (point) top (current-indentation))
1050 (while (and (not (eobp))
1051 (progn
1052 (setq column (ruby-calculate-indent start))
1053 (cond ((> column top)
1054 (setq nest t))
1055 ((and (= column top) nest)
1056 (setq nest nil) t))))
1057 (ruby-indent-to column)
1058 (beginning-of-line 2)))
1059 (goto-char here)
1060 (set-marker here nil))))
1061
1062(defun ruby-add-log-current-method ()
ee61fe97 1063 "Return the current method name as a string.
c7fbbc3c
CY
1064This string includes all namespaces.
1065
1066For example:
1067
1068 #exit
1069 String#gsub
1070 Net::HTTP#active?
5745cae6 1071 File.open
c7fbbc3c
CY
1072
1073See `add-log-current-defun-function'."
c7fbbc3c
CY
1074 (condition-case nil
1075 (save-excursion
71a048c1
DG
1076 (let* ((indent 0) mname mlist
1077 (start (point))
1078 (make-definition-re
1079 (lambda (re)
1080 (concat "^[ \t]*" re "[ \t]+"
1081 "\\("
1082 ;; \\. and :: for class methods
1083 "\\([A-Za-z_]" ruby-symbol-re "*\\|\\.\\|::" "\\)"
1084 "+\\)")))
1085 (definition-re (funcall make-definition-re ruby-defun-beg-re))
1086 (module-re (funcall make-definition-re "\\(class\\|module\\)")))
5745cae6 1087 ;; Get the current method definition (or class/module).
bb808526
DG
1088 (when (re-search-backward definition-re nil t)
1089 (goto-char (match-beginning 1))
71a048c1
DG
1090 (if (not (string-equal "def" (match-string 1)))
1091 (setq mlist (list (match-string 2)))
1092 ;; We're inside the method. For classes and modules,
1093 ;; this check is skipped for performance.
1094 (when (ruby-block-contains-point start)
1095 (setq mname (match-string 2))))
bb808526
DG
1096 (setq indent (current-column))
1097 (beginning-of-line))
5745cae6 1098 ;; Walk up the class/module nesting.
c7fbbc3c 1099 (while (and (> indent 0)
71a048c1 1100 (re-search-backward module-re nil t))
c7fbbc3c 1101 (goto-char (match-beginning 1))
71a048c1 1102 (when (< (current-column) indent)
bb808526
DG
1103 (setq mlist (cons (match-string 2) mlist))
1104 (setq indent (current-column))
1105 (beginning-of-line)))
5745cae6 1106 ;; Process the method name.
c7fbbc3c
CY
1107 (when mname
1108 (let ((mn (split-string mname "\\.\\|::")))
1109 (if (cdr mn)
1110 (progn
5745cae6
DG
1111 (unless (string-equal "self" (car mn)) ; def self.foo
1112 ;; def C.foo
1113 (let ((ml (nreverse mlist)))
1114 ;; If the method name references one of the
1115 ;; containing modules, drop the more nested ones.
c7fbbc3c
CY
1116 (while ml
1117 (if (string-equal (car ml) (car mn))
1118 (setq mlist (nreverse (cdr ml)) ml nil))
5745cae6
DG
1119 (or (setq ml (cdr ml)) (nreverse mlist))))
1120 (if mlist
1121 (setcdr (last mlist) (butlast mn))
1122 (setq mlist (butlast mn))))
1123 (setq mname (concat "." (car (last mn)))))
bb808526
DG
1124 ;; See if the method is in singleton class context.
1125 (let ((in-singleton-class
1126 (when (re-search-forward ruby-singleton-class-re start t)
1127 (goto-char (match-beginning 0))
71a048c1
DG
1128 ;; FIXME: Optimize it out, too?
1129 ;; This can be slow in a large file, but
1130 ;; unlike class/module declaration
1131 ;; indentations, method definitions can be
1132 ;; intermixed with these, and may or may not
1133 ;; be additionally indented after visibility
1134 ;; keywords.
bb808526
DG
1135 (ruby-block-contains-point start))))
1136 (setq mname (concat
1137 (if in-singleton-class "." "#")
1138 mname))))))
5745cae6 1139 ;; Generate the string.
c7fbbc3c
CY
1140 (if (consp mlist)
1141 (setq mlist (mapconcat (function identity) mlist "::")))
1142 (if mname
1143 (if mlist (concat mlist mname) mname)
1144 mlist)))))
1145
bb808526
DG
1146(defun ruby-block-contains-point (pt)
1147 (save-excursion
1148 (save-match-data
1149 (ruby-forward-sexp)
1150 (> (point) pt))))
1151
c3268831
DG
1152(defun ruby-brace-to-do-end (orig end)
1153 (let (beg-marker end-marker)
1154 (goto-char end)
1155 (when (eq (char-before) ?\})
1156 (delete-char -1)
32fb8162
DG
1157 (when (save-excursion
1158 (skip-chars-backward " \t")
1159 (not (bolp)))
c3268831
DG
1160 (insert "\n"))
1161 (insert "end")
1162 (setq end-marker (point-marker))
1163 (when (and (not (eobp)) (eq (char-syntax (char-after)) ?w))
1164 (insert " "))
1165 (goto-char orig)
1166 (delete-char 1)
1167 (when (eq (char-syntax (char-before)) ?w)
1168 (insert " "))
1169 (insert "do")
1170 (setq beg-marker (point-marker))
1171 (when (looking-at "\\(\\s \\)*|")
1172 (unless (match-beginning 1)
1173 (insert " "))
1174 (goto-char (1+ (match-end 0)))
1175 (search-forward "|"))
1176 (unless (looking-at "\\s *$")
1177 (insert "\n"))
1178 (indent-region beg-marker end-marker)
1179 (goto-char beg-marker)
1180 t)))
1181
1182(defun ruby-do-end-to-brace (orig end)
32fb8162
DG
1183 (let (beg-marker end-marker beg-pos end-pos)
1184 (goto-char (- end 3))
1185 (when (looking-at ruby-block-end-re)
1186 (delete-char 3)
1187 (setq end-marker (point-marker))
1188 (insert "}")
1189 (goto-char orig)
1190 (delete-char 2)
1191 (insert "{")
1192 (setq beg-marker (point-marker))
1193 (when (looking-at "\\s +|")
1194 (delete-char (- (match-end 0) (match-beginning 0) 1))
1195 (forward-char)
1196 (re-search-forward "|" (line-end-position) t))
1197 (save-excursion
1198 (skip-chars-forward " \t\n\r")
1199 (setq beg-pos (point))
1200 (goto-char end-marker)
1201 (skip-chars-backward " \t\n\r")
1202 (setq end-pos (point)))
1203 (when (or
1204 (< end-pos beg-pos)
1205 (and (= (line-number-at-pos beg-pos) (line-number-at-pos end-pos))
1206 (< (+ (current-column) (- end-pos beg-pos) 2) fill-column)))
1207 (just-one-space -1)
1208 (goto-char end-marker)
1209 (just-one-space -1))
1210 (goto-char beg-marker)
1211 t)))
0d9e2599
NN
1212
1213(defun ruby-toggle-block ()
c3268831
DG
1214 "Toggle block type from do-end to braces or back.
1215The block must begin on the current line or above it and end after the point.
1216If the result is do-end block, it will always be multiline."
0d9e2599 1217 (interactive)
c3268831
DG
1218 (let ((start (point)) beg end)
1219 (end-of-line)
1220 (unless
1221 (if (and (re-search-backward "\\({\\)\\|\\_<do\\(\\s \\|$\\||\\)")
1222 (progn
1223 (setq beg (point))
1224 (save-match-data (ruby-forward-sexp))
1225 (setq end (point))
1226 (> end start)))
1227 (if (match-beginning 1)
1228 (ruby-brace-to-do-end beg end)
1229 (ruby-do-end-to-brace beg end)))
1230 (goto-char start))))
0d9e2599 1231
f4ff702e 1232(declare-function ruby-syntax-propertize-heredoc "ruby-mode" (limit))
e636fafe
DG
1233(declare-function ruby-syntax-enclosing-percent-literal "ruby-mode" (limit))
1234(declare-function ruby-syntax-propertize-percent-literal "ruby-mode" (limit))
f4ff702e 1235
cf38dd42
SM
1236(if (eval-when-compile (fboundp #'syntax-propertize-rules))
1237 ;; New code that works independently from font-lock.
1238 (progn
e636fafe
DG
1239 (eval-and-compile
1240 (defconst ruby-percent-literal-beg-re
1241 "\\(%\\)[qQrswWx]?\\([[:punct:]]\\)"
f063063a
DG
1242 "Regexp to match the beginning of percent literal.")
1243
1244 (defconst ruby-syntax-methods-before-regexp
1245 '("gsub" "gsub!" "sub" "sub!" "scan" "split" "split!" "index" "match"
1246 "assert_match" "Given" "Then" "When")
1247 "Methods that can take regexp as the first argument.
1248It will be properly highlighted even when the call omits parens."))
e636fafe 1249
cf38dd42
SM
1250 (defun ruby-syntax-propertize-function (start end)
1251 "Syntactic keywords for Ruby mode. See `syntax-propertize-function'."
1252 (goto-char start)
1253 (ruby-syntax-propertize-heredoc end)
e636fafe 1254 (ruby-syntax-enclosing-percent-literal end)
cf38dd42
SM
1255 (funcall
1256 (syntax-propertize-rules
dfbd787f
SM
1257 ;; $' $" $` .... are variables.
1258 ;; ?' ?" ?` are ascii codes.
21bb5ce0
SM
1259 ("\\([?$]\\)[#\"'`]"
1260 (1 (unless (save-excursion
1261 ;; Not within a string.
1262 (nth 3 (syntax-ppss (match-beginning 0))))
1263 (string-to-syntax "\\"))))
f063063a
DG
1264 ;; Regexps: regexps are distinguished from division because
1265 ;; of the keyword, symbol, or method name before them.
dfbd787f
SM
1266 ((concat
1267 ;; Special tokens that can't be followed by a division operator.
f063063a
DG
1268 "\\(^\\|[[=(,~?:;<>]"
1269 ;; Control flow keywords and operators following bol or whitespace.
1270 "\\|\\(?:^\\|\\s \\)"
dfbd787f 1271 (regexp-opt '("if" "elsif" "unless" "while" "until" "when" "and"
f063063a
DG
1272 "or" "not" "&&" "||"))
1273 ;; Method name from the list.
1274 "\\|\\_<"
1275 (regexp-opt ruby-syntax-methods-before-regexp)
1276 "\\)\\s *"
dfbd787f 1277 ;; The regular expression itself.
f063063a
DG
1278 "\\(/\\)[^/\n\\\\]*\\(?:\\\\.[^/\n\\\\]*\\)*\\(/\\)")
1279 (2 (string-to-syntax "\"/"))
1280 (3 (string-to-syntax "\"/")))
cf38dd42
SM
1281 ("^=en\\(d\\)\\_>" (1 "!"))
1282 ("^\\(=\\)begin\\_>" (1 "!"))
1283 ;; Handle here documents.
1284 ((concat ruby-here-doc-beg-re ".*\\(\n\\)")
9cd80478
DG
1285 (7 (unless (ruby-singleton-class-p (match-beginning 0))
1286 (put-text-property (match-beginning 7) (match-end 7)
1287 'syntax-table (string-to-syntax "\""))
1288 (ruby-syntax-propertize-heredoc end))))
85222d44 1289 ;; Handle percent literals: %w(), %q{}, etc.
e636fafe
DG
1290 ((concat "\\(?:^\\|[[ \t\n<+(,=]\\)" ruby-percent-literal-beg-re)
1291 (1 (prog1 "|" (ruby-syntax-propertize-percent-literal end)))))
c62792e7 1292 (point) end)
dbb530d9 1293 (ruby-syntax-propertize-expansions start end))
cf38dd42
SM
1294
1295 (defun ruby-syntax-propertize-heredoc (limit)
1296 (let ((ppss (syntax-ppss))
1297 (res '()))
1298 (when (eq ?\n (nth 3 ppss))
1299 (save-excursion
1300 (goto-char (nth 8 ppss))
1301 (beginning-of-line)
1302 (while (re-search-forward ruby-here-doc-beg-re
1303 (line-end-position) t)
9cd80478
DG
1304 (unless (ruby-singleton-class-p (match-beginning 0))
1305 (push (concat (ruby-here-doc-end-match) "\n") res))))
cf38dd42
SM
1306 (let ((start (point)))
1307 ;; With multiple openers on the same line, we don't know in which
1308 ;; part `start' is, so we have to go back to the beginning.
1309 (when (cdr res)
1310 (goto-char (nth 8 ppss))
1311 (setq res (nreverse res)))
1312 (while (and res (re-search-forward (pop res) limit 'move))
1313 (if (null res)
1314 (put-text-property (1- (point)) (point)
1315 'syntax-table (string-to-syntax "\""))))
1316 ;; Make extra sure we don't move back, lest we could fall into an
1317 ;; inf-loop.
1318 (if (< (point) start) (goto-char start))))))
85222d44 1319
e636fafe
DG
1320 (defun ruby-syntax-enclosing-percent-literal (limit)
1321 (let ((state (syntax-ppss))
1322 (start (point)))
1323 ;; When already inside percent literal, re-propertize it.
85222d44
DG
1324 (when (eq t (nth 3 state))
1325 (goto-char (nth 8 state))
e636fafe
DG
1326 (when (looking-at ruby-percent-literal-beg-re)
1327 (ruby-syntax-propertize-percent-literal limit))
1328 (when (< (point) start) (goto-char start)))))
85222d44 1329
e636fafe 1330 (defun ruby-syntax-propertize-percent-literal (limit)
85222d44 1331 (goto-char (match-beginning 2))
e636fafe
DG
1332 ;; Not inside a simple string or comment.
1333 (when (eq t (nth 3 (syntax-ppss)))
1334 (let* ((op (char-after))
1335 (ops (char-to-string op))
1336 (cl (or (cdr (aref (syntax-table) op))
1337 (cdr (assoc op '((?< . ?>))))))
1338 parse-sexp-lookup-properties)
1339 (condition-case nil
1340 (progn
1341 (if cl ; Paired delimiters.
1342 ;; Delimiter pairs of the same kind can be nested
1343 ;; inside the literal, as long as they are balanced.
1344 ;; Create syntax table that ignores other characters.
1345 (with-syntax-table (make-char-table 'syntax-table nil)
1346 (modify-syntax-entry op (concat "(" (char-to-string cl)))
1347 (modify-syntax-entry cl (concat ")" ops))
1348 (modify-syntax-entry ?\\ "\\")
1349 (save-restriction
1350 (narrow-to-region (point) limit)
1351 (forward-list))) ; skip to the paired character
1352 ;; Single character delimiter.
1353 (re-search-forward (concat "[^\\]\\(?:\\\\\\\\\\)*"
1354 (regexp-quote ops)) limit nil))
1355 ;; Found the closing delimiter.
1356 (put-text-property (1- (point)) (point) 'syntax-table
1357 (string-to-syntax "|")))
1358 ;; Unclosed literal, leave the following text unpropertized.
1359 ((scan-error search-failed) (goto-char limit))))))
dbb530d9
DG
1360
1361 (defun ruby-syntax-propertize-expansions (start end)
1362 (remove-text-properties start end '(ruby-expansion-match-data))
1363 (goto-char start)
1364 ;; Find all expression expansions and
1365 ;; - save the match data to a text property, for font-locking later,
cccaebd2 1366 ;; - set the syntax of all double quotes and backticks to punctuation.
dbb530d9
DG
1367 (while (re-search-forward ruby-expression-expansion-re end 'move)
1368 (let ((beg (match-beginning 2))
1369 (end (match-end 2)))
1370 (when (and beg (save-excursion (nth 3 (syntax-ppss beg))))
1371 (put-text-property beg (1+ beg) 'ruby-expansion-match-data
1372 (match-data))
1373 (goto-char beg)
1374 (while (re-search-forward "[\"`]" end 'move)
1375 (put-text-property (match-beginning 0) (match-end 0)
1376 'syntax-table (string-to-syntax ".")))))))
cf38dd42 1377 )
f4ff702e 1378
cf38dd42
SM
1379 ;; For Emacsen where syntax-propertize-rules is not (yet) available,
1380 ;; fallback on the old font-lock-syntactic-keywords stuff.
1381
1382 (defconst ruby-here-doc-end-re
1383 "^\\([ \t]+\\)?\\(.*\\)\\(\n\\)"
1384 "Regexp to match the end of heredocs.
1385
1386This will actually match any line with one or more characters.
1387It's useful in that it divides up the match string so that
1388`ruby-here-doc-beg-match' can search for the beginning of the heredoc.")
1389
1390 (defun ruby-here-doc-beg-match ()
1391 "Return a regexp to find the beginning of a heredoc.
1392
1393This should only be called after matching against `ruby-here-doc-end-re'."
06d8ace5
GM
1394 (let ((contents (concat
1395 (regexp-quote (concat (match-string 2) (match-string 3)))
1396 (if (string= (match-string 3) "_") "\\B" "\\b"))))
cf38dd42
SM
1397 (concat "<<"
1398 (let ((match (match-string 1)))
1399 (if (and match (> (length match) 0))
06d8ace5
GM
1400 (concat "\\(?:-\\([\"']?\\)\\|\\([\"']\\)"
1401 (match-string 1) "\\)"
1402 contents "\\(\\1\\|\\2\\)")
1403 (concat "-?\\([\"']\\|\\)" contents "\\1"))))))
cf38dd42
SM
1404
1405 (defconst ruby-font-lock-syntactic-keywords
0ba2d4b6 1406 `(
c7fbbc3c
CY
1407 ;; the last $', $", $` in the respective string is not variable
1408 ;; the last ?', ?", ?` in the respective string is not ascii code
1409 ("\\(^\\|[\[ \t\n<+\(,=]\\)\\(['\"`]\\)\\(\\\\.\\|\\2\\|[^'\"`\n\\\\]\\)*?\\\\?[?$]\\(\\2\\)"
1410 (2 (7 . nil))
1411 (4 (7 . nil)))
1412 ;; $' $" $` .... are variables
1413 ;; ?' ?" ?` are ascii codes
1414 ("\\(^\\|[^\\\\]\\)\\(\\\\\\\\\\)*[?$]\\([#\"'`]\\)" 3 (1 . nil))
1415 ;; regexps
cc9c9831 1416 ("\\(^\\|[[=(,~?:;<>]\\|\\(^\\|\\s \\)\\(if\\|elsif\\|unless\\|while\\|until\\|when\\|and\\|or\\|&&\\|||\\)\\|g?sub!?\\|scan\\|split!?\\)\\s *\\(/\\)[^/\n\\\\]*\\(\\\\.[^/\n\\\\]*\\)*\\(/\\)"
c7fbbc3c
CY
1417 (4 (7 . ?/))
1418 (6 (7 . ?/)))
1419 ("^=en\\(d\\)\\_>" 1 "!")
e636fafe 1420 ;; Percent literal.
85222d44
DG
1421 ("\\(^\\|[[ \t\n<+(,=]\\)\\(%[xrqQwW]?\\([^<[{(a-zA-Z0-9 \n]\\)[^\n\\\\]*\\(\\\\.[^\n\\\\]*\\)*\\(\\3\\)\\)"
1422 (3 "\"")
1423 (5 "\""))
c7fbbc3c
CY
1424 ("^\\(=\\)begin\\_>" 1 (ruby-comment-beg-syntax))
1425 ;; Currently, the following case is highlighted incorrectly:
1426 ;;
1427 ;; <<FOO
1428 ;; FOO
1429 ;; <<BAR
1430 ;; <<BAZ
1431 ;; BAZ
1432 ;; BAR
1433 ;;
1434 ;; This is because all here-doc beginnings are highlighted before any endings,
1435 ;; so although <<BAR is properly marked as a beginning, when we get to <<BAZ
1436 ;; it thinks <<BAR is part of a string so it's marked as well.
1437 ;;
1438 ;; This may be fixable by modifying ruby-in-here-doc-p to use
1439 ;; ruby-in-non-here-doc-string-p rather than syntax-ppss-context,
1440 ;; but I don't want to try that until we've got unit tests set up
1441 ;; to make sure I don't break anything else.
1442 (,(concat ruby-here-doc-beg-re ".*\\(\n\\)")
1443 ,(+ 1 (regexp-opt-depth ruby-here-doc-beg-re))
1444 (ruby-here-doc-beg-syntax))
1445 (,ruby-here-doc-end-re 3 (ruby-here-doc-end-syntax)))
94584c34 1446 "Syntactic keywords for Ruby mode. See `font-lock-syntactic-keywords'.")
cf38dd42
SM
1447
1448 (defun ruby-comment-beg-syntax ()
ee61fe97 1449 "Return the syntax cell for a the first character of a =begin.
c7fbbc3c
CY
1450See the definition of `ruby-font-lock-syntactic-keywords'.
1451
1452This returns a comment-delimiter cell as long as the =begin
1453isn't in a string or another comment."
cf38dd42
SM
1454 (when (not (nth 3 (syntax-ppss)))
1455 (string-to-syntax "!")))
1456
1457 (defun ruby-in-here-doc-p ()
1458 "Return whether or not the point is in a heredoc."
1459 (save-excursion
1460 (let ((old-point (point)) (case-fold-search nil))
1461 (beginning-of-line)
1462 (catch 'found-beg
9cd80478
DG
1463 (while (and (re-search-backward ruby-here-doc-beg-re nil t)
1464 (not (ruby-singleton-class-p)))
cf38dd42
SM
1465 (if (not (or (ruby-in-ppss-context-p 'anything)
1466 (ruby-here-doc-find-end old-point)))
1467 (throw 'found-beg t)))))))
1468
1469 (defun ruby-here-doc-find-end (&optional limit)
1470 "Expects the point to be on a line with one or more heredoc openers.
1471Returns the buffer position at which all heredocs on the line
1472are terminated, or nil if they aren't terminated before the
1473buffer position `limit' or the end of the buffer."
1474 (save-excursion
1475 (beginning-of-line)
1476 (catch 'done
e180ab9f 1477 (let ((eol (point-at-eol))
cf38dd42
SM
1478 (case-fold-search nil)
1479 ;; Fake match data such that (match-end 0) is at eol
1480 (end-match-data (progn (looking-at ".*$") (match-data)))
1481 beg-match-data end-re)
1482 (while (re-search-forward ruby-here-doc-beg-re eol t)
1483 (setq beg-match-data (match-data))
1484 (setq end-re (ruby-here-doc-end-match))
1485
1486 (set-match-data end-match-data)
1487 (goto-char (match-end 0))
1488 (unless (re-search-forward end-re limit t) (throw 'done nil))
1489 (setq end-match-data (match-data))
1490
1491 (set-match-data beg-match-data)
1492 (goto-char (match-end 0)))
1493 (set-match-data end-match-data)
1494 (goto-char (match-end 0))
1495 (point)))))
1496
1497 (defun ruby-here-doc-beg-syntax ()
1498 "Return the syntax cell for a line that may begin a heredoc.
1499See the definition of `ruby-font-lock-syntactic-keywords'.
1500
1501This sets the syntax cell for the newline ending the line
1502containing the heredoc beginning so that cases where multiple
1503heredocs are started on one line are handled correctly."
1504 (save-excursion
1505 (goto-char (match-beginning 0))
1506 (unless (or (ruby-in-ppss-context-p 'non-heredoc)
1507 (ruby-in-here-doc-p))
1508 (string-to-syntax "\""))))
1509
1510 (defun ruby-here-doc-end-syntax ()
1511 "Return the syntax cell for a line that may end a heredoc.
1512See the definition of `ruby-font-lock-syntactic-keywords'."
1513 (let ((pss (syntax-ppss)) (case-fold-search nil))
1514 ;; If we aren't in a string, we definitely aren't ending a heredoc,
1515 ;; so we can just give up.
1516 ;; This means we aren't doing a full-document search
1517 ;; every time we enter a character.
1518 (when (ruby-in-ppss-context-p 'heredoc pss)
1519 (save-excursion
1520 (goto-char (nth 8 pss)) ; Go to the beginning of heredoc.
1521 (let ((eol (point)))
1522 (beginning-of-line)
1523 (if (and (re-search-forward (ruby-here-doc-beg-match) eol t) ; If there is a heredoc that matches this line...
1524 (not (ruby-in-ppss-context-p 'anything)) ; And that's not inside a heredoc/string/comment...
1525 (progn (goto-char (match-end 0)) ; And it's the last heredoc on its line...
1526 (not (re-search-forward ruby-here-doc-beg-re eol t))))
1527 (string-to-syntax "\"")))))))
c7fbbc3c 1528
cf38dd42
SM
1529 (unless (functionp 'syntax-ppss)
1530 (defun syntax-ppss (&optional pos)
1531 (parse-partial-sexp (point-min) (or pos (point)))))
1532 )
c7fbbc3c
CY
1533
1534(defun ruby-in-ppss-context-p (context &optional ppss)
1535 (let ((ppss (or ppss (syntax-ppss (point)))))
1536 (if (cond
1537 ((eq context 'anything)
1538 (or (nth 3 ppss)
1539 (nth 4 ppss)))
1540 ((eq context 'string)
1541 (nth 3 ppss))
1542 ((eq context 'heredoc)
cf38dd42 1543 (eq ?\n (nth 3 ppss)))
c7fbbc3c
CY
1544 ((eq context 'non-heredoc)
1545 (and (ruby-in-ppss-context-p 'anything)
1546 (not (ruby-in-ppss-context-p 'heredoc))))
1547 ((eq context 'comment)
1548 (nth 4 ppss))
1549 (t
1550 (error (concat
1551 "Internal error on `ruby-in-ppss-context-p': "
1552 "context name `" (symbol-name context) "' is unknown"))))
1553 t)))
1554
c7fbbc3c
CY
1555(if (featurep 'xemacs)
1556 (put 'ruby-mode 'font-lock-defaults
1557 '((ruby-font-lock-keywords)
1558 nil nil nil
1559 beginning-of-line
1560 (font-lock-syntactic-keywords
1561 . ruby-font-lock-syntactic-keywords))))
1562
1563(defvar ruby-font-lock-syntax-table
1564 (let ((tbl (copy-syntax-table ruby-mode-syntax-table)))
1565 (modify-syntax-entry ?_ "w" tbl)
1566 tbl)
ee61fe97 1567 "The syntax table to use for fontifying Ruby mode buffers.
c7fbbc3c
CY
1568See `font-lock-syntax-table'.")
1569
1570(defconst ruby-font-lock-keywords
1571 (list
1572 ;; functions
1573 '("^\\s *def\\s +\\([^( \t\n]+\\)"
1574 1 font-lock-function-name-face)
1575 ;; keywords
1576 (cons (concat
499572e4 1577 "\\(^\\|[^.@$]\\|\\.\\.\\)\\_<\\(defined\\?\\|"
c7fbbc3c
CY
1578 (regexp-opt
1579 '("alias_method"
1580 "alias"
1581 "and"
1582 "begin"
1583 "break"
1584 "case"
1585 "catch"
1586 "class"
1587 "def"
1588 "do"
1589 "elsif"
1590 "else"
1591 "fail"
1592 "ensure"
1593 "for"
1594 "end"
1595 "if"
1596 "in"
1597 "module_function"
1598 "module"
1599 "next"
1600 "not"
1601 "or"
1602 "public"
1603 "private"
1604 "protected"
1605 "raise"
1606 "redo"
1607 "rescue"
1608 "retry"
1609 "return"
1610 "then"
1611 "throw"
1612 "super"
1613 "unless"
1614 "undef"
1615 "until"
1616 "when"
1617 "while"
1618 "yield")
1619 t)
1620 "\\)"
1621 ruby-keyword-end-re)
1622 2)
1623 ;; here-doc beginnings
f178c32d
DG
1624 `(,ruby-here-doc-beg-re 0 (unless (ruby-singleton-class-p (match-beginning 0))
1625 'font-lock-string-face))
c7fbbc3c 1626 ;; variables
499572e4 1627 '("\\(^\\|[^.@$]\\|\\.\\.\\)\\_<\\(nil\\|self\\|true\\|false\\)\\>"
c7fbbc3c 1628 2 font-lock-variable-name-face)
0ba2d4b6
DG
1629 ;; symbols
1630 '("\\(^\\|[^:]\\)\\(:\\([-+~]@?\\|[/%&|^`]\\|\\*\\*?\\|<\\(<\\|=>?\\)?\\|>[>=]?\\|===?\\|=~\\|![~=]?\\|\\[\\]=?\\|@?\\(\\w\\|_\\)+\\([!?=]\\|\\b_*\\)\\|#{[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\)\\)"
6c27f0f8 1631 2 font-lock-constant-face)
c7fbbc3c
CY
1632 ;; variables
1633 '("\\(\\$\\([^a-zA-Z0-9 \n]\\|[0-9]\\)\\)\\W"
1634 1 font-lock-variable-name-face)
1635 '("\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+"
1636 0 font-lock-variable-name-face)
c7fbbc3c 1637 ;; constants
18d801db 1638 '("\\(?:\\_<\\|::\\)\\([A-Z]+\\(\\w\\|_\\)*\\)"
499572e4 1639 1 font-lock-type-face)
6c27f0f8 1640 '("\\(^\\s *\\|[\[\{\(,]\\s *\\|\\sw\\s +\\)\\(\\(\\sw\\|_\\)+\\):[^:]" 2 font-lock-constant-face)
c7fbbc3c 1641 ;; expression expansion
0ba2d4b6 1642 '(ruby-match-expression-expansion
c62792e7 1643 2 font-lock-variable-name-face t)
c7fbbc3c
CY
1644 ;; warn lower camel case
1645 ;'("\\<[a-z]+[a-z0-9]*[A-Z][A-Za-z0-9]*\\([!?]?\\|\\>\\)"
1646 ; 0 font-lock-warning-face)
1647 )
ee61fe97 1648 "Additional expressions to highlight in Ruby mode.")
c7fbbc3c 1649
0ba2d4b6 1650(defun ruby-match-expression-expansion (limit)
dbb530d9
DG
1651 (let* ((prop 'ruby-expansion-match-data)
1652 (pos (next-single-char-property-change (point) prop nil limit))
1653 value)
1654 (when (and pos (> pos (point)))
c62792e7
DG
1655 (goto-char pos)
1656 (or (and (setq value (get-text-property pos prop))
1657 (progn (set-match-data value) t))
1658 (ruby-match-expression-expansion limit)))))
0ba2d4b6 1659
c7fbbc3c 1660;;;###autoload
cf38dd42 1661(define-derived-mode ruby-mode prog-mode "Ruby"
c7fbbc3c
CY
1662 "Major mode for editing Ruby scripts.
1663\\[ruby-indent-line] properly indents subexpressions of multi-line
1664class, module, def, if, while, for, do, and case statements, taking
1665nesting into account.
1666
ee61fe97
JB
1667The variable `ruby-indent-level' controls the amount of indentation.
1668
c7fbbc3c 1669\\{ruby-mode-map}"
c7fbbc3c
CY
1670 (ruby-mode-variables)
1671
1672 (set (make-local-variable 'imenu-create-index-function)
1673 'ruby-imenu-create-index)
1674 (set (make-local-variable 'add-log-current-defun-function)
1675 'ruby-add-log-current-method)
4489104f
DG
1676 (set (make-local-variable 'beginning-of-defun-function)
1677 'ruby-beginning-of-defun)
1678 (set (make-local-variable 'end-of-defun-function)
1679 'ruby-end-of-defun)
c7fbbc3c
CY
1680
1681 (add-hook
cf38dd42 1682 (cond ((boundp 'before-save-hook) 'before-save-hook)
c7fbbc3c
CY
1683 ((boundp 'write-contents-functions) 'write-contents-functions)
1684 ((boundp 'write-contents-hooks) 'write-contents-hooks))
cf38dd42
SM
1685 'ruby-mode-set-encoding nil 'local)
1686
1687 (set (make-local-variable 'electric-indent-chars)
2122161f 1688 (append '(?\{ ?\}) electric-indent-chars))
c7fbbc3c
CY
1689
1690 (set (make-local-variable 'font-lock-defaults)
1691 '((ruby-font-lock-keywords) nil nil))
1692 (set (make-local-variable 'font-lock-keywords)
1693 ruby-font-lock-keywords)
1694 (set (make-local-variable 'font-lock-syntax-table)
1695 ruby-font-lock-syntax-table)
c7fbbc3c 1696
cf38dd42
SM
1697 (if (eval-when-compile (fboundp 'syntax-propertize-rules))
1698 (set (make-local-variable 'syntax-propertize-function)
1699 #'ruby-syntax-propertize-function)
1700 (set (make-local-variable 'font-lock-syntactic-keywords)
1701 ruby-font-lock-syntactic-keywords)))
c7fbbc3c
CY
1702
1703;;; Invoke ruby-mode when appropriate
1704
1705;;;###autoload
a7610c52 1706(add-to-list 'auto-mode-alist (cons (purecopy "\\.rb\\'") 'ruby-mode))
cabc040a 1707;;;###autoload
78269b95
DG
1708(add-to-list 'auto-mode-alist (cons (purecopy "Rakefile\\'") 'ruby-mode))
1709;;;###autoload
1710(add-to-list 'auto-mode-alist (cons (purecopy "\\.gemspec\\'") 'ruby-mode))
c7fbbc3c
CY
1711
1712;;;###autoload
4d270537 1713(dolist (name (list "ruby" "rbx" "jruby" "ruby1.9" "ruby1.8"))
a7610c52 1714 (add-to-list 'interpreter-mode-alist (cons (purecopy name) 'ruby-mode)))
c7fbbc3c
CY
1715
1716(provide 'ruby-mode)
1717
1718;;; ruby-mode.el ends here