X-Git-Url: http://git.hcoop.net/bpt/emacs.git/blobdiff_plain/efc00ab16e2890b75d7224434ac43fe944ade4dd..0877d0dc24ee792b9b14592869ea1aa0934aee58:/lisp/progmodes/ruby-mode.el diff --git a/lisp/progmodes/ruby-mode.el b/lisp/progmodes/ruby-mode.el index 5d79437c3c..a42a66dfa8 100644 --- a/lisp/progmodes/ruby-mode.el +++ b/lisp/progmodes/ruby-mode.el @@ -1,13 +1,13 @@ ;;; ruby-mode.el --- Major mode for editing Ruby files -;; Copyright (C) 1994-2012 Free Software Foundation, Inc. +;; Copyright (C) 1994-2013 Free Software Foundation, Inc. ;; Authors: Yukihiro Matsumoto ;; Nobuyoshi Nakada ;; URL: http://www.emacswiki.org/cgi-bin/wiki/RubyMode ;; Created: Fri Feb 4 14:49:13 JST 1994 ;; Keywords: languages ruby -;; Version: 1.0 +;; Version: 1.2 ;; This file is part of GNU Emacs. @@ -64,8 +64,9 @@ "Regexp to match keywords that nest without blocks.") (defconst ruby-indent-beg-re - (concat "\\(\\s *" (regexp-opt '("class" "module" "def") t) "\\)\\|" - (regexp-opt '("if" "unless" "case" "while" "until" "for" "begin"))) + (concat "^\\(\\s *" (regexp-opt '("class" "module" "def")) "\\|" + (regexp-opt '("if" "unless" "case" "while" "until" "for" "begin")) + "\\)\\_>") "Regexp to match where the indentation gets deeper.") (defconst ruby-modifier-beg-keywords @@ -98,10 +99,21 @@ (defconst ruby-block-end-re "\\_") +(defconst ruby-defun-beg-re + '"\\(def\\|class\\|module\\)" + "Regexp to match the beginning of a defun, in the general sense.") + +(defconst ruby-singleton-class-re + "class\\s *<<" + "Regexp to match the beginning of a singleton class context.") + (eval-and-compile (defconst ruby-here-doc-beg-re "\\(<\\)<\\(-\\)?\\(\\([a-zA-Z0-9_]+\\)\\|[\"]\\([^\"]+\\)[\"]\\|[']\\([^']+\\)[']\\)" - "Regexp to match the beginning of a heredoc.")) + "Regexp to match the beginning of a heredoc.") + + (defconst ruby-expression-expansion-re + "[^\\]\\(\\\\\\\\\\)*\\(#\\({[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\|\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+\\)\\)")) (defun ruby-here-doc-end-match () "Return a regexp to find the end of a heredoc. @@ -138,20 +150,12 @@ This should only be called after matching against `ruby-here-doc-beg-re'." (defvar ruby-mode-map (let ((map (make-sparse-keymap))) - (define-key map "{" 'ruby-electric-brace) - (define-key map "}" 'ruby-electric-brace) - (define-key map (kbd "M-C-a") 'ruby-beginning-of-defun) - (define-key map (kbd "M-C-e") 'ruby-end-of-defun) (define-key map (kbd "M-C-b") 'ruby-backward-sexp) (define-key map (kbd "M-C-f") 'ruby-forward-sexp) (define-key map (kbd "M-C-p") 'ruby-beginning-of-block) (define-key map (kbd "M-C-n") 'ruby-end-of-block) - (define-key map (kbd "M-C-h") 'ruby-mark-defun) (define-key map (kbd "M-C-q") 'ruby-indent-exp) - (define-key map (kbd "C-M-h") 'backward-kill-word) - (define-key map (kbd "C-j") 'reindent-then-newline-and-indent) - (define-key map (kbd "C-m") 'newline) - (define-key map (kbd "C-c C-c") 'comment-region) + (define-key map (kbd "C-c {") 'ruby-toggle-block) map) "Keymap used in Ruby mode.") @@ -380,11 +384,21 @@ and `\\' when preceded by `?'." ((and (eq c ?:) (or (not b) (eq (char-syntax b) ? )))) ((eq c ?\\) (eq b ??))))) +(defun ruby-singleton-class-p (&optional pos) + (save-excursion + (when pos (goto-char pos)) + (forward-word -1) + (and (or (bolp) (not (eq (char-before (point)) ?_))) + (looking-at ruby-singleton-class-re)))) + (defun ruby-expr-beg (&optional option) - "TODO: document." + "Check if point is possibly at the beginning of an expression. +OPTION specifies the type of the expression. +Can be one of `heredoc', `modifier', `expr-qstr', `expr-re'." (save-excursion (store-match-data nil) - (let ((space (skip-chars-backward " \t"))) + (let ((space (skip-chars-backward " \t")) + (start (point))) (cond ((bolp) t) ((progn @@ -393,9 +407,10 @@ and `\\' when preceded by `?'." (or (eq (char-syntax (char-before (point))) ?w) (ruby-special-char-p)))) nil) - ((and (eq option 'heredoc) (< space 0)) t) - ((or (looking-at ruby-operator-re) - (looking-at "[\\[({,;]") + ((looking-at ruby-operator-re)) + ((eq option 'heredoc) + (and (< space 0) (not (ruby-singleton-class-p start)))) + ((or (looking-at "[\\[({,;]") (and (looking-at "[!?]") (or (not (eq option 'modifier)) (bolp) @@ -409,7 +424,7 @@ and `\\' when preceded by `?'." ruby-block-mid-keywords) 'words)) (goto-char (match-end 0)) - (not (looking-at "\\s_"))) + (not (looking-at "\\s_\\|!"))) ((eq option 'expr-qstr) (looking-at "[a-zA-Z][a-zA-z0-9_]* +%[^ \t]")) ((eq option 'expr-re) @@ -581,9 +596,7 @@ and `\\' when preceded by `?'." (eq ?. w))))) (goto-char pnt) (setq w (char-after (point))) - (not (eq ?_ w)) (not (eq ?! w)) - (not (eq ?? w)) (skip-chars-forward " \t") (goto-char (match-beginning 0)) (or (not (looking-at ruby-modifier-re)) @@ -594,7 +607,7 @@ and `\\' when preceded by `?'." (goto-char pnt)) ((looking-at ":\\(['\"]\\)") (goto-char (match-beginning 1)) - (ruby-forward-string (buffer-substring (match-beginning 1) (match-end 1)) end)) + (ruby-forward-string (match-string 1) end t)) ((looking-at ":\\([-,.+*/%&|^~<>]=?\\|===?\\|<=>\\|![~=]?\\)") (goto-char (match-end 0))) ((looking-at ":\\([a-zA-Z_][a-zA-Z_0-9]*[!?=]?\\)?") @@ -794,7 +807,7 @@ and `\\' when preceded by `?'." ;; (not (or (eolp) (looking-at "#") ;; (and (eq (car (nth 1 state)) ?{) ;; (looking-at "|")))))) - ;; Not a regexp or general delimited literal. + ;; Not a regexp or percent literal. (null (nth 0 (ruby-parse-region (or begin parse-start) (point)))) (or (not (eq ?| (char-after (point)))) @@ -834,20 +847,13 @@ and `\\' when preceded by `?'." (+ indent ruby-indent-level) indent)))) -(defun ruby-electric-brace (arg) - "Insert a brace and re-indent the current line." - (interactive "P") - (self-insert-command (prefix-numeric-value arg)) - (ruby-indent-line t)) - -;; TODO: Why isn't one ruby-*-of-defun written in terms of the other? (defun ruby-beginning-of-defun (&optional arg) "Move backward to the beginning of the current top-level defun. With ARG, move backward multiple defuns. Negative ARG means move forward." (interactive "p") - (and (re-search-backward (concat "^\\(" ruby-block-beg-re "\\)\\b") - nil 'move (or arg 1)) + (and (re-search-backward (concat "^\\s *" ruby-defun-beg-re "\\_>") + nil t (or arg 1)) (beginning-of-line))) (defun ruby-end-of-defun (&optional arg) @@ -855,54 +861,69 @@ move forward." With ARG, move forward multiple defuns. Negative ARG means move backward." (interactive "p") - (and (re-search-forward (concat "^\\(" ruby-block-end-re "\\)\\($\\|\\b[^_]\\)") - nil 'move (or arg 1)) - (beginning-of-line)) - (forward-line 1)) + (ruby-forward-sexp) + (when (looking-back (concat "^\\s *" ruby-block-end-re)) + (forward-line 1))) (defun ruby-beginning-of-indent () - "TODO: document" - ;; I don't understand this function. - ;; It seems like it should move to the line where indentation should deepen, - ;; but ruby-indent-beg-re only accounts for whitespace before class, module and def, - ;; so this will only match other block beginners at the beginning of the line. - (and (re-search-backward (concat "^\\(" ruby-indent-beg-re "\\)\\_>") nil 'move) - (beginning-of-line))) + "Backtrack to a line which can be used as a reference for +calculating indentation on the lines after it." + (while (and (re-search-backward ruby-indent-beg-re nil 'move) + (if (ruby-in-ppss-context-p 'anything) + t + ;; We can stop, then. + (beginning-of-line))))) (defun ruby-move-to-block (n) - "Move to the beginning (N < 0) or the end (N > 0) of the current block -or blocks containing the current block." - ;; TODO: Make this work for n > 1, - ;; make it not loop for n = 0, - ;; document body - (let (start pos done down) - (setq start (ruby-calculate-indent)) - (setq down (looking-at (if (< n 0) ruby-block-end-re - (concat "\\<\\(" ruby-block-beg-re "\\)\\>")))) - (while (and (not done) (not (if (< n 0) (bobp) (eobp)))) - (forward-line n) - (cond - ((looking-at "^\\s *$")) - ((looking-at "^\\s *#")) - ((and (> n 0) (looking-at "^=begin\\>")) - (re-search-forward "^=end\\>")) - ((and (< n 0) (looking-at "^=end\\>")) - (re-search-backward "^=begin\\>")) - (t - (setq pos (current-indentation)) + "Move to the beginning (N < 0) or the end (N > 0) of the +current block, a sibling block, or an outer block. Do that (abs N) times." + (let ((orig (point)) + (start (ruby-calculate-indent)) + (signum (if (> n 0) 1 -1)) + (backward (< n 0)) + down pos done) + (dotimes (_ (abs n)) + (setq done nil) + (setq down (save-excursion + (back-to-indentation) + ;; There is a block start or block end keyword on this + ;; line, don't need to look for another block. + (and (re-search-forward + (if backward ruby-block-end-re + (concat "\\_<\\(" ruby-block-beg-re "\\)\\_>")) + (line-end-position) t) + (not (nth 8 (syntax-ppss)))))) + (while (and (not done) (not (if backward (bobp) (eobp)))) + (forward-line signum) (cond - ((< start pos) - (setq down t)) - ((and down (= pos start)) - (setq done t)) - ((> start pos) - (setq done t))))) - (if done - (save-excursion - (back-to-indentation) - (if (looking-at (concat "\\<\\(" ruby-block-mid-re "\\)\\>")) - (setq done nil)))))) - (back-to-indentation)) + ;; Skip empty and commented out lines. + ((looking-at "^\\s *$")) + ((looking-at "^\\s *#")) + ;; Skip block comments; + ((and (not backward) (looking-at "^=begin\\>")) + (re-search-forward "^=end\\>")) + ((and backward (looking-at "^=end\\>")) + (re-search-backward "^=begin\\>")) + (t + (setq pos (current-indentation)) + (cond + ;; Deeper indentation, we found a block. + ;; FIXME: We can't recognize empty blocks this way. + ((< start pos) + (setq down t)) + ;; Block found, and same indentation as when started, stop. + ((and down (= pos start)) + (setq done t)) + ;; Shallower indentation, means outer block, can stop now. + ((> start pos) + (setq done t))))) + (if done + (save-excursion + (back-to-indentation) + ;; Not really at the first or last line of the block, move on. + (if (looking-at (concat "\\<\\(" ruby-block-mid-re "\\)\\>")) + (setq done nil)))))) + (back-to-indentation))) (defun ruby-beginning-of-block (&optional arg) "Move backward to the beginning of the current block. @@ -913,8 +934,7 @@ With ARG, move up multiple blocks." (defun ruby-end-of-block (&optional arg) "Move forward to the end of the current block. With ARG, move out of multiple blocks." - ;; Passing a value > 1 to ruby-move-to-block currently doesn't work. - (interactive) + (interactive "p") (ruby-move-to-block (or arg 1))) (defun ruby-forward-sexp (&optional arg) @@ -1007,15 +1027,6 @@ With ARG, do it many times. Negative ARG means move forward." ((error))) i))) -(defun ruby-mark-defun () - "Put mark at end of this Ruby function, point at beginning." - (interactive) - (push-mark (point)) - (ruby-end-of-defun) - (push-mark (point) nil t) - (ruby-beginning-of-defun) - (re-search-backward "^\n" (- (point) 1) t)) - (defun ruby-indent-exp (&optional ignored) "Indent each line in the balanced expression following the point." (interactive "*P") @@ -1046,86 +1057,181 @@ For example: #exit String#gsub Net::HTTP#active? - File::open. + File.open See `add-log-current-defun-function'." - ;; TODO: Document body - ;; Why does this append a period to class methods? (condition-case nil (save-excursion - (let (mname mlist (indent 0)) - ;; get current method (or class/module) - (if (re-search-backward - (concat "^[ \t]*\\(def\\|class\\|module\\)[ \t]+" - "\\(" - ;; \\. and :: for class method + (let ((indent 0) mname mlist + (start (point)) + (definition-re + (concat "^[ \t]*" ruby-defun-beg-re "[ \t]+" + "\\(" + ;; \\. and :: for class methods "\\([A-Za-z_]" ruby-symbol-re "*\\|\\.\\|::" "\\)" - "+\\)") - nil t) - (progn - (setq mname (match-string 2)) - (unless (string-equal "def" (match-string 1)) - (setq mlist (list mname) mname nil)) - (goto-char (match-beginning 1)) - (setq indent (current-column)) - (beginning-of-line))) - ;; nest class/module + "+\\)"))) + ;; Get the current method definition (or class/module). + (when (re-search-backward definition-re nil t) + (goto-char (match-beginning 1)) + (when (ruby-block-contains-point start) + ;; We're inside the method, class or module. + (setq mname (match-string 2)) + (unless (string-equal "def" (match-string 1)) + (setq mlist (list mname) mname nil))) + (setq indent (current-column)) + (beginning-of-line)) + ;; Walk up the class/module nesting. (while (and (> indent 0) - (re-search-backward - (concat - "^[ \t]*\\(class\\|module\\)[ \t]+" - "\\([A-Z]" ruby-symbol-re "*\\)") - nil t)) + (re-search-backward definition-re nil t)) (goto-char (match-beginning 1)) - (if (< (current-column) indent) - (progn - (setq mlist (cons (match-string 2) mlist)) - (setq indent (current-column)) - (beginning-of-line)))) + (when (ruby-block-contains-point start) + (setq mlist (cons (match-string 2) mlist)) + (setq indent (current-column)) + (beginning-of-line))) + ;; Process the method name. (when mname (let ((mn (split-string mname "\\.\\|::"))) (if (cdr mn) (progn - (cond - ((string-equal "" (car mn)) - (setq mn (cdr mn) mlist nil)) - ((string-equal "self" (car mn)) - (setq mn (cdr mn))) - ((let ((ml (nreverse mlist))) + (unless (string-equal "self" (car mn)) ; def self.foo + ;; def C.foo + (let ((ml (nreverse mlist))) + ;; If the method name references one of the + ;; containing modules, drop the more nested ones. (while ml (if (string-equal (car ml) (car mn)) (setq mlist (nreverse (cdr ml)) ml nil)) - (or (setq ml (cdr ml)) (nreverse mlist)))))) - (if mlist - (setcdr (last mlist) mn) - (setq mlist mn)) - (setq mn (last mn 2)) - (setq mname (concat "." (cadr mn))) - (setcdr mn nil)) - (setq mname (concat "#" mname))))) - ;; generate string + (or (setq ml (cdr ml)) (nreverse mlist)))) + (if mlist + (setcdr (last mlist) (butlast mn)) + (setq mlist (butlast mn)))) + (setq mname (concat "." (car (last mn))))) + ;; See if the method is in singleton class context. + (let ((in-singleton-class + (when (re-search-forward ruby-singleton-class-re start t) + (goto-char (match-beginning 0)) + (ruby-block-contains-point start)))) + (setq mname (concat + (if in-singleton-class "." "#") + mname)))))) + ;; Generate the string. (if (consp mlist) (setq mlist (mapconcat (function identity) mlist "::"))) (if mname (if mlist (concat mlist mname) mname) mlist))))) +(defun ruby-block-contains-point (pt) + (save-excursion + (save-match-data + (ruby-forward-sexp) + (> (point) pt)))) + +(defun ruby-brace-to-do-end (orig end) + (let (beg-marker end-marker) + (goto-char end) + (when (eq (char-before) ?\}) + (delete-char -1) + (when (save-excursion + (skip-chars-backward " \t") + (not (bolp))) + (insert "\n")) + (insert "end") + (setq end-marker (point-marker)) + (when (and (not (eobp)) (eq (char-syntax (char-after)) ?w)) + (insert " ")) + (goto-char orig) + (delete-char 1) + (when (eq (char-syntax (char-before)) ?w) + (insert " ")) + (insert "do") + (setq beg-marker (point-marker)) + (when (looking-at "\\(\\s \\)*|") + (unless (match-beginning 1) + (insert " ")) + (goto-char (1+ (match-end 0))) + (search-forward "|")) + (unless (looking-at "\\s *$") + (insert "\n")) + (indent-region beg-marker end-marker) + (goto-char beg-marker) + t))) + +(defun ruby-do-end-to-brace (orig end) + (let (beg-marker end-marker beg-pos end-pos) + (goto-char (- end 3)) + (when (looking-at ruby-block-end-re) + (delete-char 3) + (setq end-marker (point-marker)) + (insert "}") + (goto-char orig) + (delete-char 2) + (insert "{") + (setq beg-marker (point-marker)) + (when (looking-at "\\s +|") + (delete-char (- (match-end 0) (match-beginning 0) 1)) + (forward-char) + (re-search-forward "|" (line-end-position) t)) + (save-excursion + (skip-chars-forward " \t\n\r") + (setq beg-pos (point)) + (goto-char end-marker) + (skip-chars-backward " \t\n\r") + (setq end-pos (point))) + (when (or + (< end-pos beg-pos) + (and (= (line-number-at-pos beg-pos) (line-number-at-pos end-pos)) + (< (+ (current-column) (- end-pos beg-pos) 2) fill-column))) + (just-one-space -1) + (goto-char end-marker) + (just-one-space -1)) + (goto-char beg-marker) + t))) + +(defun ruby-toggle-block () + "Toggle block type from do-end to braces or back. +The block must begin on the current line or above it and end after the point. +If the result is do-end block, it will always be multiline." + (interactive) + (let ((start (point)) beg end) + (end-of-line) + (unless + (if (and (re-search-backward "\\({\\)\\|\\_ end start))) + (if (match-beginning 1) + (ruby-brace-to-do-end beg end) + (ruby-do-end-to-brace beg end))) + (goto-char start)))) + (declare-function ruby-syntax-propertize-heredoc "ruby-mode" (limit)) -(declare-function ruby-syntax-general-delimiters-goto-beg "ruby-mode" ()) -(declare-function ruby-syntax-propertize-general-delimiters "ruby-mode" (limit)) +(declare-function ruby-syntax-enclosing-percent-literal "ruby-mode" (limit)) +(declare-function ruby-syntax-propertize-percent-literal "ruby-mode" (limit)) (if (eval-when-compile (fboundp #'syntax-propertize-rules)) ;; New code that works independently from font-lock. (progn + (eval-and-compile + (defconst ruby-percent-literal-beg-re + "\\(%\\)[qQrswWx]?\\([[:punct:]]\\)" + "Regexp to match the beginning of percent literal.") + + (defconst ruby-syntax-methods-before-regexp + '("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.")) + (defun ruby-syntax-propertize-function (start end) "Syntactic keywords for Ruby mode. See `syntax-propertize-function'." (goto-char start) (ruby-syntax-propertize-heredoc end) - (ruby-syntax-general-delimiters-goto-beg) + (ruby-syntax-enclosing-percent-literal end) (funcall (syntax-propertize-rules - ;; #{ }, #$hoge, #@foo are not comments. - ("\\(#\\)[{$@]" (1 ".")) ;; $' $" $` .... are variables. ;; ?' ?" ?` are ascii codes. ("\\([?$]\\)[#\"'`]" @@ -1133,37 +1239,36 @@ See `add-log-current-defun-function'." ;; Not within a string. (nth 3 (syntax-ppss (match-beginning 0)))) (string-to-syntax "\\")))) - ;; Regexps: regexps are distinguished from division either because - ;; of the keyword/symbol before them, or because of the code - ;; following them. + ;; 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. - "\\(?:\\(^\\|[[=(,~?:;<>]\\|\\(?:^\\|\\s \\)" + "\\(^\\|[[=(,~?:;<>]" + ;; Control flow keywords and operators following bol or whitespace. + "\\|\\(?:^\\|\\s \\)" (regexp-opt '("if" "elsif" "unless" "while" "until" "when" "and" - "or" "&&" "||" - "gsub" "gsub!" "sub" "sub!" "scan" "split" "split!")) - "\\)\\s *\\)?" + "or" "not" "&&" "||")) + ;; Method name from the list. + "\\|\\_<" + (regexp-opt ruby-syntax-methods-before-regexp) + "\\)\\s *" ;; The regular expression itself. - "\\(/\\)[^/\n\\\\]*\\(?:\\\\.[^/\n\\\\]*\\)*\\(/\\)" - ;; Special code that cannot follow a division operator. - ;; FIXME: Just because the second slash of "/foo/ do bar" can't - ;; be a division, doesn't mean it can't *start* a regexp, as in - ;; "x = toto/foo; if /do bar/". - "\\([imxo]*\\s *\\(?:,\\|\\_\\)\\)?") - (2 (when (or (match-beginning 1) (match-beginning 4)) - (string-to-syntax "\"/"))) - (3 (if (or (match-beginning 1) (match-beginning 4)) - (string-to-syntax "\"/") - (goto-char (match-end 2))))) + "\\(/\\)[^/\n\\\\]*\\(?:\\\\.[^/\n\\\\]*\\)*\\(/\\)") + (2 (string-to-syntax "\"/")) + (3 (string-to-syntax "\"/"))) ("^=en\\(d\\)\\_>" (1 "!")) ("^\\(=\\)begin\\_>" (1 "!")) ;; Handle here documents. ((concat ruby-here-doc-beg-re ".*\\(\n\\)") - (7 (prog1 "\"" (ruby-syntax-propertize-heredoc end)))) + (7 (unless (ruby-singleton-class-p (match-beginning 0)) + (put-text-property (match-beginning 7) (match-end 7) + 'syntax-table (string-to-syntax "\"")) + (ruby-syntax-propertize-heredoc end)))) ;; Handle percent literals: %w(), %q{}, etc. - ("\\(?:^\\|[[ \t\n<+(,=]\\)\\(%\\)[qQrswWx]?\\([[:punct:]]\\)" - (1 (prog1 "|" (ruby-syntax-propertize-general-delimiters end))))) - (point) end)) + ((concat "\\(?:^\\|[[ \t\n<+(,=]\\)" ruby-percent-literal-beg-re) + (1 (prog1 "|" (ruby-syntax-propertize-percent-literal end))))) + (point) end) + (ruby-syntax-propertize-expansions start end)) (defun ruby-syntax-propertize-heredoc (limit) (let ((ppss (syntax-ppss)) @@ -1174,7 +1279,8 @@ See `add-log-current-defun-function'." (beginning-of-line) (while (re-search-forward ruby-here-doc-beg-re (line-end-position) t) - (push (concat (ruby-here-doc-end-match) "\n") res))) + (unless (ruby-singleton-class-p (match-beginning 0)) + (push (concat (ruby-here-doc-end-match) "\n") res)))) (let ((start (point))) ;; With multiple openers on the same line, we don't know in which ;; part `start' is, so we have to go back to the beginning. @@ -1189,40 +1295,63 @@ See `add-log-current-defun-function'." ;; inf-loop. (if (< (point) start) (goto-char start)))))) - (defun ruby-syntax-general-delimiters-goto-beg () - (let ((state (syntax-ppss))) - ;; Move to the start of the literal, in case it's multiline. - ;; TODO: determine the literal type more reliably here? + (defun ruby-syntax-enclosing-percent-literal (limit) + (let ((state (syntax-ppss)) + (start (point))) + ;; When already inside percent literal, re-propertize it. (when (eq t (nth 3 state)) (goto-char (nth 8 state)) - (beginning-of-line)))) + (when (looking-at ruby-percent-literal-beg-re) + (ruby-syntax-propertize-percent-literal limit)) + (when (< (point) start) (goto-char start))))) - (defun ruby-syntax-propertize-general-delimiters (limit) + (defun ruby-syntax-propertize-percent-literal (limit) (goto-char (match-beginning 2)) - (let* ((op (char-after)) - (ops (char-to-string op)) - (cl (or (cdr (aref (syntax-table) op)) - (cdr (assoc op '((?< . ?>)))))) - parse-sexp-lookup-properties) - (ignore-errors - (if cl - (progn ; 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)) - ;; If we reached here, the closing delimiter was found. - (put-text-property (1- (point)) (point) - 'syntax-table (string-to-syntax "|"))))) + ;; Not inside a simple string or comment. + (when (eq t (nth 3 (syntax-ppss))) + (let* ((op (char-after)) + (ops (char-to-string op)) + (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)))))) + + (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 "."))))))) ) ;; For Emacsen where syntax-propertize-rules is not (yet) available, @@ -1252,8 +1381,7 @@ This should only be called after matching against `ruby-here-doc-end-re'." (concat "-?\\([\"']\\|\\)" contents "\\1")))))) (defconst ruby-font-lock-syntactic-keywords - `( ;; #{ }, #$hoge, #@foo are not comments - ("\\(#\\)[{$@]" 1 (1 . nil)) + `( ;; the last $', $", $` in the respective string is not variable ;; the last ?', ?", ?` in the respective string is not ascii code ("\\(^\\|[\[ \t\n<+\(,=]\\)\\(['\"`]\\)\\(\\\\.\\|\\2\\|[^'\"`\n\\\\]\\)*?\\\\?[?$]\\(\\2\\)" @@ -1267,7 +1395,7 @@ This should only be called after matching against `ruby-here-doc-end-re'." (4 (7 . ?/)) (6 (7 . ?/))) ("^=en\\(d\\)\\_>" 1 "!") - ;; General delimited string. + ;; Percent literal. ("\\(^\\|[[ \t\n<+(,=]\\)\\(%[xrqQwW]?\\([^<[{(a-zA-Z0-9 \n]\\)[^\n\\\\]*\\(\\\\.[^\n\\\\]*\\)*\\(\\3\\)\\)" (3 "\"") (5 "\"")) @@ -1310,7 +1438,8 @@ isn't in a string or another comment." (let ((old-point (point)) (case-fold-search nil)) (beginning-of-line) (catch 'found-beg - (while (re-search-backward ruby-here-doc-beg-re nil t) + (while (and (re-search-backward ruby-here-doc-beg-re nil t) + (not (ruby-singleton-class-p))) (if (not (or (ruby-in-ppss-context-p 'anything) (ruby-here-doc-find-end old-point))) (throw 'found-beg t))))))) @@ -1470,10 +1599,14 @@ See `font-lock-syntax-table'.") ruby-keyword-end-re) 2) ;; here-doc beginnings - (list ruby-here-doc-beg-re 0 'font-lock-string-face) + `(,ruby-here-doc-beg-re 0 (unless (ruby-singleton-class-p (match-beginning 0)) + 'font-lock-string-face)) ;; variables '("\\(^\\|[^_:.@$]\\|\\.\\.\\)\\b\\(nil\\|self\\|true\\|false\\)\\>" 2 font-lock-variable-name-face) + ;; symbols + '("\\(^\\|[^:]\\)\\(:\\([-+~]@?\\|[/%&|^`]\\|\\*\\*?\\|<\\(<\\|=>?\\)?\\|>[>=]?\\|===?\\|=~\\|![~=]?\\|\\[\\]=?\\|@?\\(\\w\\|_\\)+\\([!?=]\\|\\b_*\\)\\|#{[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\)\\)" + 2 font-lock-constant-face) ;; variables '("\\(\\$\\([^a-zA-Z0-9 \n]\\|[0-9]\\)\\)\\W" 1 font-lock-variable-name-face) @@ -1482,19 +1615,26 @@ See `font-lock-syntax-table'.") ;; constants '("\\(^\\|[^_]\\)\\b\\([A-Z]+\\(\\w\\|_\\)*\\)" 2 font-lock-type-face) - ;; symbols - '("\\(^\\|[^:]\\)\\(:\\([-+~]@?\\|[/%&|^`]\\|\\*\\*?\\|<\\(<\\|=>?\\)?\\|>[>=]?\\|===?\\|=~\\|![~=]?\\|\\[\\]=?\\|\\(\\w\\|_\\)+\\([!?=]\\|\\b_*\\)\\|#{[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\)\\)" - 2 font-lock-reference-face) - '("\\(^\\s *\\|[\[\{\(,]\\s *\\|\\sw\\s +\\)\\(\\(\\sw\\|_\\)+\\):[^:]" 2 font-lock-reference-face) + '("\\(^\\s *\\|[\[\{\(,]\\s *\\|\\sw\\s +\\)\\(\\(\\sw\\|_\\)+\\):[^:]" 2 font-lock-constant-face) ;; expression expansion - '("#\\({[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\|\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+\\)" - 0 font-lock-variable-name-face t) + '(ruby-match-expression-expansion + 2 font-lock-variable-name-face t) ;; warn lower camel case ;'("\\<[a-z]+[a-z0-9]*[A-Z][A-Za-z0-9]*\\([!?]?\\|\\>\\)" ; 0 font-lock-warning-face) ) "Additional expressions to highlight in Ruby mode.") +(defun ruby-match-expression-expansion (limit) + (let* ((prop 'ruby-expansion-match-data) + (pos (next-single-char-property-change (point) prop nil limit)) + value) + (when (and pos (> pos (point))) + (goto-char pos) + (or (and (setq value (get-text-property pos prop)) + (progn (set-match-data value) t)) + (ruby-match-expression-expansion limit))))) + ;;;###autoload (define-derived-mode ruby-mode prog-mode "Ruby" "Major mode for editing Ruby scripts. @@ -1511,6 +1651,10 @@ The variable `ruby-indent-level' controls the amount of indentation. 'ruby-imenu-create-index) (set (make-local-variable 'add-log-current-defun-function) 'ruby-add-log-current-method) + (set (make-local-variable 'beginning-of-defun-function) + 'ruby-beginning-of-defun) + (set (make-local-variable 'end-of-defun-function) + 'ruby-end-of-defun) (add-hook (cond ((boundp 'before-save-hook) 'before-save-hook) @@ -1538,6 +1682,10 @@ The variable `ruby-indent-level' controls the amount of indentation. ;;;###autoload (add-to-list 'auto-mode-alist (cons (purecopy "\\.rb\\'") 'ruby-mode)) +;;;###autoload +(add-to-list 'auto-mode-alist (cons (purecopy "Rakefile\\'") 'ruby-mode)) +;;;###autoload +(add-to-list 'auto-mode-alist (cons (purecopy "\\.gemspec\\'") 'ruby-mode)) ;;;###autoload (dolist (name (list "ruby" "rbx" "jruby" "ruby1.9" "ruby1.8"))