;; Copyright (C) 2009 Free Software Foundation, Inc.
;; Author: Steve Yegge <steve.yegge@gmail.com>
-;; mooz <stillpedant@gmail.com>
+;; Contributors: mooz <stillpedant@gmail.com>
+;; Dmitry Gutov <dgutov@yandex.ru>
;; Version: See `js2-mode-version'
;; Keywords: languages, javascript
UserDataHandler
;; Window
- alert confirm document java navigator prompt screen
+ window alert confirm document java navigator prompt screen
self top
;; W3C CSS
;;; Variables
+(defun js2-mark-safe-local (name pred)
+ "Make the variable NAME buffer-local and mark it as safe file-local
+variable with predicate PRED."
+ (make-variable-buffer-local name)
+ (put name 'safe-local-variable pred))
+
(defvar js2-emacs22 (>= emacs-major-version 22))
(defcustom js2-highlight-level 2
Similar to `c-basic-offset'."
:group 'js2-mode
:type 'integer)
-(make-variable-buffer-local 'js2-basic-offset)
+(js2-mark-safe-local 'js2-basic-offset 'integerp)
;; TODO(stevey): move this code into a separate minor mode.
(defcustom js2-mirror-mode nil
already indented to that predetermined column, indenting will choose
another likely column and indent to that spot. Repeated invocation of
the indent-line function will cycle among the computed alternatives.
-See the function `js2-bounce-indent' for details."
+See the function `js2-bounce-indent' for details. When it is non-nil,
+js2-mode also binds `js2-bounce-indent-backwards' to Shift-Tab."
:type 'boolean
:group 'js2-mode)
regardless of the beginning bracket position."
:group 'js2-mode
:type 'boolean)
+(js2-mark-safe-local 'js2-consistent-level-indent-inner-bracket-p 'booleanp)
-(defcustom js2-use-ast-for-indentation-p nil
- "Non-nil to use AST for indentation and make it more robust."
+(defcustom js2-pretty-multiline-decl-indentation-p t
+ "Non-nil to line up multiline declarations vertically. See the
+function `js-multiline-decl-indentation' for details."
:group 'js2-mode
:type 'boolean)
+(js2-mark-safe-local 'js2-pretty-multiline-decl-indentation-p 'booleanp)
+
+(defcustom js2-always-indent-assigned-expr-in-decls-p nil
+ "If both `js2-pretty-multiline-decl-indentation-p' and this are non-nil,
+always additionally indent function expression or array/object literal
+assigned in a declaration, even when only one var is declared."
+ :group 'js2-mode
+ :type 'boolean)
+(js2-mark-safe-local 'js2-always-indent-assigned-expr-in-decls-p 'booleanp)
(defcustom js2-indent-on-enter-key nil
"Non-nil to have Enter/Return key indent the line.
(mapc (lambda (key)
(define-key map key #'js2-insert-and-indent))
js2-electric-keys))
+ (when js2-bounce-indent-p
+ (define-key map (kbd "<backtab>") #'js2-indent-bounce-backwards))
(define-key map [menu-bar javascript]
(cons "JavaScript" (make-sparse-keymap "JavaScript")))
,delta)
,form
(/ (truncate (* (- (float-time (current-time))
- (float-time ,beg)))
- 10000)
+ (float-time ,beg))
+ 10000))
10000.0))))
(defsubst js2-same-line (pos)
"Signal an error when we encounter an unexpected code path."
(error "failed assertion"))
+(defsubst js2-record-text-property (beg end prop value)
+ "Record a text property to set when parsing finishes."
+ (push (list beg end prop value) js2-mode-deferred-properties))
+
;; I'd like to associate errors with nodes, but for now the
;; easiest thing to do is get the context info from the last token.
(defsubst js2-record-parse-error (msg &optional arg pos len)
The node traversal is approximately lexical-order, although there
are currently no guarantees around this."
- (let ((vfunc (get (aref node 0) 'js2-visitor)))
- ;; visit the node
- (when (funcall callback node nil)
- ;; visit the kids
- (cond
- ((eq vfunc 'js2-visit-none)
- nil) ; don't even bother calling it
- ;; Each AST node type has to define a `js2-visitor' function
- ;; that takes a node and a callback, and calls `js2-visit-ast'
- ;; on each child of the node.
- (vfunc
- (funcall vfunc node callback))
- (t
- (error "%s does not define a visitor-traversal function"
- (aref node 0)))))
- ;; call the end-visit
- (funcall callback node t)))
+ (if node
+ (let ((vfunc (get (aref node 0) 'js2-visitor)))
+ ;; visit the node
+ (when (funcall callback node nil)
+ ;; visit the kids
+ (cond
+ ((eq vfunc 'js2-visit-none)
+ nil) ; don't even bother calling it
+ ;; Each AST node type has to define a `js2-visitor' function
+ ;; that takes a node and a callback, and calls `js2-visit-ast'
+ ;; on each child of the node.
+ (vfunc
+ (funcall vfunc node callback))
+ (t
+ (error "%s does not define a visitor-traversal function"
+ (aref node 0)))))
+ ;; call the end-visit
+ (funcall callback node t))))
(defstruct (js2-node
(:constructor nil)) ; abstract
(put 'cl-struct-js2-return-node 'js2-printer 'js2-print-return-node)
(defun js2-visit-return-node (n v)
- (if (js2-return-node-retval n)
- (js2-visit-ast (js2-return-node-retval n) v)))
+ (js2-visit-ast (js2-return-node-retval n) v))
(defun js2-print-return-node (n i)
(insert (js2-make-pad i) "return")
(defun js2-visit-if-node (n v)
(js2-visit-ast (js2-if-node-condition n) v)
(js2-visit-ast (js2-if-node-then-part n) v)
- (if (js2-if-node-else-part n)
- (js2-visit-ast (js2-if-node-else-part n) v)))
+ (js2-visit-ast (js2-if-node-else-part n) v))
(defun js2-print-if-node (n i)
(let ((pad (js2-make-pad i))
(js2-visit-ast (js2-try-node-try-block n) v)
(dolist (clause (js2-try-node-catch-clauses n))
(js2-visit-ast clause v))
- (if (js2-try-node-finally-block n)
- (js2-visit-ast (js2-try-node-finally-block n) v)))
+ (js2-visit-ast (js2-try-node-finally-block n) v))
(defun js2-print-try-node (n i)
(let ((pad (js2-make-pad i))
(:constructor make-js2-catch-node (&key (type js2-CATCH)
(pos js2-ts-cursor)
len
- var-name
+ param
guard-kwd
guard-expr
block
lp
rp)))
"AST node for a catch clause."
- var-name ; a `js2-name-node'
+ param ; destructuring form or simple name node
guard-kwd ; relative buffer position of "if" in "catch (x if ...)"
guard-expr ; catch condition, a `js2-node'
block ; statements, a `js2-block-node'
(put 'cl-struct-js2-catch-node 'js2-printer 'js2-print-catch-node)
(defun js2-visit-catch-node (n v)
- (js2-visit-ast (js2-catch-node-var-name n) v)
+ (js2-visit-ast (js2-catch-node-param n) v)
(when (js2-catch-node-guard-kwd n)
(js2-visit-ast (js2-catch-node-guard-expr n) v))
(js2-visit-ast (js2-catch-node-block n) v))
(guard-kwd (js2-catch-node-guard-kwd n))
(guard-expr (js2-catch-node-guard-expr n)))
(insert " catch (")
- (js2-print-ast (js2-catch-node-var-name n) 0)
+ (js2-print-ast (js2-catch-node-param n) 0)
(when guard-kwd
(insert " if ")
(js2-print-ast guard-expr 0))
(put 'cl-struct-js2-case-node 'js2-printer 'js2-print-case-node)
(defun js2-visit-case-node (n v)
- (if (js2-case-node-expr n) ; nil for default: case
- (js2-visit-ast (js2-case-node-expr n) v))
+ (js2-visit-ast (js2-case-node-expr n) v)
(js2-visit-block n v))
(defun js2-print-case-node (n i)
target) ; target js2-labels-node or loop/switch statement
(defun js2-visit-jump-node (n v)
- ;; we don't visit the target, since it's a back-link
- (if (js2-jump-node-label n)
- (js2-visit-ast (js2-jump-node-label n) v)))
+ (js2-visit-ast (js2-jump-node-label n) v))
(defstruct (js2-break-node
(:include js2-jump-node)
(put 'cl-struct-js2-function-node 'js2-printer 'js2-print-function-node)
(defun js2-visit-function-node (n v)
- (if (js2-function-node-name n)
- (js2-visit-ast (js2-function-node-name n) v))
+ (js2-visit-ast (js2-function-node-name n) v)
(dolist (p (js2-function-node-params n))
(js2-visit-ast p v))
- (when (js2-function-node-body n)
- (js2-visit-ast (js2-function-node-body n) v)))
+ (js2-visit-ast (js2-function-node-body n) v))
(defun js2-print-function-node (n i)
(let ((pad (js2-make-pad i))
(defun js2-visit-var-init-node (n v)
(js2-visit-ast (js2-var-init-node-target n) v)
- (if (js2-var-init-node-initializer n)
- (js2-visit-ast (js2-var-init-node-initializer n) v)))
+ (js2-visit-ast (js2-var-init-node-initializer n) v))
(defun js2-print-var-init-node (n i)
(let ((pad (js2-make-pad i))
(put 'cl-struct-js2-infix-node 'js2-printer 'js2-print-infix-node)
(defun js2-visit-infix-node (n v)
- (when (js2-infix-node-left n)
- (js2-visit-ast (js2-infix-node-left n) v))
- (when (js2-infix-node-right n)
- (js2-visit-ast (js2-infix-node-right n) v)))
+ (js2-visit-ast (js2-infix-node-left n) v)
+ (js2-visit-ast (js2-infix-node-right n) v))
(defconst js2-operator-tokens
(let ((table (make-hash-table :test 'eq))
(put 'cl-struct-js2-let-node 'js2-printer 'js2-print-let-node)
(defun js2-visit-let-node (n v)
- (when (js2-let-node-vars n)
- (js2-visit-ast (js2-let-node-vars n) v))
- (when (js2-let-node-body n)
- (js2-visit-ast (js2-let-node-body n) v)))
+ (js2-visit-ast (js2-let-node-vars n) v)
+ (js2-visit-ast (js2-let-node-body n) v))
(defun js2-print-let-node (n i)
(insert (js2-make-pad i) "let (")
(js2-visit-ast (js2-new-node-target n) v)
(dolist (arg (js2-new-node-args n))
(js2-visit-ast arg v))
- (when (js2-new-node-initializer n)
- (js2-visit-ast (js2-new-node-initializer n) v)))
+ (js2-visit-ast (js2-new-node-initializer n) v))
(defun js2-print-new-node (n i)
(insert (js2-make-pad i) "new ")
(defun js2-visit-array-node (n v)
(dolist (e (js2-array-node-elems n))
- (when e ; can be nil, e.g. [a, ,b]
- (js2-visit-ast e v))))
+ (js2-visit-ast e v)))
(defun js2-print-array-node (n i)
(insert (js2-make-pad i) "[")
(put 'cl-struct-js2-prop-get-node 'js2-printer 'js2-print-prop-get-node)
(defun js2-visit-prop-get-node (n v)
- (when (js2-prop-get-node-left n)
- (js2-visit-ast (js2-prop-get-node-left n) v))
- (when (js2-prop-get-node-right n)
- (js2-visit-ast (js2-prop-get-node-right n) v)))
+ (js2-visit-ast (js2-prop-get-node-left n) v)
+ (js2-visit-ast (js2-prop-get-node-right n) v))
(defun js2-print-prop-get-node (n i)
(insert (js2-make-pad i))
(put 'cl-struct-js2-elem-get-node 'js2-printer 'js2-print-elem-get-node)
(defun js2-visit-elem-get-node (n v)
- (when (js2-elem-get-node-target n)
- (js2-visit-ast (js2-elem-get-node-target n) v))
- (when (js2-elem-get-node-element n)
- (js2-visit-ast (js2-elem-get-node-element n) v)))
+ (js2-visit-ast (js2-elem-get-node-target n) v)
+ (js2-visit-ast (js2-elem-get-node-element n) v))
(defun js2-print-elem-get-node (n i)
(insert (js2-make-pad i))
(js2-visit-ast (js2-array-comp-node-result n) v)
(dolist (l (js2-array-comp-node-loops n))
(js2-visit-ast l v))
- (if (js2-array-comp-node-filter n)
- (js2-visit-ast (js2-array-comp-node-filter n) v)))
+ (js2-visit-ast (js2-array-comp-node-filter n) v))
(defun js2-print-array-comp-node (n i)
(let ((pad (js2-make-pad i))
(put 'cl-struct-js2-xml-prop-ref-node 'js2-printer 'js2-print-xml-prop-ref-node)
(defun js2-visit-xml-prop-ref-node (n v)
- (if (js2-xml-prop-ref-node-namespace n)
- (js2-visit-ast (js2-xml-prop-ref-node-namespace n) v))
- (if (js2-xml-prop-ref-node-propname n)
- (js2-visit-ast (js2-xml-prop-ref-node-propname n) v)))
+ (js2-visit-ast (js2-xml-prop-ref-node-namespace n) v)
+ (js2-visit-ast (js2-xml-prop-ref-node-propname n) v))
(defun js2-print-xml-prop-ref-node (n i)
(insert (js2-make-pad i))
(put 'cl-struct-js2-xml-elem-ref-node 'js2-printer 'js2-print-xml-elem-ref-node)
(defun js2-visit-xml-elem-ref-node (n v)
- (if (js2-xml-elem-ref-node-namespace n)
- (js2-visit-ast (js2-xml-elem-ref-node-namespace n) v))
- (if (js2-xml-elem-ref-node-expr n)
- (js2-visit-ast (js2-xml-elem-ref-node-expr n) v)))
+ (js2-visit-ast (js2-xml-elem-ref-node-namespace n) v)
+ (js2-visit-ast (js2-xml-elem-ref-node-expr n) v))
(defun js2-print-xml-elem-ref-node (n i)
(insert (js2-make-pad i))
(defsubst js2-nested-function-p (node)
"Return t if NODE is a nested function, or is inside a nested function."
- (js2-function-node-p (if (js2-function-node-p node)
- (js2-node-parent-script-or-fn node)
- (js2-node-parent-script-or-fn
- (js2-node-parent-script-or-fn node)))))
+ (unless (js2-ast-root-p node)
+ (js2-function-node-p (if (js2-function-node-p node)
+ (js2-node-parent-script-or-fn node)
+ (js2-node-parent-script-or-fn
+ (js2-node-parent-script-or-fn node))))))
(defsubst js2-function-param-node-p (node)
"Return non-nil if NODE is a param node of a `js2-function-node'."
;; I'll wait for people to notice incorrect warnings.
((and (= tt js2-EXPR_VOID)
(js2-expr-stmt-node-p node)) ; but not if EXPR_RESULT
- (js2-node-has-side-effects (js2-expr-stmt-node-expr node)))
+ (let ((expr (js2-expr-stmt-node-expr node)))
+ (or (js2-node-has-side-effects expr)
+ (when (js2-string-node-p expr)
+ (string= "use strict" (js2-string-node-value expr))))))
((= tt js2-COMMA)
(js2-node-has-side-effects (js2-infix-node-right node)))
((or (= tt js2-AND)
js2-ts-regexp-flags (js2-collect-string flags)
js2-token-end js2-ts-cursor)
;; tell `parse-partial-sexp' to ignore this range of chars
- (put-text-property js2-token-beg js2-token-end 'syntax-class '(2)))))
+ (js2-record-text-property js2-token-beg js2-token-end 'syntax-class '(2)))))
(defun js2-get-first-xml-token ()
(setq js2-ts-xml-open-tags-count 0
(throw 'return js2-ERROR)))
(t
(unless (js2-read-entity)
- (throw 'return js2-ERROR)))))
+ (throw 'return js2-ERROR))))
+ ;; allow bare CDATA section
+ ;; ex) let xml = <![CDATA[ foo bar baz ]]>;
+ (when (zerop js2-ts-xml-open-tags-count)
+ (throw 'return js2-XMLEND)))
(??
(setq c (js2-get-char)) ;; skip ?
(js2-add-to-string c)
point-entered nil
c-in-sws nil)))
-(defsubst js2-record-text-property (beg end prop value)
- "Record a text property to set when parsing finishes."
- (push (list beg end prop value) js2-mode-deferred-properties))
-
(defconst js2-ecma-global-props
(concat "^"
(regexp-opt
"\\(?:param\\|argument\\)"
"\\)"
"\\s-*\\({[^}]+}\\)?" ; optional type
- "\\s-*\\([a-zA-Z0-9_$]+\\)?" ; name
+ "\\s-*\\[?\\([a-zA-Z0-9_$]+\\)?\\]?" ; name
"\\>")
"Matches jsdoc tags with optional type and optional param name.")
(defun js2-record-name-node (node)
"Saves NODE to `js2-recorded-identifiers' to check for undeclared variables
later. NODE must be a name node."
- (push (list node js2-current-scope
- (setq leftpos (js2-node-abs-pos node))
- (setq end (+ leftpos (js2-node-len node))))
- js2-recorded-identifiers))
+ (let (leftpos end)
+ (push (list node js2-current-scope
+ (setq leftpos (js2-node-abs-pos node))
+ (setq end (+ leftpos (js2-node-len node))))
+ js2-recorded-identifiers)))
(defun js2-highlight-undeclared-vars ()
"After entire parse is finished, look for undeclared variable references.
;; possibly other browsing mechanisms.
;; The basic strategy is to identify function assignment targets of the form
-;; `foo.bar.baz', convert them to (list foo bar baz <position>), and push the
+;; `foo.bar.baz', convert them to (list fn foo bar baz <position>), and push the
;; list into `js2-imenu-recorder'. The lists are merged into a trie-like tree
;; for imenu after parsing is finished.
;; During parsing we accumulate an entry for each definition in
;; the variable `js2-imenu-recorder', like so:
-;; '((a 5)
-;; (b 25)
-;; (foo 100)
-;; (foo bar 200)
-;; (foo bar baz 300)
-;; (foo bar zab 400))
+;; '((fn a 5)
+;; (fn b 25)
+;; (fn foo 100)
+;; (fn foo bar 200)
+;; (fn foo bar baz 300)
+;; (fn foo bar zab 400))
+;; Where 'fn' is the respective function node.
;; After parsing these entries are merged into this alist-trie:
;; '((a . 1)
"this")))
(defsubst js2-node-qname-component (node)
- "Test function: return the name of this node, if it contributes to a qname.
+ "Return the name of this node, if it contributes to a qname.
Returns nil if the node doesn't contribute."
(copy-sequence
(or (js2-prop-node-name node)
(js2-function-node-name node))
(js2-name-node-name (js2-function-node-name node))))))
-(defsubst js2-record-function-qname (fn-node qname)
- "Associate FN-NODE with its QNAME for later lookup.
-This is used in postprocessing the chain list. When we find a chain
-whose first element is a js2-THIS keyword node, we look up the parent
-function and see (using this map) whether it is the tail of a chain.
-If so, we replace the this-node with a copy of the parent's qname."
+(defsubst js2-record-imenu-entry (fn-node qname pos)
+ "Add an entry to `js2-imenu-recorder'.
+FN-NODE should be the current item's function node.
+
+Associate FN-NODE with its QNAME for later lookup.
+This is used in postprocessing the chain list. For each chain, we find
+the parent function, look up its qname, then prepend a copy of it to the chain."
+ (push (cons fn-node (append qname (list pos))) js2-imenu-recorder)
(unless js2-imenu-function-map
(setq js2-imenu-function-map (make-hash-table :test 'eq)))
(puthash fn-node qname js2-imenu-function-map))
((and fun-p
(not var)
(setq fname-node (js2-function-node-name node)))
- (push (setq qname (list fname-node (js2-node-pos node)))
- js2-imenu-recorder)
- (js2-record-function-qname node qname))
+ (js2-record-imenu-entry node (list fname-node) (js2-node-pos node)))
;; for remaining forms, compute left-side tree branch first
((and var (setq qname (js2-compute-nested-prop-get var)))
(cond
;; foo.bar.baz = function
(fun-p
- (push (nconc qname (list (js2-node-pos node)))
- js2-imenu-recorder)
- (js2-record-function-qname node qname))
+ (js2-record-imenu-entry node qname (js2-node-pos node)))
;; foo.bar.baz = object-literal
;; look for nested functions: {a: {b: function() {...} }}
((js2-object-node-p node)
expression. QNAME is a list of nodes representing the assignment target,
e.g. for foo.bar.baz = {...}, QNAME is (foo-node bar-node baz-node).
POS is the absolute position of the node.
-We do a depth-first traversal of NODE. Any functions we find are prefixed
-with QNAME plus the property name of the function and appended to the
-variable `js2-imenu-recorder'."
- (let (left right)
+We do a depth-first traversal of NODE. For any functions we find,
+we append the property name to QNAME, then call `js2-record-imenu-entry'."
+ (let (left right prop-qname)
(dolist (e (js2-object-node-elems node)) ; e is a `js2-object-prop-node'
(let ((left (js2-infix-node-left e))
;; Element positions are relative to the parent position.
;; As a policy decision, we record the position of the property,
;; not the position of the `function' keyword, since the property
;; is effectively the name of the function.
- (push (append qname (list left pos))
- js2-imenu-recorder)
- (js2-record-function-qname right qname)))
+ (js2-record-imenu-entry right (append qname (list left)) pos)))
;; foo: {object-literal} -- add foo to qname, offset position, and recurse
((js2-object-node-p right)
(js2-record-object-literal right
(js2-ast-root-p defining-scope))
(t t))))
-(defun js2-browse-postprocess-chains (chains)
+(defsubst js2-wrapper-function-p (node)
+ "Returns t if NODE is a function expression that's immediately invoked.
+NODE must be `js2-function-node'."
+ (let ((parent (js2-node-parent node)))
+ (or
+ ;; function(){...}();
+ (js2-call-node-p parent)
+ (and (js2-paren-node-p parent)
+ ;; (function(){...})();
+ (or (js2-call-node-p (setq parent (js2-node-parent parent)))
+ ;; (function(){...}).call(this);
+ (and (js2-prop-get-node-p parent)
+ (member (js2-name-node-name (js2-prop-get-node-right parent))
+ '("call" "apply"))
+ (js2-call-node-p (js2-node-parent parent))))))))
+
+(defun js2-browse-postprocess-chains (entries)
"Modify function-declaration name chains after parsing finishes.
Some of the information is only available after the parse tree is complete.
-For instance, following a 'this' reference requires a parent function node."
- (let (result head fn parent-chain p elem)
- (dolist (chain chains)
- ;; examine the head of each node to get its defining scope
- (setq head (car chain))
- ;; if top-level/external, keep as-is
- (if (js2-node-top-level-decl-p head)
- (push chain result)
- (cond
- ;; starts with this-reference
- ((js2-this-node-p head)
- (setq fn (js2-node-parent-script-or-fn head)
- chain (cdr chain))) ; discard this-node
- ;; nested named function
- ((js2-function-node-p (setq parent (js2-node-parent head)))
- (setq fn (js2-node-parent-script-or-fn parent)))
- ;; variable assigned a function expression
- (t (setq fn (js2-node-parent-script-or-fn head))))
- (unless (or (null fn) (js2-nested-function-p fn))
- ;; if the parent function is found, and it's not nested,
- ;; look it up in function-map.
- (if (setq parent-chain (and js2-imenu-function-map
- (gethash fn js2-imenu-function-map)))
- ;; prefix parent fn qname, which is the
- ;; parent-chain sans tail, to this chain.
- (push (append (butlast parent-chain) chain) result)
- ;; parent function is not nested, and not in function-map
- ;; => it's anonymous top-level wrapper, discard.
- (push chain result)))))
+For instance, processing a nested scope requires a parent function node."
+ (let (result head fn current-fn parent-qname qname p elem)
+ (dolist (entry entries)
+ ;; function node goes first
+ (destructuring-bind (current-fn &rest chain) entry
+ ;; examine its defining scope;
+ ;; if top-level/external, keep as-is
+ (if (js2-node-top-level-decl-p (car chain))
+ (push chain result)
+ (when (setq fn (js2-node-parent-script-or-fn current-fn))
+ (setq parent-qname (gethash fn js2-imenu-function-map 'not-found))
+ (when (eq parent-qname 'not-found)
+ ;; anonymous function expressions are not recorded
+ ;; during the parse, so we need to handle this case here
+ (setq parent-qname
+ (if (js2-wrapper-function-p fn)
+ (let ((grandparent (js2-node-parent-script-or-fn fn)))
+ (if (js2-ast-root-p grandparent)
+ nil
+ (gethash grandparent js2-imenu-function-map 'skip)))
+ 'skip))
+ (puthash fn parent-qname js2-imenu-function-map))
+ (unless (eq parent-qname 'skip)
+ ;; prefix parent fn qname to this chain.
+ (let ((qname (append parent-qname chain)))
+ (puthash current-fn (butlast qname) js2-imenu-function-map)
+ (push qname result)))))))
;; finally replace each node in each chain with its name.
(dolist (chain result)
(setq p chain)
'font-lock-comment-face))
(when (memq js2-ts-comment-type '(html preprocessor))
;; Tell cc-engine the bounds of the comment.
- (put-text-property js2-token-beg (1- js2-token-end) 'c-in-sws t))))
+ (js2-record-text-property js2-token-beg (1- js2-token-end) 'c-in-sws t))))
;; This function is called depressingly often, so it should be fast.
;; Most of the time it's looking at the same token it peeked before.
(js2-node-add-children root comment)))
(setf (js2-node-len root) (- end pos))
;; Give extensions a chance to muck with things before highlighting starts.
- (dolist (callback js2-post-parse-callbacks)
- (funcall callback))
- (js2-highlight-undeclared-vars)
+ (let ((js2-additional-externs js2-additional-externs))
+ (dolist (callback js2-post-parse-callbacks)
+ (funcall callback))
+ (js2-highlight-undeclared-vars))
root))
(defun js2-function-parser ()
(let (leftpos)
(js2-define-symbol decl-type (js2-name-node-name node)
node ignore-not-in-block)
- (js2-set-face (setq leftpos (js2-node-abs-pos node))
- (+ leftpos (js2-node-len node))
- face 'record)))
+ (when face
+ (js2-set-face (setq leftpos (js2-node-abs-pos node))
+ (+ leftpos (js2-node-len node))
+ face 'record))))
((js2-object-node-p node)
(dolist (elem (js2-object-node-elems node))
(js2-define-destruct-symbols
decl-type face ignore-not-in-block)))
((js2-array-node-p node)
(dolist (elem (js2-array-node-elems node))
- (js2-define-destruct-symbols elem decl-type face ignore-not-in-block)))
- (t (js2-report-error "msg.no.parm"))))
+ (when elem
+ (js2-define-destruct-symbols elem decl-type face ignore-not-in-block))))
+ (t (js2-report-error "msg.no.parm" nil (js2-node-abs-pos node)
+ (js2-node-len node)))))
(defun js2-parse-function-params (fn-node pos)
(if (js2-match-token js2-RP)
(if (js2-must-match js2-LP "msg.no.paren.for")
(setq lp (- js2-token-beg for-pos)))
(setq tt (js2-peek-token))
- ;; parse init clause
- (let ((js2-in-for-init t)) ; set as dynamic variable
- (cond
- ((= tt js2-SEMI)
- (setq init (make-js2-empty-expr-node)))
- ((or (= tt js2-VAR) (= tt js2-LET))
- (js2-consume-token)
- (setq init (js2-parse-variables tt js2-token-beg)))
- (t
- (setq init (js2-parse-expr)))))
- (if (js2-match-token js2-IN)
- (setq is-for-in t
- in-pos (- js2-token-beg for-pos)
- cond (js2-parse-expr)) ; object over which we're iterating
- ;; else ordinary for loop - parse cond and incr
- (js2-must-match js2-SEMI "msg.no.semi.for")
- (setq cond (if (= (js2-peek-token) js2-SEMI)
- (make-js2-empty-expr-node) ; no loop condition
- (js2-parse-expr)))
- (js2-must-match js2-SEMI "msg.no.semi.for.cond")
- (setq tmp-pos js2-token-end
- incr (if (= (js2-peek-token) js2-RP)
- (make-js2-empty-expr-node :pos tmp-pos)
- (js2-parse-expr))))
- (if (js2-must-match js2-RP "msg.no.paren.for.ctrl")
- (setq rp (- js2-token-beg for-pos)))
- (if (not is-for-in)
- (setq pn (make-js2-for-node :init init
- :condition cond
- :update incr
- :lp lp
- :rp rp))
- ;; cond could be null if 'in obj' got eaten by the init node.
- (if (js2-infix-node-p init)
- ;; it was (foo in bar) instead of (var foo in bar)
- (setq cond (js2-infix-node-right init)
- init (js2-infix-node-left init))
- (if (and (js2-var-decl-node-p init)
- (> (length (js2-var-decl-node-kids init)) 1))
- (js2-report-error "msg.mult.index")))
- (setq pn (make-js2-for-in-node :iterator init
- :object cond
- :in-pos in-pos
- :foreach-p is-for-each
- :each-pos each-pos
- :lp lp
- :rp rp)))
+ ;; 'for' makes local scope
+ (js2-push-scope (make-js2-scope))
(unwind-protect
- (progn
- (js2-enter-loop pn)
- ;; We have to parse the body -after- creating the loop node,
- ;; so that the loop node appears in the js2-loop-set, allowing
- ;; break/continue statements to find the enclosing loop.
- (setf body (js2-parse-statement)
- (js2-loop-node-body pn) body
- (js2-node-pos pn) for-pos
- (js2-node-len pn) (- (js2-node-end body) for-pos))
- (js2-node-add-children pn init cond incr body))
- ;; finally
- (js2-exit-loop))
+ ;; parse init clause
+ (let ((js2-in-for-init t)) ; set as dynamic variable
+ (cond
+ ((= tt js2-SEMI)
+ (setq init (make-js2-empty-expr-node)))
+ ((or (= tt js2-VAR) (= tt js2-LET))
+ (js2-consume-token)
+ (setq init (js2-parse-variables tt js2-token-beg)))
+ (t
+ (setq init (js2-parse-expr)))))
+ (if (js2-match-token js2-IN)
+ (setq is-for-in t
+ in-pos (- js2-token-beg for-pos)
+ ;; scope of iteration target object is not the scope we've created above.
+ ;; stash current scope temporary.
+ cond (let ((js2-current-scope (js2-scope-parent-scope js2-current-scope)))
+ (js2-parse-expr))) ; object over which we're iterating
+ ;; else ordinary for loop - parse cond and incr
+ (js2-must-match js2-SEMI "msg.no.semi.for")
+ (setq cond (if (= (js2-peek-token) js2-SEMI)
+ (make-js2-empty-expr-node) ; no loop condition
+ (js2-parse-expr)))
+ (js2-must-match js2-SEMI "msg.no.semi.for.cond")
+ (setq tmp-pos js2-token-end
+ incr (if (= (js2-peek-token) js2-RP)
+ (make-js2-empty-expr-node :pos tmp-pos)
+ (js2-parse-expr))))
+ (if (js2-must-match js2-RP "msg.no.paren.for.ctrl")
+ (setq rp (- js2-token-beg for-pos)))
+ (if (not is-for-in)
+ (setq pn (make-js2-for-node :init init
+ :condition cond
+ :update incr
+ :lp lp
+ :rp rp))
+ ;; cond could be null if 'in obj' got eaten by the init node.
+ (if (js2-infix-node-p init)
+ ;; it was (foo in bar) instead of (var foo in bar)
+ (setq cond (js2-infix-node-right init)
+ init (js2-infix-node-left init))
+ (if (and (js2-var-decl-node-p init)
+ (> (length (js2-var-decl-node-kids init)) 1))
+ (js2-report-error "msg.mult.index")))
+ (setq pn (make-js2-for-in-node :iterator init
+ :object cond
+ :in-pos in-pos
+ :foreach-p is-for-each
+ :each-pos each-pos
+ :lp lp
+ :rp rp)))
+ (unwind-protect
+ (progn
+ (js2-enter-loop pn)
+ ;; We have to parse the body -after- creating the loop node,
+ ;; so that the loop node appears in the js2-loop-set, allowing
+ ;; break/continue statements to find the enclosing loop.
+ (setf body (js2-parse-statement)
+ (js2-loop-node-body pn) body
+ (js2-node-pos pn) for-pos
+ (js2-node-len pn) (- (js2-node-end body) for-pos))
+ (js2-node-add-children pn init cond incr body))
+ ;; finally
+ (js2-exit-loop))
+ (js2-pop-scope))
pn))
(defun js2-parse-try ()
finally-block
saw-default-catch
peek
- var-name
+ param
catch-cond
catch-node
guard-kwd
(js2-report-error "msg.catch.unreachable"))
(if (js2-must-match js2-LP "msg.no.paren.catch")
(setq lp (- js2-token-beg catch-pos)))
- (js2-must-match js2-NAME "msg.bad.catchcond")
- (setq var-name (js2-create-name-node))
+ (js2-push-scope (make-js2-scope))
+ (let ((tt (js2-peek-token)))
+ (cond
+ ;; destructuring pattern
+ ;; catch ({ message, file }) { ... }
+ ((or (= tt js2-LB) (= tt js2-LC))
+ (setq param
+ (js2-define-destruct-symbols (js2-parse-primary-expr-lhs)
+ js2-LET nil)))
+ ;; simple name
+ (t
+ (js2-must-match js2-NAME "msg.bad.catchcond")
+ (setq param (js2-create-name-node))
+ (js2-define-symbol js2-LET js2-ts-string param))))
+ ;; pattern guard
(if (js2-match-token js2-IF)
(setq guard-kwd (- js2-token-beg catch-pos)
catch-cond (js2-parse-expr))
(setq block (js2-parse-statements)
try-end (js2-node-end block)
catch-node (make-js2-catch-node :pos catch-pos
- :var-name var-name
+ :param param
:guard-expr catch-cond
:guard-kwd guard-kwd
:block block
:lp lp
:rp rp))
+ (js2-pop-scope)
(if (js2-must-match js2-RC "msg.no.brace.after.body")
(setq try-end js2-token-beg))
(setf (js2-node-len block) (- try-end (js2-node-pos block))
(js2-node-len catch-node) (- try-end catch-pos))
- (js2-node-add-children catch-node var-name catch-cond block)
+ (js2-node-add-children catch-node param catch-cond block)
(push catch-node catch-blocks)))
((/= peek js2-FINALLY)
(js2-must-match js2-FINALLY "msg.try.no.catchfinally"
(js2-pop-scope))
pn))
-(defsubst js2-define-new-symbol (decl-type name node)
- (js2-scope-put-symbol js2-current-scope
+(defsubst js2-define-new-symbol (decl-type name node &optional scope)
+ (js2-scope-put-symbol (or scope js2-current-scope)
name
(make-js2-symbol decl-type name node)))
(js2-add-strict-warning "msg.var.redecl" name)
(if (and js2-strict-var-hides-function-arg-warning (= sdt js2-LP))
(js2-add-strict-warning "msg.var.hides.arg" name)))
- (js2-define-new-symbol decl-type name node)))
+ (js2-define-new-symbol decl-type name node
+ js2-current-script-or-fn)))
((= decl-type js2-LP)
(if symbol
;; must be duplicate parameter. Second parameter hides the
:value js2-ts-string
:flags flags)
(js2-set-face px-pos js2-ts-cursor 'font-lock-string-face 'record)
- (put-text-property px-pos js2-ts-cursor 'syntax-table '(2))))
+ (js2-record-text-property px-pos js2-ts-cursor 'syntax-table '(2))))
((or (= tt js2-NULL)
(= tt js2-THIS)
(= tt js2-FALSE)
:len (- js2-ts-cursor pos)
:elems (nreverse elems)))
(apply #'js2-node-add-children pn (js2-array-node-elems pn))
- (when after-comma
+ (when (and after-comma (not js2-is-in-lhs))
(js2-parse-warn-trailing-comma "msg.array.trailing.comma"
pos elems after-comma)))
;; destructuring binding
;; Karl for coming up with the initial approach, which packs a lot of
;; punch for so little code.
-(defconst js-possibly-braceless-keyword-re
- (regexp-opt
- '("catch" "do" "else" "finally" "for" "if" "each" "try" "while" "with" "let")
- 'words)
+(defconst js-possibly-braceless-keywords-re
+ (concat "else[ \t]+if\\|for[ \t]+each\\|"
+ (regexp-opt '("catch" "do" "else" "finally" "for" "if"
+ "try" "while" "with" "let")))
"Regular expression matching keywords that are optionally
followed by an opening brace.")
-(defconst js-possibly-braceless-keywords-re
- "\\([ \t}]*else[ \t]+if\\|[ \t}]*for[ \t]+each\\)"
- "Regular expression which matches the keywords which are consist of more than 2 words
-like 'if else' and 'for each', and optionally followed by an opening brace.")
-
(defconst js-indent-operator-re
(concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|"
(regexp-opt '("in" "instanceof") 'words))
"Regular expression matching operators that affect indentation
of continued expressions.")
+(defconst js-declaration-keyword-re
+ (regexp-opt '("var" "let" "const") 'words)
+ "Regular expression matching variable declaration keywords.")
+
;; This function has horrible results if you're typing an array
;; such as [[1, 2], [3, 4], [5, 6]]. Bounce indenting -really- sucks
;; in conjunction with electric-indent, so just disabling it.
(call-interactively cmd)))
;; don't do the electric keys inside comments or strings,
;; and don't do bounce-indent with them.
- (let ((parse-state (parse-partial-sexp (point-min) (point)))
+ (let ((parse-state (syntax-ppss (point)))
(js2-bounce-indent-p (js2-code-at-bol-p)))
(unless (or (nth 3 parse-state)
(nth 4 parse-state))
(defun js-re-search-forward-inner (regexp &optional bound count)
"Auxiliary function for `js-re-search-forward'."
- (let ((parse)
- (saved-point (point-min)))
+ (let (parse saved-point)
(while (> count 0)
(re-search-forward regexp bound)
- (setq parse (parse-partial-sexp saved-point (point)))
+ (setq parse (if saved-point
+ (parse-partial-sexp saved-point (point))
+ (syntax-ppss (point))))
(cond ((nth 3 parse)
(re-search-forward
(concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
(defun js-re-search-backward-inner (regexp &optional bound count)
"Auxiliary function for `js-re-search-backward'."
- (let ((parse)
- (saved-point (point-min)))
+ (let (parse saved-point)
(while (> count 0)
(re-search-backward regexp bound)
- (setq parse (parse-partial-sexp saved-point (point)))
+ (setq parse (if saved-point
+ (parse-partial-sexp saved-point (point))
+ (syntax-ppss (point))))
(cond ((nth 3 parse)
(re-search-backward
(concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
(and (js-re-search-backward "\n" nil t)
(progn
(skip-chars-backward " \t")
- (backward-char)
- (and (js-looking-at-operator-p)
- (and (progn (backward-char)
- (not (looking-at "\\*\\|++\\|--\\|/[/*]"))))))))))
+ (unless (bolp)
+ (backward-char)
+ (and (js-looking-at-operator-p)
+ (and (progn
+ (backward-char)
+ (not (looking-at "\\*\\|++\\|--\\|/[/*]")))))))))))
(defun js-end-of-do-while-loop-p ()
"Returns non-nil if word after point is `while' of a do-while
"\\<while\\>" (point-at-eol) t))
(= (current-indentation) saved-indent)))))))))
-(defun js-get-multiline-declaration-offset ()
- "Returns offset (> 0) if the current line is part of
- multi-line variable declaration like below example, and
- returns 0 otherwise.
+(defun js-multiline-decl-indentation ()
+ "Returns the declaration indentation column if the current line belongs
+to a multiline declaration statement. All declarations are lined up vertically:
+
+var a = 10,
+ b = 20,
+ c = 30;
- var a = 10,
- b = 20,
- c = 30;
+Note that if `js2-always-indent-assigned-expr-in-decls-p' is nil, and the first
+assigned expression is a function or array/object literal, it will be indented
+differently:
+var o = { var bar = 2,
+ foo: 3 o = {
+}, foo: 3
+ bar = 2; };
"
- (let* ((node (js2-node-at-point))
- (pnode (and node (js2-node-parent node)))
- (pnode-type (and pnode (js2-node-type pnode))))
- (if (and node
- (= js2-NAME (js2-node-type node))
- (or
- (= js2-VAR pnode-type)
- (= js2-LET pnode-type)
- (= js2-CONST pnode-type)))
- (if (= js2-CONST pnode-type)
- 6
- 4)
- 0)))
+ (let (forward-sexp-function ; use lisp version
+ at-opening-bracket)
+ (save-excursion
+ (back-to-indentation)
+ (when (not (looking-at js-declaration-keyword-re))
+ (when (looking-at js-indent-operator-re)
+ (goto-char (match-end 0))) ; continued expressions are ok
+ (while (and (not at-opening-bracket)
+ (not (bobp))
+ (let ((pos (point)))
+ (save-excursion
+ (js2-backward-sws)
+ (or (eq (char-before) ?,)
+ (and (not (eq (char-before) ?\;))
+ (and
+ (prog2 (skip-chars-backward "[[:punct:]]")
+ (looking-at js-indent-operator-re)
+ (js2-backward-sws))
+ (not (eq (char-before) ?\;))))
+ (js2-same-line pos)))))
+ (condition-case err
+ (backward-sexp)
+ (scan-error (setq at-opening-bracket t))))
+ (when (looking-at js-declaration-keyword-re)
+ (- (1+ (match-end 0)) (point-at-bol)))))))
(defun js-ctrl-statement-indentation ()
"Returns the proper indentation of the current line if it
(let (forward-sexp-function) ; temporarily unbind it
(save-excursion
(back-to-indentation)
- (when (save-excursion
- (and (not (js2-same-line (point-min)))
- (not (looking-at "{"))
- (js-re-search-backward "[[:graph:]]" nil t)
- (not (looking-at "[{([]"))
- (progn
- (forward-char)
+ (when (and (not (js2-same-line (point-min)))
+ (not (looking-at "{"))
+ (js-re-search-backward "[[:graph:]]" nil t)
+ (not (looking-at "[{([]"))
+ (progn
+ (forward-char)
+ (when (= (char-before) ?\))
;; scan-sexps sometimes throws an error
(ignore-errors (backward-sexp))
- (when (looking-at "(") (backward-word 1))
- (and (save-excursion
- (skip-chars-backward " \t}" (point-at-bol))
- (or (bolp)
- (and (backward-word 1)
- (skip-chars-backward " \t}" (point-at-bol))
- (bolp)
- (looking-at js-possibly-braceless-keywords-re))))
- (looking-at js-possibly-braceless-keyword-re)
+ (skip-chars-backward " \t" (point-at-bol)))
+ (let ((pt (point)))
+ (back-to-indentation)
+ (and (looking-at js-possibly-braceless-keywords-re)
+ (= (match-end 0) pt)
(not (js-end-of-do-while-loop-p))))))
- (save-excursion
- (goto-char (match-beginning 0))
- (+ (current-indentation) js2-basic-offset))))))
+ (+ (current-indentation) js2-basic-offset)))))
(defun js2-indent-in-array-comp (parse-status)
"Return non-nil if we think we're in an array comprehension.
(match-beginning 0)))
;; to skip arbitrary expressions we need the parser,
;; so we'll just guess at it.
- (if (re-search-forward "[^,]* \\(for\\) " end t)
+ (if (and (> end (point)) ; not empty literal
+ (re-search-forward "[^,]]* \\(for\\) " end t))
(match-beginning 1))))))))
(defun js2-array-comp-indentation (parse-status for-kwd)
(let ((ctrl-stmt-indent (js-ctrl-statement-indentation))
(same-indent-p (looking-at "[]})]\\|\\<case\\>\\|\\<default\\>"))
(continued-expr-p (js-continued-expression-p))
- (multiline-declaration-offset (or (and js2-use-ast-for-indentation-p
- (js-get-multiline-declaration-offset))
- 0))
+ (declaration-indent (and js2-pretty-multiline-decl-indentation-p
+ (js-multiline-decl-indentation)))
(bracket (nth 1 parse-status))
beg)
(cond
(ctrl-stmt-indent)
+ ((and declaration-indent continued-expr-p)
+ (+ declaration-indent js2-basic-offset))
+
+ (declaration-indent)
+
(bracket
(goto-char bracket)
(cond
(not js2-consistent-level-indent-inner-bracket-p))
(progn (goto-char (1+ (nth 1 p)))
(skip-chars-forward " \t"))
- (back-to-indentation))
+ (back-to-indentation)
+ (when (and js2-pretty-multiline-decl-indentation-p
+ js2-always-indent-assigned-expr-in-decls-p
+ (looking-at js-declaration-keyword-re))
+ (goto-char (1+ (match-end 0)))))
(cond (same-indent-p
(current-column))
(continued-expr-p
(+ (current-column) (* 2 js2-basic-offset)))
- ((> multiline-declaration-offset 0)
- (+ (current-column) js2-basic-offset multiline-declaration-offset))
(t
(+ (current-column) js2-basic-offset)))))
(t
(continued-expr-p js2-basic-offset)
- ((> multiline-declaration-offset 0)
- (+ multiline-declaration-offset))
-
(t 0)))))
(defun js2-lineup-comment (parse-status)
(skip-chars-forward " \t")
(point)))
-(defun js2-bounce-indent (normal-col parse-status)
+(defun js2-bounce-indent (normal-col parse-status backwards)
"Cycle among alternate computed indentation positions.
PARSE-STATUS is the result of `parse-partial-sexp' from the beginning
of the buffer to the current point. NORMAL-COL is the indentation
column computed by the heuristic guesser based on current paren,
-bracket, brace and statement nesting."
+bracket, brace and statement nesting. If BACKWARDS, cycle positions
+in reverse."
(let ((cur-indent (js2-current-indent))
(old-buffer-undo-list buffer-undo-list)
;; Emacs 21 only has `count-lines', not `line-number-at-pos'
(1+ (count-lines (point-min) (point)))))
positions
pos
+ main-pos
anchor
arglist-cont
same-indent
(current-column)))))
(when pos
(incf pos js2-basic-offset)
- (unless (member pos positions)
- (push pos positions)))
+ (push pos positions))
;; third likely point: same indent as previous line of code.
;; Make it the first likely point if we're not on an
(js2-indent-in-objlit-p parse-status))
(not (js2-arglist-close)))
(setq same-indent pos))
- (unless (member pos positions)
- (push pos positions)))
+ (push pos positions))
;; fourth likely point: first preceding code with less indentation
;; than the immediately preceding code line.
(setq pos (save-excursion
+ (back-to-indentation)
(js2-backward-sws)
(back-to-indentation)
(setq anchor (current-column))
(current-column))
anchor)))
(setq pos (current-column))))
- (unless (member pos positions)
- (push pos positions))
+ (push pos positions)
- ;; put nesting-heuristic position first in list, sort rest
- (setq positions (nreverse (sort positions '<)))
- (setq positions (cons normal-col (delete normal-col positions)))
+ ;; nesting-heuristic position, main by default
+ (push (setq main-pos normal-col) positions)
+
+ ;; delete duplicates and sort positions list
+ (setq positions (sort (delete-dups positions) '<))
;; comma-list continuation lines: prev line indent takes precedence
(if same-indent
- (setq positions
- (cons same-indent
- (sort (delete same-indent positions) '<))))
+ (setq main-pos same-indent))
;; common special cases where we want to indent in from previous line
(if (or (js2-indent-case-block-p)
(js2-indent-objlit-arg-p parse-status))
- (setq positions
- (cons basic-offset
- (delete basic-offset positions))))
+ (setq main-pos basic-offset))
+
+ ;; if bouncing backwards, reverse positions list
+ (if backwards
+ (setq positions (reverse positions)))
;; record whether we're already sitting on one of the alternatives
(setq pos (member cur-indent positions))
+
(cond
;; case 0: we're one one of the alternatives and this is the
;; first time they've pressed TAB on this line (best-guess).
(setq computed-pos 0))
;; case 2: not on any of the computed spots => use main spot
((not pos)
- (setq computed-pos 0))
+ (setq computed-pos (js2-position main-pos positions)))
;; case 3: on last position: cycle to first position
((null (cdr pos))
(setq computed-pos 0))
;; see commentary for `js2-mode-last-indented-line'
(setq js2-mode-last-indented-line current-line))))
+(defun js2-indent-bounce-backwards ()
+ "Calls `js2-indent-line'. When `js2-bounce-indent-p',
+cycles between the computed indentation positions in reverse order."
+ (interactive)
+ (js2-indent-line t))
+
(defsubst js2-1-line-comment-continuation-p ()
"Return t if we're in a 1-line comment continuation.
If so, we don't ever want to use bounce-indent."
(forward-line 0))
(looking-at "\\s-*//"))))))
-(defun js2-indent-line ()
+(defun js2-indent-line (&optional bounce-backwards)
"Indent the current line as JavaScript source text."
(interactive)
- (when js2-use-ast-for-indentation-p
- (js2-reparse))
(let (parse-status
- current-indent
offset
indent-col
moved
;; This has to be set before calling parse-partial-sexp below.
(inhibit-point-motion-hooks t))
(setq parse-status (save-excursion
- (parse-partial-sexp (point-min)
- (point-at-bol)))
+ (syntax-ppss (point-at-bol)))
offset (- (point) (save-excursion
(back-to-indentation)
- (setq current-indent (current-column))
(point))))
(js2-with-underscore-as-word-syntax
(if (nth 4 parse-status)
(js2-lineup-comment parse-status)
(setq indent-col (js-proper-indentation parse-status))
;; see comments below about js2-mode-last-indented-line
- (when
- (cond
- ;; bounce-indenting is disabled during electric-key indent.
- ;; It doesn't work well on first line of buffer.
- ((and js2-bounce-indent-p
- (not (js2-same-line (point-min)))
- (not (js2-1-line-comment-continuation-p)))
- (js2-bounce-indent indent-col parse-status)
- (setq moved t))
- ;; just indent to the guesser's likely spot
- ((/= current-indent indent-col)
- (indent-line-to indent-col)
- (setq moved t)))
- (when (and moved (plusp offset))
- (forward-char offset)))))))
+ (cond
+ ;; bounce-indenting is disabled during electric-key indent.
+ ;; It doesn't work well on first line of buffer.
+ ((and js2-bounce-indent-p
+ (not (js2-same-line (point-min)))
+ (not (js2-1-line-comment-continuation-p)))
+ (js2-bounce-indent indent-col parse-status bounce-backwards))
+ ;; just indent to the guesser's likely spot
+ (t (indent-line-to indent-col)))
+ (when (plusp offset)
+ (forward-char offset))))))
(defun js2-indent-region (start end)
"Indent the region, but don't use bounce indenting."
(let ((js2-bounce-indent-p nil)
- (indent-region-function nil))
- (indent-region start end nil))) ; nil for byte-compiler
+ (indent-region-function nil)
+ (after-change-functions (remq 'js2-mode-edit
+ after-change-functions)))
+ (indent-region start end nil) ; nil for byte-compiler
+ (js2-mode-edit start end (- end start))))
;;;###autoload (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode))
(kill-all-local-variables)
(set-syntax-table js2-mode-syntax-table)
(use-local-map js2-mode-map)
+ (make-local-variable 'comment-start)
+ (make-local-variable 'comment-end)
+ (make-local-variable 'comment-start-skip)
(setq major-mode 'js2-mode
mode-name "JavaScript-IDE"
comment-start "//" ; used by comment-region; don't change it
(add-to-invisibility-spec '(js2-outline . t))
(set (make-local-variable 'line-move-ignore-invisible) t)
(set (make-local-variable 'forward-sexp-function) #'js2-mode-forward-sexp)
+
+ (if (fboundp 'run-mode-hooks)
+ (run-mode-hooks 'js2-mode-hook)
+ (run-hooks 'js2-mode-hook))
+
(setq js2-mode-functions-hidden nil
js2-mode-comments-hidden nil
js2-mode-buffer-dirty-p t
js2-mode-parsing nil)
- (js2-reparse)
- (run-hooks 'js2-mode-hook))
+ (js2-reparse))
(defun js2-mode-exit ()
"Exit `js2-mode' and clean up."
(cancel-timer js2-mode-parse-timer))
(setq js2-mode-parsing nil)
(setq js2-mode-parse-timer
- (run-with-idle-timer js2-idle-timer-delay nil #'js2-reparse)))
+ (run-with-idle-timer js2-idle-timer-delay nil
+ #'js2-mode-idle-reparse (current-buffer))))
+
+(defun js2-mode-idle-reparse (buffer)
+ "Run `js2-reparse' if BUFFER is the current buffer, or schedule
+it to be reparsed when the buffer is selected."
+ (if (eq buffer (current-buffer))
+ (js2-reparse)
+ ;; reparse when the buffer is selected again
+ (with-current-buffer buffer
+ (add-hook 'window-configuration-change-hook
+ #'js2-mode-idle-reparse-inner
+ t))))
+
+(defun js2-mode-idle-reparse-inner ()
+ (remove-hook 'window-configuration-change-hook
+ #'js2-mode-idle-reparse-inner
+ t)
+ (js2-reparse))
(defun js2-mode-edit (beg end len)
"Schedule a new parse after buffer is edited.
(when (or js2-mode-buffer-dirty-p force)
(js2-remove-overlays)
(js2-with-unmodifying-text-property-changes
- (remove-text-properties (point-min) (point-max) '(syntax-table))
(setq js2-mode-buffer-dirty-p nil
js2-mode-fontifications nil
- js2-mode-deferred-properties nil
- js2-additional-externs nil)
+ js2-mode-deferred-properties nil)
(if js2-mode-verbose-parse-p
(message "parsing..."))
(setq time
(setq interrupted-p
(catch 'interrupted
(setq js2-mode-ast (js2-parse))
- (when (plusp js2-highlight-level)
- (js2-mode-fontify-regions))
+ ;; if parsing is interrupted, comments and regex
+ ;; literals stay ignored by `parse-partial-sexp'
+ (remove-text-properties (point-min) (point-max)
+ '(syntax-table))
+ (js2-mode-apply-deferred-properties)
(js2-mode-remove-suppressed-warnings)
(js2-mode-show-warnings)
(js2-mode-show-errors)
for o in (overlays-at pos)
thereis (overlay-get o 'js2-error)))
-(defun js2-mode-fontify-regions ()
- "Apply fontifications recorded during parsing."
- ;; We defer clearing faces as long as possible to eliminate flashing.
- (js2-clear-face (point-min) (point-max))
- ;; have to reverse the recorded fontifications so that errors and
- ;; warnings overwrite the normal fontifications
- (dolist (f (nreverse js2-mode-fontifications))
- (put-text-property (first f) (second f) 'face (third f)))
- (setq js2-mode-fontifications nil)
+(defun js2-mode-apply-deferred-properties ()
+ "Apply fontifications and other text properties recorded during parsing."
+ (when (plusp js2-highlight-level)
+ ;; We defer clearing faces as long as possible to eliminate flashing.
+ (js2-clear-face (point-min) (point-max))
+ ;; Have to reverse the recorded fontifications list so that errors
+ ;; and warnings overwrite the normal fontifications.
+ (dolist (f (nreverse js2-mode-fontifications))
+ (put-text-property (first f) (second f) 'face (third f)))
+ (setq js2-mode-fontifications nil))
(dolist (p js2-mode-deferred-properties)
(apply #'put-text-property p))
(setq js2-mode-deferred-properties nil))
(defun js2-echo-error (old-point new-point)
"Called by point-motion hooks."
(let ((msg (get-text-property new-point 'help-echo)))
- (if msg
+ (if (and msg (or (not (current-message))
+ (string= (current-message) "Quit")))
(message msg))))
(defalias #'js2-echo-help #'js2-echo-error)
"Handle user pressing the Enter key."
(interactive)
(let ((parse-status (save-excursion
- (parse-partial-sexp (point-min) (point)))))
+ (syntax-ppss (point))))
+ (js2-bounce-indent-p nil))
(cond
;; check if we're inside a string
((nth 3 parse-status)
(t
;; should probably figure out what the mode-map says we should do
(if js2-indent-on-enter-key
- (let ((js2-bounce-indent-p nil))
- (js2-indent-line)))
+ (js2-indent-line))
(insert "\n")
(if js2-enter-indents-newline
- (let ((js2-bounce-indent-p nil))
- (js2-indent-line)))))))
+ (js2-indent-line))))))
(defun js2-mode-split-string (parse-status)
"Turn a newline in mid-string into a string concatenation.
(backward-char 1))))
(defun js2-mode-extend-comment ()
- "When inside a comment block, add comment prefix."
+ "Indent the line and, when inside a comment block, add comment prefix."
(let (star single col first-line needs-close)
(save-excursion
(back-to-indentation)
(insert "\n")
(indent-to col)
(insert "*/"))))
- (single
- (when (save-excursion
+ ((and single
+ (save-excursion
(and (zerop (forward-line 1))
- (looking-at "\\s-*//")))
- (indent-to col)
- (insert "// "))))))
+ (looking-at "\\s-*//"))))
+ (indent-to col)
+ (insert "// "))
+ ;; don't need to extend the comment after all
+ (js2-enter-indents-newline
+ (js2-indent-line)))))
(defun js2-beginning-of-line ()
"Toggles point between bol and first non-whitespace char in line.
"Return non-nil if inside a string.
Actually returns the quote character that begins the string."
(let ((parse-state (save-excursion
- (parse-partial-sexp (point-min) (point)))))
+ (syntax-ppss (point)))))
(nth 3 parse-state)))
(defsubst js2-mode-inside-comment-or-string ()
(and comment-start
(<= comment-start (point))))
(let ((parse-state (save-excursion
- (parse-partial-sexp (point-min) (point)))))
+ (syntax-ppss (point)))))
(or (nth 3 parse-state)
(nth 4 parse-state)))))
(defun js2-mode-match-single-quote ()
"Insert matching single-quote."
(interactive)
- (let ((parse-status (parse-partial-sexp (point-min) (point))))
+ (let ((parse-status (syntax-ppss (point))))
;; don't match inside comments, since apostrophe is more common
(if (nth 4 parse-status)
(insert "'")
"Skip over close-paren rather than inserting, where appropriate."
(interactive)
(let* ((here (point))
- (parse-status (parse-partial-sexp (point-min) here))
+ (parse-status (syntax-ppss here))
(open-pos (nth 1 parse-status))
(close last-input-event)
(open (cond