]> git.rkrishnan.org Git - .emacs.d.git/blob - emacs/espresso.el
submodulized .emacs.d setup
[.emacs.d.git] / emacs / espresso.el
1 ;;; espresso.el --- Major mode for editing JavaScript source text
2 ;; Copyright (C) 2008 Free Software Foundation, Inc.
3 ;; Copyright (C) 2009 Daniel Colascione <dan.colascione@gmail.com>
4 ;; Author: Karl Landstrom <karl.landstrom@brgeight.se>
5 ;; Author: Daniel Colascione <dan.colascione@gmail.com>
6 ;; Maintainer: Daniel Colascione <dan.colascione@gmail.com>
7 ;; Version: 4
8 ;; Date: 2009-01-06
9 ;; Keywords: languages, oop, javascript
10
11 ;; This file is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; any later version.
15
16 ;; This file is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to
23 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25
26 ;;; Commentary
27
28 ;; This is based on Karl Landstrom's barebones javascript-mode. This
29 ;; is much more robust and works with cc-mode's comment filling
30 ;; (mostly).
31 ;;
32 ;; The main features of this JavaScript mode are syntactic
33 ;; highlighting (enabled with `font-lock-mode' or
34 ;; `global-font-lock-mode'), automatic indentation and filling of
35 ;; comments, and C preprocessor fontification.
36 ;;
37 ;; This package has (only) been tested with GNU Emacs 22 (the latest
38 ;; stable release).
39 ;;
40 ;; Installation:
41 ;;
42 ;; Put this file in a directory where Emacs can find it (`C-h v
43 ;; load-path' for more info). Then add the following lines to your
44 ;; Emacs initialization file:
45 ;;
46 ;;    (add-to-list 'auto-mode-alist '("\\.js\\'" . espresso-mode))
47 ;;    (autoload 'espresso-mode "espresso" nil t)
48 ;;
49 ;; General Remarks:
50 ;;
51 ;; XXX: This mode assumes that block comments are not nested inside block
52 ;; XXX: comments and that strings do not contain line breaks.
53 ;;
54 ;; Exported names start with "espresso-" whereas private names start
55 ;; with "espresso--".
56 ;;
57 ;; Code:
58
59 ;;; Code
60
61 (require 'cc-mode)
62 (require 'font-lock)
63 (require 'newcomment)
64
65 (eval-when-compile
66   (require 'cl))
67
68 ;;; User customization
69
70 (defgroup espresso nil
71   "Customization variables for `espresso-mode'."
72   :tag "JavaScript - Espresso-Mode"
73   :group 'languages)
74
75 (defcustom espresso-indent-level 4
76   "Number of spaces for each indentation step."
77   :type 'integer
78   :group 'espresso)
79
80 (defcustom espresso-expr-indent-offset 0
81   "Number of additional spaces used for indentation of continued
82 expressions. The value must be no less than minus
83 `espresso-indent-level'."
84   :type 'integer
85   :group 'espresso)
86
87 (defcustom espresso-auto-indent-flag t
88   "Automatic indentation with punctuation characters. If non-nil, the
89 current line is indented when certain punctuations are inserted."
90   :type 'boolean
91   :group 'espresso)
92
93 ;;; KeyMap
94
95 (defvar espresso-mode-map nil
96   "Keymap used in Espresso mode.")
97
98 (unless espresso-mode-map
99   (setq espresso-mode-map (make-sparse-keymap)))
100
101 (when espresso-auto-indent-flag
102   (mapc (lambda (key)
103           (define-key espresso-mode-map key 'espresso-insert-and-indent))
104         '("{" "}" "(" ")" ":" ";" ",")))
105
106 (defun espresso-insert-and-indent (key)
107   "Runs the command bound to KEY in the global keymap, and if
108 we're not in a string or comment, indents the current line."
109   (interactive (list (this-command-keys)))
110   (call-interactively (lookup-key (current-global-map) key))
111   (let ((syntax (save-restriction (widen) (syntax-ppss))))
112     (unless (nth 8 syntax)
113       (indent-according-to-mode))))
114
115 ;;; Syntax table and parsing
116
117 (defvar espresso-mode-syntax-table
118   (let ((table (make-syntax-table)))
119     (c-populate-syntax-table table)
120     (modify-syntax-entry ?$ "_" table)
121     table)
122   "Syntax table used in Espresso mode.")
123
124 (defconst espresso--name-start-re "[a-zA-Z_$]"
125   "Matches the first character of a Espresso identifier. No grouping")
126
127 (defconst espresso--stmt-delim-chars "^;{}?:")
128
129 (defconst espresso--name-re (concat espresso--name-start-re
130                                     "\\(?:\\s_\\|\\sw\\)*")
131   "Matches a Javascript name. No grouping.")
132
133 (defconst espresso--dotted-name-re
134   (concat espresso--name-re "\\(?:\\." espresso--name-re "\\)*")
135   "Matches a dot-separated sequence of Javascript names")
136
137 (defconst espresso--cpp-name-re espresso--name-re
138   "Matches a C preprocessor name")
139
140 (defconst espresso--opt-cpp-start "^\\s-*#\\s-*\\([[:alnum:]]+\\)"
141   " Regexp matching the prefix of a cpp directive including the directive
142 name, or nil in languages without preprocessor support.  The first
143 submatch surrounds the directive name.")
144
145
146 (defconst espresso--class-decls
147   `(; var NewClass = BaseClass.extend(
148     ,(concat "^\\s-*\\_<var\\_>\\s-+"
149              "\\(" espresso--dotted-name-re "\\)"
150              "\\s-*=" "\\s-*"
151              "\\(" espresso--dotted-name-re
152              "\\)\\.extend\\(?:Final\\)?\\s-*(")
153
154     ; NewClass: BaseClass.extend( ; for nested classes
155     ,(concat "^\\s-*"
156              "\\(" espresso--dotted-name-re "\\):"
157              "\\s-*\\(" espresso--dotted-name-re
158              "\\)\\.extend\\(?:Finak\\)?\\s-*("))
159   "List of regular expressions that can match class definitions.
160 Each one must set match group 1 to the name of the class being
161 defined, and optionally, group 2 to the name of the base class.")
162
163 (defun espresso--regexp-opt-symbol (list)
164   "Like regexp-opt, but surround the optimized regular expression
165 with `\\\\_<' and `\\\\_>'."
166   (concat "\\_<" (regexp-opt list t) "\\_>"))
167
168 (defun espresso--re-search-forward-inner (regexp &optional bound count)
169   "Auxiliary function for `espresso--re-search-forward'."
170   (let ((parse)
171         (orig-macro-end (save-excursion
172                           (when (espresso--beginning-of-macro)
173                             (c-end-of-macro)
174                             (point))))
175         (saved-point (point-min)))
176     (while (> count 0)
177       (re-search-forward regexp bound)
178       (setq parse (parse-partial-sexp saved-point (point)))
179       (cond ((nth 3 parse)
180              (re-search-forward
181               (concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
182               (save-excursion (end-of-line) (point)) t))
183             ((nth 7 parse)
184              (forward-line))
185             ((or (nth 4 parse)
186                  (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
187              (re-search-forward "\\*/"))
188             ((and (not (and orig-macro-end
189                             (<= (point) orig-macro-end)))
190                   (espresso--beginning-of-macro))
191              (c-end-of-macro))
192             (t
193              (setq count (1- count))))
194       (setq saved-point (point))))
195   (point))
196
197
198 (defun espresso--re-search-forward (regexp &optional bound noerror count)
199   "Search forward but ignore strings, cpp macros, and comments.
200 Invokes `re-search-forward' but treats the buffer as if strings,
201 cpp macros, and comments have been removed.
202
203 If invoked while inside a macro, treat the contents of the macro
204 as normal text.
205
206 "
207   (let ((saved-point (point))
208         (search-expr
209          (cond ((null count)
210                 '(espresso--re-search-forward-inner regexp bound 1))
211                ((< count 0)
212                 '(espresso--re-search-backward-inner regexp bound (- count)))
213                ((> count 0)
214                 '(espresso--re-search-forward-inner regexp bound count)))))
215     (condition-case err
216         (eval search-expr)
217       (search-failed
218        (goto-char saved-point)
219        (unless noerror
220          (error (error-message-string err)))))))
221
222
223 (defun espresso--re-search-backward-inner (regexp &optional bound count)
224   "Auxiliary function for `espresso--re-search-backward'."
225   (let ((parse)
226         (orig-macro-start
227          (save-excursion
228            (and (espresso--beginning-of-macro)
229                 (point))))
230         (saved-point (point-min)))
231     (while (> count 0)
232       (re-search-backward regexp bound)
233       (when (and (> (point) (point-min))
234                  (save-excursion (backward-char) (looking-at "/[/*]")))
235         (forward-char))
236       (setq parse (parse-partial-sexp saved-point (point)))
237       (cond ((nth 3 parse)
238              (re-search-backward
239               (concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
240               (save-excursion (beginning-of-line) (point)) t))
241             ((nth 7 parse)
242              (goto-char (nth 8 parse)))
243             ((or (nth 4 parse)
244                  (and (eq (char-before) ?/) (eq (char-after) ?*)))
245              (re-search-backward "/\\*"))
246             ((and (not (and orig-macro-start
247                             (>= (point) orig-macro-start)))
248                   (espresso--beginning-of-macro)))
249             (t
250              (setq count (1- count))))))
251   (point))
252
253
254 (defun espresso--re-search-backward (regexp &optional bound noerror count)
255   "Search backward but ignore strings, preprocessor macros, and
256 comments. Invokes `re-search-backward' but treats the buffer as
257 if strings, preprocessor macros, and comments have been removed.
258
259 If inside a macro when called, treat the macro as normal text.
260 "
261   (let ((saved-point (point))
262         (search-expr
263          (cond ((null count)
264                 '(espresso--re-search-backward-inner regexp bound 1))
265                ((< count 0)
266                 '(espresso--re-search-forward-inner regexp bound (- count)))
267                ((> count 0)
268                 '(espresso--re-search-backward-inner regexp bound count)))))
269     (condition-case err
270         (eval search-expr)
271       (search-failed
272        (goto-char saved-point)
273        (unless noerror
274          (error (error-message-string err)))))))
275
276
277 (defun espresso--forward-function-decl ()
278   (assert (looking-at "\\_<function\\_>"))
279   (forward-word)
280   (forward-comment most-positive-fixnum)
281   (skip-chars-forward "^(")
282   (unless (eobp)
283     (forward-list)
284     (forward-comment most-positive-fixnum)
285     (skip-chars-forward "^{"))
286   t)
287
288 (defun espresso--beginning-of-defun ()
289   (cond ((espresso--re-search-backward "\\_<function\\_>" (point-min) t)
290          (let ((pos (point)))
291            (save-excursion
292              (forward-line 0)
293              (when (looking-at espresso--function-heading-2-re)
294                (setq pos (match-beginning 1))))
295            (goto-char pos)))
296
297         (t
298          (goto-char (point-min)))))
299
300 (defun espresso--end-of-defun ()
301   ;; look for function backward. if we're inside it, go to that
302   ;; function's end. otherwise, search for the next function's end and
303   ;; go there
304   (unless (looking-at "\\_<")
305     (skip-syntax-backward "w_"))
306
307   (let ((orig-point (point)) pos)
308     (when (or (looking-at "\\_<function\\_>")
309               (espresso--re-search-backward "\\_<function\\_>" (point-min) t))
310       (goto-char (match-beginning 0))
311       (let* ((func-loc (point))
312              (opening-brace-loc (progn (espresso--forward-function-decl)
313                                        (point))))
314
315         (cond ((and (<= func-loc orig-point)
316                     (<= orig-point opening-brace-loc))
317                (setq pos opening-brace-loc))
318
319               ((/= 0 (nth 0 (parse-partial-sexp
320                              opening-brace-loc orig-point 0)))
321                (setq pos opening-brace-loc)))))
322
323     (cond
324      (pos (goto-char pos)
325           (forward-list))
326
327      ((espresso--re-search-forward "\\_<function\\_>" (point-max) t)
328       (espresso--end-of-defun))
329
330      (t (goto-char (point-max))))))
331
332 (defun espresso--beginning-of-macro (&optional lim)
333   (let ((here (point)))
334     (save-restriction
335       (if lim (narrow-to-region lim (point-max)))
336       (beginning-of-line)
337       (while (eq (char-before (1- (point))) ?\\)
338         (forward-line -1))
339       (back-to-indentation)
340       (if (and (<= (point) here)
341                (looking-at espresso--opt-cpp-start))
342           t
343         (goto-char here)
344         nil))))
345
346 (defun espresso--backward-syntactic-ws (&optional lim)
347   "Simple implementation of c-backward-syntactic-ws"
348   (save-restriction
349     (when lim (narrow-to-region lim (point-max)))
350
351     (let ((in-macro (save-excursion (espresso--beginning-of-macro)))
352           (pos (point)))
353
354       (while (progn (unless in-macro (espresso--beginning-of-macro))
355                     (forward-comment most-negative-fixnum)
356                     (/= (point)
357                         (prog1
358                             pos
359                           (setq pos (point)))))))))
360
361 (defun espresso--forward-syntactic-ws (&optional lim)
362   "Simple implementation of c-forward-syntactic-ws"
363   (save-restriction
364     (when lim (narrow-to-region (point-min) min))
365     (let ((pos (point)))
366       (while (progn
367                (forward-comment most-positive-fixnum)
368                (when (eq (char-after) ?#)
369                  (c-end-of-macro))
370                (/= (point)
371                    (prog1
372                        pos
373                      (setq pos (point)))))))))
374
375 ;;; Font Lock
376
377 (defun espresso--inside-param-list-p ()
378   "Return non-nil iff point is inside a function parameter list."
379   (condition-case err
380       (save-excursion
381         (up-list -1)
382         (and (looking-at "(")
383              (progn (forward-symbol -1)
384                     (or (looking-at "function")
385                         (progn (forward-symbol -1) (looking-at "function"))))))
386     (error nil)))
387
388 (defconst espresso--function-heading-1-re
389   (concat
390    "^\\s-*function\\s-+\\(" espresso--name-re "\\)")
391   "Regular expression matching the start of a function header. Match group 1
392 is the name of the function.")
393
394 (defconst espresso--function-heading-2-re
395   (concat
396    "^\\s-*\\(" espresso--name-re "\\)\\s-*:\\s-*function\\_>")
397   "Regular expression matching the start of a function entry in
398   an associative array. Match group 1 is the name of the function.")
399
400 (defconst espresso--macro-decl-re
401   (concat "^\\s-*#\\s-*define\\s-+\\(" espresso--cpp-name-re "\\)\\s-*(")
402   "Regular expression matching a CPP macro definition up to the opening
403 parenthesis. Match group 1 is the name of the function.")
404
405 (defconst espresso--keyword-re
406   (espresso--regexp-opt-symbol
407    '("abstract" "break" "case" "catch" "class" "const"
408      "continue" "debugger" "default" "delete" "do" "else"
409      "enum" "export" "extends" "final" "finally" "for"
410      "function" "goto" "if" "implements" "import" "in"
411      "instanceof" "interface" "native" "new" "package"
412      "private" "protected" "public" "return" "static"
413      "super" "switch" "synchronized" "throw"
414      "throws" "transient" "try" "typeof" "var" "void"
415      "volatile" "while" "with" "let"))
416   "Regular expression matching any JavaScript keyword.")
417
418 (defconst espresso--basic-type-re
419   (espresso--regexp-opt-symbol
420    '("boolean" "byte" "char" "double" "float" "int" "long"
421      "short" "void"))
422   "Regular expression matching any predefined type in JavaScript.")
423
424 (defconst espresso--constant-re
425   (espresso--regexp-opt-symbol '("false" "null" "undefined"
426                                  "true" "arguments" "this"))
427   "Regular expression matching any future reserved words in JavaScript.")
428
429
430 (defconst espresso--font-lock-keywords-1
431   (list
432    "\\_<import\\_>"
433    (list espresso--function-heading-1-re 1 font-lock-function-name-face)
434    (list espresso--function-heading-2-re 1 font-lock-function-name-face))
435   "Level one font lock.")
436
437 (defconst espresso--font-lock-keywords-2
438   (append espresso--font-lock-keywords-1
439           (list (list espresso--keyword-re 1 font-lock-keyword-face)
440                 (cons espresso--basic-type-re font-lock-type-face)
441                 (cons espresso--constant-re font-lock-constant-face)))
442   "Level two font lock.")
443
444
445 ;; Limitations with variable declarations: There seems to be no
446 ;; sensible way to highlight variables occuring after an initialized
447 ;; variable in a variable list. For instance, in
448 ;;
449 ;;    var x, y = f(a, b), z
450 ;;
451 ;; z will not be highlighted. Also, in variable declaration lists
452 ;; spanning several lines only variables on the first line will be
453 ;; highlighted. To get correct fontification, every line with variable
454 ;; declarations must contain a `var' keyword.
455
456 (defconst espresso--font-lock-keywords-3
457   `(
458     ;; This goes before keywords-2 so it gets used preferentially
459     ;; instead of the keywords in keywords-2. Don't use override
460     ;; because that will override syntactic fontification too, which
461     ;; will fontify commented-out directives as if they weren't
462     ;; commented out.
463     ,@cpp-font-lock-keywords ; from font-lock.el
464
465     ,@espresso--font-lock-keywords-2
466
467     ;; variable declarations
468     ,(list
469       (concat "\\_<\\(const\\|var\\)\\_>\\|" espresso--basic-type-re)
470       (list (concat "\\(" espresso--name-re "\\)"
471                     "\\s-*\\([=;].*\\|\\_<in\\_>.*\\|,\\|/[/*]\\|$\\)")
472             nil
473             nil
474             '(1 font-lock-variable-name-face)))
475
476     ;; class instantiation
477     ,(list
478       (concat "\\_<new\\_>\\s-+\\(" espresso--dotted-name-re "\\)")
479       (list 1 'font-lock-type-face))
480
481     ;; instanceof
482     ,(list
483       (concat "\\_<instanceof\\_>\\s-+\\(" espresso--dotted-name-re "\\)")
484       (list 1 'font-lock-type-face))
485
486     ;; formal parameters
487     ,(list
488       (concat
489        "\\_<function\\_>\\(\\s-+" espresso--name-re "\\)?\\s-*(\\s-*"
490        espresso--name-start-re)
491       (list (concat "\\(" espresso--name-re "\\)\\(\\s-*).*\\)?")
492             '(backward-char)
493             '(end-of-line)
494             '(1 font-lock-variable-name-face)))
495
496     ;; continued formal parameter list
497     ,(list
498       (concat
499        "^\\s-*" espresso--name-re "\\s-*[,)]")
500       (list espresso--name-re
501             '(if (save-excursion (backward-char)
502                                  (espresso--inside-param-list-p))
503                  (forward-symbol -1)
504                (end-of-line))
505             '(end-of-line)
506             '(0 font-lock-variable-name-face)))
507
508     ;; class declarations
509     ,@(mapcar #'(lambda (x)
510                   `(,x
511                     (1 font-lock-type-face t t)
512                     (2 font-lock-type-face t t)))
513
514               espresso--class-decls))
515   "Level three font lock.")
516
517
518 (defconst espresso--font-lock-keywords
519   '(espresso--font-lock-keywords-3 espresso--font-lock-keywords-1
520                                    espresso--font-lock-keywords-2
521                             espresso--font-lock-keywords-3)
522   "See `font-lock-keywords'.")
523
524 ;; Note: Javascript cannot continue a regular expression literal
525 ;; across lines
526 (defconst espresso--regexp-literal
527   "[=(,]\\(?:\\s-\\|\n\\)*\\(/\\)[^/*]\\(?:.*?[^\\]\\)?\\(/\\)"
528   "Match a regular expression literal. Match groups 1 and 2 are
529 the characters forming the beginning and end of the literal")
530
531 ;; we want to match regular expressions only at the beginning of
532 ;; expressions
533 (defconst espresso--font-lock-syntactic-keywords
534   `((,espresso--regexp-literal (1 "|") (2 "|")))
535   "Highlighting of regular expressions. See also the variable
536   `font-lock-keywords'.")
537
538 ;;; Indentation
539
540 (defconst espresso--possibly-braceless-keyword-re
541   (espresso--regexp-opt-symbol
542    '("catch" "do" "else" "finally" "for" "if" "try" "while" "with" "let"))
543   "Regular expression matching keywords that are optionally
544   followed by an opening brace.")
545
546 (defconst espresso--indent-operator-re
547   (concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|"
548           (espresso--regexp-opt-symbol '("in" "instanceof")))
549   "Regular expression matching operators that affect indentation
550   of continued expressions.")
551
552
553 (defun espresso--looking-at-operator-p ()
554   "Return non-nil if text after point is an operator (that is not
555 a comma)."
556   (save-match-data
557     (and (looking-at espresso--indent-operator-re)
558          (or (not (looking-at ":"))
559              (save-excursion
560                (and (espresso--re-search-backward "[?:{]\\|\\_<case\\_>" nil t)
561                     (looking-at "?")))))))
562
563
564 (defun espresso--continued-expression-p ()
565   "Returns non-nil if the current line continues an expression."
566   (save-excursion
567     (back-to-indentation)
568     (or (espresso--looking-at-operator-p)
569         (and (espresso--re-search-backward "\n" nil t)
570              (progn
571                (skip-chars-backward " \t")
572                (or (bobp) (backward-char))
573                (and (> (point) (point-min))
574                     (save-excursion (backward-char) (not (looking-at "[/*]/")))
575                     (espresso--looking-at-operator-p)
576                     (and (progn (backward-char)
577                                 (not (looking-at "++\\|--\\|/[/*]"))))))))))
578
579
580 (defun espresso--end-of-do-while-loop-p ()
581   "Returns non-nil if word after point is `while' of a do-while
582 statement, else returns nil. A braceless do-while statement
583 spanning several lines requires that the start of the loop is
584 indented to the same column as the current line."
585   (interactive)
586   (save-excursion
587     (save-match-data
588       (when (looking-at "\\s-*\\_<while\\_>")
589         (if (save-excursion
590               (skip-chars-backward "[ \t\n]*}")
591               (looking-at "[ \t\n]*}"))
592             (save-excursion
593               (backward-list) (forward-symbol -1) (looking-at "\\_<do\\_>"))
594           (espresso--re-search-backward "\\_<do\\_>" (point-at-bol) t)
595           (or (looking-at "\\_<do\\_>")
596               (let ((saved-indent (current-indentation)))
597                 (while (and (espresso--re-search-backward "^\\s-*\\_<" nil t)
598                             (/= (current-indentation) saved-indent)))
599                 (and (looking-at "\\s-*\\_<do\\_>")
600                      (not (espresso--re-search-forward
601                            "\\_<while\\_>" (point-at-eol) t))
602                      (= (current-indentation) saved-indent)))))))))
603
604
605 (defun espresso--ctrl-statement-indentation ()
606   "Returns the proper indentation of the current line if it
607 starts the body of a control statement without braces, else
608 returns nil."
609   (save-excursion
610     (back-to-indentation)
611     (when (save-excursion
612             (and (not (looking-at "[{]"))
613                  (progn
614                    (espresso--re-search-backward "[[:graph:]]" nil t)
615                    (or (eobp) (forward-char))
616                    (when (= (char-before) ?\)) (backward-list))
617                    (skip-syntax-backward " ")
618                    (skip-syntax-backward "w_")
619                    (looking-at espresso--possibly-braceless-keyword-re))
620                  (not (espresso--end-of-do-while-loop-p))))
621       (save-excursion
622         (goto-char (match-beginning 0))
623         (+ (current-indentation) espresso-indent-level)))))
624
625
626 (defun espresso--proper-indentation (parse-status)
627   "Return the proper indentation for the current line."
628   (save-excursion
629     (back-to-indentation)
630     (let ((ctrl-stmt-indent (espresso--ctrl-statement-indentation))
631           (same-indent-p (looking-at "[]})]\\|\\_<case\\_>\\|\\_<default\\_>"))
632           (continued-expr-p (espresso--continued-expression-p)))
633       (cond (ctrl-stmt-indent)
634             ((eq (char-after) ?#) 0)
635             ((save-excursion (espresso--beginning-of-macro))
636              4)
637             ((nth 1 parse-status)
638              (goto-char (nth 1 parse-status))
639              (if (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")
640                  (progn
641                    (skip-syntax-backward " ")
642                    (when (= (char-before) ?\)) (backward-list))
643                    (back-to-indentation)
644                    (cond (same-indent-p
645                           (current-column))
646                          (continued-expr-p
647                           (+ (current-column) (* 2 espresso-indent-level)
648                              espresso-expr-indent-offset))
649                          (t
650                           (+ (current-column) espresso-indent-level))))
651                (unless same-indent-p
652                  (forward-char)
653                  (skip-chars-forward " \t"))
654                (current-column)))
655             (continued-expr-p (+ espresso-indent-level
656                                  espresso-expr-indent-offset))
657             (t 0)))))
658
659
660 (defun espresso-indent-line ()
661   "Indent the current line as JavaScript source text."
662   (interactive)
663   (save-restriction
664     (widen)
665     (let* ((parse-status
666             (save-excursion (syntax-ppss (point-at-bol))))
667            (offset (- (current-column) (current-indentation))))
668
669       (if (nth 8 parse-status)
670           (indent-relative-maybe)
671         (indent-line-to (espresso--proper-indentation parse-status))
672         (when (> offset 0) (forward-char offset))))))
673
674 ;;; Filling
675
676 (defun espresso-c-fill-paragraph (&optional justify)
677   "Fill the paragraph with c-fill-paragraph"
678   (interactive "*P")
679
680   ;; FIXME: filling a single-line C-style comment into multiple lines
681   ;; does something horrible to the undo list
682
683   (flet ((c-forward-sws
684           (&optional limit)
685           (espresso--forward-syntactic-ws limit))
686
687          (c-backward-sws
688           (&optional limit)
689           (espresso--backward-syntactic-ws limit))
690
691          (c-beginning-of-macro
692           (&optional limit)
693           (espresso--beginning-of-macro limit)))
694
695     (let ((fill-paragraph-function 'c-fill-paragraph))
696       (c-fill-paragraph justify))))
697
698 ;;; Imenu
699
700 (defun espresso--imenu-create-index ()
701   (let ((search-re (mapconcat (lambda (x)
702                                 (concat "\\(" x "\\)"))
703                               (list espresso--function-heading-1-re
704                                     espresso--function-heading-2-re
705                                     (concat "\\(?:"
706                                             (mapconcat
707                                              #'identity
708                                              espresso--class-decls "\\|")
709                                             "\\)")
710                                     espresso--macro-decl-re)
711                               "\\|"))
712         entries parent-entries ends tmp syntax)
713     (save-excursion
714       (save-restriction
715         (widen)
716         (goto-char (point-min))
717
718         (while (re-search-forward search-re (point-max) t)
719           (goto-char (match-beginning 0))
720           (setq syntax (syntax-ppss))
721           (unless (or (nth 3 syntax) (nth 4 syntax))
722             (while (and ends (>= (point) (car ends)))
723               (setq tmp     (nreverse entries)
724                     entries (pop parent-entries))
725
726               (unless tmp
727                 (setq tmp (list
728                            (cons "[empty]" (set-marker (make-marker)
729                                                        (car ends))))))
730
731               (pop ends)
732
733               (setcdr (car entries) tmp))
734
735             (cond ((and (not parent-entries) ; regular function or macro
736                         (or (looking-at espresso--function-heading-1-re)
737                             (looking-at espresso--macro-decl-re)))
738
739                    (push (cons (match-string-no-properties 1)
740                                (set-marker (make-marker) (match-beginning 1)))
741                          entries))
742
743                   ;; does one of the espresso--class-decls regexps match?
744                   ((let ((r espresso--class-decls))
745                      (while (and r (not (looking-at (car r) )))
746                        (setq r (cdr r)))
747                      r)
748
749                    (push (cons
750                           (match-string-no-properties 1)
751                           nil)
752                          entries)
753                    (push entries parent-entries)
754                    (setq entries nil)
755                    (goto-char (match-end 1))
756                    (condition-case err
757                        (forward-list)
758                      (error nil))
759                    (push (point) ends))
760
761
762                   ((and parent-entries
763                         (looking-at espresso--function-heading-2-re))
764                    (push (cons (match-string-no-properties 1)
765                                (set-marker (make-marker) (match-beginning 1)))
766                          entries))))
767
768           (goto-char (match-end 0)))
769
770         (while parent-entries
771           (setq tmp     (nreverse entries)
772                 entries (pop parent-entries))
773             (setcdr (car entries) tmp))))
774
775     (nreverse entries)))
776
777 (defun espresso--which-func-joiner (parts)
778   (mapconcat #'identity parts "."))
779
780 ;;; Main Function
781
782 ;;;###autoload
783 (defun espresso-mode ()
784   "Major mode for editing JavaScript source text.
785
786 Key bindings:
787
788 \\{espresso-mode-map}"
789   (interactive)
790   (kill-all-local-variables)
791
792   (use-local-map espresso-mode-map)
793   (set-syntax-table espresso-mode-syntax-table)
794   (set (make-local-variable 'indent-line-function) 'espresso-indent-line)
795   (set (make-local-variable 'beginning-of-defun-function)
796        'espresso--beginning-of-defun)
797   (set (make-local-variable 'end-of-defun-function)
798        'espresso--end-of-defun)
799
800   (set (make-local-variable 'open-paren-in-column-0-is-defun-start) nil)
801
802   (set (make-local-variable 'font-lock-defaults)
803        (list espresso--font-lock-keywords
804              nil nil nil nil
805              '(font-lock-syntactic-keywords
806                . espresso--font-lock-syntactic-keywords)))
807
808   (set (make-local-variable 'parse-sexp-ignore-comments) t)
809   (set (make-local-variable 'parse-sexp-lookup-properties) t)
810   (set (make-local-variable 'which-func-imenu-joiner-function)
811        #'espresso--which-func-joiner)
812
813   ;; Comments
814   (setq comment-start "// ")
815   (setq comment-end "")
816   (set (make-local-variable 'fill-paragraph-function)
817        'espresso-c-fill-paragraph)
818
819   ;; Imenu
820   (setq imenu-case-fold-search nil)
821   (set (make-local-variable 'imenu-create-index-function)
822        #'espresso--imenu-create-index)
823
824   (setq major-mode 'espresso-mode)
825   (setq mode-name "Espresso")
826
827   ;; for filling, pretend we're cc-mode
828   (setq c-comment-prefix-regexp "//+\\|\\**"
829         c-paragraph-start "$"
830         c-paragraph-separate "$"
831         c-block-comment-prefix "* "
832         c-line-comment-starter "//"
833         c-comment-start-regexp "/[*/]\\|\\s!"
834         comment-start-skip "\\(//+\\|/\\*+\\)\\s *")
835
836   (let ((c-buffer-is-cc-mode t))
837     (c-setup-paragraph-variables))
838
839   ;; Important to fontify the whole buffer syntactically! If we don't,
840   ;; then we might have regular expression literals that aren't marked
841   ;; as strings, which will screw up parse-partial-sexp, scan-lists, etc.
842   ;; and and produce maddening "unbalanced parenthesis" errors. When we attempt
843   ;; to find the error and scroll to the portion of the buffer containing the problem,
844   ;; JIT-lock will apply the correct syntax to the regular expresion literal and
845   ;; the problem will mysteriously disappear.
846   (font-lock-set-defaults)
847
848   (let (font-lock-keywords) ; leaves syntactic keywords intact
849     (font-lock-fontify-buffer))
850
851   (run-mode-hooks 'espresso-mode-hook))
852
853
854 (eval-after-load "hideshow"
855   '(add-to-list 'hs-special-modes-alist
856                 '(espresso-mode "{" "}" "/[*/]"
857                                 nil hs-c-like-adjust-block-beginning)))
858
859 (eval-after-load "folding"
860   (when (fboundp 'folding-add-to-marks-list)
861     (folding-add-to-marks-list 'espresso-mode "// {{{" "// }}}" )))
862
863
864 ;;; Emacs
865 (provide 'espresso-mode)
866 ;; Local Variables:
867 ;; outline-regexp: ";;; "
868 ;; End:
869 ;; espresso.el ends here