]> code.delx.au - gnu-emacs-elpa/blob - coffee-mode.el
Update version. Fixes #59
[gnu-emacs-elpa] / coffee-mode.el
1 ;;; coffee-mode.el --- Major mode to edit CoffeeScript files in Emacs
2
3 ;; Copyright (C) 2010 Chris Wanstrath
4
5 ;; Version: 0.4.0
6 ;; Keywords: CoffeeScript major mode
7 ;; Author: Chris Wanstrath <chris@ozmm.org>
8 ;; URL: http://github.com/defunkt/coffee-script
9
10 ;; This file is not part of GNU Emacs.
11
12 ;; This program is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with this program; if not, write to the Free Software
24 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25
26 ;;; Commentary
27
28 ;; For commentary please see the README.md or
29 ;; http://github.com/defunkt/coffee-mode#readme
30
31 ;;; Installation
32
33 ;; In your shell:
34
35 ;; $ cd ~/.emacs.d/vendor
36 ;; $ git clone git://github.com/defunkt/coffee-mode.git
37
38 ;; In your emacs config:
39
40 ;; (add-to-list 'load-path "~/.emacs.d/vendor/coffee-mode")
41 ;; (require 'coffee-mode)
42
43 ;;; Thanks
44
45 ;; Major thanks to http://xahlee.org/emacs/elisp_syntax_coloring.html
46 ;; the instructions.
47
48 ;; Also thanks to Jason Blevins's markdown-mode.el and Steve Yegge's
49 ;; js2-mode for guidance.
50
51 ;; TODO:
52 ;; - Execute {buffer,region,line} and show output in new buffer
53 ;; - Make prototype accessor assignments like `String::length: -> 10` pretty.
54 ;; - mirror-mode - close brackets and parens automatically
55
56 ;;; Code:
57
58 (require 'comint)
59 (require 'easymenu)
60 (require 'font-lock)
61
62 (eval-when-compile
63 (require 'cl))
64
65 ;;
66 ;; Customizable Variables
67 ;;
68
69 (defconst coffee-mode-version "0.3.0"
70 "The version of this `coffee-mode'.")
71
72 (defgroup coffee nil
73 "A CoffeeScript major mode."
74 :group 'languages)
75
76 (defcustom coffee-debug-mode nil
77 "Whether to run in debug mode or not. Logs to `*Messages*'."
78 :type 'boolean
79 :group 'coffee-mode)
80
81 (defcustom coffee-js-mode 'js2-mode
82 "The mode to use when viewing compiled JavaScript."
83 :type 'string
84 :group 'coffee)
85
86 (defcustom coffee-cleanup-whitespace t
87 "Should we `delete-trailing-whitespace' on save? Probably."
88 :type 'boolean
89 :group 'coffee)
90
91 (defcustom coffee-tab-width tab-width
92 "The tab width to use when indenting."
93 :type 'integer
94 :group 'coffee)
95
96 (defcustom coffee-command "coffee"
97 "The CoffeeScript command used for evaluating code. Must be in your
98 path."
99 :type 'string
100 :group 'coffee)
101
102 (defcustom js2coffee-command "js2coffee"
103 "The js2coffee command used for evaluating code. Must be in your
104 path."
105 :type 'string
106 :group 'coffee)
107
108
109 (defcustom coffee-args-repl '("-i")
110 "The command line arguments to pass to `coffee-command' to start a REPL."
111 :type 'list
112 :group 'coffee)
113
114 (defcustom coffee-args-compile '("-c")
115 "The command line arguments to pass to `coffee-command' when compiling a file."
116 :type 'list
117 :group 'coffee)
118
119 (defcustom coffee-cygwin-mode t
120 "For Windows systems, add support for Cygwin-style absolute paths."
121 :type 'boolean
122 :group 'coffee)
123
124 (defcustom coffee-cygwin-prefix "/cygdrive/C"
125 "The prefix with which to replace the drive-letter for your Windows partition, e.g. 'C:' would be replaced by '/c/cygdrive'."
126 :type 'string
127 :group 'coffee)
128
129 (defcustom coffee-compiled-buffer-name "*coffee-compiled*"
130 "The name of the scratch buffer used when compiling CoffeeScript."
131 :type 'string
132 :group 'coffee)
133
134 (defcustom coffee-compile-jump-to-error t
135 "Whether to jump to the first error if compilation fails.
136 Please note that the coffee compiler doesn't always give a line
137 number for the issue and in that case it is not possible to jump
138 to the error, of course."
139 :type 'boolean
140 :group 'coffee)
141
142 (defcustom coffee-watch-buffer-name "*coffee-watch*"
143 "The name of the scratch buffer used when using the --watch flag with CoffeeScript."
144 :type 'string
145 :group 'coffee)
146
147 (defvar coffee-mode-hook nil
148 "A hook for you to run your own code when the mode is loaded.")
149
150 (defvar coffee-mode-map (make-keymap)
151 "Keymap for CoffeeScript major mode.")
152
153 ;;
154 ;; Compat
155 ;;
156
157 (unless (fboundp 'apply-partially)
158 (defun apply-partially (fun &rest args)
159 "Return a function that is a partial application of FUN to ARGS.
160 ARGS is a list of the first N arguments to pass to FUN.
161 The result is a new function which does the same as FUN, except that
162 the first N arguments are fixed at the values with which this function
163 was called."
164 (lexical-let ((fun fun) (args1 args))
165 (lambda (&rest args2) (apply fun (append args1 args2))))))
166
167 ;;
168 ;; Macros
169 ;;
170
171 (defmacro setd (var val)
172 "Like setq but optionally logs the variable's value using `coffee-debug'."
173 (if (and (boundp 'coffee-debug-mode) coffee-debug-mode)
174 `(progn
175 (coffee-debug "%s: %s" ',var ,val)
176 (setq ,var ,val))
177 `(setq ,var ,val)))
178
179 (defun coffee-debug (string &rest args)
180 "Print a message when in debug mode."
181 (when coffee-debug-mode
182 (apply 'message (append (list string) args))))
183
184 (defmacro coffee-line-as-string ()
185 "Returns the current line as a string."
186 `(buffer-substring (point-at-bol) (point-at-eol)))
187
188 ;;
189 ;; Commands
190 ;;
191
192 (defun coffee-repl ()
193 "Launch a CoffeeScript REPL using `coffee-command' as an inferior mode."
194 (interactive)
195
196 (unless (comint-check-proc "*CoffeeREPL*")
197 (set-buffer
198 (apply 'make-comint "CoffeeREPL"
199 coffee-command nil coffee-args-repl)))
200
201 (pop-to-buffer "*CoffeeREPL*"))
202
203 (defun coffee-compiled-file-name (&optional filename)
204 "Returns the name of the JavaScript file compiled from a CoffeeScript file.
205 If FILENAME is omitted, the current buffer's file name is used."
206 (concat (file-name-sans-extension (or filename (buffer-file-name))) ".js"))
207
208 (defun coffee-compile-file ()
209 "Compiles and saves the current file to disk. Doesn't open in a buffer.."
210 (interactive)
211 (let ((compiler-output (shell-command-to-string (coffee-command-compile (buffer-file-name)))))
212 (if (string= compiler-output "")
213 (message "Compiled and saved %s" (coffee-compiled-file-name))
214 (let* ((msg (car (split-string compiler-output "[\n\r]+")))
215 (line (and (string-match "on line \\([0-9]+\\)" msg)
216 (string-to-number (match-string 1 msg)))))
217 (message msg)
218 (when (and coffee-compile-jump-to-error line (> line 0))
219 (goto-char (point-min))
220 (forward-line (1- line)))))))
221
222 (defun coffee-compile-buffer ()
223 "Compiles the current buffer and displays the JS in another buffer."
224 (interactive)
225 (save-excursion
226 (coffee-compile-region (point-min) (point-max))))
227
228 (defun coffee-compile-region (start end)
229 "Compiles a region and displays the JS in another buffer."
230 (interactive "r")
231
232 (let ((buffer (get-buffer coffee-compiled-buffer-name)))
233 (when buffer
234 (kill-buffer buffer)))
235
236 (apply (apply-partially 'call-process-region start end coffee-command nil
237 (get-buffer-create coffee-compiled-buffer-name)
238 nil)
239 (append coffee-args-compile (list "-s" "-p")))
240 (switch-to-buffer (get-buffer coffee-compiled-buffer-name))
241 (funcall coffee-js-mode)
242 (goto-char (point-min)))
243
244 (defun coffee-js2coffee-replace-region (start end)
245 "Replace JS to coffee in current buffer."
246 (interactive "r")
247
248 (let ((buffer (get-buffer coffee-compiled-buffer-name)))
249 (when buffer
250 (kill-buffer buffer)))
251
252 (call-process-region start end
253 js2coffee-command nil
254 (current-buffer)
255 )
256 (delete-region start end)
257 )
258
259 (defun coffee-show-version ()
260 "Prints the `coffee-mode' version."
261 (interactive)
262 (message (concat "coffee-mode v" coffee-mode-version)))
263
264 (defun coffee-open-reference ()
265 "Open browser to CoffeeScript reference."
266 (interactive)
267 (browse-url "http://jashkenas.github.com/coffee-script/"))
268
269 (defun coffee-open-node-reference ()
270 "Open browser to node.js documentation."
271 (interactive)
272 (browse-url "http://nodejs.org/docs/"))
273
274 (defun coffee-open-github ()
275 "Open browser to `coffee-mode' project on GithHub."
276 (interactive)
277 (browse-url "http://github.com/defunkt/coffee-mode"))
278
279 (defun coffee-watch (dir-or-file)
280 "Run `coffee-run-cmd' with the --watch flag enabled for a directory or file"
281 (interactive "fDirectory or File: ")
282 (let ((coffee-compiled-buffer-name coffee-watch-buffer-name)
283 (args (mapconcat 'identity (append coffee-args-compile (list "--watch" (coffee-universal-path dir-or-file))) " ")))
284 (coffee-run-cmd args)))
285
286 ;;
287 ;; Menubar
288 ;;
289
290 (easy-menu-define coffee-mode-menu coffee-mode-map
291 "Menu for CoffeeScript mode"
292 '("CoffeeScript"
293 ["Compile File" coffee-compile-file]
294 ["Compile Buffer" coffee-compile-buffer]
295 ["Compile Region" coffee-compile-region]
296 ["REPL" coffee-repl]
297 "---"
298 ["CoffeeScript Reference" coffee-open-reference]
299 ["node.js Reference" coffee-open-node-reference]
300 ["coffee-mode on GitHub" coffee-open-github]
301 ["Version" coffee-show-version]
302 ))
303
304 ;;
305 ;; Define Language Syntax
306 ;;
307
308 ;; String literals
309 (defvar coffee-string-regexp "\"\\([^\\]\\|\\\\.\\)*?\"\\|'\\([^\\]\\|\\\\.\\)*?'")
310
311 ;; Instance variables (implicit this)
312 (defvar coffee-this-regexp "@\\(\\w\\|_\\)*\\|this")
313
314 ;; Prototype::access
315 (defvar coffee-prototype-regexp "\\(\\(\\w\\|\\.\\|_\\| \\|$\\)+?\\)::\\(\\(\\w\\|\\.\\|_\\| \\|$\\)+?\\):")
316
317 ;; Assignment
318 (defvar coffee-assign-regexp "\\(\\(\\w\\|\\.\\|_\\|$\\)+?\s*\\):")
319
320 ;; Lambda
321 (defvar coffee-lambda-regexp "\\((.+)\\)?\\s *\\(->\\|=>\\)")
322
323 ;; Namespaces
324 (defvar coffee-namespace-regexp "\\b\\(class\\s +\\(\\S +\\)\\)\\b")
325
326 ;; Booleans
327 (defvar coffee-boolean-regexp "\\b\\(true\\|false\\|yes\\|no\\|on\\|off\\|null\\|undefined\\)\\b")
328
329 ;; Regular Expressions
330 (defvar coffee-regexp-regexp "\\/\\(\\\\.\\|\\[\\(\\\\.\\|.\\)+?\\]\\|[^/]\\)+?\\/")
331
332 ;; JavaScript Keywords
333 (defvar coffee-js-keywords
334 '("if" "else" "new" "return" "try" "catch"
335 "finally" "throw" "break" "continue" "for" "in" "while"
336 "delete" "instanceof" "typeof" "switch" "super" "extends"
337 "class" "until" "loop"))
338
339 ;; Reserved keywords either by JS or CS.
340 (defvar coffee-js-reserved
341 '("case" "default" "do" "function" "var" "void" "with"
342 "const" "let" "debugger" "enum" "export" "import" "native"
343 "__extends" "__hasProp"))
344
345 ;; CoffeeScript keywords.
346 (defvar coffee-cs-keywords
347 '("then" "unless" "and" "or" "is"
348 "isnt" "not" "of" "by" "where" "when"))
349
350 ;; Regular expression combining the above three lists.
351 (defvar coffee-keywords-regexp (regexp-opt
352 (append
353 coffee-js-reserved
354 coffee-js-keywords
355 coffee-cs-keywords) 'words))
356
357
358 ;; Create the list for font-lock. Each class of keyword is given a
359 ;; particular face.
360 (defvar coffee-font-lock-keywords
361 ;; *Note*: order below matters. `coffee-keywords-regexp' goes last
362 ;; because otherwise the keyword "state" in the function
363 ;; "state_entry" would be highlighted.
364 `((,coffee-string-regexp . font-lock-string-face)
365 (,coffee-this-regexp . font-lock-variable-name-face)
366 (,coffee-prototype-regexp . font-lock-variable-name-face)
367 (,coffee-assign-regexp . font-lock-type-face)
368 (,coffee-regexp-regexp . font-lock-constant-face)
369 (,coffee-boolean-regexp . font-lock-constant-face)
370 (,coffee-keywords-regexp . font-lock-keyword-face)))
371
372 ;;
373 ;; Helper Functions
374 ;;
375
376 (defun coffee-before-save ()
377 "Hook run before file is saved. Deletes whitespace if
378 `coffee-cleanup-whitespace' is non-nil."
379 (when coffee-cleanup-whitespace
380 (delete-trailing-whitespace)))
381
382 (defun coffee-comment-dwim (arg)
383 "Comment or uncomment current line or region in a smart way.
384 For detail, see `comment-dwim'."
385 (interactive "*P")
386 (require 'newcomment)
387 (let ((deactivate-mark nil) (comment-start "#") (comment-end ""))
388 (comment-dwim arg)))
389
390 (defun coffee-cygwin-path (expanded-file-name)
391 "Given an expanded file name, derive the absolute Cygwin path based on `coffee-cygwin-prefix'."
392 (replace-regexp-in-string "^[a-zA-Z]:" coffee-cygwin-prefix expanded-file-name t))
393
394 (defun coffee-universal-path (file-name)
395 "Handle different paths for different OS configurations for CoffeeScript"
396 (let ((full-file-name (expand-file-name file-name)))
397 (if (and (equal system-type 'windows-nt)
398 coffee-cygwin-mode)
399 (coffee-cygwin-path full-file-name)
400 full-file-name)))
401
402 (defun coffee-command-compile (file-name)
403 "The `coffee-command' with args to compile a file."
404 (let ((full-file-name (coffee-universal-path file-name)))
405 (mapconcat 'identity (append (list coffee-command) coffee-args-compile (list full-file-name)) " ")))
406
407 (defun coffee-run-cmd (args)
408 "Given an arbitrary set of arguments for the `coffee-command', compile the command and show output in a custom compilation buffer."
409 (interactive "sArguments: ")
410 (let ((compilation-buffer-name-function (lambda (this-mode)
411 (generate-new-buffer-name coffee-compiled-buffer-name))))
412 (compile (concat coffee-command " " args))))
413
414 ;;
415 ;; imenu support
416 ;;
417
418 ;; This is a pretty naive but workable way of doing it. First we look
419 ;; for any lines that starting with `coffee-assign-regexp' that include
420 ;; `coffee-lambda-regexp' then add those tokens to the list.
421 ;;
422 ;; Should cover cases like these:
423 ;;
424 ;; minus: (x, y) -> x - y
425 ;; String::length: -> 10
426 ;; block: ->
427 ;; print('potion')
428 ;;
429 ;; Next we look for any line that starts with `class' or
430 ;; `coffee-assign-regexp' followed by `{` and drop into a
431 ;; namespace. This means we search one indentation level deeper for
432 ;; more assignments and add them to the alist prefixed with the
433 ;; namespace name.
434 ;;
435 ;; Should cover cases like these:
436 ;;
437 ;; class Person
438 ;; print: ->
439 ;; print 'My name is ' + this.name + '.'
440 ;;
441 ;; class Policeman extends Person
442 ;; constructor: (rank) ->
443 ;; @rank: rank
444 ;; print: ->
445 ;; print 'My name is ' + this.name + " and I'm a " + this.rank + '.'
446 ;;
447 ;; TODO:
448 ;; app = {
449 ;; window: {width: 200, height: 200}
450 ;; para: -> 'Welcome.'
451 ;; button: -> 'OK'
452 ;; }
453
454 (defun coffee-imenu-create-index ()
455 "Create an imenu index of all methods in the buffer."
456 (interactive)
457
458 ;; This function is called within a `save-excursion' so we're safe.
459 (goto-char (point-min))
460
461 (let ((index-alist '()) assign pos indent ns-name ns-indent)
462 ;; Go through every assignment that includes -> or => on the same
463 ;; line or starts with `class'.
464 (while (re-search-forward
465 (concat "^\\(\\s *\\)"
466 "\\("
467 coffee-assign-regexp
468 ".+?"
469 coffee-lambda-regexp
470 "\\|"
471 coffee-namespace-regexp
472 "\\)")
473 (point-max)
474 t)
475
476 (coffee-debug "Match: %s" (match-string 0))
477
478 ;; If this is the start of a new namespace, save the namespace's
479 ;; indentation level and name.
480 (when (match-string 8)
481 ;; Set the name.
482 (setq ns-name (match-string 8))
483
484 ;; If this is a class declaration, add :: to the namespace.
485 (setq ns-name (concat ns-name "::"))
486
487 ;; Save the indentation level.
488 (setq ns-indent (length (match-string 1)))
489
490 ;; Debug
491 (coffee-debug "ns: Found %s with indent %s" ns-name ns-indent))
492
493 ;; If this is an assignment, save the token being
494 ;; assigned. `Please.print:` will be `Please.print`, `block:`
495 ;; will be `block`, etc.
496 (when (setq assign (match-string 3))
497 ;; The position of the match in the buffer.
498 (setq pos (match-beginning 3))
499
500 ;; The indent level of this match
501 (setq indent (length (match-string 1)))
502
503 ;; If we're within the context of a namespace, add that to the
504 ;; front of the assign, e.g.
505 ;; constructor: => Policeman::constructor
506 (when (and ns-name (> indent ns-indent))
507 (setq assign (concat ns-name assign)))
508
509 (coffee-debug "=: Found %s with indent %s" assign indent)
510
511 ;; Clear the namespace if we're no longer indented deeper
512 ;; than it.
513 (when (and ns-name (<= indent ns-indent))
514 (coffee-debug "ns: Clearing %s" ns-name)
515 (setq ns-name nil)
516 (setq ns-indent nil))
517
518 ;; Add this to the alist. Done.
519 (push (cons assign pos) index-alist)))
520
521 ;; Return the alist.
522 index-alist))
523
524 ;;
525 ;; Indentation
526 ;;
527
528 ;;; The theory is explained in the README.
529
530 (defun coffee-indent-line ()
531 "Indent current line as CoffeeScript."
532 (interactive)
533
534 (if (= (point) (point-at-bol))
535 (insert-tab)
536 (save-excursion
537 (let ((prev-indent 0) (cur-indent 0))
538 ;; Figure out the indentation of the previous line
539 (setd prev-indent (coffee-previous-indent))
540
541 ;; Figure out the current line's indentation
542 (setd cur-indent (current-indentation))
543
544 ;; Shift one column to the left
545 (beginning-of-line)
546 (insert-tab)
547
548 (coffee-debug "point: %s" (point))
549 (coffee-debug "point-at-bol: %s" (point-at-bol))
550
551 (when (= (point-at-bol) (point))
552 (forward-char coffee-tab-width))
553
554 (coffee-debug "New indent: %s" (current-indentation))
555
556 ;; We're too far, remove all indentation.
557 (when (> (- (current-indentation) prev-indent) coffee-tab-width)
558 (backward-to-indentation 0)
559 (delete-region (point-at-bol) (point)))))))
560
561 (defun coffee-previous-indent ()
562 "Return the indentation level of the previous non-blank line."
563
564 (save-excursion
565 (forward-line -1)
566 (if (bobp)
567 0
568 (progn
569 (while (and (coffee-line-empty-p) (not (bobp))) (forward-line -1))
570 (current-indentation)))))
571
572 (defun coffee-line-empty-p ()
573 "Is this line empty? Returns non-nil if so, nil if not."
574 (or (bobp)
575 (string-match "^\\s *$" (coffee-line-as-string))))
576
577 (defun coffee-newline-and-indent ()
578 "Inserts a newline and indents it to the same level as the previous line."
579 (interactive)
580
581 ;; Remember the current line indentation level,
582 ;; insert a newline, and indent the newline to the same
583 ;; level as the previous line.
584 (let ((prev-indent (current-indentation)) (indent-next nil))
585 (delete-horizontal-space t)
586 (newline)
587 (insert-tab (/ prev-indent coffee-tab-width))
588
589 ;; We need to insert an additional tab because the last line was special.
590 (when (coffee-line-wants-indent)
591 (insert-tab)))
592
593 ;; Last line was a comment so this one should probably be,
594 ;; too. Makes it easy to write multi-line comments (like the one I'm
595 ;; writing right now).
596 (when (coffee-previous-line-is-comment)
597 (insert "# ")))
598
599 ;; Indenters help determine whether the current line should be
600 ;; indented further based on the content of the previous line. If a
601 ;; line starts with `class', for instance, you're probably going to
602 ;; want to indent the next line.
603
604 (defvar coffee-indenters-bol '("class" "for" "if" "try")
605 "Keywords or syntax whose presence at the start of a line means the
606 next line should probably be indented.")
607
608 (defun coffee-indenters-bol-regexp ()
609 "Builds a regexp out of `coffee-indenters-bol' words."
610 (regexp-opt coffee-indenters-bol 'words))
611
612 (defvar coffee-indenters-eol '(?> ?{ ?\[)
613 "Single characters at the end of a line that mean the next line
614 should probably be indented.")
615
616 (defun coffee-line-wants-indent ()
617 "Does the current line want to be indented deeper than the previous
618 line? Returns `t' or `nil'. See the README for more details."
619 (interactive)
620
621 (save-excursion
622 (let ((indenter-at-bol) (indenter-at-eol))
623 ;; Go back a line and to the first character.
624 (forward-line -1)
625 (backward-to-indentation 0)
626
627 ;; If the next few characters match one of our magic indenter
628 ;; keywords, we want to indent the line we were on originally.
629 (when (looking-at (coffee-indenters-bol-regexp))
630 (setd indenter-at-bol t))
631
632 ;; If that didn't match, go to the back of the line and check to
633 ;; see if the last character matches one of our indenter
634 ;; characters.
635 (when (not indenter-at-bol)
636 (end-of-line)
637
638 ;; Optimized for speed - checks only the last character.
639 (when (some (lambda (char)
640 (= (char-before) char))
641 coffee-indenters-eol)
642 (setd indenter-at-eol t)))
643
644 ;; If we found an indenter, return `t'.
645 (or indenter-at-bol indenter-at-eol))))
646
647 (defun coffee-previous-line-is-comment ()
648 "Returns `t' if the previous line is a CoffeeScript comment."
649 (save-excursion
650 (forward-line -1)
651 (coffee-line-is-comment)))
652
653 (defun coffee-line-is-comment ()
654 "Returns `t' if the current line is a CoffeeScript comment."
655 (save-excursion
656 (backward-to-indentation 0)
657 (= (char-after) (string-to-char "#"))))
658
659 ;;
660 ;; Define Major Mode
661 ;;
662
663 ;;;###autoload
664 (define-derived-mode coffee-mode fundamental-mode
665 "Coffee"
666 "Major mode for editing CoffeeScript."
667
668 ;; key bindings
669 (define-key coffee-mode-map (kbd "A-r") 'coffee-compile-buffer)
670 (define-key coffee-mode-map (kbd "A-R") 'coffee-compile-region)
671 (define-key coffee-mode-map (kbd "A-M-r") 'coffee-repl)
672 (define-key coffee-mode-map [remap comment-dwim] 'coffee-comment-dwim)
673 (define-key coffee-mode-map "\C-m" 'coffee-newline-and-indent)
674 (define-key coffee-mode-map "\C-c\C-o\C-s" 'coffee-cos-mode)
675
676 ;; code for syntax highlighting
677 (setq font-lock-defaults '((coffee-font-lock-keywords)))
678
679 ;; perl style comment: "# ..."
680 (modify-syntax-entry ?# "< b" coffee-mode-syntax-table)
681 (modify-syntax-entry ?\n "> b" coffee-mode-syntax-table)
682 (make-local-variable 'comment-start)
683 (setq comment-start "#")
684
685 ;; single quote strings
686 (modify-syntax-entry ?' "\"" coffee-mode-syntax-table)
687
688 ;; indentation
689 (make-local-variable 'indent-line-function)
690 (setq indent-line-function 'coffee-indent-line)
691 (set (make-local-variable 'tab-width) coffee-tab-width)
692
693 ;; imenu
694 (make-local-variable 'imenu-create-index-function)
695 (setq imenu-create-index-function 'coffee-imenu-create-index)
696
697 ;; no tabs
698 (setq indent-tabs-mode nil)
699
700 ;; hooks
701 (set (make-local-variable 'before-save-hook) 'coffee-before-save))
702
703 ;;
704 ;; Compile-on-Save minor mode
705 ;;
706
707 (defvar coffee-cos-mode-line " CoS")
708 (make-variable-buffer-local 'coffee-cos-mode-line)
709
710 (define-minor-mode coffee-cos-mode
711 "Toggle compile-on-save for coffee-mode."
712 :group 'coffee-cos :lighter coffee-cos-mode-line
713 (cond
714 (coffee-cos-mode
715 (add-hook 'after-save-hook 'coffee-compile-file nil t))
716 (t
717 (remove-hook 'after-save-hook 'coffee-compile-file t))))
718
719 (provide 'coffee-mode)
720
721 ;;
722 ;; On Load
723 ;;
724
725 ;; Run coffee-mode for files ending in .coffee.
726 ;;;###autoload
727 (add-to-list 'auto-mode-alist '("\\.coffee$" . coffee-mode))
728 ;;;###autoload
729 (add-to-list 'auto-mode-alist '("Cakefile" . coffee-mode))
730 ;;; coffee-mode.el ends here