;;; Code:
+(require 'comint)
(require 'easymenu)
(require 'font-lock)
-(require 'cl)
+
+(eval-when-compile
+ (require 'cl))
;;
;; Customizable Variables
:type 'string
:group 'coffee)
-(defcustom coffee-repl-args '("-i")
+(defcustom coffee-args-repl '("-i")
"The command line arguments to pass to `coffee-command' to start a REPL."
:type 'list
:group 'coffee)
-(defcustom coffee-command-args '("-s" "-p" "--no-wrap")
- "The command line arguments to pass to `coffee-command' to get it to
-print the compiled JavaScript."
+(defcustom coffee-args-compile '("-c")
+ "The command line arguments to pass to `coffee-command' when compiling a file."
:type 'list
:group 'coffee)
(defmacro setd (var val)
"Like setq but optionally logs the variable's value using `coffee-debug'."
- (if coffee-debug-mode
+ (if (and (boundp 'coffee-debug-mode) coffee-debug-mode)
`(progn
(coffee-debug "%s: %s" ',var ,val)
(setq ,var ,val))
(unless (comint-check-proc "*CoffeeREPL*")
(set-buffer
(apply 'make-comint "CoffeeREPL"
- coffee-command nil coffee-repl-args)))
+ coffee-command nil coffee-args-repl)))
+
+ (pop-to-buffer "*CoffeeREPL*"))
- (pop-to-buffer "*CoffeeScript*"))
+(defun coffee-compiled-file-name (&optional filename)
+ "Returns the name of the JavaScript file compiled from a CoffeeScript file.
+If FILENAME is omitted, the current buffer's file name is used."
+ (concat (file-name-sans-extension (or filename (buffer-file-name))) ".js"))
(defun coffee-compile-file ()
"Compiles and saves the current file to disk. Doesn't open in a buffer.."
(interactive)
- (shell-command (concat coffee-command " -c " (buffer-file-name)))
- (message "Compiled and saved %s"
- (concat
- (substring (buffer-file-name) 0 -6)
- "js")))
+ (let ((compiler-output (shell-command-to-string (coffee-command-compile (buffer-file-name)))))
+ (if (string= compiler-output "")
+ (message "Compiled and saved %s" (coffee-compiled-file-name))
+ (message (car (split-string compiler-output "[\n\r]+"))))))
(defun coffee-compile-buffer ()
"Compiles the current buffer and displays the JS in another buffer."
(call-process-region start end coffee-command nil
(get-buffer-create coffee-compiled-buffer-name)
nil
- "-s" "-p" "--no-wrap")
+ "-s" "-p" "--bare")
(switch-to-buffer (get-buffer coffee-compiled-buffer-name))
(funcall coffee-js-mode)
- (beginning-of-buffer))
+ (goto-char (point-min)))
(defun coffee-show-version ()
"Prints the `coffee-mode' version."
(browse-url "http://jashkenas.github.com/coffee-script/"))
(defun coffee-open-node-reference ()
- "Open browser to node.js reference."
+ "Open browser to node.js documentation."
(interactive)
- (browse-url "http://nodejs.org/api.html"))
+ (browse-url "http://nodejs.org/docs/"))
(defun coffee-open-github ()
"Open browser to `coffee-mode' project on GithHub."
;; Define Language Syntax
;;
+;; String literals
+(defvar coffee-string-regexp "\"\\([^\\]\\|\\\\.\\)*?\"\\|'\\([^\\]\\|\\\\.\\)*?'")
+
;; Instance variables (implicit this)
-(defvar coffee-this-regexp "@\\w*\\|this")
+(defvar coffee-this-regexp "@\\(\\w\\|_\\)*\\|this")
+
+;; Prototype::access
+(defvar coffee-prototype-regexp "\\(\\(\\w\\|\\.\\|_\\| \\|$\\)+?\\)::\\(\\(\\w\\|\\.\\|_\\| \\|$\\)+?\\):")
;; Assignment
-(defvar coffee-assign-regexp "\\(\\(\\w\\|\\.\\|_\\| \\|$\\)+?\\):")
+(defvar coffee-assign-regexp "\\(\\(\\w\\|\\.\\|_\\|$\\)+?\s*\\):")
;; Lambda
(defvar coffee-lambda-regexp "\\((.+)\\)?\\s *\\(->\\|=>\\)")
(defvar coffee-namespace-regexp "\\b\\(class\\s +\\(\\S +\\)\\)\\b")
;; Booleans
-(defvar coffee-boolean-regexp "\\b\\(true\\|false\\|yes\\|no\\|on\\|off\\)\\b")
+(defvar coffee-boolean-regexp "\\b\\(true\\|false\\|yes\\|no\\|on\\|off\\|null\\)\\b")
;; Regular Expressions
-(defvar coffee-regexp-regexp "\\/.+?\\/")
+(defvar coffee-regexp-regexp "\\/\\(\\\\.\\|\\[\\(\\\\.\\|.\\)+?\\]\\|[^/]\\)+?\\/")
;; JavaScript Keywords
(defvar coffee-js-keywords
'("if" "else" "new" "return" "try" "catch"
"finally" "throw" "break" "continue" "for" "in" "while"
"delete" "instanceof" "typeof" "switch" "super" "extends"
- "class"))
+ "class" "until" "loop"))
;; Reserved keywords either by JS or CS.
(defvar coffee-js-reserved
;; *Note*: order below matters. `coffee-keywords-regexp' goes last
;; because otherwise the keyword "state" in the function
;; "state_entry" would be highlighted.
- `((,coffee-this-regexp . font-lock-variable-name-face)
+ `((,coffee-string-regexp . font-lock-string-face)
+ (,coffee-this-regexp . font-lock-variable-name-face)
+ (,coffee-prototype-regexp . font-lock-variable-name-face)
(,coffee-assign-regexp . font-lock-type-face)
(,coffee-regexp-regexp . font-lock-constant-face)
(,coffee-boolean-regexp . font-lock-constant-face)
(let ((deactivate-mark nil) (comment-start "#") (comment-end ""))
(comment-dwim arg)))
-(defun coffee-command-full ()
- "The full `coffee-command' complete with args."
- (mapconcat 'identity (append (list coffee-command) coffee-command-args) " "))
+(defun coffee-command-compile (file-name)
+ "The `coffee-command' with args to compile a file."
+ (mapconcat 'identity (append (list coffee-command) coffee-args-compile (list file-name)) " "))
;;
;; imenu support
(interactive)
;; This function is called within a `save-excursion' so we're safe.
- (beginning-of-buffer)
+ (goto-char (point-min))
(let ((index-alist '()) assign pos indent ns-name ns-indent)
;; Go through every assignment that includes -> or => on the same
(save-excursion
(forward-line -1)
- (while (coffee-line-empty-p) (forward-line -1))
- (current-indentation)))
+ (if (bobp)
+ 0
+ (progn
+ (while (and (coffee-line-empty-p) (not (bobp))) (forward-line -1))
+ (current-indentation)))))
(defun coffee-line-empty-p ()
"Is this line empty? Returns non-nil if so, nil if not."
;; insert a newline, and indent the newline to the same
;; level as the previous line.
(let ((prev-indent (current-indentation)) (indent-next nil))
+ (delete-horizontal-space t)
(newline)
(insert-tab (/ prev-indent coffee-tab-width))
(defun coffee-indenters-bol-regexp ()
"Builds a regexp out of `coffee-indenters-bol' words."
- (concat "^" (regexp-opt coffee-indenters-bol 'words)))
+ (regexp-opt coffee-indenters-bol 'words))
(defvar coffee-indenters-eol '(?> ?{ ?\[)
"Single characters at the end of a line that mean the next line
;;;###autoload
(define-derived-mode coffee-mode fundamental-mode
- "coffee-mode"
- "Major mode for editing CoffeeScript..."
+ "Coffee"
+ "Major mode for editing CoffeeScript."
;; key bindings
(define-key coffee-mode-map (kbd "A-r") 'coffee-compile-buffer)
(define-key coffee-mode-map (kbd "A-M-r") 'coffee-repl)
(define-key coffee-mode-map [remap comment-dwim] 'coffee-comment-dwim)
(define-key coffee-mode-map "\C-m" 'coffee-newline-and-indent)
+ (define-key coffee-mode-map "\C-c\C-o\C-s" 'coffee-cos-mode)
;; code for syntax highlighting
(setq font-lock-defaults '((coffee-font-lock-keywords)))
;; perl style comment: "# ..."
(modify-syntax-entry ?# "< b" coffee-mode-syntax-table)
(modify-syntax-entry ?\n "> b" coffee-mode-syntax-table)
+ (make-local-variable 'comment-start)
(setq comment-start "#")
;; single quote strings
(modify-syntax-entry ?' "\"" coffee-mode-syntax-table)
- (modify-syntax-entry ?' "\"" coffee-mode-syntax-table)
;; indentation
(make-local-variable 'indent-line-function)
(setq indent-line-function 'coffee-indent-line)
- (setq coffee-tab-width tab-width) ;; Just in case...
+ (set (make-local-variable 'tab-width) coffee-tab-width)
;; imenu
(make-local-variable 'imenu-create-index-function)
;; hooks
(set (make-local-variable 'before-save-hook) 'coffee-before-save))
+;;
+;; Compile-on-Save minor mode
+;;
+
+(defvar coffee-cos-mode-line " CoS")
+(make-variable-buffer-local 'coffee-cos-mode-line)
+
+(define-minor-mode coffee-cos-mode
+ "Toggle compile-on-save for coffee-mode."
+ :group 'coffee-cos :lighter coffee-cos-mode-line
+ (cond
+ (coffee-cos-mode
+ (add-hook 'after-save-hook 'coffee-compile-file nil t))
+ (t
+ (remove-hook 'after-save-hook 'coffee-compile-file t))))
+
(provide 'coffee-mode)
;;