]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/js.el
Update copyright year to 2015
[gnu-emacs] / lisp / progmodes / js.el
index 2ea78fc321c31a26fd1d2de05e0a8ae14e276363..de6a33988a483dd7ab6486917897979690697271 100644 (file)
@@ -1,6 +1,6 @@
 ;;; js.el --- Major mode for editing JavaScript  -*- lexical-binding: t -*-
 
-;; Copyright (C) 2008-2013 Free Software Foundation, Inc.
+;; Copyright (C) 2008-2015 Free Software Foundation, Inc.
 
 ;; Author: Karl Landstrom <karl.landstrom@brgeight.se>
 ;;         Daniel Colascione <dan.colascione@gmail.com>
@@ -55,7 +55,6 @@
 
 (eval-when-compile
   (require 'cl-lib)
-  (require 'comint)
   (require 'ido))
 
 (defvar inferior-moz-buffer)
@@ -460,12 +459,13 @@ The value must be no less than minus `js-indent-level'."
   :group 'js
   :version "24.1")
 
-(defcustom js-auto-indent-flag t
-  "Whether to automatically indent when typing punctuation characters.
-If non-nil, the characters {}();,: also indent the current line
-in Javascript mode."
-  :type 'boolean
-  :group 'js)
+(defcustom js-switch-indent-offset 0
+  "Number of additional spaces for indenting the contents of a switch block.
+The value must not be negative."
+  :type 'integer
+  :safe 'integerp
+  :group 'js
+  :version "24.4")
 
 (defcustom js-flat-functions nil
   "Treat nested functions as top-level functions in `js-mode'.
@@ -1302,7 +1302,7 @@ LIMIT defaults to point."
     (up-list -1)))
 
 (defun js--inside-param-list-p ()
-  "Return non-nil iff point is in a function parameter list."
+  "Return non-nil if point is in a function parameter list."
   (ignore-errors
     (save-excursion
       (js--up-nearby-list)
@@ -1313,7 +1313,7 @@ LIMIT defaults to point."
                              (looking-at "function"))))))))
 
 (defun js--inside-dojo-class-list-p ()
-  "Return non-nil iff point is in a Dojo multiple-inheritance class block."
+  "Return non-nil if point is in a Dojo multiple-inheritance class block."
   (ignore-errors
     (save-excursion
       (js--up-nearby-list)
@@ -1352,7 +1352,7 @@ REGEXPS, but only if FRAMEWORK is in `js-enabled-frameworks'."
 (defun js--forward-destructuring-spec (&optional func)
   "Move forward over a JavaScript destructuring spec.
 If FUNC is supplied, call it with no arguments before every
-variable name in the spec.  Return true iff this was actually a
+variable name in the spec.  Return true if this was actually a
 spec.  FUNC must preserve the match data."
   (pcase (char-after)
     (?\[
@@ -1637,12 +1637,29 @@ This performs fontification according to `js--class-styles'."
                                    js--font-lock-keywords-3)
   "Font lock keywords for `js-mode'.  See `font-lock-keywords'.")
 
+(defconst js--syntax-propertize-regexp-syntax-table
+  (let ((st (make-char-table 'syntax-table (string-to-syntax "."))))
+    (modify-syntax-entry ?\[ "(]" st)
+    (modify-syntax-entry ?\] ")[" st)
+    (modify-syntax-entry ?\\ "\\" st)
+    st))
+
 (defun js-syntax-propertize-regexp (end)
-  (when (eq (nth 3 (syntax-ppss)) ?/)
-    ;; A /.../ regexp.
-    (when (re-search-forward "\\(?:\\=\\|[^\\]\\)\\(?:\\\\\\\\\\)*/" end 'move)
-      (put-text-property (1- (point)) (point)
-                         'syntax-table (string-to-syntax "\"/")))))
+  (let ((ppss (syntax-ppss)))
+    (when (eq (nth 3 ppss) ?/)
+      ;; A /.../ regexp.
+      (while
+          (when (re-search-forward "\\(?:\\=\\|[^\\]\\)\\(?:\\\\\\\\\\)*/"
+                                   end 'move)
+            (if (nth 1 (with-syntax-table
+                           js--syntax-propertize-regexp-syntax-table
+                         (let ((parse-sexp-lookup-properties nil))
+                           (parse-partial-sexp (nth 8 ppss) (point)))))
+                ;; A / within a character class is not the end of a regexp.
+                t
+              (put-text-property (1- (point)) (point)
+                                 'syntax-table (string-to-syntax "\"/"))
+              nil))))))
 
 (defun js-syntax-propertize (start end)
   ;; Javascript allows immediate regular expression objects, written /.../.
@@ -1656,7 +1673,7 @@ This performs fontification according to `js--class-styles'."
     ;; We can probably just add +, -, !, <, >, %, ^, ~, |, &, ?, : at which
     ;; point I think only * and / would be missing which could also be added,
     ;; but need care to avoid affecting the // and */ comment markers.
-    ("\\(?:^\\|[=([{,:;]\\)\\(?:[ \t]\\)*\\(/\\)[^/*]"
+    ("\\(?:^\\|[=([{,:;]\\|\\_<return\\_>\\)\\(?:[ \t]\\)*\\(/\\)[^/*]"
      (1 (ignore
         (forward-char -1)
          (when (or (not (memq (char-after (match-beginning 0)) '(?\s ?\t)))
@@ -1685,7 +1702,7 @@ This performs fontification according to `js--class-styles'."
   "Regular expression matching variable declaration keywords.")
 
 (defconst js--indent-operator-re
-  (concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|"
+  (concat "[-+*/%<>&^|?:.]\\([^-+*/]\\|$\\)\\|!?=\\|"
           (js--regexp-opt-symbol '("in" "instanceof")))
   "Regexp matching operators that affect indentation of continued expressions.")
 
@@ -1712,7 +1729,7 @@ This performs fontification according to `js--class-styles'."
                     (save-excursion (backward-char) (not (looking-at "[/*]/")))
                     (js--looking-at-operator-p)
                    (and (progn (backward-char)
-                               (not (looking-at "++\\|--\\|/[/*]"))))))))))
+                               (not (looking-at "+\\+\\|--\\|/[/*]"))))))))))
 
 
 (defun js--end-of-do-while-loop-p ()
@@ -1750,8 +1767,8 @@ nil."
     (when (save-excursion
             (and (not (eq (point-at-bol) (point-min)))
                  (not (looking-at "[{]"))
+                 (js--re-search-backward "[[:graph:]]" nil t)
                  (progn
-                   (js--re-search-backward "[[:graph:]]" nil t)
                    (or (eobp) (forward-char))
                    (when (= (char-before) ?\)) (backward-list))
                    (skip-syntax-backward " ")
@@ -1767,6 +1784,10 @@ nil."
          (list (cons 'c js-comment-lineup-func))))
     (c-get-syntactic-indentation (list (cons symbol anchor)))))
 
+(defun js--same-line (pos)
+  (and (>= pos (point-at-bol))
+       (<= pos (point-at-eol))))
+
 (defun js--multi-line-declaration-indentation ()
   "Helper function for `js--proper-indentation'.
 Return the proper indentation of the current line if it belongs to a declaration
@@ -1789,8 +1810,7 @@ statement spanning multiple lines; otherwise, return nil."
                                      (looking-at js--indent-operator-re)
                                    (js--backward-syntactic-ws))
                                  (not (eq (char-before) ?\;)))
-                            (and (>= pos (point-at-bol))
-                                 (<= pos (point-at-eol)))))))
+                            (js--same-line pos)))))
           (condition-case nil
               (backward-sexp)
             (scan-error (setq at-opening-bracket t))))
@@ -1798,23 +1818,68 @@ statement spanning multiple lines; otherwise, return nil."
           (goto-char (match-end 0))
           (1+ (current-column)))))))
 
+(defun js--indent-in-array-comp (bracket)
+  "Return non-nil if we think we're in an array comprehension.
+In particular, return the buffer position of the first `for' kwd."
+  (let ((end (point)))
+    (save-excursion
+      (goto-char bracket)
+      (when (looking-at "\\[")
+        (forward-char 1)
+        (js--forward-syntactic-ws)
+        (if (looking-at "[[{]")
+            (let (forward-sexp-function) ; Use Lisp version.
+              (forward-sexp)             ; Skip destructuring form.
+              (js--forward-syntactic-ws)
+              (if (and (/= (char-after) ?,) ; Regular array.
+                       (looking-at "for"))
+                  (match-beginning 0)))
+          ;; To skip arbitrary expressions we need the parser,
+          ;; so we'll just guess at it.
+          (if (and (> end (point)) ; Not empty literal.
+                   (re-search-forward "[^,]]* \\(for\\) " end t)
+                   ;; Not inside comment or string literal.
+                   (not (nth 8 (parse-partial-sexp bracket (point)))))
+              (match-beginning 1)))))))
+
+(defun js--array-comp-indentation (bracket for-kwd)
+  (if (js--same-line for-kwd)
+      ;; First continuation line.
+      (save-excursion
+        (goto-char bracket)
+        (forward-char 1)
+        (skip-chars-forward " \t")
+        (current-column))
+    (save-excursion
+      (goto-char for-kwd)
+      (current-column))))
+
 (defun js--proper-indentation (parse-status)
   "Return the proper indentation for the current line."
   (save-excursion
     (back-to-indentation)
-    (cond ((nth 4 parse-status)
+    (cond ((nth 4 parse-status)    ; inside comment
            (js--get-c-offset 'c (nth 8 parse-status)))
-          ((nth 8 parse-status) 0) ; inside string
-          ((js--ctrl-statement-indentation))
-          ((js--multi-line-declaration-indentation))
+          ((nth 3 parse-status) 0) ; inside string
           ((eq (char-after) ?#) 0)
           ((save-excursion (js--beginning-of-macro)) 4)
+          ;; Indent array comprehension continuation lines specially.
+          ((let ((bracket (nth 1 parse-status))
+                 beg)
+             (and bracket
+                  (not (js--same-line bracket))
+                  (setq beg (js--indent-in-array-comp bracket))
+                  ;; At or after the first loop?
+                  (>= (point) beg)
+                  (js--array-comp-indentation bracket beg))))
+          ((js--ctrl-statement-indentation))
+          ((js--multi-line-declaration-indentation))
           ((nth 1 parse-status)
           ;; A single closing paren/bracket should be indented at the
           ;; same level as the opening statement. Same goes for
           ;; "case" and "default".
-           (let ((same-indent-p (looking-at
-                                 "[]})]\\|\\_<case\\_>\\|\\_<default\\_>"))
+           (let ((same-indent-p (looking-at "[]})]"))
+                 (switch-keyword-p (looking-at "default\\_>\\|case\\_>[^:]"))
                  (continued-expr-p (js--continued-expression-p)))
              (goto-char (nth 1 parse-status)) ; go to the opening char
              (if (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")
@@ -1822,17 +1887,26 @@ statement spanning multiple lines; otherwise, return nil."
                    (skip-syntax-backward " ")
                    (when (eq (char-before) ?\)) (backward-list))
                    (back-to-indentation)
-                   (cond (same-indent-p
-                          (current-column))
-                         (continued-expr-p
-                          (+ (current-column) (* 2 js-indent-level)
-                             js-expr-indent-offset))
-                         (t
-                          (+ (current-column) js-indent-level
-                             (pcase (char-after (nth 1 parse-status))
-                               (?\( js-paren-indent-offset)
-                               (?\[ js-square-indent-offset)
-                               (?\{ js-curly-indent-offset))))))
+                   (let* ((in-switch-p (unless same-indent-p
+                                         (looking-at "\\_<switch\\_>")))
+                          (same-indent-p (or same-indent-p
+                                             (and switch-keyword-p
+                                                  in-switch-p)))
+                          (indent
+                           (cond (same-indent-p
+                                  (current-column))
+                                 (continued-expr-p
+                                  (+ (current-column) (* 2 js-indent-level)
+                                     js-expr-indent-offset))
+                                 (t
+                                  (+ (current-column) js-indent-level
+                                     (pcase (char-after (nth 1 parse-status))
+                                       (?\( js-paren-indent-offset)
+                                       (?\[ js-square-indent-offset)
+                                       (?\{ js-curly-indent-offset)))))))
+                     (if in-switch-p
+                         (+ indent js-switch-indent-offset)
+                       indent)))
                ;; If there is something following the opening
                ;; paren/bracket, everything else should be indented at
                ;; the same level.
@@ -1848,13 +1922,11 @@ statement spanning multiple lines; otherwise, return nil."
 (defun js-indent-line ()
   "Indent the current line as JavaScript."
   (interactive)
-  (save-restriction
-    (widen)
-    (let* ((parse-status
-            (save-excursion (syntax-ppss (point-at-bol))))
-           (offset (- (current-column) (current-indentation))))
-      (indent-line-to (js--proper-indentation parse-status))
-      (when (> offset 0) (forward-char offset)))))
+  (let* ((parse-status
+          (save-excursion (syntax-ppss (point-at-bol))))
+         (offset (- (point) (save-excursion (back-to-indentation) (point)))))
+    (indent-line-to (js--proper-indentation parse-status))
+    (when (> offset 0) (forward-char offset))))
 
 ;;; Filling
 
@@ -2217,6 +2289,9 @@ marker."
 
 (defvar find-tag-marker-ring)           ; etags
 
+;; etags loads ring.
+(declare-function ring-insert "ring" (ring item))
+
 (defun js-find-symbol (&optional arg)
   "Read a JavaScript symbol and jump to it.
 With a prefix argument, restrict symbols to those from the
@@ -2242,11 +2317,8 @@ current buffer.  Pushes a mark onto the tag ring just like
 
 ;;; MozRepl integration
 
-(put 'js-moz-bad-rpc 'error-conditions '(error timeout))
-(put 'js-moz-bad-rpc 'error-message "Mozilla RPC Error")
-
-(put 'js-js-error 'error-conditions '(error js-error))
-(put 'js-js-error 'error-message "Javascript Error")
+(define-error 'js-moz-bad-rpc "Mozilla RPC Error") ;; '(timeout error))
+(define-error 'js-js-error "Javascript Error") ;; '(js-error error))
 
 (defun js--wait-for-matching-output
   (process regexp timeout &optional start)
@@ -2639,6 +2711,11 @@ with `js--js-encode-value'."
    ;; order to catch a prompt that's only partially arrived
    (save-excursion (forward-line 0) (point))))
 
+;; Presumably "inferior-moz-process" loads comint.
+(declare-function comint-send-string "comint" (process string))
+(declare-function comint-send-input "comint"
+                  (&optional no-newline artificial))
+
 (defun js--js-enter-repl ()
   (inferior-moz-process) ; called for side-effect
   (with-current-buffer inferior-moz-buffer
@@ -2697,6 +2774,10 @@ with `js--js-encode-value'."
 (defsubst js--js-true (value)
   (not (js--js-not value)))
 
+;; The somewhat complex code layout confuses the byte-compiler into
+;; thinking this function "might not be defined at runtime".
+(declare-function js--optimize-arglist "js" (arglist))
+
 (eval-and-compile
   (defun js--optimize-arglist (arglist)
     "Convert immediate js< and js! references to deferred ones."
@@ -2824,6 +2905,8 @@ If nil, the whole Array is treated as a JS symbol.")
     (`error (signal 'js-js-error (list (cl-second result))))
     (x (error "Unmatched case in js--js-decode-retval: %S" x))))
 
+(defvar comint-last-input-end)
+
 (defun js--js-funcall (function &rest arguments)
   "Call the Mozilla function FUNCTION with arguments ARGUMENTS.
 If function is a string, look it up as a property on the global
@@ -2996,6 +3079,8 @@ left-to-right."
 
 (defvar js-read-tab-history nil)
 
+(declare-function ido-chop "ido" (items elem))
+
 (defun js--read-tab (prompt)
   "Read a Mozilla tab with prompt PROMPT.
 Return a cons of (TYPE . OBJECT).  TYPE is either 'window or
@@ -3411,6 +3496,10 @@ If one hasn't been set, or if it's stale, prompt for a new one."
   '(when (fboundp 'folding-add-to-marks-list)
      (folding-add-to-marks-list 'js-mode "// {{{" "// }}}" )))
 
+;;;###autoload
+(dolist (name (list "node" "nodejs" "gjs" "rhino"))
+  (add-to-list 'interpreter-mode-alist (cons (purecopy name) 'js-mode)))
+
 (provide 'js)
 
 ;; js.el ends here