├── .gitignore ├── image └── coffee-mode.png ├── .github └── workflows │ └── test.yml ├── examples ├── edge.coffee ├── imenu.coffee └── basic.coffee ├── Makefile ├── test ├── test-helper.el ├── coffee-syntax.el ├── coffee-imenu.el ├── coffee-private.el ├── coffee-highlight.el └── coffee-command.el ├── Changes ├── README.md └── coffee-mode.el /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | *~ 3 | -------------------------------------------------------------------------------- /image/coffee-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defunkt/coffee-mode/master/image/coffee-mode.png -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | emacs_version: 13 | - 27.2 14 | - 28.2 15 | - 29.1 16 | - snapshot 17 | steps: 18 | - uses: purcell/setup-emacs@master 19 | with: 20 | version: ${{ matrix.emacs_version }} 21 | 22 | - uses: actions/checkout@v4 23 | - name: Run tests 24 | run: | 25 | make test 26 | -------------------------------------------------------------------------------- /examples/edge.coffee: -------------------------------------------------------------------------------- 1 | # Edge cases 2 | 3 | if string.match /\// or string.match /\x1b/ or string.match /a\/b/ 4 | console.log "matched" 5 | 6 | string = "Something with a \"double quote" 7 | console.log string 8 | 9 | string = 'Something with a \'single quote' 10 | console.log string 11 | 12 | heredoc = """ 13 | Heredoc with a " double quote 14 | """ 15 | console.log heredoc 16 | 17 | ### 18 | foo 19 | bar 20 | ### 21 | console.log "foo bar is commented with block comment" 22 | 23 | ### this is 24 | block comment ### 25 | console.log "after block comment" 26 | 27 | ### this is 28 | block comment #### 29 | console.log "after block comment with 4 hash marks" 30 | -------------------------------------------------------------------------------- /examples/imenu.coffee: -------------------------------------------------------------------------------- 1 | # Testing imenu 2 | regexp = /asdas/ 3 | two = 4 / 2 4 | 5 | minus = (x, y) -> x - y 6 | 7 | String::length = -> 10 8 | 9 | class Person 10 | print: -> 11 | print 'My name is ' + this.name + '.' 12 | 13 | app = 14 | window: {width: 200, height: 200} 15 | para: 'Welcome.' 16 | button: 'OK' 17 | 18 | block = -> 19 | print('potion') 20 | 21 | Please = {} 22 | Please.print = (word) -> 23 | print(word) 24 | 25 | HomePage::get = (url) -> 26 | session: url.query.session if url.query? 27 | 28 | class Policeman extends Person 29 | constructor: (rank) -> 30 | @rank: rank 31 | 32 | print: -> 33 | print 'My name is ' + this.name + " and I'm a " + this.rank + '.' 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test test-command test-imenu test-highlight test-syntax test-private 2 | 3 | UNAME_S=$(shell uname -s) 4 | ifeq ($(UNAME_S),Darwin) 5 | EMACS ?= /Applications/Emacs.app/Contents/MacOS/Emacs 6 | else 7 | EMACS ?= emacs 8 | endif 9 | 10 | LOADPATH = -L . 11 | LOAD_HELPER = -l test/test-helper.el 12 | 13 | test: 14 | $(EMACS) -Q -batch $(LOADPATH) $(LOAD_HELPER) \ 15 | -l test/coffee-command.el \ 16 | -l test/coffee-imenu.el -l test/coffee-highlight.el \ 17 | -l test/coffee-private.el -l test/coffee-syntax.el \ 18 | -f ert-run-tests-batch-and-exit 19 | 20 | test-highlight: 21 | $(EMACS) -Q -batch $(LOADPATH) $(LOAD_HELPER) \ 22 | -l test/coffee-highlight.el \ 23 | -f ert-run-tests-batch-and-exit 24 | 25 | test-syntax: 26 | $(EMACS) -Q -batch $(LOADPATH) $(LOAD_HELPER) \ 27 | -l test/coffee-syntax.el \ 28 | -f ert-run-tests-batch-and-exit 29 | 30 | test-command: 31 | $(EMACS) -Q -batch $(LOADPATH) $(LOAD_HELPER) \ 32 | -l test/coffee-command.el \ 33 | -f ert-run-tests-batch-and-exit 34 | 35 | test-imenu: 36 | $(CASK) exec $(EMACS) -Q -batch $(LOADPATH) $(LOAD_HELPER) \ 37 | -l test/coffee-imenu.el \ 38 | -f ert-run-tests-batch-and-exit 39 | 40 | test-private: 41 | $(EMACS) -Q -batch $(LOADPATH) $(LOAD_HELPER) \ 42 | -l test/coffee-private.el \ 43 | -f ert-run-tests-batch-and-exit 44 | -------------------------------------------------------------------------------- /test/test-helper.el: -------------------------------------------------------------------------------- 1 | ;;; test-helper.el --- helper for testing coffee-mode 2 | 3 | ;; Copyright (C) 2016 by Syohei YOSHIDA 4 | 5 | ;; Author: Syohei YOSHIDA 6 | 7 | ;; This program is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;;; Code: 23 | 24 | (require 'coffee-mode) 25 | 26 | (defmacro with-coffee-temp-buffer (code &rest body) 27 | "Insert `code' and enable `coffee-mode'. cursor is beginning of buffer" 28 | (declare (indent 0) (debug t)) 29 | `(with-temp-buffer 30 | (insert ,code) 31 | (goto-char (point-min)) 32 | (coffee-mode) 33 | (font-lock-fontify-buffer) 34 | ,@body)) 35 | 36 | (defun forward-cursor-on (pattern &optional count) 37 | (let ((case-fold-search nil)) 38 | (re-search-forward pattern nil nil (or count 1))) 39 | (goto-char (match-beginning 0))) 40 | 41 | (defun backward-cursor-on (pattern &optional count) 42 | (let ((case-fold-search nil)) 43 | (re-search-backward pattern nil nil (or count 1))) 44 | (goto-char (match-beginning 0))) 45 | 46 | (defun face-at-cursor-p (face) 47 | (eq (face-at-point) face)) 48 | 49 | ;;; test-helper.el ends here 50 | -------------------------------------------------------------------------------- /test/coffee-syntax.el: -------------------------------------------------------------------------------- 1 | ;;; coffee-syntax.el --- Test for syntax of coffee-mode.el 2 | 3 | ;; Copyright (C) 2016 by Syohei YOSHIDA 4 | 5 | ;; Author: Syohei YOSHIDA 6 | 7 | ;; This program is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;;; Code: 23 | 24 | (require 'ert) 25 | (require 'coffee-mode) 26 | 27 | ;; 28 | ;; Treat variables with "_" as word delimiters 29 | ;; 30 | (ert-deftest treat-underscores-within-variables-as-symbols () 31 | "Treat '_' within variable names as word delimiters" 32 | (with-coffee-temp-buffer 33 | "foo_bar" 34 | (forward-word 1) 35 | (forward-cursor-on "_") 36 | (should (not (eobp))) 37 | 38 | (forward-word 1) 39 | (should (eobp)) 40 | 41 | (backward-word 1) 42 | (should (not (bobp))) 43 | (forward-cursor-on "bar") 44 | (backward-cursor-on "foo") 45 | 46 | (backward-word 1) 47 | (forward-cursor-on "foo") 48 | (should (bobp)))) 49 | 50 | ;; 51 | ;; #219 Invalid slash property 52 | ;; 53 | (ert-deftest slash-syntax-property () 54 | "`/' is not treat as close paren" 55 | (with-coffee-temp-buffer 56 | "( / )" 57 | (forward-sexp 1) 58 | (should (eobp)) 59 | 60 | (backward-sexp 1) 61 | (should (bobp)) 62 | 63 | (forward-cursor-on "/") 64 | (backward-up-list) 65 | (should (bobp)))) 66 | 67 | ;;; coffee-syntax.el end here 68 | -------------------------------------------------------------------------------- /examples/basic.coffee: -------------------------------------------------------------------------------- 1 | # These examples are taken from 2 | # http://jashkenas.github.com/coffee-script/ 3 | 4 | song = ["do", "re", "mi", "fa", "so"] 5 | 6 | ages = { 7 | max: 10 8 | ida: 9 9 | tim: 11 10 | } 11 | 12 | matrix = [ 13 | 1, 0, 1 14 | 0, 0, 1 15 | 1, 1, 0 16 | ] 17 | 18 | eldest = if 24 > 21 then "Liz" else "Ike" 19 | 20 | six = (one = 1) + (two = 2) + (three = 3) 21 | 22 | My.mood = greatly_improved if true 23 | 24 | # Unfancy JavaScript 25 | if happy and knows_it 26 | cha_cha_cha() 27 | false 28 | 29 | Account = (customer, cart) -> 30 | @customer: customer 31 | @cart: cart 32 | 33 | $('.shopping_cart').bind 'click', (event) => 34 | @customer.purchase @cart 35 | 36 | class Animal 37 | move: (meters) -> 38 | alert @name + " moved " + meters + "m." 39 | 40 | randomify: -> 41 | @name.replace(/^[\w_-]*$/g, "-") 42 | 43 | class Snake extends Animal 44 | constructor: (name) -> 45 | @name: name 46 | 47 | move: -> 48 | alert "Slithering..." 49 | super 5 50 | 51 | class Horse extends Animal 52 | constructor: (name) -> 53 | @name: name 54 | 55 | move: -> 56 | alert "Galloping..." 57 | super 45 58 | 59 | sam = new Snake "Sammy the Python" 60 | tom = new Horse "Tommy the Palomino" 61 | 62 | sam.move() 63 | tom.move() 64 | if car.speed < speed_limit then accelerate() 65 | 66 | print "My name is " + @name 67 | 68 | gold = silver = the_field = "unknown" 69 | 70 | award_medals = (first, second, rest...) -> 71 | gold: first 72 | silver: second 73 | the_field: rest 74 | 75 | contenders = [ 76 | "Michael Phelps" 77 | "Liu Xiang" 78 | ] 79 | 80 | award_medals contenders... 81 | 82 | alert "Gold: " + gold 83 | alert "Silver: " + silver 84 | alert "The Field: " + the_field 85 | 86 | # Eat lunch. 87 | # what up 88 | # love it. 89 | lunch = eat food for food in ['toast', 'cheese', 'wine'] 90 | 91 | $('#demo').click -> 92 | asd 93 | # sup 94 | # asd 95 | # asdasd 96 | blah = true 97 | 98 | okay 99 | 100 | 101 | # Naive collision detection. 102 | for roid in asteroids 103 | for roid2 in asteroids when roid isnt roid2 104 | roid.explode() if roid.overlaps roid2 105 | 106 | years_old = max: 10, ida: 9, tim: 11 107 | 108 | ages = for child, age of years_old 109 | child + " is " + age 110 | 111 | grade = (student) -> 112 | if student.excellent_work 113 | "A+" 114 | else if student.okay_stuff 115 | if student.tried_hard then "B" else "B-" 116 | else 117 | "C" 118 | 119 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for coffee-mode.el 2 | 3 | Revision v0.6.3 2016/03/29 4 | - Refactoring attributes(#345) 5 | - Improve package comment and meta comment(#344 Thanks Steve Purcell) 6 | - Fix coffee-compile command if buffer/region don't end with newline case(#343) 7 | - Implement auto-fill function(#337, #342 Thanks Danny McClanahan) 8 | - Better indentation(#335, #338) 9 | - Set '--no-header' by default in compiling commands 10 | - Add custom variable for switching compile buffer(#331 Thanks Danny McClanahan) 11 | - Fix lambda expression regexp(#330 Thanks Danny McClanahan) 12 | 13 | Revision v0.6.2 2015/12/10 14 | - Implement python like indentation(#323) 15 | - Fix assignment highlight issue(#326) 16 | - Add toggle arrow function command(#329 Thanks jasonm23) 17 | 18 | Revision v0.6.1 2015/10/01 19 | - Fix byte-compile warning(#308) 20 | - Improve sending multiple lines to REPL(#316, #318 Thanks Danny McClanaha) 21 | - Fix version comparison issue(#321 Reported by crackhopper) 22 | 23 | Revision v0.6.0 2015/03/19 24 | - Fix highlighting regexp literal issue(#302) 25 | - Fix highlighting class member issue(#304) 26 | 27 | Revision v0.5.9 2015/02/23 28 | - Implement highlighting multiple lines regexp literal(Experimental) 29 | - Re-implement string interpolation for string nested string interpolation 30 | - Implement indent region command 31 | - Fix indent command for 'unless' block 32 | - Fix #287, #289(slash after string interpolation breaks syntax highlighting) 33 | - Fix #285(double quote in string interpolation breaks syntax highlighting) 34 | 35 | Revision v0.5.8 2015/01/04 36 | - Fix highlighting boolean keyword(#281 Thanks Wilfred) 37 | - Fix first line indentation 38 | 39 | Revision v0.5.7 2014/12/07 40 | - Improve for tramp 41 | - Fix coffee-comment-dwim same as other major-mode 42 | - Fix highlighting class attribute issue(#272 Thanks no0) 43 | - Fix highlighting issue similar to #272 44 | 45 | Revision v0.5.6 2014/11/06 46 | - Fix not pop-up compiled buffer issue 47 | - Support sending multiple line to REPL 48 | - Improve else, catch, finally indentation 49 | - Support newer coffeescript sourmap generation 50 | - Fix passing parameter to sourcemap.el 51 | - Heighlight 'yield' as keyword 52 | - Correct '_'(underscore) syntax property and remove work-around(Thanks dotemacs) 53 | 54 | Revision v0.5.5 2014/08/04 55 | - Improve compiling command(Thanks Gunnar Wrobel) 56 | 57 | Revision v0.5.4 2014-07-14 58 | - Fix js2coffee-replace-region issue(Thanks Mihir Rege) 59 | 60 | Revision v0.5.3 2014-06-27 61 | - Support CSON file 62 | 63 | Revision v0.5.2 2014-05-25 64 | - Fix new line and indentation issue 65 | 66 | Revision v0.5.1 2014-05-19 67 | - Many bug fixes 68 | - Fix highlighting issues 69 | - Fix defun command 70 | - Fix indentation issue 71 | 72 | Revision v0.5.0 2013-12-14 73 | - Drop Emacs 23 support 74 | - Support block comment and triple quote 75 | - Many bug fix 76 | 77 | Revision v0.4.0 2013-04-21 78 | - Bug fix 79 | 80 | Revision v0.3.0 2013-03-07 81 | - Bug fix 82 | - Add compile command 83 | - Documentation 84 | 85 | Revision v0.2.0 2013-03-07 86 | - Bug fix 87 | - Support imenu 88 | 89 | Revision v0.1.0 2013-03-07 90 | - Initial implements 91 | -------------------------------------------------------------------------------- /test/coffee-imenu.el: -------------------------------------------------------------------------------- 1 | ;;; coffee-imenu.el --- Test for imenu of coffee-mode.el 2 | 3 | ;; Copyright (C) 2016 by Syohei YOSHIDA 4 | 5 | ;; Author: Syohei YOSHIDA 6 | 7 | ;; This program is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;;; Code: 23 | 24 | (require 'ert) 25 | (require 'coffee-mode) 26 | 27 | (ert-deftest class-members () 28 | "Creating class member indice" 29 | (with-coffee-temp-buffer 30 | " 31 | class Person 32 | minus: (x, y) -> x - y 33 | print: => 34 | print 'My name is ' + this.name + '.' 35 | " 36 | (let ((got (coffee-imenu-create-index))) 37 | (should (= (length got) 2)) 38 | (dolist (expected '("Person::minus" "Person::print")) 39 | (should (cl-loop for index in got 40 | for assign = (car index) 41 | thereis (string= assign expected))))))) 42 | 43 | (ert-deftest class-members-with-property () 44 | "Creating class member indice with property (`@')" 45 | (with-coffee-temp-buffer 46 | " 47 | class Person 48 | minus: (x, y) -> x - y 49 | @print: => 50 | print 'My name is ' + this.name + '.' 51 | " 52 | (let ((got (coffee-imenu-create-index))) 53 | (should (= (length got) 2)) 54 | (dolist (expected '("Person::minus" "Person::@print")) 55 | (should (cl-loop for index in got 56 | for assign = (car index) 57 | thereis (string= assign expected))))))) 58 | 59 | (ert-deftest extended-class-members () 60 | "Creating extended class member indice" 61 | (with-coffee-temp-buffer 62 | " 63 | class Policeman extends Person 64 | constructor: (rank) -> 65 | @rank = rank 66 | print: -> 67 | print 'My name is ' + this.name + \" and I'm a \" + this.rank + '.' 68 | " 69 | (let ((got (coffee-imenu-create-index))) 70 | (should (= (length got) 2)) 71 | (dolist (expected '("Policeman::constructor" "Policeman::print")) 72 | (should (cl-loop for index in got 73 | for assign = (car index) 74 | thereis (string= assign expected))))))) 75 | 76 | (ert-deftest object-properties () 77 | "Creating object property indice" 78 | (with-coffee-temp-buffer 79 | " 80 | a = 81 | minus: (x, y) -> x - y 82 | block: -> 83 | print('potion') 84 | " 85 | (let ((got (coffee-imenu-create-index))) 86 | (should (= (length got) 2)) 87 | (dolist (expected '("a.minus" "a.block")) 88 | (should (cl-loop for index in got 89 | for assign = (car index) 90 | thereis (string= assign expected))))))) 91 | 92 | (ert-deftest object-properties-with-braces () 93 | "Creating object property indice with braces" 94 | (with-coffee-temp-buffer 95 | " 96 | a = { 97 | num: 10 98 | minus: (x, y) -> x - y 99 | block: -> 100 | print('potion') 101 | } 102 | " 103 | (let ((got (coffee-imenu-create-index))) 104 | (should (= (length got) 2)) 105 | (dolist (expected '("a.minus" "a.block")) 106 | (should (cl-loop for index in got 107 | for assign = (car index) 108 | thereis (string= assign expected))))))) 109 | 110 | (ert-deftest class-members-and-object-properties () 111 | "Creating class member and object property indice" 112 | (with-coffee-temp-buffer 113 | " 114 | class Foo 115 | constructor: (rank) -> 116 | @rank = rank 117 | print: -> 118 | print 'My name is ' + this.name + \" and I'm a \" + this.rank + '.' 119 | 120 | a = 121 | minus: (x, y) -> x - y 122 | block: -> 123 | print('potion') 124 | " 125 | (let ((got (coffee-imenu-create-index))) 126 | (should (= (length got) 4)) 127 | (dolist (expected '("Foo::constructor" "Foo::print" "a.minus" "a.block")) 128 | (should (cl-loop for index in got 129 | for assign = (car index) 130 | thereis (string= assign expected))))))) 131 | 132 | (ert-deftest class-members-and-named-function () 133 | "Creating class member and named function" 134 | (with-coffee-temp-buffer 135 | " 136 | class Foo 137 | constructor: (rank) -> 138 | @rank = rank 139 | print: -> 140 | print 'My name is ' + this.name + \" and I'm a \" + this.rank + '.' 141 | 142 | named_func = (x, y) -> 143 | x + y 144 | " 145 | (let ((got (coffee-imenu-create-index))) 146 | (should (= (length got) 3)) 147 | (dolist (expected '("Foo::constructor" "Foo::print" "named_func")) 148 | (should (cl-loop for index in got 149 | for assign = (car index) 150 | thereis (string= assign expected))))))) 151 | 152 | (ert-deftest prototype-access-declaration () 153 | "Prototype access function declaration" 154 | (with-coffee-temp-buffer 155 | " 156 | Coffee::foo = (a, b) -> 157 | a + b 158 | 159 | Coffee::bar = 160 | minus: (x, y) -> x - y 161 | block: -> 162 | print('potion') 163 | " 164 | (let ((got (coffee-imenu-create-index))) 165 | (should (= (length got) 3)) 166 | (dolist (expected '("Coffee::foo" "Coffee::bar.minus" "Coffee::bar.block")) 167 | (should (cl-loop for index in got 168 | for assign = (car index) 169 | thereis (string= assign expected))))))) 170 | 171 | ;;; coffee-imenu.el end here 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CoffeeScript Major Mode 2 | ======================= 3 | [![github_actions badge][github-actions-badge]][github-actions-link] [![melpa badge][melpa-badge]][melpa-link] [![melpa stable badge][melpa-stable-badge]][melpa-stable-link] 4 | 5 | An Emacs major mode for [CoffeeScript][cs] and [IcedCoffeeScript][ics]. 6 | 7 | Provides syntax highlighting, indentation support, imenu support, 8 | a menu bar, and a few cute commands. 9 | 10 | ![Screenshot](image/coffee-mode.png) 11 | 12 | 13 | ## Requirement 14 | 15 | - Emacs 24.3 or higher 16 | - CoffeeScript 1.9.3 or higher 17 | 18 | 19 | ## Installation via package.el 20 | 21 | `coffee-mode` is available on [MELPA][melpa] and [MELPA-STABLE][melpa-stable]. 22 | 23 | You can install `coffee-mode` with the following command. 24 | 25 | M-x package-install [RET] coffee-mode [RET] 26 | 27 | **Please do not install [GNU Emacs Lisp Package Archive][elpa] version.** 28 | 29 | It's too old and many features(Block string, block comment etc) are not implemented. 30 | 31 | ## Whitespace 32 | 33 | `coffee-mode` used to offer automatic deletion of trailing whitespace. 34 | This is now left to `whitespace-mode`. See its documentation for full 35 | details, but as a hint, configure: 36 | 37 | ```lisp 38 | ;; automatically clean up bad whitespace 39 | (setq whitespace-action '(auto-cleanup)) 40 | ;; only show bad whitespace 41 | (setq whitespace-style '(trailing space-before-tab indentation empty space-after-tab)) 42 | ``` 43 | 44 | Then turn on `whitespace-mode`, or `global-whitespace-mode`. 45 | 46 | 47 | ## Indentation 48 | 49 | To set the number of spaces used with each additional indentation, add this to your `.emacs` or 50 | `init.el` or other initialization file: 51 | 52 | ```lisp 53 | ;; This gives you a tab of 2 spaces 54 | (custom-set-variables '(coffee-tab-width 2)) 55 | ``` 56 | 57 | `coffee-tab-width` is buffer local variable. You can set indentation size 58 | per buffer by using `File Variables`. 59 | 60 | ```coffee 61 | # Local variables: 62 | # coffee-tab-width: 4 63 | # End: 64 | ``` 65 | 66 | ### Using TAB 67 | 68 | Set `coffee-indent-tabs-mode` t if you want to use **TAB** instead of spaces. 69 | 70 | 71 | ### Move to corresponding point in JavaScript file after compiling 72 | 73 | You can archive this with [sourcemap](https://github.com/syohex/emacs-sourcemap) and 74 | following configuration. 75 | 76 | You can install `sourcemap` package from [MELPA][melpa]. 77 | 78 | ```lisp 79 | ;; generating sourcemap by '-m' option. And you must set '--no-header' option 80 | (setq coffee-args-compile '("-c" "--no-header" "-m")) 81 | (add-hook 'coffee-after-compile-hook 'sourcemap-goto-corresponding-point) 82 | 83 | ;; If you want to remove sourcemap file after jumping corresponding point 84 | (defun my/coffee-after-compile-hook (props) 85 | (sourcemap-goto-corresponding-point props) 86 | (delete-file (plist-get props :sourcemap))) 87 | (add-hook 'coffee-after-compile-hook 'my/coffee-after-compile-hook) 88 | ``` 89 | 90 | ## imenu 91 | 92 | If you're using imenu, `coffee-mode` should work just fine. This 93 | means users of [textmate.el][tm] will find that `⇧⌘T` 94 | (`textmate-go-to-symbol`) mostly works as expected. 95 | 96 | If you're not using imenu check out [this page][im] or textmate.el for 97 | a really awesome way to jump quickly to a function's definition in a 98 | file. 99 | 100 | ## Default Key Bindings 101 | 102 | | Key | Command | 103 | |:---------------------|:----------------------------------------| 104 | | `C-m`, `Return` | Insert newline and indent line | 105 | | `C-c C-<`, `backtab` | Indent line or region to left | 106 | | `C-c C->` | Indent line or region to right | 107 | | `C-M-a` | Move to beginning of defun | 108 | | `C-M-e` | Move to end of defun | 109 | | `C-M-h` | Mark this defun | 110 | | `A-r`, `C-c C-k` | Compile buffer to JavaScript | 111 | | `A-R` | Compile content of region to JavaScript | 112 | | `A-M-r`, `C-c C-z` | Run CoffeeScript REPL | 113 | | `C-c C-l` | Send this line to REPL buffer | 114 | | `C-c C-r` | Send content of region to REPL buffer | 115 | | `C-c C-b` | Send content of buffer to REPL buffer | 116 | | `C-c C-o C-s` | Enable coffee-cos-mode | 117 | 118 | `C-m` and `Return` key insert newline and indentation. If you don't want indentation please overwrite it as below. 119 | 120 | ``` emacs-lisp 121 | (define-key coffee-mode-map (kbd "C-m") 'newline) 122 | ``` 123 | 124 | 125 | ## Commands 126 | 127 | ### easymenu 128 | 129 | If you have `easymenu` you can get to any of these commands from the 130 | menu bar: 131 | 132 | ![coffee-mode menu bar](http://img.skitch.com/20100308-tt5yn51h2jww2pmjqaawed6eq8.png) 133 | 134 | ### coffee-repl 135 | 136 | Launch a CoffeeScript REPL 137 | 138 | ### coffee-compile-file 139 | 140 | Compile buffer to JavaScript. 141 | 142 | ### coffee-compile-buffer 143 | 144 | Compile region to JavaScript 145 | 146 | ### coffee-watch 147 | 148 | Run `coffee` with the `--watch` flag on a directory or file. 149 | 150 | ### coffee-cos-mode 151 | 152 | Minor mode for compiling to JavaScript at save file. 153 | 154 | ### coffee-live-compile-mode 155 | 156 | Minor mode for compiling buffer in real time. 157 | 158 | 159 | ## Customization 160 | 161 | ### Indent like python-mode 162 | 163 | When `coffee-indent-like-python-mode` is non-nil, indent command works like `python-mode`. 164 | I suppose that [Evil](https://bitbucket.org/lyro/evil/wiki/Home)'s `o` and `O` commands 165 | works as you expect with this option. 166 | 167 | ```lisp 168 | (custom-set-variables 169 | '(coffee-indent-like-python-mode t)) 170 | ``` 171 | 172 | ## Sample Configuration 173 | 174 | ```lisp 175 | ;; coffeescript 176 | (custom-set-variables 177 | '(coffee-tab-width 2) 178 | '(coffee-args-compile '("-c" "--no-header" "--bare"))) 179 | 180 | (eval-after-load "coffee-mode" 181 | '(progn 182 | (define-key coffee-mode-map [(meta r)] 'coffee-compile-buffer) 183 | (define-key coffee-mode-map (kbd "C-j") 'coffee-newline-and-indent))) 184 | ``` 185 | 186 | 187 | ## Bugs 188 | 189 | Please file bugs at 190 | 191 | [cs]: http://jashkenas.github.com/coffee-script/ 192 | [ics]: http://maxtaco.github.com/coffee-script/ 193 | [tm]: https://github.com/defunkt/textmate.el 194 | [im]: http://chopmo.blogspot.com/2008/09/quickly-jumping-to-symbols.html 195 | [elpa]: https://elpa.gnu.org/ 196 | [melpa]: https://melpa.org/ 197 | [melpa-stable]: https://stable.melpa.org/ 198 | [github-actions-badge]: https://github.com/defunkt/coffee-mode/workflows/CI/badge.svg 199 | [github-actions-link]: https://github.com/defunkt/coffee-mode/actions 200 | [melpa-link]: https://melpa.org/#/coffee-mode 201 | [melpa-stable-link]: https://stable.melpa.org/#/coffee-mode 202 | [melpa-badge]: https://melpa.org/packages/coffee-mode-badge.svg 203 | [melpa-stable-badge]: https://stable.melpa.org/packages/coffee-mode-badge.svg 204 | -------------------------------------------------------------------------------- /test/coffee-private.el: -------------------------------------------------------------------------------- 1 | ;;; coffee-private.el --- Test for private functions of coffee-mode.el 2 | 3 | ;; Copyright (C) 2016 by Syohei YOSHIDA 4 | 5 | ;; Author: Syohei YOSHIDA 6 | 7 | ;; This program is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;;; Code: 23 | 24 | (require 'ert) 25 | (require 'coffee-mode) 26 | 27 | (ert-deftest coffee-command-compile-without-output-argument () 28 | "`coffee-command-compile' without output argument" 29 | (let ((coffee-command "coffee")) 30 | (let* ((got (coffee-command-compile "foo.coffee" nil)) 31 | (output-dir (expand-file-name default-directory)) 32 | (expected (list "-c" "--no-header" 33 | "-o" output-dir (concat output-dir "foo.coffee")))) 34 | (should (equal got expected))))) 35 | 36 | (ert-deftest coffee-command-compile-with-output-argument () 37 | "`coffee-command-compile' with output argument" 38 | (let ((coffee-command "coffee")) 39 | (let* ((got (coffee-command-compile "foo.coffee" "bar.js")) 40 | (output-dir (expand-file-name default-directory)) 41 | (expected (list "-c" "--no-header" "-j" "bar.js" 42 | "-o" output-dir (concat output-dir "foo.coffee")))) 43 | (should (equal got expected))))) 44 | 45 | (ert-deftest coffee-compiled-file-name () 46 | "`coffee-compiled-file-name' with file name" 47 | (let ((coffee-js-directory "")) 48 | (let ((got (coffee-compiled-file-name "foo.coffee")) 49 | (expected (concat (expand-file-name default-directory) "foo.js"))) 50 | (should (string= got expected))))) 51 | 52 | (ert-deftest coffee-compiled-file-name-with-output-directory-abs () 53 | "`coffee-compiled-file-name' with absolute `coffee-js-directory'" 54 | (let ((coffee-js-directory "/foo/bar")) 55 | (let ((got (coffee-compiled-file-name "baz.coffee")) 56 | (expected "/foo/bar/baz.js")) 57 | (should (string= got expected))))) 58 | 59 | (ert-deftest coffee-compiled-file-name-with-output-directory-abs-slash () 60 | "`coffee-compiled-file-name' with absolute slash `coffee-js-directory'" 61 | (let ((coffee-js-directory "/foo/bar/")) 62 | (let ((got (coffee-compiled-file-name "baz.coffee")) 63 | (expected "/foo/bar/baz.js")) 64 | (should (string= got expected))))) 65 | 66 | (ert-deftest coffee-compiled-file-name-with-output-directory-rel () 67 | "`coffee-compiled-file-name' with relative `coffee-js-directory'" 68 | (let ((coffee-js-directory "foo/bar")) 69 | (let ((got (coffee-compiled-file-name "baz.coffee")) 70 | (expected (concat (expand-file-name default-directory) 71 | "foo/bar/baz.js"))) 72 | (should (string= got expected))))) 73 | 74 | ;; 75 | ;; Comment 76 | ;; 77 | 78 | (ert-deftest previous-line-is-single-line-comment () 79 | "" 80 | (with-coffee-temp-buffer 81 | " 82 | # foo = 10 83 | bar = 10 84 | baz = 20 85 | " 86 | (forward-cursor-on "bar") 87 | (should (coffee-previous-line-is-single-line-comment)) 88 | 89 | (forward-cursor-on "baz") 90 | (should-not (coffee-previous-line-is-single-line-comment)))) 91 | 92 | (ert-deftest previous-line-is-not-single-line-comment () 93 | "" 94 | (with-coffee-temp-buffer 95 | " 96 | ### 97 | #bar = 10 98 | baz = 20 99 | " 100 | (forward-cursor-on "bar") 101 | (should-not (coffee-previous-line-is-single-line-comment)) 102 | 103 | (forward-cursor-on "baz") 104 | (should (coffee-previous-line-is-single-line-comment)))) 105 | 106 | ;; 107 | ;; want new line 108 | ;; 109 | 110 | (ert-deftest wants-indent-within-object () 111 | "want indent within object" 112 | (with-coffee-temp-buffer 113 | " 114 | a = { 115 | foo: 'bar' 116 | } 117 | " 118 | (forward-cursor-on "foo") 119 | (should (coffee-line-wants-indent)))) 120 | 121 | (ert-deftest wants-indent-within-array () 122 | "want indent within array" 123 | (with-coffee-temp-buffer 124 | " 125 | a = [ 126 | 'apple' 127 | ] 128 | " 129 | (forward-cursor-on "apple") 130 | (should (coffee-line-wants-indent)))) 131 | 132 | (ert-deftest wants-indent-within-function () 133 | "want indent within function" 134 | (with-coffee-temp-buffer 135 | " 136 | foo = (arg) -> 137 | arg + 10 138 | " 139 | (forward-cursor-on "10") 140 | (should (coffee-line-wants-indent)))) 141 | 142 | (ert-deftest wants-indent-within-class () 143 | "want indent within class" 144 | (with-coffee-temp-buffer 145 | " 146 | class Foo 147 | @bar = 10 148 | " 149 | (forward-cursor-on "10") 150 | (should (coffee-line-wants-indent)))) 151 | 152 | (ert-deftest wants-indent-within-for () 153 | "want indent within for" 154 | (with-coffee-temp-buffer 155 | " 156 | for i in ['a', 'b', 'c'] 157 | print i 158 | " 159 | (forward-cursor-on "print") 160 | (should (coffee-line-wants-indent)))) 161 | 162 | (ert-deftest wants-indent-within-if () 163 | "want indent within if" 164 | (with-coffee-temp-buffer 165 | " 166 | if true 167 | foo = bar 168 | " 169 | (forward-cursor-on "foo") 170 | (should (coffee-line-wants-indent)))) 171 | 172 | (ert-deftest wants-indent-within-try () 173 | "want indent within try" 174 | (with-coffee-temp-buffer 175 | " 176 | try 177 | undefined_func 178 | catch error 179 | print foo 180 | " 181 | (forward-cursor-on "undefined_func") 182 | (should (coffee-line-wants-indent)))) 183 | 184 | (ert-deftest wants-indent-within-while () 185 | "want indent within while" 186 | (with-coffee-temp-buffer 187 | " 188 | while bar -= 1 189 | foo -= 1 190 | " 191 | (forward-cursor-on "foo") 192 | (should (coffee-line-wants-indent)))) 193 | 194 | (ert-deftest wants-indent-with-multiple-newlines () 195 | "want indent with multiple newlines" 196 | (with-coffee-temp-buffer 197 | " 198 | class Foo 199 | 200 | 201 | 202 | @bar = 1 203 | " 204 | (forward-cursor-on "1") 205 | (should (coffee-line-wants-indent)))) 206 | 207 | ;; 208 | ;; move command utility 209 | ;; 210 | 211 | (ert-deftest skip-line-predicate () 212 | "skip line predicate" 213 | (with-coffee-temp-buffer 214 | " 215 | # comment 216 | 217 | end 218 | " 219 | (forward-cursor-on "comment") 220 | (should (coffee-skip-line-p)) 221 | 222 | (forward-line 1) 223 | (should (coffee-skip-line-p)) 224 | 225 | (forward-cursor-on "end") 226 | (should-not (coffee-skip-line-p)))) 227 | 228 | (ert-deftest skip-forward-line () 229 | "skip line if line is comment or empty" 230 | (with-coffee-temp-buffer 231 | " 232 | # foo 233 | # bar 234 | # baz 235 | boo 236 | end 237 | " 238 | (goto-char (point-min)) 239 | (coffee-skip-forward-lines -1) 240 | (should (= (point) (point-min))) 241 | 242 | (forward-cursor-on "foo") 243 | (coffee-skip-forward-lines +1) 244 | 245 | (should (looking-at "^boo")) 246 | 247 | (goto-char (point-max)) 248 | (coffee-skip-forward-lines +1) 249 | (should (= (point) (point-max))))) 250 | 251 | ;; 252 | ;; Judge block type utility 253 | ;; 254 | 255 | (ert-deftest judge-block-type () 256 | "Judge block type of current-line. This function returns nil 257 | if cursor on line which is beginning of block." 258 | (with-coffee-temp-buffer 259 | " 260 | if bar == 1 261 | do foo1 262 | else if bar == 2 263 | do foo2 264 | else 265 | do foo3 266 | " 267 | (forward-cursor-on "if") 268 | (should-not (coffee--block-type)) 269 | 270 | (forward-cursor-on "else if") 271 | (should (eq (coffee--block-type) 'if-else)) 272 | 273 | (goto-char (line-end-position)) 274 | (forward-cursor-on "else") 275 | (should (eq (coffee--block-type) 'if-else))) 276 | 277 | (with-coffee-temp-buffer 278 | " 279 | try 280 | raise_exception 281 | catch error 282 | console.log error 283 | finally 284 | abort() 285 | " 286 | (forward-cursor-on "try") 287 | (should-not (coffee--block-type)) 288 | 289 | (forward-cursor-on "catch") 290 | (should (eq (coffee--block-type) 'try-catch)) 291 | 292 | (goto-char (line-end-position)) 293 | (forward-cursor-on "finally") 294 | (should (eq (coffee--block-type) 'try-catch)))) 295 | 296 | (ert-deftest map-file-name () 297 | "Source map file name is changed since CoffeeScript 1.8" 298 | (cl-letf (((symbol-function 'coffee--coffeescript-version) 299 | (lambda () "1.7"))) 300 | (let ((got (coffee--map-file-name "/foo/bar.coffee"))) 301 | (should (string= got "/foo/bar.map")))) 302 | 303 | (cl-letf (((symbol-function 'coffee--coffeescript-version) 304 | (lambda () "1.10.0"))) 305 | (let ((got (coffee--map-file-name "/foo/bar.coffee"))) 306 | (should (string= got "/foo/bar.js.map"))))) 307 | 308 | 309 | ;;; coffee-private.el end here 310 | -------------------------------------------------------------------------------- /test/coffee-highlight.el: -------------------------------------------------------------------------------- 1 | ;;; coffee-highlight.el --- Test for highlighting of coffee-mode.el 2 | 3 | ;; Copyright (C) 2016 by Syohei YOSHIDA 4 | 5 | ;; Author: Syohei YOSHIDA 6 | 7 | ;; This program is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;;; Code: 23 | 24 | (require 'ert) 25 | (require 'coffee-mode) 26 | 27 | ;; 28 | ;; This and class member highlight 29 | ;; 30 | 31 | (ert-deftest this-and-instance-variable () 32 | "Highlight `this' and instance variable(`@foo')" 33 | 34 | (dolist (keyword '("this" "@foo" "@foo1234" "@foo_bar" "@FoO")) 35 | (with-coffee-temp-buffer 36 | keyword 37 | (should (face-at-cursor-p 'font-lock-variable-name-face))))) 38 | 39 | (ert-deftest this-and-instance-variable-invalid () 40 | "Invalid `this' and instance variable highlighting" 41 | 42 | (dolist (keyword '("thiss" "@-" "@+")) 43 | (with-coffee-temp-buffer 44 | keyword 45 | (should-not (face-at-cursor-p 'font-lock-variable-name-face))))) 46 | 47 | ;; 48 | ;; Prototype Access 49 | ;; 50 | 51 | (ert-deftest prototype-access () 52 | "Prototype access" 53 | 54 | (with-coffee-temp-buffer 55 | " Foo::bar " 56 | 57 | (forward-cursor-on "Foo") 58 | (should (face-at-cursor-p 'font-lock-type-face)) 59 | 60 | (forward-cursor-on "::") 61 | (should (face-at-cursor-p 'font-lock-type-face)) 62 | (forward-char 1) 63 | (should (face-at-cursor-p 'font-lock-type-face)) 64 | 65 | (forward-cursor-on "bar") 66 | (should-not (face-at-cursor-p 'font-lock-variable-name-face)))) 67 | 68 | (ert-deftest prototype-access-with-underscore () 69 | "Prototype which includes underscore access" 70 | 71 | (with-coffee-temp-buffer 72 | "Foo_Bar::baz" 73 | 74 | (forward-cursor-on "Foo") 75 | (should (face-at-cursor-p 'font-lock-type-face)) 76 | 77 | (forward-cursor-on "Bar") 78 | (should (face-at-cursor-p 'font-lock-type-face)) 79 | 80 | (forward-cursor-on "baz") 81 | (should-not (face-at-cursor-p 'font-lock-variable-name-face)))) 82 | 83 | (ert-deftest prototype-access-nested-access () 84 | "Prototype access" 85 | 86 | (with-coffee-temp-buffer 87 | " Foo::Bar::baz " 88 | 89 | (forward-cursor-on "Foo") 90 | (should (face-at-cursor-p 'font-lock-type-face)) 91 | 92 | (forward-cursor-on "::") 93 | (should (face-at-cursor-p 'font-lock-type-face)) 94 | 95 | (forward-cursor-on "Bar") 96 | (should (face-at-cursor-p 'font-lock-type-face)) 97 | 98 | (forward-cursor-on "baz") 99 | (should-not (face-at-cursor-p 'font-lock-type-face)))) 100 | 101 | (ert-deftest prototype-access-with-dollar () 102 | "Prototype access" 103 | 104 | (with-coffee-temp-buffer 105 | " $Foo::bar " 106 | 107 | (forward-cursor-on "$Foo") 108 | (should (face-at-cursor-p 'font-lock-type-face)) 109 | 110 | (forward-cursor-on "::") 111 | (should (face-at-cursor-p 'font-lock-type-face)) 112 | 113 | (forward-cursor-on "bar") 114 | (should-not (face-at-cursor-p 'font-lock-type-face)))) 115 | 116 | (ert-deftest prototype-access-without-property () 117 | "Prototype without property" 118 | 119 | (with-coffee-temp-buffer 120 | "Foo:: " 121 | 122 | (forward-cursor-on "Foo") 123 | (should (face-at-cursor-p 'font-lock-type-face)) 124 | 125 | (forward-cursor-on "::") 126 | (should (face-at-cursor-p 'font-lock-type-face)))) 127 | 128 | ;; 129 | ;; Assignment 130 | ;; 131 | 132 | (ert-deftest assignment-with-no-spaces () 133 | "assignment with no spaces" 134 | 135 | (with-coffee-temp-buffer 136 | " 137 | foo = 138 | key: value 139 | " 140 | 141 | (forward-cursor-on "key") 142 | (should (face-at-cursor-p 'font-lock-type-face)) 143 | 144 | (forward-cursor-on ":") 145 | (should (face-at-cursor-p 'font-lock-type-face)) 146 | 147 | (forward-cursor-on "value") 148 | (should-not (face-at-cursor-p 'font-lock-type-face)))) 149 | 150 | (ert-deftest assignment-with-spaces () 151 | "assignment with spaces" 152 | 153 | (with-coffee-temp-buffer 154 | " 155 | foo = 156 | key : value 157 | " 158 | 159 | (forward-cursor-on "key") 160 | (should (face-at-cursor-p 'font-lock-type-face)) 161 | 162 | (forward-cursor-on ":") 163 | (should (face-at-cursor-p 'font-lock-type-face)) 164 | 165 | (forward-cursor-on "value") 166 | (should-not (face-at-cursor-p 'font-lock-type-face)))) 167 | 168 | (ert-deftest assignment-with-dollar () 169 | "assignment with `$' sign" 170 | 171 | (with-coffee-temp-buffer 172 | " 173 | foo = 174 | $key: value 175 | " 176 | 177 | (forward-cursor-on "$key") 178 | (should (face-at-cursor-p 'font-lock-type-face)) 179 | 180 | (forward-cursor-on ":") 181 | (should (face-at-cursor-p 'font-lock-type-face)) 182 | 183 | (forward-cursor-on "value") 184 | (should-not (face-at-cursor-p 'font-lock-type-face)))) 185 | 186 | ;; 187 | ;; Local assignment 188 | ;; 189 | 190 | (ert-deftest local-assignment-with-no-spaces () 191 | "local assignment with no spaces" 192 | 193 | (with-coffee-temp-buffer 194 | "foo=10" 195 | 196 | (should (face-at-cursor-p 'font-lock-variable-name-face)) 197 | 198 | (forward-cursor-on "=") 199 | (should-not (face-at-cursor-p 'font-lock-type-face)) 200 | 201 | (forward-cursor-on "10") 202 | (should-not (face-at-cursor-p 'font-lock-type-face)))) 203 | 204 | (ert-deftest local-assignment-with-spaces () 205 | "local assignment with spaces" 206 | 207 | (with-coffee-temp-buffer 208 | "foo = 10" 209 | 210 | (should (face-at-cursor-p 'font-lock-variable-name-face)) 211 | 212 | (forward-cursor-on "=") 213 | (should-not (face-at-cursor-p 'font-lock-type-face)) 214 | 215 | (forward-cursor-on "10") 216 | (should-not (face-at-cursor-p 'font-lock-type-face)))) 217 | 218 | (ert-deftest local-assignment-with-arrow () 219 | "Don't highlight if `>' is following '='" 220 | 221 | (with-coffee-temp-buffer 222 | "foo => 10" 223 | 224 | (should-not (face-at-cursor-p 'font-lock-variable-name-face)))) 225 | 226 | (ert-deftest local-assignment-not-highlight-eqeq () 227 | "Don't highlight left operand of '=='" 228 | 229 | (with-coffee-temp-buffer 230 | "foo == 10" 231 | 232 | (forward-cursor-on "foo") 233 | (should-not (face-at-cursor-p 'font-lock-variable-name-face)))) 234 | 235 | (ert-deftest local-assignment-question-equal () 236 | "Highlight question equal assignment" 237 | 238 | (with-coffee-temp-buffer 239 | "foo = 10 240 | foo ?= 20" 241 | 242 | (forward-cursor-on "foo" 2) 243 | (should (face-at-cursor-p 'font-lock-variable-name-face)))) 244 | 245 | ;; 246 | ;; Lambda expression 247 | ;; 248 | 249 | (ert-deftest lambda-expression-fat-arrow () 250 | "Highlight lambda expression with fat arrow" 251 | 252 | (with-coffee-temp-buffer 253 | "(foo) => 254 | \"bar\"" 255 | 256 | (forward-cursor-on "=>") 257 | (should (face-at-cursor-p 'font-lock-function-name-face)))) 258 | 259 | (ert-deftest lambda-expression-thin-arrow () 260 | "Highlight lambda expression with thin arrow" 261 | 262 | (with-coffee-temp-buffer 263 | "(foo) -> 264 | \"bar\"" 265 | 266 | (forward-cursor-on "->") 267 | (should (face-at-cursor-p 'font-lock-function-name-face)))) 268 | 269 | (ert-deftest lambda-expression-multiple-arguments () 270 | "Highlight lambda expression with multiple arguments" 271 | 272 | (with-coffee-temp-buffer 273 | "(foo, bar) -> 274 | foo + bar" 275 | 276 | (forward-cursor-on "->") 277 | (should (face-at-cursor-p 'font-lock-function-name-face)))) 278 | 279 | ;; 280 | ;; String 281 | ;; 282 | 283 | (ert-deftest string-double-quoted () 284 | "Highlight string double quoted" 285 | 286 | (with-coffee-temp-buffer 287 | " \"foo\" " 288 | 289 | (forward-cursor-on "foo") 290 | (should (face-at-cursor-p 'font-lock-string-face)))) 291 | 292 | (ert-deftest string-double-quoted-with-escape () 293 | "Highlight string double quoted with escape" 294 | 295 | (with-coffee-temp-buffer 296 | " \" \\\"foo\\\" \" " 297 | 298 | (forward-cursor-on "foo") 299 | (should (face-at-cursor-p 'font-lock-string-face)))) 300 | 301 | (ert-deftest string-single-quoted () 302 | "Highlight string single quoted" 303 | 304 | (with-coffee-temp-buffer 305 | " 'foo' " 306 | 307 | (forward-cursor-on "foo") 308 | (should (face-at-cursor-p 'font-lock-string-face)))) 309 | 310 | (ert-deftest string-single-quoted-with-escape () 311 | "Highlight string single quoted with escape" 312 | 313 | (with-coffee-temp-buffer 314 | " ' \\'foo\\' ' \" " 315 | 316 | (forward-cursor-on "foo") 317 | (should (face-at-cursor-p 'font-lock-string-face)))) 318 | 319 | ;; 320 | ;; String Interpolation(#120) 321 | ;; 322 | 323 | (ert-deftest string-interpolation () 324 | "Highlight lambda expression with thin arrow" 325 | 326 | (with-coffee-temp-buffer 327 | " \"foo #{var} bar\" 328 | # #{in_comment} 329 | " 330 | 331 | (forward-cursor-on "foo") 332 | (should (face-at-cursor-p 'font-lock-string-face)) 333 | 334 | (forward-cursor-on "var") 335 | (should (face-at-cursor-p 'font-lock-variable-name-face)) 336 | 337 | (forward-cursor-on "bar") 338 | (should (face-at-cursor-p 'font-lock-string-face)) 339 | 340 | (forward-cursor-on "in_comment") 341 | (should-not (face-at-cursor-p 'font-lock-variable-name-face)) 342 | (should (face-at-cursor-p 'font-lock-comment-face)))) 343 | 344 | ;; 345 | ;; Keywords 346 | ;; 347 | 348 | (ert-deftest keywords-js-keywords () 349 | "Highlight JavaScript keywords" 350 | 351 | (dolist (js-keyword '("if" "else" "new" "return" "try" "catch" 352 | "finally" "throw" "break" "continue" "for" "in" "while" 353 | "delete" "instanceof" "package" "typeof" "switch" "super" "extends" 354 | "class" "until" "loop" "yield")) 355 | (with-coffee-temp-buffer 356 | js-keyword 357 | (should (face-at-cursor-p 'font-lock-keyword-face))))) 358 | 359 | (ert-deftest keywords-js-reserved () 360 | "Highlight JavaScript reserved words" 361 | 362 | (dolist (js-reserved '("case" "default" "do" "function" "var" "void" "with" 363 | "const" "let" "debugger" "enum" "export" "import" "native" 364 | "__extends" "__hasProp")) 365 | (with-coffee-temp-buffer 366 | js-reserved 367 | (should (face-at-cursor-p 'font-lock-keyword-face))))) 368 | 369 | (ert-deftest keywords-coffee-reserved () 370 | "Highlight CoffeeScript reserved words" 371 | 372 | (dolist (cs-reserved '("then" "unless" "and" "or" "is" "own" 373 | "isnt" "not" "of" "by" "when")) 374 | (with-coffee-temp-buffer 375 | cs-reserved 376 | (should (face-at-cursor-p 'font-lock-keyword-face))))) 377 | 378 | (ert-deftest keywords-iced-coffee-keywords () 379 | "Highlight Iced CoffeeScript keywords" 380 | 381 | (dolist (iced-keyword '("await" "defer")) 382 | (with-coffee-temp-buffer 383 | iced-keyword 384 | (should (face-at-cursor-p 'font-lock-keyword-face))))) 385 | 386 | (ert-deftest keywords-js-keywords-property-name () 387 | "Don't highlight property name whose name is JavaScript keywords" 388 | 389 | (dolist (js-keyword '("if" "else" "new" "return" "try" "catch" 390 | "finally" "throw" "break" "continue" "for" "in" "while" 391 | "delete" "instanceof" "typeof" "switch" "super" "extends" 392 | "class" "until" "loop")) 393 | (with-coffee-temp-buffer 394 | (format "foo.%s" js-keyword) 395 | (forward-cursor-on js-keyword) 396 | (should-not (face-at-cursor-p 'font-lock-keyword-face))))) 397 | 398 | ;; 399 | ;; class keywords(#99) 400 | ;; 401 | (ert-deftest class-keyword-highlighting () 402 | "highlight of `class' keywords" 403 | 404 | (with-coffee-temp-buffer 405 | "class Foo\n\nclass Bar" 406 | 407 | (forward-cursor-on "class") 408 | (should (face-at-cursor-p 'font-lock-keyword-face)) 409 | 410 | (forward-cursor-on "Foo") 411 | (should-not (face-at-cursor-p 'font-lock-keyword-face)) 412 | 413 | (forward-cursor-on "class") 414 | (should (face-at-cursor-p 'font-lock-keyword-face)))) 415 | 416 | ;; 417 | ;; Boolean highlight 418 | ;; 419 | 420 | (ert-deftest boolean () 421 | "boolean highlight" 422 | 423 | (dolist (keyword '("true" "false" "yes" "no" "on" "off" "null" "undefined")) 424 | (with-coffee-temp-buffer 425 | keyword 426 | (should (face-at-cursor-p 'font-lock-constant-face))))) 427 | 428 | (ert-deftest boolean-property () 429 | "Don't highlight property name whose name is boolean keyword" 430 | 431 | (dolist (keyword '("true" "false" "yes" "no" "on" "off" "null" "undefined")) 432 | (with-coffee-temp-buffer 433 | (format "foo.%s" keyword) 434 | (forward-cursor-on keyword) 435 | (should-not (face-at-cursor-p 'font-lock-constant-face))))) 436 | 437 | ;; 438 | ;; Single line comment 439 | ;; 440 | 441 | (ert-deftest single-line-comment () 442 | "Highlight single line comment" 443 | 444 | (with-coffee-temp-buffer 445 | " 446 | # single_line_comment 447 | out_of_single_line_comment 448 | " 449 | (forward-cursor-on "single_line_comment") 450 | (should (face-at-cursor-p 'font-lock-comment-face)) 451 | 452 | (forward-cursor-on "out_of_single_line_comment") 453 | (should-not (face-at-cursor-p 'font-lock-comment-face)))) 454 | 455 | ;; 456 | ;; Block Comment Tests(#146, #149) 457 | ;; 458 | 459 | (ert-deftest block-comment-in-comment () 460 | "In block comment" 461 | 462 | (with-coffee-temp-buffer 463 | " 464 | ### 465 | block_comment 466 | ### 467 | " 468 | (forward-cursor-on "block_comment") 469 | (should (face-at-cursor-p 'font-lock-comment-face)))) 470 | 471 | (ert-deftest block-comment-before-comment () 472 | "Before block comment" 473 | (with-coffee-temp-buffer 474 | " 475 | before_comment 476 | ### 477 | block_comment 478 | ### 479 | " 480 | (forward-cursor-on "before_comment") 481 | (should-not (face-at-cursor-p 'font-lock-comment-face)))) 482 | 483 | (ert-deftest block-comment-after-comment () 484 | "After block comment" 485 | (with-coffee-temp-buffer 486 | " 487 | ### 488 | block_comment 489 | ### 490 | after_comment 491 | " 492 | (forward-cursor-on "after_comment") 493 | (should-not (face-at-cursor-p 'font-lock-comment-face)))) 494 | 495 | ;; #190 496 | (ert-deftest block-comment-in-one-line () 497 | "Block comment in one line" 498 | (with-coffee-temp-buffer 499 | " 500 | ### Comment ### 501 | notAComment() 502 | ### Comment ### 503 | " 504 | (forward-cursor-on "Comment") 505 | (should (face-at-cursor-p 'font-lock-comment-face)) 506 | 507 | (forward-cursor-on "notAComment") 508 | (should-not (face-at-cursor-p 'font-lock-comment-face)) 509 | (goto-char (line-end-position)) 510 | 511 | (forward-cursor-on "Comment") 512 | (should (face-at-cursor-p 'font-lock-comment-face)))) 513 | 514 | (ert-deftest block-comment-comment-after-triple-hash () 515 | "Block comment with comment in same line as triple hash" 516 | (with-coffee-temp-buffer 517 | " 518 | ### Comment 519 | notAComment() 520 | ### 521 | after_comment 522 | " 523 | (forward-cursor-on "Comment") 524 | (should (face-at-cursor-p 'font-lock-comment-face)) 525 | 526 | (forward-cursor-on "notAComment") 527 | (should (face-at-cursor-p 'font-lock-comment-face)) 528 | (goto-char (line-end-position)) 529 | 530 | (forward-cursor-on "###") 531 | (should (face-at-cursor-p 'font-lock-comment-face)) 532 | 533 | (forward-cursor-on "after_comment") 534 | (should-not (face-at-cursor-p 'font-lock-comment-face)))) 535 | 536 | ;; #205 537 | (ert-deftest block-comment-end-comment-is-not-beginning-of-line () 538 | "Highlight block comment if comment end is not beginning of line " 539 | (with-coffee-temp-buffer 540 | " 541 | myFunction: (callback) => 542 | ### My block comment that 543 | maybe goes a few lines ### 544 | doStuff 545 | " 546 | (forward-cursor-on "My") 547 | (should (face-at-cursor-p 'font-lock-comment-face)) 548 | 549 | (forward-cursor-on "lines") 550 | (should (face-at-cursor-p 'font-lock-comment-face)) 551 | 552 | (forward-cursor-on "doStuff") 553 | (should-not (face-at-cursor-p 'font-lock-comment-face)))) 554 | 555 | ;; #205 -- more hashes 556 | (ert-deftest block-comment-with-more-hash-marks () 557 | "Highlight block comment whose block ends is more than 3 hash marks" 558 | (with-coffee-temp-buffer 559 | " 560 | myFunction: (callback) => 561 | ### This comment ends with to many hashes #### 562 | doStuff 563 | " 564 | (forward-cursor-on "doStuff") 565 | (should-not (face-at-cursor-p 'font-lock-comment-face)))) 566 | 567 | (ert-deftest block-comment-with-more-hash-marks2 () 568 | "Highlight block comment whose block ends is more than 3 hash marks another case" 569 | (with-coffee-temp-buffer 570 | " 571 | myFunction: (callback) => 572 | ### same 573 | here ##### 574 | doStuff 575 | " 576 | (forward-cursor-on "same") 577 | (should (face-at-cursor-p 'font-lock-comment-face)) 578 | 579 | (forward-cursor-on "here") 580 | (should (face-at-cursor-p 'font-lock-comment-face)) 581 | 582 | (forward-cursor-on "doStuff") 583 | (should-not (face-at-cursor-p 'font-lock-comment-face)))) 584 | 585 | ;; 586 | ;; Regular Expression Tests (#141) 587 | ;; 588 | 589 | (ert-deftest regular-expression-in-regexp () 590 | "Face in regular expression literal" 591 | (with-coffee-temp-buffer 592 | "/foo/" 593 | (forward-cursor-on "foo") 594 | (should (face-at-cursor-p 'font-lock-constant-face)))) 595 | 596 | (ert-deftest regular-expression-after-regexp () 597 | "After regular expression literal" 598 | (with-coffee-temp-buffer 599 | "/foo/ " 600 | (goto-char (point-max)) 601 | (should-not (face-at-cursor-p 'font-lock-constant-face)))) 602 | 603 | (ert-deftest regular-expression-with-double-quote () 604 | "Regular expression with double quote" 605 | (with-coffee-temp-buffer 606 | "/\"foo/ " 607 | (forward-cursor-on "\"") 608 | (should (face-at-cursor-p 'font-lock-constant-face)) 609 | 610 | (forward-cursor-on "foo") 611 | (should (face-at-cursor-p 'font-lock-constant-face)) 612 | 613 | (goto-char (point-max)) 614 | (should-not (face-at-cursor-p 'font-lock-constant-face)))) 615 | 616 | (ert-deftest regular-expression-with-hash-mark () 617 | "Regular expression with hash mark" 618 | (with-coffee-temp-buffer 619 | "/# foo / " 620 | (forward-cursor-on "#") 621 | (should (face-at-cursor-p 'font-lock-constant-face)) 622 | 623 | (forward-cursor-on "foo") 624 | (should (face-at-cursor-p 'font-lock-constant-face)))) 625 | 626 | (ert-deftest regular-expression-with-slash-in-same-line () 627 | "Regular expression with slash in same line" 628 | (with-coffee-temp-buffer 629 | " 630 | bar = replace /foo/ig, \"bar\" 631 | baz = \"\"" 632 | (forward-cursor-on "foo") 633 | (should (face-at-cursor-p 'font-lock-constant-face)) 634 | 635 | (forward-cursor-on "bar") 636 | (should-not (face-at-cursor-p 'font-lock-constant-face)) 637 | (should (face-at-cursor-p 'font-lock-string-face)) 638 | 639 | ;; check for syntax-propertize-function 640 | (forward-cursor-on "baz") 641 | (should-not (face-at-cursor-p 'font-lock-string-face)))) 642 | 643 | (ert-deftest regular-expression-with-escape-slash () 644 | "Regular expression with escape slash" 645 | (with-coffee-temp-buffer 646 | "/foo \\/ bar/" 647 | (forward-cursor-on "foo") 648 | (should (face-at-cursor-p 'font-lock-constant-face)) 649 | 650 | (forward-cursor-on "bar") 651 | (should (face-at-cursor-p 'font-lock-constant-face)))) 652 | 653 | (ert-deftest regular-expression-with-quoted-slash () 654 | "Regular expression with quoted slash" 655 | (with-coffee-temp-buffer 656 | "foo += '/' unless /bar/" 657 | (forward-cursor-on "unless") 658 | (should-not (face-at-cursor-p 'font-lock-constant-face)) 659 | 660 | (forward-cursor-on "bar") 661 | (should (face-at-cursor-p 'font-lock-constant-face)))) 662 | 663 | (ert-deftest regular-expression-with-double-quoted-slash () 664 | "Regular expression with double quoted slash" 665 | (with-coffee-temp-buffer 666 | "foo += \"/\" unless /bar/" 667 | (forward-cursor-on "unless") 668 | (should-not (face-at-cursor-p 'font-lock-constant-face)) 669 | 670 | (forward-cursor-on "bar") 671 | (should (face-at-cursor-p 'font-lock-constant-face)))) 672 | 673 | ;; 674 | ;; Block Strings(#159) 675 | ;; 676 | 677 | (ert-deftest block-strings-dq-simple () 678 | "Double quoted Block Strings in single line" 679 | (with-coffee-temp-buffer 680 | " \"\"\"foo bar\"\"\" block-strings-end" 681 | (forward-cursor-on "foo") 682 | (should (face-at-cursor-p 'font-lock-string-face)) 683 | 684 | (forward-cursor-on "bar") 685 | (should (face-at-cursor-p 'font-lock-string-face)) 686 | 687 | (forward-cursor-on "block-strings-end") 688 | (should-not (face-at-cursor-p 'font-lock-string-face)))) 689 | 690 | (ert-deftest block-strings-dq-contains-double-quote () 691 | "Double quoted Block Strings contains double quote" 692 | (with-coffee-temp-buffer 693 | " \"\"\"\"\"\" block-strings-end" 694 | (forward-cursor-on " 718 | cup of #{data} 719 | 720 | \"\"\" 721 | block-strings-end 722 | " 723 | (forward-cursor-on "html") 724 | (should-not (face-at-cursor-p 'font-lock-string-face)) 725 | 726 | (forward-cursor-on "") 727 | (should (face-at-cursor-p 'font-lock-string-face)) 728 | 729 | (forward-cursor-on "data") 730 | (should (face-at-cursor-p 'font-lock-variable-name-face)) 731 | 732 | (forward-cursor-on "") 733 | (should (face-at-cursor-p 'font-lock-string-face)) 734 | 735 | (forward-cursor-on "block-strings-end") 736 | (should-not (face-at-cursor-p 'font-lock-string-face)) 737 | (should (or (face-at-cursor-p nil) (face-at-cursor-p 'default))))) 738 | 739 | (ert-deftest multiline-block-strings-dq-contains-double-quote () 740 | "Double quoted Block Strings contains double quote among multiple lines" 741 | (with-coffee-temp-buffer 742 | " \"\"\" 743 | 744 | \"\"\" 745 | block-strings-end" 746 | (forward-cursor-on "''' block-strings-end" 809 | (forward-cursor-on " 833 | cup of #{data} 834 | 835 | ''' 836 | block-strings-end 837 | " 838 | (forward-cursor-on "html") 839 | (should-not (face-at-cursor-p 'font-lock-string-face)) 840 | 841 | (forward-cursor-on "") 842 | (should (face-at-cursor-p 'font-lock-string-face)) 843 | 844 | (forward-cursor-on "data") 845 | (should (face-at-cursor-p 'font-lock-variable-name-face)) 846 | 847 | (forward-cursor-on "") 848 | (should (face-at-cursor-p 'font-lock-string-face)) 849 | 850 | (forward-cursor-on "block-strings-end") 851 | (should-not (face-at-cursor-p 'font-lock-string-face)) 852 | (should (or (face-at-cursor-p nil) (face-at-cursor-p 'default))))) 853 | 854 | (ert-deftest block-strings-sq-multiline-contains-double-quote () 855 | "Single quoted block Strings contains double quote among multiple lines" 856 | (with-coffee-temp-buffer 857 | " ''' 858 | 859 | ''' 860 | block-strings-end" 861 | (forward-cursor-on " 884 | ''' 885 | block-strings-end" 886 | (forward-cursor-on " # function 964 | | [-+*/%<>&|^!?=]= # compound assign / compare 965 | | >>>=? # zero-fill right shift 966 | | ([-+:])\1 # doubles 967 | | ([&|<>])\2=? # logic / shift 968 | | \?\. # soak access 969 | | \.{2,3} # range or splat 970 | ) /// 971 | after = 1 972 | " 973 | (forward-cursor-on "\\^ ") 974 | (should (face-at-cursor-p 'font-lock-string-face)) 975 | 976 | (forward-cursor-on "\\?:") 977 | (should (face-at-cursor-p 'font-lock-string-face)) 978 | 979 | (forward-cursor-on "after") 980 | (should-not (face-at-cursor-p 'font-lock-string-face)))) 981 | 982 | ;;; Regression test 983 | 984 | (ert-deftest regression-272 () 985 | "Regression test for #272" 986 | (with-coffee-temp-buffer 987 | " 988 | class Foo 989 | BAR_BAZ: 'foo' 990 | " 991 | (forward-cursor-on "BAR") 992 | (should (face-at-cursor-p 'font-lock-type-face)) 993 | 994 | (forward-cursor-on "BAZ") 995 | (should (face-at-cursor-p 'font-lock-type-face)))) 996 | 997 | (ert-deftest regression-281 () 998 | "Regression test for #281" 999 | (with-coffee-temp-buffer 1000 | " 1001 | if bar 1002 | callback(true, foo) 1003 | else 1004 | callback(false) 1005 | " 1006 | (forward-cursor-on "(true") 1007 | (should-not (face-at-cursor-p 'font-lock-constant-face)) 1008 | 1009 | (forward-cursor-on "(false") 1010 | (should-not (face-at-cursor-p 'font-lock-constant-face)))) 1011 | 1012 | (ert-deftest regression-285 () 1013 | "Regression test for #285" 1014 | (with-coffee-temp-buffer 1015 | "foo = \"#{ '\"' }\" bar" 1016 | (forward-cursor-on "bar") 1017 | (should-not (face-at-cursor-p 'font-lock-string-face)))) 1018 | 1019 | (ert-deftest regression-287 () 1020 | "Regression test for #287" 1021 | (with-coffee-temp-buffer 1022 | " 1023 | testMethod = (id) -> 1024 | req = request.post(\"/api/methods/#{id}/test/\") 1025 | promise = reqPromise req 1026 | promise.then (v) -> 1027 | console.log v 1028 | Stores.method.testResultAdded.dispatch(v) 1029 | true 1030 | " 1031 | (forward-cursor-on "promise") 1032 | (should-not (face-at-cursor-p 'font-lock-string-face)))) 1033 | 1034 | (ert-deftest regression-289 () 1035 | "Regression test for #289" 1036 | (with-coffee-temp-buffer 1037 | "\"/#{foo}/\" neat" 1038 | (forward-cursor-on "neat") 1039 | (should-not (face-at-cursor-p 'font-lock-string-face)))) 1040 | 1041 | (ert-deftest regression-292 () 1042 | "Regression test for #292" 1043 | (with-coffee-temp-buffer 1044 | "\"#{\"#{seriously?}\"}\"" 1045 | (forward-cursor-on "}" 2) 1046 | (should (face-at-cursor-p 'font-lock-variable-name-face)) 1047 | 1048 | (forward-cursor-on "\"") 1049 | (should-not (face-at-cursor-p 'font-lock-comment-face)) 1050 | (should (face-at-cursor-p 'font-lock-string-face)))) 1051 | 1052 | (ert-deftest regression-299 () 1053 | "Broken syntax highlighting inside strings with slashes." 1054 | (with-coffee-temp-buffer 1055 | "'http:\/\/' if true" 1056 | 1057 | (forward-cursor-on "if") 1058 | (should (face-at-cursor-p 'font-lock-keyword-face)))) 1059 | 1060 | (ert-deftest regression-302 () 1061 | "Broken syntax highlighting if regexp has single quote." 1062 | (with-coffee-temp-buffer 1063 | "/Brok'n/ # Broken" 1064 | 1065 | (forward-cursor-on "n") 1066 | (should (face-at-cursor-p 'font-lock-constant-face)) 1067 | 1068 | (forward-cursor-on "#") 1069 | (if (version<= "28" emacs-version) 1070 | (should (face-at-cursor-p 'font-lock-comment-delimiter-face)) 1071 | (should (face-at-cursor-p 'font-lock-comment-face))) 1072 | 1073 | (forward-cursor-on "Broken") 1074 | (should (face-at-cursor-p 'font-lock-comment-face)))) 1075 | 1076 | (ert-deftest regression-304 () 1077 | "Broken syntax highlighting if class member name contains underscore" 1078 | (with-coffee-temp-buffer 1079 | "@proto_classes = builder.build" 1080 | 1081 | (forward-cursor-on "classes") 1082 | (should (face-at-cursor-p 'font-lock-variable-name-face)))) 1083 | 1084 | (ert-deftest regression-330 () 1085 | "Multiple anonymous function expressions" 1086 | (with-coffee-temp-buffer 1087 | "a = (f = (b) ->) ->" 1088 | 1089 | (forward-cursor-on "->") 1090 | (should (face-at-cursor-p 'font-lock-function-name-face)))) 1091 | 1092 | ;;; coffee-highlight.el end here 1093 | -------------------------------------------------------------------------------- /test/coffee-command.el: -------------------------------------------------------------------------------- 1 | ;;; coffee-command.el --- Test for commands of coffee-mode.el 2 | 3 | ;; Copyright (C) 2016 by Syohei YOSHIDA 4 | 5 | ;; Author: Syohei YOSHIDA 6 | 7 | ;; This program is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;;; Code: 23 | 24 | (require 'ert) 25 | (require 'coffee-mode) 26 | 27 | ;; 28 | ;; version 29 | ;; 30 | 31 | (ert-deftest coffee-mode-version () 32 | "Check package version equals to version variable" 33 | (let* ((library (locate-library "coffee-mode")) 34 | (version (with-current-buffer (find-file-noselect library) 35 | (goto-char (point-min)) 36 | (when (re-search-forward "^;; Version: \\([[:digit:].]+\\)$" nil t) 37 | (match-string-no-properties 1))))) 38 | (should (string= version coffee-mode-version)))) 39 | 40 | ;; 41 | ;; tab command(indentation) 42 | ;; 43 | 44 | (ert-deftest indentation-with-tab () 45 | " 46 | line1() 47 | line2() 48 | line3() 49 | ^ 50 | 51 | # Pressing `TAB` will produce the following code: 52 | 53 | line1() 54 | line2() 55 | line3() 56 | ^ 57 | 58 | # Pressing `TAB` again will produce this code: 59 | 60 | line1() 61 | line2() 62 | line3() 63 | ^ 64 | " 65 | 66 | (let ((coffee-tab-width 2)) 67 | (with-coffee-temp-buffer 68 | " 69 | line1() 70 | line2() 71 | line3() 72 | " 73 | (forward-cursor-on "line3") 74 | (call-interactively 'indent-for-tab-command) 75 | 76 | (should (= (current-column) 4)) 77 | 78 | (call-interactively 'indent-for-tab-command) 79 | (should (= (current-column) 0))))) 80 | 81 | ;; 82 | ;; dedent(backspace behavior) 83 | ;; 84 | 85 | (ert-deftest dedent-command-multiple-of-coffee-tab-width () 86 | "dedent - current column is multiple of coffee-tab-width" 87 | (let ((coffee-tab-width 2)) 88 | (with-coffee-temp-buffer 89 | " 90 | line1() 91 | line2() 92 | " 93 | (forward-cursor-on "line2") 94 | (call-interactively 'coffee-dedent-line-backspace) 95 | (should (= (current-column) 0))))) 96 | 97 | (ert-deftest dedent-command-not-multiple-of-coffee-tab-width () 98 | "dedent - current column is not multiple of coffee-tab-width" 99 | (let ((coffee-tab-width 2)) 100 | (with-coffee-temp-buffer 101 | " 102 | line1() 103 | line2() 104 | " 105 | (forward-cursor-on "line2") 106 | (call-interactively 'coffee-dedent-line-backspace) 107 | (should (= (current-column) 2))))) 108 | 109 | (ert-deftest dedent-command-at-not-beginning-of-line () 110 | "dedent - current position is not beginning of line" 111 | (let ((coffee-tab-width 2)) 112 | (with-coffee-temp-buffer 113 | "foo" 114 | (goto-char (point-max)) 115 | (call-interactively 'coffee-dedent-line-backspace) 116 | (should (= (current-column) 2)) 117 | (should (string= (buffer-string) "fo"))))) 118 | 119 | (ert-deftest dedent-command-with-numeric-prefix () 120 | "dedent - with numeric prefix" 121 | (let ((coffee-tab-width 2)) 122 | (with-coffee-temp-buffer 123 | " 124 | line1() 125 | line2() 126 | " 127 | (forward-cursor-on "line2") 128 | (coffee-dedent-line-backspace 2) 129 | (should (= (current-column) 1))))) 130 | 131 | (ert-deftest dedent-command-with-electric-pair-mode () 132 | "dedent - with electric-pair-mode" 133 | (let ((coffee-tab-width 2)) 134 | (with-coffee-temp-buffer 135 | " 136 | line1() 137 | line2() 138 | " 139 | (electric-pair-mode +1) 140 | (forward-cursor-on "line2") 141 | (call-interactively 'coffee-dedent-line-backspace) 142 | (should (= (current-column) 0))) 143 | 144 | (with-coffee-temp-buffer 145 | " 146 | line1() 147 | line2() 148 | " 149 | (electric-pair-mode +1) 150 | (forward-cursor-on "line2") 151 | (call-interactively 'coffee-dedent-line-backspace) 152 | (should (= (current-column) 2))))) 153 | 154 | ;; 155 | ;; indent for else line 156 | ;; 157 | (ert-deftest indent-if-else-else-line () 158 | "Indent for `else' line" 159 | 160 | (let ((coffee-tab-width 2)) 161 | (with-coffee-temp-buffer 162 | " 163 | for a in [1] 164 | for b in [2] 165 | if true 166 | a + b 167 | else 168 | " 169 | (let (if-indent) 170 | (forward-cursor-on "if") 171 | (setq if-indent (current-indentation)) 172 | (forward-cursor-on "else") 173 | (call-interactively 'indent-for-tab-command) 174 | (should (= if-indent (current-indentation))))) 175 | 176 | (let ((coffee-tab-width 2)) 177 | (with-coffee-temp-buffer 178 | " 179 | for a in [1] 180 | for b in [2] 181 | if true 182 | a + b 183 | else 184 | " 185 | (let (if-indent) 186 | (forward-cursor-on "if") 187 | (setq if-indent (current-indentation)) 188 | (forward-cursor-on "else") 189 | (call-interactively 'indent-for-tab-command) 190 | (should (= if-indent (current-indentation)))))))) 191 | 192 | (ert-deftest indent-if-else-else-if-line () 193 | "Indent for `else-if' line" 194 | 195 | (let ((coffee-tab-width 2)) 196 | (with-coffee-temp-buffer 197 | " 198 | for a in [1] 199 | for b in [2] 200 | if true 201 | a + b 202 | else if 203 | " 204 | (let (if-indent) 205 | (forward-cursor-on "if") 206 | (setq if-indent (current-indentation)) 207 | (forward-cursor-on "else") 208 | (call-interactively 'indent-for-tab-command) 209 | (should (= if-indent (current-indentation))))))) 210 | 211 | (ert-deftest indent-if-else-not-indent () 212 | "Don't indent case for `else' line indent" 213 | 214 | (let ((coffee-tab-width 2)) 215 | (with-coffee-temp-buffer 216 | " 217 | if true 218 | a + b 219 | else 220 | " 221 | (forward-cursor-on "else") 222 | (call-interactively 'indent-for-tab-command) 223 | (should (= (current-indentation) 0))))) 224 | 225 | (ert-deftest indent-if-else-not-indent-but-moving-cursor () 226 | "Don't indent but moving cursor for if-else block" 227 | 228 | (let ((coffee-tab-width 2)) 229 | (with-coffee-temp-buffer 230 | " 231 | if true 232 | a + b 233 | else 234 | " 235 | (let (if-indent) 236 | (forward-cursor-on "if") 237 | (setq if-indent (current-indentation)) 238 | (forward-cursor-on "else") 239 | (goto-char (line-beginning-position)) 240 | (call-interactively 'indent-for-tab-command) 241 | (should (= if-indent (current-column))))))) 242 | 243 | (ert-deftest indent-if-else-nested () 244 | "Indent for nested if-else blocks" 245 | 246 | (let ((coffee-tab-width 2)) 247 | (with-coffee-temp-buffer 248 | " 249 | if a 250 | for b in [1] 251 | if c 252 | true 253 | else if b 254 | else 255 | " 256 | (goto-char (point-max)) 257 | (backward-cursor-on "else") 258 | (call-interactively 'indent-for-tab-command) 259 | (should (= (current-indentation) 4)) 260 | (call-interactively 'indent-for-tab-command) 261 | (should (= (current-indentation) 0)))) 262 | 263 | (let ((coffee-tab-width 2)) 264 | (with-coffee-temp-buffer 265 | " 266 | if 1 == 1 267 | for name in ['taro', 'jiro', 'saburo'] 268 | if 2 == 2 269 | true 270 | else 271 | if 3 == 3 272 | true 273 | else if 274 | false 275 | for name in ['hoge'] 276 | if true 277 | 1 + 2 278 | else if 279 | " 280 | (goto-char (point-max)) 281 | (backward-cursor-on "else") 282 | (call-interactively 'indent-for-tab-command) 283 | (should (= (current-indentation) 4)) 284 | (call-interactively 'indent-for-tab-command) 285 | (should (= (current-indentation) 6)) 286 | (call-interactively 'indent-for-tab-command) 287 | (should (= (current-indentation) 10)) 288 | (call-interactively 'indent-for-tab-command) 289 | (should (= (current-indentation) 4))))) 290 | 291 | (ert-deftest indent-if-else-between-functions () 292 | "Don't indent size same as if-else block in another function" 293 | 294 | (let ((coffee-tab-width 2)) 295 | (with-coffee-temp-buffer 296 | " 297 | foo = () -> 298 | if a 299 | true 300 | 301 | bar = () -> 302 | for b in [10] 303 | if b == 10 304 | false 305 | else 306 | " 307 | (forward-cursor-on "else") 308 | (call-interactively 'indent-for-tab-command) 309 | (should (= (current-indentation) 4)) 310 | (call-interactively 'indent-for-tab-command) 311 | (should (= (current-indentation) 4))))) 312 | 313 | (ert-deftest indent-if-else-with-closed-if-else-block () 314 | "Don't indent level same as already closed if-else block" 315 | 316 | (with-coffee-temp-buffer 317 | " 318 | for a in [1] 319 | if true 320 | a 321 | for b in [2] 322 | if true 323 | a + b 324 | else 325 | " 326 | (let (if-indent) 327 | (forward-cursor-on "if" 2) 328 | (setq if-indent (current-indentation)) 329 | (forward-cursor-on "else") 330 | (call-interactively 'indent-for-tab-command) 331 | (should (= if-indent (current-indentation))) 332 | (call-interactively 'indent-for-tab-command) 333 | (should (= if-indent (current-indentation))))) 334 | 335 | (with-coffee-temp-buffer 336 | " 337 | for a in [1] 338 | if true 339 | a 340 | else if true 341 | a 342 | for b in [2] 343 | if true 344 | a + b 345 | else 346 | " 347 | (let (if-indent) 348 | (forward-cursor-on "if" 3) 349 | (setq if-indent (current-indentation)) 350 | (forward-cursor-on "else") 351 | (call-interactively 'indent-for-tab-command) 352 | (should (= if-indent (current-indentation))) 353 | (call-interactively 'indent-for-tab-command) 354 | (should (= if-indent (current-indentation)))))) 355 | 356 | (ert-deftest indent-if-else-in-string () 357 | "Indent for `else' line in string" 358 | 359 | (let ((coffee-tab-width 2)) 360 | (with-coffee-temp-buffer 361 | " 362 | \"\"\" 363 | if true 364 | a + b 365 | else 366 | \"\"\" 367 | " 368 | (forward-cursor-on "else") 369 | (call-interactively 'indent-for-tab-command) 370 | (should-not (= (current-indentation) 0)) 371 | (should (= (current-indentation) coffee-tab-width))))) 372 | 373 | ;; 374 | ;; indent for try-catch-finally block 375 | ;; 376 | (ert-deftest indent-try-catch-cactch-line () 377 | "Indent for `catch' line of try-catch" 378 | 379 | (let ((coffee-tab-width 2)) 380 | (with-coffee-temp-buffer 381 | " 382 | for a in [1] 383 | for b in [2] 384 | try 385 | raise_exception(a, b) 386 | catch 387 | " 388 | (let (try-indent) 389 | (forward-cursor-on "try") 390 | (setq try-indent (current-indentation)) 391 | (forward-cursor-on "catch") 392 | (call-interactively 'indent-for-tab-command) 393 | (should (= try-indent (current-indentation))))))) 394 | 395 | (ert-deftest indent-try-catch-finally-line () 396 | "Indent for `finally' line of try-catch" 397 | 398 | (let ((coffee-tab-width 2)) 399 | (with-coffee-temp-buffer 400 | " 401 | for a in [1] 402 | for b in [2] 403 | try 404 | raise_exception(a, b) 405 | finally 406 | " 407 | (let (try-indent) 408 | (forward-cursor-on "try") 409 | (setq try-indent (current-indentation)) 410 | (forward-cursor-on "finally") 411 | (call-interactively 'indent-for-tab-command) 412 | (should (= try-indent (current-indentation))))))) 413 | 414 | (ert-deftest indent-try-catch-not-indent-but-moving-cursor () 415 | "Don't indent but moving cursor for try-catch block" 416 | 417 | (let ((coffee-tab-width 2)) 418 | (with-coffee-temp-buffer 419 | " 420 | try 421 | raise_exception(1, '2') 422 | catch 423 | " 424 | (let (try-indent) 425 | (forward-cursor-on "try") 426 | (setq try-indent (current-indentation)) 427 | (forward-cursor-on "catch") 428 | (goto-char (line-beginning-position)) 429 | (call-interactively 'indent-for-tab-command) 430 | (should (= try-indent (current-column))))))) 431 | 432 | (ert-deftest indent-try-catch-nested () 433 | "Indent for nested try-catch blocks" 434 | 435 | (let ((coffee-tab-width 2)) 436 | (with-coffee-temp-buffer 437 | " 438 | try 439 | for b in [1] 440 | try c 441 | raise_exception 442 | catch error 443 | console.log error 444 | finally 445 | " 446 | (goto-char (point-max)) 447 | (backward-cursor-on "finally") 448 | (call-interactively 'indent-for-tab-command) 449 | (should (= (current-indentation) 4)) 450 | (call-interactively 'indent-for-tab-command) 451 | (should (= (current-indentation) 0)))) 452 | 453 | (let ((coffee-tab-width 2)) 454 | (with-coffee-temp-buffer 455 | " 456 | try 457 | for name in ['taro', 'jiro', 'saburo'] 458 | try 459 | raise_exception1 460 | catch error 461 | console.log 'dummy' 462 | try 463 | raise_exception2 464 | catch error2 465 | for name in ['hoge'] 466 | try 467 | raise_exception3 468 | finally 469 | " 470 | (goto-char (point-max)) 471 | (backward-cursor-on "finally") 472 | (call-interactively 'indent-for-tab-command) 473 | (should (= (current-indentation) 4)) 474 | (call-interactively 'indent-for-tab-command) 475 | (should (= (current-indentation) 6)) 476 | (call-interactively 'indent-for-tab-command) 477 | (should (= (current-indentation) 10)) 478 | (call-interactively 'indent-for-tab-command) 479 | (should (= (current-indentation) 4))))) 480 | 481 | (ert-deftest indent-try-catch-between-functions () 482 | "Don't indent size same as try-catch block in another function" 483 | 484 | (let ((coffee-tab-width 2)) 485 | (with-coffee-temp-buffer 486 | " 487 | foo = () -> 488 | try 489 | raise_some_exception 490 | catch error 491 | console.log error 492 | 493 | bar = () -> 494 | for i in [1, 2, 3] 495 | try 496 | raise_some_exception2 497 | finally 498 | " 499 | (forward-cursor-on "finally") 500 | (call-interactively 'indent-for-tab-command) 501 | (should (= (current-indentation) 4)) 502 | (call-interactively 'indent-for-tab-command) 503 | (should (= (current-indentation) 4))))) 504 | 505 | (ert-deftest indent-try-catch-with-closed-block () 506 | "Indent try-catch block with already closed try-catch block" 507 | 508 | (with-coffee-temp-buffer 509 | " 510 | for a in [1] 511 | try 512 | raise_exception1 513 | finally 514 | die 'I am dying' 515 | for b in [2] 516 | try 517 | raise_exception2 518 | catch 519 | " 520 | (let (try-indent) 521 | (forward-cursor-on "try" 2) 522 | (setq try-indent (current-indentation)) 523 | (forward-cursor-on "catch") 524 | (call-interactively 'indent-for-tab-command) 525 | (should (= try-indent (current-indentation))) 526 | (call-interactively 'indent-for-tab-command) 527 | (should (= try-indent (current-indentation)))))) 528 | 529 | (ert-deftest first-line-indentation () 530 | "First line indentation" 531 | 532 | (let ((coffee-tab-width 2)) 533 | (with-coffee-temp-buffer 534 | " if true 535 | console.log 'foo' 536 | " 537 | (goto-char (point-min)) 538 | (let ((first-line-indentation (current-indentation))) 539 | (forward-line 1) 540 | (call-interactively 'indent-for-tab-command) 541 | (should (= (current-indentation) 2)) 542 | (call-interactively 'indent-for-tab-command) 543 | (should (= (current-indentation) 4)) 544 | (call-interactively 'indent-for-tab-command) 545 | (should (= (current-indentation) 6)) 546 | (call-interactively 'indent-for-tab-command) 547 | (should (= (current-indentation) 0)))))) 548 | 549 | ;; 550 | ;; enable coffee-indent-tabs-mode 551 | ;; 552 | 553 | (ert-deftest inserting-tab () 554 | "inserting tab when `coffee-indent-tabs-mode' is enable" 555 | 556 | (let ((coffee-tab-width 8) 557 | (coffee-indent-tabs-mode t)) 558 | (with-coffee-temp-buffer 559 | " 560 | line1() 561 | line2() 562 | " 563 | (should coffee-indent-tabs-mode) 564 | (forward-cursor-on "line2") 565 | (call-interactively 'indent-for-tab-command) 566 | (should (= (current-column) 8)) 567 | (should (looking-back "^\t")) 568 | 569 | (call-interactively 'indent-for-tab-command) 570 | (should (= (current-column) 0))))) 571 | 572 | ;; 573 | ;; newline and indent 574 | ;; 575 | 576 | (ert-deftest newline-and-indent () 577 | " 578 | class Animal 579 | ^ 580 | 581 | # Pressing enter would produce the following: 582 | 583 | class Animal 584 | ^ 585 | " 586 | (let ((coffee-tab-width 4)) 587 | (with-coffee-temp-buffer 588 | " 589 | class Animal" 590 | 591 | (goto-char (point-max)) 592 | (call-interactively 'coffee-newline-and-indent) 593 | 594 | (should (= (current-column) 4))))) 595 | 596 | (ert-deftest newline-and-indent-indenters-bol () 597 | "indenters bol keywords" 598 | (let ((coffee-tab-width 4)) 599 | (dolist (keyword '("class" "for" "if" "else" "while" "until" 600 | "try" "catch" "finally" "switch" "when")) 601 | (with-coffee-temp-buffer 602 | (format "\n %s" keyword) 603 | (goto-char (point-max)) 604 | (let ((cur-indent (current-indentation))) 605 | (call-interactively 'coffee-newline-and-indent) 606 | (should (= (current-column) (+ cur-indent coffee-tab-width)))))))) 607 | 608 | (ert-deftest newline-and-indent-indenters-eol () 609 | "indenters eol characters" 610 | (let ((coffee-tab-width 2)) 611 | (with-coffee-temp-buffer 612 | " 613 | kids = 614 | brother: 615 | " 616 | (forward-cursor-on "brother:") 617 | (let ((prev-indent (current-indentation))) 618 | (goto-char (line-end-position)) 619 | (call-interactively 'coffee-newline-and-indent) 620 | (should (= (current-indentation) (+ prev-indent coffee-tab-width))))))) 621 | 622 | (ert-deftest newline-and-indent-not-indenters-bol () 623 | "indenters bol keywords" 624 | (let ((coffee-tab-width 4)) 625 | (dolist (keyword '("new" "return")) 626 | (with-coffee-temp-buffer 627 | (format "\n %s" keyword) 628 | (goto-char (point-max)) 629 | (let ((cur-indent (current-indentation))) 630 | (call-interactively 'coffee-newline-and-indent) 631 | (should (= (current-column) cur-indent))))))) 632 | 633 | (ert-deftest newline-and-indent-deeper () 634 | " 635 | $('#demo').click -> 636 | ^ 637 | 638 | # On enter would produce this: 639 | 640 | $('#demo').click -> 641 | 642 | ^ 643 | " 644 | (let ((coffee-tab-width 2)) 645 | (with-coffee-temp-buffer 646 | " 647 | $('#demo').click ->" 648 | (goto-char (point-max)) 649 | (call-interactively 'coffee-newline-and-indent) 650 | 651 | (should (= (current-column) 2))))) 652 | 653 | ;; 654 | ;; newline and insert comment(#) 655 | ;; 656 | 657 | (ert-deftest insert-hashmark-after-single-line-comment () 658 | "Insert hashmark next line of single line comment" 659 | (with-coffee-temp-buffer 660 | " 661 | # foo 662 | " 663 | (forward-cursor-on "foo") 664 | (goto-char (line-end-position)) 665 | (coffee-newline-and-indent) 666 | 667 | (back-to-indentation) 668 | (should (looking-at "^#")))) 669 | 670 | (ert-deftest not-insert-hashmark-case () 671 | "Don't insert hashmark if previous line is statement comment" 672 | (with-coffee-temp-buffer 673 | " 674 | foo = 10 # bar 675 | " 676 | (forward-cursor-on "bar") 677 | (goto-char (line-end-position)) 678 | (coffee-newline-and-indent) 679 | 680 | (back-to-indentation) 681 | (should-not (looking-at "^#")))) 682 | 683 | (ert-deftest insert-hashmark-after-single-line-comment-not-three-hashmarks () 684 | "Insert hashmark next line of single line comment with not three hashmarks" 685 | (with-coffee-temp-buffer 686 | " 687 | ## foo 688 | 689 | #### 690 | " 691 | (forward-cursor-on "foo") 692 | (goto-char (line-end-position)) 693 | (coffee-newline-and-indent) 694 | 695 | (back-to-indentation) 696 | (should (looking-at "^#")) 697 | 698 | (forward-cursor-on "####") 699 | (goto-char (line-end-position)) 700 | (coffee-newline-and-indent) 701 | 702 | (back-to-indentation) 703 | (should (looking-at "^#")))) 704 | 705 | (ert-deftest not-insert-hashmark-after-block-comment () 706 | "Don't insert hash mark next line of block comment line" 707 | (with-coffee-temp-buffer 708 | " 709 | ### 710 | " 711 | (forward-cursor-on "###") 712 | (goto-char (line-end-position)) 713 | (coffee-newline-and-indent) 714 | 715 | (back-to-indentation) 716 | (should-not (looking-at "^#")))) 717 | 718 | (ert-deftest indent-inserted-comment-newline () 719 | "indent next line comment" 720 | (with-coffee-temp-buffer 721 | " 722 | # foo 723 | " 724 | (forward-cursor-on "foo") 725 | (let ((prev-indent (current-indentation))) 726 | (coffee-newline-and-indent) 727 | (back-to-indentation) 728 | (should (looking-at "#")) 729 | (should-not (zerop (current-indentation))) 730 | (should (= prev-indent (current-indentation)))))) 731 | 732 | (ert-deftest indent-inserted-comment-newline-deep-indent () 733 | "indent next line comment deep indent case" 734 | (with-coffee-temp-buffer 735 | " 736 | # foo 737 | " 738 | (forward-cursor-on "foo") 739 | (let ((prev-indent (current-indentation))) 740 | (coffee-newline-and-indent) 741 | (back-to-indentation) 742 | (should (looking-at "#")) 743 | (should-not (zerop (current-indentation))) 744 | (should (= prev-indent (current-indentation)))))) 745 | 746 | ;; #239 747 | (ert-deftest newline-and-indent-twice-issue () 748 | "indent-new-line twice" 749 | (with-coffee-temp-buffer 750 | " 751 | if true 752 | " 753 | (forward-cursor-on "if") 754 | (goto-char (line-end-position)) 755 | (coffee-newline-and-indent) 756 | (coffee-newline-and-indent) 757 | (should (= (current-indentation) coffee-tab-width)))) 758 | 759 | ;; #239 760 | (ert-deftest newline-and-indent-start-of-block () 761 | "Don't insert indentation after block" 762 | (with-coffee-temp-buffer 763 | " 764 | if true 765 | if true 766 | " 767 | (let ((coffee-tab-width 2)) 768 | (goto-char (point-max)) 769 | (backward-cursor-on "if") 770 | (let ((indent (current-indentation))) 771 | (coffee-newline-and-indent) 772 | (should (= (current-indentation) indent)))))) 773 | 774 | ;; #239 775 | (ert-deftest newline-and-indent-from-bol () 776 | "newline-and-indent from beginning of line" 777 | (with-coffee-temp-buffer 778 | " 779 | if true 780 | if false 781 | 782 | " 783 | (let ((coffee-tab-width 2)) 784 | (forward-cursor-on "false") 785 | (goto-char (line-beginning-position)) 786 | (coffee-newline-and-indent) 787 | (should (= (current-indentation) coffee-tab-width))))) 788 | 789 | (ert-deftest newline-and-indent-not-additional-indent () 790 | "Don't insert indentation after normal line" 791 | (with-coffee-temp-buffer 792 | " 793 | if true 794 | foo = hoge 795 | 796 | " 797 | (let ((coffee-tab-width 2)) 798 | (forward-cursor-on "foo") 799 | (goto-char (line-end-position)) 800 | (coffee-newline-and-indent) 801 | (should (= (current-indentation) coffee-tab-width))))) 802 | 803 | ;; #337 804 | (ert-deftest multiline-dont-add-hash () 805 | "Ensure no hash or other characters are inserted when filling multi-line 806 | comments." 807 | (let ((str " 808 | ### 809 | very very very very very very very very very very very very very long test comment 810 | ### 811 | ") 812 | (fill-column 70)) 813 | (with-coffee-temp-buffer 814 | str 815 | (auto-fill-mode +1) 816 | (re-search-forward "comment$") 817 | (funcall auto-fill-function) 818 | (back-to-indentation) 819 | (should-not (looking-at-p "^#")) 820 | (should (looking-at-p "^test comment"))) 821 | ;; test with point not at end of line 822 | (with-coffee-temp-buffer 823 | str 824 | (auto-fill-mode +1) 825 | (re-search-forward "test") 826 | (funcall auto-fill-function) 827 | (back-to-indentation) 828 | (should-not (looking-at-p "^#")) 829 | (should (looking-at-p "^test comment")))) 830 | ;; test with existing indentation 831 | (let ((fill-column 70)) 832 | (with-coffee-temp-buffer 833 | " 834 | if someTest 835 | do something 836 | ### 837 | this is a multiline comment which spans a lot of text. it is also indented here 838 | ### 839 | " 840 | (auto-fill-mode +1) 841 | (re-search-forward "here$") 842 | (funcall auto-fill-function) 843 | (back-to-indentation) 844 | (should-not (looking-at-p "^#")) 845 | (should (looking-at-p "indented here")) 846 | (should (string= 847 | (buffer-substring-no-properties (point-at-bol) (point)) " "))))) 848 | 849 | ;; #337 850 | (ert-deftest indent-inline-comment-fill () 851 | "Test auto-filling of inline comments; the comment on the next line should 852 | have a hash at the same column as where the above line of code begins." 853 | (let ((str " 854 | if test 855 | someFunction someArgument # inline comment that spans a very very long string of text 856 | ") 857 | (fill-column 70)) 858 | (with-coffee-temp-buffer 859 | str 860 | (auto-fill-mode +1) 861 | (re-search-forward "text$") 862 | (funcall auto-fill-function) 863 | (back-to-indentation) 864 | (should (looking-at-p "# long string of text")) 865 | (let ((cur-col (current-column))) 866 | (forward-line -1) 867 | (move-to-column cur-col) 868 | (should (looking-at-p "someFunction")))) 869 | ;; test with point not at end of line 870 | (with-coffee-temp-buffer 871 | str 872 | (auto-fill-mode +1) 873 | (re-search-forward "string ") 874 | (funcall auto-fill-function) 875 | (back-to-indentation) 876 | (should (looking-at-p "# long string of text")) 877 | (let ((cur-col (current-column))) 878 | (forward-line -1) 879 | (move-to-column cur-col) 880 | (should (looking-at-p "someFunction")))))) 881 | 882 | ;; #337 883 | (ert-deftest indent-full-line-comment-fill () 884 | "Test auto-filling of line comments; the comment on the next line should 885 | have a hash at the same column as where the above line of code begins." 886 | (let ((str " 887 | # inline comment that spans a very very very very very very very long string of text 888 | ") 889 | (fill-column 70)) 890 | (with-coffee-temp-buffer 891 | str 892 | (auto-fill-mode +1) 893 | (re-search-forward "text$") 894 | (funcall auto-fill-function) 895 | (back-to-indentation) 896 | (should (looking-at-p "# string of text")) 897 | (should (= (current-column) 0))) 898 | (with-coffee-temp-buffer 899 | str 900 | (auto-fill-mode +1) 901 | (re-search-forward "string of") 902 | (funcall auto-fill-function) 903 | (back-to-indentation) 904 | (should (looking-at-p "# string of text")) 905 | (should (= (current-column) 0)))) 906 | ;; ensure indentation is maintained 907 | (let ((coffee-tab-width 2) (fill-column 70)) 908 | (with-coffee-temp-buffer 909 | " 910 | if test 911 | # test comment that goes on for a very very very very very very very very long time 912 | " 913 | (auto-fill-mode +1) 914 | (re-search-forward "time$") 915 | (funcall auto-fill-function) 916 | (back-to-indentation) 917 | (should (looking-at-p "# very long time")) 918 | (should (= (current-column) 2))))) 919 | 920 | ;; 921 | ;; indent left 922 | ;; 923 | 924 | (ert-deftest left-indent-single-line () 925 | " 926 | foo 927 | 928 | # Call `call-indent-shift-left' 929 | 930 | foo 931 | " 932 | (let ((coffee-tab-width 2)) 933 | (with-coffee-temp-buffer 934 | " foo" 935 | 936 | (call-interactively 'coffee-indent-shift-left) 937 | (should (= (current-column) 0))))) 938 | 939 | (ert-deftest left-indent-single-line-with-count-parameter () 940 | " 941 | foo 942 | 943 | # Call C-u 3 M-x `call-indent-shift-left' 944 | 945 | foo 946 | " 947 | (let ((coffee-tab-width 2)) 948 | (with-coffee-temp-buffer 949 | " foo" 950 | 951 | (coffee-indent-shift-left (line-beginning-position) (line-end-position) 3) 952 | 953 | (back-to-indentation) 954 | (should (= (current-column) 0))))) 955 | 956 | (ert-deftest left-indent-with-region () 957 | " 958 | foo 959 | bar 960 | 961 | # Set region from beginning of `foo' and end of `bar' then call `call-indent-shift-left' 962 | 963 | foo 964 | bar 965 | " 966 | (let ((coffee-tab-width 2)) 967 | (with-coffee-temp-buffer 968 | " 969 | foo 970 | bar 971 | " 972 | (goto-char (point-min)) 973 | (let ((region-start (point)) 974 | (region-end (point-max))) 975 | (coffee-indent-shift-left region-start region-end) 976 | 977 | (goto-char (point-min)) 978 | (forward-cursor-on "foo") 979 | (should (= (current-column) 0)) 980 | 981 | (forward-cursor-on "bar") 982 | (should (= (current-column) 0)))))) 983 | 984 | (ert-deftest left-indent-for-region-with-count-parameter () 985 | " 986 | foo 987 | bar 988 | 989 | # Set region from beginning of `foo' and end of `bar' then 990 | # call C-u 1 M-x `call-indent-shift-left' 991 | 992 | foo 993 | bar 994 | " 995 | (let ((coffee-tab-width 2)) 996 | (with-coffee-temp-buffer 997 | " 998 | foo 999 | bar 1000 | " 1001 | (goto-char (point-min)) 1002 | (let ((region-start (point)) 1003 | (region-end (point-max))) 1004 | (coffee-indent-shift-left region-start region-end 1) 1005 | 1006 | (goto-char (point-min)) 1007 | (forward-cursor-on "foo") 1008 | (should (= (current-column) 2)) 1009 | 1010 | (forward-cursor-on "bar") 1011 | (should (= (current-column) 3)))))) 1012 | 1013 | ;; 1014 | ;; indent right 1015 | ;; 1016 | 1017 | (ert-deftest right-indent-single-line () 1018 | " 1019 | foo 1020 | 1021 | # Call `call-indent-shift-right' 1022 | 1023 | foo 1024 | " 1025 | (let ((coffee-tab-width 2)) 1026 | (with-coffee-temp-buffer 1027 | "foo" 1028 | 1029 | (call-interactively 'coffee-indent-shift-right) 1030 | 1031 | (back-to-indentation) 1032 | (should (= (current-column) 2))))) 1033 | 1034 | (ert-deftest right-indent-single-line-with-count-parameter () 1035 | " 1036 | foo 1037 | 1038 | # Call C-u 3 M-x `call-indent-shift-right' 1039 | 1040 | foo 1041 | " 1042 | (with-coffee-temp-buffer 1043 | "foo" 1044 | 1045 | (coffee-indent-shift-right (line-beginning-position) (line-end-position) 3) 1046 | 1047 | (back-to-indentation) 1048 | (should (= (current-column) (* 3 coffee-tab-width))))) 1049 | 1050 | (ert-deftest right-indent-with-region () 1051 | " 1052 | foo 1053 | bar 1054 | 1055 | # Set region from beginning of `foo' and end of `bar' then call `call-indent-shift-right' 1056 | 1057 | foo 1058 | bar 1059 | " 1060 | 1061 | (let ((coffee-tab-width 2)) 1062 | (with-coffee-temp-buffer 1063 | " 1064 | foo 1065 | bar 1066 | " 1067 | (goto-char (point-min)) 1068 | (let ((region-start (point)) 1069 | (region-end (point-max))) 1070 | (coffee-indent-shift-right region-start region-end) 1071 | 1072 | (goto-char (point-min)) 1073 | (forward-cursor-on "foo") 1074 | (should (= (current-column) 2)) 1075 | 1076 | (forward-cursor-on "bar") 1077 | (should (= (current-column) 2)))))) 1078 | 1079 | (ert-deftest right-indent-for-region-with-count-parameter () 1080 | " 1081 | foo 1082 | bar 1083 | 1084 | # Set region from beginning of `foo' and end of `bar' then 1085 | # call C-u 3 M-x `call-indent-shift-right' 1086 | 1087 | foo 1088 | bar 1089 | " 1090 | 1091 | (let ((coffee-tab-width 2)) 1092 | (with-coffee-temp-buffer 1093 | " 1094 | foo 1095 | bar 1096 | " 1097 | (goto-char (point-min)) 1098 | (let ((region-start (point)) 1099 | (region-end (point-max))) 1100 | (coffee-indent-shift-right region-start region-end 3) 1101 | 1102 | (goto-char (point-min)) 1103 | (forward-cursor-on "foo") 1104 | (should (= (current-column) (+ 3 (* 3 coffee-tab-width)))) 1105 | 1106 | (forward-cursor-on "bar") 1107 | (should (= (current-column) (+ 2 (* 3 coffee-tab-width)))))))) 1108 | 1109 | ;; 1110 | ;; indent region 1111 | ;; 1112 | 1113 | (ert-deftest indent-region-zero-indent () 1114 | "Indent region for no indentation case" 1115 | 1116 | (let ((coffee-tab-width 2)) 1117 | (with-coffee-temp-buffer 1118 | " 1119 | aa = 10 1120 | bb = 20 1121 | cc = 30 1122 | " 1123 | (coffee-indent-region (point-min) (point-max)) 1124 | 1125 | (goto-char (point-min)) 1126 | 1127 | (forward-line 1) 1128 | (should (= (current-indentation) 0)) 1129 | 1130 | (forward-line 1) 1131 | (should (= (current-indentation) 0)) 1132 | 1133 | (forward-line 1) 1134 | (should (= (current-indentation) 0))))) 1135 | 1136 | (ert-deftest indent-region-wants-indent () 1137 | "Indent region for indent case" 1138 | 1139 | (let ((coffee-tab-width 2)) 1140 | (with-coffee-temp-buffer 1141 | " 1142 | if true 1143 | foo 1144 | " 1145 | (coffee-indent-region (point-min) (point-max)) 1146 | 1147 | (goto-char (point-min)) 1148 | (forward-cursor-on "foo") 1149 | (should (= (current-indentation) coffee-tab-width))))) 1150 | 1151 | (ert-deftest indent-region-wants-indent-nest () 1152 | "Indent region for nested indent case" 1153 | 1154 | (let ((coffee-tab-width 2)) 1155 | (with-coffee-temp-buffer 1156 | " 1157 | if true 1158 | foo 1159 | unless false 1160 | bar 1161 | " 1162 | (coffee-indent-region (point-min) (point-max)) 1163 | 1164 | (goto-char (point-min)) 1165 | (forward-cursor-on "foo") 1166 | (should (= (current-indentation) coffee-tab-width)) 1167 | 1168 | (forward-cursor-on "unless") 1169 | (should (= (current-indentation) coffee-tab-width)) 1170 | 1171 | (let ((cur-block-indent (current-indentation))) 1172 | (forward-cursor-on "bar") 1173 | (should (= (current-indentation) (+ cur-block-indent coffee-tab-width))))))) 1174 | 1175 | ;; 1176 | ;; fill paragraph 1177 | ;; 1178 | 1179 | (ert-deftest fill-paragraph-with-block-comment () 1180 | "Block comment should be preserved if `fill-paragraph' is applied to 1181 | block comment paragraph" 1182 | 1183 | (let ((coffee-tab-width 2)) 1184 | (with-coffee-temp-buffer 1185 | " 1186 | func = -> 1187 | ### 1188 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 1189 | Donec ut tellus et felis vulputate tincidunt. 1190 | ### 1191 | " 1192 | (forward-cursor-on "Lorem") 1193 | (fill-paragraph) 1194 | 1195 | (goto-char (point-min)) 1196 | (forward-cursor-on "###") 1197 | (let ((comment-start-line (buffer-substring-no-properties 1198 | (point) (line-end-position)))) 1199 | (should (string-match-p "###$" comment-start-line)))))) 1200 | 1201 | ;; 1202 | ;; forward/backward defun 1203 | ;; 1204 | 1205 | (ert-deftest beginning-of-defun () 1206 | "Move to beginning of defun" 1207 | 1208 | (with-coffee-temp-buffer 1209 | " 1210 | foo: (apple, orange) -> 1211 | apple + orange 1212 | " 1213 | (forward-cursor-on "orange") 1214 | (coffee-beginning-of-defun) 1215 | 1216 | (should (looking-at "^foo:")))) 1217 | 1218 | (ert-deftest beginning-of-defun-same-line-not-bol () 1219 | "Move to beginning of defun in same line but cursor is not beginning of line" 1220 | 1221 | (with-coffee-temp-buffer 1222 | " 1223 | foo: (apple, orange) -> 1224 | apple + orange 1225 | " 1226 | (forward-cursor-on "->") 1227 | (coffee-beginning-of-defun) 1228 | 1229 | (should (looking-at "^foo:")))) 1230 | 1231 | (ert-deftest end-of-defun () 1232 | "Move to end of defun" 1233 | 1234 | (with-coffee-temp-buffer 1235 | " 1236 | foo: (apple, orange) -> 1237 | apple + orange 1238 | " 1239 | (forward-cursor-on "foo") 1240 | (coffee-end-of-block) 1241 | (should (eobp)))) 1242 | 1243 | (ert-deftest end-of-defun-multiple-defuns () 1244 | "Move to end of defun" 1245 | 1246 | (with-coffee-temp-buffer 1247 | " 1248 | foo: (apple) -> 1249 | apple + 10 1250 | 1251 | bar: (melon) -> 1252 | melon + 20 1253 | " 1254 | (forward-cursor-on "foo") 1255 | (coffee-end-of-block) 1256 | 1257 | (save-excursion 1258 | (forward-line 1) 1259 | (should (looking-at "^bar:"))) 1260 | 1261 | (coffee-end-of-block) 1262 | (should (eobp)))) 1263 | 1264 | (ert-deftest moving-by-defun-for-nested-defun () 1265 | "Move by defun for nested defun" 1266 | 1267 | (with-coffee-temp-buffer 1268 | " 1269 | class Foo 1270 | constructor: (name, age) -> 1271 | @name = name 1272 | @age = age 1273 | 1274 | hear: (regex, callback) -> 1275 | register regex, callback, 99 1276 | " 1277 | (forward-cursor-on "99") 1278 | 1279 | (coffee-beginning-of-defun) 1280 | (should (looking-at "hear:")) 1281 | 1282 | (coffee-beginning-of-defun) 1283 | (should (looking-at "constructor:")) 1284 | 1285 | (save-excursion 1286 | (coffee-end-of-block) 1287 | (forward-line 1) 1288 | (should (looking-at "^\\s-+hear:"))) 1289 | 1290 | (coffee-beginning-of-defun) 1291 | (should (looking-at "^class")) 1292 | 1293 | (coffee-beginning-of-defun) 1294 | (should (bobp)) 1295 | 1296 | (coffee-end-of-block) 1297 | (should (eobp)))) 1298 | 1299 | ;; 1300 | ;; mark defun 1301 | ;; 1302 | 1303 | (ert-deftest mark-defun () 1304 | "Mark-defun" 1305 | 1306 | (with-coffee-temp-buffer 1307 | " 1308 | human: (name, age) -> 1309 | @name = name 1310 | @age = age 1311 | " 1312 | (forward-cursor-on "human") 1313 | (let ((start (point))) 1314 | (let ((this-command 'coffee-mark-defun)) 1315 | (coffee-mark-defun)) 1316 | (should (= (region-beginning) start)) 1317 | (should (= (region-end) (point-max)))))) 1318 | 1319 | (ert-deftest mark-defun-with-no-argument () 1320 | "Mark-defun for no argument function" 1321 | 1322 | (with-coffee-temp-buffer 1323 | " 1324 | human: () -> 1325 | @name = 'Alice' 1326 | @age = 49 1327 | " 1328 | (forward-cursor-on "human") 1329 | (let ((start (point))) 1330 | (let ((this-command 'coffee-mark-defun)) 1331 | (coffee-mark-defun)) 1332 | (should (= (region-beginning) start)) 1333 | (should (= (region-end) (point-max)))))) 1334 | 1335 | (ert-deftest mark-defun-with-no-spaces () 1336 | "Mark-defun for no space function" 1337 | 1338 | (with-coffee-temp-buffer 1339 | " 1340 | human:(name,age)-> 1341 | @name = name 1342 | @age = age 1343 | " 1344 | (forward-cursor-on "human") 1345 | (let ((start (point))) 1346 | (let ((this-command 'coffee-mark-defun)) 1347 | (coffee-mark-defun)) 1348 | (should (= (region-beginning) start)) 1349 | (should (= (region-end) (point-max)))))) 1350 | 1351 | (ert-deftest mark-defun-for-nested-defun () 1352 | "Move by defun for nested defun" 1353 | 1354 | (with-coffee-temp-buffer 1355 | " 1356 | class Foo 1357 | constructor: (name, age) -> 1358 | @name = name 1359 | @age = age 1360 | 1361 | hear: (regex, callback) -> 1362 | register regex, callback, 99 1363 | " 1364 | (forward-cursor-on "class") 1365 | (let ((start (point))) 1366 | (let ((this-command 'coffee-mark-defun)) 1367 | (call-interactively 'coffee-mark-defun)) 1368 | (should (= (region-beginning) start)) 1369 | (should (= (region-end) (point-max)))) 1370 | 1371 | 1372 | (forward-cursor-on "hear:") 1373 | (let ((expected-start (point)) 1374 | (expected-end (save-excursion 1375 | (forward-line +1) 1376 | (goto-char (line-end-position)) 1377 | (1+ (point))))) 1378 | (goto-char (line-end-position)) 1379 | (let ((this-command 'coffee-mark-defun)) 1380 | (call-interactively 'coffee-mark-defun)) 1381 | (should (= (region-beginning) expected-start)) 1382 | (should (= (region-end) expected-end))))) 1383 | 1384 | (ert-deftest move-defun-commands-with-prototype-access () 1385 | "Move defun commands with prototype access" 1386 | 1387 | (with-coffee-temp-buffer 1388 | " 1389 | Foo::bar::baz = (apple, orange) -> 1390 | apple + orange 1391 | 1392 | add = (a, b) -> 1393 | a + b 1394 | " 1395 | (forward-cursor-on "Foo") 1396 | (coffee-end-of-block) 1397 | 1398 | (save-excursion 1399 | (forward-line 1) 1400 | (should (looking-at "^add"))) 1401 | 1402 | (save-excursion 1403 | (coffee-beginning-of-defun) 1404 | (looking-at "^Foo")) 1405 | 1406 | (coffee-end-of-block) 1407 | (should (eobp)))) 1408 | 1409 | (ert-deftest move-defun-commands-with-toplevel-assignments () 1410 | "Move defun commands with toplevel assignmentsprototype access" 1411 | 1412 | (with-coffee-temp-buffer 1413 | " 1414 | Foo::bar::baz = (apple, orange) -> 1415 | apple + orange 1416 | 1417 | value = 10 1418 | " 1419 | (forward-cursor-on "Foo") 1420 | (coffee-end-of-block) 1421 | 1422 | (save-excursion 1423 | (forward-line 1) 1424 | (should (looking-at "^value"))) 1425 | 1426 | (save-excursion 1427 | (coffee-beginning-of-defun) 1428 | (looking-at "^Foo")) 1429 | 1430 | (coffee-end-of-block) 1431 | (should (eobp)))) 1432 | 1433 | (ert-deftest regression-test-270 () 1434 | "Regression test for #270" 1435 | 1436 | (with-coffee-temp-buffer 1437 | " 1438 | a = 10 1439 | b = 20 1440 | " 1441 | (forward-cursor-on "a") 1442 | (call-interactively 'set-mark-command) 1443 | (goto-char (point-max)) 1444 | (call-interactively 'coffee-comment-dwim) 1445 | (should (not mark-active)))) 1446 | 1447 | (ert-deftest indent-same-as-python-mode () 1448 | "Move defun commands with toplevel assignmentsprototype access" 1449 | 1450 | (with-coffee-temp-buffer 1451 | " 1452 | if 1 == 1 1453 | if 2 == 2 1454 | if 3 == 3 1455 | if 4 == 4 1456 | if 5 == 5 1457 | foo 1458 | " 1459 | (let ((coffee-tab-width 2) 1460 | (coffee-indent-like-python-mode t)) 1461 | (forward-cursor-on "foo") 1462 | (call-interactively 'indent-for-tab-command) 1463 | (let ((prev-indent (save-excursion 1464 | (forward-line -1) 1465 | (current-indentation)))) 1466 | (should (= (current-indentation) (+ prev-indent coffee-tab-width))) 1467 | (let ((curindent (current-indentation))) 1468 | (call-interactively 'beginning-of-line) 1469 | (call-interactively 'delete-horizontal-space) 1470 | (call-interactively 'coffee-indent-line) 1471 | (call-interactively 'coffee-indent-line) 1472 | (should (= (current-indentation) (- curindent coffee-tab-width))) 1473 | 1474 | ;; wrap around 1475 | (call-interactively 'beginning-of-line) 1476 | (call-interactively 'delete-horizontal-space) 1477 | (call-interactively 'coffee-indent-line) 1478 | (call-interactively 'coffee-indent-line) 1479 | (call-interactively 'coffee-indent-line) 1480 | (call-interactively 'coffee-indent-line) 1481 | (call-interactively 'coffee-indent-line) 1482 | (call-interactively 'coffee-indent-line) 1483 | (call-interactively 'coffee-indent-line) 1484 | (should (= (current-indentation) curindent)))))) 1485 | 1486 | (with-coffee-temp-buffer 1487 | " 1488 | if 1 == 1 1489 | if 2 == 2 1490 | a = 10 1491 | foo 1492 | " 1493 | (let ((coffee-tab-width 2) 1494 | (coffee-indent-like-python-mode t)) 1495 | (forward-cursor-on "foo") 1496 | (call-interactively 'indent-for-tab-command) 1497 | (let ((prev-indent (save-excursion 1498 | (forward-line -1) 1499 | (current-indentation)))) 1500 | (should (= prev-indent (current-indentation))))))) 1501 | 1502 | (ert-deftest toggle-arrow-function () 1503 | "Toggle arrow function between '-' and '='" 1504 | 1505 | (with-coffee-temp-buffer 1506 | " 1507 | foo = () -> 1508 | console.log 'hello' 1509 | " 1510 | (forward-cursor-on "hello") 1511 | (coffee-toggle-fatness) 1512 | (forward-line -1) 1513 | (goto-char (line-end-position)) 1514 | (should (looking-back "=>")) 1515 | 1516 | (forward-cursor-on "hello") 1517 | (coffee-toggle-fatness) 1518 | (forward-line -1) 1519 | (goto-char (line-end-position)) 1520 | (should (looking-back "->")))) 1521 | 1522 | (ert-deftest regression-350-if-then-oneline () 1523 | "https://github.com/defunkt/coffee-mode/issues/350" 1524 | 1525 | (let ((coffee-tab-width 2)) 1526 | (with-coffee-temp-buffer 1527 | " 1528 | if x then y 1529 | 1530 | switch x 1531 | when y then z 1532 | new_line = 'here' 1533 | " 1534 | (forward-cursor-on "if") 1535 | (goto-char (line-end-position)) 1536 | (call-interactively #'coffee-newline-and-indent) 1537 | (should (= (current-indentation) 0)) 1538 | 1539 | (forward-cursor-on "when") 1540 | (let ((prev-indent (current-indentation))) 1541 | (goto-char (line-end-position)) 1542 | (call-interactively #'coffee-newline-and-indent) 1543 | (should (= (current-indentation) prev-indent)))))) 1544 | 1545 | ;;; coffee-command.el end here 1546 | -------------------------------------------------------------------------------- /coffee-mode.el: -------------------------------------------------------------------------------- 1 | ;;; coffee-mode.el --- Major mode for CoffeeScript code -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2010 Chris Wanstrath 4 | 5 | ;; Version: 0.6.3 6 | ;; Keywords: CoffeeScript major mode 7 | ;; Author: Chris Wanstrath 8 | ;; URL: http://github.com/defunkt/coffee-mode 9 | ;; Package-Requires: ((emacs "24.3")) 10 | 11 | ;; This file is not part of GNU Emacs. 12 | 13 | ;; This program is free software; you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation; either version 2, or (at your option) 16 | ;; any later version. 17 | 18 | ;; This program is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with this program; if not, write to the Free Software 25 | ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 26 | 27 | ;;; Commentary: 28 | 29 | ;; Provides syntax highlighting, indentation support, imenu support, 30 | ;; compiling to JavaScript, REPL, a menu bar, and a few cute commands. 31 | 32 | ;;; Code: 33 | 34 | (require 'comint) 35 | (require 'easymenu) 36 | (require 'font-lock) 37 | (require 'rx) 38 | 39 | (require 'cl-lib) 40 | 41 | (declare-function tramp-file-name-localname "tramp") 42 | (declare-function tramp-dissect-file-name "tramp") 43 | 44 | ;; 45 | ;; Customizable Variables 46 | ;; 47 | 48 | (defconst coffee-mode-version "0.6.3" 49 | "The version of `coffee-mode'.") 50 | 51 | (defgroup coffee nil 52 | "A CoffeeScript major mode." 53 | :group 'languages) 54 | 55 | (defcustom coffee-tab-width tab-width 56 | "The tab width to use when indenting." 57 | :type 'integer 58 | :safe 'integerp) 59 | 60 | (defcustom coffee-command "coffee" 61 | "The CoffeeScript command used for evaluating code." 62 | :type 'string) 63 | 64 | (defcustom coffee-js-directory "" 65 | "The directory for compiled JavaScript files output. This can 66 | be an absolute path starting with a `/`, or it can be path 67 | relative to the directory containing the coffeescript sources to 68 | be compiled." 69 | :type 'string) 70 | 71 | (defcustom js2coffee-command "js2coffee" 72 | "The js2coffee command used for evaluating code." 73 | :type 'string) 74 | 75 | (defcustom coffee-args-repl '("-i") 76 | "The arguments to pass to `coffee-command' to start a REPL." 77 | :type '(repeat string)) 78 | 79 | (defcustom coffee-args-compile '("-c" "--no-header") 80 | "The arguments to pass to `coffee-command' to compile a file." 81 | :type '(repeat string)) 82 | 83 | (defcustom coffee-compiled-buffer-name "*coffee-compiled*" 84 | "The name of the scratch buffer used for compiled CoffeeScript." 85 | :type 'string) 86 | 87 | (defcustom coffee-repl-buffer "*CoffeeREPL*" 88 | "The name of the CoffeeREPL buffer." 89 | :type 'string) 90 | 91 | (defcustom coffee-compile-jump-to-error t 92 | "Whether to jump to the first error if compilation fails. 93 | Since the coffee compiler does not always include a line number in 94 | its error messages, this is not always possible." 95 | :type 'boolean) 96 | 97 | (defcustom coffee-watch-buffer-name "*coffee-watch*" 98 | "The name of the scratch buffer used when using the --watch flag 99 | with CoffeeScript." 100 | :type 'string) 101 | 102 | (defcustom coffee-mode-hook nil 103 | "Hook called by `coffee-mode'. Examples: 104 | 105 | ;; Compile '.coffee' files on every save 106 | (and (file-exists-p (buffer-file-name)) 107 | (file-exists-p (coffee-compiled-file-name)) 108 | (coffee-cos-mode t)))" 109 | :type 'hook) 110 | 111 | (defcustom coffee-indent-tabs-mode nil 112 | "Indentation can insert tabs if this is t." 113 | :type 'boolean) 114 | 115 | (defcustom coffee-show-mode 'js-mode 116 | "Major mode to used to show the compiled Javascript." 117 | :type 'function) 118 | 119 | (defcustom coffee-after-compile-hook nil 120 | "Hook called after compile to Javascript" 121 | :type 'hook) 122 | 123 | (defcustom coffee-indent-like-python-mode nil 124 | "Indent like python-mode." 125 | :type 'boolean) 126 | 127 | (defcustom coffee-switch-to-compile-buffer nil 128 | "Switch to compilation buffer `coffee-compiled-buffer-name' after compiling 129 | a buffer or region." 130 | :type 'boolean) 131 | 132 | (defvar coffee-mode-map 133 | (let ((map (make-sparse-keymap))) 134 | ;; key bindings 135 | (define-key map (kbd "A-r") 'coffee-compile-buffer) 136 | (define-key map (kbd "C-c C-k") 'coffee-compile-buffer) 137 | (define-key map (kbd "A-R") 'coffee-compile-region) 138 | (define-key map (kbd "A-M-r") 'coffee-repl) 139 | (define-key map (kbd "C-c C-z") 'coffee-repl) 140 | (define-key map [remap comment-dwim] 'coffee-comment-dwim) 141 | (define-key map [remap newline-and-indent] 'coffee-newline-and-indent) 142 | (define-key map "\C-m" 'coffee-newline-and-indent) 143 | (define-key map "\C-c\C-o\C-s" 'coffee-cos-mode) 144 | (define-key map "\177" 'coffee-dedent-line-backspace) 145 | (define-key map (kbd "C-c C-<") 'coffee-indent-shift-left) 146 | (define-key map (kbd "C-c C->") 'coffee-indent-shift-right) 147 | (define-key map (kbd "C-c C-l") 'coffee-send-line) 148 | (define-key map (kbd "C-c C-r") 'coffee-send-region) 149 | (define-key map (kbd "C-c C-b") 'coffee-send-buffer) 150 | (define-key map (kbd "") 'coffee-indent-shift-left) 151 | (define-key map (kbd "C-M-a") 'coffee-beginning-of-defun) 152 | (define-key map (kbd "C-M-e") 'coffee-end-of-block) 153 | (define-key map (kbd "C-M-h") 'coffee-mark-defun) 154 | map) 155 | "Keymap for CoffeeScript major mode.") 156 | 157 | (defvar coffee--process nil) 158 | 159 | ;; 160 | ;; Commands 161 | ;; 162 | 163 | (defun coffee-comint-filter (string) 164 | (ansi-color-apply 165 | (replace-regexp-in-string 166 | "\uFF00" "\n" 167 | (replace-regexp-in-string "\x1b\\[.[GJK]" "" string)))) 168 | 169 | (defun coffee-repl () 170 | "Launch a CoffeeScript REPL using `coffee-command' as an inferior mode." 171 | (interactive) 172 | 173 | (unless (comint-check-proc coffee-repl-buffer) 174 | (set-buffer 175 | (apply 'make-comint "CoffeeREPL" 176 | "env" 177 | nil 178 | "NODE_NO_READLINE=1" 179 | coffee-command 180 | coffee-args-repl)) 181 | ;; Workaround for ansi colors 182 | (add-hook 'comint-preoutput-filter-functions 'coffee-comint-filter nil t)) 183 | 184 | (pop-to-buffer coffee-repl-buffer)) 185 | 186 | (defun coffee-compiled-file-name (&optional filename) 187 | ;; Returns the name of the JavaScript file compiled from a CoffeeScript file. 188 | ;; If FILENAME is omitted, the current buffer's file name is used. 189 | (let ((input (expand-file-name (or filename (buffer-file-name))))) 190 | (unless (string= coffee-js-directory "") 191 | (setq input 192 | (expand-file-name 193 | (concat (unless (file-name-absolute-p coffee-js-directory) 194 | (file-name-directory input)) 195 | (file-name-as-directory coffee-js-directory) 196 | (file-name-nondirectory input))))) 197 | (concat (file-name-sans-extension input) ".js"))) 198 | 199 | (defun coffee-revert-buffer-compiled-file (file-name) 200 | "Revert a buffer of compiled file when the buffer exist and is not modified." 201 | (let ((buffer (find-buffer-visiting file-name))) 202 | (when (and buffer (not (buffer-modified-p buffer))) 203 | (with-current-buffer buffer 204 | (revert-buffer nil t))))) 205 | 206 | (defun coffee-parse-error-output (compiler-errstr) 207 | (let* ((msg (car (split-string compiler-errstr "[\n\r]+"))) 208 | line column) 209 | (message msg) 210 | (when (or (string-match "on line \\([0-9]+\\)" msg) 211 | (string-match ":\\([0-9]+\\):\\([0-9]+\\): error:" msg)) 212 | (setq line (string-to-number (match-string 1 msg))) 213 | (when (match-string 2 msg) 214 | (setq column (string-to-number (match-string 2 msg)))) 215 | 216 | (when coffee-compile-jump-to-error 217 | (goto-char (point-min)) 218 | (forward-line (1- line)) 219 | (when column 220 | (move-to-column (1- column))))))) 221 | 222 | (defun coffee-compile-file () 223 | "Compiles and saves the current file to disk in a file of the same 224 | base name, with extension `.js'. Subsequent runs will overwrite the 225 | file. 226 | 227 | If there are compilation errors, point is moved to the first 228 | See `coffee-compile-jump-to-error'." 229 | (interactive) 230 | (let* ((input (buffer-file-name)) 231 | (basename (file-name-sans-extension input)) 232 | (output (when (string-match-p "\\.js\\'" basename) ;; for Rails '.js.coffee' file 233 | basename)) 234 | (compile-args (coffee-command-compile input output)) 235 | (compiler-output (with-temp-buffer 236 | (unless (zerop (apply #'process-file coffee-command nil t nil compile-args)) 237 | (error "Failed: %s %s" coffee-command compile-args)) 238 | (buffer-substring-no-properties (point-min) (point-max))))) 239 | (if (string= compiler-output "") 240 | (let ((file-name (coffee-compiled-file-name (buffer-file-name)))) 241 | (message "Compiled and saved %s" (or output (concat basename ".js"))) 242 | (coffee-revert-buffer-compiled-file file-name)) 243 | (coffee-parse-error-output compiler-output)))) 244 | 245 | (defun coffee-compile-buffer () 246 | "Compiles the current buffer and displays the JavaScript in a buffer 247 | called `coffee-compiled-buffer-name'." 248 | (interactive) 249 | (coffee-compile-region (point-min) (point-max))) 250 | 251 | (defsubst coffee-generate-sourcemap-p () 252 | (cl-find-if (lambda (opt) (member opt '("-m" "--map"))) coffee-args-compile)) 253 | 254 | (defun coffee--coffeescript-version () 255 | (with-temp-buffer 256 | (unless (zerop (process-file coffee-command nil t nil "--version")) 257 | (error "Failed: 'coffee --version'")) 258 | (goto-char (point-min)) 259 | (let ((line (buffer-substring-no-properties (point) (line-end-position)))) 260 | (when (string-match "[0-9.]+\\'" line) 261 | (match-string-no-properties 0 line))))) 262 | 263 | (defun coffee--map-file-name (coffee-file) 264 | (let* ((version (coffee--coffeescript-version)) 265 | (extension (if (version<= "1.8" version) ".js.map" ".map"))) 266 | ;; foo.js: foo.js.map(>= 1.8), foo.map(< 1.8) 267 | (concat (file-name-sans-extension coffee-file) extension))) 268 | 269 | (defmacro coffee-save-window-if (bool &rest body) 270 | `(if ,bool (save-selected-window ,@body) ,@body)) 271 | (put 'coffee-save-window-if 'lisp-indent-function 1) 272 | 273 | (defun coffee-compile-sentinel (buffer file line column) 274 | (lambda (proc _event) 275 | (when (eq (process-status proc) 'exit) 276 | (setq coffee--process nil) 277 | (coffee-save-window-if (not coffee-switch-to-compile-buffer) 278 | (pop-to-buffer (get-buffer coffee-compiled-buffer-name)) 279 | (ansi-color-apply-on-region (point-min) (point-max)) 280 | (goto-char (point-min)) 281 | (if (not (= (process-exit-status proc) 0)) 282 | (let ((compile-output (buffer-string))) 283 | (with-current-buffer buffer 284 | (coffee-parse-error-output compile-output))) 285 | (let ((props (list :sourcemap (coffee--map-file-name file) 286 | :line line :column column :source file))) 287 | (setq buffer-read-only t) 288 | (when (fboundp coffee-show-mode) 289 | (funcall coffee-show-mode)) 290 | (run-hook-with-args 'coffee-after-compile-hook props))))))) 291 | 292 | (defun coffee-start-compile-process (curbuf line column) 293 | (lambda (start end) 294 | (let ((proc (apply 'start-file-process "coffee-mode" 295 | (get-buffer-create coffee-compiled-buffer-name) 296 | coffee-command (append coffee-args-compile '("-s" "-p")))) 297 | (curfile (buffer-file-name curbuf))) 298 | (set-process-query-on-exit-flag proc nil) 299 | (set-process-sentinel 300 | proc (coffee-compile-sentinel curbuf curfile line column)) 301 | (with-current-buffer curbuf 302 | (process-send-region proc start end)) 303 | (process-send-string proc "\n") 304 | (process-send-eof proc) 305 | (setq coffee--process proc)))) 306 | 307 | (defun coffee-start-generate-sourcemap-process (start end) 308 | ;; so that sourcemap generation reads from the current buffer 309 | (save-buffer) 310 | (let* ((file (buffer-file-name)) 311 | (sourcemap-buf (get-buffer-create "*coffee-sourcemap*")) 312 | (proc (start-file-process "coffee-sourcemap" sourcemap-buf 313 | coffee-command "-m" file)) 314 | (curbuf (current-buffer)) 315 | (line (line-number-at-pos)) 316 | (column (current-column))) 317 | (setq coffee--process proc) 318 | (set-process-query-on-exit-flag proc nil) 319 | (set-process-sentinel 320 | proc 321 | (lambda (proc _event) 322 | (when (eq (process-status proc) 'exit) 323 | (setq coffee--process nil) 324 | (if (not (= (process-exit-status proc) 0)) 325 | (let ((sourcemap-output 326 | (with-current-buffer sourcemap-buf (buffer-string)))) 327 | (with-current-buffer curbuf 328 | (coffee-parse-error-output sourcemap-output))) 329 | (kill-buffer sourcemap-buf) 330 | (funcall (coffee-start-compile-process curbuf line column) start end))))))) 331 | 332 | (defun coffee-cleanup-compile-buffer () 333 | (let ((buffer (get-buffer coffee-compiled-buffer-name))) 334 | (when buffer 335 | (with-current-buffer buffer 336 | (setq buffer-read-only nil) 337 | (erase-buffer))))) 338 | 339 | (defun coffee-compile-region (start end) 340 | "Compiles a region and displays the JavaScript in a buffer called 341 | `coffee-compiled-buffer-name'." 342 | (interactive "r") 343 | (coffee-cleanup-compile-buffer) 344 | (if (coffee-generate-sourcemap-p) 345 | (coffee-start-generate-sourcemap-process start end) 346 | (funcall (coffee-start-compile-process 347 | (current-buffer) (line-number-at-pos) (current-column)) 348 | start end))) 349 | 350 | (defun coffee-get-repl-proc () 351 | (unless (comint-check-proc coffee-repl-buffer) 352 | (coffee-repl) 353 | ;; see issue #332 354 | (sleep-for 0 100)) 355 | (get-buffer-process coffee-repl-buffer)) 356 | 357 | (defun coffee-send-line () 358 | "Send the current line to the inferior Coffee process." 359 | (interactive) 360 | (coffee-send-region (line-beginning-position) (line-end-position))) 361 | 362 | (defun coffee-send-region (start end) 363 | "Send the current region to the inferior Coffee process." 364 | (interactive "r") 365 | (deactivate-mark t) 366 | (let* ((string (buffer-substring-no-properties start end)) 367 | (proc (coffee-get-repl-proc)) 368 | (multiline-escaped-string 369 | (replace-regexp-in-string "\n" "\uFF00" string))) 370 | (comint-simple-send proc multiline-escaped-string))) 371 | 372 | (defun coffee-send-buffer () 373 | "Send the current buffer to the inferior Coffee process." 374 | (interactive) 375 | (coffee-send-region (point-min) (point-max))) 376 | 377 | (defun coffee-js2coffee-replace-region (start end) 378 | "Convert JavaScript in the region into CoffeeScript." 379 | (interactive "r") 380 | 381 | (let ((buffer (get-buffer coffee-compiled-buffer-name))) 382 | (when buffer 383 | (kill-buffer buffer))) 384 | 385 | (call-process-region start end 386 | js2coffee-command t 387 | (current-buffer))) 388 | 389 | (defun coffee-version () 390 | "Show the `coffee-mode' version in the echo area." 391 | (interactive) 392 | (message (concat "coffee-mode version " coffee-mode-version))) 393 | 394 | (defun coffee-watch (dir-or-file) 395 | "Run `coffee-run-cmd' with the --watch flag on a directory or file." 396 | (interactive "fDirectory or File: ") 397 | (let ((coffee-compiled-buffer-name coffee-watch-buffer-name) 398 | (args (mapconcat 'identity (append coffee-args-compile (list "--watch" (expand-file-name dir-or-file))) " "))) 399 | (coffee-run-cmd args))) 400 | 401 | ;; 402 | ;; Menubar 403 | ;; 404 | 405 | (easy-menu-define coffee-mode-menu coffee-mode-map 406 | "Menu for CoffeeScript mode" 407 | '("CoffeeScript" 408 | ["Compile File" coffee-compile-file] 409 | ["Compile Buffer" coffee-compile-buffer] 410 | ["Compile Region" coffee-compile-region] 411 | ["REPL" coffee-repl] 412 | "---" 413 | ["Version" coffee-version] 414 | )) 415 | 416 | ;; 417 | ;; Define Language Syntax 418 | ;; 419 | 420 | ;; Instance variables (implicit this) 421 | (defvar coffee-this-regexp "\\(?:@[_[:word:]]+\\|\\") 422 | 423 | ;; Prototype::access 424 | (defvar coffee-prototype-regexp "[_[:word:].$]+?::") 425 | 426 | ;; Assignment 427 | (defvar coffee-assign-regexp "\\(@?[_[:word:].$]+?\\)\\s-*:") 428 | 429 | ;; Local Assignment 430 | (defvar coffee-local-assign-regexp "\\s-*\\([_[:word:].$]+\\)\\s-*\\??=\\(?:[^>=]\\|$\\)") 431 | 432 | ;; Lambda 433 | (defvar coffee-lambda-regexp "\\(?:([^)]*)\\)?\\s-*\\(->\\|=>\\)") 434 | 435 | ;; Namespaces 436 | (defvar coffee-namespace-regexp "\\b\\(?:class\\s-+\\(\\S-+\\)\\)\\b") 437 | 438 | ;; Booleans 439 | (defvar coffee-boolean-regexp 440 | (rx (or bol (not (any "."))) 441 | (group symbol-start 442 | (or "true" "false" "yes" "no" "on" "off" "null" "undefined") 443 | symbol-end))) 444 | 445 | ;; Regular expressions 446 | (eval-and-compile 447 | (defvar coffee-regexp-regexp "\\s/\\(\\(?:\\\\/\\|[^/\n\r]\\)*\\)\\s/")) 448 | 449 | ;; JavaScript Keywords 450 | (defvar coffee-js-keywords 451 | '("if" "else" "new" "return" "try" "catch" 452 | "finally" "throw" "break" "continue" "for" "in" "while" 453 | "delete" "instanceof" "package" "typeof" "switch" "super" "extends" 454 | "class" "until" "loop" "yield")) 455 | 456 | ;; Reserved keywords either by JS or CS. 457 | (defvar coffee-js-reserved 458 | '("case" "default" "do" "function" "var" "void" "with" 459 | "const" "let" "debugger" "enum" "export" "import" "native" 460 | "from" "as" "__extends" "__hasProp")) 461 | 462 | ;; CoffeeScript keywords. 463 | (defvar coffee-cs-keywords 464 | '("then" "unless" "and" "or" "is" "own" 465 | "isnt" "not" "of" "by" "when")) 466 | 467 | ;; Iced CoffeeScript keywords 468 | (defvar iced-coffee-cs-keywords 469 | '("await" "defer")) 470 | 471 | ;; Regular expression combining the above three lists. 472 | (defvar coffee-keywords-regexp 473 | ;; keywords can be member names. 474 | (concat "\\(?:^\\|[^.]\\)" 475 | (regexp-opt (append coffee-js-reserved 476 | coffee-js-keywords 477 | coffee-cs-keywords 478 | iced-coffee-cs-keywords) 'symbols))) 479 | 480 | ;; Create the list for font-lock. Each class of keyword is given a 481 | ;; particular face. 482 | (defvar coffee-font-lock-keywords 483 | ;; *Note*: order below matters. `coffee-keywords-regexp' goes last 484 | ;; because otherwise the keyword "state" in the function 485 | ;; "state_entry" would be highlighted. 486 | `((,coffee-regexp-regexp . font-lock-constant-face) 487 | (,coffee-this-regexp . font-lock-variable-name-face) 488 | (,coffee-prototype-regexp . font-lock-type-face) 489 | (,coffee-keywords-regexp 1 font-lock-keyword-face) 490 | (,coffee-boolean-regexp 1 font-lock-constant-face) 491 | (,coffee-assign-regexp . font-lock-type-face) 492 | (,coffee-local-assign-regexp 1 font-lock-variable-name-face) 493 | (,coffee-lambda-regexp 1 font-lock-function-name-face) 494 | (,(lambda (limit) 495 | (let ((res nil) 496 | start) 497 | (while (and (not res) (search-forward "#{" limit t) 498 | (not (coffee-in-comment-p))) 499 | (let ((restart-pos (match-end 0))) 500 | (setq start (match-beginning 0)) 501 | (let (finish) 502 | (while (and (not finish) (search-forward "}" limit t)) 503 | (let ((end-pos (point))) 504 | (save-excursion 505 | (when (and (ignore-errors (backward-list 1)) 506 | (= start (1- (point)))) 507 | (setq res end-pos finish t))))) 508 | (unless finish 509 | (goto-char restart-pos))))) 510 | (when (and res start) 511 | (set-match-data (list start res))) 512 | res)) 513 | (0 font-lock-variable-name-face t)))) 514 | 515 | ;; 516 | ;; Helper Functions 517 | ;; 518 | 519 | (defun coffee-comment-dwim (arg) 520 | "Comment or uncomment current line or region in a smart way. 521 | For details, see `comment-dwim'." 522 | (interactive "*P") 523 | (require 'newcomment) 524 | (let ((deactivate-mark nil) (comment-start "#") (comment-end "")) 525 | (comment-dwim arg) 526 | (deactivate-mark t))) 527 | 528 | (defsubst coffee-command-compile-options (output) 529 | (if output 530 | (append coffee-args-compile (list "-j" output)) 531 | coffee-args-compile)) 532 | 533 | (defun coffee-command-compile (input output) 534 | "Run `coffee-command' to compile FILE-NAME to file with default 535 | .js output file, or optionally to OUTPUT-FILE-NAME." 536 | (let* ((expanded (expand-file-name input)) 537 | (filename (if (file-remote-p expanded) 538 | (tramp-file-name-localname (tramp-dissect-file-name expanded)) 539 | (file-truename expanded))) 540 | (output-file (coffee-compiled-file-name filename)) 541 | (output-dir (file-name-directory output-file))) 542 | (unless (file-directory-p output-dir) 543 | (make-directory output-dir t)) 544 | (append (coffee-command-compile-options output) 545 | (list "-o" output-dir filename)))) 546 | 547 | (defun coffee-run-cmd (args) 548 | "Run `coffee-command' with the given arguments, and display the 549 | output in a compilation buffer." 550 | (interactive "sArguments: ") 551 | (let ((compilation-buffer-name-function 552 | (lambda (_this-mode) 553 | (generate-new-buffer-name coffee-compiled-buffer-name)))) 554 | (compile (concat coffee-command " " args)))) 555 | 556 | (defun coffee-toggle-fatness () 557 | "Toggle fatness of a coffee function arrow." 558 | (interactive) 559 | (save-excursion 560 | (when (re-search-backward "[-=]>" nil t) 561 | (cond ((looking-at "=") (replace-match "-")) 562 | ((looking-at "-") (replace-match "=")))))) 563 | 564 | ;; 565 | ;; imenu support 566 | ;; 567 | 568 | (defconst coffee-imenu-index-regexp 569 | (concat "^\\(\\s-*\\)" ; $1 570 | "\\(?:" 571 | coffee-assign-regexp ; $2 572 | "\\s-*" 573 | coffee-lambda-regexp 574 | "\\|" 575 | coffee-namespace-regexp ; $4 576 | "\\|" 577 | "\\(@?[_[:word:]:.$]+\\)\\s-*=\\(?:[^>]\\|$\\)" ; $5 match prototype access too 578 | "\\(?:" "\\s-*" "\\(" coffee-lambda-regexp "\\)" "\\)?" ; $6 579 | "\\)")) 580 | 581 | (defun coffee-imenu-create-index () 582 | "Create an imenu index of all methods in the buffer." 583 | (interactive) 584 | 585 | ;; This function is called within a `save-excursion' so we're safe. 586 | (goto-char (point-min)) 587 | 588 | (let ((index-alist '()) 589 | (ns-indent 0) 590 | ns-name) 591 | ;; Go through every assignment that includes -> or => on the same 592 | ;; line or starts with `class'. 593 | (while (re-search-forward coffee-imenu-index-regexp nil t) 594 | (let ((current-indent (- (match-end 1) (match-beginning 1))) 595 | (property-name (match-string-no-properties 2)) 596 | (class-name (match-string-no-properties 4)) 597 | (variable-name (match-string-no-properties 5)) 598 | (func-assign (match-string-no-properties 6))) 599 | 600 | ;; If this is the start of a new namespace, save the namespace's 601 | ;; indentation level and name. 602 | (if class-name 603 | (setq ns-name (concat class-name "::") 604 | ns-indent current-indent) 605 | (when (and variable-name (<= current-indent ns-indent)) 606 | (setq ns-name (concat variable-name ".") 607 | ns-indent current-indent))) 608 | 609 | (if func-assign 610 | (push (cons variable-name (match-beginning 5)) index-alist) 611 | (when (and ns-name property-name) 612 | (let ((index-pos (match-beginning 2))) 613 | (if (<= current-indent ns-indent) 614 | ;; Clear the namespace if we're no longer indented deeper 615 | (setq ns-name nil ns-indent nil) 616 | ;; Register as index-name if we are within the context of a namespace 617 | (push (cons (concat ns-name property-name) index-pos) index-alist))))))) 618 | index-alist)) 619 | 620 | ;; 621 | ;; Indentation 622 | ;; 623 | 624 | (defsubst coffee-insert-spaces (count) 625 | (if coffee-indent-tabs-mode 626 | (insert-char (string-to-char "\t") (floor count coffee-tab-width)) 627 | (insert-char ? count))) 628 | 629 | ;;; The theory is explained in the README. 630 | 631 | (defsubst coffee--in-string-or-comment-p () 632 | (nth 8 (syntax-ppss))) 633 | 634 | (defun coffee--block-type () 635 | (save-excursion 636 | (back-to-indentation) 637 | (unless (coffee--in-string-or-comment-p) 638 | (cond ((looking-at-p "else\\(\\s-+if\\)?\\_>") 'if-else) 639 | ((looking-at-p "\\(?:catch\\|finally\\)\\_>") 'try-catch))))) 640 | 641 | (defun coffee--closed-if-else-p (curindent if-indent) 642 | (let (else-if-p else-p) 643 | (when (looking-at "else\\(?:\\s-+\\(if\\)\\)?\\_>") 644 | (if (string= (match-string 1) "if") 645 | (setq else-if-p t) 646 | (setq else-p t))) 647 | (or (and (not (or else-p else-if-p)) (<= curindent if-indent)) 648 | (and else-p (= curindent if-indent))))) 649 | 650 | (defun coffee--closed-try-catch-p (curindent if-indent) 651 | (and (not (looking-at-p "\\(?:finally\\|catch\\)\\_>")) 652 | (<= curindent if-indent))) 653 | 654 | (defun coffee--closed-block-p (type if-indent limit) 655 | (let ((limit-line (line-number-at-pos limit)) 656 | (closed-pred (cl-case type 657 | (if-else 'coffee--closed-if-else-p) 658 | (try-catch 'coffee--closed-try-catch-p))) 659 | finish) 660 | (save-excursion 661 | (while (and (not finish) (< (point) limit)) 662 | (forward-line 1) 663 | (when (< (line-number-at-pos) limit-line) 664 | (let ((curindent (current-indentation))) 665 | (unless (coffee--in-string-or-comment-p) 666 | (back-to-indentation) 667 | (when (funcall closed-pred curindent if-indent) 668 | (setq finish t)))))) 669 | finish))) 670 | 671 | (defun coffee--find-if-else-indents (limit cmpfn) 672 | (let (indents) 673 | (while (re-search-forward "^\\s-*if\\_>" limit t) 674 | (let ((indent (current-indentation))) 675 | (unless (coffee--closed-block-p 'if-else indent limit) 676 | (push indent indents)))) 677 | (sort indents cmpfn))) 678 | 679 | (defun coffee--find-try-catch-indents (limit cmpfn) 680 | (let (indents) 681 | (while (re-search-forward "^\\s-*try\\_>" limit t) 682 | (let ((indent (current-indentation))) 683 | (unless (coffee--closed-block-p 'try-catch indent limit) 684 | (push indent indents)))) 685 | (sort indents cmpfn))) 686 | 687 | (defun coffee--find-indents (type limit cmpfn) 688 | (save-excursion 689 | (coffee-beginning-of-defun 1) 690 | (cl-case type 691 | (if-else (coffee--find-if-else-indents limit cmpfn)) 692 | (try-catch (coffee--find-try-catch-indents limit cmpfn))))) 693 | 694 | (defsubst coffee--decide-indent (curindent if-indents cmpfn) 695 | (cl-loop for if-indent in if-indents 696 | when (funcall cmpfn if-indent curindent) 697 | return if-indent 698 | finally 699 | return (car if-indents))) 700 | 701 | (defun coffee--indent-insert-spaces (indent-size) 702 | (unless (= (current-indentation) indent-size) 703 | (save-excursion 704 | (goto-char (line-beginning-position)) 705 | (delete-horizontal-space) 706 | (coffee-insert-spaces indent-size))) 707 | (when (< (current-column) (current-indentation)) 708 | (back-to-indentation))) 709 | 710 | (defun coffee--indent-line-like-python-mode (prev-indent repeated) 711 | (let ((next-indent (- (current-indentation) coffee-tab-width)) 712 | (indent-p (coffee-line-wants-indent))) 713 | (if repeated 714 | (if (< next-indent 0) 715 | (+ prev-indent (if indent-p coffee-tab-width 0)) 716 | next-indent) 717 | (+ prev-indent (if indent-p coffee-tab-width 0))))) 718 | 719 | (defun coffee-indent-line () 720 | "Indent current line as CoffeeScript." 721 | (interactive) 722 | (let* ((curindent (current-indentation)) 723 | (limit (+ (line-beginning-position) curindent)) 724 | (type (coffee--block-type)) 725 | indent-size 726 | begin-indents) 727 | (if (and type (setq begin-indents (coffee--find-indents type limit '<))) 728 | (setq indent-size (coffee--decide-indent curindent begin-indents '>)) 729 | (if coffee-indent-like-python-mode 730 | (setq indent-size 731 | (coffee--indent-line-like-python-mode 732 | (coffee-previous-indent) (eq last-command this-command))) 733 | (let ((prev-indent (coffee-previous-indent)) 734 | (next-indent-size (+ curindent coffee-tab-width))) 735 | (if (> (- next-indent-size prev-indent) coffee-tab-width) 736 | (setq indent-size 0) 737 | (setq indent-size (+ curindent coffee-tab-width)))))) 738 | (coffee--indent-insert-spaces indent-size))) 739 | 740 | (defun coffee-previous-indent () 741 | "Return the indentation level of the previous non-blank line." 742 | (save-excursion 743 | (forward-line -1) 744 | (while (and (looking-at-p "^[ \t]*$") (not (bobp))) 745 | (forward-line -1)) 746 | (current-indentation))) 747 | 748 | (defun coffee-newline-and-indent () 749 | "Insert a newline and indent it to the same level as the previous line." 750 | (interactive) 751 | 752 | ;; Remember the current line indentation level, 753 | ;; insert a newline, and indent the newline to the same 754 | ;; level as the previous line. 755 | (let ((prev-indent (current-indentation))) 756 | (when (< (current-column) (current-indentation)) 757 | (move-to-column (current-indentation))) 758 | (delete-horizontal-space t) 759 | (newline) 760 | 761 | (if (coffee-line-wants-indent) 762 | ;; We need to insert an additional tab because the last line was special. 763 | (coffee-insert-spaces (+ (coffee-previous-indent) coffee-tab-width)) 764 | ;; otherwise keep at the same indentation level 765 | (coffee-insert-spaces prev-indent)) 766 | 767 | ;; Last line was a comment so this one should probably be, 768 | ;; too. Makes it easy to write multi-line comments (like the one I'm 769 | ;; writing right now). 770 | (unless (and auto-fill-function comment-auto-fill-only-comments) 771 | (when (coffee-previous-line-is-single-line-comment) 772 | (insert "# "))))) 773 | 774 | (defun coffee-dedent-line-backspace (arg) 775 | "Unindent to increment of `coffee-tab-width' with ARG==1 when 776 | called from first non-blank char of line. 777 | 778 | Delete ARG spaces if ARG!=1." 779 | (interactive "*p") 780 | (if (use-region-p) 781 | (delete-region (region-beginning) (region-end)) 782 | (if (and (= 1 arg) 783 | (= (point) (save-excursion 784 | (back-to-indentation) 785 | (point))) 786 | (not (bolp))) 787 | (let* ((extra-space-count (% (current-column) coffee-tab-width)) 788 | (deleted-chars (if (zerop extra-space-count) 789 | coffee-tab-width 790 | extra-space-count))) 791 | (backward-delete-char-untabify deleted-chars)) 792 | (backward-delete-char-untabify arg)))) 793 | 794 | ;; Indenters help determine whether the current line should be 795 | ;; indented further based on the content of the previous line. If a 796 | ;; line starts with `class', for instance, you're probably going to 797 | ;; want to indent the next line. 798 | 799 | (defvar coffee-indenters-bol '("class" "for" "if" "else" "unless" "while" "until" 800 | "try" "catch" "finally" "switch" "when") 801 | "Keywords or syntax whose presence at the start of a line means the 802 | next line should probably be indented.") 803 | 804 | (defun coffee-indenters-bol-regexp () 805 | "Builds a regexp out of `coffee-indenters-bol' words." 806 | (regexp-opt coffee-indenters-bol 'words)) 807 | 808 | (defvar coffee-indenters-eol '(?> ?{ ?\[ ?:) 809 | "Single characters at the end of a line that mean the next line 810 | should probably be indented.") 811 | 812 | (defun coffee-line-wants-indent () 813 | "Return t if the current line should be indented relative to the 814 | previous line." 815 | (save-excursion 816 | (back-to-indentation) 817 | (skip-chars-backward "\r\n\t ") 818 | (let ((char-of-eol (char-before (line-end-position)))) 819 | (or (and char-of-eol (memq char-of-eol coffee-indenters-eol)) 820 | (progn 821 | (back-to-indentation) 822 | (and (looking-at-p (coffee-indenters-bol-regexp)) 823 | (not (re-search-forward "\\_" (line-end-position) t)))))))) 824 | 825 | (defun coffee-previous-line-is-single-line-comment () 826 | "Return t if the previous line is a CoffeeScript single line comment." 827 | (save-excursion 828 | (forward-line -1) 829 | (back-to-indentation) 830 | (and (looking-at-p "#") 831 | (not (looking-at-p "###\\(?:\\s-+.*\\)?$")) 832 | (progn 833 | (goto-char (line-end-position)) 834 | (nth 4 (syntax-ppss)))))) 835 | 836 | (defun coffee-indent-shift-amount (start end dir) 837 | "Compute distance to the closest increment of `coffee-tab-width'." 838 | (let ((min most-positive-fixnum)) 839 | (save-excursion 840 | (goto-char start) 841 | (while (< (point) end) 842 | (let ((current (current-indentation))) 843 | (when (< current min) 844 | (setq min current))) 845 | (forward-line)) 846 | (let ((rem (% min coffee-tab-width))) 847 | (if (zerop rem) 848 | coffee-tab-width 849 | (cond ((eq dir 'left) rem) 850 | ((eq dir 'right) (- coffee-tab-width rem)) 851 | (t 0))))))) 852 | 853 | (defun coffee-indent-shift-left (start end &optional count) 854 | "Shift lines contained in region START END by COUNT columns to the left. 855 | If COUNT is not given, indents to the closest increment of 856 | `coffee-tab-width'. If region isn't active, the current line is 857 | shifted. The shifted region includes the lines in which START and 858 | END lie. An error is signaled if any lines in the region are 859 | indented less than COUNT columns." 860 | (interactive 861 | (if (use-region-p) 862 | (list (region-beginning) (region-end) current-prefix-arg) 863 | (list (line-beginning-position) (line-end-position) current-prefix-arg))) 864 | (let ((amount (if count (* coffee-tab-width (prefix-numeric-value count)) 865 | (coffee-indent-shift-amount start end 'left)))) 866 | (when (> amount 0) 867 | (let (deactivate-mark) 868 | (save-excursion 869 | (goto-char start) 870 | ;; Check that all lines can be shifted enough 871 | (while (< (point) end) 872 | (if (and (< (current-indentation) amount) 873 | (not (looking-at-p "[ \t]*$"))) 874 | (error "Can't shift all lines enough")) 875 | (forward-line)) 876 | (indent-rigidly start end (- amount))))))) 877 | 878 | (add-to-list 'debug-ignored-errors "^Can't shift all lines enough") 879 | 880 | (defun coffee-indent-shift-right (start end &optional count) 881 | "Shift lines contained in region START END by COUNT columns to the right. 882 | if COUNT is not given, indents to the closest increment of 883 | `coffee-tab-width'. If region isn't active, the current line is 884 | shifted. The shifted region includes the lines in which START and 885 | END lie." 886 | (interactive 887 | (if (use-region-p) 888 | (list (region-beginning) (region-end) current-prefix-arg) 889 | (list (line-beginning-position) (line-end-position) current-prefix-arg))) 890 | (let (deactivate-mark 891 | (amount (if count (* coffee-tab-width (prefix-numeric-value count)) 892 | (coffee-indent-shift-amount start end 'right)))) 893 | (indent-rigidly start end amount))) 894 | 895 | (defun coffee-indent-region (start end) 896 | (interactive "r") 897 | (save-excursion 898 | (goto-char start) 899 | (forward-line 1) 900 | (while (and (not (eobp)) (< (point) end)) 901 | (let ((prev-indent (coffee-previous-indent)) 902 | (curindent (current-indentation)) 903 | indent-size) 904 | (if (coffee-line-wants-indent) 905 | (let ((expected (+ prev-indent coffee-tab-width))) 906 | (when (/= curindent expected) 907 | (setq indent-size expected))) 908 | (when (> curindent prev-indent) 909 | (setq indent-size prev-indent))) 910 | (when indent-size 911 | (save-excursion 912 | (goto-char (line-beginning-position)) 913 | (delete-horizontal-space) 914 | (coffee-insert-spaces indent-size)))) 915 | (forward-line 1)))) 916 | 917 | ;; 918 | ;; Fill 919 | ;; 920 | 921 | (defun coffee-fill-forward-paragraph-function (&optional count) 922 | "`fill-forward-paragraph-function' which correctly handles block 923 | comments such as the following: 924 | 925 | class Klass 926 | method: -> 927 | ### 928 | This is a method doc comment that spans multiple lines. 929 | If `fill-paragraph' is applied to this paragraph, the comment 930 | should preserve its format, with the delimiters on separate lines. 931 | ### 932 | ..." 933 | (let ((ret (forward-paragraph count))) 934 | (when (and (= count -1) 935 | (looking-at-p "[[:space:]]*###[[:space:]]*$")) 936 | (forward-line)) 937 | ret)) 938 | 939 | ;; 940 | ;; Define navigation functions 941 | ;; 942 | 943 | (defconst coffee-defun-regexp 944 | (concat "^\\s-*\\(?:" 945 | coffee-assign-regexp 946 | "\\s-*" 947 | coffee-lambda-regexp 948 | "\\|" 949 | coffee-namespace-regexp 950 | "\\|" 951 | "@?[_[:word:]:.$]+\\s-*=\\(?:[^>]\\|$\\)" 952 | "\\s-*" 953 | coffee-lambda-regexp 954 | "\\)")) 955 | 956 | (defun coffee-in-comment-p () 957 | (unless (eobp) 958 | (save-excursion 959 | (back-to-indentation) 960 | (when (eq (char-after) ?#) 961 | (forward-char 1)) 962 | (nth 4 (syntax-ppss))))) 963 | 964 | (defsubst coffee-current-line-empty-p () 965 | (let ((line (buffer-substring-no-properties 966 | (line-beginning-position) (line-end-position)))) 967 | (string-match-p "^\\s-*$" line))) 968 | 969 | (defun coffee-current-line-is-defun () 970 | (save-excursion 971 | (goto-char (line-end-position)) 972 | (re-search-backward coffee-defun-regexp (line-beginning-position) t))) 973 | 974 | (defun coffee-current-line-is-assignment () 975 | (save-excursion 976 | (goto-char (line-end-position)) 977 | (re-search-backward "^[_[:word:].$]+\\s-*=\\(?:[^>]\\|$\\)" 978 | (line-beginning-position) t))) 979 | 980 | (defun coffee-curline-defun-type (parent-indent start-is-defun) 981 | (save-excursion 982 | (goto-char (line-end-position)) 983 | (if (not (re-search-backward coffee-defun-regexp (line-beginning-position) t)) 984 | (when (and (zerop parent-indent) (coffee-current-line-is-assignment)) 985 | 'other) 986 | (if (not start-is-defun) 987 | 'other 988 | (if (< parent-indent (current-indentation)) 989 | 'child 990 | 'other))))) 991 | 992 | (defun coffee-same-block-p (block-indent start-is-defun) 993 | (let ((type (coffee-curline-defun-type block-indent start-is-defun))) 994 | (cond ((eq type 'child) t) 995 | ((eq type 'other) nil) 996 | (t (>= (current-indentation) block-indent))))) 997 | 998 | (defsubst coffee-skip-line-p () 999 | (or (coffee-in-comment-p) (coffee-current-line-empty-p))) 1000 | 1001 | (defun coffee-skip-forward-lines (arg) 1002 | (let ((pred (if (> arg 0) 1003 | (lambda () (not (eobp))) 1004 | (lambda () (not (bobp)))))) 1005 | (while (and (funcall pred) (coffee-skip-line-p)) 1006 | (forward-line arg)))) 1007 | 1008 | (defun coffee-beginning-of-defun (&optional count) 1009 | (interactive "p") 1010 | (unless count 1011 | (setq count 1)) 1012 | (let ((next-indent nil)) 1013 | (when (coffee-skip-line-p) 1014 | (save-excursion 1015 | (coffee-skip-forward-lines +1) 1016 | (setq next-indent (current-indentation)))) 1017 | (coffee-skip-forward-lines -1) 1018 | (let ((start-indent (or next-indent (current-indentation)))) 1019 | (when (and (not (eq this-command 'coffee-mark-defun)) (looking-back "^\\s-*" (line-beginning-position))) 1020 | (forward-line -1)) 1021 | (let ((finish nil)) 1022 | (goto-char (line-end-position)) 1023 | (while (and (not finish) (re-search-backward coffee-defun-regexp nil 'move)) 1024 | (let ((cur-indent (current-indentation))) 1025 | (when (<= cur-indent start-indent) 1026 | (setq start-indent cur-indent) 1027 | (cl-decf count))) 1028 | (when (<= count 0) 1029 | (back-to-indentation) 1030 | (setq finish t))))))) 1031 | 1032 | (defun coffee-end-of-block (&optional count) 1033 | "Move point to the end of the block." 1034 | (interactive "p") 1035 | (unless count 1036 | (setq count 1)) 1037 | (dotimes (_i count) 1038 | (let* ((curline-is-defun (coffee-current-line-is-defun)) 1039 | start-indent) 1040 | (coffee-skip-forward-lines 1) 1041 | (setq start-indent (current-indentation)) 1042 | (when (and (zerop start-indent) (not curline-is-defun)) 1043 | (when (re-search-forward coffee-defun-regexp nil 'move) 1044 | (back-to-indentation) 1045 | (setq curline-is-defun t))) 1046 | (let ((finish nil)) 1047 | (while (not finish) 1048 | (forward-line 1) 1049 | (coffee-skip-forward-lines 1) 1050 | (when (or (not (coffee-same-block-p start-indent curline-is-defun)) 1051 | (eobp)) 1052 | (setq finish t))) 1053 | (forward-line -1) 1054 | (coffee-skip-forward-lines -1) 1055 | (forward-line 1))))) 1056 | 1057 | (defun coffee-mark-defun () 1058 | (interactive) 1059 | (let ((be-actived transient-mark-mode)) 1060 | (push-mark (point)) 1061 | (let ((cur-indent (current-indentation))) 1062 | (coffee-beginning-of-defun) 1063 | (push-mark (point)) 1064 | (coffee-end-of-block) 1065 | (push-mark (point) nil be-actived) 1066 | (let ((next-indent nil)) 1067 | (when (coffee-skip-line-p) 1068 | (save-excursion 1069 | (coffee-skip-forward-lines +1) 1070 | (setq next-indent (current-indentation)))) 1071 | (when (and next-indent (< next-indent cur-indent)) 1072 | (coffee-skip-forward-lines -1)) 1073 | (coffee-beginning-of-defun))))) 1074 | 1075 | ;; 1076 | ;; hs-minor-mode 1077 | ;; 1078 | 1079 | ;; support for hs-minor-mode 1080 | (add-to-list 'hs-special-modes-alist 1081 | '(coffee-mode "\\s-*\\(?:class\\|.+[-=]>$\\)" nil "#" 1082 | coffee-end-of-block nil)) 1083 | 1084 | ;; 1085 | ;; Based on triple quote of python.el 1086 | ;; 1087 | (eval-and-compile 1088 | (defconst coffee-block-strings-delimiter 1089 | (rx (and 1090 | ;; Match even number of backslashes. 1091 | (or (not (any ?\\ ?\' ?\" ?/)) 1092 | point 1093 | ;; Quotes might be preceded by a escaped quote. 1094 | (and (or (not (any ?\\)) point) 1095 | ?\\ 1096 | (* ?\\ ?\\) 1097 | (any ?\' ?\" ?/))) 1098 | (* ?\\ ?\\) 1099 | ;; Match single or triple quotes of any kind. 1100 | (group (or "'''" "\"\"\"" "///")))))) 1101 | 1102 | (defsubst coffee-syntax-count-quotes (quote-char start-point limit) 1103 | (let ((i 0)) 1104 | (while (and (< i 3) 1105 | (< (+ start-point i) limit) 1106 | (eq (char-after (+ start-point i)) quote-char)) 1107 | (cl-incf i)) 1108 | i)) 1109 | 1110 | (defun coffee-syntax-block-strings-stringify () 1111 | (let* ((ppss (prog2 1112 | (backward-char 3) 1113 | (syntax-ppss) 1114 | (forward-char 3))) 1115 | (string-start (and (not (nth 4 ppss)) (nth 8 ppss))) 1116 | (quote-starting-pos (- (point) 3)) 1117 | (quote-ending-pos (point)) 1118 | (num-closing-quotes 1119 | (and string-start 1120 | (coffee-syntax-count-quotes 1121 | (char-before) string-start quote-starting-pos)))) 1122 | (cond ((and string-start (= num-closing-quotes 0)) 1123 | ;; This set of quotes doesn't match the string starting 1124 | ;; kind. Do nothing. 1125 | nil) 1126 | ((not string-start) 1127 | ;; This set of quotes delimit the start of a string. 1128 | (put-text-property quote-starting-pos (1+ quote-starting-pos) 1129 | 'syntax-table (string-to-syntax "|"))) 1130 | ((= num-closing-quotes 3) 1131 | ;; This set of quotes delimit the end of a string. 1132 | (put-text-property (1- quote-ending-pos) quote-ending-pos 1133 | 'syntax-table (string-to-syntax "|")))))) 1134 | 1135 | (defun coffee-syntax-propertize-block-comment () 1136 | (let ((curpoint (point)) 1137 | (inhibit-changing-match-data t)) 1138 | (let* ((valid-comment-start nil) 1139 | (valid-comment-end (looking-at-p "#\\{0,2\\}\\s-*$")) 1140 | (ppss (prog2 1141 | (backward-char 3) 1142 | (syntax-ppss) 1143 | (setq valid-comment-start 1144 | (and (looking-back "^\\s-*" (line-beginning-position)) 1145 | (looking-at-p "###[^#]"))) 1146 | (forward-char 3))) 1147 | (in-comment (nth 4 ppss)) 1148 | (in-string (nth 3 ppss))) 1149 | (when (or (and (not in-comment) (not in-string) valid-comment-start) 1150 | (and in-comment valid-comment-end)) 1151 | (put-text-property (- curpoint 3) curpoint 1152 | 'syntax-table (string-to-syntax "!")))))) 1153 | 1154 | (defsubst coffee--in-string-p () 1155 | (nth 3 (syntax-ppss))) 1156 | 1157 | (defun coffee-syntax-string-interpolation () 1158 | (let ((start (match-beginning 0)) 1159 | (end (point))) 1160 | (if (not (coffee--in-string-p)) 1161 | (put-text-property start (1+ start) 1162 | 'syntax-table (string-to-syntax "< b")) 1163 | (goto-char start) 1164 | (let (finish res) 1165 | (while (and (not finish) (search-forward "}" nil t)) 1166 | (let ((end-pos (match-end 0))) 1167 | (save-excursion 1168 | (when (and (ignore-errors (backward-list 1)) 1169 | (= start (1- (point)))) 1170 | (setq res end-pos finish t))))) 1171 | (goto-char end) 1172 | (when res 1173 | (while (re-search-forward "[\"'#]" res t) 1174 | (put-text-property (match-beginning 0) (match-end 0) 1175 | 'syntax-table (string-to-syntax "_"))) 1176 | (goto-char (1- res))))))) 1177 | 1178 | (defun coffee-syntax-propertize-function (start end) 1179 | (goto-char start) 1180 | (funcall 1181 | (syntax-propertize-rules 1182 | (coffee-block-strings-delimiter 1183 | (0 (ignore (coffee-syntax-block-strings-stringify)))) 1184 | ("/" 1185 | (0 (ignore 1186 | (let ((curpoint (point)) 1187 | (start (match-beginning 0)) 1188 | (end (match-end 0))) 1189 | (goto-char start) 1190 | (let ((ppss (syntax-ppss))) 1191 | (cond ((nth 8 ppss) 1192 | (put-text-property start end 1193 | 'syntax-table (string-to-syntax "_")) 1194 | (goto-char curpoint)) 1195 | ((looking-at coffee-regexp-regexp) 1196 | (put-text-property (match-beginning 1) (match-end 1) 1197 | 'syntax-table (string-to-syntax "_")) 1198 | (goto-char (match-end 0))) 1199 | (t (goto-char curpoint)))))))) 1200 | ("#{" (0 (ignore (coffee-syntax-string-interpolation)))) 1201 | ("###" 1202 | (0 (ignore (coffee-syntax-propertize-block-comment))))) 1203 | (point) end)) 1204 | 1205 | (defun coffee-get-comment-info () 1206 | (let* ((syntax (syntax-ppss)) 1207 | (commentp (nth 4 syntax)) 1208 | (comment-start-kinda (nth 8 syntax))) 1209 | (when commentp 1210 | (save-excursion 1211 | (if (and 1212 | (> comment-start-kinda 2) (< comment-start-kinda (point-max)) 1213 | (string= 1214 | "###" (buffer-substring 1215 | (- comment-start-kinda 2) (1+ comment-start-kinda)))) 1216 | 'multiple-line 1217 | 'single-line))))) 1218 | 1219 | (defun coffee-comment-line-break-fn (&optional _) 1220 | (let ((comment-type (coffee-get-comment-info)) 1221 | (coffee-indent-like-python-mode t)) 1222 | (comment-indent-new-line) 1223 | (cond ((eq comment-type 'multiple-line) 1224 | (save-excursion 1225 | (beginning-of-line) 1226 | (when (looking-at "[[:space:]]*\\(#\\)") 1227 | (replace-match "" nil nil nil 1)))) 1228 | ((eq comment-type 'single-line) 1229 | (coffee-indent-line))))) 1230 | 1231 | (defun coffee-auto-fill-fn () 1232 | (let ((comment-type (coffee-get-comment-info)) 1233 | (fill-result (do-auto-fill)) 1234 | (coffee-indent-like-python-mode t)) 1235 | (when (and fill-result (eq comment-type 'single-line)) 1236 | (save-excursion 1237 | (beginning-of-line) 1238 | (when (looking-at "[[:space:]]*#") 1239 | (replace-match "#"))) 1240 | (coffee-indent-line)))) 1241 | 1242 | ;; 1243 | ;; Define Major Mode 1244 | ;; 1245 | 1246 | (defvar coffee-mode-syntax-table 1247 | (let ((table (make-syntax-table))) 1248 | ;; perl style comment: "# ..." 1249 | (modify-syntax-entry ?# "< b" table) 1250 | (modify-syntax-entry ?\n "> b" table) 1251 | 1252 | ;; Treat slashes as paired delimiters; useful for finding regexps. 1253 | (modify-syntax-entry ?/ "/" table) 1254 | 1255 | ;; Following chars (=, +, -, ...) should be seemes as operators, 1256 | ;; instead of a part of a symbol 1257 | (modify-syntax-entry ?= "." table) 1258 | (modify-syntax-entry ?+ "." table) 1259 | (modify-syntax-entry ?- "." table) 1260 | (modify-syntax-entry ?* "." table) 1261 | (modify-syntax-entry ?& "." table) 1262 | (modify-syntax-entry ?% "." table) 1263 | (modify-syntax-entry ?| "." table) 1264 | (modify-syntax-entry ?> "." table) 1265 | (modify-syntax-entry ?< "." table) 1266 | 1267 | ;; single quote strings 1268 | (modify-syntax-entry ?' "\"" table) 1269 | table)) 1270 | 1271 | ;;;###autoload 1272 | (define-derived-mode coffee-mode prog-mode "Coffee" 1273 | "Major mode for editing CoffeeScript." 1274 | 1275 | ;; code for syntax highlighting 1276 | (setq font-lock-defaults '((coffee-font-lock-keywords))) 1277 | 1278 | ;; fix comment filling function 1279 | (setq-local comment-line-break-function #'coffee-comment-line-break-fn) 1280 | (setq-local normal-auto-fill-function #'coffee-auto-fill-fn) 1281 | 1282 | (setq-local comment-start "#") 1283 | 1284 | ;; indentation 1285 | (make-local-variable 'coffee-tab-width) 1286 | (make-local-variable 'coffee-indent-tabs-mode) 1287 | (setq-local indent-line-function 'coffee-indent-line) 1288 | (setq-local indent-region-function 'coffee-indent-region) 1289 | (setq-local tab-width coffee-tab-width) 1290 | 1291 | (setq-local syntax-propertize-function 'coffee-syntax-propertize-function) 1292 | 1293 | ;; fill 1294 | (setq-local fill-forward-paragraph-function 'coffee-fill-forward-paragraph-function) 1295 | 1296 | (setq-local beginning-of-defun-function 'coffee-beginning-of-defun) 1297 | (setq-local end-of-defun-function 'coffee-end-of-block) 1298 | 1299 | ;; imenu 1300 | (setq-local imenu-create-index-function 'coffee-imenu-create-index) 1301 | 1302 | ;; Don't let electric-indent-mode break coffee-mode. 1303 | (setq-local electric-indent-functions (list (lambda (_arg) 'no-indent))) 1304 | 1305 | ;; no tabs 1306 | (setq indent-tabs-mode coffee-indent-tabs-mode)) 1307 | 1308 | ;; 1309 | ;; Compile-on-Save minor mode 1310 | ;; 1311 | 1312 | (defcustom coffee-cos-mode-line " CoS" 1313 | "Lighter of `coffee-cos-mode'" 1314 | :type 'string) 1315 | 1316 | (define-minor-mode coffee-cos-mode 1317 | "Toggle compile-on-save for coffee-mode. 1318 | 1319 | Add `'(lambda () (coffee-cos-mode t))' to `coffee-mode-hook' to turn 1320 | it on by default." 1321 | :lighter coffee-cos-mode-line 1322 | (if coffee-cos-mode 1323 | (add-hook 'after-save-hook 'coffee-compile-file nil t) 1324 | (remove-hook 'after-save-hook 'coffee-compile-file t))) 1325 | 1326 | ;; 1327 | ;; Live compile minor mode 1328 | ;; 1329 | 1330 | (defun coffee--live-compile (&rest _unused) 1331 | (when (or (not coffee--process) 1332 | (not (eq (process-status coffee--process) 'run))) 1333 | (coffee-compile-buffer))) 1334 | 1335 | (defcustom coffee-live-compile-mode-line " LiveCS" 1336 | "Lighter of `coffee-live-compile-mode'" 1337 | :type 'string) 1338 | 1339 | (define-minor-mode coffee-live-compile-mode 1340 | "Compile current buffer in real time" 1341 | :lighter coffee-live-comp-mode-line 1342 | (if coffee-live-compile-mode 1343 | (add-hook 'after-change-functions 'coffee--live-compile nil t) 1344 | (remove-hook 'after-change-functions 'coffee--live-compile t))) 1345 | 1346 | (provide 'coffee-mode) 1347 | 1348 | ;; 1349 | ;; On Load 1350 | ;; 1351 | 1352 | ;; Run coffee-mode for files ending in .coffee. 1353 | ;;;###autoload 1354 | (add-to-list 'auto-mode-alist '("\\.coffee\\'" . coffee-mode)) 1355 | ;;;###autoload 1356 | (add-to-list 'auto-mode-alist '("\\.iced\\'" . coffee-mode)) 1357 | ;;;###autoload 1358 | (add-to-list 'auto-mode-alist '("Cakefile\\'" . coffee-mode)) 1359 | ;;;###autoload 1360 | (add-to-list 'auto-mode-alist '("\\.cson\\'" . coffee-mode)) 1361 | ;;;###autoload 1362 | (add-to-list 'interpreter-mode-alist '("coffee" . coffee-mode)) 1363 | 1364 | ;;; coffee-mode.el ends here 1365 | --------------------------------------------------------------------------------