├── .gitignore ├── README.md ├── clojure-mode.el ├── clojure-test-mode.el └── test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | # temporary files 2 | *~ 3 | *\#*\# 4 | *.\#* 5 | 6 | # Emacs byte-compiled files 7 | *.elc 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clojure Mode 2 | 3 | NOTE: This repository is deprecated; see [technomancy](http://github.com/technomancy/clojure-mode). 4 | 5 | Provides font-lock, indentation, and navigation for the 6 | [Clojure language](http://clojure.org). 7 | 8 | ## Installation 9 | 10 | It's easiest to install and keep Clojure Mode updated using 11 | [package.el](http://pkg-el23). 12 | 13 | (require 'package) 14 | (add-to-list 'package-archives 15 | '("marmalade" . "http://marmalade-repo.org/packages/")) 16 | (package-initialize) 17 | 18 | M-x package-install clojure-mode 19 | 20 | If you use a version of Emacs prior to 24 that doesn't include 21 | package.el, you can get it from http://bit.ly/pkg-el23. If you have an 22 | older package.el installed from tromey.com, you should upgrade in 23 | order to support installation from multiple sources. 24 | 25 | Of course, it's possible to just place it on your load-path and 26 | require it as well if you don't mind missing out on 27 | byte-compilation and autoloads. 28 | 29 | ## Paredit 30 | 31 | Using clojure-mode with paredit is highly recommended. It is also 32 | available using package.el from the above archive. 33 | 34 | Use paredit as you normally would with any other mode; for instance: 35 | 36 | ;; (require 'paredit) if you didn't install via package.el 37 | (defun turn-on-paredit () (paredit-mode 1)) 38 | (add-hook 'clojure-mode-hook 'turn-on-paredit) 39 | 40 | ## SLIME 41 | 42 | You can use Leiningen (http://github.com/technomancy/leiningen) for 43 | better interaction with subprocesses via SLIME. 44 | 45 | $ wget https://github.com/technomancy/leiningen/raw/stable/bin/lein 46 | [place the "lein" script on your $PATH and make it executable] 47 | $ lein plugin install swank-clojure 1.3.1 48 | 49 | M-x clojure-jack-in # from inside a project 50 | 51 | ## License 52 | 53 | Copyright © 2007-2011 Jeffrey Chu, Lennart Staflin, Phil Hagelberg 54 | 55 | Distributed under the GNU General Public License; see C-h t to view. 56 | -------------------------------------------------------------------------------- /clojure-mode.el: -------------------------------------------------------------------------------- 1 | ;;; clojure-mode.el --- Major mode for Clojure code 2 | 3 | ;; Copyright (C) 2007-2011 Jeffrey Chu, Lennart Staflin, Phil Hagelberg 4 | ;; 5 | ;; Authors: Jeffrey Chu 6 | ;; Lennart Staflin 7 | ;; Phil Hagelberg 8 | ;; URL: http://github.com/technomancy/clojure-mode 9 | ;; Version: 1.9.2 10 | ;; Keywords: languages, lisp 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;;; Commentary: 15 | 16 | ;; Provides font-lock, indentation, and navigation for the Clojure 17 | ;; language. (http://clojure.org) 18 | 19 | ;; Users of older Emacs (pre-22) should get version 1.4: 20 | ;; http://github.com/technomancy/clojure-mode/tree/1.4 21 | 22 | ;;; Installation: 23 | 24 | ;; Use package.el. You'll need to add Marmalade to your archives: 25 | 26 | ;; (require 'package) 27 | ;; (add-to-list 'package-archives 28 | ;; '("marmalade" . "http://marmalade-repo.org/packages/")) 29 | 30 | ;; If you use a version of Emacs prior to 24 that doesn't include 31 | ;; package.el, you can get it from http://bit.ly/pkg-el23. If you have 32 | ;; an older package.el installed from tromey.com, you should upgrade 33 | ;; in order to support installation from multiple sources. 34 | 35 | ;; Of course, it's possible to just place it on your load-path and 36 | ;; require it as well if you don't mind missing out on 37 | ;; byte-compilation and autoloads. 38 | 39 | ;; Using clojure-mode with paredit is highly recommended. It is also 40 | ;; available using package.el from the above archive. 41 | 42 | ;; Use paredit as you normally would with any other mode; for instance: 43 | ;; 44 | ;; ;; require or autoload paredit-mode 45 | ;; (defun turn-on-paredit () (paredit-mode 1)) 46 | ;; (add-hook 'clojure-mode-hook 'turn-on-paredit) 47 | 48 | ;; See Swank Clojure (http://github.com/technomancy/swank-clojure) for 49 | ;; better interaction with subprocesses via SLIME. 50 | 51 | ;;; License: 52 | 53 | ;; This program is free software; you can redistribute it and/or 54 | ;; modify it under the terms of the GNU General Public License 55 | ;; as published by the Free Software Foundation; either version 3 56 | ;; of the License, or (at your option) any later version. 57 | ;; 58 | ;; This program is distributed in the hope that it will be useful, 59 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 60 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 61 | ;; GNU General Public License for more details. 62 | ;; 63 | ;; You should have received a copy of the GNU General Public License 64 | ;; along with GNU Emacs; see the file COPYING. If not, write to the 65 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 66 | ;; Boston, MA 02110-1301, USA. 67 | 68 | ;;; Code: 69 | 70 | (require 'cl) 71 | 72 | (defgroup clojure-mode nil 73 | "A mode for Clojure" 74 | :prefix "clojure-mode-" 75 | :group 'applications) 76 | 77 | (defcustom clojure-mode-font-lock-comment-sexp nil 78 | "Set to non-nil in order to enable font-lock of (comment...) 79 | forms. This option is experimental. Changing this will require a 80 | restart (ie. M-x clojure-mode) of existing clojure mode buffers." 81 | :type 'boolean 82 | :group 'clojure-mode) 83 | 84 | (defcustom clojure-mode-load-command "(clojure.core/load-file \"%s\")\n" 85 | "*Format-string for building a Clojure expression to load a file. 86 | This format string should use `%s' to substitute a file name 87 | and should result in a Clojure expression that will command the inferior 88 | Clojure to load that file." 89 | :type 'string 90 | :group 'clojure-mode) 91 | 92 | (defcustom clojure-mode-use-backtracking-indent t 93 | "Set to non-nil to enable backtracking/context sensitive indentation." 94 | :type 'boolean 95 | :group 'clojure-mode) 96 | 97 | (defcustom clojure-max-backtracking 3 98 | "Maximum amount to backtrack up a list to check for context." 99 | :type 'integer 100 | :group 'clojure-mode) 101 | 102 | (defvar clojure-mode-map 103 | (let ((map (make-sparse-keymap))) 104 | (set-keymap-parent map lisp-mode-shared-map) 105 | (define-key map "\e\C-x" 'lisp-eval-defun) 106 | (define-key map "\C-x\C-e" 'lisp-eval-last-sexp) 107 | (define-key map "\C-c\C-e" 'lisp-eval-last-sexp) 108 | (define-key map "\C-c\C-l" 'clojure-load-file) 109 | (define-key map "\C-c\C-r" 'lisp-eval-region) 110 | (define-key map "\C-c\C-z" 'run-lisp) 111 | (define-key map (kbd "RET") 'reindent-then-newline-and-indent) 112 | (define-key map (kbd "C-c t") 'clojure-jump-to-test) 113 | map) 114 | "Keymap for Clojure mode. Inherits from `lisp-mode-shared-map'.") 115 | 116 | (defvar clojure-mode-syntax-table 117 | (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table))) 118 | (modify-syntax-entry ?~ "' " table) 119 | ;; can't safely make commas whitespace since it will apply even 120 | ;; inside string literals--ick! 121 | ;; (modify-syntax-entry ?, " " table) 122 | (modify-syntax-entry ?\{ "(}" table) 123 | (modify-syntax-entry ?\} "){" table) 124 | (modify-syntax-entry ?\[ "(]" table) 125 | (modify-syntax-entry ?\] ")[" table) 126 | (modify-syntax-entry ?^ "'" table) 127 | table)) 128 | 129 | (defvar clojure-mode-abbrev-table nil 130 | "Abbrev table used in clojure-mode buffers.") 131 | 132 | (define-abbrev-table 'clojure-mode-abbrev-table ()) 133 | 134 | (defvar clojure-prev-l/c-dir/file nil 135 | "Record last directory and file used in loading or compiling. 136 | This holds a cons cell of the form `(DIRECTORY . FILE)' 137 | describing the last `clojure-load-file' or `clojure-compile-file' command.") 138 | 139 | (defvar clojure-def-regexp "^\\s *(def\\S *\\s +\\(?:\\^\\S +\\s +\\)?\\([^ \n\t]+\\)" 140 | "A regular expression to match any top-level definitions.") 141 | 142 | (defvar clojure-test-ns-segment-position -1 143 | "Which segment of the ns is \"test\" inserted in your test name convention. 144 | 145 | Customize this depending on your project's conventions. Negative 146 | numbers count from the end: 147 | 148 | leiningen.compile -> leiningen.test.compile (uses 1) 149 | clojure.http.client -> clojure.http.test.client (uses -1)") 150 | 151 | (defun clojure-mode-version () 152 | "Currently package.el doesn't support prerelease version numbers." 153 | "1.9.0") 154 | 155 | ;;;###autoload 156 | (defun clojure-mode () 157 | "Major mode for editing Clojure code - similar to Lisp mode. 158 | Commands: 159 | Delete converts tabs to spaces as it moves back. 160 | Blank lines separate paragraphs. Semicolons start comments. 161 | \\{clojure-mode-map} 162 | Note that `run-lisp' may be used either to start an inferior Lisp job 163 | or to switch back to an existing one. 164 | 165 | Entry to this mode calls the value of `clojure-mode-hook' 166 | if that value is non-nil." 167 | (interactive) 168 | (kill-all-local-variables) 169 | (use-local-map clojure-mode-map) 170 | (setq mode-name "Clojure" 171 | major-mode 'clojure-mode 172 | imenu-create-index-function 173 | (lambda () 174 | (imenu--generic-function `((nil ,clojure-def-regexp 1)))) 175 | local-abbrev-table clojure-mode-abbrev-table 176 | indent-tabs-mode nil) 177 | (lisp-mode-variables nil) 178 | (set-syntax-table clojure-mode-syntax-table) 179 | (set (make-local-variable 'comment-start-skip) 180 | "\\(\\(^\\|[^\\\\\n]\\)\\(\\\\\\\\\\)*\\)\\(;+\\|#|\\) *") 181 | (set (make-local-variable 'lisp-indent-function) 182 | 'clojure-indent-function) 183 | (when (< emacs-major-version 24) 184 | (set (make-local-variable 'forward-sexp-function) 185 | 'clojure-forward-sexp)) 186 | (set (make-local-variable 'lisp-doc-string-elt-property) 187 | 'clojure-doc-string-elt) 188 | 189 | (clojure-mode-font-lock-setup) 190 | 191 | (run-mode-hooks 'clojure-mode-hook) 192 | (run-hooks 'prog-mode-hook) 193 | 194 | ;; Enable curly braces when paredit is enabled in clojure-mode-hook 195 | (when (and (featurep 'paredit) paredit-mode (>= paredit-version 21)) 196 | (define-key clojure-mode-map "{" 'paredit-open-curly) 197 | (define-key clojure-mode-map "}" 'paredit-close-curly))) 198 | 199 | (defun clojure-load-file (file-name) 200 | "Load a Lisp file into the inferior Lisp process." 201 | (interactive (comint-get-source "Load Clojure file: " 202 | clojure-prev-l/c-dir/file 203 | '(clojure-mode) t)) 204 | (comint-check-source file-name) ; Check to see if buffer needs saved. 205 | (setq clojure-prev-l/c-dir/file (cons (file-name-directory file-name) 206 | (file-name-nondirectory file-name))) 207 | (comint-send-string (inferior-lisp-proc) 208 | (format clojure-mode-load-command file-name)) 209 | (switch-to-lisp t)) 210 | 211 | 212 | 213 | (defun clojure-mode-font-lock-setup () 214 | "Configures font-lock for editing Clojure code." 215 | (interactive) 216 | (set (make-local-variable 'font-lock-multiline) t) 217 | (add-to-list 'font-lock-extend-region-functions 218 | 'clojure-font-lock-extend-region-def t) 219 | 220 | (when clojure-mode-font-lock-comment-sexp 221 | (add-to-list 'font-lock-extend-region-functions 222 | 'clojure-font-lock-extend-region-comment t) 223 | (make-local-variable 'clojure-font-lock-keywords) 224 | (add-to-list 'clojure-font-lock-keywords 225 | 'clojure-font-lock-mark-comment t) 226 | (set (make-local-variable 'open-paren-in-column-0-is-defun-start) nil)) 227 | 228 | (setq font-lock-defaults 229 | '(clojure-font-lock-keywords ; keywords 230 | nil nil 231 | (("+-*/.<>=!?$%_&~^:@" . "w")) ; syntax alist 232 | nil 233 | (font-lock-mark-block-function . mark-defun) 234 | (font-lock-syntactic-face-function 235 | . lisp-font-lock-syntactic-face-function)))) 236 | 237 | (defun clojure-font-lock-def-at-point (point) 238 | "Find the position range between the top-most def* and the 239 | fourth element afterwards. Note that this means there's no 240 | gaurantee of proper font locking in def* forms that are not at 241 | top-level." 242 | (goto-char point) 243 | (condition-case nil 244 | (beginning-of-defun) 245 | (error nil)) 246 | 247 | (let ((beg-def (point))) 248 | (when (and (not (= point beg-def)) 249 | (looking-at "(def")) 250 | (condition-case nil 251 | (progn 252 | ;; move forward as much as possible until failure (or success) 253 | (forward-char) 254 | (dotimes (i 4) 255 | (forward-sexp))) 256 | (error nil)) 257 | (cons beg-def (point))))) 258 | 259 | (defun clojure-font-lock-extend-region-def () 260 | "Move fontification boundaries to always include the first four 261 | elements of a def* forms." 262 | (let ((changed nil)) 263 | (let ((def (clojure-font-lock-def-at-point font-lock-beg))) 264 | (when def 265 | (destructuring-bind (def-beg . def-end) def 266 | (when (and (< def-beg font-lock-beg) 267 | (< font-lock-beg def-end)) 268 | (setq font-lock-beg def-beg 269 | changed t))))) 270 | 271 | (let ((def (clojure-font-lock-def-at-point font-lock-end))) 272 | (when def 273 | (destructuring-bind (def-beg . def-end) def 274 | (when (and (< def-beg font-lock-end) 275 | (< font-lock-end def-end)) 276 | (setq font-lock-end def-end 277 | changed t))))) 278 | changed)) 279 | 280 | (defun clojure-font-lock-extend-region-comment () 281 | "Move fontification boundaries to always contain 282 | entire (comment ..) sexp. Does not work if you have a 283 | white-space between ( and comment, but that is omitted to make 284 | this run faster." 285 | (let ((changed nil)) 286 | (goto-char font-lock-beg) 287 | (condition-case nil (beginning-of-defun) (error nil)) 288 | (let ((pos (re-search-forward "(comment\\>" font-lock-end t))) 289 | (when pos 290 | (forward-char -8) 291 | (when (< (point) font-lock-beg) 292 | (setq font-lock-beg (point) 293 | changed t)) 294 | (condition-case nil (forward-sexp) (error nil)) 295 | (when (> (point) font-lock-end) 296 | (setq font-lock-end (point) 297 | changed t)))) 298 | changed)) 299 | 300 | (defun clojure-font-lock-mark-comment (limit) 301 | "Marks all (comment ..) forms with font-lock-comment-face." 302 | (let (pos) 303 | (while (and (< (point) limit) 304 | (setq pos (re-search-forward "(comment\\>" limit t))) 305 | (when pos 306 | (forward-char -8) 307 | (condition-case nil 308 | (add-text-properties (1+ (point)) (progn 309 | (forward-sexp) (1- (point))) 310 | '(face font-lock-comment-face multiline t)) 311 | (error (forward-char 8)))))) 312 | nil) 313 | 314 | (defconst clojure-font-lock-keywords 315 | (eval-when-compile 316 | `( ;; Definitions. 317 | (,(concat "(\\(?:clojure.core/\\)?\\(" 318 | (regexp-opt '("defn" "defn-" "def" "def-" "defonce" 319 | "defmulti" "defmethod" "defmacro" 320 | "defstruct" "deftype" "defprotocol" 321 | "defrecord" "deftest" 322 | "slice" "def\\[a-z\\]" 323 | "defalias" "defhinted" "defmacro-" 324 | "defn-memo" "defnk" "defonce-" 325 | "defstruct-" "defunbound" "defunbound-" 326 | "defvar" "defvar-")) 327 | ;; Function declarations. 328 | "\\)\\>" 329 | ;; Any whitespace 330 | "[ \r\n\t]*" 331 | ;; Possibly type or metadata 332 | "\\(?:#?^\\(?:{[^}]*}\\|\\sw+\\)[ \r\n\t]*\\)*" 333 | "\\(\\sw+\\)?") 334 | (1 font-lock-keyword-face) 335 | (2 font-lock-function-name-face nil t)) 336 | ;; Deprecated functions 337 | (,(concat 338 | "(\\(?:clojure.core/\\)?" 339 | (regexp-opt 340 | '("add-watcher" "remove-watcher" "add-classpath") t) 341 | "\\>") 342 | 1 font-lock-warning-face) 343 | ;; Control structures 344 | (,(concat 345 | "(\\(?:clojure.core/\\)?" 346 | (regexp-opt 347 | '("let" "letfn" "do" 348 | "cond" "condp" 349 | "for" "loop" "recur" 350 | "when" "when-not" "when-let" "when-first" 351 | "if" "if-let" "if-not" 352 | "." ".." "->" "->>" "doto" 353 | "and" "or" 354 | "dosync" "doseq" "dotimes" "dorun" "doall" 355 | "load" "import" "unimport" "ns" "in-ns" "refer" 356 | "try" "catch" "finally" "throw" 357 | "with-open" "with-local-vars" "binding" 358 | "gen-class" "gen-and-load-class" "gen-and-save-class" 359 | "handler-case" "handle") t) 360 | "\\>") 361 | . 1) 362 | ;; Built-ins 363 | (,(concat 364 | "(\\(?:clojure.core/\\)?" 365 | (regexp-opt 366 | '("*" "*1" "*2" "*3" "*agent*" 367 | "*allow-unresolved-vars*" "*assert*" "*clojure-version*" "*command-line-args*" "*compile-files*" 368 | "*compile-path*" "*e" "*err*" "*file*" "*flush-on-newline*" 369 | "*in*" "*macro-meta*" "*math-context*" "*ns*" "*out*" 370 | "*print-dup*" "*print-length*" "*print-level*" "*print-meta*" "*print-readably*" 371 | "*read-eval*" "*source-path*" "*use-context-classloader*" "*warn-on-reflection*" "+" 372 | "-" "/" 373 | "<" "<=" "=" "==" ">" 374 | ">=" "accessor" "aclone" 375 | "agent" "agent-errors" "aget" "alength" "alias" 376 | "all-ns" "alter" "alter-meta!" "alter-var-root" "amap" 377 | "ancestors" "and" "apply" "areduce" "array-map" 378 | "aset" "aset-boolean" "aset-byte" "aset-char" "aset-double" 379 | "aset-float" "aset-int" "aset-long" "aset-short" "assert" 380 | "assoc" "assoc!" "assoc-in" "associative?" "atom" 381 | "await" "await-for" "await1" "bases" "bean" 382 | "bigdec" "bigint" "binding" "bit-and" "bit-and-not" 383 | "bit-clear" "bit-flip" "bit-not" "bit-or" "bit-set" 384 | "bit-shift-left" "bit-shift-right" "bit-test" "bit-xor" "boolean" 385 | "boolean-array" "booleans" "bound-fn" "bound-fn*" "butlast" 386 | "byte" "byte-array" "bytes" "cast" "char" 387 | "char-array" "char-escape-string" "char-name-string" "char?" "chars" 388 | "chunk" "chunk-append" "chunk-buffer" "chunk-cons" "chunk-first" 389 | "chunk-next" "chunk-rest" "chunked-seq?" "class" "class?" 390 | "clear-agent-errors" "clojure-version" "coll?" "comment" "commute" 391 | "comp" "comparator" "compare" "compare-and-set!" "compile" 392 | "complement" "concat" "cond" "condp" "conj" 393 | "conj!" "cons" "constantly" "construct-proxy" "contains?" 394 | "count" "counted?" "create-ns" "create-struct" "cycle" 395 | "dec" "decimal?" "declare" "definline" "defmacro" 396 | "defmethod" "defmulti" "defn" "defn-" "defonce" 397 | "defstruct" "delay" "delay?" "deliver" "deref" 398 | "derive" "descendants" "destructure" "disj" "disj!" 399 | "dissoc" "dissoc!" "distinct" "distinct?" "doall" 400 | "doc" "dorun" "doseq" "dosync" "dotimes" 401 | "doto" "double" "double-array" "doubles" "drop" 402 | "drop-last" "drop-while" "empty" "empty?" "ensure" 403 | "enumeration-seq" "eval" "even?" "every?" 404 | "extend" "extend-protocol" "extend-type" "extends?" "extenders" 405 | "false?" "ffirst" "file-seq" "filter" "find" "find-doc" 406 | "find-ns" "find-var" "first" "float" "float-array" 407 | "float?" "floats" "flush" "fn" "fn?" 408 | "fnext" "for" "force" "format" "future" 409 | "future-call" "future-cancel" "future-cancelled?" "future-done?" "future?" 410 | "gen-class" "gen-interface" "gensym" "get" "get-in" 411 | "get-method" "get-proxy-class" "get-thread-bindings" "get-validator" "hash" 412 | "hash-map" "hash-set" "identical?" "identity" "if-let" 413 | "if-not" "ifn?" "import" "in-ns" "inc" 414 | "init-proxy" "instance?" "int" "int-array" "integer?" 415 | "interleave" "intern" "interpose" "into" "into-array" 416 | "ints" "io!" "isa?" "iterate" "iterator-seq" 417 | "juxt" "key" "keys" "keyword" "keyword?" 418 | "last" "lazy-cat" "lazy-seq" "let" "letfn" 419 | "line-seq" "list" "list*" "list?" "load" 420 | "load-file" "load-reader" "load-string" "loaded-libs" "locking" 421 | "long" "long-array" "longs" "loop" "macroexpand" 422 | "macroexpand-1" "make-array" "make-hierarchy" "map" "map?" 423 | "mapcat" "max" "max-key" "memfn" "memoize" 424 | "merge" "merge-with" "meta" "method-sig" "methods" 425 | "min" "min-key" "mod" "name" "namespace" 426 | "neg?" "newline" "next" "nfirst" "nil?" 427 | "nnext" "not" "not-any?" "not-empty" "not-every?" 428 | "not=" "ns" "ns-aliases" "ns-imports" "ns-interns" 429 | "ns-map" "ns-name" "ns-publics" "ns-refers" "ns-resolve" 430 | "ns-unalias" "ns-unmap" "nth" "nthnext" "num" 431 | "number?" "odd?" "or" "parents" "partial" 432 | "partition" "pcalls" "peek" "persistent!" "pmap" 433 | "pop" "pop!" "pop-thread-bindings" "pos?" "pr" 434 | "pr-str" "prefer-method" "prefers" "primitives-classnames" "print" 435 | "print-ctor" "print-doc" "print-dup" "print-method" "print-namespace-doc" 436 | "print-simple" "print-special-doc" "print-str" "printf" "println" 437 | "println-str" "prn" "prn-str" "promise" "proxy" 438 | "proxy-call-with-super" "proxy-mappings" "proxy-name" "proxy-super" "push-thread-bindings" 439 | "pvalues" "quot" "rand" "rand-int" "range" 440 | "ratio?" "rational?" "rationalize" "re-find" "re-groups" 441 | "re-matcher" "re-matches" "re-pattern" "re-seq" "read" 442 | "read-line" "read-string" "reify" "reduce" "ref" "ref-history-count" 443 | "ref-max-history" "ref-min-history" "ref-set" "refer" "refer-clojure" 444 | "release-pending-sends" "rem" "remove" "remove-method" "remove-ns" 445 | "repeat" "repeatedly" "replace" "replicate" 446 | "require" "reset!" "reset-meta!" "resolve" "rest" 447 | "resultset-seq" "reverse" "reversible?" "rseq" "rsubseq" 448 | "satisfies?" "second" "select-keys" "send" "send-off" "seq" 449 | "seq?" "seque" "sequence" "sequential?" "set" 450 | "set-validator!" "set?" "short" "short-array" "shorts" 451 | "shutdown-agents" "slurp" "some" "sort" "sort-by" 452 | "sorted-map" "sorted-map-by" "sorted-set" "sorted-set-by" "sorted?" 453 | "special-form-anchor" "special-symbol?" "split-at" "split-with" "str" 454 | "stream?" "string?" "struct" "struct-map" "subs" 455 | "subseq" "subvec" "supers" "swap!" "symbol" 456 | "symbol?" "sync" "syntax-symbol-anchor" "take" "take-last" 457 | "take-nth" "take-while" "test" "the-ns" "time" 458 | "to-array" "to-array-2d" "trampoline" "transient" "tree-seq" 459 | "true?" "type" "unchecked-add" "unchecked-dec" "unchecked-divide" 460 | "unchecked-inc" "unchecked-multiply" "unchecked-negate" "unchecked-remainder" "unchecked-subtract" 461 | "underive" "unquote" "unquote-splicing" "update-in" "update-proxy" 462 | "use" "val" "vals" "var-get" "var-set" 463 | "var?" "vary-meta" "vec" "vector" "vector?" 464 | "when" "when-first" "when-let" "when-not" "while" 465 | "with-bindings" "with-bindings*" "with-in-str" "with-loading-context" "with-local-vars" 466 | "with-meta" "with-open" "with-out-str" "with-precision" "xml-seq" 467 | ) t) 468 | "\\>") 469 | 1 font-lock-builtin-face) 470 | ;; (fn name? args ...) 471 | (,(concat "(\\(?:clojure.core/\\)?\\(fn\\)[ \t]+" 472 | ;; Possibly type 473 | "\\(?:#?^\\sw+[ \t]*\\)?" 474 | ;; Possibly name 475 | "\\(\\sw+\\)?" ) 476 | (1 font-lock-keyword-face) 477 | (2 font-lock-function-name-face nil t)) 478 | ;;Other namespaces in clojure.jar 479 | (,(concat 480 | "(\\(?:\.*/\\)?" 481 | (regexp-opt 482 | '(;; clojure.inspector 483 | "atom?" "collection-tag" "get-child" "get-child-count" "inspect" 484 | "inspect-table" "inspect-tree" "is-leaf" "list-model" "list-provider" 485 | ;; clojure.main 486 | "load-script" "main" "repl" "repl-caught" "repl-exception" 487 | "repl-prompt" "repl-read" "skip-if-eol" "skip-whitespace" "with-bindings" 488 | ;; clojure.set 489 | "difference" "index" "intersection" "join" "map-invert" 490 | "project" "rename" "rename-keys" "select" "union" 491 | ;; clojure.stacktrace 492 | "e" "print-cause-trace" "print-stack-trace" "print-throwable" "print-trace-element" 493 | ;; clojure.template 494 | "do-template" "apply-template" 495 | ;; clojure.test 496 | "*initial-report-counters*" "*load-tests*" "*report-counters*" "*stack-trace-depth*" "*test-out*" 497 | "*testing-contexts*" "*testing-vars*" "are" "assert-any" "assert-expr" 498 | "assert-predicate" "compose-fixtures" "deftest" "deftest-" "file-position" 499 | "function?" "get-possibly-unbound-var" "inc-report-counter" "is" "join-fixtures" 500 | "report" "run-all-tests" "run-tests" "set-test" "successful?" 501 | "test-all-vars" "test-ns" "test-var" "testing" "testing-contexts-str" 502 | "testing-vars-str" "try-expr" "use-fixtures" "with-test" "with-test-out" 503 | ;; clojure.walk 504 | "keywordize-keys" "macroexpand-all" "postwalk" "postwalk-demo" "postwalk-replace" 505 | "prewalk" "prewalk-demo" "prewalk-replace" "stringify-keys" "walk" 506 | ;; clojure.xml 507 | "*current*" "*sb*" "*stack*" "*state*" "attrs" 508 | "content" "content-handler" "element" "emit" "emit-element" 509 | ;; clojure.zip 510 | "append-child" "branch?" "children" "down" "edit" 511 | "end?" "insert-child" "insert-left" "insert-right" "left" 512 | "leftmost" "lefts" "make-node" "next" "node" 513 | "path" "prev" "remove" "replace" "right" 514 | "rightmost" "rights" "root" "seq-zip" "up" 515 | ) t) 516 | "\\>") 517 | 1 font-lock-type-face) 518 | ;; Constant values (keywords), including as metadata e.g. ^:static 519 | ("\\<^?:\\(\\sw\\|#\\)+\\>" 0 font-lock-builtin-face) 520 | ;; Meta type annotation #^Type or ^Type 521 | ("#?^\\sw+" 0 font-lock-type-face) 522 | ("\\" 0 font-lock-warning-face) 523 | 524 | ;;Java interop highlighting 525 | ("\\<\\.[a-z][a-zA-Z0-9]*\\>" 0 font-lock-preprocessor-face) ;; .foo .barBaz .qux01 526 | ("\\<[A-Z][a-zA-Z0-9]*/[a-zA-Z0-9/$_]+\\>" 0 font-lock-preprocessor-face) ;; Foo Bar$Baz Qux_ 527 | ("\\<[a-zA-Z]+\\.[a-zA-Z0-9._]*[A-Z]+[a-zA-Z0-9/.$]*\\>" 0 font-lock-preprocessor-face) ;; Foo/Bar foo.bar.Baz foo.Bar/baz 528 | ("[a-z]*[A-Z]+[a-z][a-zA-Z0-9$]*\\>" 0 font-lock-preprocessor-face) ;; fooBar 529 | ("\\<[A-Z][a-zA-Z0-9$]*\\.\\>" 0 font-lock-preprocessor-face))) ;; Foo. BarBaz. Qux$Quux. Corge9. 530 | 531 | 532 | "Default expressions to highlight in Clojure mode.") 533 | 534 | ;; Docstring positions 535 | (put 'defn 'clojure-doc-string-elt 2) 536 | (put 'defn- 'clojure-doc-string-elt 2) 537 | (put 'defmulti 'clojure-doc-string-elt 2) 538 | (put 'defmacro 'clojure-doc-string-elt 2) 539 | (put 'definline 'clojure-doc-string-elt 2) 540 | (put 'defprotocol 'clojure-doc-string-elt 2) 541 | 542 | ;; Docstring positions - contrib 543 | (put 'defalias 'clojure-doc-string-elt 3) 544 | (put 'defmacro- 'clojure-doc-string-elt 2) 545 | (put 'defn-memo 'clojure-doc-string-elt 2) 546 | (put 'defnk 'clojure-doc-string-elt 2) 547 | (put 'defonce- 'clojure-doc-string-elt 3) 548 | (put 'defunbound 'clojure-doc-string-elt 2) 549 | (put 'defunbound- 'clojure-doc-string-elt 2) 550 | (put 'defvar 'clojure-doc-string-elt 3) 551 | (put 'defvar- 'clojure-doc-string-elt 3) 552 | 553 | 554 | 555 | (defun clojure-forward-sexp (n) 556 | "Treat record literals like #user.Foo[1] and #user.Foo{:size 1} 557 | as a single sexp so that slime will send them properly. Arguably 558 | this behavior is unintuitive for the user pressing (eg) C-M-f 559 | himself, but since these are single objects I think it's right." 560 | (let ((dir (if (> n 0) 1 -1)) 561 | (forward-sexp-function nil)) ; force the built-in version 562 | (while (not (zerop n)) 563 | (forward-sexp dir) 564 | (when (save-excursion ; move back to see if we're in a record literal 565 | (and 566 | (condition-case nil 567 | (progn (backward-sexp) 't) 568 | ('scan-error nil)) 569 | (looking-at "#\\w"))) 570 | (forward-sexp dir)) ; if so, jump over it 571 | (setq n (- n dir))))) 572 | 573 | (defun clojure-indent-function (indent-point state) 574 | "This function is the normal value of the variable `lisp-indent-function'. 575 | It is used when indenting a line within a function call, to see if the 576 | called function says anything special about how to indent the line. 577 | 578 | INDENT-POINT is the position where the user typed TAB, or equivalent. 579 | Point is located at the point to indent under (for default indentation); 580 | STATE is the `parse-partial-sexp' state for that position. 581 | 582 | If the current line is in a call to a Lisp function 583 | which has a non-nil property `lisp-indent-function', 584 | that specifies how to do the indentation. The property value can be 585 | * `defun', meaning indent `defun'-style; 586 | * an integer N, meaning indent the first N arguments specially 587 | like ordinary function arguments and then indent any further 588 | arguments like a body; 589 | * a function to call just as this function was called. 590 | If that function returns nil, that means it doesn't specify 591 | the indentation. 592 | 593 | This function also returns nil meaning don't specify the indentation." 594 | (let ((normal-indent (current-column))) 595 | (goto-char (1+ (elt state 1))) 596 | (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t) 597 | (if (and (elt state 2) 598 | (not (looking-at "\\sw\\|\\s_"))) 599 | ;; car of form doesn't seem to be a symbol 600 | (progn 601 | (if (not (> (save-excursion (forward-line 1) (point)) 602 | calculate-lisp-indent-last-sexp)) 603 | (progn (goto-char calculate-lisp-indent-last-sexp) 604 | (beginning-of-line) 605 | (parse-partial-sexp (point) 606 | calculate-lisp-indent-last-sexp 0 t))) 607 | ;; Indent under the list or under the first sexp on the same 608 | ;; line as calculate-lisp-indent-last-sexp. Note that first 609 | ;; thing on that line has to be complete sexp since we are 610 | ;; inside the innermost containing sexp. 611 | (backward-prefix-chars) 612 | (if (and (eq (char-after (point)) ?\[) 613 | (eq (char-after (elt state 1)) ?\()) 614 | (+ (current-column) 2) ;; this is probably inside a defn 615 | (current-column))) 616 | (let ((function (buffer-substring (point) 617 | (progn (forward-sexp 1) (point)))) 618 | (open-paren (elt state 1)) 619 | method) 620 | (setq method (get (intern-soft function) 'clojure-indent-function)) 621 | 622 | (cond ((member (char-after open-paren) '(?\[ ?\{)) 623 | (goto-char open-paren) 624 | (1+ (current-column))) 625 | ((or (eq method 'defun) 626 | (and (null method) 627 | (> (length function) 3) 628 | (string-match "\\`\\(?:\\S +/\\)?def\\|with-" 629 | function))) 630 | (lisp-indent-defform state indent-point)) 631 | 632 | ((integerp method) 633 | (lisp-indent-specform method state 634 | indent-point normal-indent)) 635 | (method 636 | (funcall method indent-point state)) 637 | (clojure-mode-use-backtracking-indent 638 | (clojure-backtracking-indent 639 | indent-point state normal-indent))))))) 640 | 641 | (defun clojure-backtracking-indent (indent-point state normal-indent) 642 | "Experimental backtracking support. Will upwards in an sexp to 643 | check for contextual indenting." 644 | (let (indent (path) (depth 0)) 645 | (goto-char (elt state 1)) 646 | (while (and (not indent) 647 | (< depth clojure-max-backtracking)) 648 | (let ((containing-sexp (point))) 649 | (parse-partial-sexp (1+ containing-sexp) indent-point 1 t) 650 | (when (looking-at "\\sw\\|\\s_") 651 | (let* ((start (point)) 652 | (fn (buffer-substring start (progn (forward-sexp 1) (point)))) 653 | (meth (get (intern-soft fn) 'clojure-backtracking-indent))) 654 | (let ((n 0)) 655 | (when (< (point) indent-point) 656 | (condition-case () 657 | (progn 658 | (forward-sexp 1) 659 | (while (< (point) indent-point) 660 | (parse-partial-sexp (point) indent-point 1 t) 661 | (incf n) 662 | (forward-sexp 1))) 663 | (error nil))) 664 | (push n path)) 665 | (when meth 666 | (let ((def meth)) 667 | (dolist (p path) 668 | (if (and (listp def) 669 | (< p (length def))) 670 | (setq def (nth p def)) 671 | (if (listp def) 672 | (setq def (car (last def))) 673 | (setq def nil)))) 674 | (goto-char (elt state 1)) 675 | (when def 676 | (setq indent (+ (current-column) def))))))) 677 | (goto-char containing-sexp) 678 | (condition-case () 679 | (progn 680 | (backward-up-list 1) 681 | (incf depth)) 682 | (error (setq depth clojure-max-backtracking))))) 683 | indent)) 684 | 685 | ;; clojure backtracking indent is experimental and the format for these 686 | ;; entries are subject to change 687 | (put 'implement 'clojure-backtracking-indent '(4 (2))) 688 | (put 'letfn 'clojure-backtracking-indent '((2) 2)) 689 | (put 'proxy 'clojure-backtracking-indent '(4 4 (2))) 690 | (put 'reify 'clojure-backtracking-indent '((2))) 691 | (put 'deftype 'clojure-backtracking-indent '(4 4 (2))) 692 | (put 'defrecord 'clojure-backtracking-indent '(4 4 (2))) 693 | (put 'defprotocol 'clojure-backtracking-indent '(4 (2))) 694 | (put 'extend-type 'clojure-backtracking-indent '(4 (2))) 695 | (put 'extend-protocol 'clojure-backtracking-indent '(4 (2))) 696 | 697 | (defun put-clojure-indent (sym indent) 698 | (put sym 'clojure-indent-function indent)) 699 | 700 | (defmacro define-clojure-indent (&rest kvs) 701 | `(progn 702 | ,@(mapcar (lambda (x) `(put-clojure-indent 703 | (quote ,(first x)) ,(second x))) kvs))) 704 | 705 | (defun add-custom-clojure-indents (name value) 706 | (setq clojure-defun-indents value) 707 | (mapcar (lambda (x) 708 | (put-clojure-indent x 'defun)) 709 | value)) 710 | 711 | (defcustom clojure-defun-indents nil 712 | "List of symbols to give defun-style indentation to in Clojure 713 | code, in addition to those that are built-in. You can use this to 714 | get emacs to indent your own macros the same as it does the 715 | built-ins like with-open. To set manually from lisp code, 716 | use (put-clojure-indent 'some-symbol 'defun)." 717 | :type '(repeat symbol) 718 | :group 'clojure-mode 719 | :set 'add-custom-clojure-indents) 720 | 721 | (define-clojure-indent 722 | ;; built-ins 723 | (ns 1) 724 | (fn 'defun) 725 | (def 'defun) 726 | (defn 'defun) 727 | (bound-fn 'defun) 728 | (if 1) 729 | (if-not 1) 730 | (condp 2) 731 | (when 1) 732 | (while 1) 733 | (when-not 1) 734 | (when-first 1) 735 | (do 0) 736 | (future 0) 737 | (comment 0) 738 | (doto 1) 739 | (locking 1) 740 | (proxy 2) 741 | (with-open 1) 742 | (with-precision 1) 743 | (with-local-vars 1) 744 | 745 | (reify 'defun) 746 | (deftype 2) 747 | (defrecord 2) 748 | (defprotocol 1) 749 | (extend 1) 750 | (extend-protocol 1) 751 | (extend-type 1) 752 | 753 | (try 0) 754 | (catch 2) 755 | 756 | ;; binding forms 757 | (let 1) 758 | (letfn 1) 759 | (binding 1) 760 | (loop 1) 761 | (for 1) 762 | (doseq 1) 763 | (dotimes 1) 764 | (when-let 1) 765 | (if-let 1) 766 | 767 | ;; data structures 768 | (defstruct 1) 769 | (struct-map 1) 770 | (assoc 1) 771 | 772 | (defmethod 'defun) 773 | 774 | ;; clojure.test 775 | (testing 1) 776 | (deftest 'defun) 777 | 778 | ;; contrib 779 | (handler-case 1) 780 | (handle 1) 781 | (dotrace 1) 782 | (deftrace 'defun)) 783 | 784 | 785 | 786 | (defconst *namespace-name-regex* 787 | (rx line-start 788 | "(" 789 | (zero-or-one (group (regexp "clojure.core/"))) 790 | (zero-or-one (submatch "in-")) 791 | "ns" 792 | (zero-or-one "+") 793 | (one-or-more (any whitespace "\n")) 794 | (zero-or-more (or (submatch (zero-or-one "#") 795 | "^{" 796 | (zero-or-more (not (any "}"))) 797 | "}") 798 | (zero-or-more "^:" 799 | (one-or-more (not (any whitespace))))) 800 | (one-or-more (any whitespace "\n"))) 801 | ;; why is this here? oh (in-ns 'foo) or (ns+ :user) 802 | (zero-or-one (any ":'")) 803 | (group (one-or-more (not (any "()\"" whitespace))) word-end))) 804 | 805 | ;; for testing *namespace-name-regex*, you can evaluate this code and make 806 | ;; sure foo (or whatever the namespace name is) shows up in results. some of 807 | ;; these currently fail. 808 | ;; (mapcar (lambda (s) (let ((n (string-match *namespace-name-regex* s))) 809 | ;; (if n (match-string 4 s)))) 810 | ;; '("(ns foo)" 811 | ;; "(ns 812 | ;; foo)" 813 | ;; "(ns foo.baz)" 814 | ;; "(ns ^:bar foo)" 815 | ;; "(ns ^:bar ^:baz foo)" 816 | ;; "(ns ^{:bar true} foo)" 817 | ;; "(ns #^{:bar true} foo)" 818 | ;; "(ns #^{:fail {}} foo)" 819 | ;; "(ns ^{:fail2 {}} foo.baz)" 820 | ;; "(ns ^{} foo)" 821 | ;; "(ns ^{:skip-wiki true} 822 | ;; aleph.netty 823 | ;; " 824 | ;; "(ns 825 | ;; foo)" 826 | ;; "foo")) 827 | 828 | 829 | ;;; Slime help 830 | 831 | (defvar clojure-project-root-file "project.clj") 832 | 833 | (defvar clojure-swank-command "cd %s && lein jack-in %s &") 834 | 835 | (defvar clojure-swank-port nil) 836 | 837 | ;;;###autoload 838 | (defun clojure-jack-in () 839 | (interactive) 840 | (let ((clojure-root (locate-dominating-file default-directory 841 | clojure-project-root-file))) 842 | ;; graaaahhhh--no closures in elisp (23) 843 | (setq clojure-swank-port (+ 1024 (* (random 64512)))) 844 | (when (not clojure-root) 845 | (setq clojure-root (if ido-mode 846 | (ido-read-directory-name "Project: ") 847 | (read-directory-name "Project: ")))) 848 | (shell-command (format clojure-swank-command (expand-file-name clojure-root) clojure-swank-port) 849 | "*swank*") 850 | (set-process-filter (get-buffer-process "*swank*") 851 | (lambda (process output) 852 | (with-current-buffer "*swank*" 853 | (insert output)) 854 | (when (string-match "proceed to jack in" output) 855 | (with-current-buffer "*swank*" 856 | (kill-region (save-excursion 857 | (goto-char (point-max)) 858 | (search-backward "slime-load-hook") 859 | (forward-line) 860 | (point)) 861 | (point-max))) 862 | (eval-buffer "*swank*") 863 | (slime-connect "localhost" clojure-swank-port) 864 | (set-process-filter process nil)))) 865 | (message "Starting swank server..."))) 866 | 867 | (defun clojure-find-ns () 868 | (let ((regexp *namespace-name-regex*)) 869 | (save-excursion 870 | (when (or (re-search-backward regexp nil t) 871 | (re-search-forward regexp nil t)) 872 | (match-string-no-properties 4))))) 873 | 874 | (defalias 'clojure-find-package 'clojure-find-ns) 875 | 876 | (defun clojure-enable-slime () 877 | (slime-mode t) 878 | (set (make-local-variable 'slime-find-buffer-package-function) 879 | 'clojure-find-ns)) 880 | 881 | ;;;###autoload 882 | (defun clojure-enable-slime-on-existing-buffers () 883 | (interactive) 884 | (add-hook 'clojure-mode-hook 'clojure-enable-slime) 885 | (save-window-excursion 886 | (dolist (buffer (buffer-list)) 887 | (with-current-buffer buffer 888 | (when (eq major-mode 'clojure-mode) 889 | (clojure-enable-slime)))))) 890 | 891 | ;; Test navigation: 892 | 893 | (defun clojure-underscores-for-hyphens (namespace) 894 | (replace-regexp-in-string "-" "_" namespace)) 895 | 896 | (defun clojure-test-for (namespace) 897 | (let* ((namespace (clojure-underscores-for-hyphens namespace)) 898 | (segments (split-string namespace "\\.")) 899 | (before (subseq segments 0 clojure-test-ns-segment-position)) 900 | (after (subseq segments clojure-test-ns-segment-position)) 901 | (test-segments (append before (list "test") after))) 902 | (mapconcat 'identity test-segments "/"))) 903 | 904 | (defun clojure-jump-to-test () 905 | "Jump from implementation file to test." 906 | (interactive) 907 | (find-file (format "%s/test/%s.clj" 908 | (locate-dominating-file buffer-file-name "src/") 909 | (clojure-test-for (clojure-find-ns))))) 910 | 911 | ;;;###autoload 912 | (add-hook 'slime-connected-hook 'clojure-enable-slime-on-existing-buffers) 913 | 914 | 915 | 916 | ;;;###autoload 917 | (add-to-list 'auto-mode-alist '("\\.clj$" . clojure-mode)) 918 | (add-to-list 'interpreter-mode-alist '("cake" . clojure-mode)) 919 | 920 | (provide 'clojure-mode) 921 | ;;; clojure-mode.el ends here 922 | -------------------------------------------------------------------------------- /clojure-test-mode.el: -------------------------------------------------------------------------------- 1 | ;;; clojure-test-mode.el --- Minor mode for Clojure tests 2 | 3 | ;; Copyright (C) 2009-2011 Phil Hagelberg 4 | 5 | ;; Author: Phil Hagelberg 6 | ;; URL: http://emacswiki.org/cgi-bin/wiki/ClojureTestMode 7 | ;; Version: 1.5.5 8 | ;; Keywords: languages, lisp, test 9 | ;; Package-Requires: ((slime "20091016") (clojure-mode "1.7")) 10 | 11 | ;; This file is not part of GNU Emacs. 12 | 13 | ;;; Commentary: 14 | 15 | ;; This file provides support for running Clojure tests (using the 16 | ;; clojure.test framework) via SLIME and seeing feedback in the test 17 | ;; buffer about which tests failed or errored. 18 | 19 | ;;; Installation: 20 | 21 | ;; Use package.el. You'll need to add Marmalade to your archives: 22 | 23 | ;; (require 'package) 24 | ;; (add-to-list 'package-archives 25 | ;; '("marmalade" . "http://marmalade-repo.org/packages/")) 26 | 27 | ;; If you use a version of Emacs prior to 24 that doesn't include 28 | ;; package.el, you can get it from http://bit.ly/pkg-el23. If you have 29 | ;; an older package.el installed from tromey.com, you should upgrade 30 | ;; in order to support installation from multiple sources. 31 | 32 | ;; This library does not currently support clojure.contrib.test-is 33 | ;; from Clojure Contrib's 1.0-compatibility branch. If you need it, 34 | ;; please use version 1.2 of clojure-test-mode: 35 | 36 | ;; http://github.com/technomancy/clojure-mode/tree/test-1.2 37 | 38 | ;;; Usage: 39 | 40 | ;; Once you have a SLIME session active, you can run the tests in the 41 | ;; current buffer with C-c C-,. Failing tests and errors will be 42 | ;; highlighted using overlays. To clear the overlays, use C-c k. 43 | 44 | ;; You can jump between implementation and test files with C-c t if 45 | ;; your project is laid out in a way that clojure-test-mode 46 | ;; expects. Your project root should have a src/ directory containing 47 | ;; files that correspond to their namespace. It should also have a 48 | ;; test/ directory containing files that correspond to their 49 | ;; namespace, and the test namespaces should mirror the implementation 50 | ;; namespaces with the addition of "test" as the second-to-last 51 | ;; segment of the namespace. 52 | 53 | ;; So my.project.frob would be found in src/my/project/frob.clj and 54 | ;; its tests would be in test/my/project/test/frob.clj in the 55 | ;; my.project.test.frob namespace. 56 | 57 | ;;; History: 58 | 59 | ;; 1.0: 2009-03-12 60 | ;; * Initial Release 61 | 62 | ;; 1.1: 2009-04-28 63 | ;; * Fix to work with latest version of test-is. (circa Clojure 1.0) 64 | 65 | ;; 1.2: 2009-05-19 66 | ;; * Add clojure-test-jump-to-(test|implementation). 67 | 68 | ;; 1.3: 2009-11-10 69 | ;; * Update to use clojure.test instead of clojure.contrib.test-is. 70 | ;; * Fix bug suppressing test report output in repl. 71 | 72 | ;; 1.4: 2010-05-13 73 | ;; * Fix jump-to-test 74 | ;; * Update to work with Clojure 1.2. 75 | ;; * Added next/prev problem. 76 | ;; * Depend upon slime, not swank-clojure. 77 | ;; * Don't move the mark when activating. 78 | 79 | ;; 1.5: 2010-09-16 80 | ;; * Allow customization of clojure-test-ns-segment-position. 81 | ;; * Fixes for Clojure 1.2. 82 | ;; * Check for active slime connection. 83 | ;; * Fix test toggling with negative segment-position. 84 | 85 | ;; 1.5.1: 2010-11-27 86 | ;; * Add marker between each test run. 87 | 88 | ;; 1.5.2: 2011-03-11 89 | ;; * Make clojure-test-run-tests force reload. Requires swank-clojure 1.3.0. 90 | 91 | ;; 1.5.3 2011-03-14 92 | ;; * Fix clojure-test-run-test to use fixtures. 93 | 94 | ;; 1.5.4 2011-03-16 95 | ;; * Fix clojure-test-run-tests to wait until tests are reloaded. 96 | 97 | ;; 1.5.5 2011-04-08 98 | ;; * Fix coloring/reporting 99 | ;; * Don't trigger slime-connected-hook. 100 | 101 | ;;; TODO: 102 | 103 | ;; * Prefix arg to jump-to-impl should open in other window 104 | ;; * Put Testing indicator in modeline while tests are running 105 | ;; * Implement next-problem command 106 | ;; * Error messages need line number. 107 | ;; * Currently show-message needs point to be on the line with the 108 | ;; "is" invocation; this could be cleaned up. 109 | 110 | ;;; Code: 111 | 112 | (require 'clojure-mode) 113 | (require 'cl) 114 | (require 'slime) 115 | (require 'which-func) 116 | 117 | ;; Faces 118 | 119 | (defface clojure-test-failure-face 120 | '((((class color) (background light)) 121 | :background "orange red") ;; TODO: Hard to read strings over this. 122 | (((class color) (background dark)) 123 | :background "firebrick")) 124 | "Face for failures in Clojure tests." 125 | :group 'clojure-test-mode) 126 | 127 | (defface clojure-test-error-face 128 | '((((class color) (background light)) 129 | :background "orange1") 130 | (((class color) (background dark)) 131 | :background "orange4")) 132 | "Face for errors in Clojure tests." 133 | :group 'clojure-test-mode) 134 | 135 | (defface clojure-test-success-face 136 | '((((class color) (background light)) 137 | :foreground "black" 138 | :background "green") 139 | (((class color) (background dark)) 140 | :foreground "black" 141 | :background "green")) 142 | "Face for success in Clojure tests." 143 | :group 'clojure-test-mode) 144 | 145 | ;; Counts 146 | 147 | (defvar clojure-test-count 0) 148 | (defvar clojure-test-failure-count 0) 149 | (defvar clojure-test-error-count 0) 150 | 151 | ;; Consts 152 | 153 | (defconst clojure-test-ignore-results 154 | '(:end-test-ns :begin-test-var :end-test-var) 155 | "Results from test-is that we don't use") 156 | 157 | ;; Support Functions 158 | 159 | (defun clojure-test-eval (string &optional handler) 160 | (slime-eval-async `(swank:eval-and-grab-output ,string) 161 | (or handler #'identity))) 162 | 163 | (defun clojure-test-eval-sync (string) 164 | (slime-eval `(swank:eval-and-grab-output ,string))) 165 | 166 | (defun clojure-test-load-reporting () 167 | "Redefine the test-is report function to store results in metadata." 168 | (when (eq (compare-strings "clojure" 0 7 (slime-connection-name) 0 7) t) 169 | (clojure-test-eval-sync 170 | "(require 'clojure.test) (ns clojure.test) 171 | 172 | (defonce old-report report) 173 | (defn report [event] 174 | (if-let [current-test (last *testing-vars*)] 175 | (alter-meta! current-test 176 | assoc :status (conj (:status (meta current-test)) 177 | [(:type event) (:message event) 178 | (str (:expected event)) 179 | (str (:actual event)) 180 | (if (and (= (:major *clojure-version*) 1) 181 | (< (:minor *clojure-version*) 2)) 182 | ((file-position 2) 1) 183 | (if (= (:type event) :error) 184 | ((file-position 3) 1) 185 | (:line event)))]))) 186 | (binding [*test-out* *out*] 187 | (old-report event))) 188 | 189 | (defn clojure-test-mode-test-one-var [test-ns test-name] 190 | (let [v (ns-resolve test-ns test-name) 191 | once-fixture-fn (join-fixtures (::once-fixtures (meta (find-ns test-ns)))) 192 | each-fixture-fn (join-fixtures (::each-fixtures (meta (find-ns test-ns))))] 193 | (once-fixture-fn 194 | (fn [] 195 | (when (:test (meta v)) 196 | (each-fixture-fn (fn [] (test-var v)))))))) 197 | 198 | ;; adapted from test-ns 199 | (defn clojure-test-mode-test-one-in-ns [ns test-name] 200 | (binding [*report-counters* (ref *initial-report-counters*)] 201 | (let [ns-obj (the-ns ns)] 202 | (do-report {:type :begin-test-ns, :ns ns-obj}) 203 | ;; If the namespace has a test-ns-hook function, call that: 204 | (if-let [v (find-var (symbol (str (ns-name ns-obj)) \"test-ns-hook\"))] 205 | ((var-get v)) 206 | ;; Otherwise, just test every var in the namespace. 207 | (clojure-test-mode-test-one-var ns test-name)) 208 | (do-report {:type :end-test-ns, :ns ns-obj})) 209 | (do-report (assoc @*report-counters* :type :summary)))) "))) 210 | 211 | (defun clojure-test-get-results (result) 212 | (clojure-test-eval 213 | (concat "(map #(cons (str (:name (meta %))) 214 | (:status (meta %))) (vals (ns-interns '" 215 | (slime-current-package) ")))") 216 | #'clojure-test-extract-results)) 217 | 218 | (defun clojure-test-echo-results () 219 | (message 220 | (propertize 221 | (format "Ran %s tests. %s failures, %s errors." 222 | clojure-test-count clojure-test-failure-count 223 | clojure-test-error-count) 224 | 'face 225 | (cond ((not (= clojure-test-error-count 0)) 'clojure-test-error-face) 226 | ((not (= clojure-test-failure-count 0)) 'clojure-test-failure-face) 227 | (t 'clojure-test-success-face))))) 228 | 229 | (defun clojure-test-extract-results (results) 230 | (let ((result-vars (read (cadr results)))) 231 | ;; slime-eval-async hands us a cons with a useless car 232 | (mapc #'clojure-test-extract-result result-vars) 233 | (slime-repl-emit (concat "\n" (make-string (1- (window-width)) ?=) "\n")) 234 | (clojure-test-echo-results))) 235 | 236 | (defun clojure-test-extract-result (result) 237 | "Parse the result from a single test. May contain multiple is blocks." 238 | (dolist (is-result (rest result)) 239 | (unless (member (aref is-result 0) clojure-test-ignore-results) 240 | (incf clojure-test-count) 241 | (destructuring-bind (event msg expected actual line) (coerce is-result 'list) 242 | (if (equal :fail event) 243 | (progn (incf clojure-test-failure-count) 244 | (clojure-test-highlight-problem 245 | line event (format "Expected %s, got %s" expected actual))) 246 | (when (equal :error event) 247 | (incf clojure-test-error-count) 248 | (clojure-test-highlight-problem line event actual))))))) 249 | 250 | 251 | (defun clojure-test-highlight-problem (line event message) 252 | (save-excursion 253 | (goto-char (point-min)) (forward-line (1- line)) 254 | (let ((beg (point))) 255 | (end-of-line) 256 | (let ((overlay (make-overlay beg (point)))) 257 | (overlay-put overlay 'face (if (equal event :fail) 258 | 'clojure-test-failure-face 259 | 'clojure-test-error-face)) 260 | (overlay-put overlay 'message message))))) 261 | 262 | ;; Problem navigation 263 | (defun clojure-test-find-next-problem (here) 264 | "Go to the next position with an overlay message. 265 | Retuns the problem overlay if such a position is found, otherwise nil." 266 | (let ((current-overlays (overlays-at here)) 267 | (next-overlays (next-overlay-change here))) 268 | (while (and (not (equal next-overlays (point-max))) 269 | (or 270 | (not (overlays-at next-overlays)) 271 | (equal (overlays-at next-overlays) 272 | current-overlays))) 273 | (setq next-overlays (next-overlay-change next-overlays))) 274 | (if (not (equal next-overlays (point-max))) 275 | (overlay-start (car (overlays-at next-overlays)))))) 276 | 277 | (defun clojure-test-find-previous-problem (here) 278 | "Go to the next position with the `clojure-test-problem' text property. 279 | Retuns the problem overlay if such a position is found, otherwise nil." 280 | (let ((current-overlays (overlays-at here)) 281 | (previous-overlays (previous-overlay-change here))) 282 | (while (and (not (equal previous-overlays (point-min))) 283 | (or 284 | (not (overlays-at previous-overlays)) 285 | (equal (overlays-at previous-overlays) 286 | current-overlays))) 287 | (setq previous-overlays (previous-overlay-change previous-overlays))) 288 | (if (not (equal previous-overlays (point-min))) 289 | (overlay-start (car (overlays-at previous-overlays)))))) 290 | 291 | ;; File navigation 292 | 293 | (defun clojure-test-implementation-for (namespace) 294 | (let* ((namespace (clojure-underscores-for-hyphens namespace)) 295 | (segments (split-string namespace "\\.")) 296 | (test-position 297 | (if (> 0 clojure-test-ns-segment-position) 298 | (1- (+ (length segments) clojure-test-ns-segment-position)) 299 | clojure-test-ns-segment-position)) 300 | (before (subseq segments 0 test-position)) 301 | (after (subseq segments (1+ test-position))) 302 | (impl-segments (append before after))) 303 | (mapconcat 'identity impl-segments "/"))) 304 | 305 | ;; Commands 306 | 307 | (defun clojure-test-run-tests () 308 | "Run all the tests in the current namespace." 309 | (interactive) 310 | (save-some-buffers nil (lambda () (equal major-mode 'clojure-mode))) 311 | (message "Testing...") 312 | (clojure-test-clear 313 | (lambda (&rest args) 314 | ;; clojure-test-eval will wrap in with-out-str 315 | (slime-eval-async `(swank:load-file 316 | ,(slime-to-lisp-filename 317 | (expand-file-name (buffer-file-name)))) 318 | (lambda (&rest args) 319 | (slime-eval-async '(swank:interactive-eval 320 | "(clojure.test/run-tests)") 321 | #'clojure-test-get-results)))))) 322 | 323 | ;; TODO: run tests in region 324 | (defun clojure-test-run-test () 325 | "Run the test at point." 326 | (interactive) 327 | (save-some-buffers nil (lambda () (equal major-mode 'clojure-mode))) 328 | (clojure-test-clear 329 | (lambda (&rest args) 330 | (let* ((f (which-function)) 331 | (test-name (if (listp f) (first f) f))) 332 | (slime-eval-async 333 | `(swank:interactive-eval 334 | ,(format "(do (load-file \"%s\") 335 | (clojure-test-mode-test-one-in-ns '%s '%s) 336 | (cons (:name (meta (var %s))) (:status (meta (var %s)))))" 337 | (buffer-file-name) 338 | (slime-current-package) test-name 339 | test-name test-name)) 340 | (lambda (result-str) 341 | (let ((result (read result-str))) 342 | (if (cdr result) 343 | (clojure-test-extract-result result) 344 | (message "Not in a test."))))))))) 345 | 346 | (defun clojure-test-show-result () 347 | "Show the result of the test under point." 348 | (interactive) 349 | (let ((overlay (find-if (lambda (o) (overlay-get o 'message)) 350 | (overlays-at (point))))) 351 | (if overlay 352 | (message (replace-regexp-in-string "%" "%%" 353 | (overlay-get overlay 'message)))))) 354 | 355 | (defun clojure-test-clear (&optional callback) 356 | "Remove overlays and clear stored results." 357 | (interactive) 358 | (remove-overlays) 359 | (setq clojure-test-count 0 360 | clojure-test-failure-count 0 361 | clojure-test-error-count 0) 362 | (clojure-test-eval 363 | "(doseq [t (vals (ns-interns *ns*))] 364 | (alter-meta! t assoc :status []) 365 | (alter-meta! t assoc :test nil))" 366 | callback)) 367 | 368 | (defun clojure-test-next-problem () 369 | "Go to and describe the next test problem in the buffer." 370 | (interactive) 371 | (let* ((here (point)) 372 | (problem (clojure-test-find-next-problem here))) 373 | (if problem 374 | (goto-char problem) 375 | (goto-char here) 376 | (message "No next problem.")))) 377 | 378 | (defun clojure-test-previous-problem () 379 | "Go to and describe the previous compiler problem in the buffer." 380 | (interactive) 381 | (let* ((here (point)) 382 | (problem (clojure-test-find-previous-problem here))) 383 | (if problem 384 | (goto-char problem) 385 | (goto-char here) 386 | (message "No previous problem.")))) 387 | 388 | (defun clojure-test-jump-to-implementation () 389 | "Jump from test file to implementation." 390 | (interactive) 391 | (find-file (format "%s/src/%s.clj" 392 | (locate-dominating-file buffer-file-name "src/") 393 | (clojure-test-implementation-for (clojure-find-package))))) 394 | 395 | (defvar clojure-test-mode-map 396 | (let ((map (make-sparse-keymap))) 397 | (define-key map (kbd "C-c C-,") 'clojure-test-run-tests) 398 | (define-key map (kbd "C-c ,") 'clojure-test-run-tests) 399 | (define-key map (kbd "C-c M-,") 'clojure-test-run-test) 400 | (define-key map (kbd "C-c C-'") 'clojure-test-show-result) 401 | (define-key map (kbd "C-c '") 'clojure-test-show-result) 402 | (define-key map (kbd "C-c k") 'clojure-test-clear) 403 | (define-key map (kbd "C-c t") 'clojure-test-jump-to-implementation) 404 | (define-key map (kbd "M-p") 'clojure-test-previous-problem) 405 | (define-key map (kbd "M-n") 'clojure-test-next-problem) 406 | map) 407 | "Keymap for Clojure test mode.") 408 | 409 | ;;;###autoload 410 | (define-minor-mode clojure-test-mode 411 | "A minor mode for running Clojure tests." 412 | nil " Test" clojure-test-mode-map 413 | (when (slime-connected-p) 414 | (clojure-test-load-reporting))) 415 | 416 | (add-hook 'slime-connected-hook 'clojure-test-load-reporting) 417 | 418 | ;;;###autoload 419 | (progn 420 | (defun clojure-test-maybe-enable () 421 | "Enable clojure-test-mode if the current buffer contains a namespace 422 | with a \"test.\" bit on it." 423 | (let ((ns (clojure-find-package))) ; defined in clojure-mode.el 424 | (when (search "test." ns) 425 | (save-window-excursion 426 | (clojure-test-mode t))))) 427 | (add-hook 'clojure-mode-hook 'clojure-test-maybe-enable)) 428 | 429 | (provide 'clojure-test-mode) 430 | ;;; clojure-test-mode.el ends here 431 | -------------------------------------------------------------------------------- /test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-mode.test 2 | (:use [clojure.test])) 3 | 4 | (deftest test-str 5 | (is (= "o hai" (str "o" "hai")))) 6 | 7 | (deftest test-errs 8 | (is (({} :hi))) 9 | (is (str "This one doesn't actually error.")) 10 | (is (= 0 (/ 9 0)))) 11 | 12 | (deftest test-bad-math 13 | (is (= 0 (* 8 2))) 14 | (is (= 5 (+ 2 2)))) 15 | 16 | (deftest test-something-that-actually-works 17 | (is (= 1 1))) 18 | 19 | ;; For debugging 20 | ;; (map #(cons (str (:name (meta %))) (:status (meta %))) (vals (ns-interns *ns*))) 21 | ;; (insert (pp the-result)) 22 | 23 | (comment ;; for indentation 24 | (with-hi heya 25 | somebuddy) 26 | 27 | (deftoggle cap 28 | gabba) 29 | 30 | (couch/with-db hi 31 | your-db) 32 | 33 | (clo/defguppy gurgle 34 | minnow)) --------------------------------------------------------------------------------