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