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