From: Nathan Weizenbaum Date: 2008-07-15T22:36:14+09:00 Subject: [ruby-core:17788] Re: [PATCH] ruby-mode.el: Fix here-doc strings with inner quotes This is a multi-part message in MIME format. --------------050409000902070401060508 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Alright, here's a version that fixes both the highlighting bug and the Emacs 22 incompatibility (hopefully all of it this time). Thanks for helping me debug this. Nobuyoshi Nakada wrote: > Hi, > > At Tue, 15 Jul 2008 13:09:26 +0900, > Nobuyoshi Nakada wrote in [ruby-core:17779]: > >>> Looks like version 22 doesn't support explicitly numbered regexp groups. >>> Here's a patch that uses regexp-opt-depth instead, which is more robust >>> anyway. >>> >> It worked fine. >> > > Not yet, try bootstraptest/runner.rb of 1.9. > > --------------050409000902070401060508 Content-Type: text/x-diff; name="here-doc.6.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="here-doc.6.patch" Index: misc/ruby-mode.el =================================================================== --- misc/ruby-mode.el (revision 18050) +++ misc/ruby-mode.el (working copy) @@ -48,16 +48,28 @@ (defconst ruby-block-end-re "\\") (defconst ruby-here-doc-beg-re - "<<\\(-\\)?\\(\\([a-zA-Z0-9_]+\\)\\|[\"]\\([^\"]+\\)[\"]\\|[']\\([^']+\\)[']\\)") + "\\(<\\)<\\(-\\)?\\(\\([a-zA-Z0-9_]+\\)\\|[\"]\\([^\"]+\\)[\"]\\|[']\\([^']+\\)[']\\)") +(defconst ruby-here-doc-end-re + "^\\([ \t]+\\)?\\(.*\\)\\(.\\)$") + (defun ruby-here-doc-end-match () (concat "^" - (if (match-string 1) "[ \t]*" nil) + (if (match-string 2) "[ \t]*" nil) (regexp-quote - (or (match-string 3) - (match-string 4) - (match-string 5))))) + (or (match-string 4) + (match-string 5) + (match-string 6))))) +(defun ruby-here-doc-beg-match () + (let ((contents (regexp-quote (concat (match-string 2) (match-string 3))))) + (concat "<<" + (let ((match (match-string 1))) + (if (and match (> (length match) 0)) + (concat "\\(?:-\\([\"']\\)\\|\\([\"']\\)" (match-string 1) "\\)" + contents "\\(\\1\\|\\2\\)") + (concat "-?\\([\"']\\|\\)" contents "\\1")))))) + (defconst ruby-delimiter (concat "[?$/%(){}#\"'`.:]\\|<<\\|\\[\\|\\]\\|\\<\\(" ruby-block-beg-re @@ -163,8 +175,8 @@ (eval-when-compile (require 'cl)) (defun ruby-imenu-create-index-in-block (prefix beg end) - (let ((index-alist '()) (case-fold-search nil) - name next pos decl sing) + (let ((index-alist '()) + name next pos decl sing) (goto-char beg) (while (re-search-forward "^\\s *\\(\\(class\\>\\(\\s *<<\\)?\\|module\\>\\)\\s *\\([^\(<\n ]+\\)\\|\\(def\\|alias\\)\\>\\s *\\([^\(\n ]+\\)\\)" end t) (setq sing (match-beginning 3)) @@ -211,6 +223,7 @@ (defun ruby-mode-variables () (set-syntax-table ruby-mode-syntax-table) (setq local-abbrev-table ruby-mode-abbrev-table) + (setq case-fold-search nil) (make-local-variable 'indent-line-function) (setq indent-line-function 'ruby-indent-line) (make-local-variable 'require-final-newline) @@ -226,6 +239,8 @@ (setq indent-tabs-mode ruby-indent-tabs-mode) (make-local-variable 'parse-sexp-ignore-comments) (setq parse-sexp-ignore-comments t) + (make-local-variable 'parse-sexp-lookup-properties) + (setq parse-sexp-lookup-properties t) (make-local-variable 'paragraph-start) (setq paragraph-start (concat "$\\|" page-delimiter)) (make-local-variable 'paragraph-separate) @@ -591,7 +606,6 @@ (save-excursion (beginning-of-line) (let ((indent-point (point)) - (case-fold-search nil) state bol eol begin op-end (paren (progn (skip-syntax-forward " ") (and (char-after) (matching-paren (char-after))))) @@ -1007,7 +1021,7 @@ (setq font-lock-variable-name-face font-lock-type-face)) (setq ruby-font-lock-syntactic-keywords - '( + `( ;; #{ }, #$hoge, #@foo are not comments ("\\(#\\)[{$@]" 1 (1 . nil)) ;; the last $', $", $` in the respective string is not variable @@ -1023,8 +1037,64 @@ (4 (7 . ?/)) (6 (7 . ?/))) ("^\\(=\\)begin\\(\\s \\|$\\)" 1 (7 . nil)) - ("^\\(=\\)end\\(\\s \\|$\\)" 1 (7 . nil)))) + ("^\\(=\\)end\\(\\s \\|$\\)" 1 (7 . nil)) + (,(concat ruby-here-doc-beg-re ".*\\(\n\\)") + ,(+ 1 (regexp-opt-depth ruby-here-doc-beg-re)) + (ruby-here-doc-beg-syntax)) + (,ruby-here-doc-end-re 3 (ruby-here-doc-end-syntax)))) + (defun ruby-in-here-doc-p () + (save-excursion + (let ((old-point (point))) + (beginning-of-line) + (and (re-search-backward ruby-here-doc-beg-re nil t) + (not (ruby-here-doc-find-end old-point)))))) + + (defun ruby-here-doc-find-end (&optional limit) + "Expects the point to be on a line with one or more heredoc +opener. Returns the buffer position at which all heredocs on the +line are terminated, or nil if they aren't terminated before the +buffer position `limit' or the end of the buffer." + (save-excursion + (beginning-of-line) + (catch 'done + (let ((eol (save-excursion (end-of-line) (point))) + ;; Fake match data such that (match-end 0) is at eol + (end-match-data (progn (looking-at ".*$") (match-data))) + beg-match-data end-re) + (while (re-search-forward ruby-here-doc-beg-re eol t) + (setq beg-match-data (match-data)) + (setq end-re (ruby-here-doc-end-match)) + + (set-match-data end-match-data) + (goto-char (match-end 0)) + (unless (re-search-forward end-re limit t) (throw 'done nil)) + (setq end-match-data (match-data)) + + (set-match-data beg-match-data) + (goto-char (match-end 0))) + (set-match-data end-match-data) + (goto-char (match-end 0)) + (point))))) + + (defun ruby-here-doc-beg-syntax () + (save-excursion + (goto-char (match-beginning 0)) + (unless (ruby-in-here-doc-p) (string-to-syntax "|")))) + + (defun ruby-here-doc-end-syntax () + (save-excursion + (goto-char (match-end 0)) + (let ((old-point (point)) + (beg-exists (re-search-backward (ruby-here-doc-beg-match) nil t)) + (eol (save-excursion (end-of-line) (point)))) + (if (and beg-exists ; If there is a heredoc that matches this line... + (null (syntax-ppss-context (syntax-ppss))) ; And that's not inside a heredoc/string/comment... + (progn (goto-char (match-end 0)) ; And it's the last heredoc on its line... + (not (re-search-forward ruby-here-doc-beg-re eol t))) + (eq old-point (ruby-here-doc-find-end old-point))) ; And it ends at this point... + (string-to-syntax "|"))))) + (if (featurep 'xemacs) (put 'ruby-mode 'font-lock-defaults '((ruby-font-lock-keywords) @@ -1064,35 +1134,7 @@ (modify-syntax-entry ?_ "w" tbl) tbl)) - (defun ruby-font-lock-here-docs (limit) - (if (re-search-forward ruby-here-doc-beg-re limit t) - (let (beg) - (beginning-of-line) - (forward-line) - (setq beg (point)) - (if (re-search-forward (ruby-here-doc-end-match) nil t) - (progn - (set-match-data (list beg (point))) - t))))) - - (defun ruby-font-lock-maybe-here-docs (limit) - (let (beg) - (save-excursion - (if (re-search-backward ruby-here-doc-beg-re nil t) - (progn - (beginning-of-line) - (forward-line) - (setq beg (point))))) - (if (and beg - (let ((end-match (ruby-here-doc-end-match))) - (and (not (re-search-backward end-match beg t)) - (re-search-forward end-match nil t)))) - (progn - (set-match-data (list beg (point))) - t) - nil))) - - (defvar ruby-font-lock-keywords + (defconst ruby-font-lock-keywords (list ;; functions '("^\\s *def\\s +\\([^( \t\n]+\\)" @@ -1141,6 +1183,8 @@ "\\|") "\\)\\>\\)") 2) + ;; here-doc beginnings + (list ruby-here-doc-beg-re 0 'font-lock-string-face) ;; variables '("\\(^\\|[^_:.@$]\\|\\.\\.\\)\\b\\(nil\\|self\\|true\\|false\\)\\>" 2 font-lock-variable-name-face) @@ -1154,13 +1198,6 @@ 0 font-lock-comment-face t) '(ruby-font-lock-maybe-docs 0 font-lock-comment-face t) - ;; "here" document - '(ruby-font-lock-here-docs - 0 font-lock-string-face t) - '(ruby-font-lock-maybe-here-docs - 0 font-lock-string-face t) - `(,ruby-here-doc-beg-re - 0 font-lock-string-face t) ;; general delimited string '("\\(^\\|[[ \t\n<+(,=]\\)\\(%[xrqQwW]?\\([^<[{(a-zA-Z0-9 \n]\\)[^\n\\\\]*\\(\\\\.[^\n\\\\]*\\)*\\(\\3\\)\\)" (2 font-lock-string-face)) --------------050409000902070401060508--