Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 403 lines (310 sloc) 12.563 kB
4a11c70 @defunkt documentation
authored
1 ;;; coffee-mode.el --- Major mode to edit CoffeeScript files in Emacs
2
3 ;; Copyright (C) 2010 Chris Wanstrath
4
5 ;; Version 0.1.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
0480381 @defunkt mini docs
authored
45 ;; Major thanks to http://xahlee.org/emacs/elisp_syntax_coloring.html
46 ;; the instructions.
4a11c70 @defunkt documentation
authored
47
38ce8da @defunkt small tweaks
authored
48 ;; Also thanks to Jason Blevins's markdown-mode.el for guidance.
49
12bd5d0 @defunkt todo
authored
50 ;; TODO:
51 ;; - Fix indentation toggling on blank (pure whitespace) lines
52 ;; - imenu support
53 ;; - Make prototype accessor assignments like `String::length: -> 10` pretty.
54
55 ;;; Code:
56
38ce8da @defunkt small tweaks
authored
57 (require 'easymenu)
58 (require 'font-lock)
14ab5a0 @defunkt Use Common Lisp's `some` despite its inferior API.
authored
59 (require 'cl)
0480381 @defunkt mini docs
authored
60
7557530 @defunkt compilation commands
authored
61 ;;
38ce8da @defunkt small tweaks
authored
62 ;; Customizable Variables
7557530 @defunkt compilation commands
authored
63 ;;
64
38ce8da @defunkt small tweaks
authored
65 (defconst coffee-mode-version "0.1.0"
5f14029 @defunkt Menu
authored
66 "The version of this `coffee-mode'.")
67
c063681 @defunkt no more debug mode
authored
68 (defvar coffee-debug-mode nil
ce05b32 @defunkt debug mode, t for now
authored
69 "Whether to run in debug mode or not. Logs to `*Messages*'.")
70
38ce8da @defunkt small tweaks
authored
71 (defvar coffee-mode-hook nil
72 "A hook for you to run your own code when the mode is loaded.")
73
7557530 @defunkt compilation commands
authored
74 (defvar coffee-command "coffee"
75 "The CoffeeScript command used for evaluating code. Must be in your
76 path.")
77
28485fa @defunkt `coffee-repl'
authored
78 (defvar coffee-repl-args '("-i")
79 "The command line arguments to pass to `coffee-command' to start a REPL.")
80
5f14029 @defunkt Menu
authored
81 (defvar coffee-command-args '("-s" "-p" "--no-wrap")
82 "The command line arguments to pass to `coffee-command' to get it to
83 print the compiled JavaScript.")
84
85 (defun coffee-command-full ()
86 "The full `coffee-command' complete with args."
87 (mapconcat 'identity (append (list coffee-command) coffee-command-args) " "))
88
7557530 @defunkt compilation commands
authored
89 (defvar coffee-js-mode 'js2-mode
90 "The mode to use when viewing compiled JavaScript.")
91
92 (defvar coffee-compiled-buffer-name "*coffee-compiled*"
93 "The name of the scratch buffer used when compiling CoffeeScript.")
94
eee29e0 @defunkt defvar the keymap
authored
95 (defvar coffee-mode-map (make-keymap)
96 "Keymap for CoffeeScript major mode.")
97
38ce8da @defunkt small tweaks
authored
98 ;;
99 ;; Commands
100 ;;
101
28485fa @defunkt `coffee-repl'
authored
102 (defun coffee-repl ()
103 "Launch a CoffeeScript REPL using `coffee-command' as an inferior mode."
104 (interactive)
105
17150cf @defunkt docs and starting to implement indenters
authored
106 (unless (comint-check-proc "*CoffeeREPL*")
28485fa @defunkt `coffee-repl'
authored
107 (set-buffer
17150cf @defunkt docs and starting to implement indenters
authored
108 (apply 'make-comint "CoffeeREPL"
28485fa @defunkt `coffee-repl'
authored
109 coffee-command nil coffee-repl-args)))
110
111 (pop-to-buffer "*CoffeeScript*"))
38ce8da @defunkt small tweaks
authored
112
7557530 @defunkt compilation commands
authored
113 (defun coffee-compile-buffer ()
17150cf @defunkt docs and starting to implement indenters
authored
114 "Compiles the current buffer and displays the JS in another buffer."
7557530 @defunkt compilation commands
authored
115 (interactive)
116 (save-excursion
117 (coffee-compile-region (point-min) (point-max))))
118
119 (defun coffee-compile-region (start end)
17150cf @defunkt docs and starting to implement indenters
authored
120 "Compiles a region and displays the JS in another buffer."
7557530 @defunkt compilation commands
authored
121 (interactive "r")
122
123 (let ((buffer (get-buffer coffee-compiled-buffer-name)))
124 (when buffer
125 (kill-buffer buffer)))
126
127 (call-process-region start end coffee-command nil
128 (get-buffer-create coffee-compiled-buffer-name)
129 nil
130 "-s" "-p" "--no-wrap")
131 (switch-to-buffer-other-frame (get-buffer coffee-compiled-buffer-name))
132 (funcall coffee-js-mode)
133 (beginning-of-buffer))
3a47b28 @defunkt docs
authored
134
5f14029 @defunkt Menu
authored
135 (defun coffee-show-version ()
136 "Prints the `coffee-mode' version."
137 (interactive)
138 (message (concat "coffee-mode v" coffee-mode-version)))
139
fd314cc @defunkt menu tweaks
authored
140 (defun coffee-open-reference ()
141 "Open browser to CoffeeScript reference."
142 (interactive)
143 (browse-url "http://jashkenas.github.com/coffee-script/"))
144
5f14029 @defunkt Menu
authored
145 (defun coffee-open-github ()
fd314cc @defunkt menu tweaks
authored
146 "Open browser to `coffee-mode' project on GithHub."
5f14029 @defunkt Menu
authored
147 (interactive)
148 (browse-url "http://github.com/defunkt/coffee-mode"))
149
150 ;;
151 ;; Menubar
152 ;;
153
154 (easy-menu-define coffee-mode-menu coffee-mode-map
155 "Menu for CoffeeScript mode"
156 '("CoffeeScript"
157 ["Compile Buffer" coffee-compile-buffer]
158 ["Compile Region" coffee-compile-region]
5256afd @defunkt tweaks
authored
159 ["REPL" coffee-repl]
5f14029 @defunkt Menu
authored
160 "---"
fd314cc @defunkt menu tweaks
authored
161 ["CoffeeScript reference" coffee-open-reference]
162 ["coffee-mode on GitHub" coffee-open-github]
5f14029 @defunkt Menu
authored
163 ["Version" coffee-show-version]
164 ))
165
3a47b28 @defunkt docs
authored
166 ;;
167 ;; Define Language Syntax
168 ;;
169
170 ;; Instance variables (implicit this)
5256afd @defunkt tweaks
authored
171 (defvar coffee-this-regexp "@\\w*\\|this")
3a47b28 @defunkt docs
authored
172
88e7b95 @defunkt better regexps, @blah: and {blah: true, blah2:true}
authored
173 ;; Assignment
5256afd @defunkt tweaks
authored
174 (defvar coffee-assign-regexp "\\(\\w\\|\\.\\|_\\| \\|$\\)+?:")
88e7b95 @defunkt better regexps, @blah: and {blah: true, blah2:true}
authored
175
af34f91 @defunkt Booleans, better colors
authored
176 ;; Booleans
5256afd @defunkt tweaks
authored
177 (defvar coffee-boolean-regexp "\\b\\(true\\|false\\|yes\\|no\\|on\\|off\\)\\b")
3a47b28 @defunkt docs
authored
178
dbb4ec5 @defunkt comment tweaks
authored
179 ;; Regular Expressions
5256afd @defunkt tweaks
authored
180 (defvar coffee-regexp-regexp "\\/.+?\\/")
3a47b28 @defunkt docs
authored
181
cf028f8 @defunkt basics
authored
182 ;; JavaScript Keywords
d6b78ed @defunkt defvars and basic indentation
authored
183 (defvar coffee-js-keywords
af34f91 @defunkt Booleans, better colors
authored
184 '("if" "else" "new" "return" "try" "catch"
cf028f8 @defunkt basics
authored
185 "finally" "throw" "break" "continue" "for" "in" "while"
186 "delete" "instanceof" "typeof" "switch" "super" "extends"
187 "class"))
188
189 ;; Reserved keywords either by JS or CS.
d6b78ed @defunkt defvars and basic indentation
authored
190 (defvar coffee-js-reserved
cf028f8 @defunkt basics
authored
191 '("case" "default" "do" "function" "var" "void" "with"
192 "const" "let" "debugger" "enum" "export" "import" "native"
193 "__extends" "__hasProp"))
194
195 ;; CoffeeScript keywords.
d6b78ed @defunkt defvars and basic indentation
authored
196 (defvar coffee-cs-keywords
af34f91 @defunkt Booleans, better colors
authored
197 '("then" "unless" "and" "or" "is"
cf028f8 @defunkt basics
authored
198 "isnt" "not" "of" "by" "where" "when"))
199
0480381 @defunkt mini docs
authored
200 ;; Regular expression combining the above three lists.
d6b78ed @defunkt defvars and basic indentation
authored
201 (defvar coffee-keywords-regexp (regexp-opt
cf028f8 @defunkt basics
authored
202 (append
203 coffee-js-reserved
204 coffee-js-keywords
205 coffee-cs-keywords) 'words))
206
207
ff61700 @defunkt that was bothering me
authored
208 ;; Create the list for font-lock. Each class of keyword is given a
209 ;; particular face.
5256afd @defunkt tweaks
authored
210 (defvar coffee-font-lock-keywords
ff61700 @defunkt that was bothering me
authored
211 ;; *Note*: order below matters. `coffee-keywords-regexp' goes last
212 ;; because otherwise the keyword "state" in the function
213 ;; "state_entry" would be highlighted.
214 `((,coffee-this-regexp . font-lock-variable-name-face)
215 (,coffee-assign-regexp . font-lock-type-face)
216 (,coffee-regexp-regexp . font-lock-constant-face)
217 (,coffee-boolean-regexp . font-lock-constant-face)
218 (,coffee-keywords-regexp . font-lock-keyword-face)))
cf028f8 @defunkt basics
authored
219
3a47b28 @defunkt docs
authored
220 ;;
221 ;; Helper Functions
222 ;;
223
0480381 @defunkt mini docs
authored
224 ;; The command to comment/uncomment text
9ff612a @defunkt single quotes
authored
225 (defun coffee-comment-dwim (arg)
226 "Comment or uncomment current line or region in a smart way.
227 For detail, see `comment-dwim'."
228 (interactive "*P")
229 (require 'newcomment)
230 (let ((deactivate-mark nil) (comment-start "#") (comment-end ""))
231 (comment-dwim arg)))
84ab684 @defunkt comments
authored
232
ce05b32 @defunkt debug mode, t for now
authored
233 (defun coffee-debug (string &optional args)
234 "Print a message when in debug mode."
235 (when coffee-debug-mode
236 (message string args)))
237
2504ebe @defunkt explain indentation plan
authored
238 ;;
d6b78ed @defunkt defvars and basic indentation
authored
239 ;; Indentation
2504ebe @defunkt explain indentation plan
authored
240 ;;
241
dbb4ec5 @defunkt comment tweaks
authored
242 ;;; The theory is explained in the README.
97a1001 @defunkt basic indentation with TAB
authored
243
d6b78ed @defunkt defvars and basic indentation
authored
244 (defun coffee-indent-line ()
7b03a71 @defunkt indentation theory
authored
245 "Indent current line as CoffeeScript."
d6b78ed @defunkt defvars and basic indentation
authored
246 (interactive)
247
97ef92b @defunkt Bugfix: Indentation works when point is at beginning of the line
authored
248 ;; Bail early by indenting if point as the front of the line.
249 (if (= (point) (point-at-bol))
97a1001 @defunkt basic indentation with TAB
authored
250 (insert-tab)
97ef92b @defunkt Bugfix: Indentation works when point is at beginning of the line
authored
251 (save-excursion
252 (let ((prev-indent 0) (cur-indent 0))
253 ;; Figure out the indentation of the previous line
254 (forward-line -1)
255 (setq prev-indent (current-indentation))
256 (coffee-debug "prev-indent %s" prev-indent)
257
258 ;; Figure out the current line's indentation
259 (forward-line 1)
260 (setq cur-indent (current-indentation))
261 (coffee-debug "cur-indent %s" cur-indent)
262
263 ;; Shift one column to the left
97a1001 @defunkt basic indentation with TAB
authored
264 (backward-to-indentation 0)
97ef92b @defunkt Bugfix: Indentation works when point is at beginning of the line
authored
265 (coffee-debug "backward cur-indent %s" (current-indentation))
266 (insert-tab)
267
268 ;; We're too far, remove all indentation.
269 (when (> (- (current-indentation) prev-indent) tab-width)
270 (backward-to-indentation 0)
271 (delete-region (point-at-bol) (point)))))))
df08186 @defunkt indenters
authored
272
17150cf @defunkt docs and starting to implement indenters
authored
273 (defun coffee-newline-and-indent ()
274 "Inserts a newline and indents it to the same level as the previous line."
275 (interactive)
276
277 ;; Remember the current line indentation level,
278 ;; insert a newline, and indent the newline to the same
279 ;; level as the previous line.
280 (let ((prev-indent (current-indentation)) (indent-next nil))
281 (newline)
282 (insert-tab (/ prev-indent tab-width))
3a47b28 @defunkt docs
authored
283
17150cf @defunkt docs and starting to implement indenters
authored
284 ;; We need to insert an additional tab because the last line was special.
285 (when (coffee-line-wants-indent)
286 (insert-tab)))
287
288 ;; Last line was a comment so this one should probably be,
289 ;; too. Makes it easy to write multi-line comments (like the one I'm
290 ;; writing right now).
291 (when (coffee-previous-line-is-comment)
292 (insert "# ")))
293
294 ;; Indenters help determine whether the current line should be
295 ;; indented further based on the content of the previous line. If a
296 ;; line starts with `class', for instance, you're probably going to
297 ;; want to indent the next line.
298
299 (defvar coffee-indenters-bol '("class" "for" "if" "try")
7d338d1 @defunkt coffee-indenters
authored
300 "Keywords or syntax whose presence at the start of a line means the
301 next line should probably be indented.")
302
303 (defun coffee-indenters-bol-regexp ()
304 "Builds a regexp out of `coffee-indenters-bol' words."
305 (concat "^" (regexp-opt coffee-indenters-bol 'words)))
306
5e170be @defunkt get it working (with a fake function though)
authored
307 (defvar coffee-indenters-eol '(?> ?{ ?\[)
308 "Single characters at the end of a line that mean the next line
309 should probably be indented.")
7d338d1 @defunkt coffee-indenters
authored
310
17150cf @defunkt docs and starting to implement indenters
authored
311 (defun coffee-line-wants-indent ()
312 "Does the current line want to be indented deeper than the previous
df08186 @defunkt indenters
authored
313 line? Returns `t' or `nil'. See the README for more details."
8bfb7ed @defunkt Keep same indentation of last line when creating a new line
authored
314 (interactive)
315
17150cf @defunkt docs and starting to implement indenters
authored
316 (save-excursion
317 (let ((indenter-at-bol) (indenter-at-eol))
318 ;; Go back a line and to the first character.
319 (forward-line -1)
320 (backward-to-indentation 0)
8bfb7ed @defunkt Keep same indentation of last line when creating a new line
authored
321
17150cf @defunkt docs and starting to implement indenters
authored
322 ;; If the next few characters match one of our magic indenter
323 ;; keywords, we want to indent the line we were on originally.
324 (when (looking-at (coffee-indenters-bol-regexp))
325 (setq indenter-at-bol t))
326
327 ;; If that didn't match, go to the back of the line and check to
5e170be @defunkt get it working (with a fake function though)
authored
328 ;; see if the last character matches one of our indenter
329 ;; characters.
17150cf @defunkt docs and starting to implement indenters
authored
330 (when (not indenter-at-bol)
331 (end-of-line)
332
5e170be @defunkt get it working (with a fake function though)
authored
333 ;; Optimized for speed - checks only the last character.
14ab5a0 @defunkt Use Common Lisp's `some` despite its inferior API.
authored
334 (when (some (lambda (char)
335 (= (char-before) char))
336 coffee-indenters-eol)
17150cf @defunkt docs and starting to implement indenters
authored
337 (setq indenter-at-eol t)))
338
339 ;; If we found an indenter, return `t'.
340 (or indenter-at-bol indenter-at-eol))))
341
8bfb7ed @defunkt Keep same indentation of last line when creating a new line
authored
342 (defun coffee-previous-line-is-comment ()
343 "Returns `t' if the previous line is a CoffeeScript comment."
344 (save-excursion
345 (forward-line -1)
346 (coffee-line-is-comment)))
347
348 (defun coffee-line-is-comment ()
349 "Returns `t' if the current line is a CoffeeScript comment."
350 (save-excursion
351 (backward-to-indentation 0)
352 (= (char-after) (string-to-char "#"))))
353
3a47b28 @defunkt docs
authored
354 ;;
355 ;; Define Major Mode
356 ;;
357
cf028f8 @defunkt basics
authored
358 (define-derived-mode coffee-mode fundamental-mode
359 "coffee-mode"
360 "Major mode for editing CoffeeScript..."
361
7557530 @defunkt compilation commands
authored
362 (define-key coffee-mode-map (kbd "A-r") 'coffee-compile-buffer)
eee29e0 @defunkt defvar the keymap
authored
363 (define-key coffee-mode-map (kbd "A-R") 'coffee-execute-line)
364 (define-key coffee-mode-map (kbd "A-M-r") 'coffee-repl)
7557530 @defunkt compilation commands
authored
365 (define-key coffee-mode-map [remap comment-dwim] 'coffee-comment-dwim)
8bfb7ed @defunkt Keep same indentation of last line when creating a new line
authored
366 (define-key coffee-mode-map "\C-m" 'coffee-newline-and-indent)
7557530 @defunkt compilation commands
authored
367
cf028f8 @defunkt basics
authored
368 ;; code for syntax highlighting
369 (setq font-lock-defaults '((coffee-font-lock-keywords)))
370
84ab684 @defunkt comments
authored
371 ;; perl style comment: "# ..."
372 (modify-syntax-entry ?# "< b" coffee-mode-syntax-table)
373 (modify-syntax-entry ?\n "> b" coffee-mode-syntax-table)
0480381 @defunkt mini docs
authored
374 (setq comment-start "#")
84ab684 @defunkt comments
authored
375
9ff612a @defunkt single quotes
authored
376 ;; single quote strings
377 (modify-syntax-entry ?' "\"" coffee-mode-syntax-table)
378 (modify-syntax-entry ?' "\"" coffee-mode-syntax-table)
379
d6b78ed @defunkt defvars and basic indentation
authored
380 ;; indentation
381 (make-local-variable 'indent-line-function)
382 (setq indent-line-function 'coffee-indent-line)
383
384 ;; no tabs
385 (setq indent-tabs-mode nil)
386
cf028f8 @defunkt basics
authored
387 ;; clear memory
388 (setq coffee-keywords-regexp nil)
389 (setq coffee-types-regexp nil)
390 (setq coffee-constants-regexp nil)
391 (setq coffee-events-regexp nil)
392 (setq coffee-functions-regexp nil))
eaf28ab @defunkt register as .coffee handler
authored
393
27ffada @defunkt provide something
authored
394 (provide 'coffee-mode)
395
eaf28ab @defunkt register as .coffee handler
authored
396 ;;
397 ;; On Load
398 ;;
399
400 ;; Run coffee-mode for files ending in .coffee.
401 (add-to-list 'auto-mode-alist '("\\.coffee$" . coffee-mode))
605a330 @defunkt highlight Cakefile too
authored
402 (add-to-list 'auto-mode-alist '("Cakefile" . coffee-mode))
Something went wrong with that request. Please try again.