Commit 17b5d0f7 authored by Chong Yidong's avatar Chong Yidong
Browse files

* progmodes/js2-mode.el: File removed.

* (ELCFILES): Add js.el, and remove js2-mode.el.

* speedbar.el (speedbar-supported-extension-expressions): Add .js.

* progmodes/hideshow.el (hs-special-modes-alist): Add js-mode

* progmodes/js.el: New file.
parent b6377f1d
2009-08-14 Chong Yidong <>
* progmodes/js2-mode.el: File removed.
* (ELCFILES): Add js.el, and remove js2-mode.el.
* speedbar.el (speedbar-supported-extension-expressions): Add .js.
* progmodes/hideshow.el (hs-special-modes-alist): Add js-mode
2009-08-14 Daniel Colascione <>
* progmodes/js.el: New file.
2009-08-14 Mark A. Hershberger <>
* timezone.el (timezone-parse-date): Add ability to understand ISO
......@@ -1064,7 +1064,7 @@ ELCFILES = \
$(lisp)/progmodes/idlw-toolbar.elc \
$(lisp)/progmodes/idlwave.elc \
$(lisp)/progmodes/inf-lisp.elc \
$(lisp)/progmodes/js2-mode.elc \
$(lisp)/progmodes/js.elc \
$(lisp)/progmodes/ld-script.elc \
$(lisp)/progmodes/m4-mode.elc \
$(lisp)/progmodes/make-mode.elc \
......@@ -270,7 +270,8 @@ This has effect only if `search-invisible' is set to `open'."
'((c-mode "{" "}" "/[*/]" nil nil)
(c++-mode "{" "}" "/[*/]" nil nil)
(bibtex-mode ("@\\S(*\\(\\s(\\)" 1))
(java-mode "{" "}" "/[*/]" nil nil))
(java-mode "{" "}" "/[*/]" nil nil)
(js-mode "{" "}" "/[*/]" nil))
"*Alist for initializing the hideshow variables for different modes.
Each element has the form
;;; js.el --- Major mode for editing JavaScript source text
;; Copyright (C) 2008, 2009 Free Software Foundation, Inc.
;; Author: Karl Landstrom <>
;; Daniel Colascione <>
;; Maintainer: Daniel Colascione <>
;; Version: 9
;; Date: 2009-07-25
;; Keywords: languages, oop, javascript
;; 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
;; 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 <>.
;;; Commentary
;; This is based on Karl Landstrom's barebones javascript-mode. This
;; is much more robust and works with cc-mode's comment filling
;; (mostly).
;; The main features of this JavaScript mode are syntactic
;; highlighting (enabled with `font-lock-mode' or
;; `global-font-lock-mode'), automatic indentation and filling of
;; comments, C preprocessor fontification, and MozRepl integration.
;; General Remarks:
;; XXX: This mode assumes that block comments are not nested inside block
;; XXX: comments
;; Exported names start with "js-"; private names start with
;; "js--".
;;; Code:
(require 'cc-mode)
(require 'font-lock)
(require 'newcomment)
(require 'imenu)
(require 'etags)
(require 'thingatpt)
(require 'easymenu)
(require 'moz nil t)
(require 'json nil t))
(require 'cl)
(require 'comint)
(require 'ido)
;; Swap the speed and safety values for debugging
(proclaim '(optimize (speed 3) (safety 0))))
(defvar inferior-moz-buffer)
(defvar moz-repl-name)
(defvar ido-cur-list)
(declare-function ido-mode "ido")
(declare-function inferior-moz-process "mozrepl")
;;; Constants
(defconst js--name-start-re "[a-zA-Z_$]"
"Matches the first character of a Javascript identifier. No grouping")
(defconst js--stmt-delim-chars "^;{}?:")
(defconst js--name-re (concat js--name-start-re
"Matches a Javascript identifier. No grouping.")
(defconst js--objfield-re (concat js--name-re ":")
"Matches a Javascript object field start")
(defconst js--dotted-name-re
(concat js--name-re "\\(?:\\." js--name-re "\\)*")
"Matches a dot-separated sequence of Javascript names")
(defconst js--cpp-name-re js--name-re
"Matches a C preprocessor name")
(defconst js--opt-cpp-start "^\\s-*#\\s-*\\([[:alnum:]]+\\)"
"Regexp matching the prefix of a cpp directive including the directive
name, or nil in languages without preprocessor support. The first
submatch surrounds the directive name.")
(defconst js--plain-method-re
(concat "^\\s-*?\\(" js--dotted-name-re "\\)\\.prototype"
"\\.\\(" js--name-re "\\)\\s-*?=\\s-*?\\(function\\)\\_>")
"Regexp matching an old-fashioned explicit prototype \"method\"
declaration. Group 1 is a (possibly-dotted) class name, group 2
is a method name, and group 3 is the 'function' keyword." )
(defconst js--plain-class-re
(concat "^\\s-*\\(" js--dotted-name-re "\\)\\.prototype"
"Regexp matching an old-fashioned explicit prototype \"class\"
declaration, as in Class.prototype = { method1: ...} ")
(defconst js--mp-class-decl-re
(concat "^\\s-*var\\s-+"
"\\(" js--name-re "\\)"
"\\(" js--dotted-name-re
"var NewClass = BaseClass.extend(")
(defconst js--prototype-obsolete-class-decl-re
(concat "^\\s-*\\(?:var\\s-+\\)?"
"\\(" js--dotted-name-re "\\)"
"var NewClass = Class.create()")
(defconst js--prototype-objextend-class-decl-re-1
(concat "^\\s-*Object\\.extend\\s-*("
"\\(" js--dotted-name-re "\\)"
(defconst js--prototype-objextend-class-decl-re-2
(concat "^\\s-*\\(?:var\\s-+\\)?"
"\\(" js--dotted-name-re "\\)"
(defconst js--prototype-class-decl-re
(concat "^\\s-*\\(?:var\\s-+\\)?"
"\\(" js--name-re "\\)"
"\\(?:\\(" js--dotted-name-re "\\)\\s-*,\\s-*\\)?{?")
"var NewClass = Class.create({")
;; Parent class name(s) (yes, multiple inheritance in Javascript) are
;; matched with dedicated font-lock matchers
(defconst js--dojo-class-decl-re
(concat "^\\s-*dojo\\.declare\\s-*(\"\\(" js--dotted-name-re "\\)"))
(defconst js--extjs-class-decl-re-1
(concat "^\\s-*Ext\\.extend\\s-*("
"\\s-*\\(" js--dotted-name-re "\\)"
"\\s-*,\\s-*\\(" js--dotted-name-re "\\)")
"ExtJS class declaration (style 1)")
(defconst js--extjs-class-decl-re-2
(concat "^\\s-*\\(?:var\\s-+\\)?"
"\\(" js--name-re "\\)"
"\\(" js--dotted-name-re "\\)")
"ExtJS class declaration (style 2)")
(defconst js--mochikit-class-re
(concat "^\\s-*MochiKit\\.Base\\.update\\s-*(\\s-*"
"\\(" js--dotted-name-re "\\)")
"MochiKit class declaration?")
(defconst js--dummy-class-style
'(:name "[Automatically Generated Class]"))
(defconst js--class-styles
`((:name "Plain"
:class-decl ,js--plain-class-re
:prototype t
:contexts (toplevel)
:framework javascript)
(:name "MochiKit"
:class-decl ,js--mochikit-class-re
:prototype t
:contexts (toplevel)
:framework mochikit)
(:name "Prototype (Obsolete)"
:class-decl ,js--prototype-obsolete-class-decl-re
:contexts (toplevel)
:framework prototype)
(:name "Prototype (Modern)"
:class-decl ,js--prototype-class-decl-re
:contexts (toplevel)
:framework prototype)
(:name "Prototype (Object.extend)"
:class-decl ,js--prototype-objextend-class-decl-re-1
:prototype t
:contexts (toplevel)
:framework prototype)
(:name "Prototype (Object.extend) 2"
:class-decl ,js--prototype-objextend-class-decl-re-2
:prototype t
:contexts (toplevel)
:framework prototype)
(:name "Dojo"
:class-decl ,js--dojo-class-decl-re
:contexts (toplevel)
:framework dojo)
(:name "ExtJS (style 1)"
:class-decl ,js--extjs-class-decl-re-1
:prototype t
:contexts (toplevel)
:framework extjs)
(:name "ExtJS (style 2)"
:class-decl ,js--extjs-class-decl-re-2
:contexts (toplevel)
:framework extjs)
(:name "Merrill Press"
:class-decl ,js--mp-class-decl-re
:contexts (toplevel)
:framework merrillpress))
"A list of class definition styles.
A class definition style is a plist with the following keys:
:name is a human-readable name of the class type
:class-decl is a regular expression giving the start of the
class. Its first group must match the name of its class. If there
is a parent class, the second group should match, and it should
be the name of the class.
If :prototype is present and non-nil, the parser will merge
declarations for this constructs with others at the same lexical
level that have the same name. Otherwise, multiple definitions
will create multiple top-level entries. Don't use :prototype
unnecessarily: it has an associated cost in performance.
If :strip-prototype is present and non-nil, then if the class
name as matched contains
(defconst js--available-frameworks
(loop with available-frameworks
for style in js--class-styles
for framework = (plist-get style :framework)
unless (memq framework available-frameworks)
collect framework into available-frameworks
finally return available-frameworks)
"List of available frameworks symbols")
(defconst js--function-heading-1-re
"^\\s-*function\\s-+\\(" js--name-re "\\)")
"Regular expression matching the start of a function header. Match group 1
is the name of the function.")
(defconst js--function-heading-2-re
"^\\s-*\\(" js--name-re "\\)\\s-*:\\s-*function\\_>")
"Regular expression matching the start of a function entry in
an associative array. Match group 1 is the name of the function.")
(defconst js--function-heading-3-re
"^\\s-*\\(?:var\\s-+\\)?\\(" js--dotted-name-re "\\)"
"Matches a line in the form var MUMBLE = function. Match group
1 is MUMBLE.")
(defconst js--macro-decl-re
(concat "^\\s-*#\\s-*define\\s-+\\(" js--cpp-name-re "\\)\\s-*(")
"Regular expression matching a CPP macro definition up to the opening
parenthesis. Match group 1 is the name of the function.")
(defun js--regexp-opt-symbol (list)
"Like regexp-opt, but surround the optimized regular expression
with `\\\\_<' and `\\\\_>'."
(concat "\\_<" (regexp-opt list t) "\\_>"))
(defconst js--keyword-re
'("abstract" "break" "case" "catch" "class" "const"
"continue" "debugger" "default" "delete" "do" "else"
"enum" "export" "extends" "final" "finally" "for"
"function" "goto" "if" "implements" "import" "in"
"instanceof" "interface" "native" "new" "package"
"private" "protected" "public" "return" "static"
"super" "switch" "synchronized" "throw"
"throws" "transient" "try" "typeof" "var" "void" "let"
"yield" "volatile" "while" "with"))
"Regular expression matching any JavaScript keyword.")
(defconst js--basic-type-re
'("boolean" "byte" "char" "double" "float" "int" "long"
"short" "void"))
"Regular expression matching any predefined type in JavaScript.")
(defconst js--constant-re
(js--regexp-opt-symbol '("false" "null" "undefined"
"Infinity" "NaN"
"true" "arguments" "this"))
"Regular expression matching any future reserved words in JavaScript.")
(defconst js--font-lock-keywords-1
(list js--function-heading-1-re 1 font-lock-function-name-face)
(list js--function-heading-2-re 1 font-lock-function-name-face))
"Level one font lock.")
(defconst js--font-lock-keywords-2
(append js--font-lock-keywords-1
(list (list js--keyword-re 1 font-lock-keyword-face)
(list "\\_<for\\_>"
"\\s-+\\(each\\)\\_>" nil nil
(list 1 'font-lock-keyword-face))
(cons js--basic-type-re font-lock-type-face)
(cons js--constant-re font-lock-constant-face)))
"Level two font lock.")
;; js--pitem is the basic building block of the lexical
;; database. When one refers to a real part of the buffer, the region
;; of text to which it refers is split into a conceptual header and
;; body. Consider the (very short) block described by a hypothetical
;; js--pitem:
;; function foo(a,b,c) { return 42; }
;; ^ ^ ^
;; | | |
;; +- h-begin +- h-end +- b-end
;; (Remember that these are buffer positions, and therefore point
;; between characters, not at them. An arrow drawn to a character
;; indicates the corresponding position is between that character and
;; the one immediately preceding it.)
;; The header is the region of text [h-begin, h-end], and is
;; the text needed to unambiguously recognize the start of the
;; construct. If the entire header is not present, the construct is
;; not recognized at all. No other pitems may be nested inside the
;; header.
;; The body is the region [h-end, b-end]. It may contain nested
;; js--pitem instances. The body of a pitem may be empty: in
;; that case, b-end is equal to header-end.
;; The three points obey the following relationship:
;; h-begin < h-end <= b-end
;; We put a text property in the buffer on the character *before*
;; h-end, and if we see it, on the character *before* b-end.
;; The text property for h-end, js--pstate, is actually a list
;; of all js--pitem instances open after the marked character.
;; The text property for b-end, js--pend, is simply the
;; js--pitem that ends after the marked character. (Because
;; pitems always end when the paren-depth drops below a critical
;; value, and because we can only drop one level per character, only
;; one pitem may end at a given character.)
;; In the structure below, we only store h-begin and (sometimes)
;; b-end. We can trivially and quickly find h-end by going to h-begin
;; and searching for an js--pstate text property. Since no other
;; js--pitem instances can be nested inside the header of a
;; pitem, the location after the character with this text property
;; must be h-end.
;; js--pitem instances are never modified (with the exception
;; of the b-end field). Instead, modified copies are added at subseqnce parse points.
;; (The exception for b-end and its caveats is described below.)
(defstruct (js--pitem (:type list))
;; IMPORTANT: Do not alter the position of fields within the list.
;; Various bits of code depend on their positions, particularly
;; anything that manipulates the list of children.
;; List of children inside this pitem's body
(children nil :read-only t)
;; When we reach this paren depth after h-end, the pitem ends
(paren-depth nil :read-only t)
;; Symbol or class-style plist if this is a class
(type nil :read-only t)
;; See above
(h-begin nil :read-only t)
;; List of strings giving the parts of the name of this pitem (e.g.,
;; '("MyClass" "myMethod"), or t if this pitem is anonymous
(name nil :read-only t)
;; THIS FIELD IS MUTATED, and its value is shared by all copies of
;; this pitem: when we copy-and-modify pitem instances, we share
;; their tail structures, so all the copies actually have the same
;; terminating cons cell. We modify that shared cons cell directly.
;; The field value is either a number (buffer location) or nil if
;; unknown.
;; If the field's value is greater than `js--cache-end', the
;; value is stale and must be treated as if it were nil. Conversely,
;; if this field is nil, it is guaranteed that this pitem is open up
;; to at least `js--cache-end'. (This property is handy when
;; computing whether we're inside a given pitem.)
(b-end nil))
(defconst js--initial-pitem
:paren-depth most-negative-fixnum
:type 'toplevel)
"The pitem we start parsing with")
;;; User Customization
(defgroup js nil
"Customization variables for `js-mode'."
:tag "JavaScript"
:group 'languages)
(defcustom js-indent-level 4
"Number of spaces for each indentation step."
:type 'integer
:group 'js)
(defcustom js-expr-indent-offset 0
"Number of additional spaces used for indentation of continued
expressions. The value must be no less than minus
:type 'integer
:group 'js)
(defcustom js-auto-indent-flag t
"Automatic indentation with punctuation characters. If non-nil, the
current line is indented when certain punctuations are inserted."
:type 'boolean
:group 'js)
(defcustom js-flat-functions nil
"Treat nested functions as if they were top-level functions for
function movement, marking, and so on."
:type 'boolean
:group 'js)
(defcustom js-comment-lineup-func #'c-lineup-C-comments
"cc-mode-style lineup function for C comments"
:type 'function
:group 'js)
(defcustom js-enabled-frameworks js--available-frameworks
"Select which frameworks js-mode will recognize.
Turn off some frameworks you seldom use to improve performance.
The set of recognized frameworks can also be overriden on a
per-buffer basis."
:type (cons 'set (mapcar (lambda (x)
(list 'const x))
:group 'js)
(defcustom js-js-switch-tabs
(and (memq system-type '(darwin)) t)
"Non-nil if Emacs should display tabs while selecting them.
Useful only if the windowing system has a good mechanism for
preventing Firefox from stealing the keyboard focus."
:type 'boolean
:group 'js)
(defcustom js-js-tmpdir
"Temporary directory used for communicating with Mozilla. It
must be readable and writable by both Mozilla and Emacs."
:type 'directory
:group 'js)
(defcustom js-js-timeout 5
"Wait this many seconds for a reply from Mozilla when executing
commands. Increase this value if you are getting timeout
:type 'integer
:group 'js)
;;; KeyMap
(defvar js-mode-map
(let ((keymap (make-sparse-keymap)))
(mapc (lambda (key)
(define-key keymap key #'js-insert-and-indent))
'("+" "-" "*" "{" "}" "(" ")" ":" ";" ","))
(define-key keymap [(control ?c) (meta ?:)] #'js-eval)
(define-key keymap [(control ?c) (control ?j)] #'js-set-js-context)
(define-key keymap [(control meta ?x)] #'js-eval-defun)
(define-key keymap [(meta ?.)] #'js-find-symbol)
(easy-menu-define nil keymap "Javascript Menu"
["Select new Mozilla context…" js-set-js-context
(fboundp #'inferior-moz-process)]
["Evaluate expression in Mozilla context…" js-eval
(fboundp #'inferior-moz-process)]
["Send current function to Mozilla…" js-eval-defun
(fboundp #'inferior-moz-process)]
"Keymap for js-mode")
(defun js-insert-and-indent (key)
"Runs the command bound to KEY in the global keymap, and if
we're not in a string or comment, indents the current line."
(interactive (list (this-command-keys)))
(call-interactively (lookup-key (current-global-map) key))
(let ((syntax (save-restriction (widen) (syntax-ppss))))
(when (or (and (not (nth 8 syntax))
(and (nth 4 syntax)
(eq (current-column)
(1+ (current-indentation)))))
;;; Syntax table and parsing
(defvar js-mode-syntax-table
(let ((table (make-syntax-table)))
(c-populate-syntax-table table)
(modify-syntax-entry ?$ "_" table)
"Syntax table used in JS mode.")
(defvar js--quick-match-re nil
"Autogenerated regular expression to match buffer constructs")
(defvar js--quick-match-re-func nil
"Autogenerated regular expression to match buffer constructs
and functions")
(make-variable-buffer-local 'js--quick-match-re)
(make-variable-buffer-local 'js--quick-match-re-func)
(defvar js--cache-end 1
"Last place in the buffer the function cache is valid")
(make-variable-buffer-local 'js--cache-end)
(defvar js--last-parse-pos nil
"Last place we parsed up to in js--ensure-cache")
(make-variable-buffer-local 'js--last-parse-pos)
(defvar js--state-at-last-parse-pos nil
"pstate at js--last-parse-pos")
(make-variable-buffer-local 'js--state-at-last-parse-pos)
(defun js--flatten-list (list)
(loop for item in list
nconc (cond ((consp item)
(js--flatten-list item))
(item (list item)))))
(defun js--maybe-join (prefix separator suffix &rest list)
"If LIST contains any element that is not nil, return its
non-nil elements, separated by SEPARATOR, prefixed by PREFIX, and
ended with SUFFIX as with `concat'. Otherwise, if LIST is empty,
return nil. If any element in LIST is itself a list, flatten that
(setq list (js--flatten-list list))
(when list
(concat prefix (mapconcat #'identity list separator) suffix)))
(defun js--update-quick-match-re ()
"Update js--quick-match-re based on the current set of
enabled frameworks"
(setq js--quick-match-re
"^[ \t]*\\(?:" "\\|" "\\)"
;; #define mumble
"#define[ \t]+[a-zA-Z_]"
(when (memq 'extjs js-enabled-frameworks)
(when (memq 'prototype js-enabled-frameworks)
;; var mumble = THING (
"\\(?:var[ \t]+\\)?[a-zA-Z_$0-9.]+[ \t]*=[ \t]*\\(?:"
"\\)[ \t]*\("
(when (memq 'prototype js-enabled-frameworks)
(when (memq 'extjs js-enabled-frameworks)
(when (memq 'merrillpress js-enabled-frameworks)
(when (memq 'dojo js-enabled-frameworks)
"dojo\\.declare[ \t]*\(")
(when (memq 'mochikit js-enabled-frameworks)
"MochiKit\\.Base\\.update[ \t]*\(")
;; mumble.prototypeTHING
"[a-zA-Z_$0-9.]+\\.prototype\\(?:" "\\|" "\\)"