branch: master commit a7b8100b52925d82aa1d6fd8606a537ea88b91b9 Merge: 2763c9a 494c421 Author: Dmitry Gutov <dgu...@yandex.ru> Commit: Dmitry Gutov <dgu...@yandex.ru>
Merge commit '494c421bfa6f1b72b577267cb3841b0eff262250' from js2-mode --- packages/js2-mode/js2-mode.el | 582 ++++------------------------------- packages/js2-mode/js2-old-indent.el | 493 +++++++++++++++++++++++++++++ 2 files changed, 549 insertions(+), 526 deletions(-) diff --git a/packages/js2-mode/js2-mode.el b/packages/js2-mode/js2-mode.el index 4a6e733..6ea2461 100644 --- a/packages/js2-mode/js2-mode.el +++ b/packages/js2-mode/js2-mode.el @@ -87,13 +87,14 @@ (require 'cl-lib) (require 'imenu) -(require 'cc-cmds) ; for `c-fill-paragraph' +(require 'js) (eval-and-compile - (require 'cc-mode) ; (only) for `c-populate-syntax-table' - (require 'cc-engine)) ; for `c-paragraph-start' et. al. - -(defvar electric-layout-rules) + (if (version< emacs-version "25.0") + (require 'js2-old-indent) + (defvaralias 'js2-basic-offset 'js-indent-level nil) + (defalias 'js2-proper-indentation 'js--proper-indentation) + (defalias 'js2-indent-line 'js-indent-line))) ;;; Externs (variables presumed to be defined by the host system) @@ -235,78 +236,6 @@ variable with predicate PRED." "An improved JavaScript mode." :group 'languages) -(defcustom js2-basic-offset (if (and (boundp 'c-basic-offset) - (numberp c-basic-offset)) - c-basic-offset - 4) - "Number of spaces to indent nested statements. -Similar to `c-basic-offset'." - :group 'js2-mode - :type 'integer) -(js2-mark-safe-local 'js2-basic-offset 'integerp) - -(defcustom js2-bounce-indent-p nil - "Non-nil to have indent-line function choose among alternatives. -If nil, the indent-line function will indent to a predetermined column -based on heuristic guessing. If non-nil, then if the current line is -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. When it is non-nil, -js2-mode also binds `js2-bounce-indent-backwards' to Shift-Tab." - :type 'boolean - :group 'js2-mode) - -(defcustom js2-pretty-multiline-declarations t - "Non-nil to line up multiline declarations vertically: - - var a = 10, - b = 20, - c = 30; - -If the value is t, and the first assigned value in the -declaration is a function/array/object literal spanning several -lines, it won't be indented additionally: - - var o = { var bar = 2, - foo: 3 vs. o = { - }, foo: 3 - bar = 2; }; - -If the value is `all', it will always be indented additionally: - - var o = { - foo: 3 - }; - - var o = { - foo: 3 - }, - bar = 2; - -If the value is `dynamic', it will be indented additionally only -if the declaration contains more than one variable: - - var o = { - foo: 3 - }; - - var o = { - foo: 3 - }, - bar = 2;" - :group 'js2-mode - :type 'symbol) -(js2-mark-safe-local 'js2-pretty-multiline-declarations 'symbolp) - -(defcustom js2-indent-switch-body nil - "When nil, case labels are indented on the same level as the -containing switch statement. Otherwise, all lines inside -switch statement body are indented one additional level." - :type 'boolean - :group 'js2-mode) -(js2-mark-safe-local 'js2-indent-case-same-as-switch 'booleanp) - (defcustom js2-idle-timer-delay 0.2 "Delay in secs before re-parsing after user makes changes. Multiplied by `js2-dynamic-idle-timer-adjust', which see." @@ -1185,8 +1114,6 @@ information." (define-key map (kbd "C-c C-o") #'js2-mode-toggle-element) (define-key map (kbd "C-c C-w") #'js2-mode-toggle-warnings-and-errors) (define-key map [down-mouse-3] #'js2-down-mouse-3) - (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"))) @@ -1249,6 +1176,24 @@ information." map) "Keymap used in `js2-mode' buffers.") +(defcustom js2-bounce-indent-p nil + "Non-nil to bind `js2-indent-bounce' and `js2-indent-bounce-backward'. +They will augment the default indent-line behavior with cycling +among several computed alternatives. See the function +`js2-bounce-indent' for details. The above commands will be +bound to TAB and backtab." + :type 'boolean + :group 'js2-mode + :set (lambda (sym value) + (set-default sym value) + (let ((map js2-mode-map)) + (if (not value) + (progn + (define-key map "\t" nil) + (define-key map (kbd "<backtab>") nil)) + (define-key map "\t" #'js2-indent-bounce) + (define-key map (kbd "<backtab>") #'js2-indent-bounce-backward))))) + (defconst js2-mode-identifier-re "[[:alpha:]_$][[:alnum:]_$]*") (defvar js2-mode-//-comment-re "^\\(\\s-*\\)//.+" @@ -1271,32 +1216,6 @@ First match-group is the leading whitespace.") (js2-deflocal js2-imenu-recorder nil "Private variable") (js2-deflocal js2-imenu-function-map nil "Private variable") -(defvar js2-paragraph-start - "\\(@[[:alpha:]]+\\>\\|$\\)") - -;; Note that we also set a 'c-in-sws text property in html comments, -;; so that `c-forward-sws' and `c-backward-sws' work properly. -(defvar js2-syntactic-ws-start - "\\s \\|/[*/]\\|[\n\r]\\|\\\\[\n\r]\\|\\s!\\|<!--\\|^\\s-*-->") - -(defvar js2-syntactic-ws-end - "\\s \\|[\n\r/]\\|\\s!") - -(defvar js2-syntactic-eol - (concat "\\s *\\(/\\*[^*\n\r]*" - "\\(\\*+[^*\n\r/][^*\n\r]*\\)*" - "\\*+/\\s *\\)*" - "\\(//\\|/\\*[^*\n\r]*" - "\\(\\*+[^*\n\r/][^*\n\r]*\\)*$" - "\\|\\\\$\\|$\\)") - "Copied from `java-mode'. Needed for some cc-engine functions.") - -(defvar js2-comment-prefix-regexp - "//+\\|\\**") - -(defvar js2-comment-start-skip - "\\(//+\\|/\\*+\\)\\s *") - (defvar js2-mode-verbose-parse-p js2-mode-dev-mode-p "Non-nil to emit status messages during parsing.") @@ -10907,22 +10826,19 @@ And, if CHECK-ACTIVATION-P is non-nil, use the value of TOKEN." (js2-check-activation-name s (or token js2-NAME))) name)) -;;; Indentation support +;;; Indentation support (bouncing) -;; This indenter is based on Karl Landström's "javascript.el" indenter. -;; Karl cleverly deduces that the desired indentation level is often a -;; function of paren/bracket/brace nesting depth, which can be determined -;; quickly via the built-in `parse-partial-sexp' function. His indenter -;; then does some equally clever checks to see if we're in the context of a -;; substatement of a possibly braceless statement keyword such as if, while, -;; or finally. This approach yields pretty good results. +;; In recent-enough Emacs, we reuse the indentation code from +;; `js-mode'. To continue support for the older versions, some code +;; that was here previously was moved to `js2-old-indent.el'. -;; The indenter is often "wrong", however, and needs to be overridden. -;; The right long-term solution is probably to emulate (or integrate -;; with) cc-engine, but it's a nontrivial amount of coding. Even when a -;; parse tree from `js2-parse' is present, which is not true at the -;; moment the user is typing, computing indentation is still thousands -;; of lines of code to handle every possible syntactic edge case. +;; Whichever indenter is used, it's often "wrong", however, and needs +;; to be overridden. The right long-term solution is probably to +;; emulate (or integrate with) cc-engine, but it's a nontrivial amount +;; of coding. Even when a parse tree from `js2-parse' is present, +;; which is not true at the moment the user is typing, computing +;; indentation is still thousands of lines of code to handle every +;; possible syntactic edge case. ;; In the meantime, the compromise solution is that we offer a "bounce ;; indenter", configured with `js2-bounce-indent-p', which cycles the @@ -10931,359 +10847,6 @@ And, if CHECK-ACTIVATION-P is non-nil, use the value of TOKEN." ;; move the line towards its desired indentation when manually ;; overriding Karl's heuristic nesting guesser. -;; I've made miscellaneous tweaks to Karl's code to handle some Ecma -;; extensions such as `let' and Array comprehensions. Major kudos to -;; Karl for coming up with the initial approach, which packs a lot of -;; punch for so little code. - -(defconst js2-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 js2-indent-operator-re - (concat "[-+*/%<>&^|?:.]\\([^-+*/]\\|$\\)\\|!?=\\|" - (regexp-opt '("in" "instanceof") 'words)) - "Regular expression matching operators that affect indentation -of continued expressions.") - -(defconst js2-declaration-keyword-re - (regexp-opt '("var" "let" "const") 'words) - "Regular expression matching variable declaration keywords.") - -(defun js2-re-search-forward-inner (regexp &optional bound count) - "Auxiliary function for `js2-re-search-forward'." - (let (parse saved-point) - (while (> count 0) - (re-search-forward regexp bound) - (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))) - (save-excursion (end-of-line) (point)) t)) - ((nth 7 parse) - (forward-line)) - ((or (nth 4 parse) - (and (eq (char-before) ?\/) (eq (char-after) ?\*))) - (re-search-forward "\\*/")) - (t - (setq count (1- count)))) - (setq saved-point (point)))) - (point)) - -(defun js2-re-search-forward (regexp &optional bound noerror count) - "Search forward but ignore strings and comments. -Invokes `re-search-forward' but treats the buffer as if strings -and comments have been removed." - (let ((saved-point (point))) - (condition-case err - (cond ((null count) - (js2-re-search-forward-inner regexp bound 1)) - ((< count 0) - (js2-re-search-backward-inner regexp bound (- count))) - ((> count 0) - (js2-re-search-forward-inner regexp bound count))) - (search-failed - (goto-char saved-point) - (unless noerror - (error (error-message-string err))))))) - -(defun js2-re-search-backward-inner (regexp &optional bound count) - "Auxiliary function for `js2-re-search-backward'." - (let (parse) - (while (> count 0) - (re-search-backward regexp bound) - (setq parse (syntax-ppss (point))) - (cond ((nth 3 parse) - (re-search-backward - (concat "\\([^\\]\\|^\\)" (string (nth 3 parse))) - (line-beginning-position) t)) - ((nth 7 parse) - (goto-char (nth 8 parse))) - ((or (nth 4 parse) - (and (eq (char-before) ?/) (eq (char-after) ?*))) - (re-search-backward "/\\*")) - (t - (setq count (1- count)))))) - (point)) - -(defun js2-re-search-backward (regexp &optional bound noerror count) - "Search backward but ignore strings and comments. -Invokes `re-search-backward' but treats the buffer as if strings -and comments have been removed." - (let ((saved-point (point))) - (condition-case err - (cond ((null count) - (js2-re-search-backward-inner regexp bound 1)) - ((< count 0) - (js2-re-search-forward-inner regexp bound (- count))) - ((> count 0) - (js2-re-search-backward-inner regexp bound count))) - (search-failed - (goto-char saved-point) - (unless noerror - (error (error-message-string err))))))) - -(defun js2-looking-at-operator-p () - "Return non-nil if text after point is a non-comma operator." - (and (looking-at js2-indent-operator-re) - (or (not (looking-at ":")) - (save-excursion - (and (js2-re-search-backward "[?:{]\\|\\_<case\\_>" nil t) - (looking-at "?")))))) - -(defun js2-continued-expression-p () - "Return non-nil if the current line continues an expression." - (save-excursion - (back-to-indentation) - (or (js2-looking-at-operator-p) - (when (catch 'found - (while (and (re-search-backward "\n" nil t) - (let ((state (syntax-ppss))) - (when (nth 4 state) - (goto-char (nth 8 state))) ;; skip comments - (skip-chars-backward " \t") - (if (bolp) - t - (throw 'found t)))))) - (backward-char) - (when (js2-looking-at-operator-p) - (backward-char) - (not (looking-at "\\*\\|\\+\\+\\|--\\|/[/*]"))))))) - -(defun js2-end-of-do-while-loop-p () - "Return non-nil if word after point is `while' of a do-while -statement, else returns nil. A braceless do-while statement -spanning several lines requires that the start of the loop is -indented to the same column as the current line." - (interactive) - (save-excursion - (when (looking-at "\\s-*\\_<while\\_>") - (if (save-excursion - (skip-chars-backward "[ \t\n]*}") - (looking-at "[ \t\n]*}")) - (save-excursion - (backward-list) (backward-word 1) (looking-at "\\_<do\\_>")) - (js2-re-search-backward "\\_<do\\_>" (point-at-bol) t) - (or (looking-at "\\_<do\\_>") - (let ((saved-indent (current-indentation))) - (while (and (js2-re-search-backward "^[ \t]*\\_<" nil t) - (/= (current-indentation) saved-indent))) - (and (looking-at "[ \t]*\\_<do\\_>") - (not (js2-re-search-forward - "\\_<while\\_>" (point-at-eol) t)) - (= (current-indentation) saved-indent)))))))) - -(defun js2-multiline-decl-indentation () - "Return the declaration indentation column if the current line belongs -to a multiline declaration statement. See `js2-pretty-multiline-declarations'." - (let (forward-sexp-function ; use Lisp version - at-opening-bracket) - (save-excursion - (back-to-indentation) - (when (not (looking-at js2-declaration-keyword-re)) - (when (looking-at js2-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) ?\;)) - (prog2 (skip-syntax-backward ".") - (looking-at js2-indent-operator-re) - (js2-backward-sws)) - (not (eq (char-before) ?\;))) - (js2-same-line pos))))) - (condition-case _ - (backward-sexp) - (scan-error (setq at-opening-bracket t)))) - (when (looking-at js2-declaration-keyword-re) - (goto-char (match-end 0)) - (1+ (current-column))))))) - -(defun js2-ctrl-statement-indentation () - "Return the proper indentation of current line if it is a control statement. -Returns an indentation if this line starts the body of a control -statement without braces, else returns nil." - (let (forward-sexp-function) - (save-excursion - (back-to-indentation) - (when (and (not (js2-same-line (point-min))) - (not (looking-at "{")) - (js2-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)) - (skip-chars-backward " \t" (point-at-bol))) - (let ((pt (point))) - (back-to-indentation) - (when (looking-at "}[ \t]*") - (goto-char (match-end 0))) - (and (looking-at js2-possibly-braceless-keywords-re) - (= (match-end 0) pt) - (not (js2-end-of-do-while-loop-p)))))) - (+ (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. -In particular, return the buffer position of the first `for' kwd." - (let ((bracket (nth 1 parse-status)) - (end (point))) - (when bracket - (save-excursion - (goto-char bracket) - (when (looking-at "\\[") - (forward-char 1) - (js2-forward-sws) - (if (looking-at "[[{]") - (let (forward-sexp-function) ; use Lisp version - (forward-sexp) ; skip destructuring form - (js2-forward-sws) - (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 - (let ((state (parse-partial-sexp bracket (point)))) - (not (or (nth 3 state) (nth 4 state))))) - (match-beginning 1)))))))) - -(defun js2-array-comp-indentation (parse-status for-kwd) - (if (js2-same-line for-kwd) - ;; first continuation line - (save-excursion - (goto-char (nth 1 parse-status)) - (forward-char 1) - (skip-chars-forward " \t") - (current-column)) - (save-excursion - (goto-char for-kwd) - (current-column)))) - -(defun js2-maybe-goto-declaration-keyword-end (bracket) - "Helper function for `js2-proper-indentation'. -Depending on the value of `js2-pretty-multiline-declarations', -move point to the end of a variable declaration keyword so that -indentation is aligned to that column." - (cond - ((eq js2-pretty-multiline-declarations 'all) - (when (looking-at js2-declaration-keyword-re) - (goto-char (1+ (match-end 0))))) - ((eq js2-pretty-multiline-declarations 'dynamic) - (let (declaration-keyword-end - at-closing-bracket-p - comma-p) - (when (looking-at js2-declaration-keyword-re) - ;; Preserve the match data lest it somehow be overridden. - (setq declaration-keyword-end (match-end 0)) - (save-excursion - (goto-char bracket) - (setq at-closing-bracket-p - ;; Handle scan errors gracefully. - (condition-case nil - (progn - ;; Use the regular `forward-sexp-function' because the - ;; normal one for this mode uses the AST. - (let (forward-sexp-function) - (forward-sexp)) - t) - (error nil))) - (when at-closing-bracket-p - (js2-forward-sws) - (setq comma-p (looking-at-p ",")))) - (when comma-p - (goto-char (1+ declaration-keyword-end)))))))) - -(defun js2-proper-indentation (parse-status) - "Return the proper indentation for the current line." - (save-excursion - (back-to-indentation) - (let* ((ctrl-stmt-indent (js2-ctrl-statement-indentation)) - (at-closing-bracket (looking-at "[]})]")) - (same-indent-p (or at-closing-bracket - (looking-at "\\_<case\\_>[^:]") - (and (looking-at "\\_<default:") - (save-excursion - (js2-backward-sws) - (not (memq (char-before) '(?, ?{))))))) - (continued-expr-p (js2-continued-expression-p)) - (declaration-indent (and js2-pretty-multiline-declarations - (js2-multiline-decl-indentation))) - (bracket (nth 1 parse-status)) - beg indent) - (cond - ;; indent array comprehension continuation lines specially - ((and bracket - (>= js2-language-version 170) - (not (js2-same-line bracket)) - (setq beg (js2-indent-in-array-comp parse-status)) - (>= (point) (save-excursion - (goto-char beg) - (point-at-bol)))) ; at or after first loop? - (js2-array-comp-indentation parse-status beg)) - - (ctrl-stmt-indent) - - ((and declaration-indent continued-expr-p) - (+ declaration-indent js2-basic-offset)) - - (declaration-indent) - - (bracket - (goto-char bracket) - (cond - ((looking-at "[({[][ \t]*\\(/[/*]\\|$\\)") - (when (save-excursion (skip-chars-backward " \t)") - (looking-at ")")) - (backward-list)) - (back-to-indentation) - (js2-maybe-goto-declaration-keyword-end bracket) - (setq indent - (cond (same-indent-p - (current-column)) - (continued-expr-p - (+ (current-column) (* 2 js2-basic-offset))) - (t - (+ (current-column) js2-basic-offset)))) - (if (and js2-indent-switch-body - (not at-closing-bracket) - (looking-at "\\_<switch\\_>")) - (+ indent js2-basic-offset) - indent)) - (t - (unless same-indent-p - (forward-char) - (skip-chars-forward " \t")) - (current-column)))) - - (continued-expr-p js2-basic-offset) - - (t 0))))) - -(defun js2-lineup-comment (parse-status) - "Indent a multi-line block comment continuation line." - (let* ((beg (nth 8 parse-status)) - (first-line (js2-same-line beg)) - (offset (save-excursion - (goto-char beg) - (if (looking-at "/\\*") - (+ 1 (current-column)) - 0)))) - (unless first-line - (indent-line-to offset)))) - (defun js2-backward-sws () "Move backward through whitespace and comments." (interactive) @@ -11341,7 +10904,7 @@ indentation is aligned to that column." (skip-chars-forward " \t") (looking-at "case\\s-.+:"))) -(defun js2-bounce-indent (normal-col parse-status &optional backwards) +(defun js2-bounce-indent (normal-col parse-status &optional backward) "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 @@ -11451,8 +11014,8 @@ in reverse." (js2-indent-objlit-arg-p parse-status)) (setq main-pos basic-offset)) - ;; if bouncing backwards, reverse positions list - (if backwards + ;; if bouncing backward, reverse positions list + (if backward (setq positions (reverse positions))) ;; record whether we're already sitting on one of the alternatives @@ -11496,12 +11059,6 @@ in reverse." ;; 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)) - (defun 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." @@ -11517,8 +11074,8 @@ If so, we don't ever want to use bounce-indent." (forward-line 0)) (looking-at "\\s-*//"))))) -(defun js2-indent-line (&optional bounce-backwards) - "Indent the current line as JavaScript source text." +(defun js2-indent-bounce (&optional backward) + "Indent the current line, bouncing between several positions." (interactive) (let (parse-status offset indent-col ;; Don't whine about errors/warnings when we're indenting. @@ -11531,22 +11088,23 @@ If so, we don't ever want to use bounce-indent." (point)))) ;; Don't touch multiline strings. (unless (nth 3 parse-status) - (if (nth 4 parse-status) - (js2-lineup-comment parse-status) - (setq indent-col (js2-proper-indentation parse-status)) - ;; See comments below about `js2-mode-last-indented-line'. - (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)))) + (setq indent-col (js2-proper-indentation parse-status)) + (cond + ;; It doesn't work well on first line of buffer. + ((and (not (nth 4 parse-status)) + (not (js2-same-line (point-min))) + (not (js2-1-line-comment-continuation-p))) + (js2-bounce-indent indent-col parse-status backward)) + ;; just indent to the guesser's likely spot + (t (indent-line-to indent-col))) (when (cl-plusp offset) (forward-char offset))))) +(defun js2-indent-bounce-backward () + "Indent the current line, bouncing between positions in reverse." + (interactive) + (js2-indent-bounce t)) + (defun js2-indent-region (start end) "Indent the region, but don't use bounce indenting." (let ((js2-bounce-indent-p nil) @@ -11718,18 +11276,14 @@ Selecting an error will jump it to the corresponding source-buffer error. (message msg)))))) ;;;###autoload -(define-derived-mode js2-mode prog-mode "Javascript-IDE" - ;; FIXME: Should derive from js-mode. +(define-derived-mode js2-mode js-mode "Javascript-IDE" "Major mode for editing JavaScript code." ;; Used by comment-region; don't change it. - (set (make-local-variable 'comment-start) "//") - (set (make-local-variable 'comment-end) "") - (set (make-local-variable 'comment-start-skip) js2-comment-start-skip) (set (make-local-variable 'max-lisp-eval-depth) (max max-lisp-eval-depth 3000)) (set (make-local-variable 'indent-line-function) #'js2-indent-line) (set (make-local-variable 'indent-region-function) #'js2-indent-region) - (set (make-local-variable 'fill-paragraph-function) #'c-fill-paragraph) + (set (make-local-variable 'syntax-propertize-function) nil) (set (make-local-variable 'comment-line-break-function) #'js2-line-break) (set (make-local-variable 'beginning-of-defun-function) #'js2-beginning-of-defun) (set (make-local-variable 'end-of-defun-function) #'js2-end-of-defun) @@ -11741,30 +11295,6 @@ Selecting an error will jump it to the corresponding source-buffer error. ;; needed for M-x rgrep, among other things (put 'js2-mode 'find-tag-default-function #'js2-mode-find-tag) - (set (make-local-variable 'electric-indent-chars) - (append "{}()[]:;,*." electric-indent-chars)) - (set (make-local-variable 'electric-layout-rules) - '((?\; . after) (?\{ . after) (?\} . before))) - - ;; some variables needed by cc-engine for paragraph-fill, etc. - (setq c-comment-prefix-regexp js2-comment-prefix-regexp - c-comment-start-regexp "/[*/]\\|\\s|" - c-line-comment-starter "//" - c-paragraph-start js2-paragraph-start - c-paragraph-separate "$" - c-syntactic-ws-start js2-syntactic-ws-start - c-syntactic-ws-end js2-syntactic-ws-end - c-syntactic-eol js2-syntactic-eol) - - (let ((c-buffer-is-cc-mode t)) - ;; Copied from `js-mode'. Also see Bug#6071. - (make-local-variable 'paragraph-start) - (make-local-variable 'paragraph-separate) - (make-local-variable 'paragraph-ignore-fill-prefix) - (make-local-variable 'adaptive-fill-mode) - (make-local-variable 'adaptive-fill-regexp) - (c-setup-paragraph-variables)) - (setq font-lock-defaults '(nil t)) ;; Experiment: make reparse-delay longer for longer files. diff --git a/packages/js2-mode/js2-old-indent.el b/packages/js2-mode/js2-old-indent.el new file mode 100644 index 0000000..9b1c929 --- /dev/null +++ b/packages/js2-mode/js2-old-indent.el @@ -0,0 +1,493 @@ +;;; js2-old-indent.el --- Indentation code kept for compatibility + +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; All features of this indentation code have been ported to Emacs's +;; built-in `js-mode' by now, so we derive from it. An older +;; commentary follows. + +;; This code is kept for Emacs 24.5 and ealier. + +;; This indenter is based on Karl Landström's "javascript.el" indenter. +;; Karl cleverly deduces that the desired indentation level is often a +;; function of paren/bracket/brace nesting depth, which can be determined +;; quickly via the built-in `parse-partial-sexp' function. His indenter +;; then does some equally clever checks to see if we're in the context of a +;; substatement of a possibly braceless statement keyword such as if, while, +;; or finally. This approach yields pretty good results. + +;; The indenter is often "wrong", however, and needs to be overridden. +;; The right long-term solution is probably to emulate (or integrate +;; with) cc-engine, but it's a nontrivial amount of coding. Even when a +;; parse tree from `js2-parse' is present, which is not true at the +;; moment the user is typing, computing indentation is still thousands +;; of lines of code to handle every possible syntactic edge case. + +;; In the meantime, the compromise solution is that we offer a "bounce +;; indenter", configured with `js2-bounce-indent-p', which cycles the +;; current line indent among various likely guess points. This approach +;; is far from perfect, but should at least make it slightly easier to +;; move the line towards its desired indentation when manually +;; overriding Karl's heuristic nesting guesser. + +;; I've made miscellaneous tweaks to Karl's code to handle some Ecma +;; extensions such as `let' and Array comprehensions. Major kudos to +;; Karl for coming up with the initial approach, which packs a lot of +;; punch for so little code. -- Steve + +;;; Code: + +(defvar js2-language-version) + +(declare-function js2-mark-safe-local "js2-mode") +(declare-function js2-backward-sws "js2-mode") +(declare-function js2-forward-sws "js2-mode") +(declare-function js2-same-line "js2-mode") + +(defcustom js2-basic-offset (if (and (boundp 'c-basic-offset) + (numberp c-basic-offset)) + c-basic-offset + 4) + "Number of spaces to indent nested statements. +Similar to `c-basic-offset'." + :group 'js2-mode + :safe 'integerp + :type 'integer) + +(defcustom js2-pretty-multiline-declarations t + "Non-nil to line up multiline declarations vertically: + + var a = 10, + b = 20, + c = 30; + +If the value is t, and the first assigned value in the +declaration is a function/array/object literal spanning several +lines, it won't be indented additionally: + + var o = { var bar = 2, + foo: 3 vs. o = { + }, foo: 3 + bar = 2; }; + +If the value is `all', it will always be indented additionally: + + var o = { + foo: 3 + }; + + var o = { + foo: 3 + }, + bar = 2; + +If the value is `dynamic', it will be indented additionally only +if the declaration contains more than one variable: + + var o = { + foo: 3 + }; + + var o = { + foo: 3 + }, + bar = 2;" + :group 'js2-mode + :safe 'symbolp + :type 'symbol) + +(defcustom js2-indent-switch-body nil + "When nil, case labels are indented on the same level as the +containing switch statement. Otherwise, all lines inside +switch statement body are indented one additional level." + :type 'boolean + :safe 'booleanp + :group 'js2-mode) + +(defconst js2-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 js2-indent-operator-re + (concat "[-+*/%<>&^|?:.]\\([^-+*/]\\|$\\)\\|!?=\\|" + (regexp-opt '("in" "instanceof") 'words)) + "Regular expression matching operators that affect indentation +of continued expressions.") + +(defconst js2-declaration-keyword-re + (regexp-opt '("var" "let" "const") 'words) + "Regular expression matching variable declaration keywords.") + +(defun js2-re-search-forward-inner (regexp &optional bound count) + "Auxiliary function for `js2-re-search-forward'." + (let (parse saved-point) + (while (> count 0) + (re-search-forward regexp bound) + (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))) + (save-excursion (end-of-line) (point)) t)) + ((nth 7 parse) + (forward-line)) + ((or (nth 4 parse) + (and (eq (char-before) ?\/) (eq (char-after) ?\*))) + (re-search-forward "\\*/")) + (t + (setq count (1- count)))) + (setq saved-point (point)))) + (point)) + +(defun js2-re-search-forward (regexp &optional bound noerror count) + "Search forward but ignore strings and comments. +Invokes `re-search-forward' but treats the buffer as if strings +and comments have been removed." + (let ((saved-point (point))) + (condition-case err + (cond ((null count) + (js2-re-search-forward-inner regexp bound 1)) + ((< count 0) + (js2-re-search-backward-inner regexp bound (- count))) + ((> count 0) + (js2-re-search-forward-inner regexp bound count))) + (search-failed + (goto-char saved-point) + (unless noerror + (error (error-message-string err))))))) + +(defun js2-re-search-backward-inner (regexp &optional bound count) + "Auxiliary function for `js2-re-search-backward'." + (let (parse) + (while (> count 0) + (re-search-backward regexp bound) + (setq parse (syntax-ppss (point))) + (cond ((nth 3 parse) + (re-search-backward + (concat "\\([^\\]\\|^\\)" (string (nth 3 parse))) + (line-beginning-position) t)) + ((nth 7 parse) + (goto-char (nth 8 parse))) + ((or (nth 4 parse) + (and (eq (char-before) ?/) (eq (char-after) ?*))) + (re-search-backward "/\\*")) + (t + (setq count (1- count)))))) + (point)) + +(defun js2-re-search-backward (regexp &optional bound noerror count) + "Search backward but ignore strings and comments. +Invokes `re-search-backward' but treats the buffer as if strings +and comments have been removed." + (let ((saved-point (point))) + (condition-case err + (cond ((null count) + (js2-re-search-backward-inner regexp bound 1)) + ((< count 0) + (js2-re-search-forward-inner regexp bound (- count))) + ((> count 0) + (js2-re-search-backward-inner regexp bound count))) + (search-failed + (goto-char saved-point) + (unless noerror + (error (error-message-string err))))))) + +(defun js2-looking-at-operator-p () + "Return non-nil if text after point is a non-comma operator." + (and (looking-at js2-indent-operator-re) + (or (not (looking-at ":")) + (save-excursion + (and (js2-re-search-backward "[?:{]\\|\\_<case\\_>" nil t) + (looking-at "?")))))) + +(defun js2-continued-expression-p () + "Return non-nil if the current line continues an expression." + (save-excursion + (back-to-indentation) + (or (js2-looking-at-operator-p) + (when (catch 'found + (while (and (re-search-backward "\n" nil t) + (let ((state (syntax-ppss))) + (when (nth 4 state) + (goto-char (nth 8 state))) ;; skip comments + (skip-chars-backward " \t") + (if (bolp) + t + (throw 'found t)))))) + (backward-char) + (when (js2-looking-at-operator-p) + (backward-char) + (not (looking-at "\\*\\|\\+\\+\\|--\\|/[/*]"))))))) + +(defun js2-end-of-do-while-loop-p () + "Return non-nil if word after point is `while' of a do-while +statement, else returns nil. A braceless do-while statement +spanning several lines requires that the start of the loop is +indented to the same column as the current line." + (interactive) + (save-excursion + (when (looking-at "\\s-*\\_<while\\_>") + (if (save-excursion + (skip-chars-backward "[ \t\n]*}") + (looking-at "[ \t\n]*}")) + (save-excursion + (backward-list) (backward-word 1) (looking-at "\\_<do\\_>")) + (js2-re-search-backward "\\_<do\\_>" (point-at-bol) t) + (or (looking-at "\\_<do\\_>") + (let ((saved-indent (current-indentation))) + (while (and (js2-re-search-backward "^[ \t]*\\_<" nil t) + (/= (current-indentation) saved-indent))) + (and (looking-at "[ \t]*\\_<do\\_>") + (not (js2-re-search-forward + "\\_<while\\_>" (point-at-eol) t)) + (= (current-indentation) saved-indent)))))))) + +(defun js2-multiline-decl-indentation () + "Return the declaration indentation column if the current line belongs +to a multiline declaration statement. See `js2-pretty-multiline-declarations'." + (let (forward-sexp-function ; use Lisp version + at-opening-bracket) + (save-excursion + (back-to-indentation) + (when (not (looking-at js2-declaration-keyword-re)) + (when (looking-at js2-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) ?\;)) + (prog2 (skip-syntax-backward ".") + (looking-at js2-indent-operator-re) + (js2-backward-sws)) + (not (eq (char-before) ?\;))) + (js2-same-line pos))))) + (condition-case _ + (backward-sexp) + (scan-error (setq at-opening-bracket t)))) + (when (looking-at js2-declaration-keyword-re) + (goto-char (match-end 0)) + (1+ (current-column))))))) + +(defun js2-ctrl-statement-indentation () + "Return the proper indentation of current line if it is a control statement. +Returns an indentation if this line starts the body of a control +statement without braces, else returns nil." + (let (forward-sexp-function) + (save-excursion + (back-to-indentation) + (when (and (not (js2-same-line (point-min))) + (not (looking-at "{")) + (js2-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)) + (skip-chars-backward " \t" (point-at-bol))) + (let ((pt (point))) + (back-to-indentation) + (when (looking-at "}[ \t]*") + (goto-char (match-end 0))) + (and (looking-at js2-possibly-braceless-keywords-re) + (= (match-end 0) pt) + (not (js2-end-of-do-while-loop-p)))))) + (+ (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. +In particular, return the buffer position of the first `for' kwd." + (let ((bracket (nth 1 parse-status)) + (end (point))) + (when bracket + (save-excursion + (goto-char bracket) + (when (looking-at "\\[") + (forward-char 1) + (js2-forward-sws) + (if (looking-at "[[{]") + (let (forward-sexp-function) ; use Lisp version + (forward-sexp) ; skip destructuring form + (js2-forward-sws) + (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 + (let ((state (parse-partial-sexp bracket (point)))) + (not (or (nth 3 state) (nth 4 state))))) + (match-beginning 1)))))))) + +(defun js2-array-comp-indentation (parse-status for-kwd) + (if (js2-same-line for-kwd) + ;; first continuation line + (save-excursion + (goto-char (nth 1 parse-status)) + (forward-char 1) + (skip-chars-forward " \t") + (current-column)) + (save-excursion + (goto-char for-kwd) + (current-column)))) + +(defun js2-maybe-goto-declaration-keyword-end (bracket) + "Helper function for `js2-proper-indentation'. +Depending on the value of `js2-pretty-multiline-declarations', +move point to the end of a variable declaration keyword so that +indentation is aligned to that column." + (cond + ((eq js2-pretty-multiline-declarations 'all) + (when (looking-at js2-declaration-keyword-re) + (goto-char (1+ (match-end 0))))) + ((eq js2-pretty-multiline-declarations 'dynamic) + (let (declaration-keyword-end + at-closing-bracket-p + comma-p) + (when (looking-at js2-declaration-keyword-re) + ;; Preserve the match data lest it somehow be overridden. + (setq declaration-keyword-end (match-end 0)) + (save-excursion + (goto-char bracket) + (setq at-closing-bracket-p + ;; Handle scan errors gracefully. + (condition-case nil + (progn + ;; Use the regular `forward-sexp-function' because the + ;; normal one for this mode uses the AST. + (let (forward-sexp-function) + (forward-sexp)) + t) + (error nil))) + (when at-closing-bracket-p + (js2-forward-sws) + (setq comma-p (looking-at-p ",")))) + (when comma-p + (goto-char (1+ declaration-keyword-end)))))))) + +(cl-defun js2-proper-indentation (parse-status) + "Return the proper indentation for the current line." + (save-excursion + (back-to-indentation) + (when (nth 4 parse-status) + (cl-return (js2-lineup-comment parse-status))) + (let* ((at-closing-bracket (looking-at "[]})]")) + (same-indent-p (or at-closing-bracket + (looking-at "\\_<case\\_>[^:]") + (and (looking-at "\\_<default:") + (save-excursion + (js2-backward-sws) + (not (memq (char-before) '(?, ?{))))))) + (continued-expr-p (js2-continued-expression-p)) + (declaration-indent (and js2-pretty-multiline-declarations + (js2-multiline-decl-indentation))) + (bracket (nth 1 parse-status)) + beg indent) + (cond + ;; indent array comprehension continuation lines specially + ((and bracket + (>= js2-language-version 170) + (not (js2-same-line bracket)) + (setq beg (js2-indent-in-array-comp parse-status)) + (>= (point) (save-excursion + (goto-char beg) + (point-at-bol)))) ; at or after first loop? + (js2-array-comp-indentation parse-status beg)) + + ((js2-ctrl-statement-indentation)) + + ((and declaration-indent continued-expr-p) + (+ declaration-indent js2-basic-offset)) + + (declaration-indent) + + (bracket + (goto-char bracket) + (cond + ((looking-at "[({[][ \t]*\\(/[/*]\\|$\\)") + (when (save-excursion (skip-chars-backward " \t)") + (looking-at ")")) + (backward-list)) + (back-to-indentation) + (js2-maybe-goto-declaration-keyword-end bracket) + (setq indent + (cond (same-indent-p + (current-column)) + (continued-expr-p + (+ (current-column) (* 2 js2-basic-offset))) + (t + (+ (current-column) js2-basic-offset)))) + (if (and js2-indent-switch-body + (not at-closing-bracket) + (looking-at "\\_<switch\\_>")) + (+ indent js2-basic-offset) + indent)) + (t + (unless same-indent-p + (forward-char) + (skip-chars-forward " \t")) + (current-column)))) + + (continued-expr-p js2-basic-offset) + + (t 0))))) + +(defun js2-lineup-comment (parse-status) + "Indent a multi-line block comment continuation line." + (let* ((beg (nth 8 parse-status)) + (first-line (js2-same-line beg)) + (offset (save-excursion + (goto-char beg) + (if (looking-at "/\\*") + (+ 1 (current-column)) + 0)))) + (unless first-line + (indent-line-to offset)))) + +(defun js2-indent-line (&optional bounce-backwards) + "Indent the current line as JavaScript source text." + (interactive) + (let (parse-status offset + ;; Don't whine about errors/warnings when we're indenting. + ;; This has to be set before calling parse-partial-sexp below. + (inhibit-point-motion-hooks t)) + (setq parse-status (save-excursion + (syntax-ppss (point-at-bol))) + offset (- (point) (save-excursion + (back-to-indentation) + (point)))) + ;; Don't touch multiline strings. + (unless (nth 3 parse-status) + (indent-line-to (js2-proper-indentation parse-status)) + (when (cl-plusp offset) + (forward-char offset))))) + +(provide 'js2-old-indent) + +;;; js2-old-indent.el ends here