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