* lisp/progmodes/ruby-mode.el (ruby-expression-expansion-re): Allow to

start at point, so that expansion starting right after opening
slash in a regexp is recognized.
(ruby-syntax-before-regexp-re): New defvar, extracted from
ruby-syntax-propertize-function.  Since the value of this regexp
is looked up at runtime now, we should be able to turn
`ruby-syntax-methods-before-regexp' into a defcustom later.
(ruby-syntax-propertize-function): Split regexp matching into two
parts, for opening and closing slashes.  That allows us to skip
over string interpolations and support multiline regexps.
Don't call `ruby-syntax-propertize-expansions', instead use another rule
for them, which calls `ruby-syntax-propertize-expansion'.
(ruby-syntax-propertize-expansions): Move `remove-text-properties'
call to `ruby-syntax-propertize-function'.
(ruby-syntax-propertize-expansion): Extracted from
`ruby-syntax-propertize-expansions'.  Handles one expansion.
(ruby-syntax-propertize-heredoc): Explicitly call
`ruby-syntax-propertize-expansions'.
(ruby-syntax-propertize-percent-literal): Leave point right after
the percent symbol, so that the expression expansion rule can
propertize the contents.

* test/automated/ruby-mode-tests.el (ruby-heredoc-highlights-interpolations)
(ruby-regexp-skips-over-interpolation)
(ruby-regexp-continues-till-end-when-unclosed)
(ruby-regexp-can-be-multiline)
(ruby-interpolation-inside-percent-literal): New tests.

* test/indent/ruby.rb: Add multiline regexp example.
This commit is contained in:
Dmitry Gutov 2013-05-19 10:01:23 +04:00
parent c1a6c0a420
commit 1a0a0a8a6a
5 changed files with 140 additions and 60 deletions

View file

@ -1,3 +1,27 @@
2013-05-19 Dmitry Gutov <dgutov@yandex.ru>
* progmodes/ruby-mode.el (ruby-expression-expansion-re): Allow to
start at point, so that expansion starting right after opening
slash in a regexp is recognized.
(ruby-syntax-before-regexp-re): New defvar, extracted from
ruby-syntax-propertize-function. Since the value of this regexp
is looked up at runtime now, we should be able to turn
`ruby-syntax-methods-before-regexp' into a defcustom later.
(ruby-syntax-propertize-function): Split regexp matching into two
parts, for opening and closing slashes. That allows us to skip
over string interpolations and support multiline regexps.
Don't call `ruby-syntax-propertize-expansions', instead use another rule
for them, which calls `ruby-syntax-propertize-expansion'.
(ruby-syntax-propertize-expansions): Move `remove-text-properties'
call to `ruby-syntax-propertize-function'.
(ruby-syntax-propertize-expansion): Extracted from
`ruby-syntax-propertize-expansions'. Handles one expansion.
(ruby-syntax-propertize-heredoc): Explicitly call
`ruby-syntax-propertize-expansions'.
(ruby-syntax-propertize-percent-literal): Leave point right after
the percent symbol, so that the expression expansion rule can
propertize the contents.
2013-05-18 Juri Linkov <juri@jurta.org>
* man.el (Man-default-man-entry): Remove `-' from the end

View file

@ -113,7 +113,7 @@
"Regexp to match the beginning of a heredoc.")
(defconst ruby-expression-expansion-re
"[^\\]\\(\\\\\\\\\\)*\\(#\\({[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\|\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+\\)\\)"))
"\\(?:[^\\]\\|\\=\\)\\(\\\\\\\\\\)*\\(#\\({[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\|\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+\\)\\)"))
(defun ruby-here-doc-end-match ()
"Return a regexp to find the end of a heredoc.
@ -1360,11 +1360,26 @@ If the result is do-end block, it will always be multiline."
'("gsub" "gsub!" "sub" "sub!" "scan" "split" "split!" "index" "match"
"assert_match" "Given" "Then" "When")
"Methods that can take regexp as the first argument.
It will be properly highlighted even when the call omits parens."))
It will be properly highlighted even when the call omits parens.")
(defvar ruby-syntax-before-regexp-re
(concat
;; Special tokens that can't be followed by a division operator.
"\\(^\\|[[=(,~?:;<>]"
;; Control flow keywords and operators following bol or whitespace.
"\\|\\(?:^\\|\\s \\)"
(regexp-opt '("if" "elsif" "unless" "while" "until" "when" "and"
"or" "not" "&&" "||"))
;; Method name from the list.
"\\|\\_<"
(regexp-opt ruby-syntax-methods-before-regexp)
"\\)\\s *")
"Regexp to match text that can be followed by a regular expression."))
(defun ruby-syntax-propertize-function (start end)
"Syntactic keywords for Ruby mode. See `syntax-propertize-function'."
(goto-char start)
(remove-text-properties start end '(ruby-expansion-match-data))
(ruby-syntax-propertize-heredoc end)
(ruby-syntax-enclosing-percent-literal end)
(funcall
@ -1376,25 +1391,26 @@ It will be properly highlighted even when the call omits parens."))
;; Not within a string.
(nth 3 (syntax-ppss (match-beginning 0))))
(string-to-syntax "\\"))))
;; Regexps: regexps are distinguished from division because
;; of the keyword, symbol, or method name before them.
((concat
;; Special tokens that can't be followed by a division operator.
"\\(^\\|[[=(,~?:;<>]"
;; Control flow keywords and operators following bol or whitespace.
"\\|\\(?:^\\|\\s \\)"
(regexp-opt '("if" "elsif" "unless" "while" "until" "when" "and"
"or" "not" "&&" "||"))
;; Method name from the list.
"\\|\\_<"
(regexp-opt ruby-syntax-methods-before-regexp)
"\\)\\s *"
;; The regular expression itself.
"\\(/\\)[^/\n\\\\]*\\(?:\\\\.[^/\n\\\\]*\\)*\\(/\\)")
(3 (unless (nth 3 (syntax-ppss (match-beginning 2)))
(put-text-property (match-beginning 2) (match-end 2)
'syntax-table (string-to-syntax "\"/"))
(string-to-syntax "\"/"))))
;; Regular expressions. Start with matching unescaped slash.
("\\(?:\\=\\|[^\\]\\)\\(?:\\\\\\\\\\)*\\(/\\)"
(1 (let ((state (save-excursion (syntax-ppss (match-beginning 1)))))
(when (or
;; Beginning of a regexp.
(and (null (nth 8 state))
(save-excursion
(forward-char -1)
(looking-back ruby-syntax-before-regexp-re
(point-at-bol))))
;; End of regexp. We don't match the whole
;; regexp at once because it can have
;; string interpolation inside, or span
;; several lines.
(eq ?/ (nth 3 state)))
(string-to-syntax "\"/")))))
;; Expression expansions in strings. We're handling them
;; here, so that the regexp rule never matches inside them.
(ruby-expression-expansion-re
(0 (ignore (ruby-syntax-propertize-expansion))))
("^=en\\(d\\)\\_>" (1 "!"))
("^\\(=\\)begin\\_>" (1 "!"))
;; Handle here documents.
@ -1406,8 +1422,7 @@ It will be properly highlighted even when the call omits parens."))
;; Handle percent literals: %w(), %q{}, etc.
((concat "\\(?:^\\|[[ \t\n<+(,=]\\)" ruby-percent-literal-beg-re)
(1 (prog1 "|" (ruby-syntax-propertize-percent-literal end)))))
(point) end)
(ruby-syntax-propertize-expansions start end))
(point) end))
(defun ruby-syntax-propertize-heredoc (limit)
(let ((ppss (syntax-ppss))
@ -1432,7 +1447,9 @@ It will be properly highlighted even when the call omits parens."))
'syntax-table (string-to-syntax "\""))))
;; Make extra sure we don't move back, lest we could fall into an
;; inf-loop.
(if (< (point) start) (goto-char start))))))
(if (< (point) start)
(goto-char start)
(ruby-syntax-propertize-expansions start (point)))))))
(defun ruby-syntax-enclosing-percent-literal (limit)
(let ((state (syntax-ppss))
@ -1453,44 +1470,47 @@ It will be properly highlighted even when the call omits parens."))
(cl (or (cdr (aref (syntax-table) op))
(cdr (assoc op '((?< . ?>))))))
parse-sexp-lookup-properties)
(condition-case nil
(progn
(if cl ; Paired delimiters.
;; Delimiter pairs of the same kind can be nested
;; inside the literal, as long as they are balanced.
;; Create syntax table that ignores other characters.
(with-syntax-table (make-char-table 'syntax-table nil)
(modify-syntax-entry op (concat "(" (char-to-string cl)))
(modify-syntax-entry cl (concat ")" ops))
(modify-syntax-entry ?\\ "\\")
(save-restriction
(narrow-to-region (point) limit)
(forward-list))) ; skip to the paired character
;; Single character delimiter.
(re-search-forward (concat "[^\\]\\(?:\\\\\\\\\\)*"
(regexp-quote ops)) limit nil))
;; Found the closing delimiter.
(put-text-property (1- (point)) (point) 'syntax-table
(string-to-syntax "|")))
;; Unclosed literal, leave the following text unpropertized.
((scan-error search-failed) (goto-char limit))))))
(save-excursion
(condition-case nil
(progn
(if cl ; Paired delimiters.
;; Delimiter pairs of the same kind can be nested
;; inside the literal, as long as they are balanced.
;; Create syntax table that ignores other characters.
(with-syntax-table (make-char-table 'syntax-table nil)
(modify-syntax-entry op (concat "(" (char-to-string cl)))
(modify-syntax-entry cl (concat ")" ops))
(modify-syntax-entry ?\\ "\\")
(save-restriction
(narrow-to-region (point) limit)
(forward-list))) ; skip to the paired character
;; Single character delimiter.
(re-search-forward (concat "[^\\]\\(?:\\\\\\\\\\)*"
(regexp-quote ops)) limit nil))
;; Found the closing delimiter.
(put-text-property (1- (point)) (point) 'syntax-table
(string-to-syntax "|")))
;; Unclosed literal, do nothing.
((scan-error search-failed)))))))
(defun ruby-syntax-propertize-expansion ()
;; Save the match data to a text property, for font-locking later.
;; Set the syntax of all double quotes and backticks to punctuation.
(let ((beg (match-beginning 2))
(end (match-end 2)))
(when (and beg (save-excursion (nth 3 (syntax-ppss beg))))
(put-text-property beg (1+ beg) 'ruby-expansion-match-data
(match-data))
(goto-char beg)
(while (re-search-forward "[\"`]" end 'move)
(put-text-property (match-beginning 0) (match-end 0)
'syntax-table (string-to-syntax "."))))))
(defun ruby-syntax-propertize-expansions (start end)
(remove-text-properties start end '(ruby-expansion-match-data))
(goto-char start)
;; Find all expression expansions and
;; - save the match data to a text property, for font-locking later,
;; - set the syntax of all double quotes and backticks to punctuation.
(while (re-search-forward ruby-expression-expansion-re end 'move)
(let ((beg (match-beginning 2))
(end (match-end 2)))
(when (and beg (save-excursion (nth 3 (syntax-ppss beg))))
(put-text-property beg (1+ beg) 'ruby-expansion-match-data
(match-data))
(goto-char beg)
(while (re-search-forward "[\"`]" end 'move)
(put-text-property (match-beginning 0) (match-end 0)
'syntax-table (string-to-syntax ".")))))))
(save-excursion
(goto-char start)
(while (re-search-forward ruby-expression-expansion-re end 'move)
(ruby-syntax-propertize-expansion))))
)
;; For Emacsen where syntax-propertize-rules is not (yet) available,

View file

@ -1,3 +1,13 @@
2013-05-19 Dmitry Gutov <dgutov@yandex.ru>
* indent/ruby.rb: Add multiline regexp example.
* automated/ruby-mode-tests.el (ruby-heredoc-highlights-interpolations)
(ruby-regexp-skips-over-interpolation)
(ruby-regexp-continues-till-end-when-unclosed)
(ruby-regexp-can-be-multiline)
(ruby-interpolation-inside-percent-literal): New tests.
2013-05-08 Stefan Monnier <monnier@iro.umontreal.ca>
* indent/ruby.rb: Fix indentation after =; add more cases.

View file

@ -84,6 +84,9 @@ VALUES-PLIST is a list with alternating index and value elements."
(ert-deftest ruby-singleton-class-no-heredoc-font-lock ()
(ruby-assert-face "class<<a" 8 nil))
(ert-deftest ruby-heredoc-highlights-interpolations ()
(ruby-assert-face "s = <<EOS\n #{foo}\nEOS" 15 font-lock-variable-name-face))
(ert-deftest ruby-deep-indent ()
(let ((ruby-deep-arglist nil)
(ruby-deep-indent-paren '(?\( ?\{ ?\[ ?\] t)))
@ -109,6 +112,15 @@ VALUES-PLIST is a list with alternating index and value elements."
(ert-deftest ruby-regexp-starts-after-string ()
(ruby-assert-state "'(/', /\d+/" 3 ?/ 8))
(ert-deftest ruby-regexp-skips-over-interpolation ()
(ruby-assert-state "/#{foobs.join('/')}/" 3 nil))
(ert-deftest ruby-regexp-continues-till-end-when-unclosed ()
(ruby-assert-state "/bars" 3 ?/))
(ert-deftest ruby-regexp-can-be-multiline ()
(ruby-assert-state "/bars\ntees # toots \nfoos/" 3 nil))
(ert-deftest ruby-indent-simple ()
(ruby-should-indent-buffer
"if foo
@ -325,6 +337,13 @@ VALUES-PLIST is a list with alternating index and value elements."
(search-forward "tee")
(should (string= (thing-at-point 'symbol) "tee")))))
(ert-deftest ruby-interpolation-inside-percent-literal ()
(let ((s "%( #{boo} )"))
(ruby-assert-face s 1 font-lock-string-face)
(ruby-assert-face s 4 font-lock-variable-name-face)
(ruby-assert-face s 10 font-lock-string-face)
(ruby-assert-state s 8 nil)))
(ert-deftest ruby-interpolation-inside-percent-literal-with-paren ()
:expected-result :failed
(let ((s "%(^#{\")\"}^)"))

View file

@ -21,6 +21,11 @@
# Highlight the regexp after "if".
x = toto / foo if /do bar/ =~ "dobar"
# Multiline regexp.
/bars
tees # toots
nfoos/
def test1(arg)
puts "hello"
end
@ -47,6 +52,8 @@ def test2 (arg)
case a
when "a"
6
# Support for this syntax was removed in Ruby 1.9, so we
# probably don't need to handle it either.
# when "b" :
# 7
# when "c" : 2