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